json_q_l 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +5 -0
- data/README.md +55 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/jql +16 -0
- data/json_q_l.gemspec +23 -0
- data/lib/json_q_l/condition.rb +31 -0
- data/lib/json_q_l/executor.rb +37 -0
- data/lib/json_q_l/parser.rb +65 -0
- data/lib/json_q_l/tolkienizer.rb +21 -0
- data/lib/json_q_l/version.rb +3 -0
- data/lib/json_q_l.rb +9 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2148237db30bf43925f46e545bf042957160a0cb
|
4
|
+
data.tar.gz: a07301fb48d08b24ac65dc60c0b0731911ffcb82
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d106906db104b915f4c5ebf54b04eeb800a18313353b83c3545af010d685132e58c7d5283afcef35bcd5119b4a7bc026163a8cd05eec5a1693b0c120bf361b2f
|
7
|
+
data.tar.gz: bccecddd43c53ba741f2e7ef3e462f9c899dcad5123fcab3c4b0d0a19852e9d13e151f95c808145e23176b14fecbd569b1b0c7e6b3ea3ab6297c77b5b9a19f44
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# JsonQL
|
2
|
+
|
3
|
+
jql is a small query interface for JSON that supports SELECTs with multiple conditions.
|
4
|
+
|
5
|
+
## Use
|
6
|
+
|
7
|
+
To use jql add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'json_q_l'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install json_q_l
|
20
|
+
|
21
|
+
After bundling from the command line call jql and pass a JSON file formatted like this:
|
22
|
+
```json
|
23
|
+
{
|
24
|
+
"events": [
|
25
|
+
{
|
26
|
+
"occasion": "Birthday party",
|
27
|
+
"invited_count": 120,
|
28
|
+
"year": 2015,
|
29
|
+
"month": 3,
|
30
|
+
"day": 14
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"occasion": "Press release",
|
34
|
+
"invited_count": 64,
|
35
|
+
"year": 2015,
|
36
|
+
"month": 6,
|
37
|
+
"day": 7,
|
38
|
+
"cancelled": true
|
39
|
+
}
|
40
|
+
]
|
41
|
+
}
|
42
|
+
```
|
43
|
+
Where "events" represents a table name and each element of the array a single column.
|
44
|
+
|
45
|
+
|
46
|
+
```
|
47
|
+
jql events.json
|
48
|
+
```
|
49
|
+
|
50
|
+
Then try executing a command
|
51
|
+
```
|
52
|
+
jql~> SELECT * FROM events WHERE year = 2015 AND occasion = 'Birthday party';
|
53
|
+
```
|
54
|
+
|
55
|
+
Have fun!
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "json_q_l"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/jql
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json_q_l'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
file_path = File.dirname(__FILE__) + "/../" + ARGV.first
|
7
|
+
puts file_path
|
8
|
+
executor = JsonQL::Executor.new(dataset: JSON.parse(File.read(file_path)))
|
9
|
+
|
10
|
+
while line = Readline.readline('jql~> ', true)
|
11
|
+
begin
|
12
|
+
puts executor.execute(line)
|
13
|
+
rescue
|
14
|
+
puts "Hmm something went wrong"
|
15
|
+
end
|
16
|
+
end
|
data/json_q_l.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'json_q_l/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "json_q_l"
|
8
|
+
spec.version = JsonQL::VERSION
|
9
|
+
spec.authors = ["Matt Eddy"]
|
10
|
+
spec.email = ["matteddy1@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "A SQL like JSON query language"
|
13
|
+
spec.description = "A SQL like JSON query language"
|
14
|
+
spec.homepage = "http://gfycat.com/BeautifulWholeAtlanticblackgoby"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = ["jql"]
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module JsonQL
|
2
|
+
class Condition < Proc
|
3
|
+
def self.create_from_tokens(tokens)
|
4
|
+
instance = new { |table|
|
5
|
+
set_variables = (available_keys(table) & tokens).map do |required_datum|
|
6
|
+
if table[required_datum]
|
7
|
+
required_datum + convert_type_if_needed(table[required_datum])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
eval((set_variables + tokens).join(" "))
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
private
|
17
|
+
|
18
|
+
def convert_type_if_needed(value)
|
19
|
+
if value.is_a?(Numeric)
|
20
|
+
" = #{value};"
|
21
|
+
else
|
22
|
+
" = '#{value}';"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def available_keys(table)
|
27
|
+
table.map { |key, value| key }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module JsonQL
|
2
|
+
class Executor
|
3
|
+
def initialize(dataset:)
|
4
|
+
@dataset = dataset
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute(query)
|
8
|
+
@parser = Parser.new(query)
|
9
|
+
|
10
|
+
selected_columns
|
11
|
+
end
|
12
|
+
|
13
|
+
def selected_columns
|
14
|
+
if filtered_table.empty?
|
15
|
+
"Nothing matched query"
|
16
|
+
else
|
17
|
+
parser.column_select.call(filtered_table)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def filtered_table
|
22
|
+
if parser.where_index
|
23
|
+
tables.map do |table|
|
24
|
+
table if parser.filter_conditions_from_tokens.call(table)
|
25
|
+
end.compact
|
26
|
+
else
|
27
|
+
tables
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def tables
|
32
|
+
parser.table_select.call(dataset)
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :dataset, :parser
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module JsonQL
|
2
|
+
class Parser
|
3
|
+
def self.run(query)
|
4
|
+
new(query)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(query)
|
8
|
+
@tokens = Tolkienizer.run(query)
|
9
|
+
end
|
10
|
+
|
11
|
+
def column_select
|
12
|
+
Proc.new { |table|
|
13
|
+
selected_columns.call(table).map do |column|
|
14
|
+
{
|
15
|
+
column => table.map { |row| row[column] }
|
16
|
+
}
|
17
|
+
end.flatten
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def table_select
|
22
|
+
Proc.new { |json_object|
|
23
|
+
json_object[tokens.slice(from_index + 1)]
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def where_index
|
28
|
+
tokens.index('WHERE')
|
29
|
+
end
|
30
|
+
|
31
|
+
def filter_conditions_from_tokens
|
32
|
+
Condition.create_from_tokens(condition_tokens)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def selected_columns
|
38
|
+
Proc.new { |table|
|
39
|
+
if column_tokens == ["*"]
|
40
|
+
table.first.map { |key, value| key }
|
41
|
+
else
|
42
|
+
column_tokens
|
43
|
+
end
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def condition_tokens
|
48
|
+
tokens.slice(where_index + 1, tokens.length)
|
49
|
+
end
|
50
|
+
|
51
|
+
def column_tokens
|
52
|
+
tokens.slice(select_index + 1, from_index - 1)
|
53
|
+
end
|
54
|
+
|
55
|
+
def select_index
|
56
|
+
tokens.index('SELECT')
|
57
|
+
end
|
58
|
+
|
59
|
+
def from_index
|
60
|
+
tokens.index('FROM')
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :tokens
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module JsonQL
|
2
|
+
class Tolkienizer
|
3
|
+
# the lord of the things
|
4
|
+
|
5
|
+
TOKEN_LOOKUP = {
|
6
|
+
"=" => "==",
|
7
|
+
"OR" => "||",
|
8
|
+
"AND" => "&&"
|
9
|
+
}
|
10
|
+
|
11
|
+
def self.run(query)
|
12
|
+
query.split(/\s(?=(?:[^']|'[^']*')*$)/).map do |token|
|
13
|
+
if TOKEN_LOOKUP[token.upcase]
|
14
|
+
TOKEN_LOOKUP[token.upcase]
|
15
|
+
else
|
16
|
+
token
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/json_q_l.rb
ADDED
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json_q_l
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Eddy
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: A SQL like JSON query language
|
42
|
+
email:
|
43
|
+
- matteddy1@gmail.com
|
44
|
+
executables:
|
45
|
+
- jql
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- ".rspec"
|
51
|
+
- ".travis.yml"
|
52
|
+
- Gemfile
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- bin/console
|
56
|
+
- bin/setup
|
57
|
+
- exe/jql
|
58
|
+
- json_q_l.gemspec
|
59
|
+
- lib/json_q_l.rb
|
60
|
+
- lib/json_q_l/condition.rb
|
61
|
+
- lib/json_q_l/executor.rb
|
62
|
+
- lib/json_q_l/parser.rb
|
63
|
+
- lib/json_q_l/tolkienizer.rb
|
64
|
+
- lib/json_q_l/version.rb
|
65
|
+
homepage: http://gfycat.com/BeautifulWholeAtlanticblackgoby
|
66
|
+
licenses: []
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.4.6
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: A SQL like JSON query language
|
88
|
+
test_files: []
|