Convoluted JavaScript Pretty Printing


Published on 2016-05-07


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:

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.

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:

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