json2 0.1.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/.coco.yml +3 -0
- data/.gitignore +15 -0
- data/.reek +7 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +154 -0
- data/Rakefile +7 -0
- data/bin/json2 +15 -0
- data/json2.gemspec +28 -0
- data/lib/json2.rb +14 -0
- data/lib/json2/body.rb +53 -0
- data/lib/json2/csv_with_header.rb +86 -0
- data/lib/json2/csv_without_header.rb +70 -0
- data/lib/json2/header.rb +36 -0
- data/lib/json2/option.rb +75 -0
- data/lib/json2/symbol_respond_to.rb +26 -0
- data/lib/json2/version.rb +3 -0
- data/spec/body_spec.rb +22 -0
- data/spec/csv_with_header_spec.rb +43 -0
- data/spec/csv_without_header_spec.rb +22 -0
- data/spec/data/README +4 -0
- data/spec/data/colors-array.json +30 -0
- data/spec/data/colors-object.json +33 -0
- data/spec/data/colors2.json +12 -0
- data/spec/data/colors3.json +9 -0
- data/spec/data/github-user.json +43 -0
- data/spec/data/google-maps-example.json +32 -0
- data/spec/data/product2.json +32 -0
- data/spec/data/products.json +26 -0
- data/spec/data/users.json +1 -0
- data/spec/data/vote-loi-renseignement.json +5 -0
- data/spec/data/votes.json +15 -0
- data/spec/header_spec.rb +18 -0
- data/spec/integration/colors_array_spec.rb +25 -0
- data/spec/integration/colors_object_spec.rb +24 -0
- data/spec/integration/path_selection_spec.rb +45 -0
- data/spec/integration/votes_spec.rb +21 -0
- data/spec/json2_spec.rb +7 -0
- data/spec/option_spec.rb +67 -0
- data/spec/spec_helper.rb +9 -0
- metadata +197 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
module Json2
|
2
|
+
|
3
|
+
# Turn a Json input into a Csv output without header.
|
4
|
+
class CsvWithoutHeader
|
5
|
+
|
6
|
+
# Creates a new CsvWithoutHeader instance.
|
7
|
+
#
|
8
|
+
# input - A Hash representing a Json file. This is typically
|
9
|
+
# obtained with JSON.parse.
|
10
|
+
def initialize(input)
|
11
|
+
@input = input
|
12
|
+
@names_stack = []
|
13
|
+
@output = ''
|
14
|
+
process_input
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the Csv.
|
18
|
+
#
|
19
|
+
# Returns the whole document as a single String.
|
20
|
+
attr_reader :output
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def process_input
|
25
|
+
process_keys(@input)
|
26
|
+
end
|
27
|
+
|
28
|
+
def process_keys(object)
|
29
|
+
if object.respond_to?(:each_key)
|
30
|
+
object.each_key do |key|
|
31
|
+
@names_stack.push(key)
|
32
|
+
process_key(object[key])
|
33
|
+
@names_stack.pop
|
34
|
+
end
|
35
|
+
else
|
36
|
+
record_line(object)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_key(object)
|
41
|
+
case object
|
42
|
+
when ~:each_key then process_keys(object)
|
43
|
+
when ~:at then process_array(object)
|
44
|
+
else
|
45
|
+
error(99, 'Error, try without using --without-header')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_array(object)
|
50
|
+
if object.empty?
|
51
|
+
record_line('')
|
52
|
+
else
|
53
|
+
object.each {|element| process_keys(element) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def record_line(element)
|
58
|
+
@names_stack.push(element)
|
59
|
+
@output += @names_stack.join(',') + "\n"
|
60
|
+
@names_stack.pop
|
61
|
+
end
|
62
|
+
|
63
|
+
def error(code, message)
|
64
|
+
warn(message)
|
65
|
+
exit(code)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/json2/header.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Json2
|
2
|
+
|
3
|
+
# Build a csv header.
|
4
|
+
class Header
|
5
|
+
|
6
|
+
# Get the header of a Csv file.
|
7
|
+
#
|
8
|
+
# For a description of the parameter(s) see Header#initialize.
|
9
|
+
#
|
10
|
+
# Returns the String header, that is a single line with comma
|
11
|
+
# separated column's name.
|
12
|
+
def self.get(keys)
|
13
|
+
new(keys).get
|
14
|
+
end
|
15
|
+
|
16
|
+
# Creates a new Header instance.
|
17
|
+
#
|
18
|
+
# keys - A Hash of String names of variable/column.
|
19
|
+
def initialize(keys)
|
20
|
+
@header = []
|
21
|
+
@keys = keys
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get the header of a Csv file. See also Header.get.
|
25
|
+
#
|
26
|
+
# Returns the String header.
|
27
|
+
def get
|
28
|
+
@keys.each do |key|
|
29
|
+
short_name = key.split('.').last
|
30
|
+
@header << (@header.include?(short_name) ? key : short_name)
|
31
|
+
end
|
32
|
+
@header.join(',') + "\n"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/json2/option.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module Json2
|
2
|
+
|
3
|
+
# Process command line switches.
|
4
|
+
#
|
5
|
+
# The keys you are going to use:
|
6
|
+
#
|
7
|
+
# :without_header - Boolean, if true the user want to parse the Json
|
8
|
+
# file as if it has no header data.
|
9
|
+
# :with_path - Boolean, if true the user wants to extract only
|
10
|
+
# a particular path in the Json file.
|
11
|
+
# :path - This is the String path to extract if :with_path
|
12
|
+
# is true.
|
13
|
+
#
|
14
|
+
# Examples
|
15
|
+
#
|
16
|
+
# opt = Option.new
|
17
|
+
# if opt[:with_path]
|
18
|
+
# puts "Extracting #{opt[:path]}…"
|
19
|
+
# # Do the job.
|
20
|
+
# end
|
21
|
+
class Option
|
22
|
+
|
23
|
+
# Creates a new Option instance.
|
24
|
+
def initialize
|
25
|
+
@options = { without_header: false, with_path: false }
|
26
|
+
|
27
|
+
optparse = OptionParser.new {|opts| parse(opts) }
|
28
|
+
|
29
|
+
begin
|
30
|
+
optparse.parse!
|
31
|
+
rescue OptionParser::InvalidOption => exception
|
32
|
+
puts exception.to_s
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
print_version if @options[:version]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get an option.
|
40
|
+
#
|
41
|
+
# key - The Symbol name of the option to get.
|
42
|
+
#
|
43
|
+
# Returns Any value corresponding of the key, or nil if the key
|
44
|
+
# doesn't exists.
|
45
|
+
def [](key)
|
46
|
+
@options[key]
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(opts)
|
50
|
+
opts.on('-w', '--without-header', 'Output csv without a header') do
|
51
|
+
@options[:without_header] = true
|
52
|
+
end
|
53
|
+
opts.on('-p', '--path PATH', 'Extract only this path') do |arg|
|
54
|
+
@options[:with_path] = true
|
55
|
+
@options[:path] = arg
|
56
|
+
end
|
57
|
+
opts.on('-v', '--version', 'Print version number and exit') do
|
58
|
+
@options[:version] = true
|
59
|
+
end
|
60
|
+
opts.on('-h', '--help', 'Display this screen') do
|
61
|
+
puts opts
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def print_version
|
69
|
+
puts "Json2 version #{VERSION}"
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Simplify the use of duck typing in case/when structure.
|
2
|
+
#
|
3
|
+
# Examples
|
4
|
+
#
|
5
|
+
# a = []
|
6
|
+
# b = ~:each
|
7
|
+
# b.call(a) #=> true
|
8
|
+
#
|
9
|
+
# # How is this simplification!? Are you kiding me?
|
10
|
+
# # We can use it like this:
|
11
|
+
#
|
12
|
+
# case object
|
13
|
+
# when ~:each then …
|
14
|
+
# when ~:keys then …
|
15
|
+
# else …
|
16
|
+
# end
|
17
|
+
module SymbolRespondTo
|
18
|
+
|
19
|
+
# Returns Boolean.
|
20
|
+
def ~@
|
21
|
+
->(object) { object.respond_to?(self) }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
Symbol.send(:include, SymbolRespondTo)
|
data/spec/body_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Json2
|
4
|
+
|
5
|
+
describe Body do
|
6
|
+
describe '.get' do
|
7
|
+
it 'builds a body' do
|
8
|
+
nodes = { "color": ["red", "green", "blue"],
|
9
|
+
"value": ["#f00", "#0f0", "#00f"] }
|
10
|
+
body = Body.get(nodes, nodes.keys, 3)
|
11
|
+
expect(body).to eq fake_body
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def fake_body
|
17
|
+
<<END
|
18
|
+
red,#f00
|
19
|
+
green,#0f0
|
20
|
+
blue,#00f
|
21
|
+
END
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Json2
|
4
|
+
|
5
|
+
describe CsvWithHeader do
|
6
|
+
|
7
|
+
describe 'when the header is unknown' do
|
8
|
+
|
9
|
+
it 'exits' do
|
10
|
+
expect {
|
11
|
+
CsvWithHeader.new(input)
|
12
|
+
}.to raise_error(SystemExit)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'exits with right code' do
|
16
|
+
begin
|
17
|
+
CsvWithHeader.new(input)
|
18
|
+
rescue SystemExit => e
|
19
|
+
expect(e.status).to eq 98
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def input
|
26
|
+
{
|
27
|
+
"Nom du parti" => {
|
28
|
+
"Abstention" => [
|
29
|
+
"Foo1 Bar1"
|
30
|
+
],
|
31
|
+
"Non-votant" => [],
|
32
|
+
"Contre" => [
|
33
|
+
"Foo2 Bar2",
|
34
|
+
"Foo3 Bar3"
|
35
|
+
],
|
36
|
+
"Pour" => [
|
37
|
+
"Foo4 Bar4"
|
38
|
+
]
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Json2
|
4
|
+
|
5
|
+
describe CsvWithoutHeader do
|
6
|
+
|
7
|
+
describe 'When the header is known' do
|
8
|
+
|
9
|
+
it 'exits' do
|
10
|
+
expect { CsvWithoutHeader.new(input) }.to raise_error(SystemExit)
|
11
|
+
end
|
12
|
+
|
13
|
+
def input
|
14
|
+
{
|
15
|
+
"red" => "#f00",
|
16
|
+
"green" => "#0f0",
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/spec/data/README
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"color": "red",
|
4
|
+
"value": "#f00"
|
5
|
+
},
|
6
|
+
{
|
7
|
+
"color": "green",
|
8
|
+
"value": "#0f0"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"color": "blue",
|
12
|
+
"value": "#00f"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"color": "cyan",
|
16
|
+
"value": "#0ff"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"color": "magenta",
|
20
|
+
"value": "#f0f"
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"color": "yellow",
|
24
|
+
"value": "#ff0"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"color": "black",
|
28
|
+
"value": "#000"
|
29
|
+
}
|
30
|
+
]
|
@@ -0,0 +1,33 @@
|
|
1
|
+
{
|
2
|
+
"colors":
|
3
|
+
[
|
4
|
+
{
|
5
|
+
"color": "red",
|
6
|
+
"value": "#f00"
|
7
|
+
},
|
8
|
+
{
|
9
|
+
"color": "green",
|
10
|
+
"value": "#0f0"
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"color": "blue",
|
14
|
+
"value": "#00f"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"color": "cyan",
|
18
|
+
"value": "#0ff"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"color": "magenta",
|
22
|
+
"value": "#f0f"
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"color": "yellow",
|
26
|
+
"value": "#ff0"
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"color": "black",
|
30
|
+
"value": "#000"
|
31
|
+
}
|
32
|
+
]
|
33
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
{
|
2
|
+
"login": "lkdjiin",
|
3
|
+
"id": 498017,
|
4
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/498017?v=3",
|
5
|
+
"gravatar_id": "",
|
6
|
+
"url": "https://api.github.com/users/lkdjiin",
|
7
|
+
"html_url": "https://github.com/lkdjiin",
|
8
|
+
"followers_url": "https://api.github.com/users/lkdjiin/followers",
|
9
|
+
"following_url": "https://api.github.com/users/lkdjiin/following{/other_user}",
|
10
|
+
"gists_url": "https://api.github.com/users/lkdjiin/gists{/gist_id}",
|
11
|
+
"starred_url": "https://api.github.com/users/lkdjiin/starred{/owner}{/repo}",
|
12
|
+
"subscriptions_url": "https://api.github.com/users/lkdjiin/subscriptions",
|
13
|
+
"organizations_url": "https://api.github.com/users/lkdjiin/orgs",
|
14
|
+
"repos_url": "https://api.github.com/users/lkdjiin/repos",
|
15
|
+
"events_url": "https://api.github.com/users/lkdjiin/events{/privacy}",
|
16
|
+
"received_events_url": "https://api.github.com/users/lkdjiin/received_events",
|
17
|
+
"type": "User",
|
18
|
+
"site_admin": false,
|
19
|
+
"name": "Xavier Nayrac",
|
20
|
+
"company": "",
|
21
|
+
"blog": "http://lkdjiin.github.io",
|
22
|
+
"location": "reims france",
|
23
|
+
"email": "xavier.nayrac@gmail.com",
|
24
|
+
"hireable": true,
|
25
|
+
"bio": null,
|
26
|
+
"public_repos": 70,
|
27
|
+
"public_gists": 2,
|
28
|
+
"followers": 14,
|
29
|
+
"following": 0,
|
30
|
+
"created_at": "2010-11-26T15:35:49Z",
|
31
|
+
"updated_at": "2015-05-15T12:50:44Z",
|
32
|
+
"private_gists": 0,
|
33
|
+
"total_private_repos": 0,
|
34
|
+
"owned_private_repos": 0,
|
35
|
+
"disk_usage": 192692,
|
36
|
+
"collaborators": 0,
|
37
|
+
"plan": {
|
38
|
+
"name": "free",
|
39
|
+
"space": 976562499,
|
40
|
+
"collaborators": 0,
|
41
|
+
"private_repos": 0
|
42
|
+
}
|
43
|
+
}
|