Convoluted JavaScript Pretty Printing

Let’s put together a command line JavaScript pretty printer by relying on nixpkgs! But first something completely different.

The following .nixpkgs/config.nix snippet is useful for creating command line programs from jar files which contains a Main-Class refered by MANIFEST.MF. We will go through it shortly.

pkgs: { packageOverrides = self: with self; let mkJarApp = { pname, version, jar }: stdenv.mkDerivation rec { name = pname + "-" + version; src = null; phases = [ "installPhase" ]; installPhase = let app = writeScriptBin "${pname}" '' #!${stdenv.shell} ${jdk}/bin/java -jar ${jdk} $@ ''; in '' mkdir -p $out/bin ln -s ${app}/bin/${pname} $out/bin/${pname} ''; }; in { raccoon = mkJarApp rec { pname = "raccoon"; version = "3.5"; jar = let name = pname + "-" + version; in fetchurl { url = "http://www.onyxbits.de/sites/default/files/download/25/${name}.jar"; sha256 = "15m0nmvyyqkg3y0bspxzz3chkq5lw2l0lbzlnjyxbjw23nxfxqc9"; }; }; } }

In this example we have:

A function packageOverrides which takes an argument self , self being the previous set of packages.

which takes an argument , being the previous set of packages. with self; introduces all of the contained attributes of self into the declaring set, i.e. we can reference regular packages while not having to specify self as a prefix attribute path each time, compare self.jdk to just jdk .

introduces all of the contained attributes of into the declaring set, i.e. we can reference regular packages while not having to specify as a prefix attribute path each time, compare to just . We use let to introduce a function mkJarApp which takes a set of attributes, in this case the set is final and the values in the set are bound to the specific ariates names, like “pname”. mkJarApp creates a nix derivation around a shell script which relies on the depending attribute jdk to run the jar file in jar and pass any command line arguments along to the java main class. We rely on the standard environment’s shell( ${stdenv.shell} ) as our interpreter. We use the phases in stdenv.mkDerivation to inform the standard builder to only run installPhase We use let binding to introduce app into the installPhase scope.

to introduce a function which takes a set of attributes, in this case the set is final and the values in the set are bound to the specific ariates names, like “pname”. Finally we show the mkJarApp usage using the jar file for raccoon. Since the argument set for mkJarApp is closed, i.e. doesn’t specify an ellipsis( ... ), we use another let binding to introduce the name attribute for the fetchurl rhs clause. We use rec to introduce pname and version into the right hand side of the raccoon set.

usage using the jar file for raccoon. Since the argument set for is closed, i.e. doesn’t specify an ellipsis( ), we use another let binding to introduce the name attribute for the rhs clause.

Ok, that is great! We can now wrap BigJars, but what has this to do with JavaScript pretty printing? Well.. just for the heck of mixing different technologies we’re going to rely on the rhino JavaScript engine.

Fortunately for us the rhino attribute is already in nixpkgs, sadly the derivation bound at the attribute doesn’t contain a shell wrapper. So we need to fix that by wrapping the package jar file. So we add the following snippet to our .nixpkgs/config.nix overridePackages set:

rhinosh = mkJarApp { pname = "rhinosh"; version = "1.7R2"; jar = "${rhino}/share/java/js.jar"; };

And we have a shell wrapper for rhino. Now we need a bit of domain knowledge about the rhino runtime.

It defines a load function which can load another JavaScript file into its runtime.

function which can load another JavaScript file into its runtime. It defines a print function which outputs its argument to stdout.

function which outputs its argument to stdout. It defines an array arguments which is zero indexed but, unlike the shell arguments, doesn’t contain the executable name.

which is zero indexed but, unlike the shell arguments, doesn’t contain the executable name. It defines a readFile which takes a path and returns the contents into a string.

Still no way to actually do the pretty printing… Again we’re fortunate that there exists a project called js-beautify which helps us with exactly that. We just need to fetch its pretty printer from the js-beautify repo in our derivation. Now we can define a jspretty package attribute which wraps rhinosh with a JavaScript loader and js-beautify . The only domain knowledge we need about js-beautify is that it attaches its js_beautify function to global in the rhino runtime.

jspretty = stdenv.mkDerivation rec { name = pname + "-" + version; pname = "jspretty"; version = "0.0.1"; src = null; phases = [ "installPhase" ]; installPhase = let beauty = fetchurl { url = https://raw.githubusercontent.com/beautify-web/js-beautify/bdb91a2c307cc94c6860cedd4c0149c28762e82d/js/lib/beautify.js; sha256 = "1gwf3s37h5a35b2vx33hz26wahbsa84n5sgb5nlwj2g0dnicl96j"; }; loader = writeTextFile { name = "loader.js"; text = '' load(arguments[0]) print(global.js_beautify(readFile(arguments[1]))); '';}; app = writeScriptBin "${pname}" '' #!${stdenv.shell} ${rhinosh}/bin/rhinosh ${loader} ${beauty} $@ ''; in '' mkdir -p $out/bin ln -s ${app}/bin/${pname} $out/bin/${pname} ''; };

We give our jspretty script a version and a name.. We have no sources so we set the derivation src attribute to null . Again we only care about the installPhase and we introduce some attributes into its rhs scope:

beauty points to the JavaScript pretty printer source file.

points to the JavaScript pretty printer source file. loader is a JavaScript loader file we use writeTextFile to get into nix store. It purpose is to load the js-beautify library JavaScript file, a target JavaScript file and apply the js_beautify function to the target, printing the result to stdout.

is a JavaScript loader file we use to get into nix store. It purpose is to load the js-beautify library JavaScript file, a target JavaScript file and apply the function to the target, printing the result to stdout. app is the target shell script which actually calls rhino with js-beautify and our target JavaScript.

is the target shell script which actually calls rhino with js-beautify and our target JavaScript. app and loader could of course be parametrised in order to create different JavaScript pretty printers for different conventions, intentionally left as an exercise for the reader :)

Finally we can install jspretty into a profile:

usr@lcl $ nix-env -iA jspretty

Which just says that we define an command to be run just before the buffer is written to disk, that command being our jspretty shell script fed with the file. Activating the profile, gives us access to the jspretty shell script.

usr@lcl $ jspretty ugly.js > pretty.js

Comments

Please enable JavaScript to view the comments powered by Disqus.