From 3ae91a0ae00571044721eaead914eaaca29beff1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Sko=C5=99epa?= <jakub@skorepa.info>
Date: Sat, 5 Mar 2016 19:17:02 +0100
Subject: [PATCH] Start work on promisifying and tidying up

Created jobs which will replace sequence with promise-based api
---
 .gitignore                   |   1 +
 index.js                     | 337 +++++++++++++++++------------------
 package.json                 |   1 +
 sitegin/jobs.js              | 134 ++++++++++++++
 sitegin/sitegin.js           |  43 ++---
 sitegin/transformer-other.js |  15 +-
 6 files changed, 334 insertions(+), 197 deletions(-)
 create mode 100644 sitegin/jobs.js

diff --git a/.gitignore b/.gitignore
index b3cb2ed..c09a9cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ build-debug
 content
 theme
 static
+.buildconfig
diff --git a/index.js b/index.js
index fa36619..8b12354 100755
--- a/index.js
+++ b/index.js
@@ -1,178 +1,175 @@
 #!/usr/bin/env node
-var sitegin = require('./sitegin/sitegin');
-
-var input = new sitegin.input.Hugo(process.cwd()+'/content/');
-var transformerMD = new sitegin.transformer.Markdown;
-var transformerTags = new sitegin.transformer.Tags;
-var transformerNunjucks = new sitegin.transformer.Nunjucks;
-var transformerUrlizeTags = new sitegin.transformer.UrlizeTags;
-var output = new sitegin.output.Filesystem;
-var sequencer = new sitegin.Sequencer;
-var transformerGit = new sitegin.transformer.Git;
-var transformerOther = new sitegin.transformer.Other;
-var transformerImages = new sitegin.transformer.Images;
-
-var sass = require('node-sass');
-var fs = require('fs');
-
-var builddir = "build";
-if(process.argv[2] !== "compileonly") builddir = "build-debug";
-
-var oldEmit = sequencer.emit;
-sequencer.emit = function() {
-  console.log("Event:",arguments[0]);
-  oldEmit.apply(sequencer, arguments);
-}
-
-sequencer.on("", function(ev) {
-  console.log("Event:",ev);
-})
-
-var tasks = [];
-var onDone = function(what) {
-  console.log(what, 'done');
-  var removed = false;
-  for(var i = 0; i < tasks.length; i++) {
-    if(tasks[i] == what) {
-      removed = true;
-      tasks.splice(i,1);
-      break;
-    }
+require('./sitegin/sitegin')({
+  watch: false
+}, function onLoad(jobs) {
+  var sass = require('node-sass');
+  var fs = require('fs');
+
+  console.log('Sitegin successfully loaded');
+  jobs.onReload(function() {
+    console.log("Sitegin reloaded");
+  })
+
+/*
+  var builddir = "build";
+  if(process.argv[2] !== "compileonly") builddir = "build-debug";
+
+  var oldEmit = sequencer.emit;
+  sequencer.emit = function() {
+    console.log("Event:",arguments[0]);
+    oldEmit.apply(sequencer, arguments);
   }
-  if(!removed && process.argv[2] == "compileonly") throw new Error("Task "+what+" ended but was never registered");
-  if(tasks.length == 0 && process.argv[2] == "compileonly") {
-    console.log("exitting");
-    process.exit();
+
+  sequencer.on("", function(ev) {
+    console.log("Event:",ev);
+  })
+
+  var tasks = [];
+  var onDone = function(what) {
+    console.log(what, 'done');
+    var removed = false;
+    for(var i = 0; i < tasks.length; i++) {
+      if(tasks[i] == what) {
+        removed = true;
+        tasks.splice(i,1);
+        break;
+      }
+    }
+    if(!removed && process.argv[2] == "compileonly") throw new Error("Task "+what+" ended but was never registered");
+    if(tasks.length == 0 && process.argv[2] == "compileonly") {
+      console.log("exitting");
+      process.exit();
+    }
   }
-}
-
-
-// PIPELINE
-sequencer
-.load(input)
-.registerNext(transformerGit)
-.registerNext(transformerOther)
-.registerNext(transformerImages)
-.registerNext(transformerMD)
-.registerNext(transformerUrlizeTags)
-.registerNext(transformerTags)
-.registerNext(transformerNunjucks)
-.registerNext(output)
-.finish(function(list) {
-  onDone("sequencer");
-})
-tasks.push('sequencer');
-
-try {fs.mkdirSync(builddir);}catch(e){}
-try {fs.mkdirSync(builddir+"/theme");}catch(e){}
-
-// SASS
-var rendersass = function() {
-  sass.render ({file: "theme/sass/style.scss"},function(err, result) {
-    if(err == null) {
-      console.log("compiled sass");
-      fs.writeFile(builddir+"/theme/style.css",result.css);
-    } else console.log("error ", err);
-    onDone("sass");
-  });
-};
-tasks.push('sass');
-
-rendersass();
-
-// STATIC FILES
-var fsextra = require('node-fs-extra');
-tasks.push('copy static');
-fsextra.copy(
-    'static',
-    builddir,
-    function(file){ return !(file.match(".git") || file.match("static/articles")); },
-    function(){onDone("copy static");}
-  );
-tasks.push('copy static/articles');
-fsextra.copy(
-    'static/articles',
-    builddir+"/clanek",
-    function(){onDone("copy static/articles");}
-  );
-tasks.push('copy theme static');
-fsextra.copy(
-    'theme/static',
-    builddir,
-    function(file){ return !file.match("\\.git") },
-    function(){onDone("copy theme static");}
-  );
-
-// DEVEL INCOMING
-if(process.argv[2] !== "compileonly") {
-  var sync = require('browser-sync').create();
-  sync.init({
-    server: {
-      baseDir: builddir
-    },
-    ui: {
-      port: 1338
-    },
-    port: 1337
-  });
-
-  // CHANGE WATCHER
-  var chokidar = require('chokidar');
-
-  chokidar.watch('theme/static', {ignoreInitial: true, usePolling: false})
-  .on('all', (event, path) => {
-    console.log(path);
-    console.log("theme static copy watcher");
-    fsextra.copy(
-      'theme/static',
-      builddir,
-      function(file){ return !file.match(".git"); },
-      function(){}
-      );
-    sync.reload(path);
-  });
-  
-  chokidar.watch('static/', {ignoreInitial: true, usePolling: false})
-  .on('all', (event, path) => {
-    if(path.match(/^static\/articles\/.*$/)) return;
-    console.log("static copy watcher");
-    fsextra.copy(
+
+
+  // PIPELINE
+  sequencer
+  .load(input)
+  .registerNext(transformerGit)
+  .registerNext(transformerOther)
+  .registerNext(transformerImages)
+  .registerNext(transformerMD)
+  .registerNext(transformerUrlizeTags)
+  .registerNext(transformerTags)
+  .registerNext(transformerNunjucks)
+  .registerNext(output)
+  .finish(function(list) {
+    onDone("sequencer");
+  })
+  tasks.push('sequencer');
+
+  try {fs.mkdirSync(builddir);}catch(e){}
+  try {fs.mkdirSync(builddir+"/theme");}catch(e){}
+
+  // SASS
+  var rendersass = function() {
+    sass.render ({file: "theme/sass/style.scss"},function(err, result) {
+      if(err == null) {
+        console.log("compiled sass");
+        fs.writeFile(builddir+"/theme/style.css",result.css);
+      } else console.log("error ", err);
+      onDone("sass");
+    });
+  };
+  tasks.push('sass');
+
+  rendersass();
+
+  // STATIC FILES
+  var fsextra = require('node-fs-extra');
+  tasks.push('copy static');
+  fsextra.copy(
       'static',
       builddir,
       function(file){ return !(file.match(".git") || file.match("static/articles")); },
-      function(){}
+      function(){onDone("copy static");}
+    );
+  tasks.push('copy static/articles');
+  fsextra.copy(
+      'static/articles',
+      builddir+"/clanek",
+      function(){onDone("copy static/articles");}
+    );
+  tasks.push('copy theme static');
+  fsextra.copy(
+      'theme/static',
+      builddir,
+      function(file){ return !file.match("\\.git") },
+      function(){onDone("copy theme static");}
     );
-    sync.reload(path);
-  })
-  .unwatch('static/articles');
-  
-  chokidar.watch('static/articles', {ignoreInitial: true, usePolling: false})
-  .on('all', (event, path) => {
-    console.log("static articles copy watcher");
-    fsextra.copy('static/articles',builddir+"/clanek",function(){});
-    sync.reload(path);
-  })
-  .unwatch('static/articles');
-
-  chokidar.watch(['theme/','content/'], {ignoreInitial: true, usePolling: false})
-  .on('all', (event, path) => {
-    if(path.match(/^theme\/static\/.*$/)) return;
-    if(path.match(/^theme\/sass\/.*$/)) return;
-    console.log("rebuild watcher",path);
-    sequencer.load(input);
-  })
-  .unwatch("theme/static")
-  .unwatch("theme/sass");
-
-  sequencer.finish(function() {
-    sync.reload("*.html");
-  });
-
-  chokidar.watch('theme/sass', {ignoreInitial: true, usePolling: false})
-  .on('all', function(event, path) {
-    console.log("sass: file change detected - rerendering "+event+" "+path);
-    rendersass();
-    sync.reload(path);
-  });
-}
 
+  // DEVEL INCOMING
+  if(process.argv[2] !== "compileonly") {
+    var sync = require('browser-sync').create();
+    sync.init({
+      server: {
+        baseDir: builddir
+      },
+      ui: {
+        port: 1338
+      },
+      port: 1337
+    });
+
+    // CHANGE WATCHER
+    var chokidar = require('chokidar');
+
+    chokidar.watch('theme/static', {ignoreInitial: true, usePolling: false})
+    .on('all', (event, path) => {
+      console.log(path);
+      console.log("theme static copy watcher");
+      fsextra.copy(
+        'theme/static',
+        builddir,
+        function(file){ return !file.match(".git"); },
+        function(){}
+        );
+      sync.reload(path);
+    });
+
+    chokidar.watch('static/', {ignoreInitial: true, usePolling: false})
+    .on('all', (event, path) => {
+      if(path.match(/^static\/articles\/.*$/)) return;
+      console.log("static copy watcher");
+      fsextra.copy(
+        'static',
+        builddir,
+        function(file){ return !(file.match(".git") || file.match("static/articles")); },
+        function(){}
+      );
+      sync.reload(path);
+    })
+    .unwatch('static/articles');
+
+    chokidar.watch('static/articles', {ignoreInitial: true, usePolling: false})
+    .on('all', (event, path) => {
+      console.log("static articles copy watcher");
+      fsextra.copy('static/articles',builddir+"/clanek",function(){});
+      sync.reload(path);
+    })
+    .unwatch('static/articles');
+
+    chokidar.watch(['theme/','content/'], {ignoreInitial: true, usePolling: false})
+    .on('all', (event, path) => {
+      if(path.match(/^theme\/static\/.*$/)) return;
+      if(path.match(/^theme\/sass\/.*$/)) return;
+      console.log("rebuild watcher",path);
+      sequencer.load(input);
+    })
+    .unwatch("theme/static")
+    .unwatch("theme/sass");
+
+    sequencer.finish(function() {
+      sync.reload("*.html");
+    });
+
+    chokidar.watch('theme/sass', {ignoreInitial: true, usePolling: false})
+    .on('all', function(event, path) {
+      console.log("sass: file change detected - rerendering "+event+" "+path);
+      rendersass();
+      sync.reload(path);
+    });
+  }
+  */
+});
diff --git a/package.json b/package.json
index 0856faf..d37bc7b 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "nodegit-kit": "^0.8.0",
     "nunjucks": "^2.3.0",
     "nunjucks-date-filter": "^0.1.1",
+    "syntax-error": "^1.1.5",
     "toml": "~2.3.0",
     "toml-js": "0.0.8"
   },
diff --git a/sitegin/jobs.js b/sitegin/jobs.js
new file mode 100644
index 0000000..e529de6
--- /dev/null
+++ b/sitegin/jobs.js
@@ -0,0 +1,134 @@
+var syntaxError = require('syntax-error');
+var fs = require('fs');
+var chokidar = require('chokidar');
+var eventEmitter = new (require('events').EventEmitter);
+
+var jobList = {};
+var watchers = [];
+
+function JobError(message, name) {
+  Error.captureStackTrace(this, JobError);
+  this.name = name;
+  this.message = message;
+  return this;
+}
+
+var onReload = function() {}
+var requireError = function(e, jobName, module, reject) {
+  if(e instanceof SyntaxError) {
+    var resolved = require.resolve(module);
+    fs.readFile(resolved, 'utf8', function(err, content) {
+      console.log(syntaxError(content,resolved));
+      reject(new JobError('Failed to register job '+jobName))
+    })
+  } else {
+    if(e.code === 'MODULE_NOT_FOUND')
+    console.log('Module '+module+' not found');
+    reject(new JobError('Failed to register job '+jobName))
+  }
+}
+
+var jobs = {
+  register: function(jobName, module, watch) {
+    return new Promise(function(resolve, reject) {
+      if(watch === undefined) watch = true;
+      if(jobList[jobName] !== undefined) {
+        return Promise.reject(
+          new JobError('Job '+jobName+' is already registered', 'JobAlreadyRegistered')
+        )
+      }
+
+      try {
+        var f = require(module);
+        jobList[jobName] = {
+          f: f,
+          module: module
+        };
+        if(watch) jobs.watch(jobName);
+        resolve();
+      } catch(e) {requireError(e, jobName, module, reject);}
+    });
+  },
+  reload: function(jobName) {
+    var module = jobList[jobName].module;
+    delete require.cache[require.resolve(module)];
+
+    return new Promise(function(resolve, reject) {
+      try {
+        var f = require(module);
+        delete jobList[jobName];
+        jobList[jobName] = {
+          f: f,
+          module: module
+        };
+        resolve();
+      } catch(e) {
+        console.log(e);
+        requireError(e, jobName, module, reject);
+      }
+    });
+  },
+  // registerMultiple(
+  //  {watch: true},
+  //  ['jobName','./module'],['jobName2','./module2']
+  // )
+  registerMultiple: function() {
+    var regs = [];
+    var watch = true;
+    for(i in arguments) {
+      var job = arguments[i];
+      if(i == 0) {
+        if(job.watch !== undefined) watch = job.watch;
+      } else {
+        regs.push(jobs.register(job[0], job[1], watch));
+      }
+    }
+    return Promise.all(regs);
+  },
+  run: function() {
+    var jobName = arguments[0];
+    if(jobList[jobName] === undefined) {
+      throw new JobError('Job '+jobName+' is not registered', 'JobNotRegistered')
+    }
+    return jobList[jobName].f.apply(null, Array.prototype.slice.call(arguments, 1));
+  },
+  runSequence: function() {
+    var prom;
+    for(i in arguments) {
+      var job = arguments[i];
+      if(prom === undefined)
+      prom = jobs.run(job);
+      else
+      prom = prom.then(function() {
+        var args = [].splice.call(arguments, 0);
+        args.splice(0,0,job);
+        return jobs.run.apply(null,args)
+      })
+    }
+    return prom;
+  },
+  watch: function(jobName) {
+    var module = jobList[jobName].module;
+    var file = require.resolve(module);
+    var w = chokidar.watch(file);
+    w.on('change', path => {
+      jobs.reload(jobName)
+      .then(function() {
+        return onReload.apply(null, arguments);
+      });
+    });
+    watchers.push(w);
+  },
+  onReload: function(f) {
+    onReload = f;
+  },
+  close: function() {
+    watchers.forEach(function(w) {
+      w.close();
+      console.log(w.getWatched());
+    });
+    watchers = [];
+  }
+}
+
+module.exports = jobs;
diff --git a/sitegin/sitegin.js b/sitegin/sitegin.js
index f7a4a92..3c413c7 100644
--- a/sitegin/sitegin.js
+++ b/sitegin/sitegin.js
@@ -1,22 +1,25 @@
-exports.input = {
-  Hugo: require('./input-hugo')
-}
-
-exports.output = {
-  Console: require('./output-console'),
-  Filesystem: require('./output-filesystem')
-}
+var jobs = require('./jobs');
 
-exports.transformer = {
-  Basichtml:  require('./transformer-basichtml'),
-  Git:        require('./transformer-git'),
-  Markdown:   require('./transformer-markdown'),
-  Nunjucks:   require('./transformer-nunjucks'),
-  Tags:       require('./transformer-tags'),
-  UrlizeTags: require('./transformer-urlizetags'),
-  Other:      require('./transformer-other'),
-  Images:     require('./transformer-images'),
+module.exports = function(config, onDone) {
+  jobs.registerMultiple(
+    config,
+    /*['input-hugo', './input-hugo'],
+    ['output-console', './output-console'],
+    ['output-filesystem', './output-filesystem'],
+    ['transformer-basichtml', './transformer-basichtml'],
+    ['transformer-git', './transformer-git'],
+    ['transformer-markdown', './transformer-markdown'],
+    ['transformer-nunjucks', './transformer-nunjucks'],
+    ['transformer-tags', './transformer-tags'],
+    ['transformer-urlizetags', './transformer-urlizetags'],*/
+    ['transformer-other', './transformer-other']
+    //['transformer-images', './transformer-images']
+  )
+  .then(function() {
+    if(onDone) onDone(jobs);
+  })
+  .catch(function(e) {
+    console.log(e.stack)
+    jobs.close();
+  });
 }
-
-exports.Page = require('./Page');
-exports.Sequencer = require('./sequencer');
diff --git a/sitegin/transformer-other.js b/sitegin/transformer-other.js
index b948057..1c471a7 100644
--- a/sitegin/transformer-other.js
+++ b/sitegin/transformer-other.js
@@ -1,8 +1,10 @@
 var path = require('path');
-module.exports = function() {
-  var tr = this;
-  tr.fancyname = "transformer other";
-  tr.forEachPage = function(article, cb) {
+
+module.exports = function(articleList) {
+  console.log(articleList)
+  return Promise.resolve(articleList);
+
+  articleList.forEach(function(article) {
     var img = article.metadata.image;
     article.file = article.file.split(path.sep).join("/");
     if(img) {
@@ -12,7 +14,6 @@ module.exports = function() {
         article.metadata.image = img.replace("/","");
       }
     }
-
-    cb();
-  }
+  });
+  return Promise.resolve(articleList);
 }
-- 
GitLab