auto-type parser

This commit is contained in:
antelle 2016-04-08 18:40:00 +03:00
parent 735ba1fbcf
commit ee3e9dcb95
3 changed files with 312 additions and 0 deletions

View File

@ -0,0 +1,131 @@
'use strict';
var AutoTypeRunner = require('./auto-type-runner');
var AutoTypeParser = function(sequence) {
this.sequence = sequence;
this.ix = 0;
this.states = [];
};
AutoTypeParser.opSepRegex = /[\s:=]+/;
AutoTypeParser.prototype.parse = function() {
var len = this.sequence.length;
this.pushState();
while (this.ix < len) {
var ch = this.sequence[this.ix];
switch (ch) {
case '{':
this.readOp();
break;
case '+':
case '%':
case '^':
this.readModifier(ch);
break;
case '(':
this.pushState();
break;
case ')':
this.popState();
break;
case '~':
this.addOp('ENTER');
break;
default:
this.addChar(ch);
break;
}
}
if (this.states.length !== 0) {
throw 'Groups count mismatch';
}
return new AutoTypeRunner(this.state().ops);
};
AutoTypeParser.prototype.pushState = function() {
this.states.unshift({
modifiers: null,
ops: []
});
};
AutoTypeParser.prototype.popState = function() {
if (this.states.length <= 1) {
throw 'Unexpected ")" at index ' + this.ix;
}
var state = this.states.shift();
this.addState(state);
};
AutoTypeParser.prototype.state = function() {
return this.states[0];
};
AutoTypeParser.prototype.readOp = function() {
var toIx = this.sequence.indexOf('}', this.ix + 2);
if (toIx < 0) {
throw 'Mismatched "{" at index ' + this.ix;
}
var contents = this.sequence.substring(this.ix + 1, toIx);
this.ix = toIx + 1;
if (contents.length === 1) {
this.addChar(contents);
return;
}
var parts = contents.split(AutoTypeParser.opSepRegex, 2);
if (parts.length > 1 && parts[0].length && parts[1].length) {
var op = parts[1];
var sep = contents.substr(op.length, 1);
var arg = parts[2];
this.addOp(op, sep, arg);
} else {
this.addOp(contents);
}
};
AutoTypeParser.prototype.readModifier = function(modifier) {
var state = this.state();
if (!state.modifiers) {
state.modifiers = {};
}
if (modifier === '^' && state.modifiers['^']) {
state.modifiers['^'] = false;
modifier = '^^';
}
state.modifiers[modifier] = true;
};
AutoTypeParser.prototype.resetModifiers = function() {
this.state.modifiers = null;
return this.state.modifiers;
};
AutoTypeParser.prototype.addState = function(state) {
this.state().ops.push({
type: 'group',
value: state.ops,
mod: this.resetModifiers()
});
};
AutoTypeParser.prototype.addChar = function(ch) {
this.state().ops.push({
type: 'text',
value: ch,
mod: this.resetModifiers()
});
};
AutoTypeParser.prototype.addOp = function(op, sep, arg) {
this.state().ops.push({
type: 'op',
value: op,
mod: this.resetModifiers(),
sep: sep,
arg: arg
});
};
module.exports = AutoTypeParser;

View File

@ -0,0 +1,143 @@
'use strict';
var AutoTypeRunner = function(ops) {
this.ops = ops;
this.pendingResolves = 0;
};
AutoTypeRunner.PendingResolve = { pending: true };
AutoTypeRunner.Keys = {
tab: 'tab', enter: 'enter', space: 'space',
up: 'up', down: 'down', left: 'left', right: 'right', home: 'home', end: 'end', pgup: 'pgup', pgdn: 'pgdn',
insert: 'ins', ins: 'ins', delete: 'del', del: 'del', backspace: 'bs', bs: 'bs', bksp: 'bs', esc: 'esc',
win: 'win', lwin: 'win', rwin: 'rwin', f1: 'f1', f2: 'f2', f3: 'f3', f4: 'f4', f5: 'f5', f6: 'f6',
f7: 'f7', f8: 'f8', f9: 'f9', f10: 'f10', f11: 'f11', f12: 'f12', f13: 'f13', f14: 'f14', f15: 'f15', f16: 'f16',
add: 'add', subtract: 'subtract', multiply: 'multiply', divide: 'divide',
numpad0: 'n0', numpad1: 'n1', numpad2: 'n2', numpad3: 'n3', numpad4: 'n4',
numpad5: 'n5', numpad6: 'n6', numpad7: 'n7', numpad8: 'n8', numpad9: 'n9'
};
AutoTypeRunner.Substitutions = {
title: function() {},
username: function() {},
url: function() {},
password: function() {},
notes: function() {},
group: function() {},
totp: function() {},
s: function() {},
'dt_simple': function() {},
'dt_year': function() {},
'dt_month': function() {},
'dt_day': function() {},
'dt_hour': function() {},
'dt_minute': function() {},
'dt_second': function() {},
'dt_utc_simple': function() {},
'dt_utc_year': function() {},
'dt_utc_month': function() {},
'dt_utc_day': function() {},
'dt_utc_hour': function() {},
'dt_utc_minute': function() {},
'dt_utc_second': function() {}
};
AutoTypeRunner.Commands = {
wait: function() {},
setDelay: function() {}
};
AutoTypeRunner.prototype.resolve = function(entry, callback) {
this.entry = entry;
this.resolveCallback = callback;
try {
this.resolveOps(this.ops);
if (!this.pendingResolves) {
callback();
}
} catch (e) {
return callback(e);
}
};
AutoTypeRunner.prototype.resolveOps = function(ops) {
for (var i = 0, len = ops.length; i < len; i++) {
this.forEachOp(ops[i], this.resolveOp, this);
}
};
AutoTypeRunner.prototype.forEachOp = function(op, fn, context) {
switch (op.type) {
case 'group':
return this.resolveOps(op.value);
case 'op':
return fn.call(context, op);
}
};
AutoTypeRunner.prototype.resolveOp = function(op) {
if (op.value.length === 1 && !op.sep) {
// {x}
op.type = 'text';
return;
}
if (op.value.length === 1 && op.sep === ' ') {
// {x 3}
op.type = 'text';
var ch = op.value, text = ch, len = +op.arg;
while (text.length < len) {
text += ch;
}
op.value = text;
return;
}
var lowerValue = op.value.toLowerCase();
var key = AutoTypeRunner.Keys[lowerValue];
if (key) {
if (op.sep === ' ' && +op.arg > 0) {
// {TAB 3}
op.type = 'group';
op.value = [];
var count = 0;
for (var i = 0; i < count; i++) {
op.value.push({ type: 'key', value: key });
}
} else {
// {TAB}
op.type = 'key';
op.value = key;
}
return;
}
var substitution = AutoTypeRunner.Substitutions[lowerValue];
if (substitution) {
op.type = 'text';
op.value = substitution(this, op);
if (op.value === AutoTypeRunner.PendingResolve) {
this.pendingResolves++;
}
return;
}
switch (lowerValue) {
case 'clearfield':
op.type = 'group';
op.value = [{ type: 'key', value: 'a', mod: { '^': true } }, { type: 'key', value: 'bs' }];
return;
case 'vkey':
op.type = 'key';
op.value = parseInt(op.value);
break;
case 'delay':
op.type = 'cmd';
op.value = op.sep === '=' ? 'setDelay' : 'wait';
op.arg = +op.arg;
break;
}
throw 'Bad op: ' + op.value;
};
AutoTypeRunner.prototype.run = function() {
};
module.exports = AutoTypeRunner;

View File

@ -0,0 +1,38 @@
'use strict';
var AutoTypeParser = require('./auto-type-parser');
var Logger = require('../../util/logger');
var logger = new Logger('auto-type');
var AutoType = {
run: function(entry, sequence, callback) {
logger.debug('Start', sequence);
try {
var parser = new AutoTypeParser(sequence);
var runner = parser.parse();
logger.debug('Parsed', runner.ops.length);
runner.resolve(entry, function(err) {
if (err) {
logger.error('Error', err);
return callback(err);
}
logger.debug('Running', runner.ops);
runner.run(function(err) {
if (err) {
logger.error('Run error', err);
return callback(err);
}
logger.debug('Complete');
return callback();
});
});
runner.run();
} catch (ex) {
logger.error('Parse error', ex);
return callback(ex);
}
}
};
module.exports = AutoType;