UNPKG

klog

Version:
956 lines (898 loc) 38.2 kB
// Generated by CoffeeScript 1.3.3 /* Author: Billy Moon (http://billy.itaccess.org/) LICENSE: Copyright (c) 2012 by Billy Moon. All rights reserved. This module is free software; you can redistribute it and/or modify it under the MIT license The LICENSE file contains the full text of the license. */ (function() { var asDate, buffer, changeBugState, cmd, editFile, editor, exec, exit, folder, fs, getBugByUIDORNumber, getBugs, getDate, get_command, get_confirmation, get_required, get_user_details, glob, hook, hooks, main, md5, opts, output_cli, pad, parseArgs, path, print, randomUID, remove_comments, sep, settings, tpath, usage, _, _i, _len; fs = require('fs'); exec = require("child_process").exec; _ = require('../lib/underscore-min.js'); md5 = require('../lib/md5.js').MD5.hex_md5; editor = require('../lib/editor.js'); parseArgs = function() { var arg, args, i, k, m, na, o, options, switches, v, validOptions, _i, _len, _ref; options = { s: 'state', m: 'message', e: 'editor', t: 'type', p: 'priority', l: 'label' }; switches = { a: 'all', d: 'debug', f: 'force', r: 'return', x: 'plain' }; args = process.argv; o = { _: [], $0: [] }; validOptions = []; for (k in options) { v = options[k]; validOptions.push(v); } i = -2; na = false; for (_i = 0, _len = args.length; _i < _len; _i++) { arg = args[_i]; if (m = arg.match(/^--(.+?)(=(.+))?$/)) { na = m[1]; o[m[1]] = m[3] || true; } else if (m = arg.match(/^-(.+?)(=(.+))?$/)) { if (na = options[m[1]]) { if (na === 'message') { o[na] = ((_ref = m[3]) != null ? _ref : [m[3]]) | ['']; } else { o[na] = m[3] || true; } } else if (switches[m[1]]) { na = false; o[switches[m[1]]] = m[3] || true; } else { print('Unknown flag: ' + m[1]); exit(1); } } else if (++i > 0) { if (na === 'message') { o.message = [arg]; } else if (na !== false) { o[na] = arg; } else { if (o.message) { o.message.push(arg); } else { o._.push(arg); } } na = false; } else { o['$0'].push(arg); } } if (o.message) { o.message = o.message.join(' '); } return o; }; pad = function(e, t, n) { n = n || "0"; t = t || 2; while (("" + e).length < t) { e = n + e; } return e; }; getDate = function() { var c; c = new Date(); return c.getFullYear() + "-" + pad(c.getMonth() + 1) + "-" + pad(c.getDate()) + "_" + c.toLocaleTimeString().replace(/\D/g, '-') + "." + pad(c.getMilliseconds(), 3); }; asDate = function(datestring) { return new Date(datestring.replace(/_/, 'T').replace(/T(.+)-(.+)-/, "T$1:$2:")); }; randomUID = function() { var $uid; $uid = opts.date + "." + opts.email; $uid = md5($uid); $uid = $uid.replace(/(.{4}).+/, "$1"); return $uid; }; getBugs = function() { var $added, $author, $body, $modified, $number, $priority, $results, $status, $title, $type, $uid, buffer, file, files, line, lines, m, _i, _j, _len, _len1; if (!opts.path) { print("This directory does not appear to have Klogs on!\n\nPut them on with:\n\n klog init\n\nor try `klog help` for more info"); exit(); } files = fs.readdirSync("" + (opts.path + opts.store)); files.sort(); $results = []; $number = 1; for (_i = 0, _len = files.length; _i < _len; _i++) { file = files[_i]; if (file.match(/\.log$/)) { $status = 'open'; buffer = fs.readFileSync("" + (opts.path + opts.store) + file); lines = buffer.toString().split(/[\r\n]+/); $priority = 0; $modified = null; $body = []; for (_j = 0, _len1 = lines.length; _j < _len1; _j++) { line = lines[_j]; if (m = line.match(/^Title: (.*)/)) { $title = m[1]; } else if (m = line.match(/^Type: (.*)/)) { $type = m[1]; } else if (m = line.match(/^Priority: (.*)/)) { $priority = m[1]; } else if (m = line.match(/^Added: (.*)/)) { $added = m[1]; } else if (m = line.match(/^Modified: (.*)/)) { $modified = m[1]; } else if (m = line.match(/^Author: (.*)/)) { $author = m[1]; } else if (m = line.match(/^UID: (.*)/)) { $uid = m[1]; } else if (m = line.match(/^Status: (.*)/i)) { $status = m[1]; } else { $body.push("\r\n" + line); } } if (!$modified) { $modified = $added; } $results.push({ file: file, body: $body, number: $number++, uid: $uid, status: $status, type: $type, priority: $priority, title: $title, added: $added, modified: $modified, author: $author || 'unspecified' }); } } return $results; }; print = function(txt) { return console.log(txt); }; getBugByUIDORNumber = function($arg) { var $bug, $bugs, $possible, bug, cb, ch, cr, hl, m, _i, _len; $bugs = getBugs(); for (_i = 0, _len = $bugs.length; _i < _len; _i++) { $possible = $bugs[_i]; $arg = $arg.replace(/^%/, ''); if (m = $arg.match(/^([0-9]{1,3})$/i)) { if (parseInt(m[1]) === $possible.number) { $bug = $possible; } } else { if ($arg.toLowerCase() === $possible.uid.toLowerCase()) { $bug = $possible; } } if ($bug) { return $bug; } } print("Last resort, trying to search (open issues) for: " + glob.clrs.yellow + $arg + glob.clrs.reset); bug = cmd.search({ "return": true, terms: $arg, state: 'open', all: false }); if (bug) { hl = bug.status === 'open' ? glob.clrs.green : glob.clrs.red; cb = glob.clrs.bright; ch = glob.clrs.yellow; cr = glob.clrs.reset; print("Found: %" + hl + bug.uid + glob.clrs.reset + " [" + ch + bug.status + cr + "] [" + (ch + cb) + bug.type + cr + "] " + bug.title); return bug; } print("Bug not found!!"); return exit(1); }; exit = function(code) { if (!opts.server) { return process.exit(code); } }; editFile = function(file) { var $editor; $editor = opts.args.editor ? opts.args.editor : process.env.EDITOR ? process.env.EDITOR : opts.win ? "notepad" : "vim"; return editor(file, {}); }; remove_comments = function($file) { var buffer, content; try { buffer = fs.readFileSync($file); } catch (e) { print("Failed to open " + $file); exit; } content = buffer.toString().replace(/^# klog:.*(\r\n|\n|\r)/mg, ''); return fs.writeFileSync($file, content); }; usage = function() { print('\nklog [options] sub-command [args]\n\n Available sub-commands:\n\n add - Add a new bug.\n append - Append text to an existing bug.\n Set type with -t, and use `.` as message for no message\n close - Change an open bug to closed.\n closed - List all currently closed bugs.\n edit - Allow a bug to be edited.\n delete - Allow a bug to be deleted.\n destroy - Destroys the whole klog storage folder (including all issue data!)\n init - Initialise the system.\n list|search - Display existing bugs.\n open - List all currently open bugs.\n reopen - Change a closed bug to open.\n view - Show all details about a specific bug.\n server - HTTP server displays bugs, and accepts commands\n\n Options:\n -f, --force - no confirmation when deleting\n -t, --type - issue type (default:bug) i.e. feature/enhance/task\n -m, --message - Use the given message rather than spawning an editor.\n -s, --state - Restrict matches when searching (open/closed).\n -a, --all - Search everywhere (type, and message), not just the title \n -p, --priority - Set the priority (`.` is replaced with `-`, so `.3` will result in `-3`)\n'); return exit(0); }; hook = function(action, file) { if (hooks[action]) { return hooks[action].run(file); } }; changeBugState = function($value, $state) { var $bug, add, content, mod; if (!$state.match(/^(open|closed)$/i)) { print("Invalid status " + $state); exit(1); } $bug = getBugByUIDORNumber($value); if ($bug.status === $state) { print("The bug is already " + $state + "!\r\n"); exit(1); } content = "\r\n\nModified: " + opts.date + "\nStatus: " + $state; fs.appendFileSync(opts.path + opts.store + $bug.file, content); add = asDate($bug.added); mod = asDate(opts.date); print("(" + Math.round(((mod - add) / 1000 / 60 / 60) * 100) / 100 + " hours after issue was added)"); return hook($state, $bug.file); }; get_user_details = function(callback) { if (opts.user && opts.email) { return callback(); } else { return exec('git config --get user.email', function(se, so, e) { var stdin; if (so.length) { opts.email = so.replace(/[\r\n]+/, ''); return exec('git config --get user.name', function(se, so, e) { if (so.length) { opts.user = so.replace(/[\r\n]+/, ''); } else { opts.user = opts.email.replace(/@.+$/, ''); } return callback(); }); } else { print("Tried to get email address from Git, but could not determine using:\n\r\n\tgit config --get user.email\r\n\nIt might be a good idea to set it with:\n\r\n\tgit config etc...\r\n"); print("Please enter your details... (leave blank to abort)"); stdin = process.openStdin(); process.stdout.write("Name: "); return stdin.addListener("data", function(d) { if (!opts.user && (opts.user = d.toString().trim())) { return process.stdout.write("Email: "); } else if (!opts.email && (opts.email = d.toString().trim())) { process.stdin.destroy(); return callback(); } else { print("Error: tried everything, still no name and email!"); return exit(1); } }); } }); } }; get_confirmation = function(callback, message) { var stdin; stdin = process.openStdin(); process.stdout.write("Are you sure? [yep/nope]: "); return stdin.addListener("data", function(d) { if (d.toString().match(/y(e(p|s|ah))?/i)) { callback(); return process.stdin.destroy(); } else { if (message) { print(message); } process.stdin.destroy(); return exit(1); } }); }; get_required = function(items, final) { var item, stdin, _ref; stdin = process.stdin; if (!(items != null ? items.length : void 0)) { stdin.pause(); if (!((_ref = opts.command.needs) != null ? _ref.length : void 0)) { delete opts.command.needs; } return final(); } else { if (!opts.args[items[0]]) { item = items.shift(); } if (!opts.args[item] && item) { process.stdout.write("" + item + ": "); stdin.resume(); return stdin.once('data', function(d) { var line; stdin.pause(); line = d.toString().trim(); if (line) { opts.command.args[item] = line; } else { items.unshift(item); } return get_required(items, final); }); } } }; cmd = {}; cmd.add = function(args) { var $priority, $title, $type, $uid; print(args); $uid = randomUID(); $title = args.title; $type = args.type || 'bug'; $priority = args.priority || '0'; $priority = $priority.replace(/\./, '-'); opts.args.file = "" + opts.date + "." + $uid + ".log"; opts.args.template = "UID: " + $uid + "\nType: " + $type + "\nPriority: " + $priority + "\nTitle: " + $title + "\nAdded: " + opts.date + "\nAuthor: " + opts.user + "\n\r\n"; if (args.message) { fs.writeFileSync(opts.path + opts.store + opts.args.file, opts.args.template + args.message); print("added issue %" + glob.clrs.yellow + $uid + glob.clrs.reset); hook("add", opts.args.file); } else { opts.args.template += "# klog:\n# klog: Enter your bug report here; it is better to write too much than\n# klog: too little.\n# klog:\n# klog: Lines beginning with \"# klog:\" will be ignored, and removed,\n# klog: this file is saved.\n# klog:\r\n"; fs.writeFileSync(opts.args.file, opts.args.template); editFile(opts.args.file); remove_comments(opts.args.file); print("added issue %" + glob.clrs.yellow + $uid + glob.clrs.reset); return hook("add", opts.args.file); } }; cmd.append = function(args) { var $bug, $out; if (!args.id) { print("You must specify a bug to append to, either by the UID, or via the number.\nFor example to append text to bug number 3 you'd run:\n\r\n\tklog append 3\r\n"); exit(1); } $bug = getBugByUIDORNumber(args.id); if (args.message || args.type) { $out = "\r\n\r\nModified: " + opts.date + "\r\n"; if (args.type) { $out += "Type: " + args.type + "\r\n"; } if (args.priority) { $out += "Priority: " + (args.priority.replace(/[\.]/, '-')) + "\r\n"; } if (args.message !== '.') { $out += "" + (args.message || ''); } fs.appendFileSync(opts.path + opts.store + $bug.file, $out); return; } else { $out = "\r\nModified: " + opts.date + "\r\n\r\n"; fs.appendFileSync(opts.path + opts.store + $bug.file, $out); } editFile(opts.path + opts.store + $bug.file); return hook("append", $bug.file); }; cmd.html = function(args) { var $b, $bugs, $closed, $closed_count, $open, $open_count, out, _i, _j, _k, _len, _len1, _len2; $bugs = getBugs(); $open = []; $closed = []; for (_i = 0, _len = $bugs.length; _i < _len; _i++) { $b = $bugs[_i]; if ($b.status.match(/open/i)) { $open.push($b); } else { $closed.push($b); } } $open_count = $open.length; $closed_count = $closed.length; out = "<!DOCTYPE HTML>\n<html lang=\"en-US\">\n<head>\n <meta charset=\"UTF-8\">\n <title>klog : issue tracking and time management</title>\n <style type='text/css'>\n /*\n This is the<a href=\"#\" class=\"button default inline\">Default</a> action!\n <a href=\"#\" class=\"button blue\">Blue</a>\n */\n\n .button {\n margin: 0 15px 15px 0;\n font-family: 'Lucida Grande', 'Helvetica Neue', sans-serif;\n font-size: 13px;\n display: inline-block;\n background-color: #f5f5f5;\n background-image: -webkit-linear-gradient(top,#f5f5f5,#f1f1f1);\n background-image: -moz-linear-gradient(top,#f5f5f5,#f1f1f1);\n background-image: -ms-linear-gradient(top,#f5f5f5,#f1f1f1);\n background-image: -o-linear-gradient(top,#f5f5f5,#f1f1f1);\n background-image: linear-gradient(top,#f5f5f5,#f1f1f1);\n color: #444;\n \n border: 1px solid #dcdcdc;\n -webkit-border-radius: 2px;\n -moz-border-radius: 2px;\n border-radius: 2px;\n \n cursor: default;\n font-size: 11px;\n font-weight: bold;\n text-align: center;\n height: 27px;\n line-height: 27px;\n min-width: 54px;\n padding: 0 8px;\n text-decoration: none;\n }\n\n .button.inline {\n margin: 0 .2em 0 .5em;\n }\n\n .button:hover {\n background-color: #F8F8F8;\n background-image: -webkit-linear-gradient(top,#f8f8f8,#f1f1f1);\n background-image: -moz-linear-gradient(top,#f8f8f8,#f1f1f1);\n background-image: -ms-linear-gradient(top,#f8f8f8,#f1f1f1);\n background-image: -o-linear-gradient(top,#f8f8f8,#f1f1f1);\n background-image: linear-gradient(top,#f8f8f8,#f1f1f1);\n \n border: 1px solid #C6C6C6;\n color: #333;\n \n -webkit-box-shadow: 0px 1px 1px rgba(0,0,0,.1);\n -moz-box-shadow: 0px 1px 1px rgba(0,0,0,.1);\n box-shadow: 0px 1px 1px rgba(0,0,0,.1);\n text-decoration: none;\n\n cursor: pointer;\n }\n\n /* blue */\n\n .button.blue {\n background-color: #4D90FE;\n background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed);\n background-image: -moz-linear-gradient(top,#4d90fe,#4787ed);\n background-image: -ms-linear-gradient(top,#4d90fe,#4787ed);\n background-image: -o-linear-gradient(top,#4d90fe,#4787ed);\n background-image: linear-gradient(top,#4d90fe,#4787ed);\n\n border: 1px solid #3079ED;\n color: white;\n }\n\n .button.blue:hover {\n border: 1px solid #2F5BB7;\n \n background-color: #357AE8;\n background-image: -webkit-linear-gradient(top,#4d90fe,#357ae8);\n background-image: -moz-linear-gradient(top,#4d90fe,#357ae8);\n background-image: -ms-linear-gradient(top,#4d90fe,#357ae8);\n background-image: -o-linear-gradient(top,#4d90fe,#357ae8);\n background-image: linear-gradient(top,#4d90fe,#357ae8);\n \n -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.1);\n -moz-box-shadow: 0 1px 1px rgba(0,0,0,.1);\n box-shadow: 0 1px 1px rgba(0,0,0,.1);\n }\n\n /* red */\n\n .button.red {\n background-color: #D14836;\n background-image: -webkit-linear-gradient(top,#dd4b39,#d14836);\n background-image: -moz-linear-gradient(top,#dd4b39,#d14836);\n background-image: -ms-linear-gradient(top,#dd4b39,#d14836);\n background-image: -o-linear-gradient(top,#dd4b39,#d14836);\n background-image: linear-gradient(top,#dd4b39,#d14836);\n \n border: 1px solid transparent;\n color: white;\n text-shadow: 0 1px rgba(0, 0, 0, 0.1);\n }\n\n .button.red:hover {\n background-color: #C53727;\n background-image: -webkit-linear-gradient(top,#dd4b39,#c53727);\n background-image: -moz-linear-gradient(top,#dd4b39,#c53727);\n background-image: -ms-linear-gradient(top,#dd4b39,#c53727);\n background-image: -o-linear-gradient(top,#dd4b39,#c53727);\n background-image: linear-gradient(top,#dd4b39,#c53727); \n }\n\n /* green */\n\n .button.green {\n background-color: #3D9400;\n background-image: -webkit-linear-gradient(top,#3d9400,#398a00);\n background-image: -moz-linear-gradient(top,#3d9400,#398a00);\n background-image: -ms-linear-gradient(top,#3d9400,#398a00);\n background-image: -o-linear-gradient(top,#3d9400,#398a00);\n background-image: linear-gradient(top,#3d9400,#398a00);\n \n border: 1px solid #29691D;\n color: white;\n text-shadow: 0 1px rgba(0, 0, 0, 0.1);\n }\n\n .button.green:hover {\n background-color: #368200;\n background-image: -webkit-linear-gradient(top,#3d9400,#368200);\n background-image: -moz-linear-gradient(top,#3d9400,#368200);\n background-image: -ms-linear-gradient(top,#3d9400,#368200);\n background-image: -o-linear-gradient(top,#3d9400,#368200);\n background-image: linear-gradient(top,#3d9400,#368200);\n \n border: 1px solid #2D6200;\n text-shadow: 0 1px rgba(0, 0, 0, 0.3);\n }\n </style>\n <style type='text/css'>\n body{\n font-family: century gothic;\n }\n .bug {\n background-color: #F7F7F7;\n border: 5px solid #666666;\n border-radius: 0.5em 0.5em 0.5em 0.5em;\n margin: 0.5em 0;\n padding: 0.3em 1em;\n }\n .bug h3{\n font-size: 2em;\n margin: 0.2em 0;\n }\n #command-intro {\n padding-left: 0.5em;\n width: 3.4em;\n }\n input {\n background-color: black;\n border: medium none;\n color: silver;\n float: left;\n height: 2em;\n margin: 0;\n padding: 0;\n font-size: 1em;\n }\n h1, h2, h3, h4, h5, h6, p, ul{\n clear: both;\n }\n #command{\n width: 40em;\n }\n #execute{\n border-left: 1px solid red;\n padding: 0 0.3em;\n }\n ul.nav{\n padding-top: 1em;\n }\n ul.nav li{\n float: left;\n list-style-type: none;\n margin: 0 1em 0 -1em;\n padding: 0;\n }\n ul.actions li{\n float: left;\n list-style-type: none;\n }\n ul.actions{\n padding: 0;\n }\n ul {\n margin: 0 0 1em;\n padding: 0 1em;\n }\n ul.attributes {\n color: #666666;\n list-style-type: circle;\n }\n .clear{\n clear: both;\n }\n form{\n border: 5px solid #666666;\n border-radius: 0.3em 0.3em 0.3em 0.3em;\n height: 2em;\n width: 49.05em;\n }\n </style>\n</head>\n<body onload=\"document.getElementById('command').focus()\">\n \n <h1>Klog : distributed issue tracking</h1>\n\n <form action='.' method='POST'>\n <input type=\"text\" value=\"$ klog\" readonly=\"readonly\" name=\"intro\" id=\"command-intro\">\n <input type=\"text\" name=\"command\" id=\"command\">\n <input type=\"submit\" id=\"execute\" value=\"execute!\">\n </form>\n\n <ul class='nav'>\n <li><a href='#open' class='button'>" + $open_count + " : open bugs</a></li>\n <li><a href='#closed' class='button'>" + $closed_count + " : closed bugs</a></li>\n </ul>\n <hr class='clear' />\n\n <a name='open'></a>\n <h2 id=\"open\">Open bugs</h2>"; for (_j = 0, _len1 = $open.length; _j < _len1; _j++) { $b = $open[_j]; out += "<div class='bug'>\n <h3>" + $b.title + "</h3>\n <ul class='attributes'>\n <li><strong>UID</strong>: " + $b.uid + "</li>\n <li><strong>Added</strong>: " + $b.added + "</li>\n <li><strong>Author</strong>: " + $b.author + "</li>\n <li><strong>Type</strong>: " + $b.type + "</li>\n <li><strong>Priority</strong>: " + $b.priority + "</li>\n </ul>\n <p>" + ($b.body.join("<br>\r\n<br>\r\n")) + "</p>\n <hr>\n <ul class='actions'>\n <li><a href='./?command=close " + $b.uid + "' class='button blue'>Close</a></li>\n <li><a href='./?command=delete " + $b.uid + " -f' class='button red' onclick='return confirm(\"Do you really want to delete this item?\")'>Delete</a></li>\n </ul>\n <br class='clear' />\n</div>"; } out += "<h2 id=\"closed\">Closed bugs</h2>"; for (_k = 0, _len2 = $closed.length; _k < _len2; _k++) { $b = $closed[_k]; out += "<div class='bug'>\n <h3>" + $b.title + "</h3>\n <ul class='attributes'>\n <li><strong>UID</strong>: " + $b.uid + "</li>\n <li><strong>Added</strong>: " + $b.added + "</li>\n <li><strong>Author</strong>: " + $b.author + "</li>\n <li><strong>Type</strong>: " + $b.type + "</li>\n <li><strong>Priority</strong>: " + $b.priority + "</li>\n </ul>\n <p>" + ($b.body.join("<br>\r\n<br>\r\n")) + "</p>\n <hr>\n <ul class='actions'>\n <li><a href='./?command=reopen " + $b.uid + "' class='button green'>Re-open</a></li>\n <li><a href='./?command=delete " + $b.uid + " -f' class='button red' onclick='return confirm(\"Do you really want to delete this item?\")'>Delete</a></li>\n </ul>\n <br class='clear' />\n</div>"; } out += " <div id=\"foot\">\n Generated by <a href=\"http://billymoon.github.com/klog/\">klog</a>.\n </div>\n</body>\n</html>"; if (args["return"]) { return out; } else { return print(out); } }; cmd.search = function(args) { var $b_body, $bug, $bugs, $match, $priority, $state, $term, $terms, $type, direction, found, m, pool, _i, _j, _len, _len1, _ref; $terms = args.terms; $bugs = getBugs(); $state = args.state || 'all'; $type = args.type || "all"; $priority = args.priority || "all"; if ($priority === true) { $priority = "all"; } if (m = $priority.match(/(.+)([+-])$/)) { $priority = m[1]; direction = m[2]; } else { direction = null; } found = []; for (_i = 0, _len = $bugs.length; _i < _len; _i++) { $bug = $bugs[_i]; if ($state !== "all" && $state.toLowerCase() !== $bug.status.toLowerCase()) { continue; } if ($type !== "all" && $type.toLowerCase() !== $bug.type.toLowerCase()) { continue; } if (($priority + '').match(/\./)) { $priority = 0 - $priority * 10; } $bug.priority = parseInt($bug.priority); if ($priority !== "all") { $priority = parseInt($priority); if (direction === '+' && $priority > $bug.priority) { continue; } else if (direction === null && $priority !== $bug.priority) { continue; } else if (direction === '-' && $priority < $bug.priority) { continue; } } $match = 1; $b_body = $bug.body.join('').replace(/(\\.|[^\w\s])/g, ''); pool = args.all ? $bug.title + $bug.type + $b_body : $bug.title; if (args.terms) { _ref = $terms.split(/[ \t]+/); for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { $term = _ref[_j]; if (!pool.match(new RegExp($term, 'i'))) { $match = 0; } } } if (!$match) { continue; } found.push($bug); } if (args["return"] && found.length === 1) { return found[0]; } else { return output_cli(found, 'priority'); } }; output_cli = function(bugs, sort) { var bug, cb, ch, cr, hl, out, pr, _i, _len; if (sort === 'priority') { bugs.sort(function(a, b) { return b.priority - a.priority; }); } else if (sort === 'added') { bugs.sort(function(a, b) { var aa, bb; bb = parseInt(b.added.replace(/\D/g, '')); aa = parseInt(a.added.replace(/\D/g, '')); return bb - aa; }); } else if (sort === 'modified') { bugs.sort(function(a, b) { var aa, bb; bb = parseInt(b.modified.replace(/\D/g, '')); aa = parseInt(a.modified.replace(/\D/g, '')); return bb - aa; }); } out = []; for (_i = 0, _len = bugs.length; _i < _len; _i++) { bug = bugs[_i]; hl = bug.status === 'open' ? glob.clrs.green : glob.clrs.red; cb = glob.clrs.bright; ch = glob.clrs.yellow; cr = glob.clrs.reset; pr = bug.priority > 1 ? glob.clrs.bright + glob.clrs.yellow : bug.priority > 0 ? glob.clrs.yellow : bug.priority < -1 ? glob.clrs.gunmetal : glob.clrs.silver; out.push("%" + hl + bug.uid + glob.clrs.reset + " [" + pr + (pad((bug.priority + '').replace(/^([1-9])/, '+$1'), 2, ' ')) + cr + "] [" + ch + bug.status + cr + "] [" + (ch + cb) + bug.type + cr + "] " + bug.title); } return print(out.join("\r\n")); }; cmd.view = function(args) { var $bug, $value, buffer; $value = args.id; if (!$value) { print("You must specify a bug to view, either by the UID, or via the number.\r\n"); print("\r\nFor example to view bug number 3 you'd run:\r\n"); print("\tklog view 3\r\n\r\n"); print("Maybe a list of open bugs will help you:\r\n\r\n"); cmd.search(); print("\r\n"); exit(1); } $bug = getBugByUIDORNumber($value); buffer = fs.readFileSync(opts.path + opts.store + $bug.file); return print(buffer.toString().replace(/^(\w+): /gm, "" + glob.clrs.yellow + "$1" + glob.clrs.reset + ": ")); }; cmd.close = function(args) { var $value; $value = args.id; if (!$value) { print("You must specify a bug to close, either by the UID, or via the number.\nFor example to close bug number 3 you'd run:\n\r\n\tklog close 3\r\n\r\n"); exit(1); } return changeBugState($value, "closed"); }; cmd.reopen = function(args) { var $value; $value = args.id; if (!$value) { print("You must specify a bug to reopen, either by the UID, or via the number.\nFor example to reopen bug number 3 you'd run:\n\r\n\tklog reopen 3"); exit(1); } return changeBugState($value, "open"); }; cmd.edit = function(args) { var $bug, $value; $value = args.id; if (!$value) { print("You must specify a bug to edit, either by the UID, or via the number.\nFor example to edit bug number 3 you'd run:\n\r\n\tklog edit 3\r\n\r\n"); exit(1); } $bug = getBugByUIDORNumber($value); editFile(opts.path + opts.store + $bug.file); return hook("edit", $bug.file); }; cmd["delete"] = function(args) { var do_delete; cmd.view(opts.command.args); do_delete = function() { var $bug, $file, $value; $value = args.id; if (!$value) { print("You must specify a bug to delete, either by the UID, or via the number.\nFor example to delete bug number 3 you'd run:\n\r\n\tklog delete 3\r\n"); exit(1); } $bug = getBugByUIDORNumber($value); $file = $bug.file; fs.unlinkSync(opts.path + opts.store + $file); return hook("delete", $bug.file); }; if (!args.force) { print("About to delete this bug..."); return get_confirmation(function() { return do_delete(); }, "Phew, that was close!"); } else { return do_delete(); } }; cmd.init = function() { if (!fs.existsSync(opts.store)) { fs.mkdirSync(opts.store); opts.path = process.cwd() + '/'; print("" + glob.clrs.gunmetal + "Now you have klogs on" + glob.clrs.reset + glob.clrs.red + "!" + glob.clrs.reset); return cmd.setup(); } else { print("There is already a .klog/ directory present here"); return exit(1); } }; cmd.destroy = function(args) { if (args.force) { return exec("rm -Rf " + (opts.path + opts.store)); } else { return print("This will destroy all issues. You must force this with `-f`."); } }; cmd.setup = function() { var settings; if (opts.user && opts.email) { settings = "{\n \"user\":\"" + (opts.user || 'John Doe') + "\",\n \"email\":\"" + (opts.email || 'john@thedoughfactory.com') + "\"\n}"; fs.writeFileSync("" + (opts.path + opts.store) + ".gitignore", "local"); fs.mkdirSync("" + (opts.path + opts.store) + "local"); fs.writeFileSync("" + (opts.path + opts.store) + "local/settings.json", settings); return print("Wrote settings to local file: " + (opts.path + opts.store) + "local/settings.json\r\n\r\n" + settings + "\r\n"); } else { return get_user_details(cmd.setup); } }; cmd.server = function() { var command, http, port, qs, url; opts.server = true; port = 1234; http = require('http'); qs = require('querystring'); url = require('url'); command = function(data) { var args; if (data.command) { args = data.command.trim().split(' '); } print(args); while (process.argv.length > 2) { process.argv.pop(); } _.each(args, function(v) { return process.argv.push(v); }); opts.date = getDate(); return main(); }; http.createServer(function(req, res) { var body, out_html, url_parts; out_html = function() { res.writeHead(200, { 'Content-Type': 'text/html' }); opts.command.args["return"] = true; return res.end(cmd.html(opts.command.args)); }; if (req.method === 'POST') { body = ''; req.on('data', function(data) { return body += data; }); return req.on('end', function() { var POST; POST = qs.parse(body); command(POST); return out_html(); }); } else if (req.method === 'GET') { url_parts = url.parse(req.url, true); command(url_parts.query); return out_html(); } }).listen(port); return print("Serving `" + opts.path + "` at http://127.0.0.1:" + port + "/"); }; get_command = function() { var command, commands, get_id, has, message, out, rejects, requirement, subcommand, valid, x, _i, _j, _len, _len1, _ref, _ref1; out = { args: [] }; get_id = function() { var id; if (id = opts.args._.shift()) { return opts.args.id = id.replace(/^%/, ''); } }; commands = { add: { required: ['title', 'message'], valid: ['type', 'priority', 'label'], args: function() { if (opts.args._.length) { return opts.args.title = opts.args._.join(' '); } } }, "delete": { required: ['id'], valid: ['force'], args: function() { var id; if (id = opts.args._.shift()) { return opts.args.id = id.replace(/^%/, ''); } } }, help: {}, init: {}, list: { valid: ['type', 'state', 'terms', 'all', 'return', 'priority'], args: function() { if (subcommand === 'search') { opts.args.all = true; } if (opts.args._.length) { opts.args.terms = opts.args._.join(' '); return console.log(opts.args.terms); } } }, open: { required: ['state'], valid: ['type', 'priority'], args: function() { return opts.args.state = 'open'; } }, closed: { required: ['state'], valid: ['type', 'priority'], args: function() { return opts.args.state = 'closed'; } }, view: { required: ['id'], args: get_id }, edit: { required: ['id'], valid: ['editor'], args: get_id }, append: { required: ['id', 'message'], valid: ['type', 'priority'], args: get_id }, reopen: { required: ['id'], args: get_id }, close: { required: ['id'], args: get_id }, html: {}, server: {}, destroy: { valid: ['force'] } }; for (command in commands) { if (!commands[command].valid) { commands[command].valid = []; } commands[command].valid.push('plain'); } commands.search = commands.list; subcommand = opts.args._.shift() || 'help'; command = commands[subcommand] || (subcommand = 'help'); out.name = subcommand; if (command.args) { command.args(); } if (command.required) { _ref = command.required; for (_i = 0, _len = _ref.length; _i < _len; _i++) { requirement = _ref[_i]; if (has = opts.args[requirement]) { if (!out.args) { out.args = {}; } out.args[requirement] = has; } else { if (!out.needs) { out.needs = []; } out.needs.push(requirement); } } } if (command.valid) { _ref1 = command.valid; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { valid = _ref1[_j]; if (has = opts.args[valid]) { if (!out.args) { out.args = {}; } out.args[valid] = has; } } } for (x in opts.args) { if (x !== '$0' && x !== '_') { if (!out.args[x]) { if (!rejects) { rejects = []; } rejects.push(x); } } } if (rejects) { message = "Error: unsupported option used"; print(message); exit(1); } return out; }; main = function() { var clr; opts.args = parseArgs(); opts.command = get_command(); opts.command.name = opts.command.name.replace(/^(open|closed|list)$/, 'search'); if (opts.command.args.plain) { for (clr in glob.clrs) { glob.clrs[clr] = ""; } } return get_required(opts.command.needs, function() { opts.args._.unshift(opts.command.name); if (opts.args.debug) { print(opts.args); } if (opts.args.exit) { exit(0); } if (opts.args.help || !opts.args._.length) { usage(); exit(1); } else { opts.cmd = opts.args._.shift(); } if (cmd[opts.command.name]) { return cmd[opts.command.name](opts.command.args); } else { return usage(); } }); }; opts = { ext: 'log', date: getDate(), store: '.klog/', win: process.platform === 'win32' }; path = process.cwd().split(/\//); for (_i = 0, _len = path.length; _i < _len; _i++) { folder = path[_i]; sep = opts.win ? "\\" : "/"; tpath = (path.join(sep)) + sep; if (fs.existsSync("" + (tpath + opts.store))) { opts.path = tpath; break; } path.pop(); } if (fs.existsSync("" + (opts.path + opts.store) + "/local/settings.json")) { buffer = fs.readFileSync("" + (opts.path + opts.store) + "/local/settings.json"); settings = JSON.parse(buffer.toString()); opts = _.extend(opts, settings); } glob = {}; glob.clrs = { bright: "\u001b[1m", red: "\u001b[31m", green: "\u001b[32m", blue: "\u001b[34m", cyan: "\u001b[36m", magenta: "\u001b[35m", yellow: "\u001b[33m", black: "\u001b[30m", gunmetal: "\u001b[30m\u001b[1m", silver: "\u001b[37m", white: "\u001b[37m\u001b[1m", back_red: "\u001b[41m", back_green: "\u001b[42m", back_blue: "\u001b[44m", back_cyan: "\u001b[46m", back_magenta: "\u001b[45m", back_yellow: "\u001b[43m", back_black: "\u001b[40m", back_silver: "\u001b[47m", reset: "\u001b[m" }; hooks = {}; if (fs.existsSync("" + (opts.path + opts.store) + "hooks")) { fs.readdirSync("" + (opts.path + opts.store) + "hooks").forEach(function(file) { return hooks[file.replace(/\.\w+$/, '')] = require("" + (opts.path + opts.store) + "hooks/" + file); }); } main(); }).call(this);