pcottle.learnGitBranching/src/js/git/commands.js
2013-05-27 10:56:35 -07:00

204 lines
5.2 KiB
JavaScript

var _ = require('underscore');
var intl = require('../intl');
var Errors = require('../util/errors');
var CommandProcessError = Errors.CommandProcessError;
var GitError = Errors.GitError;
var Warning = Errors.Warning;
var CommandResult = Errors.CommandResult;
var shortcutMap = {
'git commit': /^(gc|git ci)($|\s)/,
'git add': /^ga($|\s)/,
'git checkout': /^(go|git co)($|\s)/,
'git rebase': /^gr($|\s)/,
'git branch': /^(gb|git br)($|\s)/,
'git status': /^(gst|gs|git st)($|\s)/,
'git help': /^git$/
};
var instantCommands = [
[/^git help($|\s)/, function() {
var lines = [
intl.str('git-version'),
'<br/>',
intl.str('git-usage'),
_.escape(intl.str('git-usage-command')),
'<br/>',
intl.str('git-supported-commands'),
'<br/>'
];
var commands = GitOptionParser.prototype.getMasterOptionMap();
// build up a nice display of what we support
_.each(commands, function(commandOptions, command) {
lines.push('git ' + command);
_.each(commandOptions, function(vals, optionName) {
lines.push('\t ' + optionName);
}, this);
}, this);
// format and throw
var msg = lines.join('\n');
msg = msg.replace(/\t/g, '&nbsp;&nbsp;&nbsp;');
throw new CommandResult({
msg: msg
});
}]
];
var regexMap = {
// ($|\s) means that we either have to end the string
// after the command or there needs to be a space for options
'git commit': /^git +commit($|\s)/,
'git add': /^git +add($|\s)/,
'git checkout': /^git +checkout($|\s)/,
'git rebase': /^git +rebase($|\s)/,
'git reset': /^git +reset($|\s)/,
'git branch': /^git +branch($|\s)/,
'git revert': /^git +revert($|\s)/,
'git log': /^git +log($|\s)/,
'git merge': /^git +merge($|\s)/,
'git show': /^git +show($|\s)/,
'git status': /^git +status($|\s)/,
'git cherry-pick': /^git +cherry-pick($|\s)/,
'git fakeTeamwork': /^git +fakeTeamwork *?$/,
'git fetch': /^git +fetch *?$/,
'git pull': /^git +pull($|\s)/,
'git clone': /^git +clone *?$/
};
var parse = function(str) {
var method;
var options;
// see if we support this particular command
_.each(regexMap, function(regex, thisMethod) {
if (regex.exec(str)) {
options = str.slice(thisMethod.length + 1);
method = thisMethod.slice('git '.length);
}
});
if (!method) {
return false;
}
// we support this command!
// parse off the options and assemble the map / general args
var parsedOptions = new GitOptionParser(method, options);
return {
toSet: {
generalArgs: parsedOptions.generalArgs,
supportedMap: parsedOptions.supportedMap,
method: method,
options: options,
eventName: 'processGitCommand'
}
};
};
/**
* GitOptionParser
*/
function GitOptionParser(method, options) {
this.method = method;
this.rawOptions = options;
this.supportedMap = this.getMasterOptionMap()[method];
if (this.supportedMap === undefined) {
throw new Error('No option map for ' + method);
}
this.generalArgs = [];
this.explodeAndSet();
}
GitOptionParser.prototype.getMasterOptionMap = function() {
// here a value of false means that we support it, even if its just a
// pass-through option. If the value is not here (aka will be undefined
// when accessed), we do not support it.
return {
commit: {
'--amend': false,
'-a': false, // warning
'-am': false, // warning
'-m': false
},
status: {},
log: {},
add: {},
'cherry-pick': {},
branch: {
'-d': false,
'-D': false,
'-f': false,
'-a': false,
'-r': false,
'--contains': false
},
checkout: {
'-b': false,
'-B': false,
'-': false
},
reset: {
'--hard': false,
'--soft': false // this will raise an error but we catch it in gitEngine
},
merge: {},
rebase: {
'-i': false // the mother of all options
},
revert: {},
show: {},
clone: {},
fetch: {},
pull: {
'--rebase': false
},
fakeTeamwork: {}
};
};
GitOptionParser.prototype.explodeAndSet = function() {
// split on spaces, except when inside quotes
var exploded = this.rawOptions.match(/('.*?'|".*?"|\S+)/g) || [];
for (var i = 0; i < exploded.length; i++) {
var part = exploded[i];
if (part.slice(0,1) == '-') {
// it's an option, check supportedMap
if (this.supportedMap[part] === undefined) {
throw new CommandProcessError({
msg: intl.str(
'option-not-supported',
{ option: part }
)
});
}
// go through and include all the next args until we hit another option or the end
var optionArgs = [];
var next = i + 1;
while (next < exploded.length && exploded[next].slice(0,1) != '-') {
optionArgs.push(exploded[next]);
next += 1;
}
i = next - 1;
// **phew** we are done grabbing those. theseArgs is truthy even with an empty array
this.supportedMap[part] = optionArgs;
} else {
// must be a general arg
this.generalArgs.push(part);
}
}
};
exports.shortcutMap = shortcutMap;
exports.instantCommands = instantCommands;
exports.parse = parse;
exports.regexMap = regexMap;