-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AHK. Added JSON support (config extracted). Chrome devtools. Some ref…
…actoring.
- Loading branch information
Showing
9 changed files
with
916 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,374 @@ | ||
/** | ||
* Lib: JSON.ahk | ||
* JSON lib for AutoHotkey. | ||
* Version: | ||
* v2.1.3 [updated 04/18/2016 (MM/DD/YYYY)] | ||
* License: | ||
* WTFPL [http://wtfpl.net/] | ||
* Requirements: | ||
* Latest version of AutoHotkey (v1.1+ or v2.0-a+) | ||
* Installation: | ||
* Use #Include JSON.ahk or copy into a function library folder and then | ||
* use #Include <JSON> | ||
* Links: | ||
* GitHub: - https://github.com/cocobelgica/AutoHotkey-JSON | ||
* Forum Topic - http://goo.gl/r0zI8t | ||
* Email: - cocobelgica <at> gmail <dot> com | ||
*/ | ||
|
||
|
||
/** | ||
* Class: JSON | ||
* The JSON object contains methods for parsing JSON and converting values | ||
* to JSON. Callable - NO; Instantiable - YES; Subclassable - YES; | ||
* Nestable(via #Include) - NO. | ||
* Methods: | ||
* Load() - see relevant documentation before method definition header | ||
* Dump() - see relevant documentation before method definition header | ||
*/ | ||
class JSON | ||
{ | ||
/** | ||
* Method: Load | ||
* Parses a JSON string into an AHK value | ||
* Syntax: | ||
* value := JSON.Load( text [, reviver ] ) | ||
* Parameter(s): | ||
* value [retval] - parsed value | ||
* text [in, ByRef] - JSON formatted string | ||
* reviver [in, opt] - function object, similar to JavaScript's | ||
* JSON.parse() 'reviver' parameter | ||
*/ | ||
class Load extends JSON.Functor | ||
{ | ||
Call(self, ByRef text, reviver:="") | ||
{ | ||
this.rev := IsObject(reviver) ? reviver : false | ||
; Object keys(and array indices) are temporarily stored in arrays so that | ||
; we can enumerate them in the order they appear in the document/text instead | ||
; of alphabetically. Skip if no reviver function is specified. | ||
this.keys := this.rev ? {} : false | ||
|
||
static quot := Chr(34), bashq := "\" . quot | ||
, json_value := quot . "{[01234567890-tfn" | ||
, json_value_or_array_closing := quot . "{[]01234567890-tfn" | ||
, object_key_or_object_closing := quot . "}" | ||
|
||
key := "" | ||
is_key := false | ||
root := {} | ||
stack := [root] | ||
next := json_value | ||
pos := 0 | ||
|
||
while ((ch := SubStr(text, ++pos, 1)) != "") { | ||
if InStr(" `t`r`n", ch) | ||
continue | ||
if !InStr(next, ch, 1) | ||
this.ParseError(next, text, pos) | ||
|
||
holder := stack[1] | ||
is_array := holder.IsArray | ||
|
||
if InStr(",:", ch) { | ||
next := (is_key := !is_array && ch == ",") ? quot : json_value | ||
|
||
} else if InStr("}]", ch) { | ||
ObjRemoveAt(stack, 1) | ||
next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}" | ||
|
||
} else { | ||
if InStr("{[", ch) { | ||
; Check if Array() is overridden and if its return value has | ||
; the 'IsArray' property. If so, Array() will be called normally, | ||
; otherwise, use a custom base object for arrays | ||
static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0 | ||
|
||
; sacrifice readability for minor(actually negligible) performance gain | ||
(ch == "{") | ||
? ( is_key := true | ||
, value := {} | ||
, next := object_key_or_object_closing ) | ||
; ch == "[" | ||
: ( value := json_array ? new json_array : [] | ||
, next := json_value_or_array_closing ) | ||
|
||
ObjInsertAt(stack, 1, value) | ||
|
||
if (this.keys) | ||
this.keys[value] := [] | ||
|
||
} else { | ||
if (ch == quot) { | ||
i := pos | ||
while (i := InStr(text, quot,, i+1)) { | ||
value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c") | ||
|
||
static tail := A_AhkVersion<"2" ? 0 : -1 | ||
if (SubStr(value, tail) != "\") | ||
break | ||
} | ||
|
||
if (!i) | ||
this.ParseError("'", text, pos) | ||
|
||
value := StrReplace(value, "\/", "/") | ||
, value := StrReplace(value, bashq, quot) | ||
, value := StrReplace(value, "\b", "`b") | ||
, value := StrReplace(value, "\f", "`f") | ||
, value := StrReplace(value, "\n", "`n") | ||
, value := StrReplace(value, "\r", "`r") | ||
, value := StrReplace(value, "\t", "`t") | ||
|
||
pos := i ; update pos | ||
|
||
i := 0 | ||
while (i := InStr(value, "\",, i+1)) { | ||
if !(SubStr(value, i+1, 1) == "u") | ||
this.ParseError("\", text, pos - StrLen(SubStr(value, i+1))) | ||
|
||
uffff := Abs("0x" . SubStr(value, i+2, 4)) | ||
if (A_IsUnicode || uffff < 0x100) | ||
value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6) | ||
} | ||
|
||
if (is_key) { | ||
key := value, next := ":" | ||
continue | ||
} | ||
|
||
} else { | ||
value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos) | ||
|
||
static number := "number", integer :="integer" | ||
if value is %number% | ||
{ | ||
if value is %integer% | ||
value += 0 | ||
} | ||
else if (value == "true" || value == "false") | ||
value := %value% + 0 | ||
else if (value == "null") | ||
value := "" | ||
else | ||
; we can do more here to pinpoint the actual culprit | ||
; but that's just too much extra work. | ||
this.ParseError(next, text, pos, i) | ||
|
||
pos += i-1 | ||
} | ||
|
||
next := holder==root ? "" : is_array ? ",]" : ",}" | ||
} ; If InStr("{[", ch) { ... } else | ||
|
||
is_array? key := ObjPush(holder, value) : holder[key] := value | ||
|
||
if (this.keys && this.keys.HasKey(holder)) | ||
this.keys[holder].Push(key) | ||
} | ||
|
||
} ; while ( ... ) | ||
|
||
return this.rev ? this.Walk(root, "") : root[""] | ||
} | ||
|
||
ParseError(expect, ByRef text, pos, len:=1) | ||
{ | ||
static quot := Chr(34), qurly := quot . "}" | ||
|
||
line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length() | ||
col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1)) | ||
msg := Format("{1}`n`nLine:`t{2}`nCol:`t{3}`nChar:`t{4}" | ||
, (expect == "") ? "Extra data" | ||
: (expect == "'") ? "Unterminated string starting at" | ||
: (expect == "\") ? "Invalid \escape" | ||
: (expect == ":") ? "Expecting ':' delimiter" | ||
: (expect == quot) ? "Expecting object key enclosed in double quotes" | ||
: (expect == qurly) ? "Expecting object key enclosed in double quotes or object closing '}'" | ||
: (expect == ",}") ? "Expecting ',' delimiter or object closing '}'" | ||
: (expect == ",]") ? "Expecting ',' delimiter or array closing ']'" | ||
: InStr(expect, "]") ? "Expecting JSON value or array closing ']'" | ||
: "Expecting JSON value(string, number, true, false, null, object or array)" | ||
, line, col, pos) | ||
|
||
static offset := A_AhkVersion<"2" ? -3 : -4 | ||
throw Exception(msg, offset, SubStr(text, pos, len)) | ||
} | ||
|
||
Walk(holder, key) | ||
{ | ||
value := holder[key] | ||
if IsObject(value) { | ||
for i, k in this.keys[value] { | ||
; check if ObjHasKey(value, k) ?? | ||
v := this.Walk(value, k) | ||
if (v != JSON.Undefined) | ||
value[k] := v | ||
else | ||
ObjDelete(value, k) | ||
} | ||
} | ||
|
||
return this.rev.Call(holder, key, value) | ||
} | ||
} | ||
|
||
/** | ||
* Method: Dump | ||
* Converts an AHK value into a JSON string | ||
* Syntax: | ||
* str := JSON.Dump( value [, replacer, space ] ) | ||
* Parameter(s): | ||
* str [retval] - JSON representation of an AHK value | ||
* value [in] - any value(object, string, number) | ||
* replacer [in, opt] - function object, similar to JavaScript's | ||
* JSON.stringify() 'replacer' parameter | ||
* space [in, opt] - similar to JavaScript's JSON.stringify() | ||
* 'space' parameter | ||
*/ | ||
class Dump extends JSON.Functor | ||
{ | ||
Call(self, value, replacer:="", space:="") | ||
{ | ||
this.rep := IsObject(replacer) ? replacer : "" | ||
|
||
this.gap := "" | ||
if (space) { | ||
static integer := "integer" | ||
if space is %integer% | ||
Loop, % ((n := Abs(space))>10 ? 10 : n) | ||
this.gap .= " " | ||
else | ||
this.gap := SubStr(space, 1, 10) | ||
|
||
this.indent := "`n" | ||
} | ||
|
||
return this.Str({"": value}, "") | ||
} | ||
|
||
Str(holder, key) | ||
{ | ||
value := holder[key] | ||
|
||
if (this.rep) | ||
value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : JSON.Undefined) | ||
|
||
if IsObject(value) { | ||
; Check object type, skip serialization for other object types such as | ||
; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc. | ||
static type := A_AhkVersion<"2" ? "" : Func("Type") | ||
if (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") { | ||
if (this.gap) { | ||
stepback := this.indent | ||
this.indent .= this.gap | ||
} | ||
|
||
is_array := value.IsArray | ||
; Array() is not overridden, rollback to old method of | ||
; identifying array-like objects. Due to the use of a for-loop | ||
; sparse arrays such as '[1,,3]' are detected as objects({}). | ||
if (!is_array) { | ||
for i in value | ||
is_array := i == A_Index | ||
until !is_array | ||
} | ||
|
||
str := "" | ||
if (is_array) { | ||
Loop, % value.Length() { | ||
if (this.gap) | ||
str .= this.indent | ||
|
||
v := this.Str(value, A_Index) | ||
str .= (v != "") ? v . "," : "null," | ||
} | ||
} else { | ||
colon := this.gap ? ": " : ":" | ||
for k in value { | ||
v := this.Str(value, k) | ||
if (v != "") { | ||
if (this.gap) | ||
str .= this.indent | ||
|
||
str .= this.Quote(k) . colon . v . "," | ||
} | ||
} | ||
} | ||
|
||
if (str != "") { | ||
str := RTrim(str, ",") | ||
if (this.gap) | ||
str .= stepback | ||
} | ||
|
||
if (this.gap) | ||
this.indent := stepback | ||
|
||
return is_array ? "[" . str . "]" : "{" . str . "}" | ||
} | ||
|
||
} else ; is_number ? value : "value" | ||
return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value) | ||
} | ||
|
||
Quote(string) | ||
{ | ||
static quot := Chr(34), bashq := "\" . quot | ||
|
||
if (string != "") { | ||
string := StrReplace(string, "\", "\\") | ||
; , string := StrReplace(string, "/", "\/") ; optional in ECMAScript | ||
, string := StrReplace(string, quot, bashq) | ||
, string := StrReplace(string, "`b", "\b") | ||
, string := StrReplace(string, "`f", "\f") | ||
, string := StrReplace(string, "`n", "\n") | ||
, string := StrReplace(string, "`r", "\r") | ||
, string := StrReplace(string, "`t", "\t") | ||
|
||
static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]" | ||
while RegExMatch(string, rx_escapable, m) | ||
string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value))) | ||
} | ||
|
||
return quot . string . quot | ||
} | ||
} | ||
|
||
/** | ||
* Property: Undefined | ||
* Proxy for 'undefined' type | ||
* Syntax: | ||
* undefined := JSON.Undefined | ||
* Remarks: | ||
* For use with reviver and replacer functions since AutoHotkey does not | ||
* have an 'undefined' type. Returning blank("") or 0 won't work since these | ||
* can't be distnguished from actual JSON values. This leaves us with objects. | ||
* Replacer() - the caller may return a non-serializable AHK objects such as | ||
* ComObject, Func, BoundFunc, FileObject, RegExMatchObject, and Property to | ||
* mimic the behavior of returning 'undefined' in JavaScript but for the sake | ||
* of code readability and convenience, it's better to do 'return JSON.Undefined'. | ||
* Internally, the property returns a ComObject with the variant type of VT_EMPTY. | ||
*/ | ||
Undefined[] | ||
{ | ||
get { | ||
static empty := {}, vt_empty := ComObject(0, &empty, 1) | ||
return vt_empty | ||
} | ||
} | ||
|
||
class Functor | ||
{ | ||
__Call(method, ByRef arg, args*) | ||
{ | ||
; When casting to Call(), use a new instance of the "function object" | ||
; so as to avoid directly storing the properties(used across sub-methods) | ||
; into the "function object" itself. | ||
if IsObject(method) | ||
return (new this).Call(method, arg, args*) | ||
else if (method == "") | ||
return (new this).Call(arg, args*) | ||
} | ||
} | ||
} |
Oops, something went wrong.