Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
textgoeshere committed Jan 13, 2013
0 parents commit fe9ac55
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
node_modules/
tmp/*
.rvmrc
7 changes: 7 additions & 0 deletions .npmignore
@@ -0,0 +1,7 @@
/node_modules/
.travis.yml
/features/
/tmp/
Gemfile
Gemfile.lock
Guardfile
8 changes: 8 additions & 0 deletions Gemfile
@@ -0,0 +1,8 @@
source :rubygems

gem "cucumber"
gem "aruba"
gem "rspec"
gem "guard"
gem "guard-cucumber"
gem "rb-inotify", "~> 0.8.8"
59 changes: 59 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,59 @@
GEM
remote: http://rubygems.org/
specs:
aruba (0.5.1)
childprocess (~> 0.3.6)
cucumber (>= 1.1.1)
rspec-expectations (>= 2.7.0)
builder (3.1.4)
childprocess (0.3.6)
ffi (~> 1.0, >= 1.0.6)
coderay (1.0.8)
cucumber (1.2.1)
builder (>= 2.1.2)
diff-lcs (>= 1.1.3)
gherkin (~> 2.11.0)
json (>= 1.4.6)
diff-lcs (1.1.3)
ffi (1.3.1)
gherkin (2.11.5)
json (>= 1.4.6)
guard (1.6.1)
listen (>= 0.6.0)
lumberjack (>= 1.0.2)
pry (>= 0.9.10)
thor (>= 0.14.6)
guard-cucumber (1.3.1)
cucumber (>= 1.2.0)
guard (>= 1.1.0)
json (1.7.6)
listen (0.7.2)
lumberjack (1.0.2)
method_source (0.8.1)
pry (0.9.10)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.3.1)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.12.0)
rspec-core (2.12.2)
rspec-expectations (2.12.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.12.1)
slop (3.3.3)
thor (0.16.0)

PLATFORMS
ruby

DEPENDENCIES
aruba
cucumber
guard
guard-cucumber
rb-inotify (~> 0.8.8)
rspec
10 changes: 10 additions & 0 deletions Guardfile
@@ -0,0 +1,10 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard 'cucumber' do
watch(%r{^features/.+\.feature$})
watch(%r{^features/support/.+$}) { 'features' }
watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
watch(%r{^lib/*.js}) { 'features' }
watch(%r{^bin/*}) { 'features' }
end
93 changes: 93 additions & 0 deletions README.md
@@ -0,0 +1,93 @@
# jsong

Filter JSON with regexen and display the complete path of keys to the results. Streaming-friendly.

When you know roughly what you need, but you can't remember the path to get there.

Given some nested JSON, `my.json`:

{
"foo": {
"bar": {
"zip": "val1",
"zap": "val2",
"arr": [1, 2, 3, 4, 5, 6]
}
},
"quux": {
"zip": "val"
}
}

Filter by regex matching key with `-k`:

$ cat my.json | jsong -k 'z\wp'

foo.bar.zip: val1
foo.bar.zap: val2
quux.zip: val

It will return everything nested below a matching key:

$ cat my.json | jsong -k fo

foo.bar.zip: val1
foo.bar.zap: val2

Filter by regex matching value with `-v`:

cat my.json | jsong -v 'val\d'

foo.bar.zip: val1
foo.bar.zap: val2

Filter by regex matching key or value with `-a`:

cat my.json | jsong -a '[\w]{4}'

foo.bar.zip: val1
foo.bar.zap: val2
quux.zip: val

It will show paths including array indices:

cat my.json | jsong -v 5

foo.bar.arr[4]: 5

## Requirements

* `nodejs`
* `npm`

## Installation

It usually makes sense to install `jsong` globally so all users can use it:

$ npm install -g jsong

## Usage

`jsong [filename] [options]`

If no filename is given, `jsong` reads from `STDIN`.

Filtering is disjunctive - the result will be displayed if at least one of the filters match.

Depending on the vagaries of your shell, you may have to single-quote your regexen.

### Options

* `-k`, `--key`: optional regex, default null

Display result line if any of the keys match the regex.

* `-v`, `--value`: optional regex, default null

Display result line if the value matches the regex.

* `-a`, `--any`: optional regex, default null

Display path of keys/array indices to values if any of the keys or the value match the regex.

* `-h`, `--help`: display help
4 changes: 4 additions & 0 deletions bin/jsong
@@ -0,0 +1,4 @@
#!/usr/bin/env node

var jsong = require("../lib/jsong")
jsong.cli(process.argv);
5 changes: 5 additions & 0 deletions features/cli.feature
@@ -0,0 +1,5 @@
Feature: cli features

Scenario: get help
When I run `jsong -h`
Then jsong fails with a help message
64 changes: 64 additions & 0 deletions features/filter-json.feature
@@ -0,0 +1,64 @@
Feature: filter JSON

Background:
Given a file named "my.json" with:
"""
{
"foo": {
"bar": {
"zip": "val1",
"zap": "val2",
"arr": [1, 2, 3, 4, 5, 6]
}
},
"quux": {
"zip": "val"
}
}
"""

Scenario: filter by key regex
When I run `jsong my.json -k 'z\wp'`
Then the output should contain exactly:
"""
foo.bar.zip: val1
foo.bar.zap: val2
quux.zip: val
"""

Scenario: filter by key regex shows nested objects
When I run `jsong my.json -k foo`
Then the output should contain:
"""
foo.bar.zip: val1
foo.bar.zap: val2
"""

Scenario: filter by value regex
When I run `jsong my.json -v 'val\d'`
Then the output should contain exactly:
"""
foo.bar.zip: val1
foo.bar.zap: val2
"""

Scenario: filter by regex matching either key or value
When I run `jsong my.json -a '[\w]{4}'`
Then the output should contain exactly:
"""
foo.bar.zip: val1
foo.bar.zap: val2
quux.zip: val
"""

Scenario: display array indices
When I run `jsong my.json -v 5`
Then the output should contain exactly:
"""
foo.bar.arr[4]: 5
"""

3 changes: 3 additions & 0 deletions features/step_definitions/cli-steps.rb
@@ -0,0 +1,3 @@
Then /^jsong fails with a help message$/ do
assert_failing_with "Usage:"
end
2 changes: 2 additions & 0 deletions features/support/env.rb
@@ -0,0 +1,2 @@
require 'aruba/cucumber'

119 changes: 119 additions & 0 deletions lib/jsong.js
@@ -0,0 +1,119 @@
'use strict';

var fs = require("fs"),
streamin = require("streamin"),
nopt = require("nopt"),
clarinet = require("clarinet");

var filter = function(options) {
var parser = clarinet.createStream(),
stack = [],
key_match = false,
value_match = false,
any_match = false;

var incrementArrayIndex = function() {
if(typeof stack[stack.length - 1] === 'number') { // it's an array index
stack[stack.length - 1] += 1;
};
};

var print = function(val) {
var str = "";
stack.forEach(function(key) {
if(typeof key === 'number') {
str += "[" + key + "]";
} else {
if (str == "") {
str = key;
} else {
str += "." + key;
};
};
});
str = str + ": " + val;
console.log(str);
};

var updateKeyMatch = function(key) {
if(stack.length <= key_match) {
key_match = false;
};

if(!key_match) {
key_match = ((options.key && key.match(options.key)) ||
(options.any && key.match(options.any))) ?
stack.length : false;
};
};

parser.onopenobject = function(key) {
incrementArrayIndex();
stack.push(key);
updateKeyMatch(key);
};

parser.oncloseobject = function() {
stack.pop();
};

parser.onopenarray = function() {
incrementArrayIndex();
// the first element in the array will increment this to 0 (i.e. its index)
stack.push(-1);
};

parser.onclosearray = function() {
stack.pop();
};

parser.onkey = function(key) {
incrementArrayIndex();

stack.pop();
stack.push(key);

updateKeyMatch(key);
};

parser.onvalue = function(v) {
incrementArrayIndex();

value_match = ((options.value && v.toString().match(options.value)) ||
(options.any && v.toString().match(options.any))) ?
true : false;

if(!options.filter || (key_match || value_match)) {
print(v);
};
};

streamin(options.input).pipe(parser);
};

// exports

exports.cli = function(args) {
var options = nopt({
"key": String,
"value": String,
"any": String,
"help": String
}, {}, args);

if(options.help) {
console.log("Usage: jsong [filename] [[-k key regexp] [-v value regex] [-a any regex]]");
console.log("If [filename] is not supplied, STDIN is used.");
process.exit(1);
}

if (options.argv.remain.length == 1) { // filename given
options.input = options.argv.remain.shift();
} else { // use STDIN
options.input = process.stdin;
}
if(options.key || options.value || options.any) {
options.filter = true;
}
filter(options);
};

0 comments on commit fe9ac55

Please sign in to comment.