json2 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|