Friday, September 30, 2016

Another example of file modifiers

The following is a command line Zsh snippet to looking for all .java files under src/main/java, then to see whether each has an equivalent "src/main/java//TestAbc.java" file:
% for i in src/main/java/**/*.java; do t=${i:h:s/main/test}/Test${i:t}; [[ -e $t ]] || echo $i; done
A bit of a breakdown for the code within the for loop: Recursive for all .java files under src/main/java:
% for i in src/main/java/**/*.java
Take the parent directory (":h") of the file name, and replace its first occurrence of "main" with "test" (thus "src/main/java" becomes "src/test/java"), then prepend that with "/Test" to the basename (":t") of the file name, and assign it to "t":
t=${i:h:s/main/test}/Test${i:t}
Check that the test file exists, and if it doesn't (meaning -e $t returns 0, or false), then print the original file name.
[[ -e $t ]] || echo $i

Friday, September 23, 2016

Running most recent test by file globbing

This is a little snippet that shows a couple of features of Z shell and file globbing.

In my Java projects, when I've changed a file, I usually want to run its related tests, without running the entire test suite. While I'm aware of third-party libraries (such as Guard for Ruby/Rails, I don't need that much functionality, and it's more fun to write my own code anyway.

I use Gradle for my Java projects, and this function is what directs it to run a single test:

gst () {
    gradle test -Dtest.single=$*
}

With the file name expansion that Gradle does, "gst TestSomeClass" gets globbed more or less into looking for "**/TestSomeClass*", so all we need to give to Gradle is the root name of the file.

Easy enough to run on the command line, but what gets annoying is to repeatedly execute the same process: I change file Foo.java, which has the corresponding test file TestFoo.java, which I then execute as:

% gst TestFoo

Similarly, I might change the test code itself, in TestFoo.java, then go to the command line and again, run:

% gst TestFoo

So the process that I'm following, manually, is: Whatever is the most recent file that changed, run its corresponding tests, and that can be deduced by the file name: file Foo.java means run TestFoo, and, of course, TestFoo.java also means TestFoo.

Lo and behold, the above can be nearly directly translated into a Z shell function:

runlatest() {
    f=`echo src/**/*.java(om[1])`
    e=Test$f:gs/Test//:t:r
    gst $e
}

Those lines mean:

f=`echo src/**/*.java(om[1])`: get all .java files under the src directory (src/**/*.java), sort them by modification time, most recent first (om), take the first one in the list ([1]), and assign it to f.

e=Test$f:gs/Test//:t:r: take the file $f, remove the string "Test" from its name (gs/Test//), take its base name (:t for "tail"), then its root name (:r, removing the extension ".java", prepend it with "Test", and assign that to e.

gst $e: run gradle test -Dtest.single=$e.

Thus, runlatest determines from the most recent file modified, how to convert from its name to the root name that Gradle takes as its argument.

The above shows how file globbing can be integrated into useful code snippets. I hope you've found it useful.

Saturday, January 16, 2016

Opening a script

Very often I want to open a script in my path, usually but not always in ~/bin. In the past (prior to using my shortcuts from AForAlias), this would be:
% emacsclient --no-wait ~/bin/somescript
With AForAlias, this, is now:
% o ~/bin/somescript
But the =name shortcut in Zsh resolves to the first occurrence of "name" in the path, so the above now becomes the even more terse:
% o =somescript
This is handiest, in my experience, when one has a number of directories that contain scripts, such as ~/bin/java, ~/bin/ruby, ~/bin/git, and ~/bin/work.

Tuesday, December 8, 2015

Process substitution

Process substitution is among my favorite features of Zsh, and is somewhat extended from Bash. The essence of the feature is that a command-line argument of the form "=(some code here)" is executed and piped to a temporary file, so the "=(...)" part can be used on the command line anywhere a file could be used. Normally, of course, you could just pipe the process yourself, of the form:
# count the number of files:
% find -type f | wc
The equivalent would be:
% wc =(find -type f)
That's somewhat silly and trite, but where process substitution really is valuable is when comparing hierarchies by file name:
% diff =(cd project/v1 && find src -type f | sort) =(cd project/v2 && find src -type f | sort)
That's much simpler than piping the list to two files and comparing them. And in the interest of not repeating a command, just create an alias for it, which can be used within the subcommand:
% mklist() { cd project/$1 && find src -type f | sort }
% diff =(mklist v1) =(mklist v2)
So if you find yourself dumping output to temporary files and manipulating them directly, consider using this functionality instead.

Sunday, November 8, 2015

Command line help

I've often found myself in the middle of long command, and needing to look at the help (man page) for that command. Usually that is relatively easy to do, with the ctrl-u and ctrl-y sequences, ctrl-u to escape from the current command, and ctrl-y to return from it:
% find -type f -name \*.rb -size +1<ctrl-u>
% man find
# search through man page for size modifiers, quitting (q) when done ...
<ctrl-y>
% find -type f -name \*.rb -size +1k | xargs wc
Of course, given that this is Z shell, there is an easier way. <alt-h> runs the command run-help, which returns the man page for the current command at the beginning of the line:
% find -type f -name \*.rb -size +1<alt-h>
% man find
# search through man page for size modifiers, quitting (q) when done ...
% find -type f -name \*.rb -size +1k | xargs wc
There is no need for ctrl-u and ctrl-y here (although they are very useful in other contexts), and this eliminates the "man find" command as well. The only glitch is that out of the box (with the default settings for run-help), if you are running a command with sudo, such as:
% sudo find /etc -type f -mmin -15 -size <alt-h>
the help is shown for sudo, not find. This is easy enough to fix, by adding this to your .zshrc file (or in my case, to ~/.config/zsh/help.zsh)
autoload -U run-help
autoload run-help-sudo
All well and good, but Git (and Subversion) have many subcommands, so with the default Z shell run-help configuration, doing the following:
% git log -<alt-h>
shows the man page for git, not git log. Again, easy enough to fix:
autoload -U run-help
autoload run-help-sudo
autoload run-help-git
autoload run-help-svn
More greatness from Z shell.

Sunday, September 27, 2015

Caching command output, part 2

In a previous post I began to define a pair of Zsh commands to cache the output from a previously-run command, to refer to its lines in subsequent commands.

For example, listing a set of files:

% diff -rq joda-format-2.3 joda-format-2.8.1 TA
     1  Files joda-format-2.3/DateTimeFormat.java and joda-format-2.8.1/DateTimeFormat.java differ
     2  Files joda-format-2.3/DateTimeFormatterBuilder.java and joda-format-2.8.1/DateTimeFormatterBuilder.java differ
     3  Files joda-format-2.3/DateTimeFormatter.java and joda-format-2.8.1/DateTimeFormatter.java differ
     4  Files joda-format-2.3/DateTimeParserBucket.java and joda-format-2.8.1/DateTimeParserBucket.java differ
     5  Only in joda-format-2.8.1: DateTimeParserInternalParser.java
     6  Only in joda-format-2.8.1: DateTimePrinterInternalPrinter.java
     7  Files joda-format-2.3/DateTimePrinter.java and joda-format-2.8.1/DateTimePrinter.java differ
     8  Files joda-format-2.3/FormatUtils.java and joda-format-2.8.1/FormatUtils.java differ
     9  Only in joda-format-2.8.1: InternalParserDateTimeParser.java
    10  Only in joda-format-2.8.1: InternalParser.java
    11  Only in joda-format-2.8.1: InternalPrinterDateTimePrinter.java
    12  Only in joda-format-2.8.1: InternalPrinter.java
    13  Files joda-format-2.3/ISODateTimeFormat.java and joda-format-2.8.1/ISODateTimeFormat.java differ
    14  Files joda-format-2.3/PeriodFormat.java and joda-format-2.8.1/PeriodFormat.java differ
    15  Files joda-format-2.3/PeriodFormatterBuilder.java and joda-format-2.8.1/PeriodFormatterBuilder.java differ
    16  Files joda-format-2.3/PeriodFormatter.java and joda-format-2.8.1/PeriodFormatter.java differ

With the previous version of ta/TA, one was able to refer to a full line, via `ta 13`.

The updated version of ta, shown below, allows individual elements (words) to be referenced, as ta LINE:ELEMENT.

For example, ta 13:2 refers to the second element of the 13th line, which is "joda-format-2.3/ISODateTimeFormat.java". The element can also be negative, which means to use the Nth element from the end of the line, such as ta 4:-2 ("joda-format-2.8.1/DateTimeParserBucket.java").

This functionality was added precisely for the above situation, when getting a summary (diff -rq) of differences between code bases, and wanting to run an individual diff against one of the listed pairs of files, such as:
    13  Files joda-format-2.3/ISODateTimeFormat.java and joda-format-2.8.1/ISODateTimeFormat.java differ

Those files can be referred by their line (13) and indices (second, and second-from-end):

% diff -bwB `ta 13:2 13:-2`

This is expanded to:

% diff -bwB joda-format-2.3/ISODateTimeFormat.java joda-format-2.8.1/ISODateTimeFormat.java

The updated code follows, and is also at GitHub.

alias -g TA='| tee /tmp/cmdoutput | cat -n' 
ta() { 
    arg=$1
    if [ -z "$arg" ]
    then
        tail -1 /tmp/cmdoutput
    else
        a=(${(s.:.)arg})
        first=$a[1]
        second=$a[2]
        if [ -z $second ]
        then
            tail -n +$first /tmp/cmdoutput | head -1
        elif [[ $second[1] = "-" ]]
        then
            cmd="{print \$(NF + 1 + $second)}"
            tail -n +$first /tmp/cmdoutput | head -1 | awk "$cmd"
        else
            cmd="{print \$$second}"
            tail -n +$first /tmp/cmdoutput | head -1 | awk "$cmd"
        fi
    fi
}

Another benefit of this functionality is that because the cache is a single file in the /tmp directory, the references to it as `ta L:E` can be done in different terminal sessions, getting the same result.

Sunday, August 16, 2015

Useful tips to master Zsh

This post is an excellent overview of file/glob modifiers, one of the most powerful yet unknown features unique to Zsh.