Overview
The command processing system parses text commands entered by users and executes them against the git simulation. It supports both git and Mercurial command sets with a flexible regex-based parsing system. Location:src/js/commands/
Architecture
Command Flow
User Input
↓
Command String
↓
Parser (parse function)
↓
Command Object
↓
Command Executor
↓
GitEngine Method
↓
Visualization Update
Command Structure
Location:src/js/commands/index.js
Command Configuration
Commands are defined with regex patterns and execution functions:var commandConfigs = {
'git': GitCommands.commandConfig,
'hg': MercurialCommands.commandConfig
};
// Example from GitCommands.commandConfig
{
commit: {
regex: /^git +commit($|\s)/,
options: ['--amend'],
execute: function(engine, command) {
var options = command.getSupportedMap();
engine.commit({
isAmend: !!options['--amend']
});
}
},
branch: {
regex: /^git +branch($|\s)/,
options: ['-d', '-D', '-f', '--contains'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
if (options['-d'] || options['-D']) {
engine.deleteBranch(args[0]);
} else if (options['--contains']) {
engine.printBranchesWithout(args[0]);
} else if (args.length === 0) {
engine.printBranches(engine.getBranches());
} else {
engine.validateAndMakeBranch(args[0], engine.getCommitFromRef('HEAD'));
}
}
}
}
Command Object
Parsed commands create a command object with metadata:var commandData = {
generalArgs: parsedOptions.generalArgs,
supportedMap: parsedOptions.supportedMap,
error: error,
vcs: vcs,
method: method,
options: options,
eventName: 'processGitCommand'
};
Parser Implementation
Location:src/js/commands/index.js
Main Parse Function
var parse = function(str) {
var vcs;
var method;
var options;
// Match against all command regexes
var regexMap = commands.getRegexMap();
Object.keys(regexMap).forEach(function (thisVCS) {
var map = regexMap[thisVCS];
Object.keys(map).forEach(function(thisMethod) {
var regex = map[thisMethod];
if (regex.exec(str)) {
vcs = thisVCS;
method = thisMethod;
// Parse options: split on space-groups, skip first 2 (vcs + command)
options = str.match(/('.*?'|".*?"|\S+)/g).slice(2);
}
});
});
if (!method) {
return false;
}
// Parse options and arguments
var parsedOptions = new CommandOptionParser(vcs, method, options);
var error = parsedOptions.explodeAndSet();
return {
toSet: {
generalArgs: parsedOptions.generalArgs,
supportedMap: parsedOptions.supportedMap,
error: error,
vcs: vcs,
method: method,
options: options,
eventName: 'processGitCommand'
}
};
};
CommandOptionParser
Parses command options and arguments:function CommandOptionParser(vcs, method, options) {
this.vcs = vcs;
this.method = method;
this.rawOptions = options;
// Get supported options for this command
this.supportedMap = commands.getOptionMap()[vcs][method];
if (this.supportedMap === undefined) {
throw new Error('No option map for ' + method);
}
this.generalArgs = [];
}
CommandOptionParser.prototype.explodeAndSet = function() {
for (var i = 0; i < this.rawOptions.length; i++) {
var part = this.rawOptions[i];
if (part.slice(0,1) == '-') {
// It's an option
if (this.supportedMap[part] === undefined) {
return new CommandProcessError({
msg: intl.str('option-not-supported', {option: part})
});
}
var next = this.rawOptions[i + 1];
var optionArgs = [];
// If next arg doesn't start with -, it's the option value
if (next && next.slice(0,1) !== '-') {
i++;
optionArgs = [next];
}
this.supportedMap[part] = optionArgs;
} else {
// General argument
this.generalArgs.push(part);
}
}
};
Command Execution
Location:src/js/commands/index.js
Execute Method
var commands = {
execute: function(vcs, name, engine, commandObj) {
if (!commandConfigs[vcs][name]) {
throw new Error('i don\'t have a command for ' + name);
}
var config = commandConfigs[vcs][name];
if (config.delegate) {
return this.delegateExecute(config, engine, commandObj);
}
config.execute.call(this, engine, commandObj);
}
};
Command Delegation
Some commands delegate to others:delegateExecute: function(config, engine, commandObj) {
var result = config.delegate.call(this, engine, commandObj);
if (result.multiDelegate) {
// Execute multiple commands in sequence
result.multiDelegate.forEach(function(delConfig) {
commandObj.setOptionsMap(delConfig.options || {});
commandObj.setGeneralArgs(delConfig.args || []);
commandConfigs[delConfig.vcs][delConfig.name]
.execute.call(this, engine, commandObj);
}, this);
} else {
// Single delegation
commandConfigs[result.vcs][result.name]
.execute.call(this, engine, commandObj);
}
}
Common Git Commands
Location:src/js/git/commands.js
Commit
commit: {
regex: /^git +commit($|\s)/,
options: ['--amend'],
execute: function(engine, command) {
var options = command.getSupportedMap();
engine.commit({
isAmend: !!options['--amend']
});
}
}
Checkout
checkout: {
regex: /^git +checkout($|\s)/,
options: ['-b', '-B', '--detach'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
if (options['-b'] || options['-B']) {
// Create and checkout new branch
var branch = engine.validateAndMakeBranch(
args[0],
engine.getCommitFromRef('HEAD')
);
engine.checkout(branch);
} else {
// Checkout existing ref
engine.checkout(args[0]);
}
}
}
Branch
branch: {
regex: /^git +branch($|\s)/,
options: ['-d', '-D', '-f', '--contains'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
if (options['-d'] || options['-D']) {
// Delete branch
engine.deleteBranch(args[0]);
} else if (options['--contains']) {
// List branches containing ref
engine.printBranchesWithout(args[0]);
} else if (args.length === 0) {
// List all branches
engine.printBranches(engine.getBranches());
} else {
// Create new branch
engine.validateAndMakeBranch(
args[0],
engine.getCommitFromRef('HEAD')
);
}
}
}
Merge
merge: {
regex: /^git +merge($|\s)/,
options: ['--no-ff'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
engine.merge(args[0], {
noFF: !!options['--no-ff']
});
}
}
Rebase
rebase: {
regex: /^git +rebase($|\s)/,
options: ['-i', '--interactive', '--aboveAll'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
if (options['-i'] || options['--interactive']) {
engine.rebaseInteractive(args[0]);
} else {
engine.rebase(args[0]);
}
}
}
Cherry-pick
cherrypick: {
regex: /^git +cherry-pick($|\s)/,
options: [],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var toCherrypick = args.map(function(arg) {
return engine.getCommitFromRef(arg);
});
engine.setupCherrypickChain(toCherrypick);
}
}
Reset
reset: {
regex: /^git +reset($|\s)/,
options: ['--hard', '--soft'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
engine.reset(args[0]);
}
}
Remote Commands
Clone
clone: {
regex: /^git +clone($|\s)/,
options: [],
execute: function(engine, command) {
engine.makeOrigin();
}
}
Fetch
fetch: {
regex: /^git +fetch($|\s)/,
options: ['--force'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
engine.fetch({
source: args[0],
destination: args[1],
force: !!options['--force']
});
}
}
Pull
pull: {
regex: /^git +pull($|\s)/,
options: ['--rebase', '--force'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
engine.pull({
source: args[0],
destination: args[1],
isRebase: !!options['--rebase'],
force: !!options['--force']
});
}
}
Push
push: {
regex: /^git +push($|\s)/,
options: ['--force', '-f', '-u', '--set-upstream'],
execute: function(engine, command) {
var args = command.getGeneralArgs();
var options = command.getSupportedMap();
engine.push({
source: args[0] || 'HEAD',
destination: args[1] || args[0],
force: !!(options['--force'] || options['-f'])
});
}
}
Utility Methods
Location:src/js/commands/index.js
getRegexMap()
Returns map of command patterns:getRegexMap: function() {
var map = this.blankMap();
this.loop(function(config, name, vcs) {
var displayName = config.displayName || name;
map[vcs][displayName] = config.regex;
});
return map;
}
getOptionMap()
Returns supported options for each command:getOptionMap: function() {
var optionMap = this.blankMap();
this.loop(function(config, name, vcs) {
var displayName = config.displayName || name;
var thisMap = {};
(config.options || []).forEach(function(option) {
thisMap[option] = false;
});
optionMap[vcs][displayName] = thisMap;
});
return optionMap;
}
getCommandsThatCount()
Returns commands that count toward level score:getCommandsThatCount: function() {
var counted = this.blankMap();
this.loop(function(config, name, vcs) {
if (config.dontCountForGolf) {
return;
}
counted[vcs][name] = config.regex;
});
return counted;
}
Command Shortcuts
Commands can define shortcuts:commit: {
regex: /^git +commit($|\s)/,
sc: /^(gc|git ci)($|\s)/,
options: ['--amend'],
execute: function(engine, command) {
// ...
}
}
// Usage: "gc" instead of "git commit"
Error Handling
CommandProcessError
function CommandProcessError(options) {
this.msg = options.msg;
}
CommandProcessError.prototype.getMsg = function() {
return this.msg;
};
CommandResult
For non-error results:function CommandResult(options) {
this.msg = options.msg;
}
CommandResult.prototype.getMsg = function() {
return this.msg;
};
Command Descriptions
Commands can include descriptions for help:getDescriptionMap: function() {
var map = this.blankMap();
this.loop(function(config, name, vcs) {
var displayName = config.displayName || name;
if (config.description) {
map[vcs][displayName] = config.description;
}
});
return map;
}
Level-Specific Commands
Levels can disable certain commands:// In level definition
var level = {
name: 'Example Level',
disabledMap: {
'git rebase': true,
'git cherry-pick': true
},
// ...
};
// DisabledMap implementation
class DisabledMap {
getInstantCommands() {
return Object.keys(this.disabledMap).map(function(command) {
return [
new RegExp('^' + command),
function() {
throw new CommandResult({
msg: intl.str('disabled-command', {command: command})
});
}
];
});
}
}
Related Components
- GitEngine - Executes parsed commands
- Level System - Validates command results
- Visualization System - Displays command effects