diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..9c0d9770
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,21 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+
+# Change these settings to your own preference
+indent_style = tab
+indent_size = 4
+
+# We recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..16e959be
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+# and /bower_components/* ignored by default
+
+# Ignore built files except build/index.js
+/node_modules/*
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 00000000..a209ab6c
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+ "root": true,
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "ecmaVersion": 2017
+ },
+ "env": {
+ "node": true,
+ "es6": true
+ },
+ "rules": {
+ "no-console": 0
+ },
+ "globals": {
+ "console": true,
+ "log": true
+ }
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..21256661
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index cdcd2d66..cf8edbfd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,41 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
-testing
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# npm, yarn, build etc
+.vscode
+.sass-cache
+yarn.lock
+package-lock.json
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Optional eslint cache
+.eslintcache
+
+# Os
+Thumbs.db
+ehthumbs.db
+Desktop.ini
+.directory
+*~
+.DS_Store
+
+# maven & eclipse
+target/
+.classpath
+.project
+
+# project specific
+.tmp
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..4171b836
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "printWidth": 100,
+ "useTabs": true,
+ "semi": true,
+ "singleQuote": true
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..d9340af3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "plantuml-icon-font-sprites",
+ "version": "0.1.0",
+ "author": "tupadr3",
+ "description": "A usefull description",
+ "license": "MIT",
+ "scripts": {
+ "devel": "node ./src/lib/index.js --devel",
+ "build": "rm .tmp/build -rf && node ./src/lib/index.js -c 4 --release",
+ "clean": "rm .tmp -rf"
+ },
+ "devDependencies": {},
+ "dependencies": {
+ "bluebird": "^3.5.1",
+ "command-line-args": "^5.1.1",
+ "command-line-usage": "^6.0.2",
+ "dateformat": "^3.0.3",
+ "eslint": "^5.9.0",
+ "extend": "^3.0.1",
+ "fs-extra": "^6.0.1",
+ "isomorphic-git": ">=1.8.2",
+ "lodash": "^4.17.10",
+ "minimist": "^1.2.0",
+ "ncp": "^2.0.0",
+ "os": "^0.1.1",
+ "pngjs": "^3.3.3",
+ "progress": "^2.0.0",
+ "winston": "^2.4.2",
+ "xml2js": "^0.4.19"
+ }
+}
diff --git a/src/assets/bin/plantuml.jar b/src/assets/bin/plantuml.jar
new file mode 100644
index 00000000..c9fdc185
Binary files /dev/null and b/src/assets/bin/plantuml.jar differ
diff --git a/src/assets/bin/rsvg-convert.exe b/src/assets/bin/rsvg-convert.exe
new file mode 100644
index 00000000..02d59919
Binary files /dev/null and b/src/assets/bin/rsvg-convert.exe differ
diff --git a/src/lib/cliOptions.js b/src/lib/cliOptions.js
new file mode 100644
index 00000000..bcd82a18
--- /dev/null
+++ b/src/lib/cliOptions.js
@@ -0,0 +1,114 @@
+const os = require('os');
+
+let cpuCount = os.cpus().length;
+
+if (cpuCount > 2) {
+ cpuCount = Number((cpuCount / 2).toFixed(0));
+ if (cpuCount > 6) {
+ cpuCount--;
+ cpuCount--;
+ }
+}
+
+let binRsvgDft = os.platform() === 'linux' ? 'rsvg-convert' : 'src/assets/bin/rsvg-convert.exe';
+
+module.exports = [
+ {
+ name: 'limit',
+ type: Number,
+ alias: 'l',
+ defaultValue: 0,
+ },
+ {
+ name: 'concurrency',
+ type: Number,
+ alias: 'c',
+ defaultValue: cpuCount,
+ },
+ {
+ name: 'progress',
+ type: Boolean,
+ alias: 'p',
+ defaultValue: true,
+ },
+ {
+ name: 'verbose',
+ type: Boolean,
+ alias: 'v',
+ defaultValue: false,
+ },
+ {
+ name: 'formats',
+ type: String,
+ multiple: true,
+ defaultValue: ['png', 'svg', 'puml'],
+ typeLabel: '{underline format} ...',
+ },
+ {
+ name: 'release',
+ type: Boolean,
+ defaultValue: false,
+ },
+ {
+ name: 'colors',
+ type: String,
+ multiple: true,
+ defaultValue: ['black'],
+ typeLabel: '{underline color} ...',
+ },
+ {
+ name: 'sizes',
+ type: Number,
+ multiple: true,
+ defaultValue: [48],
+ typeLabel: '{underline size} ...',
+ },
+ {
+ name: 'icons',
+ type: String,
+ multiple: true,
+ defaultValue: [],
+ typeLabel: '{underline icon} ...',
+ },
+ {
+ name: 'fonts',
+ type: String,
+ multiple: true,
+ defaultValue: [],
+ typeLabel: '{underline font} ...',
+ },
+ {
+ name: 'binPlantuml',
+ type: String,
+ defaultValue: 'src/assets/bin/plantuml.jar',
+ description: 'The path to the PlantUML executable (.jar) to use',
+ },
+ {
+ name: 'binRsvg',
+ type: String,
+ defaultValue: binRsvgDft,
+ description: 'The path to the rsvg-convert executable to use',
+ },
+ {
+ name: 'build',
+ type: String,
+ defaultValue: 'build',
+ },
+ {
+ name: 'temp',
+ type: String,
+ defaultValue: '.tmp',
+ },
+ {
+ name: 'devel',
+ type: Boolean,
+ defaultValue: false,
+ },
+ {
+ name: 'help',
+ alias: 'h',
+ type: Boolean,
+ description: 'Display this usage guide.',
+ defaultValue: false,
+ },
+];
diff --git a/src/lib/config.js b/src/lib/config.js
new file mode 100644
index 00000000..2c9e4c27
--- /dev/null
+++ b/src/lib/config.js
@@ -0,0 +1,58 @@
+/**
+ * @authot tupadr3
+ */
+const cliArgs = require('command-line-args'),
+ cliOptions = require('./cliOptions.js'),
+ path = require('path');
+
+function initConfig() {
+ const cfg = cliArgs(cliOptions);
+ const fontsDef = require('./fonts').def();
+ let fonts = cfg.fonts;
+ cfg.fonts = [];
+
+ // validate fonts
+ if (fonts.length > 0) {
+ fonts.forEach((item) => {
+ const found = fontsDef.find(function (element) {
+ return element.type === item;
+ });
+ if (found) {
+ cfg.fonts.push(found);
+ } else {
+ throw new Error('Font ' + item + ' not found');
+ }
+ });
+ } else {
+ cfg.fonts = fontsDef;
+ }
+
+ cfg.png = false;
+ cfg.puml = false;
+ cfg.svg = false;
+
+ if (cfg.formats) {
+ cfg.formats.forEach((item) => {
+ if (item === 'png') {
+ cfg.png = true;
+ }
+ if (item === 'puml') {
+ cfg.puml = true;
+ }
+ if (item === 'svg') {
+ cfg.svg = true;
+ }
+ });
+ }
+
+ // setup dirs
+ cfg.dirs = {};
+ cfg.dirs.temp = path.resolve(cfg.temp);
+ cfg.dirs.project = path.resolve('./');
+ cfg.dirs.generated = path.resolve(cfg.temp + '/generated');
+ cfg.dirs.build = path.resolve(cfg.temp + '/' + cfg.build);
+ cfg.dirs.fonts = path.resolve(cfg.temp + '/fonts');
+ return cfg;
+}
+
+module.exports = initConfig();
diff --git a/src/lib/fonts.js b/src/lib/fonts.js
new file mode 100644
index 00000000..2fa07ac6
--- /dev/null
+++ b/src/lib/fonts.js
@@ -0,0 +1,340 @@
+/**
+ * @authot tupadr3
+ */
+const log = require('./logger'),
+ fs = require('fs-extra'),
+ git = require('isomorphic-git'),
+ http = require('isomorphic-git/http/node'),
+ PNG = require('pngjs').PNG;
+
+function def() {
+ const fontDefs = [
+ {
+ prefix: 'FA5',
+ name: 'font-awesome-5',
+ type: 'fa5',
+ repo: 'https://github.com/FortAwesome/Font-Awesome.git',
+ branch: '5.15.3',
+ },
+ {
+ prefix: 'FA',
+ name: 'font-awesome',
+ type: 'fa',
+ repo: 'https://github.com/FortAwesome/Font-Awesome.git',
+ branch: 'fa-4',
+ },
+ {
+ prefix: 'DEV',
+ name: 'devicons',
+ type: 'dev',
+ repo: 'https://github.com/vorillaz/devicons.git',
+ branch: 'master',
+ },
+ {
+ prefix: 'GOV',
+ name: 'govicons',
+ type: 'gov',
+ repo: 'https://github.com/540co/govicons.git',
+ branch: '1.5.1',
+ },
+ {
+ prefix: 'WEATHER',
+ name: 'weather',
+ type: 'weather',
+ repo: 'https://github.com/erikflowers/weather-icons.git',
+ branch: 'master',
+ },
+ {
+ prefix: 'MATERIAL',
+ name: 'material',
+ type: 'material',
+ repo: 'https://github.com/google/material-design-icons.git',
+ branch: '3.0.2',
+ },
+ {
+ prefix: 'DEV2',
+ name: 'devicons2',
+ type: 'dev2',
+ repo: 'https://github.com/devicons/devicon.git',
+ branch: 'v2.12.0',
+ },
+ ];
+ return fontDefs;
+}
+
+async function load(cfg) {
+ // Prep the folder structue
+ let buildFolder = cfg.dirs.build;
+ await fs.ensureDirSync(buildFolder);
+
+ let work = [];
+ for (let item of cfg.fonts) {
+ try {
+ item.path = await repo(cfg, item);
+ } catch (err) {
+ throw err;
+ }
+
+ let buildSetFolder = buildFolder + '/' + item.type;
+ await fs.ensureDirSync(buildSetFolder);
+ await fs.ensureDirSync(cfg.dirs.generated);
+
+ if (cfg.svg) {
+ await fs.ensureDirSync(buildSetFolder + '/svg');
+ }
+ if (cfg.puml) {
+ await fs.ensureDirSync(buildSetFolder + '/puml');
+ }
+ if (cfg.png) {
+ await fs.ensureDirSync(buildSetFolder + '/png');
+ for (let index in cfg.sizes) {
+ await fs.ensureDirSync(buildSetFolder + '/png/' + cfg.sizes[index]);
+ }
+ }
+
+ const fontHandler = require('./handler/' + item.type);
+ let fontData = await fontHandler.load(cfg, item);
+
+ if (cfg.icons.length > 0) {
+ log.debug(`Filtering selection to ${cfg.icons}`);
+ fontData = fontData.filter((item) => {
+ if (cfg.icons.includes(`${item.type}-${item.id}`)) {
+ return true;
+ }
+ return false;
+ });
+ }
+ work = work.concat(fontData);
+ }
+
+ if (cfg.limit > 0) {
+ log.debug(`Trimming selection from ${work.length} to ${cfg.limit}`);
+ work = work.slice(0, cfg.limit);
+ }
+
+ return work;
+}
+
+async function repo(cfg, item) {
+ log.info('Loading repo ' + item.repo + ' into ' + cfg.dirs.fonts);
+
+ const repoDir = cfg.dirs.fonts + '/' + item.type,
+ repoGitDir = cfg.dirs.fonts + '/' + item.type + '/.git';
+
+ try {
+ await fs.ensureDirSync(repoDir);
+
+ // check if it already exists
+ log.debug(`checking dir ${repoGitDir} for repo`);
+ const repoExists = fs.existsSync(repoGitDir);
+
+ if (!repoExists) {
+ await git.clone({
+ fs,
+ http,
+ dir: repoDir,
+ url: item.repo,
+ singleBranch: true,
+ ref: item.branch,
+ depth: 10,
+ });
+ }
+
+ log.debug(`checkout ${item.repo} branch:${item.branch} completed to dir ${repoDir}`);
+ } catch (err) {
+ log.error('repo error', err);
+ throw err;
+ }
+ return repoDir;
+}
+
+async function generate(cfg, item) {
+ if (cfg.svg) {
+ try {
+ await generateSvg(cfg, item);
+ } catch (error) {
+ log.error(error);
+ }
+ }
+
+ if (cfg.png) {
+ for (let index in cfg.sizes) {
+ try {
+ await generatePng(cfg, item, cfg.sizes[index]);
+ } catch (error) {
+ log.error(error);
+ }
+ }
+ }
+ if (cfg.puml) {
+ try {
+ await generatePuml(cfg, item);
+ } catch (error) {
+ log.error(error);
+ }
+ }
+}
+
+function generateSvg(cfg, item) {
+ log.debug('Generating svg for ' + item.type + '-' + item.id);
+
+ return new Promise((resolve) => {
+ var svgCode = getSvgCode(cfg, item);
+ var filename = cfg.dirs.build + '/' + item.type + '/svg/' + item.id + '.svg';
+ log.debug('Wrting svg for ' + item.type + '-' + item.id + ' to ' + filename);
+ fs.writeFileSync(filename, svgCode);
+ resolve(filename);
+ });
+}
+
+function getSvgCode(cfg, item) {
+ const fontHandler = require('./handler/' + item.type);
+ let svgCode = fontHandler.getSvgCode(cfg, item);
+ return svgCode;
+}
+
+function generatePng(cfg, item, size, path) {
+ return new Promise(function (resolve, reject) {
+ let destPath = path;
+ if (!destPath) {
+ destPath = cfg.dirs.build + '/' + item.type + '/png/' + size + '/' + item.id + '.png';
+ }
+
+ log.debug('Generating png for ' + item.type + '-' + item.id);
+
+ let cliparams = ['-a', '-w', size, '-h', size, '-f', 'png', '-o', destPath],
+ error;
+ log.debug(cfg.binRsvg, cliparams.join(' '));
+ let rsvgConvert = require('child_process').spawn(cfg.binRsvg, cliparams);
+ rsvgConvert.stderr.on('data', (data) => {
+ error += data.toString();
+ });
+ rsvgConvert.once('close', function (code) {
+ if (code > 0) {
+ return reject(error);
+ }
+ resolve(destPath);
+ });
+ rsvgConvert.stdin.end(getSvgCode(cfg, item));
+ });
+}
+
+function generatePuml(cfg, item) {
+ return new Promise(async function (resolve, reject) {
+ log.debug('Generating plantuml for ' + item.type + '-' + item.id);
+
+ let pngInterFileName = cfg.dirs.generated + '/' + item.type + '-png-48-' + item.id + '.png';
+ let pngFileName = await generatePng(cfg, item, 48, pngInterFileName);
+
+ // now we need to modify the png a little bit
+ fs.createReadStream(pngFileName)
+ .pipe(
+ new PNG({
+ colorType: 2,
+ })
+ )
+ .on('error', function (err) {
+ log.error(err);
+ })
+ .on('parsed', async function () {
+ log.debug('Modifing for puml generation for icon ' + item.type + '-' + item.id);
+
+ for (var y = 0; y < this.height; y++) {
+ for (var x = 0; x < this.width; x++) {
+ var idx = (this.width * y + x) << 2;
+ // invert color
+ if (this.data[idx] > 0) {
+ this.data[idx] = 0;
+ this.data[idx + 1] = 0;
+ this.data[idx + 2] = 0;
+ }
+ }
+ }
+
+ // i don't know if we have to wait
+ this.pack().pipe(fs.createWriteStream(pngInterFileName));
+
+ let pumlCode;
+ try {
+ pumlCode = await getPumlCode(cfg, item, pngInterFileName);
+ } catch (error) {
+ reject(error);
+ return;
+ }
+
+ var filename = cfg.dirs.build + '/' + item.type + '/puml/' + item.id + '.puml';
+ await fs.writeFileSync(filename, pumlCode);
+ resolve(filename);
+ });
+ });
+}
+
+function getPumlCode(cfg, item, pngPath) {
+ return new Promise(function (resolve, reject) {
+ var plantumlJar,
+ result = '',
+ error = '';
+
+ var template =
+ '@startuml' +
+ '\n' +
+ '{sprite}' +
+ '\n' +
+ '!define {set}_{entity}(_alias) ENTITY(rectangle,black,{id},_alias,{set} {entity})' +
+ '\n' +
+ '!define {set}_{entity}(_alias, _label) ENTITY(rectangle,black,{id},_label, _alias,{set} {entity})' +
+ '\n' +
+ '!define {set}_{entity}(_alias, _label, _shape) ENTITY(_shape,black,{id},_label, _alias,{set} {entity})' +
+ '\n' +
+ '!define {set}_{entity}(_alias, _label, _shape, _color) ENTITY(_shape,_color,{id},_label, _alias,{set} {entity})' +
+ '\n' +
+ 'skinparam folderBackgroundColor<<{set} {entity}>> White' +
+ '\n' +
+ '@enduml';
+
+ var plantumlParams = [
+ '-Djava.awt.headless=true',
+ '-jar',
+ cfg.binPlantuml,
+ '-encodesprite',
+ '16',
+ pngPath,
+ ];
+
+ log.debug('java ' + plantumlParams.join(' '));
+
+ plantumlJar = require('child_process').spawn('java', plantumlParams);
+ plantumlJar.stdout.on('data', (data) => {
+ result += data.toString();
+ });
+ plantumlJar.stderr.on('data', (data) => {
+ error += data.toString();
+ });
+ plantumlJar.once('close', function (code) {
+ if (code > 0) {
+ reject(error);
+ return;
+ }
+
+ var out = template.substr(0);
+
+ var params = {
+ sprite: result.replace('$' + item.type, '$' + item.id.replace(/-/g, '_')),
+ set: item.prefix.toUpperCase(),
+ entity: item.id.replace(/-/g, '_').toUpperCase(),
+ id: item.id.replace(/-/g, '_'),
+ };
+
+ Object.keys(params).forEach(function (key) {
+ out = out.replace(new RegExp('{' + key + '}', 'g'), params[key]);
+ });
+ resolve(out);
+ });
+ });
+}
+
+module.exports = {
+ def: def,
+ load: load,
+ generate: generate,
+};
diff --git a/src/lib/handler/dev.js b/src/lib/handler/dev.js
new file mode 100644
index 00000000..f3b555b7
--- /dev/null
+++ b/src/lib/handler/dev.js
@@ -0,0 +1,107 @@
+const fs = require('fs-extra'),
+ util = require('util'),
+ log = require('./../logger'),
+ readFile = util.promisify(fs.readFile),
+ loadash = require('lodash'),
+ parseXml = util.promisify(require('xml2js').parseString);
+
+async function load(cfg, item) {
+ let iconList = await loadIcons(cfg, item);
+ let icons = await loadFontData(cfg, item, iconList);
+ return icons;
+}
+
+async function loadIcons(cfg, item) {
+ log.debug("Loading devicons id's");
+
+ let content = await readFile(item.path + '/css/devicons.css');
+ let lines = content.toString();
+
+ let match,
+ result = [];
+
+ const regex = /devicons-([\w-]*).*\s.*"\S([0-9a-f]+)"/gm;
+ while ((match = regex.exec(lines))) {
+ result.push({
+ id: match[1],
+ unicodeHex: match[2],
+ unicodeDec: parseInt(match[2], 16)
+ });
+ }
+ return result;
+}
+
+async function loadFontData(cfg, item, iconList) {
+ log.debug('Loading devicons font-data');
+
+ let content = await readFile(item.path + '/fonts/devicons.svg');
+ content = content.toString('utf-8');
+
+ let parsedXml = await parseXml(content),
+ glyph = parsedXml.svg.defs[0].font[0].glyph,
+ svghorz = parsedXml.svg.defs[0].font[0].$['horiz-adv-x'],
+ offset = parsedXml.svg.defs[0].font[0]['font-face'][0].$['descent'],
+ size = parsedXml.svg.defs[0].font[0]['font-face'][0].$['units-per-em'];
+
+ let fontData = glyph
+ .filter(data => {
+ if (!data.$.unicode) {
+ return false;
+ }
+ return true;
+ })
+ .map(data => {
+ return {
+ data: data.$,
+ unicodeDec: data.$.unicode.charCodeAt(0)
+ };
+ });
+
+ let indexFontData = await loadash.keyBy(fontData, 'unicodeDec');
+
+ let icons = iconList.map(icon => {
+ let iconConfig = {
+ id: icon.id.split('-').join('_'),
+ type: item.type,
+ prefix: item.prefix,
+ unicodeHex: icon.unicodeHex,
+ unicodeDec: icon.unicodeDec,
+ data: indexFontData[icon.unicodeDec].data
+ };
+ iconConfig.advWidth = parseInt(iconConfig.data['horiz-adv-x'] || svghorz);
+ iconConfig.offset = parseInt(offset);
+ iconConfig.size = parseInt(size);
+ return iconConfig;
+ });
+
+ return icons;
+}
+function getSvgCode(cfg, item) {
+ log.debug('Getting svg code for ' + item.type + '-' + item.id);
+
+ let params = {
+ color: cfg.color || 'black',
+ path: item.data.d,
+ width: item.advWidth,
+ height: item.size,
+ shiftX: 0,
+ shiftY: -item.size - item.offset
+ };
+
+ return (
+ ``
+ );
+}
+
+module.exports = {
+ load: load,
+ getSvgCode: getSvgCode
+};
diff --git a/src/lib/handler/dev2.js b/src/lib/handler/dev2.js
new file mode 100644
index 00000000..4382e3e0
--- /dev/null
+++ b/src/lib/handler/dev2.js
@@ -0,0 +1,108 @@
+const fs = require('fs-extra'),
+ util = require('util'),
+ log = require('./../logger'),
+ readFile = util.promisify(fs.readFile),
+ lodash = require('lodash'),
+ parseXml = util.promisify(require('xml2js').parseString);
+
+async function load(cfg, item) {
+ return await loadFontData(cfg, item, await loadIcons(cfg, item));
+}
+
+async function loadIcons(cfg, item) {
+ log.debug("Loading devicons2 id's");
+
+ let content = await readFile(item.path + '/devicon.css');
+ let lines = content.toString();
+
+ let match,
+ result = [];
+
+ const regex = /.devicon-([\w-]*):before\s?{\s*content:\s?"\\([\w|\d]*)";\s*}/;
+ while ((match = regex.exec(lines))) {
+ log.verbose('DEV2 - found ' + match[1]);
+ result.push({
+ id: match[1].replace('-plain', ''),
+ unicodeHex: match[2],
+ unicodeDec: parseInt(match[2], 16)
+ });
+ lines = lines.replace(match[0], '');
+ }
+ return result;
+}
+
+async function loadFontData(cfg, item, iconList) {
+ log.debug('Loading devicons2 font-data');
+
+ let content = await readFile(item.path + '/fonts/devicon.svg');
+ content = content.toString('utf-8');
+
+ let parsedXml = await parseXml(content),
+ glyph = parsedXml.svg.defs[0].font[0].glyph,
+ svghorz = parsedXml.svg.defs[0].font[0].$['horiz-adv-x'],
+ offset = parsedXml.svg.defs[0].font[0]['font-face'][0].$['descent'],
+ size = parsedXml.svg.defs[0].font[0]['font-face'][0].$['units-per-em'];
+
+ let fontData = glyph
+ .filter(data => {
+ if (!data.$.unicode) {
+ return false;
+ }
+ return true;
+ })
+ .map(data => {
+ return {
+ data: data.$,
+ unicodeDec: data.$.unicode.charCodeAt(0)
+ };
+ });
+
+ let indexFontData = await lodash.keyBy(fontData, 'unicodeDec');
+
+ let icons = iconList.map(icon => {
+ let iconConfig = {
+ id: icon.id.split('-').join('_'),
+ type: item.type,
+ prefix: item.prefix,
+ unicodeHex: icon.unicodeHex,
+ unicodeDec: icon.unicodeDec,
+ data: indexFontData[icon.unicodeDec].data
+ };
+ iconConfig.advWidth = parseInt(iconConfig.data['horiz-adv-x'] || svghorz);
+ iconConfig.offset = parseInt(offset);
+ iconConfig.size = parseInt(size);
+ return iconConfig;
+ });
+
+ return icons;
+}
+
+function getSvgCode(cfg, item) {
+ log.debug('Getting svg code for ' + item.type + '-' + item.id);
+
+ let params = {
+ color: cfg.color || 'black',
+ path: item.data.d,
+ width: item.advWidth,
+ height: item.size,
+ shiftX: 0,
+ shiftY: -item.size - item.offset
+ };
+
+ return (
+ `\n` +
+ `\t\n` +
+ `\t\t\n` +
+ `\t\t\t\n` +
+ `\t\t\n` +
+ `\t\n` +
+ ``
+ );
+}
+
+module.exports = {
+ load: load,
+ getSvgCode: getSvgCode
+};
diff --git a/src/lib/handler/fa.js b/src/lib/handler/fa.js
new file mode 100644
index 00000000..3d4065fc
--- /dev/null
+++ b/src/lib/handler/fa.js
@@ -0,0 +1,108 @@
+const fs = require('fs-extra'),
+ util = require('util'),
+ log = require('./../logger'),
+ readFile = util.promisify(fs.readFile),
+ loadash = require('lodash'),
+ parseXml = util.promisify(require('xml2js').parseString),
+ extend = require('extend');
+
+async function load(cfg, item) {
+ let iconList = await loadIcons(cfg, item);
+ let icons = await loadFontData(cfg, item, iconList);
+ return icons;
+}
+
+async function loadIcons(cfg, item) {
+ log.debug("Loading fa id's");
+ let content = await readFile(item.path + '/less/variables.less');
+ let lines = content.toString();
+
+ let match,
+ result = [];
+
+ const regex = /@fa-var-([\w-]+):\s*"\\([0-9a-f]+)";/g;
+ while ((match = regex.exec(lines))) {
+ result.push({
+ id: match[1],
+ unicodeHex: match[2],
+ unicodeDec: parseInt(match[2], 16)
+ });
+ }
+ return result;
+}
+
+async function loadFontData(cfg, item, iconList) {
+ log.debug('Loading fa font-data');
+ let content = await readFile(item.path + '/fonts/fontawesome-webfont.svg');
+ content = content.toString('utf-8');
+
+ let parsedXml = await parseXml(content),
+ glyph = parsedXml.svg.defs[0].font[0].glyph,
+ svghorz = parsedXml.svg.defs[0].font[0].$['horiz-adv-x'],
+ offset = parsedXml.svg.defs[0].font[0]['font-face'][0].$['descent'],
+ size = parsedXml.svg.defs[0].font[0]['font-face'][0].$['units-per-em'];
+
+ let fontData = glyph
+ .filter(data => {
+ if (!data.$.unicode) {
+ return false;
+ }
+ return true;
+ })
+ .map(data => {
+ return {
+ data: data.$,
+ unicodeDec: data.$.unicode.charCodeAt(0)
+ };
+ });
+
+ let indexFontData = await loadash.keyBy(fontData, 'unicodeDec');
+
+ let icons = iconList.map(icon => {
+ let iconConfig = {
+ id: icon.id.split('-').join('_'),
+ type: item.type,
+ prefix: item.prefix,
+ unicodeHex: icon.unicodeHex,
+ unicodeDec: icon.unicodeDec,
+ data: indexFontData[icon.unicodeDec].data
+ };
+ iconConfig.advWidth = parseInt(iconConfig.data['horiz-adv-x'] || svghorz);
+ iconConfig.offset = parseInt(offset);
+ iconConfig.size = parseInt(size);
+ return iconConfig;
+ });
+
+ return icons;
+}
+
+function getSvgCode(cfg, item) {
+ log.debug('Getting svg code for ' + item.type + '-' + item.id);
+
+ let params = {
+ color: cfg.color || 'black',
+ path: item.data.d,
+ width: item.advWidth,
+ height: item.size,
+ shiftX: item.advWidth / 10 / 2,
+ shiftY: -item.size - item.offset - item.size / 10 / 2
+ };
+
+ return (
+ `\n` +
+ `\t\n` +
+ `\t\t\n` +
+ `\t\t\t\n` +
+ `\t\t\n` +
+ `\t\n` +
+ ``
+ );
+}
+
+module.exports = {
+ load: load,
+ getSvgCode: getSvgCode
+};
diff --git a/src/lib/handler/fa5.js b/src/lib/handler/fa5.js
new file mode 100644
index 00000000..41fae246
--- /dev/null
+++ b/src/lib/handler/fa5.js
@@ -0,0 +1,132 @@
+const fs = require('fs-extra'),
+ util = require('util'),
+ log = require('./../logger'),
+ readFile = util.promisify(fs.readFile),
+ loadash = require('lodash'),
+ parseXml = util.promisify(require('xml2js').parseString);
+
+async function load(cfg, item) {
+ let iconList = await loadIcons(cfg, item);
+ let icons = await loadFontData(cfg, item, iconList);
+ return icons;
+}
+
+async function loadIcons(cfg, item) {
+ log.debug("Loading fa id's");
+ let content = await readFile(item.path + '/less/_variables.less');
+ let lines = content.toString();
+
+ let match,
+ result = [];
+
+ const regex = /@fa-var-([\w-]+):\s*"\\([0-9a-f]+)";/g;
+ while ((match = regex.exec(lines))) {
+ result.push({
+ id: match[1],
+ unicodeHex: match[2],
+ unicodeDec: parseInt(match[2], 16)
+ });
+ }
+ return result;
+}
+
+async function loadFontData(cfg, item, iconList) {
+ log.debug('Loading fa5 font-data');
+
+ let filesList = [
+ item.path + '/webfonts/fa-regular-400.svg',
+ item.path + '/webfonts/fa-brands-400.svg',
+ item.path + '/webfonts/fa-solid-900.svg'
+ ];
+
+ let fontData = [];
+ for (let key in filesList) {
+ let item = filesList[key];
+ let content = await readFile(item);
+ content = content.toString('utf-8');
+
+ let parsedXml = await parseXml(content),
+ glyph = parsedXml.svg.defs[0].font[0].glyph,
+ svghorz = parsedXml.svg.defs[0].font[0].$['horiz-adv-x'],
+ offset = parsedXml.svg.defs[0].font[0]['font-face'][0].$['descent'],
+ size = parsedXml.svg.defs[0].font[0]['font-face'][0].$['units-per-em'];
+
+ let fontDataItem = glyph
+ .filter(data => {
+ if (!data.$.unicode) {
+ return false;
+ }
+ return true;
+ })
+ .map(data => {
+ return {
+ data: data.$,
+ unicodeDec: data.$.unicode.charCodeAt(0),
+ svghorz: svghorz,
+ offset: -offset,
+ size: size
+ };
+ });
+ fontData = fontData.concat(fontDataItem);
+ }
+
+ let indexFontData = await loadash.keyBy(fontData, 'unicodeDec');
+
+ let icons = iconList
+ .filter(icon => {
+ if (!indexFontData[icon.unicodeDec]) {
+ log.debug(`Skipping ${icon.unicodeHex}`);
+ return false;
+ }
+ return true;
+ })
+ .map(icon => {
+ let iconData = indexFontData[icon.unicodeDec];
+
+ let iconConfig = {
+ id: icon.id.split('-').join('_'),
+ type: item.type,
+ prefix: item.prefix,
+ unicodeHex: icon.unicodeHex,
+ unicodeDec: icon.unicodeDec,
+ data: iconData.data,
+ offset: parseInt(iconData.offset),
+ size: parseInt(iconData.size),
+ advWidth: parseInt(iconData.data['horiz-adv-x'] || iconData.svghorz)
+ };
+ return iconConfig;
+ });
+
+ return icons;
+}
+
+function getSvgCode(cfg, item) {
+ log.debug('Getting svg code for ' + item.type + '-' + item.id);
+
+ let params = {
+ color: cfg.color || 'black',
+ path: item.data.d,
+ width: item.advWidth,
+ height: item.size,
+ shiftX: item.advWidth / 10 / 2,
+ shiftY: -item.size +item.offset -item.size / 10 / 2
+ };
+
+ return (
+ `\n` +
+ `\t\n` +
+ `\t\t\n` +
+ `\t\t\t\n` +
+ `\t\t\n` +
+ `\t\n` +
+ ``
+ );
+}
+
+module.exports = {
+ load: load,
+ getSvgCode: getSvgCode
+};
diff --git a/src/lib/handler/gov.js b/src/lib/handler/gov.js
new file mode 100644
index 00000000..d412296a
--- /dev/null
+++ b/src/lib/handler/gov.js
@@ -0,0 +1,128 @@
+const fs = require('fs-extra'),
+ util = require('util'),
+ log = require('./../logger'),
+ readFile = util.promisify(fs.readFile),
+ loadash = require('lodash'),
+ parseXml = util.promisify(require('xml2js').parseString);
+
+async function load(cfg, item) {
+ let iconList = await loadIcons(cfg, item);
+ let icons = await loadFontData(cfg, item, iconList);
+ return icons;
+}
+
+async function loadIcons(cfg, item) {
+ log.debug("Loading fa id's");
+ let content = await readFile(item.path + '/less/variables.less');
+ let lines = content.toString();
+
+ let match,
+ result = [];
+
+ const regex = /@gi-([\w-]+):\s*"\\([0-9a-f]+)";/g;
+ while ((match = regex.exec(lines))) {
+ result.push({
+ id: match[1],
+ unicodeHex: match[2],
+ unicodeDec: parseInt(match[2], 16)
+ });
+ }
+ return result;
+}
+
+async function loadFontData(cfg, item, iconList) {
+ log.debug('Loading fa5 font-data');
+
+ let filesList = [item.path + '/fonts/govicons-webfont.svg'];
+
+ let fontData = [];
+ for (let key in filesList) {
+ let item = filesList[key];
+ let content = await readFile(item);
+ content = content.toString('utf-8');
+
+ let parsedXml = await parseXml(content),
+ glyph = parsedXml.svg.defs[0].font[0].glyph,
+ svghorz = parsedXml.svg.defs[0].font[0].$['horiz-adv-x'],
+ offset = parsedXml.svg.defs[0].font[0]['font-face'][0].$['descent'],
+ size = parsedXml.svg.defs[0].font[0]['font-face'][0].$['units-per-em'];
+
+ let fontDataItem = glyph
+ .filter(data => {
+ if (!data.$.unicode) {
+ return false;
+ }
+ return true;
+ })
+ .map(data => {
+ return {
+ data: data.$,
+ unicodeDec: data.$.unicode.charCodeAt(0),
+ svghorz: svghorz,
+ offset: offset,
+ size: size
+ };
+ });
+ fontData = fontData.concat(fontDataItem);
+ }
+
+ let indexFontData = await loadash.keyBy(fontData, 'unicodeDec');
+
+ let icons = iconList
+ .filter(icon => {
+ if (!indexFontData[icon.unicodeDec]) {
+ log.debug(`Skipping ${icon.unicodeHex}`);
+ return false;
+ }
+ return true;
+ })
+ .map(icon => {
+ let iconData = indexFontData[icon.unicodeDec];
+
+ let iconConfig = {
+ id: icon.id.split('-').join('_'),
+ type: item.type,
+ prefix: item.prefix,
+ unicodeHex: icon.unicodeHex,
+ unicodeDec: icon.unicodeDec,
+ data: iconData.data,
+ offset: parseInt(iconData.offset),
+ size: parseInt(iconData.size),
+ advWidth: parseInt(iconData.data['horiz-adv-x'] || iconData.svghorz)
+ };
+ return iconConfig;
+ });
+
+ return icons;
+}
+
+function getSvgCode(cfg, item) {
+ log.debug('Getting svg code for ' + item.type + '-' + item.id);
+
+ let params = {
+ color: cfg.color || 'black',
+ path: item.data.d,
+ width: item.advWidth,
+ height: item.size,
+ shiftX: item.advWidth / 10 / 2,
+ shiftY: -item.size - item.offset - item.size / 10 / 2
+ };
+
+ return (
+ `\n` +
+ `\t\n` +
+ `\t\t\n` +
+ `\t\t\t\n` +
+ `\t\t\n` +
+ `\t\n` +
+ ``
+ );
+}
+
+module.exports = {
+ load: load,
+ getSvgCode: getSvgCode
+};
diff --git a/src/lib/handler/material.js b/src/lib/handler/material.js
new file mode 100644
index 00000000..21b06731
--- /dev/null
+++ b/src/lib/handler/material.js
@@ -0,0 +1,106 @@
+const fs = require('fs-extra'),
+ util = require('util'),
+ log = require('./../logger'),
+ readFile = util.promisify(fs.readFile),
+ parseXml = util.promisify(require('xml2js').parseString);
+
+async function load(cfg, item) {
+ let icons = await loadFontData(cfg, item);
+ return icons;
+}
+
+async function loadFontData(cfg, item) {
+ log.debug(`Loading ${item.type} font-data`);
+
+ let filesList = [item.path + '/iconfont/MaterialIcons-Regular.svg'];
+
+ let fontData = [];
+ for (let key in filesList) {
+ let item = filesList[key];
+ let content = await readFile(item);
+ content = content.toString('utf-8');
+
+ let parsedXml = await parseXml(content),
+ glyph = parsedXml.svg.defs[0].font[0].glyph,
+ svghorz = parsedXml.svg.defs[0].font[0].$['horiz-adv-x'],
+ offset = parsedXml.svg.defs[0].font[0]['font-face'][0].$['descent'],
+ size = parsedXml.svg.defs[0].font[0]['font-face'][0].$['units-per-em'];
+
+ let fontDataItem = glyph
+ .filter(data => {
+ if (!data.$.unicode) {
+ return false;
+ }
+ if (!data.$.d) {
+ return false;
+ }
+ if (data.$.d === 'M0 0z') {
+ return false;
+ }
+ return true;
+ })
+ .map(data => {
+ return {
+ data: data.$,
+ unicodeDec: data.$.unicode.charCodeAt(0),
+ svghorz: svghorz,
+ offset: offset,
+ size: size
+ };
+ });
+
+
+ fontData = fontData.concat(fontDataItem);
+ }
+
+ let icons = fontData.map(icon => {
+ let iconData = icon.data;
+
+ let iconConfig = {
+ id: icon.data['glyph-name'].split('-').join('_'),
+ type: item.type,
+ prefix: item.prefix,
+ unicodeHex: icon.unicodeHex,
+ unicodeDec: icon.unicodeDec,
+ data: iconData,
+ offset: parseInt(icon.offset),
+ size: parseInt(icon.size),
+ advWidth: parseInt(iconData['horiz-adv-x'] || icon.svghorz)
+ };
+
+ log.verbose('MATERIAL - found ' + iconConfig.id);
+ return iconConfig;
+ });
+
+ return icons;
+}
+
+function getSvgCode(cfg, item) {
+ log.debug('Getting svg code for ' + item.type + '-' + item.id);
+
+ let params = {
+ color: cfg.color || 'black',
+ path: item.data.d,
+ width: item.advWidth,
+ height: item.size,
+ shiftX: 0,
+ shiftY: -item.size - item.offset
+ };
+
+ return (
+ `\n` +
+ `\t\n` +
+ `\t\t\n` +
+ `\t\t\t\n` +
+ `\t\t\n` +
+ `\t\n` +
+ ``
+ );
+}
+
+module.exports = {
+ load: load,
+ getSvgCode: getSvgCode
+};
diff --git a/src/lib/handler/weather.js b/src/lib/handler/weather.js
new file mode 100644
index 00000000..5fcc29b3
--- /dev/null
+++ b/src/lib/handler/weather.js
@@ -0,0 +1,128 @@
+const fs = require('fs-extra'),
+ util = require('util'),
+ log = require('./../logger'),
+ readFile = util.promisify(fs.readFile),
+ loadash = require('lodash'),
+ parseXml = util.promisify(require('xml2js').parseString);
+
+async function load(cfg, item) {
+ let iconList = await loadIcons(cfg, item);
+ let icons = await loadFontData(cfg, item, iconList);
+ return icons;
+}
+
+async function loadIcons(cfg, item) {
+ log.debug("Loading fa id's");
+ let content = await readFile(item.path + '/css/weather-icons.css');
+ let lines = content.toString();
+
+ let match,
+ result = [];
+
+ const regex = /wi-([\w-]*).*\s.*"\S([0-9a-f]+)"/g;
+ while ((match = regex.exec(lines))) {
+ result.push({
+ id: match[1],
+ unicodeHex: match[2],
+ unicodeDec: parseInt(match[2], 16)
+ });
+ }
+ return result;
+}
+
+async function loadFontData(cfg, item, iconList) {
+ log.debug('Loading fa5 font-data');
+
+ let filesList = [item.path + '/font/weathericons-regular-webfont.svg'];
+
+ let fontData = [];
+ for (let key in filesList) {
+ let item = filesList[key];
+ let content = await readFile(item);
+ content = content.toString('utf-8');
+
+ let parsedXml = await parseXml(content),
+ glyph = parsedXml.svg.defs[0].font[0].glyph,
+ svghorz = parsedXml.svg.defs[0].font[0].$['horiz-adv-x'],
+ offset = parsedXml.svg.defs[0].font[0]['font-face'][0].$['descent'],
+ size = parsedXml.svg.defs[0].font[0]['font-face'][0].$['units-per-em'];
+
+ let fontDataItem = glyph
+ .filter(data => {
+ if (!data.$.unicode) {
+ return false;
+ }
+ return true;
+ })
+ .map(data => {
+ return {
+ data: data.$,
+ unicodeDec: data.$.unicode.charCodeAt(0),
+ svghorz: svghorz,
+ offset: offset,
+ size: size
+ };
+ });
+ fontData = fontData.concat(fontDataItem);
+ }
+
+ let indexFontData = await loadash.keyBy(fontData, 'unicodeDec');
+
+ let icons = iconList
+ .filter(icon => {
+ if (!indexFontData[icon.unicodeDec]) {
+ log.debug(`Skipping ${icon.unicodeHex}`);
+ return false;
+ }
+ return true;
+ })
+ .map(icon => {
+ let iconData = indexFontData[icon.unicodeDec];
+
+ let iconConfig = {
+ id: icon.id.split('-').join('_'),
+ type: item.type,
+ prefix: item.prefix,
+ unicodeHex: icon.unicodeHex,
+ unicodeDec: icon.unicodeDec,
+ data: iconData.data,
+ offset: parseInt(iconData.offset),
+ size: parseInt(iconData.size),
+ advWidth: parseInt(iconData.data['horiz-adv-x'] || iconData.svghorz)
+ };
+ return iconConfig;
+ });
+
+ return icons;
+}
+
+function getSvgCode(cfg, item) {
+ log.debug('Getting svg code for ' + item.type + '-' + item.id);
+
+ let params = {
+ color: cfg.color || 'black',
+ path: item.data.d,
+ width: item.advWidth,
+ height: item.size,
+ shiftX: item.advWidth / ((10 / 2) * 2),
+ shiftY: -item.size - item.offset - item.size / ((10 / 2) * 2)
+ };
+
+ return (
+ `\n` +
+ `\t\n` +
+ `\t\t\n` +
+ `\t\t\t\n` +
+ `\t\t\n` +
+ `\t\n` +
+ ``
+ );
+}
+
+module.exports = {
+ load: load,
+ getSvgCode: getSvgCode
+};
diff --git a/src/lib/index.js b/src/lib/index.js
new file mode 100644
index 00000000..050fcdaa
--- /dev/null
+++ b/src/lib/index.js
@@ -0,0 +1,237 @@
+const Bluebird = require('bluebird'),
+ cliOptions = require('./cliOptions.js'),
+ cliUsage = require('command-line-usage'),
+ fs = require('fs-extra'),
+ path = require('path'),
+ ProgressBar = require('progress'),
+ utils = require('./utils');
+
+// config
+const cfg = require('./config');
+const log = require('./logger');
+
+if (cfg.devel) {
+ cfg.limit = cfg.limit == 0 ? 5 : cfg.limit;
+ cfg.verbose = true;
+}
+
+if (cfg.verbose) {
+ log.level = 'debug';
+} else {
+ log.level = 'warn';
+}
+
+if (cfg.help) {
+ const usage = cliUsage([
+ {
+ header: 'Options',
+ optionList: cliOptions,
+ },
+ ]);
+
+ process.stdout.write(usage);
+} else {
+ printInfo();
+ generate();
+}
+
+async function generate() {
+ const fonts = require('./fonts');
+ let work = [],
+ icons = [];
+
+ if (cfg.github) {
+ cfg.png = true;
+ cfg.puml = true;
+ cfg.colors = ['black'];
+ cfg.sizes = [48];
+ }
+
+ if (cfg.devel) {
+ cfg.png = true;
+ cfg.puml = true;
+ cfg.svg = true;
+ cfg.limit = 25;
+ cfg.sizes = [128];
+ cfg.icons = [
+ 'fa5-user_alt',
+ 'fa5-gitlab',
+ 'fa5-server',
+ 'fa5-database',
+ 'fa-gears',
+ 'fa-fire',
+ 'fa-clock_o',
+ 'fa-lock',
+ 'fa-cloud',
+ 'fa-server',
+ 'dev-nginx',
+ 'dev-mysql',
+ 'dev-redis',
+ 'dev-docker',
+ 'dev-linux',
+ 'dev2-html5',
+ 'gov-ambulance',
+ 'weather-night_alt_thunderstorm',
+ 'material-3d_rotation',
+ ];
+ }
+
+ try {
+ icons = await fonts.load(cfg);
+ } catch (err) {
+ throw err;
+ }
+
+ log.debug(`Starting work for ${icons.length} icons`);
+
+ let progressBar = new ProgressBar('working [:bar] :percent :etas :info', {
+ complete: '=',
+ incomplete: ' ',
+ width: 50,
+ total: icons.length,
+ });
+
+ work.push(
+ Bluebird.map(
+ icons,
+ (item) => {
+ if (cfg.progress) {
+ progressBar.tick({
+ info: item.type + '-' + item.id,
+ });
+ }
+ return fonts.generate(cfg, item);
+ },
+ {
+ concurrency: cfg.concurrency,
+ }
+ )
+ );
+
+ await Promise.all(work);
+
+ if (cfg.release) {
+ // copy icons to project
+ for (let item of cfg.fonts) {
+ log.debug('Copying ' + item.name);
+
+ let releasePath = cfg.dirs.project + '/' + item.name,
+ pngPath = cfg.dirs.build + '/' + item.type + '/png/' + cfg.sizes[0],
+ pumlPath = cfg.dirs.build + '/' + item.type + '/puml';
+
+ await fs.ensureDirSync(releasePath);
+ await fs.emptyDirSync(releasePath);
+
+ let files = await utils.getFiles(pngPath);
+ files = files
+ .map((file) => {
+ return {
+ file: path.parse(file).name + path.parse(file).ext,
+ name: path.parse(file).name,
+ ext: path.parse(file).ext,
+ path: file,
+ };
+ })
+ .sort(function (a, b) {
+ return a.name > b.name ? 1 : b.name > a.name ? -1 : 0;
+ });
+
+ log.debug('Found ' + item.name + ' ' + files.length);
+
+ let indexFileName = releasePath + '/index.md';
+ let indexContent = `# ${item.name}\n\n\n`;
+ indexContent += `### Overview\n`;
+ indexContent += `| Name | Macro | Image | Url |\n`;
+ indexContent += `|-------|--------|-------|-----|\n`;
+
+ for (let file of files) {
+ await fs.copyFileSync(file.path, releasePath + '/' + file.file);
+
+ indexContent += `${file.name} |`;
+ indexContent += `${item.type.toUpperCase()}_${file.name.toUpperCase()} |`;
+ indexContent += ` |`;
+ indexContent += `${file.name}.puml |\n`;
+ }
+
+ fs.writeFileSync(indexFileName, indexContent);
+
+ let pumlFiles = await utils.getFiles(pumlPath);
+ pumlFiles = pumlFiles
+ .map((file) => {
+ return {
+ file: path.parse(file).name + path.parse(file).ext,
+ name: path.parse(file).name,
+ ext: path.parse(file).ext,
+ path: file,
+ };
+ })
+ .sort(function (a, b) {
+ return a.name > b.name ? 1 : b.name > a.name ? -1 : 0;
+ });
+
+ for (let file of pumlFiles) {
+ await fs.copyFileSync(file.path, releasePath + '/' + file.file);
+ }
+ }
+ // Render examples
+ let examplesPath = cfg.dirs.project + '/examples';
+ let exampleFiles = await utils.getFiles(examplesPath);
+ exampleFiles = exampleFiles.filter((file) => path.parse(file).ext === '.puml');
+
+ for (let file of exampleFiles) {
+ await renderPuml(file);
+ }
+ }
+
+ console.log('Done');
+}
+
+function renderPuml(path) {
+ return new Promise(function (resolve, reject) {
+ var plantumlJar,
+ error = '';
+
+ var plantumlParams = ['-Djava.awt.headless=true', '-jar', cfg.binPlantuml, path];
+
+ log.debug('java ' + plantumlParams.join(' '));
+
+ plantumlJar = require('child_process').spawn('java', plantumlParams);
+ plantumlJar.stderr.on('data', (data) => {
+ error += data.toString();
+ });
+ plantumlJar.once('close', function (code) {
+ if (code > 0) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+}
+
+function printInfo() {
+ // info
+ let msg = '\nSettings:\n';
+ if (cfg.icons.length > 0) {
+ msg += 'icons: ';
+ cfg.icons.forEach((element) => (msg += ' ' + element));
+ } else {
+ msg += 'fonts: ';
+ cfg.fonts.forEach((element) => (msg += ' ' + element.name));
+ }
+
+ msg += '\nformats:';
+ msg += cfg.puml ? ' puml' : '';
+ msg += cfg.png ? ' png' : '';
+ msg += cfg.svg ? ' svg' : '';
+
+ msg += cfg.limit > 0 ? ' \nlimit: ' + cfg.limit : '';
+
+ msg += '\ncolors: ';
+ cfg.colors.forEach((element) => (msg += ' ' + element));
+
+ msg += '\nsizes: ';
+ cfg.sizes.forEach((element) => (msg += ' ' + element));
+
+ log.debug(msg);
+}
diff --git a/src/lib/logger.js b/src/lib/logger.js
new file mode 100644
index 00000000..f322ee33
--- /dev/null
+++ b/src/lib/logger.js
@@ -0,0 +1,54 @@
+/**
+ * Based on https://gist.github.com/spmason/1670196
+ */
+const util = require('util'),
+ winston = require('winston'),
+ logger = new winston.Logger(),
+ env = (process.env.NODE_ENV || '').toLowerCase(),
+ dateFormat = require('dateformat');
+
+// Override the built-in console methods with winston hooks
+switch (env) {
+ case 'production':
+ logger.add(winston.transports.File, {
+ filename: __dirname + '/application.log',
+ handleExceptions: true,
+ exitOnError: false
+ });
+ break;
+ case 'test':
+ // Don't set up the logger overrides
+ return;
+ default:
+ logger.add(winston.transports.Console, {
+ colorize: true,
+ timestamp: function() {
+ return dateFormat(new Date(), 'HH:MM:ss');
+ }
+ });
+ break;
+}
+
+function formatArgs(args) {
+ return [util.format.apply(util.format, Array.prototype.slice.call(args))];
+}
+console.log = function() {
+ logger.debug.apply(logger, formatArgs(arguments));
+};
+console.info = function() {
+ logger.info.apply(logger, formatArgs(arguments));
+};
+console.warn = function() {
+ logger.warn.apply(logger, formatArgs(arguments));
+};
+console.error = function() {
+ logger.error.apply(logger, formatArgs(arguments));
+};
+console.debug = function() {
+ logger.debug.apply(logger, formatArgs(arguments));
+};
+console.progress = function() {
+ logger.debug.apply(logger, formatArgs(arguments));
+};
+
+module.exports = logger;
diff --git a/src/lib/utils.js b/src/lib/utils.js
new file mode 100644
index 00000000..162cea5d
--- /dev/null
+++ b/src/lib/utils.js
@@ -0,0 +1,54 @@
+const fs = require('fs-extra'),
+ git = require('isomorphic-git'),
+ http = require('isomorphic-git/http/node'),
+ log = require('./logger'),
+ { promisify } = require('util'),
+ { resolve } = require('path'),
+ readdir = promisify(fs.readdir),
+ stat = promisify(fs.stat);
+
+async function repo(repo, branch, target) {
+ log.debug('Loading Repo ' + repo + ' into ' + target);
+
+ try {
+ await fs.ensureDirSync(target);
+
+ // check if it already exists
+ log.debug(`checking dir ${target}/.git for repo`);
+ const repoExists = fs.existsSync(target + '/.git');
+
+ if (!repoExists) {
+ await git.clone({
+ fs,
+ http,
+ dir: target,
+ url: repo,
+ singleBranch: true,
+ ref: branch,
+ depth: 10,
+ });
+ }
+
+ log.info(`checkout ${repo} branch:${branch} completed to dir ${target}`);
+ } catch (err) {
+ log.error('repo error', err);
+ throw err;
+ }
+ return target;
+}
+
+async function getFiles(dir) {
+ const subdirs = await readdir(dir);
+ const files = await Promise.all(
+ subdirs.map(async (subdir) => {
+ const res = resolve(dir, subdir);
+ return (await stat(res)).isDirectory() ? getFiles(res) : res;
+ })
+ );
+ return files.reduce((a, f) => a.concat(f), []);
+}
+
+module.exports = {
+ repo: repo,
+ getFiles: getFiles,
+};