Notepad++ vertical block select copy paste not retained across clips - clipboard

I'm using Notepad++ to vertically select and copy blocks of text. Pasting the latest block of text (that is currently in the clipboard buffer) works fine, however going back to the second and third previous clipboard buffers (which were originally copied as blocks) does not paste these other buffers as blocks, but rather as a first line followed by newline, second line newline, etc, etc.
For example, suppose I block copy the following block
test
test
test
The latest clipboard buffer pastes as
test
test
test
assuming the cursor is 4 spaces indented.
However, if the latest clipboard buffer is moved to the second place in the clipboard buffer queue, I get the following paste:
test
test
test
I'm using ClipMate to store previous clipboard buffers.
Why does notepad++ know to block-paste the latest clipboard text data (copied as a block) but not the second to last clipboard buffer?
Is there a way to store a clipboard clips' block state?

It looks like Notepad++ is pasting from an internal buffer, and not taking the data from the clipboard. There IS a private data format called MSDEVColumnSelect, but if I try to force ClipMate to capture it, the data is empty. So this seems to be a case where the application is playing smoke-and-mirrors games with copy/paste, and it's not really a clipboard feature at all.

I've written a solution using the Python Script Scintilla wrapper notepad++ plugin.
Use this for copy.
Save as C:\Program Files\Notepad++\plugins\PythonScript\scripts\Samples\Copy.py
Map to shortcuts Ctrl+c and Ctrl+INS
# $Revision: 1.2 $
# $Author: dot $
# $Date: 2012/03/23 20:59:46 $
from Npp import *
import string
# First we'll start an undo action, then Ctrl+z will undo the actions of the whole script
editor.beginUndoAction()
if editor.getSelText() != '':
strClip = ""
if editor.selectionIsRectangle():
strClip = "<vertical>"+editor.getSelText()+"</vertical>"
else:
strClip = editor.getSelText()
editor.copyText(strClip)
# End the undo action, so Ctrl+z will undo the above two actions
editor.endUndoAction()
Use this for cut.
Save as C:\Program Files\Notepad++\plugins\PythonScript\scripts\Samples\Cut.py
Map to shortcuts Ctrl+x and Shift+DEL
# $Revision: 1.2 $
# $Author: dot $
# $Date: 2012/03/23 20:59:46 $
from Npp import *
import string
# First we'll start an undo action, then Ctrl+z will undo the actions of the whole script
editor.beginUndoAction()
if editor.getSelText() != '':
strClip = ""
if editor.selectionIsRectangle():
strClip = "<vertical>"+editor.getSelText()+"</vertical>"
else:
strClip = editor.getSelText()
editor.copyText(strClip)
editor.clear()
# End the undo action, so Ctrl+z will undo the above two actions
editor.endUndoAction()
Now download pyperclip.py to C:\Program Files\Notepad++\plugins\PythonScript\lib
Use this for paste.
Save as C:\Program Files\Notepad++\plugins\PythonScript\scripts\Samples\Paste.py
Map to shortcuts Ctrl+v and Shift+INS
# $Revision: 1.11 $
# $Author: dot $
# $Date: 2012/05/18 22:22:22 $
from Npp import *
import pyperclip
import string
#debug = True
debug = False
# First we'll start an undo action, then Ctrl-z will undo the actions of the whole script
editor.beginUndoAction()
# Get the clip
clip = pyperclip.getcb()
# Debug
if debug:
bufferID = notepad.getCurrentBufferID()
# Show console for debugging
console.clear()
console.show()
console.write( "editor.getRectangularSelectionCaret() = " + str(editor.getRectangularSelectionCaret() ) + "\n")
console.write( "editor.getRectangularSelectionAnchor() = " + str(editor.getRectangularSelectionAnchor()) + "\n")
console.write( "editor.getSelectionStart() = " + str(editor.getSelectionStart() ) + "\n")
console.write( "editor.getSelectionEnd() = " + str(editor.getSelectionEnd() ) + "\n")
console.write( "editor.getCurrentPos() = " + str(editor.getCurrentPos() ) + "\n")
console.write( "editor.getAnchor() = " + str(editor.getAnchor() ) + "\n")
console.write( "editor.getRectangularSelectionAnchorVirtualSpace() = " + str(editor.getRectangularSelectionAnchorVirtualSpace()) + "\n")
console.write( "editor.getRectangularSelectionCaretVirtualSpace() = " + str(editor.getRectangularSelectionCaretVirtualSpace() ) + "\n")
console.write( "editor.getSelectionNCaretVirtualSpace(0) = " + str(editor.getSelectionNCaretVirtualSpace(0) ) + "\n")
console.write( "editor.getSelectionNAnchorVirtualSpace(0) = " + str(editor.getSelectionNAnchorVirtualSpace(0) ) + "\n")
if editor.getRectangularSelectionCaret() == -1 and \
editor.getRectangularSelectionAnchor() == -1 and \
editor.getSelectionStart() == 0 and \
editor.getSelectionEnd() == 0 and \
editor.getCurrentPos() == 0 and \
editor.getAnchor() == 0 and \
editor.getRectangularSelectionAnchorVirtualSpace() == 0 and \
editor.getRectangularSelectionCaretVirtualSpace() == 0 and \
editor.getSelectionNCaretVirtualSpace(0) == 0 and \
editor.getSelectionNAnchorVirtualSpace(0) == 0:
currentPos = editor.getCurrentPos()
# Debug
if debug:
console.write( "state 0\n")
if editor.getRectangularSelectionCaret() != 0 and editor.getRectangularSelectionAnchor() != 0:
if editor.getSelectionStart() == editor.getRectangularSelectionCaret() and \
editor.getSelectionEnd() == editor.getRectangularSelectionAnchor():
# Debug
if debug:
console.write( "state 1\n" )
currentPos = min(editor.getRectangularSelectionCaret(),editor.getRectangularSelectionAnchor())
elif editor.getSelectionStart() < editor.getRectangularSelectionCaret():
# Debug
if debug:
console.write( "state 2\n")
currentPos = editor.getSelectionStart()
elif editor.getSelectionStart() == editor.getRectangularSelectionCaret() and \
editor.getSelectionEnd() > editor.getRectangularSelectionAnchor():
currentPos = min(editor.getRectangularSelectionCaret(),editor.getRectangularSelectionAnchor())
# Debug
if debug:
console.write( "state 3\n")
elif editor.getCurrentPos() != 0 and editor.getAnchor() != 0:
# Debug
if debug:
console.write( "state 4\n")
currentPos = min(editor.getCurrentPos(),editor.getAnchor())
elif editor.getSelectionStart() == editor.getRectangularSelectionCaret() and \
editor.getSelectionEnd() > editor.getRectangularSelectionAnchor():
# Debug
if debug:
console.write( "state 5\n")
currentPos = min(editor.getRectangularSelectionCaret(),editor.getRectangularSelectionAnchor())
else:
currentPos = editor.getCurrentPos()
# Debug
if debug:
console.write( "state 6\n")
# Debug
if debug:
console.write( "currentPos = " + str(currentPos) + "\n")
if editor.getRectangularSelectionAnchorVirtualSpace() != editor.getRectangularSelectionCaretVirtualSpace() and \
( editor.getRectangularSelectionAnchorVirtualSpace() == editor.getSelectionNCaretVirtualSpace(0) and \
editor.getSelectionNCaretVirtualSpace(0) == editor.getSelectionNAnchorVirtualSpace(0) ):
prefix = editor.getRectangularSelectionCaretVirtualSpace()
# Debug
if debug:
console.write( "state 7\n")
else:
prefix = min(editor.getSelectionNCaretVirtualSpace(0),editor.getSelectionNAnchorVirtualSpace(0))
# Debug
if debug:
console.write( "state 8\n")
# Debug
if debug:
console.write( "prefix = " + str(prefix) + "\n")
prefixSpaces = "".ljust(prefix,' ')
eolmode = editor.getEOLMode()
# SC_EOL_CRLF (0), SC_EOL_CR (1), or SC_EOL_LF (2)
if eolmode == 0:
eol = "\r\n"
eolcnt = 2
elif eolmode == 1:
eol = '\r'
eolcnt = 1
elif eolmode == 2:
eol = "\n"
eolcnt = 1
if prefix > 0:
if currentPos < editor.getCurrentPos():
editor.insertText(editor.getLineEndPosition(editor.lineFromPosition(currentPos)),prefixSpaces)
editor.gotoPos(editor.getLineEndPosition(editor.lineFromPosition(currentPos+prefix)))
start = currentPos+prefix
# Debug
if debug:
console.write( "state 9\n")
else:
editor.insertText(editor.getLineEndPosition(editor.lineFromPosition(editor.getCurrentPos())),prefixSpaces)
editor.gotoPos(editor.getLineEndPosition(editor.lineFromPosition(editor.getCurrentPos())))
start = editor.getCurrentPos()
# Debug
if debug:
console.write( "state 10\n")
else:
start = currentPos
# Debug
if debug:
console.write( "state 11\n")
# Debug
if debug:
console.write( "start = " + str(start) + "\n")
if clip != "":
if editor.getSelectionStart() != editor.getSelectionEnd() and \
( editor.getColumn(editor.getSelectionStart()) != editor.getColumn(editor.getSelectionEnd()) or \
( editor.getColumn(editor.getSelectionStart()) == editor.getColumn(editor.getSelectionEnd()) and \
editor.getColumn(editor.getSelectionStart()) == 0 ) ) and \
prefix == 0:
editor.clear()
# Debug
if debug:
console.write( "state 12\n")
# We are dealing with a vertical paste
if clip.startswith("<vertical>") and clip.endswith("</vertical>"):
clip = clip[10:-11]
startCol = editor.getColumn(start)
startRow = editor.lineFromPosition(start)
# Debug
if debug:
console.write( "startCol = " + str(startCol) + "\n")
# Debug
if debug:
console.write( "startRow = " + str(startRow) + "\n")
# keepends = False
clipSplit = clip.splitlines(False)
clipSplitLen = len(clipSplit)
for index,line in enumerate(clipSplit):
if index == 0:
localPrefixSpaces = ""
elif index == (clipSplitLen-1):
localPrefixSpaces = prefixSpaces
else:
localPrefixSpaces = prefixSpaces
try:
editorLine = editor.getLine(startRow+index).strip(eol)
editorLineLen = len(editorLine)
# Empty line
if editorLineLen == 0:
editor.insertText(editor.positionFromLine(startRow+index),"".ljust(startCol,' '))
editor.insertText(editor.findColumn(startRow+index,startCol),line)
else:
if editorLineLen < startCol:
editor.insertText(editor.getLineEndPosition(startRow+index),"".ljust(startCol-editorLineLen,' '))
editor.insertText(editor.findColumn(startRow+index,startCol),line)
# End of file
except IndexError:
editor.documentEnd()
editor.appendText(eol)
editor.appendText("".ljust(startCol,' ') + line)
editor.setCurrentPos(start)
editor.setSelection(start,start)
# We are dealing with a horizontal paste
else:
editor.insertText(start, clip)
editor.setCurrentPos(start + len(clip))
editor.setSelection(start + len(clip),start + len(clip))
# End the undo action, so Ctrl-z will undo the above two actions
editor.endUndoAction()
# Debug
if debug:
notepad.activateBufferID(bufferID)
This assumes that VirtualSpaceOptions are set to 3.
Modify file C:\projects\misc\PythonScriptNppPlugin\startup.py to the following
code below and ensure it runs on every notepad++ start.
# $Revision: 1.2 $
# $Author: dot $
# $Date: 2012/03/23 20:59:46 $
# The lines up to and including sys.stderr should always come first
# Then any errors that occur later get reported to the console
# If you'd prefer to report errors to a file, you can do that instead here.
import sys
from Npp import *
# Set the stderr to the normal console as early as possible, in case of early errors
sys.stderr = console
# Define a class for writing to the console in red
class ConsoleError:
def __init__(self):
global console
self._console = console;
def write(self, text):
self._console.writeError(text);
def flush(self):
pass
# Set the stderr to write errors in red
sys.stderr = ConsoleError()
# This imports the "normal" functions, including "help"
import site
# See docs
# http://npppythonscript.sourceforge.net/docs/latest/intro.html
# http://npppythonscript.sourceforge.net/docs/latest/scintilla.html
def set_word_chars(args):
# Enable the virtual space options for both Scintilla views
# For more information, see the Scintilla documentation on virtual space and the SCI_SETVIRTUALSPACEOPTIONS message.
editor.setVirtualSpaceOptions(3)
# Set the word characters
editor.setWordChars('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$')
notepad.callback(set_word_chars, [NOTIFICATION.BUFFERACTIVATED])
# This sets the stdout to be the currently active document, so print "hello world",
# will insert "hello world" at the current cursor position of the current document
sys.stdout = editor
editor.setVirtualSpaceOptions(3)
# Set the word characters
editor.setWordChars('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$')
#console.show()

Related

extracting vdate form nc header with bash without opening file

I have to change the title of a couple of hundred files by adding the vdate from its header to its title.
If vdate = 19971222, then I want the name of that nc file to become rerun4_spindown_19971222.nc
I know I can find the vdate by ncdump -h filename (see example header below).
ncdump -h rerun4_1997_spindown_09191414_co2
netcdf rerun4_1997_spindown_09191414_co2 {
dimensions:
lon = 768 ;
lat = 384 ;
nhgl = 192 ;
nlevp1 = 96 ;
spc = 32896 ;
// global attributes:
:file_type = "Restart history file" ;
:source_type = "IEEE" ;
:history = "" ;
:user = " Linda" ;
:created = " Date - 20190919 Time - 134447" ;
:label_1 = " Atmospheric model " ;
:label_2 = " Library 23-Feb-2012" ;
:label_3 = " Lin & Rood ADVECTION is default" ;
:label_4 = " Modified physics" ;
:label_5 = " Modified radiation" ;
:label_6 = " Date - 20190919 Time - 134447" ;
:label_7 = " Linda " ;
:label_8 = " Linux " ;
:fdate = 19950110 ;
:ftime = 0 ;
:vdate = 19971222 ;
:vtime = 235800 ;
:nstep = 776158 ;
:timestep = 120. ;
However, then I have to manually open all the files and manually change the title of the file... of hundreds of files. I would prefer making a bash that can automatically do that.
I am sure there must be a more intelligent way to extract the vdate from the nc header, could you guys help me out?
Thank you!
In theory, something like that should work:
#! /bin/sh
for file in rerun4_*_spindown_* ; do
vdate=$(ncdump -h $file | awk '$1 == ":vdate" { print $3 }')
new_name="rerun4_spindown_$vdate.nc"
mv "$file" "$new_name"
done
I do not have access to netCDF files - more testing is needed.

Local `commit-msg` doesn't work in conjunction with global `prepare-commit-msg`

When I have my global git hooks directory (which contains just a prepare-commit-msg hook) set up in config, my local commit-msg doesn't run (although the global hook does). However, when I disable the global prepare-commit-msg hook (by commenting out core.hookspath in gitconfig), the local commit-msg hook works just fine.
~/dotfiles/git-hooks/prepare-commit-msg
#!/usr/bin/env bash
pcregrep -Mv '(# Please.*|# with.*|^#$\n(?!#)|^#$(?=\n# On))' $1 > /tmp/msg && cat /tmp/msg > $1
./.git/hooks/commit-msg (Gerrit's hook to add change-id's if necessary, trimmed to remove license comments)
#!/bin/sh
...
unset GREP_OPTIONS
CHANGE_ID_AFTER="Bug|Depends-On|Issue|Test|Feature|Fixes|Fixed"
MSG="$1"
# Check for, and add if missing, a unique Change-Id
#
add_ChangeId() {
clean_message=`sed -e '
/^diff --git .*/{
s///
q
}
/^Signed-off-by:/d
/^#/d
' "$MSG" | git stripspace`
if test -z "$clean_message"
then
return
fi
# Do not add Change-Id to temp commits
if echo "$clean_message" | head -1 | grep -q '^\(fixup\|squash\)!'
then
return
fi
if test "false" = "`git config --bool --get gerrit.createChangeId`"
then
return
fi
# Does Change-Id: already exist? if so, exit (no change).
if grep -i '^Change-Id:' "$MSG" >/dev/null
then
return
fi
id=`_gen_ChangeId`
T="$MSG.tmp.$$"
AWK=awk
if [ -x /usr/xpg4/bin/awk ]; then
# Solaris AWK is just too broken
AWK=/usr/xpg4/bin/awk
fi
# Get core.commentChar from git config or use default symbol
commentChar=`git config --get core.commentChar`
commentChar=${commentChar:-#}
# How this works:
# - parse the commit message as (textLine+ blankLine*)*
# - assume textLine+ to be a footer until proven otherwise
# - exception: the first block is not footer (as it is the title)
# - read textLine+ into a variable
# - then count blankLines
# - once the next textLine appears, print textLine+ blankLine* as these
# aren't footer
# - in END, the last textLine+ block is available for footer parsing
$AWK '
BEGIN {
if (match(ENVIRON["OS"], "Windows")) {
RS="\r?\n" # Required on recent Cygwin
}
# while we start with the assumption that textLine+
# is a footer, the first block is not.
isFooter = 0
footerComment = 0
blankLines = 0
}
# Skip lines starting with commentChar without any spaces before it.
/^'"$commentChar"'/ { next }
# Skip the line starting with the diff command and everything after it,
# up to the end of the file, assuming it is only patch data.
# If more than one line before the diff was empty, strip all but one.
/^diff --git / {
blankLines = 0
while (getline) { }
next
}
# Count blank lines outside footer comments
/^$/ && (footerComment == 0) {
blankLines++
next
}
# Catch footer comment
/^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
footerComment = 1
}
/]$/ && (footerComment == 1) {
footerComment = 2
}
# We have a non-blank line after blank lines. Handle this.
(blankLines > 0) {
print lines
for (i = 0; i < blankLines; i++) {
print ""
}
lines = ""
blankLines = 0
isFooter = 1
footerComment = 0
}
# Detect that the current block is not the footer
(footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
isFooter = 0
}
{
# We need this information about the current last comment line
if (footerComment == 2) {
footerComment = 0
}
if (lines != "") {
lines = lines "\n";
}
lines = lines $0
}
# Footer handling:
# If the last block is considered a footer, splice in the Change-Id at the
# right place.
# Look for the right place to inject Change-Id by considering
# CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
# then Change-Id, then everything else (eg. Signed-off-by:).
#
# Otherwise just print the last block, a new line and the Change-Id as a
# block of its own.
END {
unprinted = 1
if (isFooter == 0) {
print lines "\n"
lines = ""
}
changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
numlines = split(lines, footer, "\n")
for (line = 1; line <= numlines; line++) {
if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
unprinted = 0
print "Change-Id: I'"$id"'"
}
print footer[line]
}
if (unprinted) {
print "Change-Id: I'"$id"'"
}
}' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
}
_gen_ChangeIdInput() {
echo "tree `git write-tree`"
if parent=`git rev-parse "HEAD^0" 2>/dev/null`
then
echo "parent $parent"
fi
echo "author `git var GIT_AUTHOR_IDENT`"
echo "committer `git var GIT_COMMITTER_IDENT`"
echo
printf '%s' "$clean_message"
}
_gen_ChangeId() {
_gen_ChangeIdInput |
git hash-object -t commit --stdin
}
add_ChangeId
~/dotfiles/gitconfig (trimmed) when global hook is enabled.
...
[core]
editor = code -rwg $1:2
excludesfile = /Users/shreyasminocha/.gitignore
compression = 0
hookspath = /Users/shreyasminocha/dotfiles/git-hooks
...
Git version: 2.18.0
Edit: As #phd pointed out in the comments, "The problem is that global hookspath completely takes over local hooks. If the global hookspath is defined local hooks are never consulted. [I had] to create global /Users/shreyasminocha/dotfiles/git-hooks/commit-msg that [would] run local .git/hooks/commit-msg". Confirming duplicate.
Eventually solved this by adding some code to the global hook, just as #phd suggested. It looks for a local hook and runs it if it exists.
Global prepare-commit-msg hook:
#!/usr/bin/env bash
# if a local hook exists, run it
if [ -e ./.git/hooks/prepare-commit-msg ]; then
./.git/hooks/prepare-commit-msg "$#"
fi
if [ -e ./.git/hooks/commit-msg ]; then
./.git/hooks/commit-msg "$#"
fi
# global_hook-related things follow
pcregrep -Mv '(# Please.*|# with.*|^#$\n(?!#)|^#$(?=\n# On))' $1 > /tmp/msg && cat /tmp/msg > $1

./count:78: syntax error, unexpected keyword_end, expecting end-of-input

I modified my post and managed to copy/paste the code here. I know it's ugly but really hope it's going to help to find the error.
Error message is: "./v_c:2:in ': undefined local variable or method  ' for main:Object (NameError)"
#!/usr/bin/env ruby
 
#
# This program is used for collecting web server visit information.
#
# Author: A. Genius
#
 
require 'optparse'
 
def print_usage
puts "USAGE: v_c -d DNS_NAME"
exit
end
 
# add option switch and handler
 
options = {}
 
option_parser = OptionParser.new do |opts|
   
# DNS_NAME argument
 options[:dns_name] = nil
opts.on('-d', '--dns-name DNS_NAME', 'Specify a DNS NAME') { |dns_name| options[:dns_name] = dns_name }
   
# HELP argument
options[:help] = nil
opts.on('-h', '--help', 'Display usage') { |help| options[:help] = help }
end
 
option_parser.parse!
 
# verify arguments
 
if options[:dns_name] then
dns_name = options[:dns_name]
else
  puts "Please set a balancer's DNS."
  print_usage
  exit
end
 
if options[:help] then
print_usage
exit
end
 
# Keep STDOUT
# orig_stdout = $stdout
 
# redirect stdout to /dev/null
# $stdout = File.new('/dev/null', 'w')
 
server1_visit_count = 0
server2_visit_count = 0
server3_visit_count = 0
server4_visit_count = 0
 
# starting to visit load balancing server
 
puts "Starting to visit load balancing server"
2000.times do
# visit load balancer
# o = "curl #{dns_name}"
o = "curl -s #{dns_name}"
if o =~ /server\s*1/i
server1_visit_count += 1
elsif o =~ /server\s*2/i
server2_visit_count += 1
elsif o =~ /server\s*3/i
server3_visit_count += 1
elsif o =~ /server\s*4/i
server4_visit_count += 1
end
print "."
end
puts
puts '-------------------------'
puts ' Summary'
puts '-------------------------'
puts "Server1 visit counts : " + server1_visit_count.to_s
puts "Server2 visit counts : " + server2_visit_count.to_s
puts "Server3 visit counts : " + server3_visit_count.to_s
puts "Server4 visit counts : " + server4_visit_count.to_s
puts "total is: " + (server1_visit_count + server2_visit_count +
server3_visit_count + server4_visit_count).to_s
Oh... String interpolation, once again... Replace this line
#o = 'curl #{dns_name}' with:
#o = "curl #{dns_name}"
I explained why it was the problem below.
You can see from editor that end is not recognised. Maybe you copy/pasted some code before and some characters (like quotation marks) are not the same format as your editor uses. So rewriting this short peace of code might save your ###. And paste whats rewritten here :)
elsif writes as a single word, but Ruby probably understands it, since no error I got using it this way..
String interpolation is used with double quotes
'curl -s #{dns}'
Supposed to be:
"curl -s #{dns}"
(Expect you really want to output this as "curl -s #{dns}")
Was the problem one of these?
I just pasted this into repl.it and it works well. Please check if it meets your code (I have added a value to dns_name variable since I do not have it defined in my case):
dns_name = 10
10.times do
# visit load balancer
#o = "curl #{dns_name}"
o = "curl -s #{dns_name}"
if o =~ /server\s*1/i
server1_visit_count += 1
elsif o =~ /server\s*2/i
server2_visit_count += 1
elsif o =~ /server\s*3/i
server3_visit_count += 1
elsif o =~ /server\s*4/i
server4_visit_count += 1
end
print "."
end
Try to copy/paste this and tell if any errors:
#!/usr/bin/env ruby
 
#
# This program is used for collecting web server visit information.
#
# Author: A. Genius
#
 
require 'optparse'
 
def print_usage
puts "USAGE: v_c -d DNS_NAME"
exit
end
 
# add option switch and handler
 
options = {}
 
option_parser = OptionParser.new do |opts|
   
# DNS_NAME argument
 options[:dns_name] = nil
opts.on('-d', '--dns-name DNS_NAME', 'Specify a DNS NAME') { |dns_name| options[:dns_name] = dns_name }
   
# HELP argument
options[:help] = nil
opts.on('-h', '--help', 'Display usage') { |help| options[:help] = help }
end
 
option_parser.parse!
 
# verify arguments
 
if options[:dns_name] then
dns_name = options[:dns_name]
else
  puts "Please set a balancer's DNS."
  print_usage
  exit
end
 
if options[:help] then
print_usage
exit
end
 
# Keep STDOUT
# orig_stdout = $stdout
 
# redirect stdout to /dev/null
# $stdout = File.new('/dev/null', 'w')
 
server1_visit_count = 0
server2_visit_count = 0
server3_visit_count = 0
server4_visit_count = 0
 
# starting to visit load balancing server
 
puts "Starting to visit load balancing server"
2000.times do
# visit load balancer
# o = "curl #{dns_name}"
o = "curl -s #{dns_name}"
if o =~ /server\s*1/i
server1_visit_count += 1
elsif o =~ /server\s*2/i
server2_visit_count += 1
elsif o =~ /server\s*3/i
server3_visit_count += 1
elsif o =~ /server\s*4/i
server4_visit_count += 1
end
print "."
end
puts
puts '-------------------------'
puts ' Summary'
puts '-------------------------'
puts "Server1 visit counts : " + server1_visit_count.to_s
puts "Server2 visit counts : " + server2_visit_count.to_s
puts "Server3 visit counts : " + server3_visit_count.to_s
puts "Server4 visit counts : " + server4_visit_count.to_s
puts "total is: " + (server1_visit_count + server2_visit_count + server3_visit_count + server4_visit_count).to_s

How to read the output AND the return value of a external program in shell script?

I'm making a script that reads a tracking code, looks at the results of posting the tracking to a website and prints some messages and has a return value.
Here's part of the python code:
# update return True if there was a change to the .msg file
def update(cod):
msg = extract_msg(cod)
if msg == 'ERROR':
print('ERROR: invalid code\n')
sys.exit(2)
file = open('.msg', "r+")
old_msg = file.read()
if msg == old_msg:
return False
else:
print('Previous message: ' + old_msg)
print('Latest message: ' + msg)
file = overwrite(file, msg)
file.close()
return True
def main(argv):
if len(argv) > 1:
cod_rastr = argv[1]
else:
print("Error: no arg, no code\n")
return -1
# Verify if file exists
if os.path.isfile(".msg") == False:
arq = open('.msg', 'w')
arq.close()
# post() returns the source code of the resulting page of the posted code.
cod = post(cod_rastr)
if update(cod) == False:
return 0
else:
print ('\n Message!\n')
return 1
And here, I want to read not only the prints (for the final user) but the return values (for conditional use). This script should read the output of the .py and send me an email in case there is an update from the last check (I'll put this script in the crontab):
#!/bin/bash
if [ -z "$1" ]; then
echo usage: $0 CODE
exit
fi
CODE=$1
STATUS=$(myscript.py $CODE 2>&1)
VAL=$?
FILE=$(<.msg)
# always prints 0 (zero)
echo $VAL
# I want to check for an existing update case
if [[ $STATUS == 'Message!' ]]
then
echo $STATUS
echo $FILE | mail myuser#mydomain.com -s '$CODE: Tracking status'
fi
The problem is that $? always returns 0, and my string check inside the if, is not working, because I think It reads the update() prints too, which has variables in the print.
How can I make this shell script run, without changing the python script?
Thanks in advance.
I suspect that you can do what you want with the subprocess module. Either use rc = subprocess.call(...) to get a return code while directing stdout to a file, or use p = subprocess.Popen(...) and then perhaps p.communicate to get output and p.returncode to get the returncode.

What are some good Xcode scripts to speed up development?

Xcode allows you to create automated scripts for performing repetitive tasks. What scripts have you written to speed up development?
I've created three for my JSON.Framework for Cocoa and the iPhone. These take care of the following:
Creates a release Disk Image with dynamic embedded Framework, custom iPhone SDK, API documentation and some documentation files in.
Run Doxygen over the source to create an Xcode-compatible documentation set and install this. This means when you search for things in Xcode's documentation search your documentation can be found too.
Run Doxygen over the source to update a checked-in version of the API documentation in the source tree itself. This is pretty neat if you use Subversion (which it assumes) as the documentation is always up-to-date for the branch you're in. Great if you're hosting on Google Code, for example.
Beware some hard-coded project-specific values in the below. I didn't want to potentially break the scripts by editing those out. These are launched from a Custom Script Phase in Xcode. You can see how they're integrated in the Xcode project for the project linked above.
CreateDiskImage.sh:
#!/bin/sh
set -x
# Determine the project name and version
VERS=$(agvtool mvers -terse1)
# Derived names
VOLNAME=${PROJECT}_${VERS}
DISK_IMAGE=$BUILD_DIR/$VOLNAME
DISK_IMAGE_FILE=$INSTALL_DIR/$VOLNAME.dmg
# Remove old targets
rm -f $DISK_IMAGE_FILE
test -d $DISK_IMAGE && chmod -R +w $DISK_IMAGE && rm -rf $DISK_IMAGE
mkdir -p $DISK_IMAGE
# Create the Embedded framework and copy it to the disk image.
xcodebuild -target JSON -configuration Release install || exit 1
cp -p -R $INSTALL_DIR/../Frameworks/$PROJECT.framework $DISK_IMAGE
IPHONE_SDK=2.2.1
# Create the iPhone SDK directly in the disk image folder.
xcodebuild -target libjson -configuration Release -sdk iphoneos$IPHONE_SDK install \
ARCHS=armv6 \
DSTROOT=$DISK_IMAGE/SDKs/JSON/iphoneos.sdk || exit 1
sed -e "s/%PROJECT%/$PROJECT/g" \
-e "s/%VERS%/$VERS/g" \
-e "s/%IPHONE_SDK%/$IPHONE_SDK/g" \
$SOURCE_ROOT/Resources/iphoneos.sdk/SDKSettings.plist > $DISK_IMAGE/SDKs/JSON/iphoneos.sdk/SDKSettings.plist || exit 1
xcodebuild -target libjson -configuration Release -sdk iphonesimulator$IPHONE_SDK install \
ARCHS=i386 \
DSTROOT=$DISK_IMAGE/SDKs/JSON/iphonesimulator.sdk || exit 1
sed -e "s/%PROJECT%/$PROJECT/g" \
-e "s/%VERS%/$VERS/g" \
-e "s/%IPHONE_SDK%/$IPHONE_SDK/g" \
$SOURCE_ROOT/Resources/iphonesimulator.sdk/SDKSettings.plist > $DISK_IMAGE/SDKs/JSON/iphonesimulator.sdk/SDKSettings.plist || exit 1
# Allow linking statically into normal OS X apps
xcodebuild -target libjson -configuration Release -sdk macosx10.5 install \
DSTROOT=$DISK_IMAGE/SDKs/JSON/macosx.sdk || exit 1
# Copy the source verbatim into the disk image.
cp -p -R $SOURCE_ROOT/Source $DISK_IMAGE/$PROJECT
rm -rf $DISK_IMAGE/$PROJECT/.svn
# Create the documentation
xcodebuild -target Documentation -configuration Release install || exit 1
cp -p -R $INSTALL_DIR/Documentation/html $DISK_IMAGE/Documentation
rm -rf $DISK_IMAGE/Documentation/.svn
cat <<HTML > $DISK_IMAGE/Documentation.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<script type="text/javascript">
<!--
window.location = "Documentation/index.html"
//-->
</script>
</head>
<body>
<p>Aw, shucks! I tried to redirect you to the api documentation but obviously failed. Please find it yourself. </p>
</body>
</html>
HTML
cp -p $SOURCE_ROOT/README $DISK_IMAGE
cp -p $SOURCE_ROOT/Credits.rtf $DISK_IMAGE
cp -p $SOURCE_ROOT/Install.rtf $DISK_IMAGE
cp -p $SOURCE_ROOT/Changes.rtf $DISK_IMAGE
hdiutil create -fs HFS+ -volname $VOLNAME -srcfolder $DISK_IMAGE $DISK_IMAGE_FILE
InstallDocumentation.sh:
#!/bin/sh
# See also http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
set -x
VERSION=$(agvtool mvers -terse1)
DOXYFILE=$DERIVED_FILES_DIR/doxygen.config
DOXYGEN=/Applications/Doxygen.app/Contents/Resources/doxygen
DOCSET=$INSTALL_DIR/Docset
rm -rf $DOCSET
mkdir -p $DOCSET || exit 1
mkdir -p $DERIVED_FILES_DIR || exit 1
if ! test -x $DOXYGEN ; then
echo "*** Install Doxygen to get documentation generated for you automatically ***"
exit 1
fi
# Create a doxygen configuration file with only the settings we care about
$DOXYGEN -g - > $DOXYFILE
cat <<EOF >> $DOXYFILE
PROJECT_NAME = $FULL_PRODUCT_NAME
PROJECT_NUMBER = $VERSION
OUTPUT_DIRECTORY = $DOCSET
INPUT = $SOURCE_ROOT/Source
FILE_PATTERNS = *.h *.m
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_UNDOC_RELATIONS = YES
REPEAT_BRIEF = NO
CASE_SENSE_NAMES = YES
INLINE_INHERITED_MEMB = YES
SHOW_FILES = NO
SHOW_INCLUDE_FILES = NO
GENERATE_LATEX = NO
GENERATE_HTML = YES
GENERATE_DOCSET = YES
DOCSET_FEEDNAME = "$PROJECT.framework API Documentation"
DOCSET_BUNDLE_ID = org.brautaset.$PROJECT
EOF
# Run doxygen on the updated config file.
# doxygen creates a Makefile that does most of the heavy lifting.
$DOXYGEN $DOXYFILE
# make will invoke docsetutil. Take a look at the Makefile to see how this is done.
make -C $DOCSET/html install
# Construct a temporary applescript file to tell Xcode to load a docset.
rm -f $TEMP_DIR/loadDocSet.scpt
cat <<EOF > $TEMP_DIR/loadDocSet.scpt
tell application "Xcode"
load documentation set with path "/Users/$USER/Library/Developer/Shared/Documentation/DocSets/org.brautaset.${PROJECT}.docset/"
end tell
EOF
# Run the load-docset applescript command.
osascript $TEMP_DIR/loadDocSet.scpt
RegenerateDocumentation.sh:
#!/bin/sh
# See also http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
set -x
VERSION=$(agvtool mvers -terse1)
DOXYFILE=$DERIVED_FILES_DIR/doxygen.config
DOXYGEN=/Applications/Doxygen.app/Contents/Resources/doxygen
DOCSET=$INSTALL_DIR/Documentation
APIDOCDIR=$SOURCE_ROOT/documentation
rm -rf $DOCSET
mkdir -p $DOCSET || exit 1
mkdir -p $DERIVED_FILES_DIR || exit 1
if ! test -x $DOXYGEN ; then
echo "*** Install Doxygen to get documentation generated for you automatically ***"
exit 1
fi
# Create a doxygen configuration file with only the settings we care about
$DOXYGEN -g - > $DOXYFILE
cat <<EOF >> $DOXYFILE
PROJECT_NAME = $FULL_PRODUCT_NAME
PROJECT_NUMBER = $VERSION
OUTPUT_DIRECTORY = $DOCSET
INPUT = $SOURCE_ROOT/Source
FILE_PATTERNS = *.h *.m
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_UNDOC_RELATIONS = YES
REPEAT_BRIEF = NO
CASE_SENSE_NAMES = YES
INLINE_INHERITED_MEMB = YES
SHOW_FILES = NO
SHOW_INCLUDE_FILES = NO
GENERATE_LATEX = NO
GENERATE_HTML = YES
GENERATE_DOCSET = NO
EOF
# Run doxygen on the updated config file.
$DOXYGEN $DOXYFILE
# Replace the old dir with the newly generated one.
rm -f $APIDOCDIR/*
cp -p $DOCSET/html/* $APIDOCDIR
cd $APIDOCDIR
# Revert files that differ only in the timestamp.
svn diff *.html | diffstat | awk '$3 == 2 { print $1 }' | xargs svn revert
# Add/remove files from subversion.
svn st | awk '
$1 == "?" { print "svn add", $2 }
$1 == "!" { print "svn delete", $2 }
' | sh -
svn propset svn:mime-type text/html *.html
svn propset svn:mime-type text/css *.css
svn propset svn:mime-type image/png *.png
svn propset svn:mime-type image/gif *.gif
This is an improvement of the "Create Property and Synths for instance variables" script that Lawrence Johnston posted above.
Settings:
Input: Entire Document
Directory: Home Directory
Output: Discard Output
Errors: Ignore Errors (or Alert if you want to see them)
Select any number of variables and it'll create properties and syns for all of them. It'll even create/edit your dalloc method as necessary.
Edit up the results if they are not exactly right (copy vs. retain, etc.)
Handles more things like underbar storage name, behavior, dealloc,…
Link to where this comes from and discussion: http://cocoawithlove.com/2008/12/instance-variable-to-synthesized.html
#! /usr/bin/perl -w
# Created by Matt Gallagher on 20/10/08.
# Copyright 2008 Matt Gallagher. All rights reserved.
#
# Enhancements by Yung-Luen Lan and Mike Schrag on 12/08/09.
# (mainly: multiple lines)
# Copyright 2009 Yung-Luen Lan and Mike Schrag. All rights reserved.
#
# Enhancements by Pierre Bernard on 20/09/09.
# (mainly: underbar storage name, behavior, dealloc,…)
# Copyright 2009 Pierre Bernard. All rights reserved.
#
# Permission is given to use this source code file without charge in any
# project, commercial or otherwise, entirely at your risk, with the condition
# that any redistribution (in part or whole) of source code must retain
# this copyright and permission notice. Attribution in compiled projects is
# appreciated but not required.
use strict;
# Get the header file contents from Xcode user scripts
my $headerFileContents = <<'HEADERFILECONTENTS';
%%%{PBXAllText}%%%
HEADERFILECONTENTS
# Get the indices of the selection from Xcode user scripts
my $selectionStartIndex = %%%{PBXSelectionStart}%%%;
my $selectionEndIndex = %%%{PBXSelectionEnd}%%%;
# Find the closing brace (end of the class variables section)
my $remainderOfHeader = substr $headerFileContents, $selectionEndIndex;
my $indexAfterClosingBrace = $selectionEndIndex + index($remainderOfHeader, "\n}\n") + 3;
if ($indexAfterClosingBrace == -1)
{
exit 1;
}
# Get path of the header file
my $implementationFilePath = "%%%{PBXFilePath}%%%";
my $headerFilePath = $implementationFilePath;
# Look for an implemenation file with a ".m" or ".mm" extension
$implementationFilePath =~ s/\.[hm]*$/.m/;
if (!(-e $implementationFilePath))
{
$implementationFilePath =~ s/.m$/.mm/;
}
# Stop now if the implementation file can't be found
if (!(-e $implementationFilePath))
{
exit 1;
}
my $propertyDeclarations = '';
my $synthesizeStatements = '';
my $releaseStatements = '';
# Handle subroutine to trim whitespace off both ends of a string
sub trim
{
my $string = shift;
$string =~ s/^\s*(.*?)\s*$/$1/;
return $string;
}
# Get the selection out of the header file
my $selectedText = substr $headerFileContents, $selectionStartIndex, ($selectionEndIndex - $selectionStartIndex);
$selectedText = trim $selectedText;
my $selectedLine;
foreach $selectedLine (split(/\n+/, $selectedText)) {
my $type = '';
my $asterisk = '';
my $name = '';
my $behavior = '';
# Test that the selection is:
# At series of identifiers (the type name and access specifiers)
# Possibly an asterisk
# Another identifier (the variable name)
# A semi-colon
if (length($selectedLine) && ($selectedLine =~ /([_A-Za-z][_A-Za-z0-9]*\s*)+([\s\*]+)([_A-Za-z][_A-Za-z0-9]*);/))
{
$type = $1;
$type = trim $type;
$asterisk = $2;
$asterisk = trim $asterisk;
$name = $3;
$behavior = 'assign';
if (defined($asterisk) && length($asterisk) == 1)
{
if (($type eq 'NSString') || ($type eq 'NSArray') || ($type eq 'NSDictionary') || ($type eq 'NSSet'))
{
$behavior = 'copy';
}
else
{
if (($name =~ /Delegate/) || ($name =~ /delegate/) || ($type =~ /Delegate/) || ($type =~ /delegate/))
{
$behavior = 'assign';
}
else
{
$behavior = 'retain';
}
}
}
else
{
if ($type eq 'id')
{
$behavior = 'copy';
}
$asterisk = '';
}
}
else
{
next;
}
my $storageName = '';
if ($name =~ /_([_A-Za-z][_A-Za-z0-9]*)/) {
$storageName = $name;
$name = $1;
}
# Create and insert the propert declaration
my $propertyDeclaration = "\#property (nonatomic, $behavior) $type " . $asterisk . $name . ";\n";
$propertyDeclarations = $propertyDeclarations . $propertyDeclaration;
# Create and insert the synthesize statement
my $synthesizeStatement = '';
if (length($storageName))
{
$synthesizeStatement = "\#synthesize $name = $storageName;\n";
}
else
{
$synthesizeStatement = "\#synthesize $name;\n";
}
$synthesizeStatements = $synthesizeStatements . $synthesizeStatement;
# Create and insert release statement
my $releaseName = $name;
my $releaseStatement = '';
if (length($storageName))
{
$releaseName = $storageName;
}
if ($behavior eq 'assign')
{
if ($type eq 'SEL')
{
$releaseStatement = "\t$releaseName = NULL;\n";
}
}
else
{
$releaseStatement = "\t[$releaseName release];\n\t$releaseName = nil;\n";
}
$releaseStatements = $releaseStatements . $releaseStatement;
}
my $leadingNewline = '';
my $trailingNewline = '';
# Determine if we need to add a newline in front of the property declarations
if (substr($headerFileContents, $indexAfterClosingBrace, 1) eq "\n")
{
$indexAfterClosingBrace += 1;
$leadingNewline = '';
}
else
{
$leadingNewline = "\n";
}
# Determine if we need to add a newline after the property declarations
if (substr($headerFileContents, $indexAfterClosingBrace, 9) eq '#property')
{
$trailingNewline = '';
}
else
{
$trailingNewline = "\n";
}
substr($headerFileContents, $indexAfterClosingBrace, 0) = $leadingNewline . $propertyDeclarations . $trailingNewline;
my $replaceFileContentsScript = <<'REPLACEFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
set newDocText to (item 2 of argv)
tell application "Xcode"
set doc to open fileAlias
set text of doc to (text 1 thru -2 of newDocText)
end tell
end run
REPLACEFILESCRIPT
# Use Applescript to replace the contents of the header file
# (I could have used the "Output" of the Xcode user script instead)
system 'osascript', '-e', $replaceFileContentsScript, $headerFilePath, $headerFileContents;
my $getFileContentsScript = <<'GETFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
tell application "Xcode"
set doc to open fileAlias
set docText to text of doc
end tell
return docText
end run
GETFILESCRIPT
# Get the contents of the implmentation file
open(SCRIPTFILE, '-|') || exec 'osascript', '-e', $getFileContentsScript, $implementationFilePath;
my $implementationFileContents = do {local $/; <SCRIPTFILE>};
close(SCRIPTFILE);
# Look for the class implementation statement
if (length($implementationFileContents) && ($implementationFileContents =~ /(\#implementation [_A-Za-z][_A-Za-z0-9]*\n)/))
{
my $matchString = $1;
my $indexAfterMatch = index($implementationFileContents, $matchString) + length($matchString);
# Determine if we want a newline before the synthesize statement
if (substr($implementationFileContents, $indexAfterMatch, 1) eq "\n")
{
$indexAfterMatch += 1;
$leadingNewline = '';
}
else
{
$leadingNewline = "\n";
}
# Determine if we want a newline after the synthesize statement
if (substr($implementationFileContents, $indexAfterMatch, 11) eq '#synthesize')
{
$trailingNewline = '';
}
else
{
$trailingNewline = "\n";
}
substr($implementationFileContents, $indexAfterMatch, 0) = $leadingNewline. $synthesizeStatements . $trailingNewline;
if ($implementationFileContents =~ /([ \t]*\[.*super.*dealloc.*\].*;.*\n)/)
{
my $deallocMatch = $1;
my $indexAfterDeallocMatch = index($implementationFileContents, $deallocMatch);
substr($implementationFileContents, $indexAfterDeallocMatch, 0) = "$releaseStatements\n";
}
elsif ($implementationFileContents =~ /(\#synthesize .*\n)*(\#synthesize [^\n]*\n)/s) {
my $synthesizeMatch = $2;
my $indexAfterSynthesizeMatch = index($implementationFileContents, $synthesizeMatch) + length($synthesizeMatch);
my $deallocMethod = "\n- (void)dealloc\n{\n$releaseStatements\n\t[super dealloc];\n}\n";
substr($implementationFileContents, $indexAfterSynthesizeMatch, 0) = $deallocMethod;
}
# Use Applescript to replace the contents of the implementation file in Xcode
system 'osascript', '-e', $replaceFileContentsScript, $implementationFilePath, $implementationFileContents;
}
exit 0;
Here's one to log a method and its arguments any time it's executed (Select the method definition up through the lie with the opening brace and execute the script). If FIXME shows up in the output it means it's an unrecognized type. You can either add it to the script or choose the proper format specifier manually.
#!/usr/bin/python
# LogMethod
# Selection
# Selection
# Insert after Selection
# Display in Alert
import sys
import re
input = sys.stdin.read()
methodPieces = re.findall("""(\w*:)""", input)
vars = re.findall(""":\(([^)]*)\)[ ]?(\w*)""", input)
outputStrings = ["\n NSLog(#\""]
# Method taking no parameters
if not methodPieces:
outputStrings.append(re.findall("""(\w*)[ ]?{""", input)[0])
for (methodPiece, var) in zip(methodPieces, vars):
type = var[0]
outputStrings.append(methodPiece)
if "**" in type:
outputStrings.append("%p")
elif "*" in type:
if "char" in type:
outputStrings.append("%c")
else:
outputStrings.append("%#")
else:
if "int" in type or "NSInteger" in type or "BOOL" in type:
outputStrings.append("%i")
elif "NSUInteger" in type:
outputStrings.append("%u")
elif "id" in type:
outputStrings.append("%#")
elif "NSTimeInterval" in type:
outputStrings.append("%f")
elif "SEL" in type:
outputString.append("%s")
else:
outputStrings.append('"FIXME"')
if not methodPiece == methodPieces[-1]:
outputStrings.append('\\n"\n #"')
outputStrings.append("\"")
for var in vars:
name = var[1]
outputStrings.append(",\n ")
outputStrings.append(name)
outputStrings.append(");")
print "".join(outputStrings),
Here's one to create a -description method for a class. Highlight the instance variables declaration section (#interface ... { ... }) and execute the script. Then paste the result into your implementation. I use this one along with po objectName in GDB. If FIXME shows up in the output it means it's an unrecognized type. You can either add it to the script or choose the proper format specifier manually.
#!/usr/bin/python
# Create description method for class
# Selection
# Selection
# Insert after Selection
# Display in Alert
import sys
import re
input = sys.stdin.read()
className = re.findall("""(?:#interface )(\w*)""", input)[0]
vars = re.findall("""(\w*[ ][*]?)(\w*?)_?;""", input)
outputStrings = ["- (NSString *)description {\n"]
outputStrings.append("""return [NSString stringWithFormat:#"%s :\\n"\n#" -""" % className)
for type, var in vars:
outputStrings.append("%s:" % var)
if "**" in type:
outputStrings.append("%p")
elif "*" in type:
if "char" in type:
outputStrings.append("%c")
else:
outputStrings.append("%#")
else:
if "int" in type or "NSInteger" in type or "BOOL" in type:
outputStrings.append("%i")
elif "NSUInteger" in type:
outputStrings.append("%u")
elif "id" in type:
outputStrings.append("%#")
elif "NSTimeInterval" in type:
outputStrings.append("%f")
elif "SEL" in type:
outputString.append("%s")
else:
outputStrings.append('"FIXME"')
if not var == vars[-1][1]:
outputStrings.append(',\\n"\n#" -')
outputStrings.append("\"")
for type, var in vars:
outputStrings.append(",\n")
outputStrings.append("[self %s]" % var)
outputStrings.append("];\n}")
print "".join(outputStrings),
Here's one that I found somewhere else that creates #property (copy) and #synthesize property directives for an instance variable. It could use a bit of improvement (say, to let you synthesize multiple variables at once), but it's better than creating them by hand.
Select the instance variable you want to create a property for and activate the script.
If I want a (retain) instead of (copy) I just activate the script and change it to retain manually (it's smart enough to not include the (copy) on primitive types such as int to begin with).
#! /usr/bin/perl -w
#Create property from instance variable
#Entire Document
#Home Directory
#Discard Output
#Display in Alert
use strict;
# Get the header file contents from Xcode user scripts
my $headerFileContents = <<'HEADERFILECONTENTS';
%%%{PBXAllText}%%%
HEADERFILECONTENTS
# Get the indices of the selection from Xcode user scripts
my $selectionStartIndex = %%%{PBXSelectionStart}%%%;
my $selectionEndIndex = %%%{PBXSelectionEnd}%%%;
# Get path of the header file
my $implementationFilePath = "%%%{PBXFilePath}%%%";
my $headerFilePath = $implementationFilePath;
# Look for an implemenation file with a ".m" or ".mm" extension
$implementationFilePath =~ s/\.[hm]*$/.m/;
if (!(-e $implementationFilePath))
{
$implementationFilePath =~ s/.m$/.mm/;
}
# Handle subroutine to trime whitespace off both ends of a string
sub trim
{
my $string = shift;
$string =~ s/^\s*(.*?)\s*$/$1/;
return $string;
}
# Get the selection out of the header file
my $selectedText = substr $headerFileContents, $selectionStartIndex, ($selectionEndIndex - $selectionStartIndex);
$selectedText = trim $selectedText;
my $type = "";
my $asterisk = "";
my $name = "";
my $behavior = "";
# Test that the selection is:
# At series of identifiers (the type name and access specifiers)
# Possibly an asterisk
# Another identifier (the variable name)
# A semi-colon
if (length($selectedText) && ($selectedText =~ /([_A-Za-z][_A-Za-z0-9]*\s*)+([\s\*]+)([_A-Za-z][_A-Za-z0-9]*);/))
{
$type = $1;
$type = trim $type;
$asterisk = $2;
$asterisk = trim $asterisk;
$name = $3;
$behavior = "";
if (defined($asterisk) && length($asterisk) == 1)
{
$behavior = "(copy) "; #"(nonatomic, retain) ";
}
else
{
$asterisk = "";
}
}
else
{
exit 1;
}
# Find the closing brace (end of the class variables section)
my $remainderOfHeader = substr $headerFileContents, $selectionEndIndex;
my $indexAfterClosingBrace = $selectionEndIndex + index($remainderOfHeader, "\n}\n") + 3;
if ($indexAfterClosingBrace == -1)
{
exit 1;
}
# Determine if we need to add a newline in front of the property declaration
my $leadingNewline = "\n";
if (substr($headerFileContents, $indexAfterClosingBrace, 1) eq "\n")
{
$indexAfterClosingBrace += 1;
$leadingNewline = "";
}
# Determine if we need to add a newline after the property declaration
my $trailingNewline = "\n";
if (substr($headerFileContents, $indexAfterClosingBrace, 9) eq "\#property")
{
$trailingNewline = "";
}
# Create and insert the propert declaration
my $propertyDeclaration = $leadingNewline . "\#property " . $behavior . $type . " " . $asterisk . $name . ";\n" . $trailingNewline;
substr($headerFileContents, $indexAfterClosingBrace, 0) = $propertyDeclaration;
my $replaceFileContentsScript = <<'REPLACEFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
set newDocText to (item 2 of argv)
tell application "Xcode"
set doc to open fileAlias
set text of doc to newDocText
end tell
end run
REPLACEFILESCRIPT
# Use Applescript to replace the contents of the header file
# (I could have used the "Output" of the Xcode user script instead)
system 'osascript', '-e', $replaceFileContentsScript, $headerFilePath, $headerFileContents;
# Stop now if the implementation file can't be found
if (!(-e $implementationFilePath))
{
exit 1;
}
my $getFileContentsScript = <<'GETFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
tell application "Xcode"
set doc to open fileAlias
set docText to text of doc
end tell
return docText
end run
GETFILESCRIPT
# Get the contents of the implmentation file
open(SCRIPTFILE, '-|') || exec 'osascript', '-e', $getFileContentsScript, $implementationFilePath;
my $implementationFileContents = do {local $/; <SCRIPTFILE>};
close(SCRIPTFILE);
# Look for the class implementation statement
if (length($implementationFileContents) && ($implementationFileContents =~ /(\#implementation [_A-Za-z][_A-Za-z0-9]*\n)/))
{
my $matchString = $1;
my $indexAfterMatch = index($implementationFileContents, $matchString) + length($matchString);
# Determine if we want a newline before the synthesize statement
$leadingNewline = "\n";
if (substr($implementationFileContents, $indexAfterMatch, 1) eq "\n")
{
$indexAfterMatch += 1;
$leadingNewline = "";
}
# Determine if we want a newline after the synthesize statement
$trailingNewline = "\n";
if (substr($implementationFileContents, $indexAfterMatch, 11) eq "\#synthesize")
{
$trailingNewline = "";
}
# Create and insert the synthesize statement
my $synthesizeStatement = $leadingNewline . "\#synthesize " . $name . ";\n" . $trailingNewline;
substr($implementationFileContents, $indexAfterMatch, 0) = $synthesizeStatement;
# Use Applescript to replace the contents of the implementation file in Xcode
system 'osascript', '-e', $replaceFileContentsScript, $implementationFilePath, $implementationFileContents;
}
exit 0;
This on creates #proptery, #synthesize, dealloc, viewDidUnload and public methods for you. Easy XCode integration:
http://github.com/holtwick/xobjc

Resources