have visual refs working and better error models

This commit is contained in:
Peter Cottle 2012-09-12 21:38:00 -07:00
parent 9221088853
commit af76c03ad1
10 changed files with 212 additions and 74 deletions

5
src/collections.js Normal file
View file

@ -0,0 +1,5 @@
var CommitCollection = Backbone.Collection.extend({
model: Commit
});
var commitCollection = new CommitCollection();

View file

@ -37,22 +37,24 @@ Command.prototype.getRegexMap = function() {
Command.prototype.getSandboxCommands = function() { Command.prototype.getSandboxCommands = function() {
return [ return [
[/^ls/, function() { [/^ls/, function() {
throw new CommandResult("\ throw new CommandResult({
DontWorryAboutFilesInThisDemo.txt\ msg: "DontWorryAboutFilesInThisDemo.txt"
"); });
}], }],
[/^cd/, function() { [/^cd/, function() {
throw new CommandResult("\ throw new CommandResult({
Directory Changed to '/directories/dont/matter/in/this/demo' \ msg: "Directory Changed to '/directories/dont/matter/in/this/demo'"
"); });
}], }],
[/^git$/, function() { [/^git$/, function() {
throw new CommandResult(_.escape("\ throw new CommandResult({
msg: _.escape("\
Git Version \n \ Git Version \n \
PCOTTLE.1.0 \ PCOTTLE.1.0 \
Usage: \n \ Usage: \n \
git <command> [<args>] \ git <command> [<args>] \
")); ")
});
}] }]
]; ];
}; };
@ -60,7 +62,7 @@ Command.prototype.getSandboxCommands = function() {
Command.prototype.parse = function(str) { Command.prototype.parse = function(str) {
// first if the string is empty, they just want a blank line // first if the string is empty, they just want a blank line
if (!str.length) { if (!str.length) {
throw new CommandResult(""); throw new CommandResult({msg: ""});
} }
// then check if it's one of our sandbox commands // then check if it's one of our sandbox commands
@ -82,7 +84,9 @@ Command.prototype.parse = function(str) {
// see if begins with git // see if begins with git
if (str.slice(0,3) !== 'git') { if (str.slice(0,3) !== 'git') {
throw new CommandProcessError('Git commands only, sorry!'); throw new CommandProcessError({
msg: 'Git commands only, sorry!'
});
} }
// ok, we have a (probably) valid command. actually parse it // ok, we have a (probably) valid command. actually parse it
@ -106,9 +110,9 @@ Command.prototype.gitParse = function(str) {
}, this); }, this);
if (!matched) { if (!matched) {
throw new CommandProcessError( throw new CommandProcessError({
"Sorry, this demo does not support that git command: " + this.fullCommand msg: "Sorry, this demo does not support that git command: " + this.fullCommand
); });
} }
this.optionParser = new OptionParser(this.method, this.options); this.optionParser = new OptionParser(this.method, this.options);
@ -170,7 +174,9 @@ OptionParser.prototype.explodeAndSet = function() {
if (part.slice(0,1) == '-') { if (part.slice(0,1) == '-') {
// it's an option, check supportedMap // it's an option, check supportedMap
if (this.supportedMap[part] === undefined) { if (this.supportedMap[part] === undefined) {
throw new CommandProcessError('The option "' + part + '" is not supported'); throw new CommandProcessError({
msg: 'The option "' + part + '" is not supported'
});
} }
// go through and include all the next args until we hit another option or the end // go through and include all the next args until we hit another option or the end

View file

@ -20,10 +20,21 @@ var graphics = {
edgeStroke: 'rgba(94%, 96%, 98%, 0.5)', // '#EFF5FB', edgeStroke: 'rgba(94%, 96%, 98%, 0.5)', // '#EFF5FB',
nodeEdge: 'rgba(94%, 96%, 98%, 0.9)', // '#EFF5FB', nodeEdge: 'rgba(94%, 96%, 98%, 0.9)', // '#EFF5FB',
nodeFill: '#0066cc', nodeFill: '#0066cc',
nodeRadius: 10,
// widths // widths
nodeStrokeWidth: 15, nodeStrokeWidth: 15,
edgeWidth: 2, edgeWidth: 2,
// ref names
refFont: '14pt Courier New',
refFontFill: '#FFF',
// ref arrows
arrowFill: '#FFF',
arrowStroke: '#000',
arrowWidth: 4,
arrowHeadWidth: 5
}; };
function randomString(string_length) { function randomString(string_length) {

View file

@ -1,31 +1,35 @@
function CommandProcessError(msg) { var MyError = Backbone.Model.extend({
this.msg = msg; defaults: {
} type: 'MyError',
msg: 'Unknown Error'
},
toString: function() {
return this.get('type') + ': ' + this.get('msg');
},
CommandProcessError.prototype.toString = function() { getMsg: function() {
return 'Command Process Error: ' + this.msg; return this.get('msg') || 'Unknown Error';
}; },
CommandProcessError.prototype.toResult = function() { toResult: function() {
return this.msg.replace('\n', '</br>'); return this.get('msg').replace('\n', '</br>');
}; }
});
function CommandResult(msg) { var CommandProcessError = MyError.extend({
this.msg = msg; defaults: {
} type: 'Command Process Error'
}
});
CommandResult.prototype.toString = function() { var CommandResult = MyError.extend({
return 'Command Result: ' + this.msg; defaults: {
}; type: 'Command Result'
}
});
CommandResult.prototype.toResult = function() { var GitError = MyError.extend({
return this.msg.replace('\n', '</br>'); defaults: {
}; type: 'Git Error'
}
function GitError(msg) { });
this.msg = msg;
}
GitError.prototype.toString = function() {
return 'Git Error: ' + this.msg;
};

View file

@ -13,6 +13,7 @@ function GitEngine() {
this.HEAD = null; this.HEAD = null;
this.id_gen = 0; this.id_gen = 0;
this.branches = []; this.branches = [];
this.collection = commitCollection;
// global variable to keep track of the options given // global variable to keep track of the options given
// along with the command call. // along with the command call.
@ -27,6 +28,8 @@ function GitEngine() {
GitEngine.prototype.init = function() { GitEngine.prototype.init = function() {
// make an initial commit and a master branch // make an initial commit and a master branch
this.rootCommit = new Commit({rootCommit: true}); this.rootCommit = new Commit({rootCommit: true});
commitCollection.add(this.rootCommit);
this.refs[this.rootCommit.get('id')] = this.rootCommit; this.refs[this.rootCommit.get('id')] = this.rootCommit;
var master = this.makeBranch('master', this.rootCommit); var master = this.makeBranch('master', this.rootCommit);
@ -50,10 +53,14 @@ GitEngine.prototype.getDetachedHead = function() {
GitEngine.prototype.validateBranchName = function(name) { GitEngine.prototype.validateBranchName = function(name) {
name = name.replace(/\s/g, ''); name = name.replace(/\s/g, '');
if (!/^[a-zA-Z0-9]+$/.test(name)) { if (!/^[a-zA-Z0-9]+$/.test(name)) {
throw new Error('woah bad branch name!! This is not ok: ' + name); throw new GitError({
msg: 'woah bad branch name!! This is not ok: ' + name
});
} }
if (/[hH][eE][aA][dD]/.test(name)) { if (/[hH][eE][aA][dD]/.test(name)) {
throw new Error('branch name of "head" is ambiguous, dont name it that'); throw new GitError({
msg: 'branch name of "head" is ambiguous, dont name it that'
});
} }
return name; return name;
}; };
@ -61,7 +68,9 @@ GitEngine.prototype.validateBranchName = function(name) {
GitEngine.prototype.makeBranch = function(id, target) { GitEngine.prototype.makeBranch = function(id, target) {
id = this.validateBranchName(id); id = this.validateBranchName(id);
if (this.refs[id]) { if (this.refs[id]) {
throw new Error('that branch id already exists!'); throw new GitError({
msg: 'that branch id already exists!'
});
} }
var branch = new Branch({ var branch = new Branch({
@ -73,18 +82,34 @@ GitEngine.prototype.makeBranch = function(id, target) {
return branch; return branch;
}; };
GitEngine.prototype.getHead = function() {
return _.clone(this.HEAD);
};
GitEngine.prototype.getBranches = function() { GitEngine.prototype.getBranches = function() {
var toReturn = []; var toReturn = [];
_.each(this.branches, function(branch) { _.each(this.branches, function(branch) {
toReturn.push({ toReturn.push({
id: branch.get('id'), id: branch.get('id'),
selected: this.HEAD.get('target') === branch selected: this.HEAD.get('target') === branch,
target: branch.get('target')
}); });
}, this); }, this);
return toReturn; return toReturn;
}; };
GitEngine.prototype.printBranches = function() { GitEngine.prototype.printBranches = function() {
var branches = this.getBranches();
var result = '';
_.each(branches, function(branch) {
result += (branch.selected ? '* ' : '') + branch.id + '\n';
});
throw new CommandResult({
msg: result
});
};
GitEngine.prototype.logBranches = function() {
var branches = this.getBranches(); var branches = this.getBranches();
_.each(branches, function(branch) { _.each(branches, function(branch) {
console.log((branch.selected ? '* ' : '') + branch.id); console.log((branch.selected ? '* ' : '') + branch.id);
@ -96,12 +121,15 @@ GitEngine.prototype.makeCommit = function(parent) {
parents: [parent] parents: [parent]
}); });
this.refs[commit.get('id')] = commit; this.refs[commit.get('id')] = commit;
this.collection.add(commit);
return commit; return commit;
}; };
GitEngine.prototype.acceptNoGeneralArgs = function() { GitEngine.prototype.acceptNoGeneralArgs = function() {
if (this.generalArgs.length) { if (this.generalArgs.length) {
throw new GitError("That command accepts no general arguments"); throw new GitError({
msg: "That command accepts no general arguments"
});
} }
}; };
@ -167,10 +195,14 @@ GitEngine.prototype.resolveStringRef = function(ref) {
}, this); }, this);
if (!startRef) { if (!startRef) {
throw new Error('unknown ref ' + ref); throw new GitError({
msg: 'unknown ref ' + ref
});
} }
if (!this.refs[startRef]) { if (!this.refs[startRef]) {
throw new Error('the ref ' + startRef +' does not exist.'); throw new GitError({
msg: 'the ref ' + startRef +' does not exist.'
});
} }
var commit = this.getCommitFromRef(startRef); var commit = this.getCommitFromRef(startRef);
@ -224,14 +256,18 @@ GitEngine.prototype.numBackFrom = function(commit, numBack) {
} }
if (numBack !== 0 || pQueue.length == 0) { if (numBack !== 0 || pQueue.length == 0) {
throw new Error('exhausted search, sorry'); throw new GitError({
msg: "Sorry, I can't go that many commits back"
});
} }
return pQueue.shift(0); return pQueue.shift(0);
}; };
GitEngine.prototype.checkoutStarter = function() { GitEngine.prototype.checkoutStarter = function() {
if (this.generalArgs.length != 1) { if (this.generalArgs.length != 1) {
throw new GitError('I expect one argument along with git checkout (dont reference files)'); throw new GitError({
msg: 'I expect one argument along with git checkout (dont reference files)'
});
} }
this.checkout(this.generalArgs[0]); this.checkout(this.generalArgs[0]);
@ -249,7 +285,9 @@ GitEngine.prototype.checkout = function(idOrTarget) {
var type = target.get('type'); var type = target.get('type');
if (type !== 'branch' && type !== 'commit') { if (type !== 'branch' && type !== 'commit') {
throw new Error('can only checkout branches and commits!'); throw new GitError({
msg: 'can only checkout branches and commits!'
});
} }
this.HEAD.set('target', target); this.HEAD.set('target', target);
@ -259,7 +297,9 @@ GitEngine.prototype.branchStarter = function() {
// handle deletion first // handle deletion first
if (this.commandOptions['-d'] || this.commandOptions['-D']) { if (this.commandOptions['-d'] || this.commandOptions['-D']) {
if (!this.generalArgs.length) { if (!this.generalArgs.length) {
throw new GitError('I expect branch names when deleting'); throw new GitError({
msg: 'I expect branch names when deleting'
});
} }
_.each(this.generalArgs, function(name) { _.each(this.generalArgs, function(name) {
this.deleteBranch(name); this.deleteBranch(name);
@ -269,11 +309,14 @@ GitEngine.prototype.branchStarter = function() {
var len = this.generalArgs.length; var len = this.generalArgs.length;
if (len > 2) { if (len > 2) {
throw new GitError('git branch with more than two general args does not make sense!'); throw new GitError({
msg: 'git branch with more than two general args does not make sense!'
});
} }
if (len == 0) { if (len == 0) {
throw new Error('going to implement eventually'); this.printBranches();
//TODO: print branches, etc
return; return;
} }
@ -293,13 +336,19 @@ GitEngine.prototype.deleteBranch = function(name) {
// trying to delete, lets check our refs // trying to delete, lets check our refs
var target = this.resolveId(name); var target = this.resolveId(name);
if (target.get('type') !== 'branch') { if (target.get('type') !== 'branch') {
throw new GitError("You can't delete things that arent branches with branch command"); throw new GitError({
msg: "You can't delete things that arent branches with branch command"
});
} }
if (target.get('id') == 'master') { if (target.get('id') == 'master') {
throw new GitError("You can't delete the master branch!"); throw new GitError({
msg: "You can't delete the master branch!"
});
} }
if (this.HEAD.get('target') === target) { if (this.HEAD.get('target') === target) {
throw new GitError("Cannot delete the branch you are currently on"); throw new GitError({
msg: "Cannot delete the branch you are currently on"
});
} }
var id = target.get('id'); var id = target.get('id');
@ -315,10 +364,10 @@ GitEngine.prototype.dispatch = function(commandObj) {
}; };
GitEngine.prototype.add = function() { GitEngine.prototype.add = function() {
throw new Error( throw new CommandResult({
"This demo is meant to demonstrate git branching, so don't worry about " + msg: "This demo is meant to demonstrate git branching, so don't worry about " +
"adding / staging files. Just go ahead and commit away!" "adding / staging files. Just go ahead and commit away!"
); });
}; };
GitEngine.prototype.execute = function(command, callback) { GitEngine.prototype.execute = function(command, callback) {
@ -401,7 +450,6 @@ var Commit = Backbone.Model.extend({
initialize: function() { initialize: function() {
this.validateAtInit(); this.validateAtInit();
this.addNodeToVisuals(); this.addNodeToVisuals();
console.log('MAKING NEW COMMIT', this.get('id'));
_.each(this.get('parents'), function(parent) { _.each(this.get('parents'), function(parent) {
parent.get('children').push(this); parent.get('children').push(this);

View file

@ -51,5 +51,7 @@
<script src="commandline.js"></script> <script src="commandline.js"></script>
<script src="views.js"></script> <script src="views.js"></script>
<script src="errors.js"></script> <script src="errors.js"></script>
<script src="collections.js"></script>
<script src="visuals.js"></script>
</body> </body>
</html> </html>

View file

@ -39,6 +39,7 @@ Renderer = function(canvas) {
ctx.clearRect(0,0, canvas.width, canvas.height); ctx.clearRect(0,0, canvas.width, canvas.height);
particleSystem.eachEdge(this.drawEdge); particleSystem.eachEdge(this.drawEdge);
particleSystem.eachNode(this.drawNode); particleSystem.eachNode(this.drawNode);
events.trigger('drawGitVisuals', particleSystem, ctx, canvas);
}, },
resize: function(){ resize: function(){

View file

@ -5,6 +5,7 @@ var events = _.clone(Backbone.Events);
var sys = null; var sys = null;
var graphicsEffects = {}; var graphicsEffects = {};
var gitEngine = null; var gitEngine = null;
var gitVisuals = null;
$(document).ready(function(){ $(document).ready(function(){
sys = arbor.ParticleSystem(4000, 500, 0.5, false, 55, 0.005, 'verlet'); sys = arbor.ParticleSystem(4000, 500, 0.5, false, 55, 0.005, 'verlet');
@ -18,6 +19,7 @@ $(document).ready(function(){
}); });
gitEngine = new GitEngine(); gitEngine = new GitEngine();
gitVisuals = new GitVisuals();
var repulsionBreathe = function(r) { var repulsionBreathe = function(r) {
sys.parameters({repulsion: r}); sys.parameters({repulsion: r});
@ -40,9 +42,10 @@ Node.prototype.drawCircleNode = function(ctx, pt) {
ctx.strokeStyle = graphics.nodeEdge; ctx.strokeStyle = graphics.nodeEdge;
ctx.lineWidth = graphics.nodeStrokeWidth; ctx.lineWidth = graphics.nodeStrokeWidth;
ctx.fillStyle = graphics.nodeFill; ctx.fillStyle = graphics.nodeFill;
var radius = graphics.nodeRadius;
ctx.beginPath(); ctx.beginPath();
ctx.arc(pt.x, pt.y, 10, 0, Math.PI*2, true); ctx.arc(pt.x, pt.y, radius, 0, Math.PI*2, true);
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
ctx.fill(); ctx.fill();

View file

@ -7,7 +7,7 @@ var CommandLineView = Backbone.View.extend({
$.proxy(this.keyUp, this) $.proxy(this.keyUp, this)
); );
events.on('commandConsumed', _.bind( events.on('commandReadyForProcess', _.bind(
this.parseOrCatch, this this.parseOrCatch, this
)); ));
}, },
@ -53,8 +53,8 @@ var CommandLineView = Backbone.View.extend({
processError: function(err) { processError: function(err) {
// in this demo, every command that's not a git command will // in this demo, every command that's not a git command will
// throw an exception. Some of these errors might be just to // throw an exception. Some of these errors might be just to
// short-circuit the normal programatic flow, so we handle them // short-circuit the normal programatic flow and print stuff,
// here // so we handle them here
if (err instanceof CommandProcessError) { if (err instanceof CommandProcessError) {
events.trigger('commandProcessError', err); events.trigger('commandProcessError', err);
} else if (err instanceof CommandResult) { } else if (err instanceof CommandResult) {
@ -81,13 +81,13 @@ var CommandLineView = Backbone.View.extend({
} }
this.index = -1; this.index = -1;
events.trigger('commandConsumed', value); events.trigger('commandSubmitted', value);
events.trigger('commandReadyForProcess', value);
}, },
parseOrCatch: function(value) { parseOrCatch: function(value) {
try { try {
var command = new Command(value); var command = new Command(value);
console.log(command);
events.trigger('gitCommandReady', command); events.trigger('gitCommandReady', command);
} catch (err) { } catch (err) {
this.processError(err); this.processError(err);
@ -97,7 +97,7 @@ var CommandLineView = Backbone.View.extend({
var CommandLineHistoryView = Backbone.View.extend({ var CommandLineHistoryView = Backbone.View.extend({
initialize: function(options) { initialize: function(options) {
events.on('commandConsumed', _.bind( events.on('commandSubmitted', _.bind(
this.addCommand, this this.addCommand, this
)); ));
@ -165,7 +165,8 @@ var CommandLineHistoryView = Backbone.View.extend({
}, },
commandResultPrint: function(err) { commandResultPrint: function(err) {
if (!err.msg.length) { if (!err.get('msg') || !err.get('msg').length) {
console.log(err);
// blank lines // blank lines
return; return;
} }

57
src/visuals.js Normal file
View file

@ -0,0 +1,57 @@
function GitVisuals() {
this.collection = commitCollection;
this.collection.on('change', _.bind(this.collectionChanged, this));
events.on('drawGitVisuals', _.bind(this.drawVisuals, this));
}
GitVisuals.prototype.drawVisuals = function(sys, ctx, canvas) {
// we need to draw refs here
var branches = gitEngine.getBranches();
var detachedHead = gitEngine.getDetachedHead();
var HEAD = gitEngine.getHead();
_.forEach(branches, _.bind(function(branch) {
// get the location of the arbor node and then somehow draw the ref to the side?
var node = branch.target.get('arborNode');
var nodePoint = sys.toScreen(node._p);
// text position
// TODO: better positioning of text here
var screenPoint = _.clone(nodePoint);
screenPoint.x += 100;
ctx.font = graphics.refFont;
ctx.fillStyle = graphics.refFontFill;
ctx.fillText(branch.id, screenPoint.x, screenPoint.y);
// also draw an arrow
var offset = Math.round(graphics.nodeRadius * 2.5);
this.drawArrow(ctx, screenPoint, nodePoint, graphics.arrowHeadWidth, offset);
}, this));
};
GitVisuals.prototype.drawArrow = function(ctx, start, end, headWidth, offset) {
// TODO only horizontal arrows for now, fix this later
var end = _.clone(end);
end.x += offset;
ctx.lineWidth = graphics.arrowWidth;
ctx.fillStyle = graphics.arrowFill;
ctx.strokeStyle = graphics.arrowStroke;
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(end.x, end.y);
// now do the little arrow head
ctx.lineTo(end.x + headWidth, end.y + headWidth);
ctx.lineTo(end.x + headWidth, end.y - headWidth);
ctx.lineTo(end.x, end.y);
ctx.stroke();
};
GitVisuals.prototype.collectionChanged = function() {
// redo the algorithms
};