bondden/esf-puml

View on GitHub
index.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

Object.defineProperty(exports, "__esModule", {
  value: true
});
var fs = require('fs-extra'),
    path = require('path'),
    stream = require('stream'),
    exec = require('child_process').exec,
    svgoMod = require('svgo');

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 setFmt() {
        let fmt = arguments.length <= 0 || arguments[0] === undefined ? 'svg' : arguments[0];


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

        return fmtOpt;
      },

      customCwd: function customCwd(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 createQryDir(inpDir, outDir) {
        let fmt = arguments.length <= 2 || arguments[2] === undefined ? 'svg' : arguments[2];


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

      createQryFile: function createQryFile(inp, out) {
        let fmt = arguments.length <= 2 || arguments[2] === undefined ? 'svg' : arguments[2];


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

      createQryStr: function createQryStr(inpStr, outFile) {
        let fmt = arguments.length <= 2 || arguments[2] === undefined ? 'svg' : arguments[2];


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

      createQryStm: function createQryStm() {
        let fmt = arguments.length <= 0 || arguments[0] === undefined ? 'svg' : arguments[0];


        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) {
    let format = arguments.length <= 2 || arguments[2] === undefined ? 'svg' : arguments[2];

    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) {
    let format = arguments.length <= 2 || arguments[2] === undefined ? 'svg' : arguments[2];

    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) {
    let format = arguments.length <= 1 || arguments[1] === undefined ? 'svg' : arguments[1];

    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() {
    let format = arguments.length <= 0 || arguments[0] === undefined ? 'svg' : arguments[0];
    let cwd = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];


    var H = this;

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

    let stm = stream.Duplex({
      read: function read(n) {
        //console.log('To read: '+n);
      },
      write: function write(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;
  }

}
exports.PumlRenderer = PumlRenderer;
//# sourceMappingURL=.maps/index.es7.js.map