var Backbone = require('backbone'); var Errors = require('../util/errors'); var ParseWaterfall = require('../level/parseWaterfall').ParseWaterfall; var LevelStore = require('../stores/LevelStore'); var intl = require('../intl'); var CommandProcessError = Errors.CommandProcessError; var GitError = Errors.GitError; var Warning = Errors.Warning; var CommandResult = Errors.CommandResult; var Command = Backbone.Model.extend({ defaults: { status: 'inqueue', rawStr: null, result: '', createTime: null, error: null, warnings: null, parseWaterfall: new ParseWaterfall(), generalArgs: null, supportedMap: null, options: null, method: null }, initialize: function() { this.initDefaults(); this.validateAtInit(); this.on('change:error', this.errorChanged, this); // catch errors on init if (this.get('error')) { this.errorChanged(); } this.parseOrCatch(); }, initDefaults: function() { // weird things happen with defaults if you don't // make new objects this.set('generalArgs', []); this.set('supportedMap', {}); this.set('warnings', []); }, replaceDotWithHead: function(string) { return string.replace(/\./g, 'HEAD'); }, /** * Since mercurial always wants revisions with * -r, we want to just make these general * args for git */ appendOptionR: function() { var rOptions = this.getOptionsMap()['-r'] || []; this.setGeneralArgs( this.getGeneralArgs().concat(rOptions) ); }, // if order is important prependOptionR: function() { var rOptions = this.getOptionsMap()['-r'] || []; this.setGeneralArgs( rOptions.concat(this.getGeneralArgs()) ); }, mapDotToHead: function() { var generalArgs = this.getGeneralArgs(); var options = this.getOptionsMap(); generalArgs = generalArgs.map(function(arg) { return this.replaceDotWithHead(arg); }, this); var newMap = {}; Object.keys(options).forEach(function(key) { var args = options[key]; newMap[key] = Object.values(args).map(function (arg) { return this.replaceDotWithHead(arg); }, this); }, this); this.setGeneralArgs(generalArgs); this.setOptionsMap(newMap); }, deleteOptions: function(options) { var map = this.getOptionsMap(); options.forEach(function(option) { delete map[option]; }, this); this.setOptionsMap(map); }, getGeneralArgs: function() { return this.get('generalArgs'); }, setGeneralArgs: function(args) { this.set('generalArgs', args); }, setOptionsMap: function(map) { this.set('supportedMap', map); }, getOptionsMap: function() { return this.get('supportedMap'); }, acceptNoGeneralArgs: function() { if (this.getGeneralArgs().length) { throw new GitError({ msg: intl.str('git-error-no-general-args') }); } }, oneArgImpliedHead: function(args, option) { this.validateArgBounds(args, 0, 1, option); // and if it's one, add a HEAD to the back this.impliedHead(args, 0); }, twoArgsImpliedHead: function(args, option) { // our args we expect to be between 1 and 2 this.validateArgBounds(args, 1, 2, option); // and if it's one, add a HEAD to the back this.impliedHead(args, 1); }, oneArgImpliedOrigin: function(args) { this.validateArgBounds(args, 0, 1); if (!args.length) { args.unshift('origin'); } }, twoArgsForOrigin: function(args) { this.validateArgBounds(args, 0, 2); }, impliedHead: function(args, min) { if(args.length == min) { args.push('HEAD'); } }, // this is a little utility class to help arg validation that happens over and over again validateArgBounds: function(args, lower, upper, option) { var what = (option === undefined) ? 'git ' + this.get('method') : this.get('method') + ' ' + option + ' '; what = 'with ' + what; if (args.length < lower) { throw new GitError({ msg: intl.str( 'git-error-args-few', { lower: String(lower), what: what } ) }); } if (args.length > upper) { throw new GitError({ msg: intl.str( 'git-error-args-many', { upper: String(upper), what: what } ) }); } }, validateAtInit: function() { if (this.get('rawStr') === null) { throw new Error('Give me a string!'); } if (!this.get('createTime')) { this.set('createTime', new Date().toString()); } }, setResult: function(msg) { this.set('result', msg); }, finishWith: function(deferred) { this.set('status', 'finished'); deferred.resolve(); }, addWarning: function(msg) { this.get('warnings').push(msg); // change numWarnings so the change event fires. This is bizarre -- Backbone can't // detect if an array changes, so adding an element does nothing this.set('numWarnings', this.get('numWarnings') ? this.get('numWarnings') + 1 : 1); }, parseOrCatch: function() { this.expandShortcuts(this.get('rawStr')); try { this.processInstants(); } catch (err) { Errors.filterError(err); // errorChanged() will handle status and all of that this.set('error', err); return; } if (this.parseAll()) { // something in our parse waterfall succeeded return; } // if we reach here, this command is not supported :-/ this.set('error', new CommandProcessError({ msg: intl.str( 'git-error-command-not-supported', { command: this.get('rawStr') }) }) ); }, errorChanged: function() { var err = this.get('error'); if (!err) { return; } if (err instanceof CommandProcessError || err instanceof GitError) { this.set('status', 'error'); } else if (err instanceof CommandResult) { this.set('status', 'finished'); } else if (err instanceof Warning) { this.set('status', 'warning'); } this.formatError(); }, formatError: function() { this.set('result', this.get('error').getMsg()); }, expandShortcuts: function(str) { str = this.get('parseWaterfall').expandAllShortcuts(str); this.set('rawStr', str); }, processInstants: function() { var str = this.get('rawStr'); // first if the string is empty, they just want a blank line if (!str.length) { throw new CommandResult({msg: ""}); } // then instant commands that will throw this.get('parseWaterfall').processAllInstants(str); }, parseAll: function() { var rawInput = this.get('rawStr'); const aliasMap = LevelStore.getAliasMap(); for (var i = 0; i