bondden/esf-puml

View on GitHub
src/index.es7.js

Summary

Maintainability
F
1 wk
Test Coverage
/**
 * Created by Denis Bondarenko <bond.den@gmail.com> on 29.07.2015.
 */
"use strict";

//todo: implement ESF coding standards
var
  fs      = require('fs-extra'),
  path    = require('path'),
  stream  = require('stream'),
  exec    = require('child_process').exec,
  svgoMod = require('svgo')
  ;

export class PumlRenderer{

  constructor(){

    var H=this;

    this.pumlJar='bin/plantuml.jar';
    this.jarPth =path.resolve(__dirname+'/'+this.pumlJar);

    this.supportedFormats={
      svg:'-tsvg',
      eps:'-teps',
      png:''
    };

    /**
     * private methods
     * @type {Object}
     */
    this._={

      setFmt       :function(fmt='svg'){

        let fmtOpt=H.supportedFormats.svg;
        if(H.supportedFormats.hasOwnProperty(fmt)){
          fmtOpt=H.supportedFormats[fmt];
        }

        return fmtOpt;

      },

      customCwd    :function(buf,cwd){

        let s=buf.toString('utf8');

        let rxs='!include ((((\.\/)?(\.\.\/)+)|(\.\/(\.\.\/)*))((.+)[\r\n]))';
        let m=s.match(new RegExp(rxs,'ig'));
        if(m){

          m.forEach((v,i,a)=>{

            let before=v;
            let m1=before.match(new RegExp(rxs,'i'));
            if(m1 && m1.length && m1.length>1){

              let after=before.replace(
                before,
                '!include '+path.resolve(cwd+'/'+m1[1].replace(/[\/\\]/gi,path.sep))
              );

              s=s.replace(before,after);

            }

          });
        }

        return new Buffer(s,'utf8');

      },

      createQryDir :function(inpDir,outDir,fmt='svg'){

        return  'java -jar '+H.jarPth+' ' +
                '-charset "utf8" ' +
                H._.setFmt(fmt) +' '+
                '-o "'+outDir+'" "'+path.resolve(inpDir+'/**.puml')+'"';

      },

      createQryFile:function(inp,out,fmt='svg'){

        return  'java -jar '+H.jarPth+' ' +
                '-charset "utf8" ' +
                H._.setFmt(fmt) +' '+
                '-o "'+out+'" "'+inp+'"';

      },

      createQryStr:function(inpStr,outFile,fmt='svg'){

        return  'java -jar '+H.jarPth+' ' +
                '-charset "utf8" ' +
                H._.setFmt(fmt) +' '+
                '-pipe > "'+outFile+'"';

      },

      createQryStm:function(fmt='svg'){

        return  'java -jar '+H.jarPth+' ' +
                '-charset "utf8" ' +
                H._.setFmt(fmt) +' '+
                '-pipe';

      }

    };

  }

  //todo: clean after render files by *.cmapx existence
  //todo: limit execution times
  /**
   * [renderDir description]
   * @param  {string}  inpDir input directory, that is to be recursively scanned
   * @param  {string}  outDir output directory to save rendered files
   * @return {Promise}        returns a Promise, tha is resolved on process end.
   */
  renderDir(inpDir,outDir){
    var H=this;
    return new Promise((rs,rj)=>{

      var t=setInterval(()=>{
        process.stdout.write(' ->');
      },1000);

      exec(
        H._.createQryDir(inpDir,outDir),
        {
          encoding:"utf8"
        },
        (e,r)=>{
          if(e){
            clearInterval(t);
            rj(e);
            return e;
          }
          clearInterval(t);
          rs(true);
        }
      );
    });
  }

  cleanSvgFile(fileOut){
    //var H=this;
    return new Promise((rs,rj)=>{

      fs.readFile(fileOut,'utf8',(e1,r1)=>{

        if(e1){
          rj(e1);
          return e1;
        }

        let svgo=new svgoMod({
          plugins: [
            {
              cleanupIDs: false
            }
          ]
        });
        svgo.optimize(r1,r2=>{

          if(!r2){
            let e2=new Error('SVG Optimisation Error');
            rj(e2);
            return e2;
          }

          let format=path.extname(fileOut);
          fileOut=path.resolve(fileOut.replace(format,'opt'+format));

          fs.writeFile(fileOut,r2.data,(e3,r3)=>{

            if(e3){
              rj(e3);
              return e3;
            }

            rs(fileOut);

          });

        });

      });

    });
  }

/**
 * Renders a single puml to an image of one of supported by PlantUML formats
 * @param  {string} fileIn       path to input file
 * @param  {string} dirOut       output directory
 * @param  {string} format='svg' format of rendered image, supported formats: SVG, PNG, EPS. Default: SVG. Optional
 * @return {Promise}             returns a Promise
 */
  renderFile(fileIn,dirOut,format='svg'){
    var H=this;
    return new Promise((rs,rj)=>{

      exec(
        H._.createQryFile(fileIn,dirOut,format),
        {
          encoding:"utf8"
        },
        (e,r)=>{

          if(e){
            rj(e);
            return e;
          }

          let fileOutBase=path.basename(fileIn,'.puml');
          let fileOut    =path.resolve(dirOut+'/'+fileOutBase+'.'+format);

          if(format==='svg'){

            H.cleanSvgFile(fileOut).then(r=>{
              rs(fileOut);
            }).catch((e)=>{
              rj(e);
              return e;
            });

          }else{
            rs(fileOut);
          }

        }
      );

    });
  }

  renderString(strIn,fileOut,format='svg'){
    var H=this;
    return new Promise((rs,rj)=>{

      let qry=H._.createQryStr(strIn,fileOut,format);
      let pcs=exec(qry);

      pcs.stdin.write(strIn,e=>{
        if(e){
          rj(e);
        }
        pcs.stdin.end();
      });

      pcs.on('error',e=>{
        rj(e);
        return e;
      });

      pcs.stderr.on('data',data=>{
        rj('stderr: '+data);
        return 1;
      });

      pcs.on('close', (code)=>{
        if(code!==0){
          rj(new Error(qry+' exited with code '+code));
        }else{
          rs(code);
        }
      });

    });
  }

  renderStringToString(strIn,format='svg'){
    var H=this;
    return new Promise((rs,rj)=>{

      let out='';
      let qry=H._.createQryStm(format);
      let pcs=exec(qry);

      pcs.stdin.write(strIn,e=>{
        if(e){
          rj(e);
        }
        pcs.stdin.end();
      });

      pcs.on('error',e=>{
        rj(e);
        return e;
      });

      pcs.stderr.on('data',d=>{
        rj('stderr: '+d);
        return 1;
      });

      pcs.on('close', code=>{
        if(code!==0){
          rj(new Error(qry+' exited with code '+code));
        }else{
          rs(code);
        }
      });

      pcs.stdout.on('data', d=>{
        out+=d;
      });

      pcs.stdout.on('end',()=>{
        rs(out);
      });

    });
  }

  /**
   * Renders a stream of utf8-encoded puml code to selected format. Currently supported SVG
   * @param  {string} format='svg' format of rendered image, supported formats: SVG. Default: SVG. Optional
   * @param  {string} cwd.         custom CWD. Deault: null. Optional
   * @return {stream.Duplex}       returns a duplex stream, that can be piped in and out.
   */
  stream(format='svg',cwd=null){

    var H=this;

    let pcs=exec(this._.createQryStm(format));

    let stm=stream.Duplex({
      read:function(n){
        //console.log('To read: '+n);
      },
      write:function(d, encoding, next){

        if(cwd){
          d=H._.customCwd(d,cwd);
        }

        pcs.stdin.write(d,null,()=>{
          pcs.stdin.end();
        });

      }
    });

    pcs.stdout.on('data', d=>{
      stm.push(d);
    });

    pcs.stdout.on('end',()=>{
      stm.push(null);
    });

    pcs.stderr.on('data',v=>{
      stm.push(null);
    });

    pcs.on('error', e=>{
      stm.push(null);
    });

    pcs.on('close', ()=>{
      stm.push(null);
    });

    return stm;

  }

}