├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── _config.yml ├── package-lock.json ├── package.json ├── readme_template.md ├── src ├── common_tests.js ├── index.js ├── jq.js ├── jq.pegjs ├── test.js ├── test_helpers.js ├── tests.json └── travis_test.js ├── webpack.config.js └── webpack.test.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | install: 5 | - npm install 6 | script: 7 | - npm run test:travis 8 | - npm run build 9 | 10 | deploy: 11 | provider: script 12 | skip_cleanup: true 13 | script: 14 | - npx semantic-release 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Daniel Kantor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | README.md: ./readme_template.md ./src/tests.json 2 | (cat readme_template.md; cat src/tests.json | jq '.[] | .[] | [.[0], "```" +(.[1] | join("```, ```")) + "```"] | join("@")' -c -r | sed "s/|/\\\|/g; s/@/|/g") > $@ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DISCONTINUED in favor of [emuto](https://github.com/kantord/emuto) 2 | 3 | # jq-in-the-browser 4 | 5 | jq-in-the-browser is a JavaScript port of jq. [Try it online](https://runkit.com/kantord/runkit-npm-jq-in-the-browser) 6 | 7 | Instead of processing serialized data, jq-in-the-browser processes JavaScript 8 | objects. It is written from scratch and is relatively small. (~33 kB, ~6.1 kB gzipped) 9 | 10 | ## Install 11 | `npm install --save jq-in-the-browser` 12 | 13 | ## Usage 14 | ```javascript 15 | import jq from 'jq-in-the-browser' 16 | 17 | const query = jq('{"names": [.[] | .name]}') 18 | 19 | query([ 20 | {"name": "Mary", "age": 22}, 21 | {"name": "Rupert", "age": 29}, 22 | {"name": "Jane", "age": 11}, 23 | {"name": "John", "age": 42} 24 | ]) 25 | ``` 26 | 27 | Output: 28 | ```json 29 | { 30 | "names": [ 31 | "Mary", 32 | "Rupert", 33 | "Jane", 34 | "John" 35 | ] 36 | } 37 | ``` 38 | 39 | ## Comparison with alternatives 40 | 41 | ### jq-web 42 | 43 | - jq-web is an emcripten port of jq, thus it implements all of its features 44 | - ... but it's also too big for many purposes (in the megabytes) 45 | - jq-in-the-browser is written from scratch, but is more limited in features 46 | - ... and also much much smaller :-) 47 | 48 | ### node-jq 49 | - node-jq is great, but it doesn't work in the browser. 50 | 51 | ### something else? 52 | If you know an alternative, feel free to create a pull request. :-) 53 | 54 | ## Supported features 55 | 56 | Feature | Example 57 | --- | --- 58 | Identity|```.```, ``` . ``` 59 | Array Index|```.[0]```, ```.[1 ]```, ```.[-1]```, ```.[ 1][0]```, ```.[1][1].x```, ```.[1][1].x[0]```, ```.[ -1 ]``` 60 | Object Identifier-Index|```.foo```, ```.bar```, ```.bar.x```, ```.foo[1]``` 61 | Generic Object Index|```.["foo"]```, ```.["bar"].x```, ```.bar[ "y"]```, ```.["2bar"]```, ```.["a b" ]``` 62 | Pipe|```.a \| .b```, ```.a\|.b``` 63 | Parentheses|```( .a)```, ```((.a))```, ```(-1 )```, ```(-5.5)```, ```(.4)```, ```(. \| .)``` 64 | Addition (numbers)|```1 + 1```, ```.a + [.b][0]```, ```.b + .a```, ```3 + 4.1 + .a```, ```3 + (-3)``` 65 | Subtraction (numbers)|```.a - .b```, ```.b - .a```, ```4- 3```, ```-3 -(4)``` 66 | Multiplication (numbers)|```1 * 1```, ```.a * [.b][0]```, ```.b * .a```, ```3 * 4.1 * .a```, ```3 * (-.3)``` 67 | Modulo (numbers)|```1 % 1```, ```.a % [.b][0]```, ```.b % .a```, ```3 % 4 % .a``` 68 | Division (numbers)|```.a / .b```, ```.b / .a```, ```4/ 3```, ```-3/(4)```, ```-1.1 + (3 * (((.4 - .b) / .a) + .b))``` 69 | Array Construction|```[]```, ```[ ]```, ```[4]```, ```[ -6, [0]]```, ```[7 \| 4]```, ```[.]```, ```[. \| [6]]```, ```[5, 6] \| .``` 70 | Object Construction|```{}```, ```{ }```, ```{"foo": 6}```, ```{"foo": 6, "bar": [5, 3]}```, ```{"x": 3} \| {"y": .x}```, ```{foo: "bar"}```, ```{({"a": "b"} \| .a): true}```, ```{"a": 4, "b": 3, "c": -1, "d": "f"}``` 71 | Integer literal|```3```, ``` 6```, ```-4```, ```0```, ```8``` 72 | Float literal|```.3```, ```6.0```, ```-4.001```, ```3.14```, ```0.1``` 73 | Boolean literal|```true```, ```false``` 74 | Double quote String literal|```"true"```, ```"false"```, ```"foo"```, ```["ba'r"]``` 75 | length|```[] \| length```, ```length``` 76 | keys|```keys``` 77 | keys_unsorted|```keys_unsorted``` 78 | to_entries|```. \| to_entries``` 79 | from_entries|```. \| from_entries``` 80 | reverse|```. \| reverse``` 81 | map|```map(.+1 )```, ```. \| map( {foo: .})``` 82 | map_values|```map_values(.+1 )```, ```. \| map_values( {foo: .})``` 83 | with_entries|```with_entries({key: .key, value: (2 * .value)})```, ```with_entries({key: "a", value: (2 * .value)})``` 84 | tonumber|```tonumber``` 85 | tostring|```tostring``` 86 | sort|```sort```, ```[4, 5, 6] \| sort``` 87 | sort_by|```sort_by(-.)```, ```sort_by(1 + .)```, ```sort_by(1)``` 88 | join|```join(", ")```, ```join("")```, ```join(.[0])``` 89 | Additive inverse|```-(1 + 3)```, ```-(-1)```, ```.a \| -(.b)```, ```[--1]``` 90 | Array Construction|```[]```, ```[4]``` 91 | Array/Object Value Iterator|```.[]```, ```.[ ]``` 92 | Array/Object Value Iterator 2|```.["foo"][]```, ```.foo[]``` 93 | Pipe|```.[] \| .```, ```.[] \| .name``` 94 | Stream as object value|```{names: .[] \| .name}```, ```{"names": .[] \| .name, "ages": .[] \| .age}```, ```{"names": .[] \| .name, "x": 3}```, ```{"names": 5.4, "x": .[] \| .age}```, ```{names: 5.4, ages: .[] \| .age, ages2: .[] \| .id}``` 95 | Array/String slice|```.[2:4]```, ```.[0:1]``` 96 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | show_downloads: true 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jq-in-the-browser", 3 | "version": "0.0.3", 4 | "description": "jq-compatible query language for the web", 5 | "main": "lib/jq-in-the-browser.js", 6 | "scripts": { 7 | "test": "mocha-webpack \"src/test.js\" \"src/common_tests.js\" --webpack-config webpack.test.config.js", 8 | "test:travis": "mocha-webpack \"src/travis_test.js\" \"src/common_tests.js\" --webpack-config webpack.test.config.js", 9 | "test:watch": "mocha-webpack --watch \"src/test.js\" \"src/common_tests.js\" --webpack-config webpack.test.config.js --reporter dot", 10 | "build": "webpack" 11 | }, 12 | "author": "Dániel Kántor", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "child_process": "^1.0.2", 16 | "jq-web": "^0.1.3", 17 | "json-loader": "^0.5.7", 18 | "mocha": "^5.0.0", 19 | "mocha-webpack": "^1.0.1", 20 | "node-jq": "^1.2.0", 21 | "pegjs": "^0.10.0", 22 | "pegjs-loader": "^0.5.4", 23 | "semantic-release": "^12.4.1", 24 | "should": "^13.2.1", 25 | "uglifyjs-webpack-plugin": "^1.1.8", 26 | "webpack": "^3.10.0", 27 | "@babel/core": "^7.0.0-beta.39", 28 | "@babel/preset-env": "^7.0.0-beta.39", 29 | "babel-loader": "^8.0.0-beta.0" 30 | }, 31 | "dependencies": {} 32 | } 33 | -------------------------------------------------------------------------------- /readme_template.md: -------------------------------------------------------------------------------- 1 | # jq-in-the-browser 2 | 3 | jq-in-the-browser is a JavaScript port of jq. [Try it online](https://runkit.com/kantord/runkit-npm-jq-in-the-browser) 4 | 5 | Instead of processing serialized data, jq-in-the-browser processes JavaScript 6 | objects. It is written from scratch and is relatively small. (~33 kB, ~6.1 kB gzipped) 7 | 8 | ## Install 9 | `npm install --save jq-in-the-browser` 10 | 11 | ## Usage 12 | ```javascript 13 | import jq from 'jq-in-the-browser' 14 | 15 | const query = jq('{"names": [.[] | .name]}') 16 | 17 | query([ 18 | {"name": "Mary", "age": 22}, 19 | {"name": "Rupert", "age": 29}, 20 | {"name": "Jane", "age": 11}, 21 | {"name": "John", "age": 42} 22 | ]) 23 | ``` 24 | 25 | Output: 26 | ```json 27 | { 28 | "names": [ 29 | "Mary", 30 | "Rupert", 31 | "Jane", 32 | "John" 33 | ] 34 | } 35 | ``` 36 | 37 | ## Comparison with alternatives 38 | 39 | ### jq-web 40 | 41 | - jq-web is an emcripten port of jq, thus it implements all of its features 42 | - ... but it's also too big for many purposes (in the megabytes) 43 | - jq-in-the-browser is written from scratch, but is more limited in features 44 | - ... and also much much smaller :-) 45 | 46 | ### node-jq 47 | - node-jq is great, but it doesn't work in the browser. 48 | 49 | ### something else? 50 | If you know an alternative, feel free to create a pull request. :-) 51 | 52 | ## Supported features 53 | 54 | Feature | Example 55 | --- | --- 56 | -------------------------------------------------------------------------------- /src/common_tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import parser from './jq.js' 3 | import should from 'should' 4 | 5 | describe('Single quote String literal', () => { 6 | it('per se', () => { 7 | assert.equal(parser("'Hello \"World\"!'")(null), 'Hello "World"!') 8 | }) 9 | 10 | it('as key', () => { 11 | assert.deepEqual(parser("{'a': false}")(null), {'a': false}) 12 | }) 13 | }) 14 | 15 | describe('Other tests', () => { 16 | it('handle example code correctly', () => { 17 | const query = '{"names": .[] | .name}' 18 | const input = [ 19 | {"name": "Mary", "age": 22}, 20 | {"name": "Rupert", "age": 29}, 21 | {"name": "Jane", "age": 11}, 22 | {"name": "John", "age": 42} 23 | ] 24 | const output = [ 25 | { 26 | "names": "Mary" 27 | }, 28 | { 29 | "names": "Rupert" 30 | }, 31 | { 32 | "names": "Jane" 33 | }, 34 | { 35 | "names": "John" 36 | }, 37 | ] 38 | assert.deepEqual(parser(query)(input), output) 39 | }) 40 | 41 | it('handle example code correctly 2', () => { 42 | const query = '{"names": [.[] | .name]}' 43 | const input = [ 44 | {"name": "Mary", "age": 22}, 45 | {"name": "Rupert", "age": 29}, 46 | {"name": "Jane", "age": 11}, 47 | {"name": "John", "age": 42} 48 | ] 49 | const output = { 50 | "names": [ 51 | "Mary", 52 | "Rupert", 53 | "Jane", 54 | "John" 55 | ] 56 | } 57 | 58 | assert.deepEqual(parser(query)(input), output) 59 | }) 60 | }) 61 | 62 | 63 | describe('Error messages', () => { 64 | const tests = [ 65 | ['. | foo', 'function foo/0 is not defined'], 66 | ['. | bar', 'function bar/0 is not defined'], 67 | ['. | bar(4)', 'function bar/1 is not defined'], 68 | ['. | baz(4)', 'function baz/1 is not defined'] 69 | ] 70 | 71 | tests.forEach(([query, error]) => 72 | it(`Error '${error}' for '${query}'`, () => { 73 | (() => parser(query)(input)).should.throw(error) 74 | }) 75 | ) 76 | }) 77 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import parse from './jq.js' 2 | 3 | const jq = parse 4 | 5 | export default jq 6 | -------------------------------------------------------------------------------- /src/jq.js: -------------------------------------------------------------------------------- 1 | import parser from './jq.pegjs' 2 | 3 | export default parser.parse 4 | -------------------------------------------------------------------------------- /src/jq.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | const identity = f => f 3 | const flow = funcs => funcs.reduce((result, element) => (input => element(result(input))), identity) 4 | const mapf = func => function(input) { 5 | if (input instanceof Stream) { 6 | return input.map(func) 7 | } 8 | 9 | return func(input) 10 | } 11 | const construct_pair_simple = (key, value) => input => { 12 | let obj = {}; 13 | obj[key] = value(input); 14 | return obj; 15 | } 16 | const construct_pair = (key, value) => input => { 17 | const value_results = value(input) 18 | if (value_results instanceof Stream) { 19 | return value_results.map(i => construct_pair_simple(key, f => f)(i)) 20 | } 21 | return construct_pair_simple(key, value)(input) 22 | } 23 | 24 | const flatten = (arr) => [].concat.apply([], arr); 25 | 26 | const product = (...sets) => 27 | sets.reduce((acc, set) => 28 | flatten(acc.map(x => set.map(y => [ ...x, y ]))), 29 | [[]]); 30 | 31 | const combine_pairs = (left, right, input) => { 32 | const left_value = left(input) 33 | const right_value = right(input) 34 | if (left_value instanceof Stream) { 35 | if (right_value instanceof Stream) { 36 | return left_value.product(right_value).map((a, _) => Object.assign({}, ...a)) 37 | } else { 38 | return combine_pairs(left, i => new Stream([right(i)]), input) 39 | } 40 | } 41 | 42 | if (right_value instanceof Stream) { 43 | return combine_pairs(right, i => new Stream([left(i)]), input) 44 | } 45 | return Object.assign(left_value, right_value) 46 | } 47 | const unpack = a => (a instanceof Stream) ? (unpack(a.unpack())) : a 48 | 49 | class Stream { 50 | // Simulates multiple "lines" of output 51 | constructor(items) { 52 | this.items = items; 53 | } 54 | 55 | unpack() { 56 | return this.items 57 | } 58 | 59 | map(f) { 60 | return new Stream(this.items.map(f)) 61 | } 62 | 63 | product(other) { 64 | return new Stream(product(this.items, unpack(other))) 65 | } 66 | } 67 | 68 | const get_function_0 = name => { 69 | const f = function0_map[name] 70 | if (f === undefined) throw new Error(`function ${name}/0 is not defined`) 71 | return f 72 | } 73 | 74 | const get_function_1 = name => { 75 | const f = function1_map[name] 76 | if (f === undefined) throw new Error(`function ${name}/1 is not defined`) 77 | return f 78 | } 79 | 80 | const function0_map = { 81 | "length": input => input.length, 82 | "keys": input => Object.keys(input).sort(), 83 | "keys_unsorted": input => Object.keys(input), 84 | "to_entries": input => Object.entries(input).map(([key, value]) => ({ key, value })), 85 | "from_entries": input => input.reduce( 86 | (result, element) => Object.assign({}, result, {[element.key]: element.value}), {}), 87 | "reverse": input => ([].concat(input).reverse()), 88 | "tonumber": input => input * 1, 89 | "tostring": input => ((typeof input === "object") ? JSON.stringify(input) : String(input)), 90 | "sort": input => {return unpack(input).sort()} 91 | } 92 | 93 | const function1_map = { 94 | "map": arg => input => input.map(i => arg(i)), 95 | "map_values": arg => input => { 96 | const pairs = Object.keys(input).map(key => ({[key]: arg(input[key])})) 97 | return Object.assign({}, ...pairs) 98 | }, 99 | "with_entries": arg => input => { 100 | const from_entries = function0_map["from_entries"] 101 | const to_entries = function0_map["to_entries"] 102 | const mapped = to_entries(input).map(arg) 103 | return from_entries(mapped) 104 | }, 105 | "join": arg => input => input.join(arg(input)), 106 | "sort_by": arg => input => unpack(input).sort((a, b) => { 107 | const va = arg(a) 108 | const vb = arg(b) 109 | if (va < vb) return -1 110 | if (va > vb) return 1 111 | return 0 112 | }) 113 | } 114 | } 115 | 116 | value 117 | = _ additive:additive _ {return input => unpack(additive(input))} 118 | 119 | additive 120 | = left:multiplicative right:((_ ('+'/ '-') _ additive)+) {return input => { 121 | const f = (k) => ({ 122 | '+': (a, b) => a + b, 123 | '-': (a, b) => a - b, 124 | }[k]) 125 | return right.reduce( 126 | (result, element) => f(element[1])(result, element[3](input)), 127 | left(input)) 128 | }} 129 | / "-" _ additive:additive {return input => 0 - additive(input)} 130 | / multiplicative 131 | 132 | multiplicative 133 | = left:pipeline right:((_ ('*'/ '/' / '%') _ pipeline)+) {return input => { 134 | const f = (k) => ({ 135 | '*': (a, b) => a * b, 136 | '/': (a, b) => a / b, 137 | '%': (a, b) => a % b, 138 | }[k]) 139 | return right.reduce( 140 | (result, element) => f(element[1])(result, element[3](input)), 141 | left(input)) 142 | }} 143 | / pipeline 144 | 145 | _ 146 | = [ ]* 147 | 148 | pipeline 149 | = "(" _ pipeline:value _ ")" {return pipeline} 150 | / "-" _ "(" _ pipeline:value _ ")" {return input => 0 - pipeline(input)} 151 | / left:filter _ "|" _ right:pipeline {return input => mapf(right)(left(input))} 152 | / filter 153 | 154 | head_filter 155 | = float_literal 156 | / boolean_literal 157 | / object_identifier_index 158 | / identity 159 | / array_construction 160 | / object_construction 161 | / integer_literal 162 | / single_quote_string_literal 163 | / double_quote_string_literal 164 | / function1 165 | / function0 166 | 167 | function1 168 | = name:name _ "(" _ arg:value _ ")" {return get_function_1(name)(arg)} 169 | 170 | function0 171 | = name:name {return get_function_0(name)} 172 | 173 | double_quote_string_literal 174 | = '"' core:double_quote_string_core '"' {return input => core} 175 | 176 | single_quote_string_literal 177 | = '\'' core:single_quote_string_core '\'' {return input => core} 178 | 179 | boolean_literal 180 | = true 181 | / false 182 | 183 | true 184 | = "true" {return input => true} 185 | 186 | false 187 | = "false" {return input => false} 188 | 189 | 190 | 191 | array_construction 192 | = "[" _ "]" {return input => []} 193 | / "[" array_inside:array_inside "]" {return input => unpack(array_inside(input))} 194 | 195 | object_construction 196 | = "{" _ "}" {return input => ({})} 197 | / "{" object_inside:object_inside "}" {return input => object_inside(input)} 198 | 199 | array_inside 200 | = left:value _ "," _ right:array_inside {return input => [left(input)].concat(right(input))} 201 | / value:additive {return input => { 202 | const v = value(input); 203 | return (v instanceof Stream) ? unpack(v) : [v] 204 | }} 205 | 206 | object_inside 207 | = left:pair _ "," _ right:object_inside {return input => combine_pairs(left, right, input)} 208 | / pair:pair {return input => pair(input)} 209 | 210 | pair 211 | = '"' key:double_quote_string_core '"' _ ':' _ value:additive {return construct_pair(key, value)} 212 | / "'" key:single_quote_string_core "'" _ ':' _ value:additive {return construct_pair(key, value)} 213 | / "(" _ key:value _ ")" _ ':' _ value:additive {return input => construct_pair(key(input), value)(input)} 214 | / key:name _ ':' _ value:additive {return construct_pair(key, value)} 215 | 216 | float_literal 217 | = "-" number:float_literal {return input => -number(input)} 218 | / ([0-9]*) "." ([0-9]+) {const v = (text() * 1); return input => v} 219 | 220 | integer_literal 221 | = "-" number:integer_literal {return input => -number(input)} 222 | / number:([0-9]+) {return input => number.join() * 1} 223 | 224 | filter 225 | = head_filter:head_filter transforms:transforms {return i => transforms(head_filter(i))} 226 | / head_filter 227 | 228 | transforms 229 | = funcs:(transform +) {return input => flow(funcs)(input)} 230 | 231 | transform 232 | = bracket_transforms 233 | / object_identifier_index 234 | 235 | bracket_transforms 236 | = "[" _ "]" { 237 | return function(input) { 238 | const handle_array = function(array) { 239 | if (array.length == 0) return [] 240 | if (array.length == 1) return array[0] 241 | return new Stream(array) 242 | } 243 | 244 | if (input instanceof Array) { 245 | return handle_array(input) 246 | } else { 247 | if (typeof input === 'object') return handle_array(Object.values(input)) 248 | } 249 | 250 | return input 251 | } 252 | } 253 | / '[' _ '"' _ key:double_quote_string_core _ '"' _ ']' {return i => i[key]} 254 | / '[' _ "'" _ key:single_quote_string_core _ "'" _ ']' {return i => i[key]} 255 | / "[" _ start:index _ ":" _ end:index _ "]" {return i => i.slice(start, end)} 256 | / "[" _ index:index _ "]" {return i => i[index]} 257 | / "[" _ "-" _ index:index _ "]" {return i => i[i.length - index]} 258 | 259 | identity 260 | = "." {return identity} 261 | 262 | object_identifier_index 263 | = "." name:name {return mapf(x => x[name])} 264 | 265 | double_quote_string_core 266 | = double_quote_string_char* {return text()} 267 | 268 | double_quote_string_char 269 | = [^"] {return text()} 270 | 271 | single_quote_string_core 272 | = single_quote_string_char* {return text()} 273 | 274 | single_quote_string_char 275 | = [^\'] {return text()} 276 | 277 | name 278 | = name:([a-zA-Z_$][0-9a-zA-Z_$]*) {return text()} 279 | 280 | index 281 | = index:[0-9]+ {return index} 282 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | import { test_with_jq_web, test_with_node_jq } from './test_helpers.js' 2 | const { tests_jq_web, tests_node_jq } = require('json-loader!./tests.json') 3 | 4 | tests_jq_web.forEach(test_with_jq_web) 5 | tests_node_jq.forEach(test_with_node_jq) 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test_helpers.js: -------------------------------------------------------------------------------- 1 | import jq from './index.js' 2 | import assert from 'assert' 3 | const jq_web = require('jq-web') 4 | const node_jq = require('node-jq') 5 | 6 | const test_with_node_jq = ([feature, queries, inputs]) => { 7 | describe(feature, () => 8 | queries.forEach((query) => 9 | describe(`Query: ${query}`, () => { 10 | inputs.forEach((input) => { 11 | it(`Input: ${JSON.stringify(input)}`, async () => { 12 | const parser_result = jq(query)(input) 13 | const jq_result = await node_jq.run(query, input, {input: 'json', output: 'json'}) 14 | assert.deepEqual(parser_result, jq_result) 15 | }) 16 | }) 17 | }) 18 | ) 19 | ) 20 | } 21 | 22 | const test_with_jq_web = ([feature, queries, inputs]) => { 23 | describe(feature, () => 24 | queries.forEach((query) => 25 | describe(`Query: ${query}`, () => { 26 | inputs.forEach((input) => { 27 | it(`Input: ${JSON.stringify(input)}`, () => { 28 | const parser_result = jq(query)(input) 29 | const jq_result = jq_web(input, query) 30 | assert.deepEqual(parser_result, jq_result) 31 | }) 32 | }) 33 | }) 34 | ) 35 | ) 36 | } 37 | 38 | export { test_with_jq_web, test_with_node_jq } 39 | -------------------------------------------------------------------------------- /src/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests_node_jq": [ 3 | [ 4 | "Identity", 5 | [ 6 | ".", 7 | " . " 8 | ], 9 | [ 10 | [ 11 | 4 12 | ], 13 | { 14 | "foo": "bar" 15 | } 16 | ] 17 | ], 18 | [ 19 | "Array Index", 20 | [ 21 | ".[0]", 22 | ".[1 ]", 23 | ".[-1]", 24 | ".[ 1][0]", 25 | ".[1][1].x", 26 | ".[1][1].x[0]", 27 | ".[ -1 ]" 28 | ], 29 | [ 30 | [ 31 | 0, 32 | [ 33 | 1, 34 | { 35 | "x": [ 36 | "y" 37 | ] 38 | } 39 | ] 40 | ], 41 | [ 42 | 1, 43 | [ 44 | 1, 45 | { 46 | "x": [ 47 | -1.1 48 | ] 49 | } 50 | ], 51 | 3 52 | ] 53 | ] 54 | ], 55 | [ 56 | "Object Identifier-Index", 57 | [ 58 | ".foo", 59 | ".bar", 60 | ".bar.x", 61 | ".foo[1]" 62 | ], 63 | [ 64 | { 65 | "foo": [ 66 | 3, 67 | 1 68 | ], 69 | "bar": { 70 | "x": 3, 71 | "y": 0.5 72 | }, 73 | "2bar": null, 74 | "a b": 0 75 | }, 76 | { 77 | "foo": [ 78 | 4, 79 | 1 80 | ], 81 | "bar": { 82 | "x": 3, 83 | "y": 0.5 84 | }, 85 | "2bar": 1, 86 | "a b": "a b" 87 | } 88 | ] 89 | ], 90 | [ 91 | "Generic Object Index", 92 | [ 93 | ".[\"foo\"]", 94 | ".[\"bar\"].x", 95 | ".bar[ \"y\"]", 96 | ".[\"2bar\"]", 97 | ".[\"a b\" ]" 98 | ], 99 | [ 100 | { 101 | "foo": [ 102 | 3, 103 | 1 104 | ], 105 | "bar": { 106 | "x": 3, 107 | "y": 0.5 108 | }, 109 | "2bar": null, 110 | "a b": 0 111 | }, 112 | { 113 | "foo": [ 114 | 4, 115 | 1 116 | ], 117 | "bar": { 118 | "x": 3, 119 | "y": 0.5 120 | }, 121 | "2bar": 1, 122 | "a b": "a b" 123 | } 124 | ] 125 | ], 126 | [ 127 | "Pipe", 128 | [ 129 | ".a | .b", 130 | ".a|.b" 131 | ], 132 | [ 133 | { 134 | "a": { 135 | "b": 4 136 | } 137 | }, 138 | { 139 | "a": { 140 | "b": "oo" 141 | } 142 | } 143 | ] 144 | ], 145 | [ 146 | "Parentheses", 147 | [ 148 | "( .a)", 149 | "((.a))", 150 | "(-1 )", 151 | "(-5.5)", 152 | "(.4)", 153 | "(. | .)" 154 | ], 155 | [ 156 | { 157 | "a": "a" 158 | }, 159 | { 160 | "a": -5 161 | } 162 | ] 163 | ], 164 | [ 165 | "Addition (numbers)", 166 | [ 167 | "1 + 1", 168 | ".a + [.b][0]", 169 | ".b + .a", 170 | "3 + 4.1 + .a", 171 | "3 + (-3)" 172 | ], 173 | [ 174 | { 175 | "a": 3, 176 | "b": 0 177 | }, 178 | { 179 | "a": -3, 180 | "b": 1.1 181 | } 182 | ] 183 | ], 184 | [ 185 | "Subtraction (numbers)", 186 | [ 187 | ".a - .b", 188 | ".b - .a", 189 | "4- 3", 190 | "-3 -(4)" 191 | ], 192 | [ 193 | { 194 | "a": 3, 195 | "b": 0 196 | }, 197 | { 198 | "a": -3, 199 | "b": 1.1 200 | } 201 | ] 202 | ], 203 | [ 204 | "Multiplication (numbers)", 205 | [ 206 | "1 * 1", 207 | ".a * [.b][0]", 208 | ".b * .a", 209 | "3 * 4.1 * .a", 210 | "3 * (-.3)" 211 | ], 212 | [ 213 | { 214 | "a": 3, 215 | "b": 0 216 | }, 217 | { 218 | "a": -3, 219 | "b": 1.1 220 | } 221 | ] 222 | ], 223 | [ 224 | "Modulo (numbers)", 225 | [ 226 | "1 % 1", 227 | ".a % [.b][0]", 228 | ".b % .a", 229 | "3 % 4 % .a" 230 | ], 231 | [ 232 | { 233 | "a": 3, 234 | "b": 1 235 | }, 236 | { 237 | "a": -3, 238 | "b": 1 239 | } 240 | ] 241 | ], 242 | [ 243 | "Division (numbers)", 244 | [ 245 | ".a / .b", 246 | ".b / .a", 247 | "4/ 3", 248 | "-3/(4)", 249 | "-1.1 + (3 * (((.4 - .b) / .a) + .b))" 250 | ], 251 | [ 252 | { 253 | "a": 3, 254 | "b": -1.1 255 | }, 256 | { 257 | "a": -3, 258 | "b": 1.1 259 | } 260 | ] 261 | ], 262 | [ 263 | "Array Construction", 264 | [ 265 | "[]", 266 | "[ ]", 267 | "[4]", 268 | "[ -6, [0]]", 269 | "[7 | 4]", 270 | "[.]", 271 | "[. | [6]]", 272 | "[5, 6] | ." 273 | ], 274 | [ 275 | [ 276 | 1 277 | ], 278 | { 279 | "a": "a" 280 | } 281 | ] 282 | ], 283 | [ 284 | "Object Construction", 285 | [ 286 | "{}", 287 | "{ }", 288 | "{\"foo\": 6}", 289 | "{\"foo\": 6, \"bar\": [5, 3]}", 290 | "{\"x\": 3} | {\"y\": .x}", 291 | "{foo: \"bar\"}", 292 | "{({\"a\": \"b\"} | .a): true}", 293 | "{\"a\": 4, \"b\": 3, \"c\": -1, \"d\": \"f\"}" 294 | ], 295 | [ 296 | [ 297 | 1 298 | ], 299 | { 300 | "a": "a" 301 | } 302 | ] 303 | ], 304 | [ 305 | "Integer literal", 306 | [ 307 | "3", 308 | " 6", 309 | "-4", 310 | "0", 311 | "8" 312 | ], 313 | [ 314 | [ 315 | 1 316 | ], 317 | { 318 | "a": "a" 319 | } 320 | ] 321 | ], 322 | [ 323 | "Float literal", 324 | [ 325 | ".3", 326 | "6.0", 327 | "-4.001", 328 | "3.14", 329 | "0.1" 330 | ], 331 | [ 332 | [ 333 | 1 334 | ], 335 | { 336 | "a": "a" 337 | } 338 | ] 339 | ], 340 | [ 341 | "Boolean literal", 342 | [ 343 | "true", 344 | "false" 345 | ], 346 | [ 347 | [ 348 | 1 349 | ], 350 | { 351 | "a": "a" 352 | } 353 | ] 354 | ], 355 | [ 356 | "Double quote String literal", 357 | [ 358 | "\"true\"", 359 | "\"false\"", 360 | "\"foo\"", 361 | "[\"ba'r\"]" 362 | ], 363 | [ 364 | [ 365 | 1 366 | ], 367 | { 368 | "a": "a" 369 | } 370 | ] 371 | ], 372 | [ 373 | "length", 374 | [ 375 | "[] | length", 376 | "length" 377 | ], 378 | [ 379 | [], 380 | [ 381 | 1 382 | ], 383 | [ 384 | 3, 385 | [ 386 | 3 387 | ] 388 | ] 389 | ] 390 | ], 391 | [ 392 | "keys", 393 | [ 394 | "keys" 395 | ], 396 | [ 397 | {}, 398 | { 399 | "a": 3 400 | }, 401 | { 402 | "b": { 403 | "a": 3 404 | }, 405 | "a": null 406 | } 407 | ] 408 | ], 409 | [ 410 | "keys_unsorted", 411 | [ 412 | "keys_unsorted" 413 | ], 414 | [ 415 | {}, 416 | { 417 | "a": 3 418 | }, 419 | { 420 | "b": { 421 | "a": 3 422 | }, 423 | "a": null 424 | } 425 | ] 426 | ], 427 | ["to_entries", [ 428 | ". | to_entries" 429 | ], [ 430 | {"id": 0, "name": "Mary", "age": 22}, 431 | {"id": 1, "name": "Rupert", "age": 29}, 432 | {"id": 2, "name": "Jane", "age": 11}, 433 | {"id": 3, "name": "John", "age": 42} 434 | ]], 435 | ["from_entries", [ 436 | ". | from_entries" 437 | ], [ 438 | [{"key": "baz", "value": false}], 439 | [{"key": "baz", "value": false}, {"key": "foo", "value": "bar"}] 440 | ]], 441 | ["reverse", [". | reverse"], [ 442 | [1, 2, 3, 4], 443 | [3, 1, 2], 444 | [3, "foo", 2, false, "bar", -3.14] 445 | ]], 446 | ["map", ["map(.+1 )", ". | map( {foo: .})"], [ 447 | [1, 2, 3, 4], 448 | [4, 2] 449 | ]], 450 | ["map_values", ["map_values(.+1 )", ". | map_values( {foo: .})"], [ 451 | {"a": 4, "b": 2}, 452 | {"a": 4, "b": -1, "c": 0} 453 | ]], 454 | ["with_entries", [ 455 | "with_entries({key: .key, value: (2 * .value)})", 456 | "with_entries({key: \"a\", value: (2 * .value)})" 457 | ], [ 458 | {"foo": 2, "bar": -1}, 459 | {"a": 2, "_": -1, "??": 0} 460 | ]], 461 | ["tonumber", ["tonumber"], [1, -1, "1", "-1", "1.23", "-.1"]], 462 | ["tostring", ["tostring"], [1, -1, "1", "-1", "1.23", "-.1", [1], {"a": 2}]], 463 | ["sort", ["sort", "[4, 5, 6] | sort"], [[1, -1], ["1", "-1", "1.23", "-.1"]]], 464 | ["sort_by", ["sort_by(-.)", "sort_by(1 + .)", "sort_by(1)"], [[1, -1], [4.12, 42.3, -342, 0, 32, 1, 2, 3, 6, 6]]], 465 | ["join", ["join(\", \")", "join(\"\")", "join(.[0])"], [["a","b,c,d","e"], ["foo"], [], ["foo", "bar"]]], 466 | ["Additive inverse", ["-(1 + 3)", "-(-1)", ".a | -(.b)", "[--1]"], [ 467 | {"a": {"b": 3}}, 468 | {"a": {"b": -3.1}} 469 | ]] 470 | ], 471 | "tests_jq_web": [ 472 | [ 473 | "Array Construction", 474 | [ 475 | "[]", 476 | "[4]" 477 | ], 478 | [ 479 | [ 480 | 1 481 | ], 482 | { 483 | "a": "a" 484 | } 485 | ] 486 | ], 487 | [ 488 | "Array/Object Value Iterator", 489 | [ 490 | ".[]", 491 | ".[ ]" 492 | ], 493 | [ 494 | [ 495 | 1, 496 | -1 497 | ], 498 | [ 499 | "foo" 500 | ], 501 | { 502 | "foo": 1, 503 | "bar": -5.3 504 | }, 505 | { 506 | "foo": [] 507 | } 508 | ] 509 | ], 510 | [ 511 | "Array/Object Value Iterator 2", 512 | [ 513 | ".[\"foo\"][]", 514 | ".foo[]" 515 | ], 516 | [ 517 | { 518 | "foo": [ 519 | 3, 520 | 3 521 | ] 522 | } 523 | ] 524 | ], 525 | [ 526 | "Pipe", 527 | [ 528 | ".[] | .", ".[] | .name" 529 | ], 530 | [ 531 | [ 532 | {"name": "Mary", "age": 22}, 533 | {"name": "Rupert", "age": 29}, 534 | {"name": "Jane", "age": 11}, 535 | {"name": "John", "age": 42} 536 | ] 537 | ] 538 | ], 539 | ["Stream as object value", [ 540 | "{names: .[] | .name}", "{\"names\": .[] | .name, \"ages\": .[] | .age}", 541 | "{\"names\": .[] | .name, \"x\": 3}", 542 | "{\"names\": 5.4, \"x\": .[] | .age}", 543 | "{names: 5.4, ages: .[] | .age, ages2: .[] | .id}" 544 | ], [ 545 | [ 546 | {"id": 0, "name": "Mary", "age": 22}, 547 | {"id": 1, "name": "Rupert", "age": 29}, 548 | {"id": 2, "name": "Jane", "age": 11}, 549 | {"id": 3, "name": "John", "age": 42} 550 | ] 551 | ]], 552 | ["Array/String slice", [".[2:4]", ".[0:1]"], [ 553 | ["a","b","c","d","e"], 554 | [0, 3, "foo", -2, false, [true]], 555 | "Hello World!" 556 | ]] 557 | ] 558 | } 559 | -------------------------------------------------------------------------------- /src/travis_test.js: -------------------------------------------------------------------------------- 1 | import { test_with_jq_web } from './test_helpers.js' 2 | const { tests_jq_web, tests_node_jq } = require('json-loader!./tests.json') 3 | 4 | tests_jq_web.forEach(test_with_jq_web) 5 | tests_node_jq.forEach(test_with_jq_web) 6 | 7 | 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var libraryName = 'jq-in-the-browser'; 4 | var outputFile = libraryName + '.js'; 5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 6 | 7 | 8 | module.exports = { 9 | entry: './src/jq.js', 10 | entry: __dirname + '/src/index.js', 11 | devtool: 'source-map', 12 | output: { 13 | path: __dirname + '/lib', 14 | filename: outputFile, 15 | library: libraryName, 16 | libraryTarget: 'umd', 17 | umdNamedDefine: true 18 | }, 19 | target: 'node', 20 | plugins: [new UglifyJsPlugin()], 21 | module: { 22 | loaders: [ 23 | { 24 | test: /\.pegjs$/, 25 | loader: 'pegjs-loader?trace=false&cache=true' 26 | }, 27 | { 28 | test: /(\.jsx|\.js)$/, 29 | loader: 'babel-loader', 30 | exclude: /(node_modules|bower_components)/ 31 | }, 32 | ] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /webpack.test.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './src/jq.js', 3 | target: 'node', 4 | output: { 5 | filename: './lib/jq.js' 6 | }, 7 | module: { 8 | loaders: [ 9 | { 10 | test: /\.pegjs$/, 11 | loader: 'pegjs-loader?trace=false&cache=true' 12 | } 13 | ] 14 | } 15 | }; 16 | --------------------------------------------------------------------------------