Unforked the shared modules -- ES6 is now the default
This commit is contained in:
parent
323a42be37
commit
822ab39cbe
108 changed files with 3310 additions and 9776 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -28,11 +28,9 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
if (DEBUGGER) {
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
if (NODE) {
|
||||
var Str = require("../../shared/lib/strlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -50,105 +48,102 @@ if (DEBUGGER) {
|
|||
* fTemporary:(boolean|undefined),
|
||||
* sCmd:(string|undefined),
|
||||
* aCmds:(Array.<string>|undefined)
|
||||
* }} DbgAddr
|
||||
* }}
|
||||
*/
|
||||
var DbgAddr;
|
||||
|
||||
/**
|
||||
* Debugger(parmsDbg)
|
||||
* Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default,
|
||||
* it deters us from defining named properties on our components; eg:
|
||||
*
|
||||
* The Debugger component supports the following optional (parmsDbg) properties:
|
||||
* this['exports'] = {...}
|
||||
*
|
||||
* base: the base to use for most numeric input/output (default is 16)
|
||||
* results in an error:
|
||||
*
|
||||
* The Debugger component is a shared component containing a subset of functionality used by
|
||||
* the other CPU-specific Debuggers (eg, DebuggerX86). Over time, the goal is to factor out as
|
||||
* much common debugging support as possible from those components into this one.
|
||||
* Cannot do '[]' access on a struct
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsDbg
|
||||
* So, in order to define 'exports', we must override the @struct assumption by annotating
|
||||
* the class as @unrestricted (or @dict). Note that this must be done both here and in the
|
||||
* subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference*
|
||||
* the named property either.
|
||||
*
|
||||
* TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to
|
||||
* define every single property the class uses in its constructor, which results in a fair
|
||||
* bit of redundant initialization, since many properties aren't (and don't need to be) fully
|
||||
* initialized until the appropriate init(), reset(), restore(), etc. function is called.
|
||||
*
|
||||
* The upside, however, may be that since the structure of the class is completely defined by
|
||||
* the constructor, JavaScript engines may be able to optimize and run more efficiently.
|
||||
*
|
||||
* @unrestricted
|
||||
*/
|
||||
function Debugger(parmsDbg)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
class Debugger extends Component {
|
||||
/**
|
||||
* Debugger(parmsDbg)
|
||||
*
|
||||
* The Debugger component supports the following optional (parmsDbg) properties:
|
||||
*
|
||||
* base: the base to use for most numeric input/output (default is 16)
|
||||
*
|
||||
* The Debugger component is a shared component containing a subset of functionality used by
|
||||
* the other CPU-specific Debuggers (eg, DebuggerX86). Over time, the goal is to factor out as
|
||||
* much common debugging support as possible from those components into this one.
|
||||
*
|
||||
* @param {Object} parmsDbg
|
||||
*/
|
||||
constructor(parmsDbg) {
|
||||
if (DEBUGGER) {
|
||||
|
||||
Component.call(this, "Debugger", parmsDbg, Debugger);
|
||||
super("Debugger", parmsDbg);
|
||||
|
||||
/*
|
||||
* Default base used to display all values; modified with the "s base" command.
|
||||
*/
|
||||
this.nBase = parmsDbg['base'] || 16;
|
||||
this.fParens = false;
|
||||
/*
|
||||
* Default base used to display all values; modified with the "s base" command.
|
||||
*/
|
||||
this.nBase = +parmsDbg['base'] || 16;
|
||||
this.fParens = false;
|
||||
|
||||
/*
|
||||
* These keep track of instruction activity, but only when tracing or when Debugger checks
|
||||
* have been enabled (eg, one or more breakpoints have been set).
|
||||
*
|
||||
* They are zeroed by the reset() notification handler. cInstructions is advanced by
|
||||
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
|
||||
* call and simply represents the number of cycles performed by the last run of instructions.
|
||||
*/
|
||||
this.nCycles = 0;
|
||||
this.cOpcodes = this.cOpcodesStart = 0;
|
||||
/*
|
||||
* These keep track of instruction activity, but only when tracing or when Debugger checks
|
||||
* have been enabled (eg, one or more breakpoints have been set).
|
||||
*
|
||||
* They are zeroed by the reset() notification handler. cInstructions is advanced by
|
||||
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
|
||||
* call and simply represents the number of cycles performed by the last run of instructions.
|
||||
*/
|
||||
this.nCycles = 0;
|
||||
this.cOpcodes = this.cOpcodesStart = 0;
|
||||
|
||||
/*
|
||||
* fAssemble is true when "assemble mode" is active, false when not.
|
||||
*/
|
||||
this.fAssemble = false;
|
||||
/*
|
||||
* fAssemble is true when "assemble mode" is active, false when not.
|
||||
*/
|
||||
this.fAssemble = false;
|
||||
|
||||
/*
|
||||
* This maintains command history. New commands are inserted at index 0 of the array.
|
||||
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
|
||||
*/
|
||||
this.iPrevCmd = -1;
|
||||
this.aPrevCmds = [];
|
||||
/*
|
||||
* This maintains command history. New commands are inserted at index 0 of the array.
|
||||
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
|
||||
*/
|
||||
this.iPrevCmd = -1;
|
||||
this.aPrevCmds = [];
|
||||
|
||||
/*
|
||||
* aVariables is an object with properties that grow as setVariable() assigns more variables;
|
||||
* each property corresponds to one variable, where the property name is the variable name (ie,
|
||||
* a string beginning with a letter or underscore, followed by zero or more additional letters,
|
||||
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
|
||||
* and setVariable() for details.
|
||||
*
|
||||
* Note that parseValue() parses variables before numbers, so any variable that looks like a
|
||||
* unprefixed hex value (eg, "a5" as opposed to "0xa5") will trump the numeric value. Unprefixed
|
||||
* hex values are a convenience of parseValue(), which always calls str.parseInt() with a default
|
||||
* base of 16; however, that default be overridden with a variety of explicit prefixes or suffixes
|
||||
* (eg, a leading "0o" to indicate octal, a trailing period to indicate decimal, etc.)
|
||||
*
|
||||
* See str.parseInt() for more details about supported numbers.
|
||||
*/
|
||||
this.aVariables = {};
|
||||
/*
|
||||
* aVariables is an object with properties that grow as setVariable() assigns more variables;
|
||||
* each property corresponds to one variable, where the property name is the variable name (ie,
|
||||
* a string beginning with a letter or underscore, followed by zero or more additional letters,
|
||||
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
|
||||
* and setVariable() for details.
|
||||
*
|
||||
* Note that parseValue() parses variables before numbers, so any variable that looks like a
|
||||
* unprefixed hex value (eg, "a5" as opposed to "0xa5") will trump the numeric value. Unprefixed
|
||||
* hex values are a convenience of parseValue(), which always calls Str.parseInt() with a default
|
||||
* base of 16; however, that default be overridden with a variety of explicit prefixes or suffixes
|
||||
* (eg, a leading "0o" to indicate octal, a trailing period to indicate decimal, etc.)
|
||||
*
|
||||
* See Str.parseInt() for more details about supported numbers.
|
||||
*/
|
||||
this.aVariables = {};
|
||||
|
||||
} // endif DEBUGGER
|
||||
}
|
||||
|
||||
if (DEBUGGER) {
|
||||
|
||||
Component.subclass(Debugger);
|
||||
|
||||
Debugger.aBinOpPrecedence = {
|
||||
'||': 0, // logical OR
|
||||
'&&': 1, // logical AND
|
||||
'|': 2, // bitwise OR
|
||||
'^': 3, // bitwise XOR
|
||||
'&': 4, // bitwise AND
|
||||
'!=': 5, // inequality
|
||||
'==': 5, // equality
|
||||
'>=': 6, // greater than or equal to
|
||||
'>': 6, // greater than
|
||||
'<=': 6, // less than or equal to
|
||||
'<': 6, // less than
|
||||
'>>>': 7, // unsigned bitwise right shift
|
||||
'>>': 7, // bitwise right shift
|
||||
'<<': 7, // bitwise left shift
|
||||
'-': 8, // subtraction
|
||||
'+': 8, // addition
|
||||
'%': 9, // remainder
|
||||
'/': 9, // division
|
||||
'*': 9 // multiplication
|
||||
};
|
||||
} // endif DEBUGGER
|
||||
}
|
||||
|
||||
/**
|
||||
* getRegIndex(sReg, off)
|
||||
|
|
@ -160,10 +155,9 @@ if (DEBUGGER) {
|
|||
* @param {number} [off] optional offset into sReg
|
||||
* @return {number} register index, or -1 if not found
|
||||
*/
|
||||
Debugger.prototype.getRegIndex = function(sReg, off)
|
||||
{
|
||||
getRegIndex(sReg, off) {
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* getRegValue(iReg)
|
||||
|
|
@ -174,10 +168,9 @@ if (DEBUGGER) {
|
|||
* @param {number} iReg
|
||||
* @return {number|undefined}
|
||||
*/
|
||||
Debugger.prototype.getRegValue = function(iReg)
|
||||
{
|
||||
getRegValue(iReg) {
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* parseAddrReference(s, sAddr)
|
||||
|
|
@ -191,10 +184,9 @@ if (DEBUGGER) {
|
|||
* @param {string} sAddr
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.parseAddrReference = function(s, sAddr)
|
||||
{
|
||||
parseAddrReference(s, sAddr) {
|
||||
return s.replace('[' + sAddr + ']', "unimplemented");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* getNextCommand()
|
||||
|
|
@ -202,8 +194,7 @@ if (DEBUGGER) {
|
|||
* @this {Debugger}
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.getNextCommand = function()
|
||||
{
|
||||
getNextCommand() {
|
||||
var sCmd;
|
||||
if (this.iPrevCmd > 0) {
|
||||
sCmd = this.aPrevCmds[--this.iPrevCmd];
|
||||
|
|
@ -212,7 +203,7 @@ if (DEBUGGER) {
|
|||
this.iPrevCmd = -1;
|
||||
}
|
||||
return sCmd;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* getPrevCommand()
|
||||
|
|
@ -220,14 +211,13 @@ if (DEBUGGER) {
|
|||
* @this {Debugger}
|
||||
* @return {string|null}
|
||||
*/
|
||||
Debugger.prototype.getPrevCommand = function()
|
||||
{
|
||||
getPrevCommand() {
|
||||
var sCmd = null;
|
||||
if (this.iPrevCmd < this.aPrevCmds.length - 1) {
|
||||
sCmd = this.aPrevCmds[++this.iPrevCmd];
|
||||
}
|
||||
return sCmd;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* parseCommand(sCmd, fSave, chSep)
|
||||
|
|
@ -238,8 +228,7 @@ if (DEBUGGER) {
|
|||
* @param {string} [chSep] is the command separator character (default is ';')
|
||||
* @return {Array.<string>}
|
||||
*/
|
||||
Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
|
||||
{
|
||||
parseCommand(sCmd, fSave, chSep) {
|
||||
if (fSave) {
|
||||
if (!sCmd) {
|
||||
if (this.fAssemble) {
|
||||
|
|
@ -265,7 +254,7 @@ if (DEBUGGER) {
|
|||
* associated with a breakpoint), we can no longer perform simplistic splitting.
|
||||
*
|
||||
* a = sCmd.split(chSep || ';');
|
||||
* for (var i = 0; i < a.length; i++) a[i] = str.trim(a[i]);
|
||||
* for (var i = 0; i < a.length; i++) a[i] = Str.trim(a[i]);
|
||||
*
|
||||
* We may now split on semi-colons ONLY if they are outside a quoted sequence.
|
||||
*
|
||||
|
|
@ -298,13 +287,13 @@ if (DEBUGGER) {
|
|||
* Recall that substring() accepts starting (inclusive) and ending (exclusive)
|
||||
* indexes, whereas substr() accepts a starting index and a length. We need the former.
|
||||
*/
|
||||
a.push(str.trim(sCmd.substring(iPrev, i)));
|
||||
a.push(Str.trim(sCmd.substring(iPrev, i)));
|
||||
iPrev = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return a;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* evalExpression(aVals, aOps, cOps)
|
||||
|
|
@ -327,8 +316,7 @@ if (DEBUGGER) {
|
|||
* @param {number} [cOps] (default is all)
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
Debugger.prototype.evalExpression = function(aVals, aOps, cOps)
|
||||
{
|
||||
evalExpression(aVals, aOps, cOps) {
|
||||
cOps = cOps || -1;
|
||||
while (cOps-- && aOps.length) {
|
||||
var chOp = aOps.pop();
|
||||
|
|
@ -402,7 +390,7 @@ if (DEBUGGER) {
|
|||
aVals.push(valNew|0);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* parseExpression(sExp, fPrint)
|
||||
|
|
@ -440,8 +428,7 @@ if (DEBUGGER) {
|
|||
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
|
||||
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
|
||||
*/
|
||||
Debugger.prototype.parseExpression = function(sExp, fPrint)
|
||||
{
|
||||
parseExpression(sExp, fPrint) {
|
||||
var value;
|
||||
|
||||
if (sExp) {
|
||||
|
|
@ -468,7 +455,7 @@ if (DEBUGGER) {
|
|||
while (i < asValues.length) {
|
||||
var sValue = asValues[i++];
|
||||
var cchValue = sValue.length;
|
||||
var s = str.trim(sValue);
|
||||
var s = Str.trim(sValue);
|
||||
if (!s) {
|
||||
fError = true;
|
||||
break;
|
||||
|
|
@ -500,7 +487,7 @@ if (DEBUGGER) {
|
|||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* parseReference(s)
|
||||
|
|
@ -514,8 +501,7 @@ if (DEBUGGER) {
|
|||
* @param {string} s
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.parseReference = function(s)
|
||||
{
|
||||
parseReference(s) {
|
||||
var a;
|
||||
var chOpen = this.fParens? '(' : '{';
|
||||
var chClose = this.fParens? ')' : '}';
|
||||
|
|
@ -530,7 +516,7 @@ if (DEBUGGER) {
|
|||
s = this.parseAddrReference(s, a[1]);
|
||||
}
|
||||
return this.parseSysVars(s);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* parseSysVars(s)
|
||||
|
|
@ -543,8 +529,7 @@ if (DEBUGGER) {
|
|||
* @param {string} s
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.parseSysVars = function(s)
|
||||
{
|
||||
parseSysVars(s) {
|
||||
var a;
|
||||
while (a = s.match(/\$([a-z]+)/i)) {
|
||||
var v = null;
|
||||
|
|
@ -557,7 +542,7 @@ if (DEBUGGER) {
|
|||
s = s.replace(a[0], v.toString());
|
||||
}
|
||||
return s;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* parseValue(sValue, sName, fQuiet)
|
||||
|
|
@ -568,8 +553,7 @@ if (DEBUGGER) {
|
|||
* @param {boolean} [fQuiet]
|
||||
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
|
||||
*/
|
||||
Debugger.prototype.parseValue = function(sValue, sName, fQuiet)
|
||||
{
|
||||
parseValue(sValue, sName, fQuiet) {
|
||||
var value;
|
||||
if (sValue != null) {
|
||||
var iReg = this.getRegIndex(sValue);
|
||||
|
|
@ -578,7 +562,7 @@ if (DEBUGGER) {
|
|||
} else {
|
||||
value = this.getVariable(sValue);
|
||||
if (value == null) {
|
||||
value = str.parseInt(sValue, this.nBase);
|
||||
value = Str.parseInt(sValue, this.nBase);
|
||||
}
|
||||
}
|
||||
if (value == null && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
|
||||
|
|
@ -586,7 +570,7 @@ if (DEBUGGER) {
|
|||
if (!fQuiet) this.println("missing " + (sName || "value"));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* printValue(sVar, value)
|
||||
|
|
@ -596,13 +580,12 @@ if (DEBUGGER) {
|
|||
* @param {number|undefined} value
|
||||
* @return {boolean} true if value defined, false if not
|
||||
*/
|
||||
Debugger.prototype.printValue = function(sVar, value)
|
||||
{
|
||||
printValue(sVar, value) {
|
||||
var sValue;
|
||||
var fDefined = false;
|
||||
if (value !== undefined) {
|
||||
fDefined = true;
|
||||
sValue = str.toHex(value, 0, true) + " " + value + ". " + str.toOct(value, 0, true) + " " + str.toBinBytes(value, 4, true);
|
||||
sValue = Str.toHex(value, 0, true) + " " + value + ". " + Str.toOct(value, 0, true) + " " + Str.toBinBytes(value, 4, true);
|
||||
if (value >= 0x20 && value < 0x7F) {
|
||||
sValue += " '" + String.fromCharCode(value) + "'";
|
||||
}
|
||||
|
|
@ -610,7 +593,7 @@ if (DEBUGGER) {
|
|||
sVar = (sVar != null? (sVar + ": ") : "");
|
||||
this.println(sVar + sValue);
|
||||
return fDefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* printVariable(sVar)
|
||||
|
|
@ -619,8 +602,7 @@ if (DEBUGGER) {
|
|||
* @param {string} [sVar]
|
||||
* @return {boolean} true if all value(s) defined, false if not
|
||||
*/
|
||||
Debugger.prototype.printVariable = function(sVar)
|
||||
{
|
||||
printVariable(sVar) {
|
||||
if (sVar) {
|
||||
return this.printValue(sVar, this.aVariables[sVar]);
|
||||
}
|
||||
|
|
@ -630,7 +612,7 @@ if (DEBUGGER) {
|
|||
cVariables++;
|
||||
}
|
||||
return cVariables > 0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* delVariable(sVar)
|
||||
|
|
@ -638,10 +620,9 @@ if (DEBUGGER) {
|
|||
* @this {Debugger}
|
||||
* @param {string} sVar
|
||||
*/
|
||||
Debugger.prototype.delVariable = function(sVar)
|
||||
{
|
||||
delVariable(sVar) {
|
||||
delete this.aVariables[sVar];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* getVariable(sVar)
|
||||
|
|
@ -650,10 +631,9 @@ if (DEBUGGER) {
|
|||
* @param {string} sVar
|
||||
* @return {number|undefined}
|
||||
*/
|
||||
Debugger.prototype.getVariable = function(sVar)
|
||||
{
|
||||
getVariable(sVar) {
|
||||
return this.aVariables[sVar];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* setVariable(sVar, value)
|
||||
|
|
@ -662,15 +642,14 @@ if (DEBUGGER) {
|
|||
* @param {string} sVar
|
||||
* @param {number} value
|
||||
*/
|
||||
Debugger.prototype.setVariable = function(sVar, value)
|
||||
{
|
||||
setVariable(sVar, value) {
|
||||
this.aVariables[sVar] = value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* toStrBase(n, nBytes, fStripLeadingZeros)
|
||||
*
|
||||
* Use this instead of str.toHex() or str.toOct() to convert bytes/words to the Debugger's default base.
|
||||
* Use this instead of Str.toHex() or Str.toOct() to convert bytes/words to the Debugger's default base.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {number|null|undefined} n
|
||||
|
|
@ -678,22 +657,46 @@ if (DEBUGGER) {
|
|||
* @param {boolean} [fStripLeadingZeros]
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.toStrBase = function(n, nBytes, fStripLeadingZeros)
|
||||
{
|
||||
toStrBase(n, nBytes, fStripLeadingZeros) {
|
||||
var s;
|
||||
switch(this.nBase) {
|
||||
case 8:
|
||||
s = str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
|
||||
s = Str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
|
||||
break;
|
||||
case 10:
|
||||
s = n.toString();
|
||||
break;
|
||||
case 16:
|
||||
default:
|
||||
s = str.toHex(n, nBytes * 2);
|
||||
s = Str.toHex(n, nBytes * 2);
|
||||
break;
|
||||
}
|
||||
return (fStripLeadingZeros? str.stripLeadingZeros(s) : s);
|
||||
return (fStripLeadingZeros? Str.stripLeadingZeros(s) : s);
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUGGER) {
|
||||
|
||||
Debugger.aBinOpPrecedence = {
|
||||
'||': 0, // logical OR
|
||||
'&&': 1, // logical AND
|
||||
'|': 2, // bitwise OR
|
||||
'^': 3, // bitwise XOR
|
||||
'&': 4, // bitwise AND
|
||||
'!=': 5, // inequality
|
||||
'==': 5, // equality
|
||||
'>=': 6, // greater than or equal to
|
||||
'>': 6, // greater than
|
||||
'<=': 6, // less than or equal to
|
||||
'<': 6, // less than
|
||||
'>>>': 7, // unsigned bitwise right shift
|
||||
'>>': 7, // bitwise right shift
|
||||
'<<': 7, // bitwise left shift
|
||||
'-': 8, // subtraction
|
||||
'+': 8, // addition
|
||||
'%': 9, // remainder
|
||||
'/': 9, // division
|
||||
'*': 9 // multiplication
|
||||
};
|
||||
|
||||
} // endif DEBUGGER
|
||||
|
|
|
|||
|
|
@ -32,9 +32,13 @@
|
|||
* @define {string}
|
||||
*/
|
||||
var APPVERSION = "1.x.x"; // this @define is overridden by the Closure Compiler with the version in package.json
|
||||
|
||||
var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML
|
||||
|
||||
var COPYRIGHT = "Copyright © 2012-2017 Jeff Parsons <Jeff@pcjs.org>";
|
||||
|
||||
var LICENSE = "License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>";
|
||||
|
||||
var CSSCLASS = "pcjs";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -69,36 +69,33 @@ var DiskAPI = {
|
|||
};
|
||||
|
||||
/*
|
||||
* Common (supported) disk formats
|
||||
* Common (supported) diskette formats
|
||||
*
|
||||
* Each entry in DISK_FORMATS begins with an array of three "CHS" values:
|
||||
* For no particular reason that I can recall, each entry in DISK_FORMATS is an array of values in "CHS" order:
|
||||
*
|
||||
* [# cylinders, # heads, # sectors/track, # bytes/sector, media type]
|
||||
*
|
||||
* The 4th value is optional; if omitted, the sector size is assumed to be 512 bytes. The order of these geometric
|
||||
* values mirrors the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which
|
||||
* is an array of heads, each of which is an array of sector objects.
|
||||
*
|
||||
* The 5th value is also optional and is used only with PC-DOS diskettes, to help us verify that the logical format
|
||||
* matches the physical format; it should be one of the values in DiskAPI.FAT. TODO: Actually use the DiskAPI.FAT values.
|
||||
* If the 4th value is omitted, the sector size is assumed to be 512. The order of these "geometric" values mirrors
|
||||
* the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which is an array of
|
||||
* heads, each of which is an array of sector objects.
|
||||
*/
|
||||
DiskAPI.DISK_FORMATS = {
|
||||
163840: [ 40,1, 8,,0xFE], // media type 0xFE: 40 cylinders, 1 head (single-sided), 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)
|
||||
184320: [ 40,1, 9,,0xFC], // media type 0xFC: 40 cylinders, 1 head (single-sided), 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)
|
||||
327680: [ 40,2, 8,,0xFF], // media type 0xFF: 40 cylinders, 2 heads (double-sided), 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)
|
||||
368640: [ 40,2, 9,,0xFD], // media type 0xFD: 40 cylinders, 2 heads (double-sided), 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)
|
||||
737280: [ 80,2, 9,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)
|
||||
1228800: [ 80,2,15,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)
|
||||
1474560: [ 80,2,18,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)
|
||||
2949120: [ 80,2,36,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
|
||||
163840: [40,1,8,,0xFE], // media type 0xFE: 40 cylinders, 1 head (single-sided), 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)
|
||||
184320: [40,1,9,,0xFC], // media type 0xFC: 40 cylinders, 1 head (single-sided), 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)
|
||||
327680: [40,2,8,,0xFF], // media type 0xFF: 40 cylinders, 2 heads (double-sided), 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)
|
||||
368640: [40,2,9,,0xFD], // media type 0xFD: 40 cylinders, 2 heads (double-sided), 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)
|
||||
737280: [80,2,9,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)
|
||||
1228800: [80,2,15,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)
|
||||
1474560: [80,2,18,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)
|
||||
2949120: [80,2,36,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
|
||||
/*
|
||||
* The following are common early hard drive sizes, which we explicitly map to CHS values, since the BPB can mislead us when attempting to calculate total cylinders.
|
||||
* The following are common early hard drive sizes, which we explicitly map to CHS values, since the BPB can mislead us when attempting to calculate total cylinders
|
||||
*/
|
||||
21368320:[615,4,17], // PC AT 20Mb hard drive (type 2)
|
||||
/*
|
||||
* Assorted DEC disk pack formats.
|
||||
*/
|
||||
2494464: [203,2,12,512], // RK03 single-platter disk cartridge: 203 tracks, 2 heads, 12 sectors/track, 512 bytes/sector, for a total of 2494464 bytes
|
||||
2494464: [203,2,12,512], // RK03 single-platter disk cartridge: 203 tracks, 2 heads, 12 sectors/track, 512 bytes/sector, for a total of 2494464 bytes
|
||||
5242880: [256,2,40,256], // RL01K single-platter disk cartridge: 256 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 5242880 bytes
|
||||
10485760:[512,2,40,256] // RL02K single-platter disk cartridge: 512 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 10485760 bytes
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,12 +28,10 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* global document: true, window: true, XSLTProcessor: false, APPNAME: false, APPVERSION: false, XMLVERSION: true, DEBUG: true */
|
||||
|
||||
if (NODE) {
|
||||
var Component = require("./component");
|
||||
var str = require("./strlib");
|
||||
var web = require("./weblib");
|
||||
var Str = require("../../shared/lib/strlib");
|
||||
var Web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -45,8 +43,8 @@ if (NODE) {
|
|||
* machine component init() handlers.
|
||||
*
|
||||
* Also, to prevent those init() handlers from running prematurely, we must disable all page
|
||||
* notification events at the start of the embedding process (web.enablePageEvents(false)) and
|
||||
* re-enable them at the end (web.enablePageEvents(true)).
|
||||
* notification events at the start of the embedding process (Web.enablePageEvents(false)) and
|
||||
* re-enable them at the end (Web.enablePageEvents(true)).
|
||||
*/
|
||||
var fAsync = true;
|
||||
var cAsyncMachines = 0;
|
||||
|
|
@ -95,7 +93,7 @@ function loadXML(sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, dis
|
|||
parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done);
|
||||
};
|
||||
display("Loading " + sXMLFile + "...");
|
||||
web.getResource(sXMLFile, null, fAsync, doneLoadXML);
|
||||
Web.getResource(sXMLFile, null, fAsync, doneLoadXML);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -171,9 +169,11 @@ function parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResol
|
|||
/*
|
||||
* Non-COMPILED kludge to replace the version number template in the XSL file (which we assume we're reading,
|
||||
* since fResolve is false) with whatever XMLVERSION we extracted from the XML file (see corresponding kludge below).
|
||||
*
|
||||
* ES6 ALERT: Template strings.
|
||||
*/
|
||||
if (!COMPILED && XMLVERSION) {
|
||||
sXML = sXML.replace(/<xsl:variable name="APPVERSION">1.x.x<\/xsl:variable>/, '<xsl:variable name="APPVERSION">' + XMLVERSION + '</xsl:variable>');
|
||||
sXML = sXML.replace(/<xsl:variable name="APPVERSION">1.x.x<\/xsl:variable>/, `<xsl:variable name="APPVERSION">${XMLVERSION}</xsl:variable>`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ function parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResol
|
|||
* Supposedly, the IE XML DOM parser will throw an exception, but I haven't tested that, and unless all other
|
||||
* browsers do that, that's not helpful.
|
||||
*
|
||||
* The best I can do at this stage (assuming web.getResource() didn't drop any error information on the floor)
|
||||
* The best I can do at this stage (assuming Web.getResource() didn't drop any error information on the floor)
|
||||
* is verify that the requested resource "looks like" valid XML (in other words, it begins with a '<').
|
||||
*/
|
||||
var xmlDoc = null;
|
||||
|
|
@ -320,7 +320,7 @@ function resolveXML(sXML, display, done)
|
|||
};
|
||||
|
||||
display("Loading " + sRefFile + "...");
|
||||
web.getResource(sRefFile, null, fAsync, doneReadXML);
|
||||
Web.getResource(sRefFile, null, fAsync, doneReadXML);
|
||||
return;
|
||||
}
|
||||
done(sXML, null);
|
||||
|
|
@ -350,7 +350,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
|
|||
var doneMachine = function() {
|
||||
Component.assert(cAsyncMachines > 0);
|
||||
if (!--cAsyncMachines) {
|
||||
if (fAsync) web.enablePageEvents(true);
|
||||
if (fAsync) Web.enablePageEvents(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -378,7 +378,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
|
|||
var aeWarning = (eMachine && Component.getElementsByClass(eMachine, "machine-warning"));
|
||||
eWarning = (aeWarning && aeWarning[0]) || eMachine;
|
||||
}
|
||||
if (eWarning) eWarning.innerHTML = str.escapeHTML(sMessage);
|
||||
if (eWarning) eWarning.innerHTML = Str.escapeHTML(sMessage);
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
@ -545,7 +545,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
|
|||
*/
|
||||
function embedC1P(idMachine, sXMLFile, sXSLFile)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
if (fAsync) Web.enablePageEvents(false);
|
||||
return embedMachine("C1Pjs", "c1pjs", APPVERSION, idMachine, sXMLFile, sXSLFile);
|
||||
}
|
||||
|
||||
|
|
@ -560,7 +560,7 @@ function embedC1P(idMachine, sXMLFile, sXSLFile)
|
|||
*/
|
||||
function embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
if (fAsync) Web.enablePageEvents(false);
|
||||
return embedMachine("PCx86", "pcx86", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
}
|
||||
|
||||
|
|
@ -575,7 +575,7 @@ function embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
|
|||
*/
|
||||
function embedPC8080(idMachine, sXMLFile, sXSLFile, sParms)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
if (fAsync) Web.enablePageEvents(false);
|
||||
return embedMachine("PC8080", "pc8080", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
}
|
||||
|
||||
|
|
@ -590,10 +590,22 @@ function embedPC8080(idMachine, sXMLFile, sXSLFile, sParms)
|
|||
*/
|
||||
function embedPDP11(idMachine, sXMLFile, sXSLFile, sParms)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
if (fAsync) Web.enablePageEvents(false);
|
||||
return embedMachine("PDPjs", "pdp11", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
}
|
||||
|
||||
/**
|
||||
* findMachineComponent(idMachine, sType)
|
||||
*
|
||||
* @param {string} idMachine
|
||||
* @param {string} sType
|
||||
* @return {Component|null}
|
||||
*/
|
||||
function findMachineComponent(idMachine, sType)
|
||||
{
|
||||
return Component.getComponentByType(sType, idMachine + ".machine");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the Closure Compiler from renaming functions we want to export, by adding them as global properties.
|
||||
*/
|
||||
|
|
@ -611,5 +623,7 @@ if (APPNAME == "PDPjs") {
|
|||
window['embedPDP11'] = embedPDP11;
|
||||
}
|
||||
|
||||
window['enableEvents'] = web.enablePageEvents;
|
||||
window['sendEvent'] = web.sendPageEvent;
|
||||
window['findMachineComponent'] = findMachineComponent;
|
||||
|
||||
window['enableEvents'] = Web.enablePageEvents;
|
||||
window['sendEvent'] = Web.sendPageEvent;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ var Keys = {
|
|||
/*
|
||||
* Keys and/or key combinations that generate common ASCII codes.
|
||||
*
|
||||
* NOTE: If you're looking for a general-purpose ASCII code table, see str.ASCII in strlib.js;
|
||||
* NOTE: If you're looking for a general-purpose ASCII code table, see Str.ASCII in strlib.js;
|
||||
* if something's missing, that's probably the more appropriate table to add it to.
|
||||
*
|
||||
* TODO: The Closure Compiler doesn't inline all references to these values, at least those with
|
||||
|
|
@ -201,6 +201,7 @@ var Keys = {
|
|||
*/
|
||||
Keys.KEYCODE.NUM_CR = Keys.KEYCODE.CR + Keys.KEYCODE.ONRIGHT;
|
||||
|
||||
|
||||
/*
|
||||
* Maps Firefox keyCodes to their more common keyCode counterparts; a number of entries in this table
|
||||
* are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I
|
||||
|
|
|
|||
|
|
@ -28,18 +28,345 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* global Buffer: false */
|
||||
|
||||
var net = {};
|
||||
|
||||
if (NODE) {
|
||||
var sServerRoot;
|
||||
var fs = require("fs");
|
||||
var http = require("http");
|
||||
var path = require("path");
|
||||
var url = require("url");
|
||||
}
|
||||
|
||||
class Net {
|
||||
/**
|
||||
* hasParm(sParm, sValue, req)
|
||||
*
|
||||
* @param {string} sParm
|
||||
* @param {string|null} sValue (pass null to check for the presence of ANY sParm)
|
||||
* @param {Object} [req] is the web server's (ie, Express) request object, if any
|
||||
* @return {boolean} true if the request Object contains the specified parameter/value, false if not
|
||||
*
|
||||
* TODO: Consider whether sParm === null should check for the presence of ANY parameter in Net.asPropagate.
|
||||
*/
|
||||
static hasParm(sParm, sValue, req)
|
||||
{
|
||||
return (req && req.query && req.query[sParm] && (!sValue || req.query[sParm] == sValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* propagateParms(sURL, req)
|
||||
*
|
||||
* Propagates any "special" query parameters (as listed in Net.asPropagate) from the given
|
||||
* request object (req) to the given URL (sURL).
|
||||
*
|
||||
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
|
||||
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
|
||||
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
|
||||
* same reason, since any hash string must follow any query parameters, but stripping
|
||||
* and re-appending the hash string is pretty trivial, so we do handle that.
|
||||
*
|
||||
* TODO: Make propagateParms() more general-purpose (eg, capable of detecting any URL
|
||||
* to the same site, and capable of merging any of our "special" query parameters with any
|
||||
* existing query parameters.
|
||||
*
|
||||
* @param {string|null} sURL
|
||||
* @param {Object} [req] is the web server's (ie, Express) request object, if any
|
||||
* @return {string} massaged sURL
|
||||
*/
|
||||
static propagateParms(sURL, req)
|
||||
{
|
||||
if (sURL !== null && sURL.indexOf('?') < 0) {
|
||||
var i;
|
||||
var sHash = "";
|
||||
if ((i = sURL.indexOf('#')) >= 0) {
|
||||
sHash = sURL.substr(i);
|
||||
sURL = sURL.substr(0, i);
|
||||
}
|
||||
var match = sURL.match(/^([a-z])+:(.*)/);
|
||||
if (!match && req && req.query) {
|
||||
for (i = 0; i < Net.asPropagate.length; i++) {
|
||||
var sQuery = Net.asPropagate[i];
|
||||
var sValue;
|
||||
if ((sValue = req.query[sQuery])) {
|
||||
var sParm = (sURL.indexOf('?') < 0 ? '?' : '&');
|
||||
sParm += sQuery + '=';
|
||||
if (sURL.indexOf(sParm) < 0) sURL += sParm + encodeURIComponent(sValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
sURL += sHash;
|
||||
}
|
||||
return sURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* encodeURL(sURL, req, fDebug)
|
||||
*
|
||||
* Used to encodes any URLs presented on the current page, using this, um, simple 5-step process:
|
||||
*
|
||||
* 1) Replace any backslashes with slashes, in case the URL was derived from a file system path
|
||||
* 2) Remap links that begin with "archive/" to the corresponding URL at "http://archive.pcjs.org/"
|
||||
* 3) Use decodeURI() to eliminate escape sequences (like "%20") so that encodeURI() won't re-encode the "%"
|
||||
* 4) Use encodeURI() to transform all "htmlspecialchars" and reserved characters into the appropriate sequences
|
||||
* 5) Massage the result with Net.propagateParms(), so that any special parameters are passed along
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {Object} req is the web server's (ie, Express) request object, if any
|
||||
* @param {boolean} [fDebug]
|
||||
* @return {string} encoded URL
|
||||
*/
|
||||
static encodeURL(sURL, req, fDebug)
|
||||
{
|
||||
if (sURL) {
|
||||
sURL = sURL.replace(/\\/g, '/');
|
||||
if (!fDebug) {
|
||||
if (sURL.match(/^[^:?]*archive\//)) {
|
||||
if (sURL.charAt(0) != '/') sURL = path.join(req.path, sURL);
|
||||
sURL = "http://archive.pcjs.org" + sURL.replace("/archive/", "/");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If the incoming URL already contains URI-style escape sequences (eg, "%20" instead of spaces),
|
||||
* calling decodeURI() first will eliminate them, preventing encodeURI() from converting leading
|
||||
* "%" into "%25" and corrupting sequences like "%20" by turning them into "%2520".
|
||||
*/
|
||||
return Net.propagateParms(encodeURI(decodeURI(sURL)), req);
|
||||
}
|
||||
return sURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* isRemote(sPath)
|
||||
*
|
||||
* TODO: Add support for FTP? HTTPS? Anything else?
|
||||
*
|
||||
* @param {string} sPath
|
||||
* @return {boolean} true if sPath is a (supported) remote path, false if not
|
||||
*/
|
||||
static isRemote(sPath)
|
||||
{
|
||||
return (sPath.indexOf("http:") === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* getStat(sURL, done)
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {function(Error,Object)} done
|
||||
*/
|
||||
static getStat(sURL, done)
|
||||
{
|
||||
var options = url.parse(sURL);
|
||||
options.method = "HEAD";
|
||||
options.path = options.pathname; // TODO: Determine the necessity of aliasing this
|
||||
var req = http.request(options, function(res)
|
||||
{
|
||||
var err = null;
|
||||
var stat = null;
|
||||
// console.log(JSON.stringify(res.headers));
|
||||
if (res.statusCode == 200) {
|
||||
/*
|
||||
* Apparently Node lower-cases response headers (at least incoming headers, despite
|
||||
* lots of amusing whining by certain people in the Node community), which seems like
|
||||
* a good thing, because that means I can do two simple key look-ups.
|
||||
*/
|
||||
var sLength = res.headers['content-length'];
|
||||
var sModified = res.headers['last-modified'];
|
||||
stat = {
|
||||
size: sLength ? parseInt(sLength, 10) : -1,
|
||||
mtime: sModified ? new Date(sModified) : null,
|
||||
remote: true // an additional property we provide to indicate this is not your normal stats object
|
||||
};
|
||||
} else {
|
||||
err = new Error("unexpected response code: " + res.statusCode);
|
||||
}
|
||||
done(err, stat);
|
||||
});
|
||||
req.on('error', function(err)
|
||||
{
|
||||
done(err, null);
|
||||
});
|
||||
req.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* getFile(sURL, sEncoding, done)
|
||||
*
|
||||
* TODO: Add support for FTP? HTTPS? Anything else?
|
||||
*
|
||||
* @param {string} sURL is the source file
|
||||
* @param {string|null} sEncoding is the encoding to assume, if any
|
||||
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
|
||||
*/
|
||||
static getFile(sURL, sEncoding, done)
|
||||
{
|
||||
/*
|
||||
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
|
||||
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
|
||||
* chunk arrives. The latter seems best.
|
||||
*
|
||||
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
|
||||
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
|
||||
* performs an implied "toString()" on the Buffer.
|
||||
*
|
||||
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
|
||||
* data matches that encoding.
|
||||
*/
|
||||
var sFile = "";
|
||||
var bufFile = null;
|
||||
http.get(sURL, function(res)
|
||||
{
|
||||
res.on('data', function(data)
|
||||
{
|
||||
if (sEncoding) {
|
||||
sFile += data;
|
||||
return;
|
||||
}
|
||||
if (!bufFile) {
|
||||
bufFile = data;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* We need to grow bufFile. I used to do this myself, using the "copy" method:
|
||||
*
|
||||
* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
|
||||
*
|
||||
* which defaults to 0 for [targetStart] and [sourceStart], but the docs don't clearly
|
||||
* define the default value for [sourceEnd]. They say "buffer.length", but there is no
|
||||
* parameter here named "buffer". Let's hope that in the case of "bufFile.copy(buf)"
|
||||
* they meant "bufFile.length".
|
||||
*
|
||||
* However, it turns out this is moot, because there's a new kid in town: Buffer.concat().
|
||||
*
|
||||
* buf = new Buffer(bufFile.length + data.length);
|
||||
* bufFile.copy(buf);
|
||||
* data.copy(buf, bufFile.length);
|
||||
* bufFile = buf;
|
||||
*/
|
||||
bufFile = Buffer.concat([bufFile, data], bufFile.length + data.length);
|
||||
}).on('end', function()
|
||||
{
|
||||
/*
|
||||
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
|
||||
* in such cases, the file content will likely just be an HTML error page.
|
||||
*/
|
||||
if (res.statusCode < 400) {
|
||||
done(null, res.statusCode, sEncoding? sFile : bufFile);
|
||||
} else {
|
||||
done(new Error(sEncoding? sFile : bufFile), res.statusCode, null);
|
||||
}
|
||||
}).on('error', function(err)
|
||||
{
|
||||
done(err, res.statusCode, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* downloadFile(sURL, sFile, done)
|
||||
*
|
||||
* @param {string} sURL is the source file
|
||||
* @param {string} sFile is a fully-qualified target file
|
||||
* @param {function(Error,number)} done is a callback that receives an Error and a HTTP status code
|
||||
*/
|
||||
static downloadFile(sURL, sFile, done)
|
||||
{
|
||||
var file = fs.createWriteStream(sFile);
|
||||
|
||||
/*
|
||||
* http.get() accepts a "url" string in lieu of an "options" object; it automatically builds
|
||||
* the latter from the former using url.parse(). This is good, because it relieves me from
|
||||
* building my own "options" object, and also from wondering why http functions expect "options"
|
||||
* to contain a "path" property, whereas url.parse() returns a "pathname" property.
|
||||
*
|
||||
* Either the documentation isn't quite right for url.parse() or http.request() (the big brother
|
||||
* of http.get), or one of those "options" properties is aliased to the other, or...?
|
||||
*/
|
||||
http.get(sURL, function(res)
|
||||
{
|
||||
res.on('data', function(data)
|
||||
{
|
||||
file.write(data);
|
||||
}).on('end', function()
|
||||
{
|
||||
file.end();
|
||||
/*
|
||||
* TODO: We should try to update the file's modification time to match the 'last-modified'
|
||||
* response header value, if any.
|
||||
*
|
||||
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
|
||||
* in such cases, the file content will likely just be an HTML error page.
|
||||
*/
|
||||
done(null, res.statusCode);
|
||||
}).on('error', function(err)
|
||||
{
|
||||
done(err, res.statusCode);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* getResource(sURL, dataPost, fAsync, done)
|
||||
*
|
||||
* Request the specified resource (sURL), and once the request is complete, notify done().
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {Object|null} [dataPost] for a POST request (default is a GET request)
|
||||
* @param {boolean} [fAsync] is true for an asynchronous request
|
||||
* @param {function(string,string|null,number)} [done]
|
||||
* @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
|
||||
*/
|
||||
static getResource(sURL, dataPost, fAsync, done)
|
||||
{
|
||||
var nErrorCode = -1, sResource = null, response = null;
|
||||
|
||||
if (Net.isRemote(sURL)) {
|
||||
/*
|
||||
* TODO: This code is nothing more than a band-aid. It assumes the URL uses "http:"
|
||||
* (hence the call to getFile(), which only supports HTTP GET operations), it assumes
|
||||
* the requested data is UTF-8 string data (which is normally the case, because nearly
|
||||
* all our requests are for JSON files), it doesn't deal with dataPost, it assumes
|
||||
* that fAsync is true, and it performs very simplistic error code mapping.
|
||||
*
|
||||
* But, it gets the job done for what little we actually ask of it, when our machines
|
||||
* are running in the Node environment.
|
||||
*/
|
||||
Net.getFile(sURL, "utf8", function(err, status, data) {
|
||||
if (done) done(sURL, data, err? status : 0);
|
||||
});
|
||||
} else {
|
||||
if (!Net.sServerRoot) {
|
||||
Net.sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
|
||||
}
|
||||
var sFile = path.join(Net.sServerRoot, sURL);
|
||||
if (fAsync) {
|
||||
fs.readFile(sFile, {encoding: "utf8"}, function(err, s)
|
||||
{
|
||||
/*
|
||||
* TODO: If err is set, is there an error code we should return (instead of -1)?
|
||||
*/
|
||||
if (!err) {
|
||||
sResource = s;
|
||||
nErrorCode = 0;
|
||||
}
|
||||
if (done) done(sURL, sResource, nErrorCode);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
sResource = fs.readFileSync(sFile, {encoding: "utf8"});
|
||||
nErrorCode = 0;
|
||||
} catch (err) {
|
||||
/*
|
||||
* TODO: If err is set, is there an error code we should return (instead of -1)?
|
||||
*/
|
||||
console.log(err.message);
|
||||
}
|
||||
if (done) done(sURL, sResource, nErrorCode);
|
||||
response = [sResource, nErrorCode];
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The following are (super-secret) commands that can be added to the URL to enable special features.
|
||||
*
|
||||
|
|
@ -49,342 +376,24 @@ if (NODE) {
|
|||
* http://www.pcjs.org/?gort=debug
|
||||
*
|
||||
* hasParm() detects the presence of the specified command, and propagateParms() is a URL filter that ensures
|
||||
* any commands listed in asPropagate are passed through to all other URLs on the same page; using any of these
|
||||
* any commands listed in Net.asPropagate are passed through to all other URLs on the same page; using any of these
|
||||
* commands also forces the page to be rebuilt and not cached (since we would never want a cached "index.html" to
|
||||
* contain/expose any of these commands).
|
||||
*/
|
||||
net.GORT_COMMAND = "gort";
|
||||
net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
|
||||
net.GORT_NODEBUG = "nodebug"; // use this to force uncompiled JavaScript but with DEBUG code disabled
|
||||
net.GORT_RELEASE = "release"; // use this to force the use of compiled JavaScript even on a Debug server
|
||||
net.GORT_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
|
||||
Net.GORT_COMMAND = "gort";
|
||||
Net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
|
||||
Net.GORT_NODEBUG = "nodebug"; // use this to force uncompiled JavaScript but with DEBUG code disabled
|
||||
Net.GORT_RELEASE = "release"; // use this to force the use of compiled JavaScript even on a Debug server
|
||||
Net.GORT_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
|
||||
|
||||
net.REVEAL_COMMAND = "reveal";
|
||||
net.REVEAL_PDFS = "pdfs";
|
||||
Net.REVEAL_COMMAND = "reveal";
|
||||
Net.REVEAL_PDFS = "pdfs";
|
||||
|
||||
/*
|
||||
* This is a list of the URL parameters that propagateParms() will propagate from the requester's URL to
|
||||
* other URLs provided by the requester.
|
||||
*/
|
||||
var asPropagate = [net.GORT_COMMAND, "autostart"];
|
||||
Net.asPropagate = [Net.GORT_COMMAND, "autostart"];
|
||||
Net.sServerRoot = null;
|
||||
|
||||
/**
|
||||
* hasParm(sParm, sValue, req)
|
||||
*
|
||||
* @param {string} sParm
|
||||
* @param {string|null} sValue (pass null to check for the presence of ANY sParm)
|
||||
* @param {Object} [req] is the web server's (ie, Express) request object, if any
|
||||
* @return {boolean} true if the request Object contains the specified parameter/value, false if not
|
||||
*
|
||||
* TODO: Consider whether sParm === null should check for the presence of ANY parameter in asPropagate.
|
||||
*/
|
||||
net.hasParm = function(sParm, sValue, req)
|
||||
{
|
||||
return (req && req.query && req.query[sParm] && (!sValue || req.query[sParm] == sValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* propagateParms(sURL, req)
|
||||
*
|
||||
* Propagates any "special" query parameters (as listed in asPropagate) from the given
|
||||
* request object (req) to the given URL (sURL).
|
||||
*
|
||||
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
|
||||
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
|
||||
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
|
||||
* same reason, since any hash string must follow any query parameters, but stripping
|
||||
* and re-appending the hash string is pretty trivial, so we do handle that.
|
||||
*
|
||||
* TODO: Make propagateParms() more general-purpose (eg, capable of detecting any URL
|
||||
* to the same site, and capable of merging any of our "special" query parameters with any
|
||||
* existing query parameters.
|
||||
*
|
||||
* @param {string|null} sURL
|
||||
* @param {Object} [req] is the web server's (ie, Express) request object, if any
|
||||
* @return {string} massaged sURL
|
||||
*/
|
||||
net.propagateParms = function(sURL, req)
|
||||
{
|
||||
if (sURL !== null && sURL.indexOf('?') < 0) {
|
||||
var i;
|
||||
var sHash = "";
|
||||
if ((i = sURL.indexOf('#')) >= 0) {
|
||||
sHash = sURL.substr(i);
|
||||
sURL = sURL.substr(0, i);
|
||||
}
|
||||
var match = sURL.match(/^([a-z])+:(.*)/);
|
||||
if (!match && req && req.query) {
|
||||
for (i = 0; i < asPropagate.length; i++) {
|
||||
var sQuery = asPropagate[i];
|
||||
var sValue;
|
||||
if ((sValue = req.query[sQuery])) {
|
||||
var sParm = (sURL.indexOf('?') < 0? '?' : '&');
|
||||
sParm += sQuery + '=';
|
||||
if (sURL.indexOf(sParm) < 0) sURL += sParm + encodeURIComponent(sValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
sURL += sHash;
|
||||
}
|
||||
return sURL;
|
||||
};
|
||||
|
||||
/**
|
||||
* encodeURL(sURL, req, fDebug)
|
||||
*
|
||||
* Used to encodes any URLs presented on the current page, using this, um, simple 5-step process:
|
||||
*
|
||||
* 1) Replace any backslashes with slashes, in case the URL was derived from a file system path
|
||||
* 2) Remap links that begin with "archive/" to the corresponding URL at "http://archive.pcjs.org/"
|
||||
* 3) Use decodeURI() to eliminate escape sequences (like "%20") so that encodeURI() won't re-encode the "%"
|
||||
* 4) Use encodeURI() to transform all "htmlspecialchars" and reserved characters into the appropriate sequences
|
||||
* 5) Massage the result with net.propagateParms(), so that any special parameters are passed along
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {Object} req is the web server's (ie, Express) request object, if any
|
||||
* @param {boolean} [fDebug]
|
||||
* @return {string} encoded URL
|
||||
*/
|
||||
net.encodeURL = function(sURL, req, fDebug)
|
||||
{
|
||||
if (sURL) {
|
||||
sURL = sURL.replace(/\\/g, '/');
|
||||
if (!fDebug) {
|
||||
if (sURL.match(/^[^:?]*archive\//)) {
|
||||
if (sURL.charAt(0) != '/') sURL = path.join(req.path, sURL);
|
||||
sURL = "http://archive.pcjs.org" + sURL.replace("/archive/", "/");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If the incoming URL already contains URI-style escape sequences (eg, "%20" instead of spaces),
|
||||
* calling decodeURI() first will eliminate them, preventing encodeURI() from converting leading
|
||||
* "%" into "%25" and corrupting sequences like "%20" by turning them into "%2520".
|
||||
*/
|
||||
return net.propagateParms(encodeURI(decodeURI(sURL)), req);
|
||||
}
|
||||
return sURL;
|
||||
};
|
||||
|
||||
/**
|
||||
* isRemote(sPath)
|
||||
*
|
||||
* @param {string} sPath
|
||||
* @return {boolean} true if sPath is a (supported) remote path, false if not
|
||||
*
|
||||
* TODO: Add support for FTP? HTTPS? Anything else?
|
||||
*/
|
||||
net.isRemote = function(sPath)
|
||||
{
|
||||
return (sPath.indexOf("http:") === 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* getStat(sURL, done)
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {function(Error,Object)} done
|
||||
*/
|
||||
net.getStat = function(sURL, done)
|
||||
{
|
||||
var options = url.parse(sURL);
|
||||
options.method = "HEAD";
|
||||
options.path = options.pathname; // TODO: Determine the necessity of aliasing this
|
||||
var req = http.request(options, function(res) {
|
||||
var err = null;
|
||||
var stat = null;
|
||||
// console.log(JSON.stringify(res.headers));
|
||||
if (res.statusCode == 200) {
|
||||
/*
|
||||
* Apparently Node lower-cases response headers (at least incoming headers, despite
|
||||
* lots of amusing whining by certain people in the Node community), which seems like
|
||||
* a good thing, because that means I can do two simple key look-ups.
|
||||
*/
|
||||
var sLength = res.headers['content-length'];
|
||||
var sModified = res.headers['last-modified'];
|
||||
stat = {
|
||||
size: sLength? parseInt(sLength, 10) : -1,
|
||||
mtime: sModified? new Date(sModified) : null,
|
||||
remote: true // an additional property we provide to indicate this is not your normal stats object
|
||||
};
|
||||
} else {
|
||||
err = new Error("unexpected response code: " + res.statusCode);
|
||||
}
|
||||
done(err, stat);
|
||||
});
|
||||
req.on('error', function(err) {
|
||||
done(err, null);
|
||||
});
|
||||
req.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* getFile(sURL, sEncoding, done)
|
||||
*
|
||||
* @param {string} sURL is the source file
|
||||
* @param {string|null} sEncoding is the encoding to assume, if any
|
||||
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
|
||||
*
|
||||
* TODO: Add support for FTP? HTTPS? Anything else?
|
||||
*/
|
||||
net.getFile = function(sURL, sEncoding, done)
|
||||
{
|
||||
/*
|
||||
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
|
||||
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
|
||||
* chunk arrives. The latter seems best.
|
||||
*
|
||||
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
|
||||
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
|
||||
* performs an implied "toString()" on the Buffer.
|
||||
*
|
||||
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
|
||||
* data matches that encoding.
|
||||
*/
|
||||
var sFile = "";
|
||||
var bufFile = null;
|
||||
http.get(sURL, function(res) {
|
||||
res.on('data', function(data) {
|
||||
if (sEncoding) {
|
||||
sFile += data;
|
||||
return;
|
||||
}
|
||||
if (!bufFile) {
|
||||
bufFile = data;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* We need to grow bufFile. I used to do this myself, using the "copy" method:
|
||||
*
|
||||
* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
|
||||
*
|
||||
* which defaults to 0 for [targetStart] and [sourceStart], but the docs don't clearly
|
||||
* define the default value for [sourceEnd]. They say "buffer.length", but there is no
|
||||
* parameter here named "buffer". Let's hope that in the case of "bufFile.copy(buf)"
|
||||
* they meant "bufFile.length".
|
||||
*
|
||||
* However, it turns out this is moot, because there's a new kid in town: Buffer.concat().
|
||||
*
|
||||
* buf = new Buffer(bufFile.length + data.length);
|
||||
* bufFile.copy(buf);
|
||||
* data.copy(buf, bufFile.length);
|
||||
* bufFile = buf;
|
||||
*/
|
||||
bufFile = Buffer.concat([bufFile, data], bufFile.length + data.length);
|
||||
}).on('end', function() {
|
||||
/*
|
||||
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
|
||||
* in such cases, the file content will likely just be an HTML error page.
|
||||
*/
|
||||
if (res.statusCode < 400) {
|
||||
done(null, res.statusCode, sEncoding? sFile : bufFile);
|
||||
} else {
|
||||
done(new Error(sEncoding? sFile : bufFile), res.statusCode, null);
|
||||
}
|
||||
}).on('error', function(err) {
|
||||
done(err, res.statusCode, null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* downloadFile(sURL, sFile, done)
|
||||
*
|
||||
* @param {string} sURL is the source file
|
||||
* @param {string} sFile is a fully-qualified target file
|
||||
* @param {function(Error,number)} done is a callback that receives an Error and a HTTP status code
|
||||
*/
|
||||
net.downloadFile = function(sURL, sFile, done)
|
||||
{
|
||||
var file = fs.createWriteStream(sFile);
|
||||
|
||||
/*
|
||||
* http.get() accepts a "url" string in lieu of an "options" object; it automatically builds
|
||||
* the latter from the former using url.parse(). This is good, because it relieves me from
|
||||
* building my own "options" object, and also from wondering why http functions expect "options"
|
||||
* to contain a "path" property, whereas url.parse() returns a "pathname" property.
|
||||
*
|
||||
* Either the documentation isn't quite right for url.parse() or http.request() (the big brother
|
||||
* of http.get), or one of those "options" properties is aliased to the other, or...?
|
||||
*/
|
||||
http.get(sURL, function(res) {
|
||||
res.on('data', function(data) {
|
||||
file.write(data);
|
||||
}).on('end', function() {
|
||||
file.end();
|
||||
/*
|
||||
* TODO: We should try to update the file's modification time to match the 'last-modified'
|
||||
* response header value, if any.
|
||||
*
|
||||
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
|
||||
* in such cases, the file content will likely just be an HTML error page.
|
||||
*/
|
||||
done(null, res.statusCode);
|
||||
}).on('error', function(err) {
|
||||
done(err, res.statusCode);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* getResource(sURL, dataPost, fAsync, done)
|
||||
*
|
||||
* Request the specified resource (sURL), and once the request is complete, notify done().
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {Object|null} [dataPost] for a POST request (default is a GET request)
|
||||
* @param {boolean} [fAsync] is true for an asynchronous request
|
||||
* @param {function(string,string,number)} [done]
|
||||
* @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
|
||||
*/
|
||||
net.getResource = function(sURL, dataPost, fAsync, done)
|
||||
{
|
||||
var nErrorCode = -1, sResource = null, response = null;
|
||||
|
||||
if (net.isRemote(sURL)) {
|
||||
/*
|
||||
* TODO: This code is nothing more than a band-aid. It assumes the URL uses "http:"
|
||||
* (hence the call to getFile(), which only supports HTTP GET operations), it assumes
|
||||
* the requested data is UTF-8 string data (which is normally the case, because nearly
|
||||
* all our requests are for JSON files), it doesn't deal with dataPost, it assumes
|
||||
* that fAsync is true, and it performs very simplistic error code mapping.
|
||||
*
|
||||
* But, it gets the job done for what little we actually ask of it, when our machines
|
||||
* are running in the Node environment.
|
||||
*/
|
||||
Net.getFile(sURL, "utf8", function(err, status, data) {
|
||||
if (done) done(sURL, data, err? status : 0);
|
||||
});
|
||||
} else {
|
||||
if (!sServerRoot) {
|
||||
sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
|
||||
}
|
||||
var sFile = path.join(sServerRoot, sURL);
|
||||
if (fAsync) {
|
||||
fs.readFile(sFile, {encoding: "utf8"}, function(err, s) {
|
||||
/*
|
||||
* TODO: If err is set, is there an error code we should return (instead of -1)?
|
||||
*/
|
||||
if (!err) {
|
||||
sResource = s;
|
||||
nErrorCode = 0;
|
||||
}
|
||||
if (done) done(sURL, sResource, nErrorCode);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
sResource = fs.readFileSync(sFile, {encoding: "utf8"});
|
||||
nErrorCode = 0;
|
||||
} catch(err) {
|
||||
/*
|
||||
* TODO: If err is set, is there an error code we should return (instead of -1)?
|
||||
*/
|
||||
console.log(err.message);
|
||||
}
|
||||
if (done) done(sURL, sResource, nErrorCode);
|
||||
response = [sResource, nErrorCode];
|
||||
}
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
if (NODE) module.exports = net;
|
||||
if (NODE) module.exports = Net;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* global DEBUG: true */
|
||||
|
||||
/*
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUG, setting it to false,
|
||||
* so that all DEBUG-only code will be removed by the compiler.
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* global PRIVATE: true */
|
||||
|
||||
/*
|
||||
* PRIVATE enables certain private features that we don't want enabled on the public web server, so by default,
|
||||
* PRIVATE is false. For Jekyll configurations, if site.pcjs.private is set, this file gets included, overriding
|
||||
|
|
|
|||
|
|
@ -28,59 +28,60 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var proc = {};
|
||||
|
||||
/**
|
||||
* getArgs()
|
||||
*
|
||||
* Processes command-line arguments. Arguments may be introduced by either
|
||||
* a double-hyphen (--) or a long dash (—), and argument values, if any, must be
|
||||
* separated by an "=" without any intervening whitespace. Arguments without
|
||||
* an explicit value default to true, and any argument appearing more than once
|
||||
* is automatically converted to an Array.
|
||||
*
|
||||
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
|
||||
* of single-character arguments, each set to true, and any of these arguments
|
||||
* appearing more than once is discarded.
|
||||
*
|
||||
* @return {{argc:number, argv:{}}}
|
||||
*/
|
||||
proc.getArgs = function() {
|
||||
var argc = 0;
|
||||
var argv = {};
|
||||
for (var i = 2; i < process.argv.length; i++) {
|
||||
var j, sSep;
|
||||
var sArg = process.argv[i];
|
||||
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
|
||||
sArg = sArg.substr(sSep.length);
|
||||
var sValue = true;
|
||||
j = sArg.indexOf("=");
|
||||
if (j > 0) {
|
||||
sValue = sArg.substr(j+1);
|
||||
sArg = sArg.substr(0, j);
|
||||
sValue = (sValue == "true")? true : ((sValue == "false")? false : sValue);
|
||||
}
|
||||
if (argv[sArg] === undefined) {
|
||||
argc++;
|
||||
argv[sArg] = sValue;
|
||||
} else {
|
||||
// console.log("too many '" + sArg + "' arguments");
|
||||
if (typeof argv[sArg] == "string") {
|
||||
argv[sArg] = [argv[sArg]];
|
||||
class Proc {
|
||||
/**
|
||||
* getArgs()
|
||||
*
|
||||
* Processes command-line arguments. Arguments may be introduced by either
|
||||
* a double-hyphen (--) or a long dash (—), and argument values, if any, must be
|
||||
* separated by an "=" without any intervening whitespace. Arguments without
|
||||
* an explicit value default to true, and any argument appearing more than once
|
||||
* is automatically converted to an Array.
|
||||
*
|
||||
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
|
||||
* of single-character arguments, each set to true, and any of these arguments
|
||||
* appearing more than once is discarded.
|
||||
*
|
||||
* @return {{argc:number, argv:{}}}
|
||||
*/
|
||||
static getArgs()
|
||||
{
|
||||
var argc = 0;
|
||||
var argv = {};
|
||||
for (var i = 2; i < process.argv.length; i++) {
|
||||
var j, sSep;
|
||||
var sArg = process.argv[i];
|
||||
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
|
||||
sArg = sArg.substr(sSep.length);
|
||||
var sValue = true;
|
||||
j = sArg.indexOf("=");
|
||||
if (j > 0) {
|
||||
sValue = sArg.substr(j + 1);
|
||||
sArg = sArg.substr(0, j);
|
||||
sValue = (sValue == "true") ? true : ((sValue == "false") ? false : sValue);
|
||||
}
|
||||
argv[sArg].push(sValue);
|
||||
}
|
||||
} else if (!sArg.indexOf("-")) {
|
||||
for (j = 1; j < sArg.length; j++) {
|
||||
var ch = sArg.charAt(j);
|
||||
if (argv[ch] === undefined) {
|
||||
argv[ch] = true;
|
||||
if (argv[sArg] === undefined) {
|
||||
argc++;
|
||||
argv[sArg] = sValue;
|
||||
} else {
|
||||
// console.log("too many '" + sArg + "' arguments");
|
||||
if (typeof argv[sArg] == "string") {
|
||||
argv[sArg] = [argv[sArg]];
|
||||
}
|
||||
argv[sArg].push(sValue);
|
||||
}
|
||||
} else if (!sArg.indexOf("-")) {
|
||||
for (j = 1; j < sArg.length; j++) {
|
||||
var ch = sArg.charAt(j);
|
||||
if (argv[ch] === undefined) {
|
||||
argv[ch] = true;
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {argc: argc, argv: argv};
|
||||
}
|
||||
return {argc: argc, argv: argv};
|
||||
};
|
||||
}
|
||||
|
||||
if (NODE) module.exports = proc;
|
||||
if (NODE) module.exports = Proc;
|
||||
|
|
|
|||
|
|
@ -28,12 +28,10 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* global window: true, APPVERSION: false, XMLVERSION: true, DEBUG: true */
|
||||
|
||||
if (NODE) {
|
||||
var Component = require("./component");
|
||||
var str = require("./strlib");
|
||||
var web = require("./weblib");
|
||||
var Str = require("../../shared/lib/strlib");
|
||||
var Web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -59,12 +57,12 @@ function savePC(idMachine, sPCJSFile, callback)
|
|||
}
|
||||
}
|
||||
if (callback && callback({ state: sState, parms: sParms })) return true;
|
||||
web.getResource(sPCJSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
downloadCSS(sURL, sResponse, nErrorCode, [idMachine, str.getBaseName(sPCJSFile, true), sParms, sState]);
|
||||
Web.getResource(sPCJSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
downloadCSS(sURL, sResponse, nErrorCode, [idMachine, Str.getBaseName(sPCJSFile, true), sParms, sState]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
web.alertUser("Unable to identify machine '" + idMachine + "'");
|
||||
Component.alertUser("Unable to identify machine '" + idMachine + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +81,7 @@ function downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
|
|||
var res = Component.getMachineResources(aMachineInfo[0]);
|
||||
var sCSSFile = null;
|
||||
for (var sName in res) {
|
||||
if (str.endsWith(sName, "components.xsl")) {
|
||||
if (Str.endsWith(sName, "components.xsl")) {
|
||||
sCSSFile = sName.replace(".xsl", ".css");
|
||||
break;
|
||||
}
|
||||
|
|
@ -94,13 +92,13 @@ function downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
|
|||
*/
|
||||
downloadPC(sURL, null, 0, aMachineInfo);
|
||||
} else {
|
||||
web.getResource(sCSSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
Web.getResource(sCSSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
downloadPC(sURL, sResponse, nErrorCode, aMachineInfo);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
web.alertUser("Error (" + nErrorCode + ") requesting " + sURL);
|
||||
Component.alertUser("Error (" + nErrorCode + ") requesting " + sURL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -152,7 +150,7 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
|
|||
var resOld = Component.getMachineResources(idMachine), resNew = {};
|
||||
for (var sName in resOld) {
|
||||
var data = resOld[sName];
|
||||
var sExt = str.getExtension(sName);
|
||||
var sExt = Str.getExtension(sName);
|
||||
if (sExt == "xml") {
|
||||
/*
|
||||
* Look through this resource for <disk> entries whose paths do not appear as one of the
|
||||
|
|
@ -169,10 +167,10 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
|
|||
}
|
||||
}
|
||||
}
|
||||
sXMLFile = sName = str.getBaseName(sName);
|
||||
sXMLFile = sName = Str.getBaseName(sName);
|
||||
}
|
||||
else if (sExt == "xsl") {
|
||||
sXSLFile = sName = str.getBaseName(sName);
|
||||
sXSLFile = sName = Str.getBaseName(sName);
|
||||
}
|
||||
Component.log("saving resource: '" + sName + "' (" + data.length + " bytes)");
|
||||
resNew[sName] = data;
|
||||
|
|
@ -202,7 +200,7 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
|
|||
|
||||
sPCJS = sPCJS.replace(/\u00A9/g, "©");
|
||||
|
||||
var sAlert = web.downloadFile(sPCJS, "javascript", false, sScript);
|
||||
var sAlert = Web.downloadFile(sPCJS, "javascript", false, sScript);
|
||||
|
||||
sAlert += ', copy it to your web server as "' + sScript + '", and then add the following to your web page:\n\n';
|
||||
sAlert += '<div id="' + idMachine + '"></div>\n';
|
||||
|
|
@ -210,10 +208,10 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
|
|||
sAlert += '<script type="text/javascript" src="' + sScript + '"></script>\n';
|
||||
sAlert += '<script type="text/javascript">embedPC("' + idMachine + '","' + sXMLFile + '","' + sXSLFile + '");</script>\n\n';
|
||||
sAlert += 'The machine should appear where the <div> is located.';
|
||||
web.alertUser(sAlert);
|
||||
Component.alertUser(sAlert);
|
||||
return;
|
||||
}
|
||||
web.alertUser("Missing XML/XSL resources");
|
||||
Component.alertUser("Missing XML/XSL resources");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -29,183 +29,46 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var web = require("./../../shared/lib/weblib");
|
||||
var Component = require("./../../shared/lib/component");
|
||||
var Web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* State(component, sVersion, sSuffix)
|
||||
*
|
||||
* State objects are used by components to save/restore their state.
|
||||
*
|
||||
* During a save operation, components add data to a State object via set(), and then return
|
||||
* the resulting data using data().
|
||||
*
|
||||
* During a restore operation, the Computer component passes the results of each data() call
|
||||
* back to the originating component.
|
||||
*
|
||||
* WARNING: Since State objects are low-level objects that have no UI requirements, they do not
|
||||
* inherit from the Component class, so you should only use class methods of Component, such as
|
||||
* Component.assert() (or Debugger methods if the Debugger is available).
|
||||
*
|
||||
* NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
|
||||
* From that point on, care must be taken to insure that any new version that's incompatible with
|
||||
* previous localStorage data be released with a version number that is at least 1 greater,
|
||||
* since we're tagging the localStorage data with the integer portion of the version string.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Component} component
|
||||
* @param {string} [sVersion] is used to append a major version number to the key
|
||||
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
||||
*/
|
||||
function State(component, sVersion, sSuffix) {
|
||||
this.id = component.id;
|
||||
this.key = State.key(component, sVersion, sSuffix);
|
||||
this.dbg = component.dbg;
|
||||
this.unload(component.parms);
|
||||
}
|
||||
|
||||
/**
|
||||
* State.key(component, sVersion, sSuffix)
|
||||
*
|
||||
* This encapsulates the key generation code.
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {string} [sVersion] is used to append a major version number to the key
|
||||
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
||||
* @return {string} key
|
||||
*/
|
||||
State.key = function(component, sVersion, sSuffix) {
|
||||
var key = component.id;
|
||||
if (sVersion) {
|
||||
var i = sVersion.indexOf('.');
|
||||
if (i > 0) key += ".v" + sVersion.substr(0, i);
|
||||
class State {
|
||||
/**
|
||||
* State(component, sVersion, sSuffix)
|
||||
*
|
||||
* State objects are used by components to save/restore their state.
|
||||
*
|
||||
* During a save operation, components add data to a State object via set(), and then return
|
||||
* the resulting data using data().
|
||||
*
|
||||
* During a restore operation, the Computer component passes the results of each data() call
|
||||
* back to the originating component.
|
||||
*
|
||||
* WARNING: Since State objects are low-level objects that have no UI requirements, they do not
|
||||
* inherit from the Component class, so you should only use class methods of Component, such as
|
||||
* Component.assert() (or Debugger methods if the Debugger is available).
|
||||
*
|
||||
* NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
|
||||
* From that point on, care must be taken to insure that any new version that's incompatible with
|
||||
* previous localStorage data be released with a version number that is at least 1 greater,
|
||||
* since we're tagging the localStorage data with the integer portion of the version string.
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {string} [sVersion] is used to append a major version number to the key
|
||||
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
||||
*/
|
||||
constructor(component, sVersion, sSuffix)
|
||||
{
|
||||
this.id = component.id;
|
||||
this.dbg = component.dbg;
|
||||
this.json = "";
|
||||
this.state = {};
|
||||
this.fLoaded = this.fParsed = false;
|
||||
this.key = State.key(component, sVersion, sSuffix);
|
||||
this.unload(component.parms);
|
||||
}
|
||||
if (sSuffix) {
|
||||
key += "." + sSuffix;
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.compress(aSrc)
|
||||
*
|
||||
* @param {Array.<number>|null} aSrc
|
||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
||||
*/
|
||||
State.compress = function(aSrc) {
|
||||
if (aSrc) {
|
||||
var iSrc = 0;
|
||||
var iComp = 0;
|
||||
var aComp = [];
|
||||
while (iSrc < aSrc.length) {
|
||||
var n = aSrc[iSrc];
|
||||
Component.assert(n !== undefined);
|
||||
var iCompare = iSrc + 1;
|
||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
|
||||
aComp[iComp++] = iCompare - iSrc;
|
||||
aComp[iComp++] = n;
|
||||
iSrc = iCompare;
|
||||
}
|
||||
if (aComp.length < aSrc.length) return aComp;
|
||||
}
|
||||
return aSrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.decompress(aComp)
|
||||
*
|
||||
* @param {Array.<number>} aComp
|
||||
* @param {number} nLength is expected length of decompressed data
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
State.decompress = function(aComp, nLength) {
|
||||
var iDst = 0;
|
||||
var aDst = new Array(nLength);
|
||||
var iComp = 0;
|
||||
while (iComp < aComp.length - 1) {
|
||||
var c = aComp[iComp++];
|
||||
var n = aComp[iComp++];
|
||||
while (c--) {
|
||||
aDst[iDst++] = n;
|
||||
}
|
||||
}
|
||||
Component.assert(aDst.length == nLength);
|
||||
return aDst;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.compressEvenOdd(aSrc)
|
||||
*
|
||||
* This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
|
||||
* followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
|
||||
* addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
|
||||
* for compress(), but the best case for compressEvenOdd().
|
||||
*
|
||||
* One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
|
||||
* and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
|
||||
* and return an uninitialized array.
|
||||
*
|
||||
* @param {Array.<number>|null} aSrc
|
||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
||||
*/
|
||||
State.compressEvenOdd = function(aSrc) {
|
||||
if (aSrc) {
|
||||
var iComp = 0, aComp = [];
|
||||
if (aSrc[0] !== undefined) {
|
||||
for (var off = 0; off < 2; off++) {
|
||||
var iSrc = off;
|
||||
while (iSrc < aSrc.length) {
|
||||
var n = aSrc[iSrc];
|
||||
var iCompare = iSrc + 2;
|
||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
|
||||
aComp[iComp++] = (iCompare - iSrc) >> 1;
|
||||
aComp[iComp++] = n;
|
||||
iSrc = iCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aComp.length < aSrc.length) return aComp;
|
||||
}
|
||||
return aSrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.decompressEvenOdd(aComp, nLength)
|
||||
*
|
||||
* This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
|
||||
* that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
|
||||
* if you used even/odd compression, then you must use even/odd decompression.
|
||||
*
|
||||
* @param {Array.<number>} aComp
|
||||
* @param {number} nLength is expected length of decompressed data
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
State.decompressEvenOdd = function(aComp, nLength) {
|
||||
var iDst = 0;
|
||||
var aDst = new Array(nLength);
|
||||
var iComp = 0;
|
||||
while (iComp < aComp.length - 1) {
|
||||
var c = aComp[iComp++];
|
||||
var n = aComp[iComp++];
|
||||
while (c--) {
|
||||
aDst[iDst] = n;
|
||||
iDst += 2;
|
||||
}
|
||||
/*
|
||||
* The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
|
||||
* the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
|
||||
* done.
|
||||
*/
|
||||
Component.assert(iDst <= nLength || iComp == aComp.length);
|
||||
if (iDst == nLength) iDst = 1;
|
||||
}
|
||||
Component.assert(aDst.length == nLength);
|
||||
return aDst;
|
||||
};
|
||||
|
||||
State.prototype = {
|
||||
constructor: State,
|
||||
/**
|
||||
* set(id, data)
|
||||
*
|
||||
|
|
@ -213,13 +76,15 @@ State.prototype = {
|
|||
* @param {number|string} id
|
||||
* @param {Object|string} data
|
||||
*/
|
||||
set: function(id, data) {
|
||||
set(id, data)
|
||||
{
|
||||
try {
|
||||
this[this.id][id] = data;
|
||||
this.state[id] = data;
|
||||
} catch(e) {
|
||||
Component.log(e.message);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* get(id)
|
||||
*
|
||||
|
|
@ -227,43 +92,38 @@ State.prototype = {
|
|||
* @param {number|string} id
|
||||
* @return {Object|string|null}
|
||||
*/
|
||||
get: function(id) {
|
||||
return this[this.id][id] || null;
|
||||
},
|
||||
/**
|
||||
* value()
|
||||
*
|
||||
* Use this instead of data() if you haven't called parse() yet.
|
||||
*
|
||||
* @this {State}
|
||||
* @return {string}
|
||||
*/
|
||||
value: function() {
|
||||
return this[this.id];
|
||||
},
|
||||
get(id)
|
||||
{
|
||||
return this.state[id] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* data()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {Object}
|
||||
*/
|
||||
data: function() {
|
||||
return this[this.id];
|
||||
},
|
||||
data()
|
||||
{
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* load(s)
|
||||
* load(json)
|
||||
*
|
||||
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
|
||||
* because any stringified data that we've loaded isn't usable until it's been parsed.
|
||||
*
|
||||
* @this {State}
|
||||
* @param {Object|string|null} [s]
|
||||
* @param {string|null} [json]
|
||||
* @return {boolean} true if state exists in localStorage, false if not
|
||||
*/
|
||||
load: function(s) {
|
||||
if (s) {
|
||||
this[this.id] = s;
|
||||
load(json)
|
||||
{
|
||||
if (json) {
|
||||
this.json = json;
|
||||
this.fLoaded = true;
|
||||
this.fParsed = false;
|
||||
return true;
|
||||
}
|
||||
if (this.fLoaded) {
|
||||
|
|
@ -272,17 +132,18 @@ State.prototype = {
|
|||
*/
|
||||
return true;
|
||||
}
|
||||
if (web.hasLocalStorage()) {
|
||||
s = web.getLocalStorageItem(this.key);
|
||||
if (Web.hasLocalStorage()) {
|
||||
var s = Web.getLocalStorageItem(this.key);
|
||||
if (s) {
|
||||
this[this.id] = s;
|
||||
this.json = s;
|
||||
this.fLoaded = true;
|
||||
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes loaded");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* parse()
|
||||
*
|
||||
|
|
@ -293,27 +154,33 @@ State.prototype = {
|
|||
* @this {State}
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
parse: function() {
|
||||
parse()
|
||||
{
|
||||
var fSuccess = true;
|
||||
try {
|
||||
this[this.id] = JSON.parse(this[this.id]);
|
||||
} catch (e) {
|
||||
Component.error(e.message || e);
|
||||
fSuccess = false;
|
||||
if (!this.fParsed) {
|
||||
try {
|
||||
this.state = JSON.parse(this.json);
|
||||
this.fParsed = true;
|
||||
} catch (e) {
|
||||
Component.error(e.message || e);
|
||||
fSuccess = false;
|
||||
}
|
||||
}
|
||||
return fSuccess;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* store()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
store: function() {
|
||||
store()
|
||||
{
|
||||
var fSuccess = true;
|
||||
if (web.hasLocalStorage()) {
|
||||
var s = JSON.stringify(this[this.id]);
|
||||
if (web.setLocalStorageItem(this.key, s)) {
|
||||
if (Web.hasLocalStorage()) {
|
||||
var s = JSON.stringify(this.state);
|
||||
if (Web.setLocalStorageItem(this.key, s)) {
|
||||
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes stored");
|
||||
} else {
|
||||
/*
|
||||
|
|
@ -327,20 +194,19 @@ State.prototype = {
|
|||
}
|
||||
}
|
||||
return fSuccess;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* toString()
|
||||
*
|
||||
* We can't know whether this might be called before parse() or after parse(), so we check.
|
||||
* If before, then this[this.id] will still be in string form; if after, it will be an Object.
|
||||
*
|
||||
* @this {State}
|
||||
* @return {string} JSON-encoded state
|
||||
*/
|
||||
toString: function() {
|
||||
var value = this[this.id];
|
||||
return (typeof value == "string"? value : JSON.stringify(value));
|
||||
},
|
||||
toString()
|
||||
{
|
||||
return this.state? JSON.stringify(this.state) : this.json;
|
||||
}
|
||||
|
||||
/**
|
||||
* unload(parms)
|
||||
*
|
||||
|
|
@ -351,11 +217,14 @@ State.prototype = {
|
|||
* @this {State}
|
||||
* @param {Object} [parms]
|
||||
*/
|
||||
unload: function(parms) {
|
||||
this[this.id] = {};
|
||||
unload(parms)
|
||||
{
|
||||
this.json = "";
|
||||
this.state = {};
|
||||
this.fLoaded = this.fParsed = false;
|
||||
if (parms) this.set("parms", parms);
|
||||
this.fLoaded = false;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* clear(fAll)
|
||||
*
|
||||
|
|
@ -365,19 +234,164 @@ State.prototype = {
|
|||
* @this {State}
|
||||
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
|
||||
*/
|
||||
clear: function(fAll) {
|
||||
clear(fAll)
|
||||
{
|
||||
this.unload();
|
||||
var aKeys = web.getLocalStorageKeys();
|
||||
var aKeys = Web.getLocalStorageKeys();
|
||||
for (var i = 0; i < aKeys.length; i++) {
|
||||
var sKey = aKeys[i];
|
||||
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
|
||||
web.removeLocalStorageItem(sKey);
|
||||
Web.removeLocalStorageItem(sKey);
|
||||
if (DEBUG) Component.log("localStorage(" + sKey + ") removed");
|
||||
aKeys.splice(i, 1);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* State.key(component, sVersion, sSuffix)
|
||||
*
|
||||
* This encapsulates the key generation code.
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {string} [sVersion] is used to append a major version number to the key
|
||||
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
||||
* @return {string} key
|
||||
*/
|
||||
static key(component, sVersion, sSuffix)
|
||||
{
|
||||
var key = component.id;
|
||||
if (sVersion) {
|
||||
var i = sVersion.indexOf('.');
|
||||
if (i > 0) key += ".v" + sVersion.substr(0, i);
|
||||
}
|
||||
if (sSuffix) {
|
||||
key += "." + sSuffix;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* State.compress(aSrc)
|
||||
*
|
||||
* @param {Array.<number>|null} aSrc
|
||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
||||
*/
|
||||
static compress(aSrc)
|
||||
{
|
||||
if (aSrc) {
|
||||
var iSrc = 0;
|
||||
var iComp = 0;
|
||||
var aComp = [];
|
||||
while (iSrc < aSrc.length) {
|
||||
var n = aSrc[iSrc];
|
||||
Component.assert(n !== undefined);
|
||||
var iCompare = iSrc + 1;
|
||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
|
||||
aComp[iComp++] = iCompare - iSrc;
|
||||
aComp[iComp++] = n;
|
||||
iSrc = iCompare;
|
||||
}
|
||||
if (aComp.length < aSrc.length) return aComp;
|
||||
}
|
||||
return aSrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* State.decompress(aComp)
|
||||
*
|
||||
* @param {Array.<number>} aComp
|
||||
* @param {number} nLength is expected length of decompressed data
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
static decompress(aComp, nLength)
|
||||
{
|
||||
var iDst = 0;
|
||||
var aDst = new Array(nLength);
|
||||
var iComp = 0;
|
||||
while (iComp < aComp.length - 1) {
|
||||
var c = aComp[iComp++];
|
||||
var n = aComp[iComp++];
|
||||
while (c--) {
|
||||
aDst[iDst++] = n;
|
||||
}
|
||||
}
|
||||
Component.assert(aDst.length == nLength);
|
||||
return aDst;
|
||||
}
|
||||
|
||||
/**
|
||||
* State.compressEvenOdd(aSrc)
|
||||
*
|
||||
* This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
|
||||
* followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
|
||||
* addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
|
||||
* for compress(), but the best case for compressEvenOdd().
|
||||
*
|
||||
* One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
|
||||
* and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
|
||||
* and return an uninitialized array.
|
||||
*
|
||||
* @param {Array.<number>|null} aSrc
|
||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
||||
*/
|
||||
static compressEvenOdd(aSrc)
|
||||
{
|
||||
if (aSrc) {
|
||||
var iComp = 0, aComp = [];
|
||||
if (aSrc[0] !== undefined) {
|
||||
for (var off = 0; off < 2; off++) {
|
||||
var iSrc = off;
|
||||
while (iSrc < aSrc.length) {
|
||||
var n = aSrc[iSrc];
|
||||
var iCompare = iSrc + 2;
|
||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
|
||||
aComp[iComp++] = (iCompare - iSrc) >> 1;
|
||||
aComp[iComp++] = n;
|
||||
iSrc = iCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aComp.length < aSrc.length) return aComp;
|
||||
}
|
||||
return aSrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* State.decompressEvenOdd(aComp, nLength)
|
||||
*
|
||||
* This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
|
||||
* that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
|
||||
* if you used even/odd compression, then you must use even/odd decompression.
|
||||
*
|
||||
* @param {Array.<number>} aComp
|
||||
* @param {number} nLength is expected length of decompressed data
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
static decompressEvenOdd(aComp, nLength)
|
||||
{
|
||||
var iDst = 0;
|
||||
var aDst = new Array(nLength);
|
||||
var iComp = 0;
|
||||
while (iComp < aComp.length - 1) {
|
||||
var c = aComp[iComp++];
|
||||
var n = aComp[iComp++];
|
||||
while (c--) {
|
||||
aDst[iDst] = n;
|
||||
iDst += 2;
|
||||
}
|
||||
/*
|
||||
* The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
|
||||
* the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
|
||||
* done.
|
||||
*/
|
||||
Component.assert(iDst <= nLength || iComp == aComp.length);
|
||||
if (iDst == nLength) iDst = 1;
|
||||
}
|
||||
Component.assert(aDst.length == nLength);
|
||||
return aDst;
|
||||
}
|
||||
}
|
||||
|
||||
if (NODE) module.exports = State;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -33,7 +33,6 @@
|
|||
*
|
||||
* web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser;
|
||||
*/
|
||||
|
||||
var UserAPI = {
|
||||
ENDPOINT: "/api/v1/user",
|
||||
QUERY: {
|
||||
|
|
|
|||
|
|
@ -28,181 +28,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var usr = {};
|
||||
|
||||
/**
|
||||
* binarySearch(a, v, fnCompare)
|
||||
*
|
||||
* @param {Array} a is an array
|
||||
* @param {number|string|Array|Object} v
|
||||
* @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]
|
||||
* @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point
|
||||
*/
|
||||
usr.binarySearch = function(a, v, fnCompare) {
|
||||
var left = 0;
|
||||
var right = a.length;
|
||||
var found = 0;
|
||||
if (fnCompare === undefined) {
|
||||
fnCompare = function(a, b) {
|
||||
return a > b? 1 : a < b? -1 : 0;
|
||||
};
|
||||
}
|
||||
while (left < right) {
|
||||
var middle = (left + right) >> 1;
|
||||
var compareResult;
|
||||
compareResult = fnCompare(v, a[middle]);
|
||||
if (compareResult > 0) {
|
||||
left = middle + 1;
|
||||
} else {
|
||||
right = middle;
|
||||
found = !compareResult;
|
||||
}
|
||||
}
|
||||
return found? left : ~left;
|
||||
};
|
||||
|
||||
/**
|
||||
* binaryInsert(a, v, fnCompare)
|
||||
*
|
||||
* If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the
|
||||
* element is inserted into the array at the appropriate index.
|
||||
*
|
||||
* @param {Array} a is an array
|
||||
* @param {number|string|Array|Object} v is the value to insert
|
||||
* @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]
|
||||
*/
|
||||
usr.binaryInsert = function(a, v, fnCompare) {
|
||||
var index = usr.binarySearch(a, v, fnCompare);
|
||||
if (index < 0) {
|
||||
a.splice(-(index + 1), 0, v);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getTime()
|
||||
*
|
||||
* @return {number} the current time, in milliseconds
|
||||
*/
|
||||
usr.getTime = Date.now || function() { return +new Date(); };
|
||||
|
||||
/**
|
||||
* getTimestamp()
|
||||
*
|
||||
* @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss")
|
||||
*/
|
||||
usr.getTimestamp = function() {
|
||||
var date = new Date();
|
||||
var padNum = function(n) {
|
||||
return (n < 10? "0" : "") + n;
|
||||
};
|
||||
return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate()) + " " + padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds());
|
||||
};
|
||||
|
||||
/**
|
||||
* getMonthDays(nMonth, nYear)
|
||||
*
|
||||
* Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100),
|
||||
* so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails
|
||||
* the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap
|
||||
* year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year.
|
||||
*
|
||||
* TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD,
|
||||
* so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and
|
||||
* we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what
|
||||
* a real RTC actually does.
|
||||
*
|
||||
* @param {number} nMonth (1-12)
|
||||
* @param {number} nYear (normally a 4-digit year, but it may also be mod 100)
|
||||
* @return {number} the maximum (1-based) day allowed for the specified month and year
|
||||
*/
|
||||
usr.getMonthDays = function(nMonth, nYear)
|
||||
{
|
||||
var nDays = usr.aMonthDays[nMonth - 1];
|
||||
if (nDays == 28) {
|
||||
if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) {
|
||||
nDays++;
|
||||
}
|
||||
}
|
||||
return nDays;
|
||||
};
|
||||
|
||||
usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
/**
|
||||
* formatDate(sFormat, date)
|
||||
*
|
||||
* @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s")
|
||||
* @param {Date} [date] (default is the current time)
|
||||
* @return {string}
|
||||
*
|
||||
* Supported identifiers in sFormat include:
|
||||
*
|
||||
* a: lowercase ante meridiem and post meridiem (am or pm)
|
||||
* d: day of the month, 2 digits with leading zeros (01,...,31)
|
||||
* g: hour in 12-hour format, without leading zeros (1,...,12)
|
||||
* i: minutes, with leading zeros (00,...,59)
|
||||
* j: day of the month, without leading zeros (1,...,31)
|
||||
* l: day of the week ("Sunday",...,"Saturday")
|
||||
* m: month, with leading zeros (01,...,12)
|
||||
* s: seconds, with leading zeros (00,...,59)
|
||||
* F: month ("January",...,"December")
|
||||
* H: hour in 24-hour format, with leading zeros (00,...,23)
|
||||
* Y: year (eg, 2014)
|
||||
*
|
||||
* For more inspiration, see: http://php.net/manual/en/function.date.php
|
||||
*/
|
||||
usr.formatDate = function(sFormat, date) {
|
||||
var sDate = "";
|
||||
if (!date) date = new Date();
|
||||
var iHour = date.getHours();
|
||||
var iDay = date.getDate();
|
||||
var iMonth = date.getMonth() + 1;
|
||||
for (var i = 0; i < sFormat.length; i++) {
|
||||
var ch;
|
||||
switch((ch = sFormat.charAt(i))) {
|
||||
case 'a':
|
||||
sDate += (iHour < 12? "am" : "pm");
|
||||
break;
|
||||
case 'd':
|
||||
sDate += ('0' + iDay).slice(-2);
|
||||
break;
|
||||
case 'g':
|
||||
sDate += (!iHour? 12 : (iHour > 12? iHour - 12 : iHour));
|
||||
break;
|
||||
case 'i':
|
||||
sDate += ('0' + date.getMinutes()).slice(-2);
|
||||
break;
|
||||
case 'j':
|
||||
sDate += iDay;
|
||||
break;
|
||||
case 'l':
|
||||
sDate += usr.asDays[date.getDay()];
|
||||
break;
|
||||
case 'm':
|
||||
sDate += ('0' + iMonth).slice(-2);
|
||||
break;
|
||||
case 's':
|
||||
sDate += ('0' + date.getSeconds()).slice(-2);
|
||||
break;
|
||||
case 'F':
|
||||
sDate += usr.asMonths[iMonth - 1];
|
||||
break;
|
||||
case 'H':
|
||||
sDate += ('0' + iHour).slice(-2);
|
||||
break;
|
||||
case 'Y':
|
||||
sDate += date.getFullYear();
|
||||
break;
|
||||
default:
|
||||
sDate += ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sDate;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* mask: number,
|
||||
|
|
@ -216,100 +41,279 @@ var BitField;
|
|||
*/
|
||||
var BitFields;
|
||||
|
||||
/**
|
||||
* defineBitFields(bfs)
|
||||
*
|
||||
* Prepares a bit field definition for use with getBitField() and setBitField(); eg:
|
||||
*
|
||||
* var bfs = usr.defineBitFields({num:20, count:8, btmod:1, type:3});
|
||||
*
|
||||
* The above defines a set of bit fields containg four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31).
|
||||
*
|
||||
* usr.setBitField(bfs.num, n, 1);
|
||||
*
|
||||
* The above set bit field "bfs.num" in numeric variable "n" to the value 1.
|
||||
*
|
||||
* @param {Object} bfs
|
||||
* @return {BitFields}
|
||||
*/
|
||||
usr.defineBitFields = function(bfs)
|
||||
{
|
||||
var bit = 0;
|
||||
for (var f in bfs) {
|
||||
var width = bfs[f];
|
||||
var mask = ((1 << width) - 1) << bit;
|
||||
bfs[f] = {mask: mask, shift: bit};
|
||||
bit += width;
|
||||
class Usr {
|
||||
/**
|
||||
* binarySearch(a, v, fnCompare)
|
||||
*
|
||||
* @param {Array} a is an array
|
||||
* @param {number|string|Array|Object} v
|
||||
* @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]
|
||||
* @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point
|
||||
*/
|
||||
static binarySearch(a, v, fnCompare)
|
||||
{
|
||||
var left = 0;
|
||||
var right = a.length;
|
||||
var found = 0;
|
||||
if (fnCompare === undefined) {
|
||||
fnCompare = function(a, b)
|
||||
{
|
||||
return a > b ? 1 : a < b ? -1 : 0;
|
||||
};
|
||||
}
|
||||
while (left < right) {
|
||||
var middle = (left + right) >> 1;
|
||||
var compareResult;
|
||||
compareResult = fnCompare(v, a[middle]);
|
||||
if (compareResult > 0) {
|
||||
left = middle + 1;
|
||||
} else {
|
||||
right = middle;
|
||||
found = !compareResult;
|
||||
}
|
||||
}
|
||||
return found ? left : ~left;
|
||||
}
|
||||
// Component.assert(bit <= 32);
|
||||
return bfs;
|
||||
};
|
||||
|
||||
/**
|
||||
* initBitFields(bfs, ...)
|
||||
*
|
||||
* @param {BitFields} bfs
|
||||
* @param {...number} var_args
|
||||
* @return {number} a value containing all supplied bit fields
|
||||
*/
|
||||
usr.initBitFields = function(bfs, var_args)
|
||||
{
|
||||
var v = 0, i = 1;
|
||||
for (var f in bfs) {
|
||||
if (i >= arguments.length) break;
|
||||
v = usr.setBitField(bfs[f], v, arguments[i++]);
|
||||
/**
|
||||
* binaryInsert(a, v, fnCompare)
|
||||
*
|
||||
* If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the
|
||||
* element is inserted into the array at the appropriate index.
|
||||
*
|
||||
* @param {Array} a is an array
|
||||
* @param {number|string|Array|Object} v is the value to insert
|
||||
* @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]
|
||||
*/
|
||||
static binaryInsert(a, v, fnCompare)
|
||||
{
|
||||
var index = Usr.binarySearch(a, v, fnCompare);
|
||||
if (index < 0) {
|
||||
a.splice(-(index + 1), 0, v);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
/**
|
||||
* getBitField(bf, v)
|
||||
*
|
||||
* @param {BitField} bf
|
||||
* @param {number} v is a value containing bit fields
|
||||
* @return {number} the value of the bit field in v defined by bf
|
||||
*/
|
||||
usr.getBitField = function(bf, v)
|
||||
{
|
||||
return (v & bf.mask) >> bf.shift;
|
||||
};
|
||||
|
||||
/**
|
||||
* setBitField(bf, v, n)
|
||||
*
|
||||
* @param {BitField} bf
|
||||
* @param {number} v is a value containing bit fields
|
||||
* @param {number} n is a value to store in v in the bit field defined by bf
|
||||
* @return {number} updated v
|
||||
*/
|
||||
usr.setBitField = function(bf, v, n)
|
||||
{
|
||||
// Component.assert(!(n & ~(bf.mask >>> bf.shift)));
|
||||
return (v & ~bf.mask) | ((n << bf.shift) & bf.mask);
|
||||
};
|
||||
|
||||
/**
|
||||
* indexOf(a, t, i)
|
||||
*
|
||||
* Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it.
|
||||
*
|
||||
* @param {Array} a
|
||||
* @param {*} t
|
||||
* @param {number} [i]
|
||||
* @returns {number}
|
||||
*/
|
||||
usr.indexOf = function(a, t, i)
|
||||
{
|
||||
if (Array.prototype.indexOf) {
|
||||
return a.indexOf(t, i);
|
||||
/**
|
||||
* getTimestamp()
|
||||
*
|
||||
* @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss")
|
||||
*/
|
||||
static getTimestamp()
|
||||
{
|
||||
var date = new Date();
|
||||
var padNum = function(n)
|
||||
{
|
||||
return (n < 10 ? "0" : "") + n;
|
||||
};
|
||||
return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate()) + " " + padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds());
|
||||
}
|
||||
i = i || 0;
|
||||
if (i < 0) i += a.length;
|
||||
if (i < 0) i = 0;
|
||||
for (var n = a.length; i < n; i++) {
|
||||
if (i in a && a[i] === t) return i;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
if (NODE) module.exports = usr;
|
||||
/**
|
||||
* getMonthDays(nMonth, nYear)
|
||||
*
|
||||
* Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100),
|
||||
* so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails
|
||||
* the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap
|
||||
* year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year.
|
||||
*
|
||||
* TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD,
|
||||
* so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and
|
||||
* we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what
|
||||
* a real RTC actually does.
|
||||
*
|
||||
* @param {number} nMonth (1-12)
|
||||
* @param {number} nYear (normally a 4-digit year, but it may also be mod 100)
|
||||
* @return {number} the maximum (1-based) day allowed for the specified month and year
|
||||
*/
|
||||
static getMonthDays(nMonth, nYear)
|
||||
{
|
||||
var nDays = Usr.aMonthDays[nMonth - 1];
|
||||
if (nDays == 28) {
|
||||
if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) {
|
||||
nDays++;
|
||||
}
|
||||
}
|
||||
return nDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* formatDate(sFormat, date)
|
||||
*
|
||||
* @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s")
|
||||
* @param {Date} [date] (default is the current time)
|
||||
* @return {string}
|
||||
*
|
||||
* Supported identifiers in sFormat include:
|
||||
*
|
||||
* a: lowercase ante meridiem and post meridiem (am or pm)
|
||||
* d: day of the month, 2 digits with leading zeros (01,...,31)
|
||||
* g: hour in 12-hour format, without leading zeros (1,...,12)
|
||||
* i: minutes, with leading zeros (00,...,59)
|
||||
* j: day of the month, without leading zeros (1,...,31)
|
||||
* l: day of the week ("Sunday",...,"Saturday")
|
||||
* m: month, with leading zeros (01,...,12)
|
||||
* s: seconds, with leading zeros (00,...,59)
|
||||
* F: month ("January",...,"December")
|
||||
* H: hour in 24-hour format, with leading zeros (00,...,23)
|
||||
* Y: year (eg, 2014)
|
||||
*
|
||||
* For more inspiration, see: http://php.net/manual/en/function.date.php
|
||||
*/
|
||||
static formatDate(sFormat, date)
|
||||
{
|
||||
var sDate = "";
|
||||
if (!date) date = new Date();
|
||||
var iHour = date.getHours();
|
||||
var iDay = date.getDate();
|
||||
var iMonth = date.getMonth() + 1;
|
||||
for (var i = 0; i < sFormat.length; i++) {
|
||||
var ch;
|
||||
switch ((ch = sFormat.charAt(i))) {
|
||||
case 'a':
|
||||
sDate += (iHour < 12 ? "am" : "pm");
|
||||
break;
|
||||
case 'd':
|
||||
sDate += ('0' + iDay).slice(-2);
|
||||
break;
|
||||
case 'g':
|
||||
sDate += (!iHour ? 12 : (iHour > 12 ? iHour - 12 : iHour));
|
||||
break;
|
||||
case 'i':
|
||||
sDate += ('0' + date.getMinutes()).slice(-2);
|
||||
break;
|
||||
case 'j':
|
||||
sDate += iDay;
|
||||
break;
|
||||
case 'l':
|
||||
sDate += Usr.asDays[date.getDay()];
|
||||
break;
|
||||
case 'm':
|
||||
sDate += ('0' + iMonth).slice(-2);
|
||||
break;
|
||||
case 's':
|
||||
sDate += ('0' + date.getSeconds()).slice(-2);
|
||||
break;
|
||||
case 'F':
|
||||
sDate += Usr.asMonths[iMonth - 1];
|
||||
break;
|
||||
case 'H':
|
||||
sDate += ('0' + iHour).slice(-2);
|
||||
break;
|
||||
case 'Y':
|
||||
sDate += date.getFullYear();
|
||||
break;
|
||||
default:
|
||||
sDate += ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* defineBitFields(bfs)
|
||||
*
|
||||
* Prepares a bit field definition for use with getBitField() and setBitField(); eg:
|
||||
*
|
||||
* var bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3});
|
||||
*
|
||||
* The above defines a set of bit fields containing four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31).
|
||||
*
|
||||
* Usr.setBitField(bfs.num, n, 1);
|
||||
*
|
||||
* The above set bit field "bfs.num" in numeric variable "n" to the value 1.
|
||||
*
|
||||
* @param {Object} bfs
|
||||
* @return {BitFields}
|
||||
*/
|
||||
static defineBitFields(bfs)
|
||||
{
|
||||
var bit = 0;
|
||||
for (var f in bfs) {
|
||||
var width = bfs[f];
|
||||
var mask = ((1 << width) - 1) << bit;
|
||||
bfs[f] = {mask: mask, shift: bit};
|
||||
bit += width;
|
||||
}
|
||||
return bfs;
|
||||
}
|
||||
|
||||
/**
|
||||
* initBitFields(bfs, ...)
|
||||
*
|
||||
* @param {BitFields} bfs
|
||||
* @param {...number} var_args
|
||||
* @return {number} a value containing all supplied bit fields
|
||||
*/
|
||||
static initBitFields(bfs, var_args)
|
||||
{
|
||||
var v = 0, i = 1;
|
||||
for (var f in bfs) {
|
||||
if (i >= arguments.length) break;
|
||||
v = Usr.setBitField(bfs[f], v, arguments[i++]);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* getBitField(bf, v)
|
||||
*
|
||||
* @param {BitField} bf
|
||||
* @param {number} v is a value containing bit fields
|
||||
* @return {number} the value of the bit field in v defined by bf
|
||||
*/
|
||||
static getBitField(bf, v)
|
||||
{
|
||||
return (v & bf.mask) >> bf.shift;
|
||||
}
|
||||
|
||||
/**
|
||||
* setBitField(bf, v, n)
|
||||
*
|
||||
* @param {BitField} bf
|
||||
* @param {number} v is a value containing bit fields
|
||||
* @param {number} n is a value to store in v in the bit field defined by bf
|
||||
* @return {number} updated v
|
||||
*/
|
||||
static setBitField(bf, v, n)
|
||||
{
|
||||
return (v & ~bf.mask) | ((n << bf.shift) & bf.mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* indexOf(a, t, i)
|
||||
*
|
||||
* Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it.
|
||||
*
|
||||
* @param {Array} a
|
||||
* @param {*} t
|
||||
* @param {number} [i]
|
||||
* @returns {number}
|
||||
*/
|
||||
static indexOf(a, t, i)
|
||||
{
|
||||
if (Array.prototype.indexOf) {
|
||||
return a.indexOf(t, i);
|
||||
}
|
||||
i = i || 0;
|
||||
if (i < 0) i += a.length;
|
||||
if (i < 0) i = 0;
|
||||
for (var n = a.length; i < n; i++) {
|
||||
if (i in a && a[i] === t) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
Usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
Usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
/**
|
||||
* getTime()
|
||||
*
|
||||
* @return {number} the current time, in milliseconds
|
||||
*/
|
||||
Usr.getTime = Date.now || function() { return +new Date(); };
|
||||
|
||||
if (NODE) module.exports = Usr;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue