prevent ttk::treeview from selecting on expanding/collapsing subtrees - treeview

so i'm having a simple treeview, with some expandible subtrees, and a listener for selection changes:
ttk::treeview .tree
pack .tree -expand 1 -fill both
.tree tag configure disabled -foreground grey
.tree tag bind all <<TreeviewSelect>> "puts selected"
set node [.tree insert {} end -text "Node 0" -tags all]
.tree insert $node end -text "A" -tags "all"
.tree insert $node end -text "B" -tags "all"
.tree insert $node end -text "C" -tags "all"
set node [.tree insert {} end -text "Node 1" -tags all -open 1]
.tree insert $node end -text "X" -tags "all"
.tree insert $node end -text "Y" -tags "all"
.tree insert $node end -text "Z" -tags "all"
It seems that tcl/tk will automatically (de)select a subtree-node ("Node 0" resp "Node 1") and call the <<TreeviewSelect>> listener, if the user tries to expand/collapse the subtree (by clicking on the Expander icon left of the node element).
Is there any way to allow expansion/callapsing of the subtree without selecting the item at the same time?
My dev platform is Tcl/Tk-8.6 (on Debian/stretch), but any solution is supposed to work cross-platform.

Something like this works.
package require Tk
variable vars
proc sel { args } {
variable vars
if { $vars(skip) && $vars(oldsel) ne {} } {
.tree selection set $vars(oldsel)
set vars(skip) false
return
}
set vars(skip) false
set vars(oldsel) [.tree selection]
}
proc op { args } {
variable vars
set vars(skip) true
}
proc cl { args } {
variable vars
set vars(skip) true
}
ttk::treeview .tree
set id1 [.tree insert {} end -id id1 -text id1]
set id1-1 [.tree insert $id1 end -id id1-1 -text id1-1]
set id1-2 [.tree insert $id1 end -id id1-2 -text id1-2]
set id2 [.tree insert {} end -id id2 -text id2]
set id2-1 [.tree insert $id2 end -id id2-1 -text id2-1]
set id2-2 [.tree insert $id2 end -id id2-2 -text id2-2]
bind .tree <<TreeviewSelect>> sel
bind .tree <<TreeviewOpen>> op
bind .tree <<TreeviewClose>> cl
pack .tree
set vars(oldsel) {}

Related

Invalid Command and files not populating using treeview in TCL

In my endeavour to create a simple treeview GUI I have come across tree.tcl which is available in the active tcl 8.6 installation.
I've managed to adapt the code to fit my purpose (hardy changed anything) and when I run the code in the same way as the demos for Active TCL 8.6 (via widget) are run the code is running as expected (though I've not tried making any selection of node within tree).
However that is not the case once I copy the code over into my gui application.
The structure is as expected but when I try to expand the nodes I get:
I get invalid command error
ERROR: invalid command name "populateTree"
command bound to event:
"populateTree .fc.tv.tree [.fc.tv.tree focus]"
Now for some reason none of the files within the folders are read i.e. all file types are recognised as Directories hence everything under nodes shown as "dummy"
I'd also like to add filter to only read a specific file type, i.e. *.txt, if I do so then even folders are not read.
i.e foreach f [lsort -dictionary [glob -nocomplain -dir $path *]] to
foreach f [lsort -dictionary [glob -nocomplain -dir $path *.txt]]
I'd be obliged if someone could help.
# temp dir to mimic Network dir
set ::dir "C:/Dev"
proc populateRoots {tree} {
populateTree $tree [$tree insert {} end -text "Network File" \
-values [list $::dir directory]]
}
## Code to populate a node of the tree
proc populateTree {tree node} {
if {[$tree set $node type] ne "directory"} {
return
}
set path [$tree set $node fullpath]
$tree delete [$tree children $node]
foreach f [lsort -dictionary [glob -nocomplain -dir $path *]] {
set type [file type $f]
set id [$tree insert $node end -text [file tail $f] \
-values [list $f $type]]
if {$type eq "directory"} {
## Make it so that this node is openable
$tree insert $id 0 -text dummy ;# a dummy
$tree item $id -text [file tail $f]/
} elseif {$type eq "file"} {
set size [file size $f]
set ttime [file mtime $f]
## Format the file size nicely
if {$size >= 1024*1024*1024} {
set size [format %.1f\ GB [expr {$size/1024/1024/1024.}]]
} elseif {$size >= 1024*1024} {
set size [format %.1f\ MB [expr {$size/1024/1024.}]]
} elseif {$size >= 1024} {
set size [format %.1f\ kB [expr {$size/1024.}]]
} else {
append size " bytes"
}
$tree set $id size $size
}
}
# Stop this code from rerunning on the current node
$tree set $node type processedDirectory
}
# ## Create the tree and set it up
ttk::treeview $tw.tree -columns {fullpath type size date time} -displaycolumns {size date time} \
-yscroll "$tw.vsb set" -xscroll "$tw.hsb set"
ttk::scrollbar $tw.vsb -orient vertical -command "$tw.tree yview"
ttk::scrollbar $tw.hsb -orient horizontal -command "$tw.tree xview"
$tw.tree heading \#0 -text "Directory Structure"
$tw.tree heading size -text "File Size"
$tw.tree column size -stretch 0 -width 70
populateRoots $tw.tree
bind $tw.tree <<TreeviewOpen>> {populateTree %W [%W focus]}
# ## Arrange the tree and its scrollbars in the toplevel
lower [ttk::frame $tw.dummy]
pack $tw.dummy -fill both -expand 1
grid $tw.tree $tw.vsb -sticky nsew -in $tw.dummy
grid $tw.hsb -sticky nsew -in $tw.dummy
grid columnconfigure $tw.dummy 0 -weight 1
grid rowconfigure $tw.dummy 0 -weight 1
Thanks in advance,
George
The issue was that the procedure populateTree needed to be ::populateTree so it could be found within the namespace.
PS: I still can't print selection to console...

Expect Script - Fixing weird terminal resizing behaviour

I wrote a small expect script to connect to multiple SSH Servers.
However if I interact with the terminal after initializing the connection, the terminal window behaves very odd.
For example, if I start the script in a non-fullsize terminal, and resize the window to make it larger after the Interact, it looks like that:
Now, I had this issue in my first expect script as well. But I was able to resolve it by adding the following code:
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
And this worked perfectly fine! I was able to resize the terminal without a problem.
However, I added this piece of code to my new script, where there are multiple interact's in different proc's (functions). And on window resize, I get the following error:
can't read "spawn_out(slave,name)": no such variable
while executing
"stty rows $XZ columns $YZ < $spawn_out(slave,name)"
I have no idea on how to resolve this. Here is my code:
#!/usr/bin/expect -f
set SERVER "0"
set CHOICE "0"
set SERVER_1_PKEY [exec cat /home/me/p1]
set SERVER_2_PKEY [exec cat /home/me/p2]
set SERVER_1_HOST "server1.com"
set SERVER_2_HOST "server2.com"
set SERVER_1_USER "server1user"
set SERVER_2_USER "server2user"
set SERVER_1_PORT "22"
set SERVER_2_PORT "22"
trap {
set XZ [stty rows ]
set YZ [stty columns]
stty rows $XZ columns $YZ < $spawn_out(slave,name)
} WINCH
proc s1 {SERVER_1_PKEY SERVER_1_HOST SERVER_1_USER SERVER_1_PORT} {
send_user "\033c"
spawn ssh ${SERVER_1_USER}#${SERVER_1_HOST} -p ${SERVER_1_PORT}
expect "assword:"
send "${SERVER_1_PKEY}\r"
interact
}
proc s2 {} {
send_user "\033c"
spawn ssh ${SERVER_2_USER}#${SERVER_2_HOST} -p ${SERVER_2_PORT}
expect "assword:"
send "${SERVER_2_PKEY}\r"
interact
}
set arg [lindex $argv 0]
switch $arg {
"" { set CHOICE "0" }
"1" { set CHOICE "1" }
"2" { set CHOICE "2" }
}
if {$CHOICE eq "0"} {
puts -nonewline " Input \[1,2\]: "
flush stdout
gets stdin SERVER
if {$SERVER eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$SERVER eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
}
if {$CHOICE eq "1"} { s1 $SERVER_1_PKEY $SERVER_1_HOST $SERVER_1_USER $SERVER_1_PORT }
if {$CHOICE eq "2"} { s2 $SERVER_2_PKEY $SERVER_2_HOST $SERVER_2_USER $SERVER_2_PORT }
Can anyone help me resolve this issue or tell me what I'm missing?
When you call spawn inside a procedure the array variable spawn_out(slave,name) has the scope of that procedure only. Usually, you can just make this into a global scope by declaring it as such inside each procedure:
proc s1 {...} {
global spawn_out
...
spawn ...
}
send_user $spawn_out(slave,name)

How to evaluate a tclsh script?

tclsh is a shell containing the TCL commands.
The TCL uplevel command evaluates the given TCL script, but it fails to evaluate a tclsh script (which can contain bash commands).
How can I obtain an analogue of uplevel for the tclsh script?
Consider this TCL script:
# file main.tcl
proc prompt { } \
{
puts -nonewline stdout "MyShell > "
flush stdout
}
proc process { } \
{
catch { uplevel #0 [gets stdin] } got
if { $got ne "" } {
puts stderr $got
flush stderr
}
prompt
}
fileevent stdin readable process
prompt
while { true } { update; after 100 }
This is a kind of TCL shell, so when you type tclsh main.tcl it shows a prompt MyShell > and it acts like you are in interactive tclsh session. However, you are in non-interactive tclsh session, and everything you type is evaluated by the uplevel command. So here you can't use bash commands like you can do it int interactive tclsh session. E.g. you can't open vim right from the shell, also exec vim will not work.
What I want is to make MyShell > act like interactive tclsh session. The reason why I can't just use tclsh is the loop at the last line of main.tcl: I have to have that loop and everything has to happen in that loop. I also have to do some stuff at each iteration of that loop, so can use vwait.
Here is the solution.
I have found no better solution then to overwrite the ::unknown function.
# file main.tcl
proc ::unknown { args } \
{
variable ::tcl::UnknownPending
global auto_noexec auto_noload env tcl_interactive
global myshell_evaluation
if { [info exists myshell_evaluation] && $myshell_evaluation } {
set level #0
} else {
set level 1
}
# If the command word has the form "namespace inscope ns cmd"
# then concatenate its arguments onto the end and evaluate it.
set cmd [lindex $args 0]
if {[regexp "^:*namespace\[ \t\n\]+inscope" $cmd] && [llength $cmd] == 4} {
#return -code error "You need an {*}"
set arglist [lrange $args 1 end]
set ret [catch {uplevel $level ::$cmd $arglist} result opts]
dict unset opts -errorinfo
dict incr opts -level
return -options $opts $result
}
catch {set savedErrorInfo $::errorInfo}
catch {set savedErrorCode $::errorCode}
set name $cmd
if {![info exists auto_noload]} {
#
# Make sure we're not trying to load the same proc twice.
#
if {[info exists UnknownPending($name)]} {
return -code error "self-referential recursion in \"unknown\" for command \"$name\"";
}
set UnknownPending($name) pending;
set ret [catch {
auto_load $name [uplevel $level {::namespace current}]
} msg opts]
unset UnknownPending($name);
if {$ret != 0} {
dict append opts -errorinfo "\n (autoloading \"$name\")"
return -options $opts $msg
}
if {![array size UnknownPending]} {
unset UnknownPending
}
if {$msg} {
if {[info exists savedErrorCode]} {
set ::errorCode $savedErrorCode
} else {
unset -nocomplain ::errorCode
}
if {[info exists savedErrorInfo]} {
set ::errorInfo $savedErrorInfo
} else {
unset -nocomplain ::errorInfo
}
set code [catch {uplevel $level $args} msg opts]
if {$code == 1} {
#
# Compute stack trace contribution from the [uplevel].
# Note the dependence on how Tcl_AddErrorInfo, etc.
# construct the stack trace.
#
set errorInfo [dict get $opts -errorinfo]
set errorCode [dict get $opts -errorcode]
set cinfo $args
if {[string bytelength $cinfo] > 150} {
set cinfo [string range $cinfo 0 150]
while {[string bytelength $cinfo] > 150} {
set cinfo [string range $cinfo 0 end-1]
}
append cinfo ...
}
append cinfo "\"\n (\"uplevel\" body line 1)"
append cinfo "\n invoked from within"
append cinfo "\n\"uplevel $level \$args\""
#
# Try each possible form of the stack trace
# and trim the extra contribution from the matching case
#
set expect "$msg\n while executing\n\"$cinfo"
if {$errorInfo eq $expect} {
#
# The stack has only the eval from the expanded command
# Do not generate any stack trace here.
#
dict unset opts -errorinfo
dict incr opts -level
return -options $opts $msg
}
#
# Stack trace is nested, trim off just the contribution
# from the extra "eval" of $args due to the "catch" above.
#
set expect "\n invoked from within\n\"$cinfo"
set exlen [string length $expect]
set eilen [string length $errorInfo]
set i [expr {$eilen - $exlen - 1}]
set einfo [string range $errorInfo 0 $i]
#
# For now verify that $errorInfo consists of what we are about
# to return plus what we expected to trim off.
#
if {$errorInfo ne "$einfo$expect"} {
error "Tcl bug: unexpected stack trace in \"unknown\"" {} [list CORE UNKNOWN BADTRACE $einfo $expect $errorInfo]
}
return -code error -errorcode $errorCode -errorinfo $einfo $msg
} else {
dict incr opts -level
return -options $opts $msg
}
}
}
if { ( [info exists myshell_evaluation] && $myshell_evaluation ) || (([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive) } {
if {![info exists auto_noexec]} {
set new [auto_execok $name]
if {$new ne ""} {
set redir ""
if {[namespace which -command console] eq ""} {
set redir ">&#stdout <#stdin"
}
uplevel $level [list ::catch [concat exec $redir $new [lrange $args 1 end]] ::tcl::UnknownResult ::tcl::UnknownOptions]
dict incr ::tcl::UnknownOptions -level
return -options $::tcl::UnknownOptions $::tcl::UnknownResult
}
}
if {$name eq "!!"} {
set newcmd [history event]
} elseif {[regexp {^!(.+)$} $name -> event]} {
set newcmd [history event $event]
} elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} {
set newcmd [history event -1]
catch {regsub -all -- $old $newcmd $new newcmd}
}
if {[info exists newcmd]} {
tclLog $newcmd
history change $newcmd 0
uplevel $level [list ::catch $newcmd ::tcl::UnknownResult ::tcl::UnknownOptions]
dict incr ::tcl::UnknownOptions -level
return -options $::tcl::UnknownOptions $::tcl::UnknownResult
}
set ret [catch {set candidates [info commands $name*]} msg]
if {$name eq "::"} {
set name ""
}
if {$ret != 0} {
dict append opts -errorinfo "\n (expanding command prefix \"$name\" in unknown)"
return -options $opts $msg
}
# Filter out bogus matches when $name contained
# a glob-special char [Bug 946952]
if {$name eq ""} {
# Handle empty $name separately due to strangeness
# in [string first] (See RFE 1243354)
set cmds $candidates
} else {
set cmds [list]
foreach x $candidates {
if {[string first $name $x] == 0} {
lappend cmds $x
}
}
}
if {[llength $cmds] == 1} {
uplevel $level [list ::catch [lreplace $args 0 0 [lindex $cmds 0]] ::tcl::UnknownResult ::tcl::UnknownOptions]
dict incr ::tcl::UnknownOptions -level
return -options $::tcl::UnknownOptions $::tcl::UnknownResult
}
if {[llength $cmds]} {
return -code error "ambiguous command name \"$name\": [lsort $cmds]"
}
}
return -code error "invalid command name \"$name\""
}
proc prompt { } \
{
puts -nonewline stdout "MyShell > "
flush stdout
}
proc process { } \
{
global myshell_evaluation
set myshell_evaluation true
catch { uplevel #0 [gets stdin] } got
set myshell_evaluation false
if { $got ne "" } {
puts stderr $got
flush stderr
}
prompt
}
fileevent stdin readable process
prompt
while { true } { update; after 100 }
The idea is to modify the ::unknown function so that it handles MyShell evaluations as the ones of tclsh interactive session.
This is an ugly solution, as I am fixing the code of ::unknown function which can be different for different systems and diferent versions of tcl.
Is there any solution which circumvents these issues?
uplevel does not only evaluate a script, but it evaluates it in the stack context of the caller of the instance where it's executed. It's a pretty advanced command which should be used when you define your own execution control structures, and OFC it's TCL specific - I find myself unable to imagine how a tclsh equivalent should work.
If you just want to evaluate another script, the proper TCL command would be eval. If that other script is tclsh, why don't you just open another tclsh?
The simplest answer, I think, would be to use the approach you're using; to rewrite the unknown command. Specifically, there is a line in it that checks to make sure the current context is
Not run in a script
Interactive
At the top level
If you replace that line:
if {([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive} {
with something that just checks the level
if ([info level] == 1} {
you should get what you want.
Vaghan, you do have the right solution. Using ::unknown is how tclsh itself provides the interactive-shell-functionality you're talking about (invoking external binaries, etc). And you've lifted that same code and included it in your MyShell.
But, if I understand your concerns about it being an "ugly solution", you'd rather not reset ::unknown ?
In which case, why not just append the additional functionality you want to the end of the pre-existing ::unknown's body (or prepend it - you choose)
If you search on the Tcl'ers wiki for "let unknown know", you'd see a simple proc which demonstrates this. It prepends new code to the existing ::unknown, so you can keep adding additional "fallback code" as you go along.
(apologies if I've misunderstood why you feel your solution is "ugly")
Instead of changing the unknown proc, I suggest that you make the changes to evaluate the expresion
if {([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive} {
to true.
info level: call your stuff with uplevel #0 $code
info script: call info script {} to set it to an empty value
tcl_interactive. Simple: set ::tcl_interactive 1
so your code would be
proc prompt { } {
puts -nonewline stdout "MyShell > "
flush stdout
}
proc process { } {
catch { uplevel #0 [gets stdin] } got
if { $got ne "" } {
puts stderr $got
flush stderr
}
prompt
}
fileevent stdin readable process
set tcl_interactive 1
info script {}
prompt
vwait forever

How to split a variable in two args with exec in TCL?

I want to create a simple Console in Tcl/Tk
I have two problems. First changing every * with a [glob *] but also, when my entry contains "ls -a" it doesn't understand that ls is the command and -a the first arg.
How can I manage to do that ?
Thanks
proc execute {} {
# ajoute le contenu de .add_frame.add_entry
set value [.add_frame.add_entry get]
if {[string compare "$value" ""] == 1} {
.text insert end "\n\n% $value\n"
.text insert end [exec $value]
.add_frame.add_entry delete 0 end
}
}
frame .add_frame
label .add_frame.add_label -text "Nouvel élément : "
entry .add_frame.add_entry
button .add_frame.add_button -text "Executer" -command execute
button .add_frame.exit_button -text "Quitter" -command exit
bind .add_frame.add_entry <Return> execute
bind .add_frame.add_entry <KP_Enter> execute
bind . <Escape> exit
bind . <Control-q> exit
pack .add_frame.add_label -side left
pack .add_frame.exit_button -side right
pack .add_frame.add_button -side right
pack .add_frame.add_entry -fill x -expand true
pack .add_frame -side top -fill x
text .text
.text insert end "% Tcl/Tk Console"
pack .text -side bottom -fill both -expand true
The simple answer in Tcl 8.5 is to use this:
exec {*}$value
In 8.4 and before, that syntax didn't exist. That meant that many people wrote this:
eval exec $value
But in reality, the safe version was one of these:
eval exec [lrange $value 0 end]
eval [linsert $value 0 exec]
Of course, if the $value is coming straight from the user then you're better off using a system shell to evaluate it since more users expect that sort of syntax:
exec /usr/bin/bash -c $value

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