If you have a problem and you think, I'll use sed...

Posted on June 7, 2014 by Tommy McGuire
Labels: software development, job, ruby, unix, java

If you have a problem and you think, "I'll use sed," you now have two problems. One is that you know too much about sed. The second is your original problem, because unless you really know too much about sed, it does not actually do anything.

Recently, I discovered the need to translate a Ruby on Rails module using Builder::XmlMarkup into a small collection of FreeMarker templates. (SOAP requests, to be specific. Isn't my life great?) Considering that the basic structure of the Ruby code was identical to the XML that I needed, I decided to fire up my old sed skills for a line-by-line translation. This is the script I came up with:

sed \
-e 's,xm.\([^(]*\)(\(.*\)).*$,<\1>${(\2)!},' \
-e 's/nvl(\([^,]*\), *.\([^)]*\)\x27)/\1.\2/' \
-e '/ do *$/{s/xm.\([^ ]*\).*/\1/; h; s/\( *\)\(.*\)/\1<\2>/}' \
-e '/end/{ g; s/\( *\)\(.*\)/\1<\/\2>/}'

If that is not sanity-blasting enough, I will now explain what it does. Hide the impressionable youngsters and small pets.

xm is the Builder::XmlMarkup object; a call such as xm.VIN(...) produces XML of the form <VIN>...</VIN>. Therefore, the first expression converts a line like xm.VIN(...) # argh! into <VIN>${(...)!}</VIN> complete with the FreeMarker expansion syntax. The first capturing group is the XML tag and the second is the argument text which becomes the content of the tag.

The contents of many of the tags are properties of objects. However, the object may not actually exist when the Ruby code is used; therefore it used a function nvl(X, 'Y') to evaluate X.Y safely even if X is nil. (I do not know what nvl stands for. "No Value L'here" maybe.) The second sed expression captures the two arguments to nvl and translates them to X.Y directly. In this case, the FreeMarker syntax ${(...)!} will expand to the right side of the ! operator if anything in the ellipsis is invalid and the right side of this is "", the same thing as produced by nvl. Because the second argument to nvl is a string enclosed by single quotes which I am also using in my sed script, I had to use the ASCII hex value 0x27 for the closing single quote in the pattern.

According to the Builder::XmlMarkup documentation, "Any method with a block will be treated as an XML markup tag with nested markup in the block." xm.VEH_INFO do\n...\nend is such a block, creating an XML tag with nested content. Unfortunately, the do was on one line and the end was on a subsequent one. Fortunately, sed has a "hold space" to temporarily store text to be used on a subsequent line. (Learned something new about sed, eh? Don't say I didn't warn you.) The third expression finds do lines and translates them into the bare tag; then uses the h sed command to place the bare tag in hold space and translates the bare tag still in the pattern space into an XML open tag, complete with initial whitespace. Thus xm.VEH_INFO do becomes <VEH_INFO>.

The fourth expression matches the end of the Ruby block and uses the g sed command to replace the end line with the hold space and translates that into an XML close tag, changing end into </VEH_INFO>. This only works for a single nested block, but since I was using this script from within Emacs to produce the templates while I was also noting what needed to be present in the environment for template expansion, working in chunks was eminently feasible.

End result: Success! Yay! Go me!

I'm going to go wash my brain out with gin and tonic now.

active directory applied formal logic ashurbanipal authentication books c c++ comics conference continuations coq data structure digital humanities Dijkstra eclipse virgo electronics emacs goodreads haskell http java job Knuth ldap link linux lisp math naming nimrod notation OpenAM osgi parsing pony programming language protocols python quote R random REST ruby rust SAML scala scheme shell software development system administration theory tip toy problems unix vmware yeti
Member of The Internet Defense League
Site proudly generated by Hakyll.