<commit_before>$(document).ready(function(){
  if (typeof initEditing === 'undefined') {
    initEditing = false;
  }
  var alphabet = ['A','B','C','D','E','F','G'];

  // Javascript that fills out value tag when label is filled out

  // TODO THIS BREAKS!
  $(document).on("click", "a.add-followup-dummy", function(event){
    $(this).parents(".option-field").find(".add-followup-button").click();
    $(this).parents(".question-entry").addClass("has-followup");
    event.preventDefault();
  });

  // recalculate all form values
  $(".item-container").on("insertion-callback after-removal-callback", ".question-entry", function (event){
    var entry = $(this);

    if (entry.find(".followup-field").length === 0 || entry.find(".followup-field").is(":hidden")){ // second piece is for deleted followups when editing an existing poll
      entry.removeClass("has-followup");
    } else {
      entry.addClass("has-followup");
    }
    // make sure this is not a yes/no question
    if (entry.find("select.question-type").val() == "MULTI"){
      // loop through each option text input
      entry.find(".option-text").each(function(i,el){
        // surround each text input with a nice bootstrappy prepend thing
        if (!$(el).hasClass("prepended")){
          $(el).addClass("prepended");
          $(el).before('
').after('
');
        }

        if (i == 6){
          $(".add-option-button").hide();
        } else if (i == 5){
          $(".add-option-button").show();
        }
      });

      // refresh all prepended label values
      entry.find(".option-field .option-add-on").each(function(i, el){
        $(el).text(alphabet[i]);
      });
      // refresh all hidden value inputs
      entry.find(".option-field .option-value").each(function(i, el){
        $(el).val(alphabet[i]);
      });
    }
    if (entry.find("select.followup-type").val() == "MULTI"){
      entry.find(".followup-option-text").each(function(i,el){

        if (!$(el).hasClass("prepended")){
          $(el).addClass("prepended");
          $(el).before('
').after('
');
        }

        if (i == 6){
          entry.find(".add-followup-option-button").hide();
        } else if (i == 5){
          entry.find(".add-followup-option-button").show();
        }
      });

      // refresh all prepended label values
      entry.find(".followup-option-field .followup-add-on").each(function(i, el){
        $(el).text(alphabet[i]);
      });

      entry.find(".followup-option-field .followup-option-value").each(function(i, e){
        $(e).val(alphabet[i]);
      });
    }
  });

  // Autofills the options form when a question type is selected
  $(".simple_form").on("change", "select.question-type", function(event) {

    // Wipes existing options TODO: hide, instead of remove?

    if (!initEditing){
      $(this).parents(".nested-fields").find(".option-field").remove();
    }
    $(this).parents(".question-entry").removeClass("has-followup");

    var value = $(this).val();

    var parent_container_entry = $(this).parents(".question-entry");
    parent_container_entry.find(".add-option-button").hide();

    if (value == "YN") {

      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-option-button").click();
        parent_container_entry.find(".add-option-button").click();
      }
      // Pre-fills out information, sets class to disabled
      parent_container_entry.find(".option-field .option-text").first().addClass("disabled yes-no").val("Yes");
      parent_container_entry.find(".option-field .option-value").first().addClass("disabled yes-no").val("yes");
      parent_container_entry.find(".option-field .option-text").last().addClass("disabled yes-no").val("No");
      parent_container_entry.find(".option-field .option-value").last().addClass("disabled yes-no").val("no");

      // Removes "remove option" button
      parent_container_entry.find(".help-inline a.remove_fields").remove();
    }
    else if (value == "MULTI") {
      // Show "add option" button
      parent_container_entry.find(".add-option-button").show();

      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-option-button").click();
        parent_container_entry.find(".add-option-button").click();
      }
    }
    else if (value == "OPEN") {
    }
  });

  // This is the
  $(".simple_form").on("change", "select.followup-type", function(event) {

    // Wipes existing options TODO: hide, instead of remove?

    if (!initEditing){ // make sure it's not just the /edit UI refresh
      $(this).parents(".nested-fields").find(".remove-follow-up-option").click();
    }

    var value = $(this).val();

    var parent_container_entry = $(this).parents(".followup-field");
    parent_container_entry.find(".add-followup-option-button").hide();

    if (value == "YN") {
      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-followup-option-button").click();
        parent_container_entry.find(".add-followup-option-button").click();
      }
      // Pre-fills out information, sets class to disabled
      parent_container_entry.find(".followup-option-field .followup-option-text").first().addClass("disabled yes-no").val("Yes");
      parent_container_entry.find(".followup-option-field .followup-option-text").last().addClass("disabled yes-no").val("No");

      parent_container_entry.find(".followup-option-field .followup-option-value").first().addClass("disabled yes-no").val("yes");
      parent_container_entry.find(".followup-option-field .followup-option-value").last().addClass("disabled yes-no").val("no");
      // Removes "remove option" button
      parent_container_entry.find(".followup-option-field a.remove_fields").remove();
    }
    else if (value == "MULTI") {
      // Show "add option" button
      parent_container_entry.find(".add-followup-option-button").show();

      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-followup-option-button").click();
        parent_container_entry.find(".add-followup-option-button").click();
      }
    }
    else if (value == "OPEN") {
    }
  });
});
<commit_msg>changing followup question type to yn now hides the removal link rather than deleting it, so the options can later be removed if question type is changed fixes issue #15<commit_after>$(document).ready(function(){
  if (typeof initEditing === 'undefined') {
    initEditing = false;
  }
  var alphabet = ['A','B','C','D','E','F','G'];

  // Javascript that fills out value tag when label is filled out

  // TODO THIS BREAKS!
  $(document).on("click", "a.add-followup-dummy", function(event){
    $(this).parents(".option-field").find(".add-followup-button").click();
    $(this).parents(".question-entry").addClass("has-followup");
    event.preventDefault();
  });

  // recalculate all form values
  $(".item-container").on("insertion-callback after-removal-callback", ".question-entry", function (event){
    var entry = $(this);

    if (entry.find(".followup-field").length === 0 || entry.find(".followup-field").is(":hidden")){ // second piece is for deleted followups when editing an existing poll
      entry.removeClass("has-followup");
    } else {
      entry.addClass("has-followup");
    }
    // make sure this is not a yes/no question
    if (entry.find("select.question-type").val() == "MULTI"){
      // loop through each option text input
      entry.find(".option-text").each(function(i,el){
        // surround each text input with a nice bootstrappy prepend thing
        if (!$(el).hasClass("prepended")){
          $(el).addClass("prepended");
          $(el).before('
').after('
');
        }

        if (i == 6){
          $(".add-option-button").hide();
        } else if (i == 5){
          $(".add-option-button").show();
        }
      });

      // refresh all prepended label values
      entry.find(".option-field .option-add-on").each(function(i, el){
        $(el).text(alphabet[i]);
      });
      // refresh all hidden value inputs
      entry.find(".option-field .option-value").each(function(i, el){
        $(el).val(alphabet[i]);
      });
    }
    if (entry.find("select.followup-type").val() == "MULTI"){
      entry.find(".followup-option-text").each(function(i,el){

        if (!$(el).hasClass("prepended")){
          $(el).addClass("prepended");
          $(el).before('
').after('
');
        }

        if (i == 6){
          entry.find(".add-followup-option-button").hide();
        } else if (i == 5){
          entry.find(".add-followup-option-button").show();
        }
      });

      // refresh all prepended label values
      entry.find(".followup-option-field .followup-add-on").each(function(i, el){
        $(el).text(alphabet[i]);
      });

      entry.find(".followup-option-field .followup-option-value").each(function(i, e){
        $(e).val(alphabet[i]);
      });
    }
  });

  // Autofills the options form when a question type is selected
  $(".simple_form").on("change", "select.question-type", function(event) {

    // Wipes existing options TODO: hide, instead of remove?

    if (!initEditing){
      $(this).parents(".nested-fields").find(".option-field").remove();
    }
    $(this).parents(".question-entry").removeClass("has-followup");

    var value = $(this).val();

    var parent_container_entry = $(this).parents(".question-entry");
    parent_container_entry.find(".add-option-button").hide();

    if (value == "YN") {

      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-option-button").click();
        parent_container_entry.find(".add-option-button").click();
      }
      // Pre-fills out information, sets class to disabled
      parent_container_entry.find(".option-field .option-text").first().addClass("disabled yes-no").val("Yes");
      parent_container_entry.find(".option-field .option-value").first().addClass("disabled yes-no").val("yes");
      parent_container_entry.find(".option-field .option-text").last().addClass("disabled yes-no").val("No");
      parent_container_entry.find(".option-field .option-value").last().addClass("disabled yes-no").val("no");

      // Removes "remove option" button
      parent_container_entry.find(".help-inline a.remove_fields").remove();
    }
    else if (value == "MULTI") {
      // Show "add option" button
      parent_container_entry.find(".add-option-button").show();

      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-option-button").click();
        parent_container_entry.find(".add-option-button").click();
      }
    }
    else if (value == "OPEN") {
    }
  });

  // This is the
  $(".simple_form").on("change", "select.followup-type", function(event) {

    // Wipes existing options TODO: hide, instead of remove?

    if (!initEditing){ // make sure it's not just the /edit UI refresh
      $(this).parents(".nested-fields").find(".remove-follow-up-option").click();
    }

    var value = $(this).val();

    var parent_container_entry = $(this).parents(".followup-field");
    parent_container_entry.find(".add-followup-option-button").hide();

    if (value == "YN") {
      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-followup-option-button").click();
        parent_container_entry.find(".add-followup-option-button").click();
      }
      // Pre-fills out information, sets class to disabled
      parent_container_entry.find(".followup-option-field .followup-option-text").first().addClass("disabled yes-no").val("Yes");
      parent_container_entry.find(".followup-option-field .followup-option-text").last().addClass("disabled yes-no").val("No");

      parent_container_entry.find(".followup-option-field .followup-option-value").first().addClass("disabled yes-no").val("yes");
      parent_container_entry.find(".followup-option-field .followup-option-value").last().addClass("disabled yes-no").val("no");
      // Removes "remove option" button
      parent_container_entry.find(".followup-option-field a.remove_fields").hide();
    }
    else if (value == "MULTI") {
      // Show "add option" button
      parent_container_entry.find(".add-followup-option-button").show();

      if (!initEditing){
        // Clicks the add_option button twice
        parent_container_entry.find(".add-followup-option-button").click();
        parent_container_entry.find(".add-followup-option-button").click();
      }
    }
    else if (value == "OPEN") {
    }
  });
});
<|endoftext|>


<commit_before>//= require gerbil
//= require jquery-1.7.1

Gerbil.Rails = {}

Gerbil.Rails.jQuery = jQuery.noConflict(true);

Gerbil.Rails.formatter = (function($) {
    var currentScenario, status;

    function test(text, className) {
        var li = $("
").addClass(className).html(text);
        currentScenario.find("ul").append(li);

        currentScenario.
            removeClass("fail").
            removeClass("pending").
            removeClass("ok");

        if (status.fail > 0) {
            currentScenario.addClass("fail");
        } else if (status.pending > 0) {
            currentScenario.addClass("pending");
        } else if (status.ok > 0) {
            currentScenario.addClass("ok");
        }
    }

    function pluralize(n, singular, plural) {
        if (typeof plural == "undefined")
            plural = singular + "s";
        return n == 1 ? [n, singular].join(" ") : [n, plural].join(" ");
    }

    return {
        scenario: function(msg) {
            status = { ok: 0, fail: 0, pending: 0 };

            currentScenario = $("
");
            currentScenario.append("
Running " + msg + "
");
            currentScenario.append("
");

            $("#results").append(currentScenario);
        },

        summary: function(summary) {
            var msg, p = $("
"),
                time = Math.round(100 * summary.time) / 100;

            msg = [[summary.pass, "passed"].join(" "),
                   [summary.fail, "failed"].join(" "),
                   [summary.pending, "pending"].join(" ")].join(", ");

            msg += " (" + summary.total + " total, " + pluralize(summary.assertions, "assertion") + ")";
            msg += " in " + time + "s.";

            p.text(msg);
            currentScenario.append(p);
            currentScenario.addClass("finished");
        },

        ok: function(msg) {
            status.ok++
            test(msg, "ok");
        },

        fail: function(msg) {
            status.fail++
            test(msg, "fail");
        },

        pending: function(msg) {
            status.pending++
            test(msg, "pending");
        }
    };

})(Gerbil.Rails.jQuery);

Gerbil.Rails.run = (function($) {
    return function() {
        function flashTitle(token, times) {
            $("title").text("   spec runner");

            setTimeout(function() {
                $("title").text(token + " spec runner");
            }, 200);

            if (times == 0) return;

            setTimeout(function() {
                flashTitle(token, times - 1);
            }, 1000);
        }

        flashTitle("⌛", 0);
        $.ajax({
            url:      "/assets/spec.js",
            type:     "GET",
            dataType: "script",
            success:  function() {
                $("#results").empty()
            },
            complete: function() {
                setTimeout(function() {
                    var fail    = $("section.fail").length,
                        pending = $("section.pending").length,
                        ok      = $("section.ok").length;

                    if (fail > 0) {
                        flashTitle("✘", 2);
                    } else if (pending > 0) {
                        flashTitle("☯", 2);
                    } else if (ok > 0) {
                        flashTitle("✔", 2);
                    }
                }, 0);
            }
        });
    }
})(Gerbil.Rails.jQuery);

Gerbil.formatter = Gerbil.Rails.formatter;

Gerbil.Rails.jQuery(function($) {
    var reload = $("#reload");

    setInterval(function() {
        if (!reload.is(":checked"))
            return;
        Gerbil.Rails.run();
    }, 30000);

    Gerbil.Rails.run();
});
<commit_msg>Throw error on when the loaded script fails.<commit_after>//= require gerbil
//= require jquery-1.7.1

Gerbil.Rails = {}

Gerbil.Rails.jQuery = jQuery.noConflict(true);

Gerbil.Rails.formatter = (function($) {
    var currentScenario, status;

    function test(text, className) {
        var li = $("
").addClass(className).html(text);
        currentScenario.find("ul").append(li);

        currentScenario.
            removeClass("fail").
            removeClass("pending").
            removeClass("ok");

        if (status.fail > 0) {
            currentScenario.addClass("fail");
        } else if (status.pending > 0) {
            currentScenario.addClass("pending");
        } else if (status.ok > 0) {
            currentScenario.addClass("ok");
        }
    }

    function pluralize(n, singular, plural) {
        if (typeof plural == "undefined")
            plural = singular + "s";
        return n == 1 ? [n, singular].join(" ") : [n, plural].join(" ");
    }

    return {
        scenario: function(msg) {
            status = { ok: 0, fail: 0, pending: 0 };

            currentScenario = $("
");
            currentScenario.append("
Running " + msg + "
");
            currentScenario.append("
");

            $("#results").append(currentScenario);
        },

        summary: function(summary) {
            var msg, p = $("
"),
                time = Math.round(100 * summary.time) / 100;

            msg = [[summary.pass, "passed"].join(" "),
                   [summary.fail, "failed"].join(" "),
                   [summary.pending, "pending"].join(" ")].join(", ");

            msg += " (" + summary.total + " total, " + pluralize(summary.assertions, "assertion") + ")";
            msg += " in " + time + "s.";

            p.text(msg);
            currentScenario.append(p);
            currentScenario.addClass("finished");
        },

        ok: function(msg) {
            status.ok++
            test(msg, "ok");
        },

        fail: function(msg) {
            status.fail++
            test(msg, "fail");
        },

        pending: function(msg) {
            status.pending++
            test(msg, "pending");
        }
    };

})(Gerbil.Rails.jQuery);

Gerbil.Rails.run = (function($) {
    return function() {
        function flashTitle(token, times) {
            $("title").text("   spec runner");

            setTimeout(function() {
                $("title").text(token + " spec runner");
            }, 200);

            if (times == 0) return;

            setTimeout(function() {
                flashTitle(token, times - 1);
            }, 1000);
        }

        flashTitle("⌛", 0);
        $.ajax({
            url:      "/assets/spec.js",
            type:     "GET",
            dataType: "script",
            success:  function() {
                $("#results").empty()
            },
            error: function(xhr, errorString, error) {
                throw error;
            },
            complete: function() {
                setTimeout(function() {
                    var fail    = $("section.fail").length,
                        pending = $("section.pending").length,
                        ok      = $("section.ok").length;

                    if (fail > 0) {
                        flashTitle("✘", 2);
                    } else if (pending > 0) {
                        flashTitle("☯", 2);
                    } else if (ok > 0) {
                        flashTitle("✔", 2);
                    }
                }, 0);
            }
        });
    }
})(Gerbil.Rails.jQuery);

Gerbil.formatter = Gerbil.Rails.formatter;

Gerbil.Rails.jQuery(function($) {
    var reload = $("#reload");

    setInterval(function() {
        if (!reload.is(":checked"))
            return;
        Gerbil.Rails.run();
    }, 30000);

    Gerbil.Rails.run();
});
<|endoftext|>


<commit_before>#!/usr/local/bin/node

var CONFIG = {
    name: "Cook",
    author: "Feifei Hang",
    version: "0.0.1",
    description: "A Node.JS executable application assists creating Web development project.",
    package_file: "http://feifeihang.info/cook/template/package.php?name=",
    gruntfile_file: "http://feifeihang.info/cook/template/Gruntfile.js",
    currentFetched: 0,
    doneFetched: 0
};

var HTML = {
    html_file: "http://feifeihang.info/cook/template/html5/index.html",
    error_file: "http://feifeihang.info/cook/template/html5/404.html",
    humans_file: "http://feifeihang.info/cook/template/html5/humans.txt",
    robots_file: "http://feifeihang.info/cook/template/html5/robots.txt",
    main_css: "http://feifeihang.info/cook/template/html5/css/main.css",
    normalize_css: "http://feifeihang.info/cook/template/html5/css/normalize.css",
    plugins_js: "http://feifeihang.info/cook/template/html5/js/plugins.js",
    main_min_js: "http://feifeihang.info/cook/template/html5/js/main.min.js",
    main_js: "http://feifeihang.info/cook/template/html5/js/dev/main.js",
    jquery_js: "http://feifeihang.info/cook/template/html5/js/vendor/jquery.min.js",
    modernizr_js: "http://feifeihang.info/cook/template/html5/js/vendor/modernizr.min.js"
};

var QUNIT = {
    html: "http://feifeihang.info/cook/template/html5/_qunit.html",
    css: "http://feifeihang.info/cook/template/html5/css/qunit.css",
    js: "http://feifeihang.info/cook/template/html5/js/qunit/qunit.js",
    test: "http://feifeihang.info/cook/template/html5/js/qunit/test.js"
};

var settings = {};

var colors = require('colors'),
    mkdirp = require('mkdirp'),
    request = require('request')
    fs = require('fs'),
    npm = require('npm'),
    prompt = require('prompt');

prompt.message = prompt.delimiter = '';

var createDirectory = function(path) {
    mkdirp(path, function (err) {
        if(err) {
            console.error(err);
        }
        else {
            console.log("Directory " + path.underline.green + " is created.");
        }
    });
};

var fetchFiles = function(_args) {
    request(_args.url, function(err, response, body) {
            if(!err && response.statusCode === 200) {
                fs.writeFile(_args.filename, body, function(e) {
                    if(e) {
                        console.error(e);
                    }
                    else {
                        console.log("Fetched %s from %s", _args.filename.green, _args.url.underline.cyan);
                        ++CONFIG.currentFetched;
                        if(_args.callback && CONFIG.currentFetched === (CONFIG.doneFetched + 2)) {
                            _args.callback(_args.project);
                        }
                    }
                })
            }
            else {
                console.error(err);
            }
    });
};

var showSettingsDialog = function(project) {
    var key;
    for(key in HTML) {
        ++CONFIG.doneFetched;
    }

    prompt.get({
       properties : {
           qunit: {
               description: "Do you want to grab QUnit for Javascript Unit test? [Y/n]",
               pattern: /^[YyNn]$/,
                message: "Input must be either Y/y or N/n."
           }
       }
    }, function(err, results) {
        if(err) {
            console.error(err);
        }
        else {
            settings.qunit = results.qunit;
            if(settings.qunit === 'Y' || settings.qunit === 'y') {
                var key;
                for(key in QUNIT) {
                    ++CONFIG.doneFetched;
                }
            }
            fetchProjectStructure(project);
        }
    });
};

var gruntInstall = function(project) {
    prompt.get({
       properties : {
           answer: {
               description: "Do you want to grab Grunt dependencies now? [Y/n]",
               pattern: /^[YyNn]$/,
                message: "Input must be either Y/y or N/n."
           }
       }
    }, function(err, results) {
        if(err) {
            console.error(err);
        }
        else {
            if(results.answer === 'Y' || results.answer === 'y') {
                // switch to project folder
                process.chdir(project);

                // install npm dependencies for Grunt
                npm.load(npm.config, function(err) {
                    npm.commands.install([], function(er, data) {
                        if(er) {
                            console.error(er);
                        }
                    });
                    npm.on('log', function(message) {
                        console.log(message);
                    });
                });
            }
        }
    });
};

var fetchProjectStructure = function(project) {
    createDirectory(project);
    createDirectory(project + '/js');
    createDirectory(project + '/js/dev');
    createDirectory(project + '/js/vendor');
    createDirectory(project + '/css');
    createDirectory(project + '/img');
    fetchFiles({project: project, url: HTML.html_file, filename: project + '/index.html', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.error_file, filename: project + '/404.html', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.humans_file, filename: project + '/humans.txt', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.robots_file, filename: project + '/robots.txt', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.main_css, filename: project + '/css/main.css', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.normalize_css, filename: project + '/css/normalize.css', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.plugins_js, filename: project + '/js/plugins.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.main_min_js, filename: project + '/js/main.min.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.main_js, filename: project + '/js/dev/main.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.jquery_js, filename: project + '/js/vendor/jquery.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.modernizr_js, filename: project + '/js/vendor/modernizr.js', callback: gruntInstall});

    fetchFiles({project: project, url: CONFIG.package_file + project, filename: project + '/package.json', callback: gruntInstall});
    fetchFiles({project: project, url: CONFIG.gruntfile_file, filename: project + '/Gruntfile.js', callback: gruntInstall});

    // QUnit
    console.log("Now start to grab & deploy QUnit...".green);
    if(settings.qunit === 'Y' || settings.qunit === 'y') {
        createDirectory(project + '/js/qunit');
        fetchFiles({project: project, url: QUNIT.html, filename: project + '/_qunit.html', callback: gruntInstall});
        fetchFiles({project: project, url: QUNIT.css, filename: project + '/css/qunit.css', callback: gruntInstall});
        fetchFiles({project: project, url: QUNIT.js, filename: project + '/js/qunit/qunit.js', callback: gruntInstall});
        fetchFiles({project: project, url: QUNIT.test, filename: project + '/js/qunit/test.js', callback: gruntInstall});
    }
};

exports.app = function(args) {
    // Show Cook information
    if(args.v || args.ver || args.version) {
        console.log("%s -- %s\nAuthor: %s\n%s", CONFIG.name,
                CONFIG.version.underline.green, CONFIG.author.cyan,
                CONFIG.description);
        return;
    }

    // Create a new project
    if(args.c || args.create) {
        var project = args.c || args.create;
        showSettingsDialog(project);
        return;
    }
}

<commit_msg>update<commit_after>#!/usr/local/bin/node

var CONFIG = {
    name: "Cook",
    author: "Feifei Hang",
    version: "0.0.1",
    description: "A Node.JS executable application assists creating Web development project.",
    package_file: "http://feifeihang.info/cook/template/package.php?name=",
    gruntfile_file: "http://feifeihang.info/cook/template/Gruntfile.js",
    currentFetched: 0,
    doneFetched: 0
};

var HTML = {
    html_file: "http://feifeihang.info/cook/template/html5/index.html",
    error_file: "http://feifeihang.info/cook/template/html5/404.html",
    humans_file: "http://feifeihang.info/cook/template/html5/humans.txt",
    robots_file: "http://feifeihang.info/cook/template/html5/robots.txt",
    main_css: "http://feifeihang.info/cook/template/html5/css/main.css",
    normalize_css: "http://feifeihang.info/cook/template/html5/css/normalize.css",
    plugins_js: "http://feifeihang.info/cook/template/html5/js/plugins.js",
    main_min_js: "http://feifeihang.info/cook/template/html5/js/main.min.js",
    main_js: "http://feifeihang.info/cook/template/html5/js/dev/main.js",
    jquery_js: "http://feifeihang.info/cook/template/html5/js/vendor/jquery.min.js",
    modernizr_js: "http://feifeihang.info/cook/template/html5/js/vendor/modernizr.min.js"
};

var QUNIT = {
    html: "http://feifeihang.info/cook/template/html5/_qunit.html",
    css: "http://feifeihang.info/cook/template/html5/css/qunit.css",
    js: "http://feifeihang.info/cook/template/html5/js/qunit/qunit.js",
    test: "http://feifeihang.info/cook/template/html5/js/qunit/test.js"
};

var settings = {};

var colors = require('colors'),
    mkdirp = require('mkdirp'),
    request = require('request')
    fs = require('fs'),
    npm = require('npm'),
    prompt = require('prompt');

prompt.message = prompt.delimiter = '';

var createDirectory = function(path) {
    mkdirp(path, function (err) {
        if(err) {
            console.error(err);
        }
        else {
            console.log("Directory " + path.underline.green + " is created.");
        }
    });
};

var fetchFiles = function(_args) {
    request(_args.url, function(err, response, body) {
            if(!err && response.statusCode === 200) {
                fs.writeFile(_args.filename, body, function(e) {
                    if(e) {
                        console.error(e);
                    }
                    else {
                        console.log("Fetched %s from %s", _args.filename.green, _args.url.underline.cyan);
                        ++CONFIG.currentFetched;
                        if(_args.callback && CONFIG.currentFetched === (CONFIG.doneFetched + 2)) {
                            _args.callback(_args.project);
                        }
                    }
                })
            }
            else {
                console.error(err);
            }
    });
};

var showSettingsDialog = function(project) {
    var key;
    for(key in HTML) {
        ++CONFIG.doneFetched;
    }

    prompt.get({
       properties : {
           qunit: {
               description: "Do you want to grab QUnit for Javascript Unit test? [Y/n]",
               pattern: /^[YyNn]$/,
                message: "Input must be either Y/y or N/n."
           }
       }
    }, function(err, results) {
        if(err) {
            console.error(err);
        }
        else {
            settings.qunit = results.qunit;
            if(settings.qunit === 'Y' || settings.qunit === 'y') {
                var key;
                for(key in QUNIT) {
                    ++CONFIG.doneFetched;
                }
            }
            fetchProjectStructure(project);
        }
    });
};

var gruntInstall = function(project) {
    prompt.get({
       properties : {
           answer: {
               description: "Do you want to grab Grunt dependencies now? [Y/n]",
               pattern: /^[YyNn]$/,
                message: "Input must be either Y/y or N/n."
           }
       }
    }, function(err, results) {
        if(err) {
            console.error(err);
        }
        else {
            if(results.answer === 'Y' || results.answer === 'y') {
                // switch to project folder
                process.chdir(project);

                // install npm dependencies for Grunt
                npm.load(npm.config, function(err) {
                    npm.commands.install([], function(er, data) {
                        if(er) {
                            console.error(er);
                        }
                    });
                    npm.on('log', function(message) {
                        console.log(message);
                    });
                });
            }
        }
    });
};

var fetchProjectStructure = function(project) {
    createDirectory(project);
    createDirectory(project + '/js');
    createDirectory(project + '/js/dev');
    createDirectory(project + '/js/vendor');
    createDirectory(project + '/css');
    createDirectory(project + '/img');
    fetchFiles({project: project, url: HTML.html_file, filename: project + '/index.html', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.error_file, filename: project + '/404.html', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.humans_file, filename: project + '/humans.txt', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.robots_file, filename: project + '/robots.txt', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.main_css, filename: project + '/css/main.css', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.normalize_css, filename: project + '/css/normalize.css', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.plugins_js, filename: project + '/js/plugins.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.main_min_js, filename: project + '/js/main.min.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.main_js, filename: project + '/js/dev/main.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.jquery_js, filename: project + '/js/vendor/jquery.js', callback: gruntInstall});
    fetchFiles({project: project, url: HTML.modernizr_js, filename: project + '/js/vendor/modernizr.js', callback: gruntInstall});

    fetchFiles({project: project, url: CONFIG.package_file + project, filename: project + '/package.json', callback: gruntInstall});
    fetchFiles({project: project, url: CONFIG.gruntfile_file, filename: project + '/Gruntfile.js', callback: gruntInstall});

    // QUnit
    if(settings.qunit === 'Y' || settings.qunit === 'y') {
        createDirectory(project + '/js/qunit');
        fetchFiles({project: project, url: QUNIT.html, filename: project + '/_qunit.html', callback: gruntInstall});
        fetchFiles({project: project, url: QUNIT.css, filename: project + '/css/qunit.css', callback: gruntInstall});
        fetchFiles({project: project, url: QUNIT.js, filename: project + '/js/qunit/qunit.js', callback: gruntInstall});
        fetchFiles({project: project, url: QUNIT.test, filename: project + '/js/qunit/test.js', callback: gruntInstall});
    }
};

exports.app = function(args) {
    // Show Cook information
    if(args.v || args.ver || args.version) {
        console.log("%s -- %s\nAuthor: %s\n%s", CONFIG.name,
                CONFIG.version.underline.green, CONFIG.author.cyan,
                CONFIG.description);
        return;
    }

    // Create a new project
    if(args.c || args.create) {
        var project = args.c || args.create;
        showSettingsDialog(project);
        return;
    }
}

<|endoftext|>


<commit_before>document.observe('dom:loaded', function () {

    // Prevent the enter key from releasing grades
    preventDefault();

    // Bind the 'send data to db' event to all grade id boxes
    bindEventToGradeEntry();

    /**
     Because the page has loaded and 'dom:loaded' has fired, we can't
     rely on this event to detect changes to the subtree. When we click
     'Next >' or '< Prev' we replace elements on the page instead of
     reloading the page (dom:loaded is not fired in this case). The new
     grade elements loaded when this happens have not had bindEventToGradeEntry()
     called on them, and modifying them will do nothing. So we need to detect the
     change of elements and call bindEventToGradeEntry() on the new grade boxes.
     */

    // The following from https://developer.mozilla.org/en/docs/Web/API/MutationObserver

    // DOM mutation observers for Firefox and Chrome respectively.
    MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

    // select the target node, which is 'content' in our case. This is the highest level
    // DOM with an ID that contains the grades table
    var target = document.querySelector('#content');

    // create an observer instance
    var observer = new MutationObserver(function (mutations) {
        // For any mutation observed, call bindEventToGradeEntry();
        mutations.forEach(function (mutation) {

            // We only want bindEventToGradeEntry() to be called only when it hasn't been
            // called on a set of grade boxes already. To accomplish this, check if an
            // attribute exists in the table. If it exists, we called bindEventToGradeEntry()
            // already. If not, call bindEventToGradeEntry(), then add the attribute.
            var attr = $('grades').attributes["bound"];
            if (attr == undefined) {
                $('grades').attributes['bound'] = 'true';
                bindEventToGradeEntry();
            }
        });
    });

    // configuration of the observer: we want to detect changes to the subtree (when the
    // grade elements are replaced)
    var config = { childList: true, subtree: true };

    // start observing
    observer.observe(target, config);

});

/** This function will prevent the default HTML5 action of submitting the content form
 *  when the user presses "enter". As an Admin user, this would release the grades.
 */
function preventDefault(){
    jQuery(document).keypress(function(event){
        if (event.which == 13) {
            event.preventDefault();
        }
    });
}

/**Updates a cell. Called from Rails controller grade_entry_forms/update_grade to
 * check if a cell exists before updating it, as cell existence can't be efficiently
 * checked from the Ruby controller
 */
function update_cell(cell, value) {
    // If the cell exists, change it's value to the one supplied
    if ($(cell)) {
        $(cell).value = value;
    }
}

/**
 * get all of the grade input fields, attach an observer that updates
 * the grade when it is changed
 */
function bindEventToGradeEntry() {
    $$('.grade-input').each(function (item) {
        new Form.Element.EventObserver(item, function (element, value) {

            var url = element.readAttribute('data-action');
            var params = {
                'updated_grade': value,
                'student_id': element.readAttribute('data-student-id'),
                'grade_entry_item_id': element.readAttribute('data-grade-entry-item-id'),
                'authenticity_token': AUTH_TOKEN
            }

            new Ajax.Request(url, {
                asynchronous: true,
                evalScripts: true,
                parameters: params
            });
        });
    });
}

function toggleTotalColVisibility() {
    var allElements = document.getElementsByClassName("total_value");

    for (var i = 0; i < allElements.length; i++) {
        if (allElements [i].style.display == 'inline-block')
            allElements [i].style.display = 'none';
        else
            allElements [i].style.display = 'inline-block';
    }

}
<commit_msg>Made code more readable<commit_after>document.observe('dom:loaded', function () {

    // Prevent the enter key from releasing grades
    preventEnterSubmit();

    // Bind the 'send data to db' event to all grade id boxes
    bindEventToGradeEntry();

    /**
     Because the page has loaded and 'dom:loaded' has fired, we can't
     rely on this event to detect changes to the subtree. When we click
     'Next >' or '< Prev' we replace elements on the page instead of
     reloading the page (dom:loaded is not fired in this case). The new
     grade elements loaded when this happens have not had bindEventToGradeEntry()
     called on them, and modifying them will do nothing. So we need to detect the
     change of elements and call bindEventToGradeEntry() on the new grade boxes.
     */

    // The following from https://developer.mozilla.org/en/docs/Web/API/MutationObserver

    // DOM mutation observers for Firefox and Chrome respectively.
    MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

    // select the target node, which is 'content' in our case. This is the highest level
    // DOM with an ID that contains the grades table
    var target = document.querySelector('#content');

    // create an observer instance
    var observer = new MutationObserver(function (mutations) {
        // For any mutation observed, call bindEventToGradeEntry();
        mutations.forEach(function (mutation) {

            // We only want bindEventToGradeEntry() to be called only when it hasn't been
            // called on a set of grade boxes already. To accomplish this, check if an
            // attribute exists in the table. If it exists, we called bindEventToGradeEntry()
            // already. If not, call bindEventToGradeEntry(), then add the attribute.
            var attr = $('grades').attributes["bound"];
            if (attr == undefined) {
                $('grades').attributes['bound'] = 'true';
                bindEventToGradeEntry();
            }
        });
    });

    // configuration of the observer: we want to detect changes to the subtree (when the
    // grade elements are replaced)
    var config = { childList: true, subtree: true };

    // start observing
    observer.observe(target, config);

});

/** This function will prevent the default HTML5 action of submitting the content form
 *  when the user presses "enter". As an Admin user, this would release the grades.
 */
function preventEnterSubmit(){
    jQuery(document).keypress(function(event){
        if (event.which == 13) {
            event.preventDefault();
        }
    });
}

/**Updates a cell. Called from Rails controller grade_entry_forms/update_grade to
 * check if a cell exists before updating it, as cell existence can't be efficiently
 * checked from the Ruby controller
 */
function update_cell(cell, value) {
    // If the cell exists, change it's value to the one supplied
    if ($(cell)) {
        $(cell).value = value;
    }
}

/**
 * get all of the grade input fields, attach an observer that updates
 * the grade when it is changed
 */
function bindEventToGradeEntry() {
    $$('.grade-input').each(function (item) {
        new Form.Element.EventObserver(item, function (element, value) {

            var url = element.readAttribute('data-action');
            var params = {
                'updated_grade': value,
                'student_id': element.readAttribute('data-student-id'),
                'grade_entry_item_id': element.readAttribute('data-grade-entry-item-id'),
                'authenticity_token': AUTH_TOKEN
            }

            new Ajax.Request(url, {
                asynchronous: true,
                evalScripts: true,
                parameters: params
            });
        });
    });
}

function toggleTotalColVisibility() {
    var allElements = document.getElementsByClassName("total_value");

    for (var i = 0; i < allElements.length; i++) {
        if (allElements [i].style.display == 'inline-block')
            allElements [i].style.display = 'none';
        else
            allElements [i].style.display = 'inline-block';
    }

}
<|endoftext|>


<commit_before>var fs      = require('fs'),
    path    = require('path'),
    util    = require('util'),
    async   = require('async'),
    colors  = require('colors'),
    Satan   = require('pm2/lib/Satan'),
    cst     = require('pm2/constants'),
    exec    = require('child_process').exec,
    Emitter = require('events').EventEmitter

var globalConfig,
    globalConfigPath = process.env.HOME + '/.podrc'

var testing = false,
    rawAppnameRE = '/apps/(.+)/.+\.js'

var api = new Emitter()
process.on('satan:client:ready', function () {
    api.emit('ready')
})

api.createApp = function (appname, options, callback) {

    if (typeof options === 'function') {
        callback = options
        options = null
    }

    if (globalConfig.apps[appname]) {
        return callback(new Error('an app with that name already exists.'))
    }

    var paths = getAppPaths(appname)
    async.parallel([
        function (done) {
            // write config file
            var opts = {}
            // merge options
            if (options) {
                for (var o in options) {
                    opts[o] = options[o]
                }
            }
            globalConfig.apps[appname] = opts
            var data = JSON.stringify(globalConfig, null, 4)
            fs.writeFile(globalConfigPath, data, function (err) {
                done(err, 'updated config.')
            })
        },
        function (done) {
            // create log directory
            fs.mkdir(paths.logPath, function (err) {
                done(err, 'created logs dir at ' + paths.logPath.yellow)
            })
        },
        function (done) {
            // create repo
            createAppRepo(paths, done)
        }
    ],
    function (err, msgs) {
        var repoMsgs = msgs.pop()
        msgs = msgs.concat(repoMsgs)
        callback(err, msgs, api.getAppInfo(appname))
    })
}

api.removeApp = function (appname, callback) {

    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist.'))
    }

    api.stopApp(appname, function (err) {
        if (!err || /is not running/.test(err.toString())) {
            async.parallel([
                function (done) {
                    // remove files
                    exec('rm -rf ' +
                        app.repoPath + ' ' +
                        app.workPath + ' ' +
                        app.logPath,
                        done
                    )
                },
                function (done) {
                    // rewrite config
                    delete globalConfig.apps[appname]
                    var data = JSON.stringify(globalConfig, null, 4)
                    fs.writeFile(globalConfigPath, data, done)
                }
            ],
            function (err) {
                if (err) return callback(err)
                callback(null, 'deleted app: ' + appname.yellow)
            })
        } else {
            callback(err)
        }
    })

}

api.startApp = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist.'))
    }
    fs.exists(app.script, function (exists) {
        if (!exists) {
            return callback(new Error('cannot locate main script for ' + appname.yellow + '\n' +('(' + app.script + ')').grey))
        }
        Satan.executeRemote('findByScript', {script: app.script}, function (err, proc) {
            if (err) return callback(err)
            if (proc) return callback(null, appname.yellow + ' already running.')

            var appConf = prepareConfig(app)
            appConf.NODE_ENV = app.config.nodeEnv || globalConfig.nodeEnv
            if (app.config.port) appConf.PORT = app.config.port

            Satan.executeRemote('prepare', appConf, function (err) {
                if (err) return callback(err)
                callback(null, appname.yellow + ' running on ' + app.port)
            })
        })
    })
}

api.startAllApps = function (callback) {
    async.map(
        Object.keys(globalConfig.apps),
        api.startApp,
        callback
    )
}

api.stopApp = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist.'))
    }
    Satan.executeRemote('findByScript', {script: app.script}, function (err, proc) {
        if (err) return callback(err)
        if (!proc) return callback(null, appname.yellow + ' is not running')
        Satan.executeRemote('stopId', { id: proc.pm_id }, function (err) {
            if (err) return callback(err)
            callback(null, appname.yellow + ' stopped.')
        })
    })
}

api.stopAllApps = function (callback) {
    // only stop ones in the config
    async.map(
        Object.keys(globalConfig.apps),
        api.stopApp,
        callback
    )
}

api.restartApp = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist'))
    }
    Satan.executeRemote('list', {}, function (err, list) {
        if (err) return callback(err)
        var inList = false
        for (var i = 0, l = list.length; i < l; i++) {
            if (list[i].opts.name === appname) {
                restart(list[i], callback)
                inList = true
            }
        }
        if (!inList) callback(new Error(appname.yellow + ' is not running'))
    })
}

api.restartAllApps = function (callback) {
    Satan.executeRemote('list', {}, function (err, list) {
        if (err) return callback(err)
        async.map(list, function (proc, done) {
            if (proc.opts.name in globalConfig.apps) {
                restart(proc, done)
            } else {
                done()
            }
        }, callback)
    })
}

api.listApps = function (callback) {
    var apps = Object.keys(globalConfig.apps)
    if (!apps.length) {
        return callback(null, [])
    }
    Satan.executeRemote('list', {}, function (err, list) {
        if (err) return callback(err)
        callback(null, apps.map(function (appname) {
            var app = api.getAppInfo(appname)
            app.isRunning = isRunning(appname, list)
            return app
        }))
    })
}

api.cleanAppLog = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist'))
    }
    exec('rm -f ' + app.logPath + '/*', function (err) {
        if (err) return callback(err)
        callback(null, 'cleaned logs for ' + appname.yellow)
    })
}

api.getAppInfo = function (appname) {
    var info = getAppPaths(appname)
    info.config = globalConfig.apps[appname]
    if (!info.config) return
    info.script = info.workPath + '/' + (getAppMainScript(info.workPath) || globalConfig.defaultScript)
    info.port = info.config.port || sniffPort(info.script) || 'unknown port'
    return info
}

api.getConfig = function () {
    return globalConfig
}

// helpers

function restart (app, callback) {
    var msg = app.opts.name.yellow + ' restarted'
    if (app.status === 'stopped') {
        Satan.executeRemote('startId', { id: app.pm_id }, function (err) {
            if (err) return callback(err)
            callback(null, msg)
        })
    } else {
        try {
            process.kill(app.pid)
        } catch (err) {
            return callback(err)
        }
        return callback(null, msg)
    }
}

function getAppPaths (app) {
    return {
        name: app,
        repoPath    : globalConfig.root + '/repos/' + app + '.git',
        workPath    : globalConfig.root + '/apps/' + app,
        logPath     : globalConfig.root + '/logs/' + app
    }
}

function createAppRepo (info, done) {
    async.series([
        function (next) {
            // create repo directory
            fs.mkdir(info.repoPath, next)
        },
        function (next) {
            // init bare repo
            exec('git --git-dir ' + info.repoPath + ' --bare init', function (err) {
                next(err, 'created bare repo at ' + info.repoPath.yellow)
            })
        },
        function (next) {
            // create post-receive hook
            createHook(info, function (err) {
                next(err, 'created post-receive hook.')
            })
        },
        function (next) {
            // clone an empty working copy
            exec('git clone ' + info.repoPath + ' ' + info.workPath, function (err) {
                next(err, 'created empty working copy at ' + info.workPath.yellow)
            })
        }
    ], function (err, msgs) {
        msgs.shift()
        done(err, msgs)
    })
}

function createHook (info, done) {
    var hookPath = info.repoPath + '/hooks/post-receive'
    async.waterfall([
        function (next) {
            fs.readFile(__dirname + '/../hooks/post-receive', 'utf-8', next)
        },
        function (data, next) {
            data = data
                .replace(/\{\{pod_dir\}\}/g, globalConfig.root)
                .replace(/\{\{app\}\}/g, info.name)
            fs.writeFile(hookPath, data, next)
        },
        function (next) {
            fs.chmod(hookPath, '0777', next)
        }
    ], done)
}

function isRunning(appname, list) {
    if (!list || !list.length) return false
    for (var i = 0, j = list.length; i < j; i++) {
        if (list[i].opts.name === appname) {
            return true
        }
    }
}

function getAppMainScript (workPath) {
    var pkg = readJSON(workPath + '/package.json')
    if (pkg) return pkg.main
}

function readJSON (file) {
    if (!fs.existsSync(file)) {
        return null
    } else {
        return JSON.parse(fs.readFileSync(file, 'utf-8'))
    }
}

function sniffPort (script) {
    if (fs.existsSync(script)) {
        // sniff port
        var content = fs.readFileSync(script, 'utf-8'),
            portMatch = content.match(/\.listen\(\D*(\d\d\d\d\d?)\D*\)/)
        return portMatch ? portMatch[1] : null
    } else {
        return null
    }
}

function prepareConfig (appInfo) {

    var app = {
        name: appInfo.name,
        script: appInfo.script
    }

    for (var o in appInfo.config) {
        if (o !== 'port' && o !== 'nodeEnv') {
            app[o] = appInfo.config[o]
        }
    }

    app["pm_exec_path"]        = path.resolve(process.cwd(), app.script)

    if (app.fileOutput)
        app["pm_out_log_path"] = path.resolve(process.cwd(), app.fileOutput)
    else {
        app["pm_out_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [app.name, '-out.log'].join(''))
        app.fileOutput         = app["pm_out_log_path"]
    }

    if (app.fileError)
        app["pm_err_log_path"] = path.resolve(process.cwd(), app.fileError)
    else {
        app["pm_err_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [app.name, '-err.log'].join(''))
        app.fileError          = app["pm_err_log_path"]
    }

    if (app.pidFile)
        app["pm_pid_path"]     = path.resolve(process.cwd(), app.pidFile)
    else {
        app["pm_pid_path"]     = path.resolve(cst.DEFAULT_PID_PATH, [app.name, '.pid'].join(''))
        app.pidFile            = app["pm_pid_path"]
    }

    return app
}

// Initialization

function tryInit () {
    if (fs.readFileSync(globalConfigPath)) {
        globalConfig = readJSON(globalConfigPath)
        makeDirs()
        return api
    }
}

function initConfig (root) {
    globalConfig = {
        root: root,
        nodeEnv: 'development',
        defaultScript: 'app.js',
        editor: process.env.VISUAL || process.env.EDITOR || 'vi',
        apps: {}
    }
    fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 4))
    makeDirs()
    return api
}

function makeDirs() {
    if (!fs.existsSync(globalConfig.root)) {
        fs.mkdirSync(globalConfig.root)
    }
    ;['apps', 'repos', 'logs'].forEach(function (dir) {
        dir = globalConfig.root + '/' + dir
        if (!fs.existsSync(dir)) fs.mkdirSync(dir)
    })
}

// Expose stuff
// TODO auto create config if not found
// prompt for root dir if cli
// auto pick root dir if api
module.exports = {

    tryInit: tryInit,

    init: function (root) {
        return tryInit() || initConfig(root || (process.env.HOME + '/.pod'))
    },

    initTest: function (config) {
        testing = true
        globalConfig = config
        globalConfigPath = config.root + '/.podrc'
        return api
    },

}
<commit_msg>only restart running AND in-config apps<commit_after>var fs      = require('fs'),
    path    = require('path'),
    util    = require('util'),
    async   = require('async'),
    colors  = require('colors'),
    Satan   = require('pm2/lib/Satan'),
    cst     = require('pm2/constants'),
    exec    = require('child_process').exec,
    Emitter = require('events').EventEmitter

var globalConfig,
    globalConfigPath = process.env.HOME + '/.podrc'

var testing = false,
    rawAppnameRE = '/apps/(.+)/.+\.js'

var api = new Emitter()
process.on('satan:client:ready', function () {
    api.emit('ready')
})

api.createApp = function (appname, options, callback) {

    if (typeof options === 'function') {
        callback = options
        options = null
    }

    if (globalConfig.apps[appname]) {
        return callback(new Error('an app with that name already exists.'))
    }

    var paths = getAppPaths(appname)
    async.parallel([
        function (done) {
            // write config file
            var opts = {}
            // merge options
            if (options) {
                for (var o in options) {
                    opts[o] = options[o]
                }
            }
            globalConfig.apps[appname] = opts
            var data = JSON.stringify(globalConfig, null, 4)
            fs.writeFile(globalConfigPath, data, function (err) {
                done(err, 'updated config.')
            })
        },
        function (done) {
            // create log directory
            fs.mkdir(paths.logPath, function (err) {
                done(err, 'created logs dir at ' + paths.logPath.yellow)
            })
        },
        function (done) {
            // create repo
            createAppRepo(paths, done)
        }
    ],
    function (err, msgs) {
        var repoMsgs = msgs.pop()
        msgs = msgs.concat(repoMsgs)
        callback(err, msgs, api.getAppInfo(appname))
    })
}

api.removeApp = function (appname, callback) {

    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist.'))
    }

    api.stopApp(appname, function (err) {
        if (!err || /is not running/.test(err.toString())) {
            async.parallel([
                function (done) {
                    // remove files
                    exec('rm -rf ' +
                        app.repoPath + ' ' +
                        app.workPath + ' ' +
                        app.logPath,
                        done
                    )
                },
                function (done) {
                    // rewrite config
                    delete globalConfig.apps[appname]
                    var data = JSON.stringify(globalConfig, null, 4)
                    fs.writeFile(globalConfigPath, data, done)
                }
            ],
            function (err) {
                if (err) return callback(err)
                callback(null, 'deleted app: ' + appname.yellow)
            })
        } else {
            callback(err)
        }
    })

}

api.startApp = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist.'))
    }
    fs.exists(app.script, function (exists) {
        if (!exists) {
            return callback(new Error('cannot locate main script for ' + appname.yellow + '\n' +('(' + app.script + ')').grey))
        }
        Satan.executeRemote('findByScript', {script: app.script}, function (err, proc) {
            if (err) return callback(err)
            if (proc) return callback(null, appname.yellow + ' already running.')

            var appConf = prepareConfig(app)
            appConf.NODE_ENV = app.config.nodeEnv || globalConfig.nodeEnv
            if (app.config.port) appConf.PORT = app.config.port

            Satan.executeRemote('prepare', appConf, function (err) {
                if (err) return callback(err)
                callback(null, appname.yellow + ' running on ' + app.port)
            })
        })
    })
}

api.startAllApps = function (callback) {
    async.map(
        Object.keys(globalConfig.apps),
        api.startApp,
        callback
    )
}

api.stopApp = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist.'))
    }
    Satan.executeRemote('findByScript', {script: app.script}, function (err, proc) {
        if (err) return callback(err)
        if (!proc) return callback(null, appname.yellow + ' is not running')
        Satan.executeRemote('stopId', { id: proc.pm_id }, function (err) {
            if (err) return callback(err)
            callback(null, appname.yellow + ' stopped.')
        })
    })
}

api.stopAllApps = function (callback) {
    // only stop ones in the config
    async.map(
        Object.keys(globalConfig.apps),
        api.stopApp,
        callback
    )
}

api.restartApp = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist'))
    }
    Satan.executeRemote('list', {}, function (err, list) {
        if (err) return callback(err)
        var inList = false
        for (var i = 0, l = list.length; i < l; i++) {
            if (list[i].opts.name === appname) {
                restart(list[i], callback)
                inList = true
            }
        }
        if (!inList) callback(new Error(appname.yellow + ' is not running'))
    })
}

api.restartAllApps = function (callback) {
    Satan.executeRemote('list', {}, function (err, list) {
        if (err) return callback(err)
        var runningApps = []
        list.forEach(function (proc) {
            if (proc.opts.name in globalConfig.apps) {
                runningApps.push(proc)
            }
        })
        async.map(runningApps, restart, callback)
    })
}

api.listApps = function (callback) {
    var apps = Object.keys(globalConfig.apps)
    if (!apps.length) {
        return callback(null, [])
    }
    Satan.executeRemote('list', {}, function (err, list) {
        if (err) return callback(err)
        callback(null, apps.map(function (appname) {
            var app = api.getAppInfo(appname)
            app.isRunning = findInList(appname, list)
            return app
        }))
    })
}

api.cleanAppLog = function (appname, callback) {
    var app = api.getAppInfo(appname)
    if (!app) {
        return callback(new Error('app ' + appname.yellow + ' does not exist'))
    }
    exec('rm -f ' + app.logPath + '/*', function (err) {
        if (err) return callback(err)
        callback(null, 'cleaned logs for ' + appname.yellow)
    })
}

api.getAppInfo = function (appname) {
    var info = getAppPaths(appname)
    info.config = globalConfig.apps[appname]
    if (!info.config) return
    info.script = info.workPath + '/' + (getAppMainScript(info.workPath) || globalConfig.defaultScript)
    info.port = info.config.port || sniffPort(info.script) || 'unknown port'
    return info
}

api.getConfig = function () {
    return globalConfig
}

// helpers

function restart (app, callback) {
    var msg = app.opts.name.yellow + ' restarted'
    if (app.status === 'stopped') {
        Satan.executeRemote('startId', { id: app.pm_id }, function (err) {
            if (err) return callback(err)
            callback(null, msg)
        })
    } else {
        try {
            process.kill(app.pid)
        } catch (err) {
            return callback(err)
        }
        return callback(null, msg)
    }
}

function getAppPaths (app) {
    return {
        name: app,
        repoPath    : globalConfig.root + '/repos/' + app + '.git',
        workPath    : globalConfig.root + '/apps/' + app,
        logPath     : globalConfig.root + '/logs/' + app
    }
}

function createAppRepo (info, done) {
    async.series([
        function (next) {
            // create repo directory
            fs.mkdir(info.repoPath, next)
        },
        function (next) {
            // init bare repo
            exec('git --git-dir ' + info.repoPath + ' --bare init', function (err) {
                next(err, 'created bare repo at ' + info.repoPath.yellow)
            })
        },
        function (next) {
            // create post-receive hook
            createHook(info, function (err) {
                next(err, 'created post-receive hook.')
            })
        },
        function (next) {
            // clone an empty working copy
            exec('git clone ' + info.repoPath + ' ' + info.workPath, function (err) {
                next(err, 'created empty working copy at ' + info.workPath.yellow)
            })
        }
    ], function (err, msgs) {
        msgs.shift()
        done(err, msgs)
    })
}

function createHook (info, done) {
    var hookPath = info.repoPath + '/hooks/post-receive'
    async.waterfall([
        function (next) {
            fs.readFile(__dirname + '/../hooks/post-receive', 'utf-8', next)
        },
        function (data, next) {
            data = data
                .replace(/\{\{pod_dir\}\}/g, globalConfig.root)
                .replace(/\{\{app\}\}/g, info.name)
            fs.writeFile(hookPath, data, next)
        },
        function (next) {
            fs.chmod(hookPath, '0777', next)
        }
    ], done)
}

function findInList (appname, list) {
    if (!list || !list.length) return false
    for (var i = 0, j = list.length; i < j; i++) {
        if (list[i].opts.name === appname) {
            return list[i]
        }
    }
}

function getAppMainScript (workPath) {
    var pkg = readJSON(workPath + '/package.json')
    if (pkg) return pkg.main
}

function readJSON (file) {
    if (!fs.existsSync(file)) {
        return null
    } else {
        return JSON.parse(fs.readFileSync(file, 'utf-8'))
    }
}

function sniffPort (script) {
    if (fs.existsSync(script)) {
        // sniff port
        var content = fs.readFileSync(script, 'utf-8'),
            portMatch = content.match(/\.listen\(\D*(\d\d\d\d\d?)\D*\)/)
        return portMatch ? portMatch[1] : null
    } else {
        return null
    }
}

function prepareConfig (appInfo) {

    var app = {
        name: appInfo.name,
        script: appInfo.script
    }

    for (var o in appInfo.config) {
        if (o !== 'port' && o !== 'nodeEnv') {
            app[o] = appInfo.config[o]
        }
    }

    app["pm_exec_path"]        = path.resolve(process.cwd(), app.script)

    if (app.fileOutput)
        app["pm_out_log_path"] = path.resolve(process.cwd(), app.fileOutput)
    else {
        app["pm_out_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [app.name, '-out.log'].join(''))
        app.fileOutput         = app["pm_out_log_path"]
    }

    if (app.fileError)
        app["pm_err_log_path"] = path.resolve(process.cwd(), app.fileError)
    else {
        app["pm_err_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [app.name, '-err.log'].join(''))
        app.fileError          = app["pm_err_log_path"]
    }

    if (app.pidFile)
        app["pm_pid_path"]     = path.resolve(process.cwd(), app.pidFile)
    else {
        app["pm_pid_path"]     = path.resolve(cst.DEFAULT_PID_PATH, [app.name, '.pid'].join(''))
        app.pidFile            = app["pm_pid_path"]
    }

    return app
}

// Initialization

function tryInit () {
    if (fs.readFileSync(globalConfigPath)) {
        globalConfig = readJSON(globalConfigPath)
        makeDirs()
        return api
    }
}

function initConfig (root) {
    globalConfig = {
        root: root,
        nodeEnv: 'development',
        defaultScript: 'app.js',
        editor: process.env.VISUAL || process.env.EDITOR || 'vi',
        apps: {}
    }
    fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 4))
    makeDirs()
    return api
}

function makeDirs() {
    if (!fs.existsSync(globalConfig.root)) {
        fs.mkdirSync(globalConfig.root)
    }
    ;['apps', 'repos', 'logs'].forEach(function (dir) {
        dir = globalConfig.root + '/' + dir
        if (!fs.existsSync(dir)) fs.mkdirSync(dir)
    })
}

// Expose stuff
// TODO auto create config if not found
// prompt for root dir if cli
// auto pick root dir if api
module.exports = {

    tryInit: tryInit,

    init: function (root) {
        return tryInit() || initConfig(root || (process.env.HOME + '/.pod'))
    },

    initTest: function (config) {
        testing = true
        globalConfig = config
        globalConfigPath = config.root + '/.podrc'
        return api
    },

}
<|endoftext|>


<commit_before>var net = require('net');
var debug = require('debug')('khan:core');
var util = require('util');
var events = require('events');

function Nexy() {
  this.routes = {};
  this.settings = {};
  this.middlewares = [];

  var env = process.env.NODE_ENV || 'development';
  this.set('env', env);

  debug('booting in %s', env);
}

util.inherits(Nexy, events.EventEmitter);
module.exports = Nexy;

Nexy.prototype.use = function(fn) {
  debug('loading - %s - middleware', fn.name || 'anonymous');
  this.middlewares.push(fn);
};

Nexy.prototype.route = function(route, fn) {
  debug('loading - %s - route', route);
  this.routes[route] = fn;
};

Nexy.prototype.go = function(req, res) {
  var routes = this.routes,
    middlewares = this.middlewares,
    index = 0,
    method = this.get('msg:key') || 'method'; // req.method

  // proxy write to send
  res.send = function(msg) {
    // auto stringify object
    try {
      res.write(msg);
    } catch (err) {
      res.write(JSON.stringify(msg));
    }
  };

  // try to parse json
  try {
    req = JSON.parse(req);
  } catch (err) {
    // assume packet id is after packet length
    req[method] = req.readUInt16LE(2);
  }

  // get the route
  var route = req[method] || 'default';

  // check if request has a handler
  var handler = routes[route];

  // destroy request with no handlers
  if (!handler)
    return res.destroy();

  function out(err, req, res) {
    if (err) {

      return;
    }

    // routing
    return routes[route](req, res);
  }

  function next(err) {
    var layer;

    // if an error is detected emit an error event
    if (err)
      return out(err);

    // next callback
    layer = middlewares[index++];

    debug('callback layer %s', layer.name || 'anonymous');

    // all out
    if (!layer)
      return out(err, req, res);

    // handle callback
    layer(req, res, next);
  }

  next();
};

Nexy.prototype.connection_callback = function(res) {
  res.on('data', function(req) {
    this.go(req, res);
  }.bind(this));

  // should also handle lost clients
  res.on('end', this.emit.bind(this, 'disconnect', res));
  res.on('close', this.emit.bind(this, 'disconnect', res));
  res.on('error', this.emit.bind(this, 'disconnect', res));
};

Nexy.prototype.set = function(setting, value) {
  this.settings[setting] = value;
  return this.settings[setting];
};

Nexy.prototype.get = function(setting) {
  return this.settings[setting];
};

Nexy.prototype.listen = function() {
  var server = net.createServer(this.connection_callback.bind(this));
  this.server = server;
  return server.listen.apply(server, arguments);
};

Nexy.prototype.connect = function(port, host, callback) {
  if (typeof host !== 'string') {
    callback = host;
    host = 'localhost';
  }

  var client = new net.Socket();
  this.client = client;
  client.connect(port, host, function() {
    this.connection_callback(client);
    callback(client);
  }.bind(this));
};

Nexy.prototype.close = function() {
  if (this.server) {
    this.server.close();
  } else {
    this.client.destroy();
  }
};
<commit_msg>fixed layer.name undefined<commit_after>var net = require('net');
var debug = require('debug')('khan:core');
var util = require('util');
var events = require('events');

function Nexy() {
  this.routes = {};
  this.settings = {};
  this.middlewares = [];

  var env = process.env.NODE_ENV || 'development';
  this.set('env', env);

  debug('booting in %s', env);
}

util.inherits(Nexy, events.EventEmitter);
module.exports = Nexy;

Nexy.prototype.use = function(fn) {
  debug('loading - %s - middleware', fn.name || 'anonymous');
  this.middlewares.push(fn);
};

Nexy.prototype.route = function(route, fn) {
  debug('loading - %s - route', route);
  this.routes[route] = fn;
};

Nexy.prototype.go = function(req, res) {
  var routes = this.routes,
    middlewares = this.middlewares,
    index = 0,
    method = this.get('msg:key') || 'method'; // req.method

  // proxy write to send
  res.send = function(msg) {
    // auto stringify object
    try {
      res.write(msg);
    } catch (err) {
      res.write(JSON.stringify(msg));
    }
  };

  // try to parse json
  try {
    req = JSON.parse(req);
  } catch (err) {
    // assume packet id is after packet length
    req[method] = req.readUInt16LE(2);
  }

  // get the route
  var route = req[method] || 'default';

  // check if request has a handler
  var handler = routes[route];

  // destroy request with no handlers
  if (!handler)
    return res.destroy();

  function out(err, req, res) {
    if (err) {

      return;
    }

    // routing
    return routes[route](req, res);
  }

  function next(err) {
    var layer;

    // if an error is detected emit an error event
    if (err)
      return out(err);

    // next callback
    layer = middlewares[index++];

    // all out
    if (!layer)
      return out(err, req, res);

    debug('callback layer %s', layer.name || 'anonymous');

    // handle callback
    layer(req, res, next);
  }

  next();
};

Nexy.prototype.connection_callback = function(res) {
  res.on('data', function(req) {
    this.go(req, res);
  }.bind(this));

  // should also handle lost clients
  res.on('end', this.emit.bind(this, 'disconnect', res));
  res.on('close', this.emit.bind(this, 'disconnect', res));
  res.on('error', this.emit.bind(this, 'disconnect', res));
};

Nexy.prototype.set = function(setting, value) {
  this.settings[setting] = value;
  return this.settings[setting];
};

Nexy.prototype.get = function(setting) {
  return this.settings[setting];
};

Nexy.prototype.listen = function() {
  var server = net.createServer(this.connection_callback.bind(this));
  this.server = server;
  return server.listen.apply(server, arguments);
};

Nexy.prototype.connect = function(port, host, callback) {
  if (typeof host !== 'string') {
    callback = host;
    host = 'localhost';
  }

  var client = new net.Socket();
  this.client = client;
  client.connect(port, host, function() {
    this.connection_callback(client);
    callback(client);
  }.bind(this));
};

Nexy.prototype.close = function() {
  if (this.server) {
    this.server.close();
  } else {
    this.client.destroy();
  }
};
<|endoftext|>