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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 51f0cb006cd0e9d7641a2beb85a77ff46258f693
4
+ data.tar.gz: 84d96f803db004b7b12b2eb313bd4fca9de1fb9f
5
+ SHA512:
6
+ metadata.gz: 7317aa9c145dd7fcc928e5223cc26bcdf3b9f66ed7d3d2076f44336a54806b437c38a3fa2360185132c01b2cff11ce06b46a76ce482bc8e768e8d0be3426cda1
7
+ data.tar.gz: 0f5918f0343a5b281ea5bac2240a6194a201ee725162040f86fa42e7a02555f05303fc7033d12c81e765a8af20e49ec1ae48ef5debd041c68dd32b8a79999571
@@ -0,0 +1,3 @@
1
+ :excludes:
2
+ - spec
3
+ - lib/json2/version.rb
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ TODO
data/.reek ADDED
@@ -0,0 +1,7 @@
1
+ UncommunicativeModuleName:
2
+ enabled: false
3
+
4
+ TooManyStatements:
5
+ exclude:
6
+ - "Json2::Option#parse" # OptionParser is not «short method friendly».
7
+ - initialize
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order=random
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
@@ -0,0 +1,15 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [Unreleased] - unreleased
6
+
7
+ ## [0.1.0] - 2015-07-10
8
+ ### Added
9
+ - Basic Json to Csv behavior, based on several Json examples.
10
+ - Works well with other command line tools by using a file as input or by
11
+ listening to STDIN.
12
+ - Option `--without-header` to parse the Json when it doesn't seems to have
13
+ *heading information* inside itself.
14
+ - Option `--path` to extract a selection of the Json file.
15
+ - Common switches `--version` and `--help`.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in json2.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 lkdjiin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,154 @@
1
+ # Json2
2
+
3
+ Json2 transforms a Json file into a Csv one.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'json2'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install json2
20
+
21
+ ## Usage
22
+
23
+ ### Example 1
24
+
25
+ Given the following Json file:
26
+
27
+ ```json
28
+ [
29
+ {
30
+ "color": "red",
31
+ "value": "#f00"
32
+ },
33
+ {
34
+ "color": "green",
35
+ "value": "#0f0"
36
+ },
37
+ {
38
+ "color": "blue",
39
+ "value": "#00f"
40
+ }
41
+ ]
42
+ ```
43
+
44
+ You could obtain a Csv with a header like that:
45
+
46
+ $ json2 colors-array.json
47
+ color,value
48
+ red,#f00
49
+ green,#0f0
50
+ blue,#00f
51
+
52
+ ### Example 2
53
+
54
+ With this simpler kind of Json:
55
+
56
+ ```json
57
+ {
58
+ "red":"#f00",
59
+ "green":"#0f0",
60
+ "blue":"#00f"
61
+ }
62
+ ```
63
+
64
+ Here is the resulting Csv:
65
+
66
+
67
+ $ json2 colors3.json
68
+ red,green,blue
69
+ #f00,#0f0,#00f
70
+
71
+ ### Example 3
72
+
73
+ Take a slightly more complicated Json file:
74
+
75
+ ```json
76
+ {
77
+ "id": "0001",
78
+ "type": "donut",
79
+ "batters":
80
+ {
81
+ "batter":
82
+ [
83
+ { "id": "1001", "type": "Regular" },
84
+ { "id": "1002", "type": "Chocolate" },
85
+ { "id": "1003", "type": "Blueberry" },
86
+ { "id": "1004", "type": "Devil's Food" }
87
+ ]
88
+ },
89
+ "topping":
90
+ [
91
+ { "id": "5001", "type": "None" },
92
+ { "id": "5002", "type": "Glazed" },
93
+ { "id": "5005", "type": "Sugar" }
94
+ ]
95
+ }
96
+ ```
97
+
98
+ Say you want to extract the *batter* stuff. Use the `--path` switch:
99
+
100
+ $ json2 --path='batters.batter' products.json
101
+ id,type
102
+ 1001,Regular
103
+ 1002,Chocolate
104
+ 1003,Blueberry
105
+ 1004,Devil's Food
106
+
107
+ ### Example 4
108
+
109
+ Json don't always map very well with Csv.
110
+ Sometimes there is simply no header information inside the Json:
111
+
112
+ ```json
113
+ {
114
+ "Nom du parti": {
115
+ "Abstention": [
116
+ "Jean"
117
+ ],
118
+ "Non-votant": [],
119
+ "Contre": [
120
+ "Alice",
121
+ "Georges"
122
+ ],
123
+ "Pour": [
124
+ "Julie"
125
+ ]
126
+ }
127
+ }
128
+ ```
129
+
130
+ In this case, you can use the `--without-header` switch:
131
+
132
+ $ json2 --without-header votes.json
133
+ Nom du parti,Abstention,Jean
134
+ Nom du parti,Non-votant,
135
+ Nom du parti,Contre,Alice
136
+ Nom du parti,Contre,Georges
137
+ Nom du parti,Pour,Julie
138
+
139
+ ## Contributing
140
+
141
+ 1. Fork it ( https://github.com/[my-github-username]/json2/fork )
142
+ 2. **Create your feature branch** (`git checkout -b my-new-feature`)
143
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
144
+ 4. Push to the branch (`git push origin my-new-feature`)
145
+ 5. Create a new Pull Request
146
+
147
+ ## License
148
+
149
+ MIT
150
+
151
+ ## Questions and/or Comments
152
+
153
+ Feel free to email [Xavier Nayrac](mailto:xavier.nayrac@gmail.com)
154
+ with any questions, or contact me on [twitter](https://twitter.com/lkdjiin).
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json2'
4
+
5
+ opt = Json2::Option.new
6
+
7
+ input = JSON.parse((ARGV[0] ? File.open(ARGV[0]) : $stdin).read)
8
+
9
+ json = if opt[:without_header]
10
+ Json2::CsvWithoutHeader.new(input)
11
+ else
12
+ Json2::CsvWithHeader.new(input, opt)
13
+ end
14
+
15
+ print json.output
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'json2/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "json2"
8
+ spec.version = Json2::VERSION
9
+ spec.authors = ["lkdjiin"]
10
+ spec.email = ["xavier.nayrac@gmail.com"]
11
+ spec.summary = %q{Transform json to csv.}
12
+ spec.description = %q{json2 takes a json file as input and outputs a clean
13
+ csv.}
14
+ spec.homepage = "https://github.com/lkdjiin/json2"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "reek", "~> 3.0"
26
+ spec.add_development_dependency "coco", "~> 0.13.0"
27
+ spec.add_development_dependency "flay"
28
+ end
@@ -0,0 +1,14 @@
1
+ require 'json'
2
+ require 'optparse'
3
+
4
+ require "json2/symbol_respond_to"
5
+ require "json2/version"
6
+ require "json2/csv_with_header"
7
+ require "json2/csv_without_header"
8
+ require "json2/header"
9
+ require "json2/body"
10
+ require "json2/option"
11
+
12
+ # Transform a Json file into Csv.
13
+ module Json2
14
+ end
@@ -0,0 +1,53 @@
1
+ module Json2
2
+
3
+ # Build a csv body.
4
+ class Body
5
+
6
+ # Get the body of a Csv file.
7
+ #
8
+ # For a description of the parameters see Body#initialize.
9
+ #
10
+ # Returns the String body, that is several lines in a single string,
11
+ # each lines with comma separated values.
12
+ def self.get(nodes, keys, column_size)
13
+ new(nodes, keys, column_size).get
14
+ end
15
+
16
+ # Creates a new Body instance.
17
+ #
18
+ # nodes - A Hash representing all columns. The key is the
19
+ # Symbol column's header and the value is an Array
20
+ # full of column's values.
21
+ # keys - An Array of Symbol header's column. Why passing the
22
+ # column's names while they are contained in `nodes`?
23
+ # This is because we want the header's names in that
24
+ # particular order given by `keys`, we can't rely on
25
+ # the order of the *Hash* `nodes`.
26
+ # column_size - The Fixnum size of a column. TODO I think this
27
+ # parameter isn't needed.
28
+ def initialize(nodes, keys, column_size)
29
+ @nodes = nodes
30
+ @column_size = column_size
31
+ @keys = keys
32
+ @body = ''
33
+ end
34
+
35
+ # Get the body of a Csv file. See also Body.get.
36
+ #
37
+ # Returns the String body.
38
+ def get
39
+ (0...@column_size).each {|observation| process_line(observation) }
40
+ @body
41
+ end
42
+
43
+ private
44
+
45
+ def process_line(observation)
46
+ line = []
47
+ @keys.each {|key| line << @nodes[key][observation] }
48
+ @body += line.join(',') + "\n"
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,86 @@
1
+ module Json2
2
+
3
+ # Turn a Json input into a Csv output with a header.
4
+ class CsvWithHeader
5
+
6
+ # Creates a new CsvWithHeader instance.
7
+ #
8
+ # input - A Hash representing a Json file. This is typically
9
+ # obtained with JSON.parse.
10
+ # options - Optional. A Hash of options, see Option to get the list.
11
+ def initialize(input, options = {})
12
+ @options = options
13
+ @input = input
14
+ @names_stack = []
15
+ @nodes = Hash.new {|hash, key| hash[key] = [] }
16
+ process_input
17
+ @column_size = @nodes.values.map {|node| node.size }.max
18
+ @keys = @nodes.keys.select {|key| @nodes[key].size == @column_size }
19
+ end
20
+
21
+ # Get the Csv.
22
+ #
23
+ # Returns the whole document as a single String.
24
+ def output
25
+ Header.get(@keys) + Body.get(@nodes, @keys, @column_size)
26
+ end
27
+
28
+ private
29
+
30
+ def process_input
31
+ if @input.respond_to?(:each_key)
32
+ process_keys(@input)
33
+ else
34
+ process_array(@input)
35
+ end
36
+ end
37
+
38
+ def process_keys(object)
39
+ if object.respond_to?(:each_key)
40
+ object.each_key do |key|
41
+ @names_stack.push(key)
42
+ process_key(object[key])
43
+ @names_stack.pop
44
+ end
45
+ else
46
+ error(98, 'Error, try with json2 --without-header')
47
+ end
48
+ end
49
+
50
+ def process_key(object)
51
+ case object
52
+ when ~:each_key then process_keys(object)
53
+ when ~:at then process_array(object)
54
+ else
55
+ @nodes[current_line] <<= object if line_eligible?
56
+ end
57
+ end
58
+
59
+ def process_array(object)
60
+ object.each {|element| process_keys(element) }
61
+ end
62
+
63
+ def current_line
64
+ @names_stack.join('.')
65
+ end
66
+
67
+ def line_eligible?
68
+ with_a_good_path? || without_path?
69
+ end
70
+
71
+ def with_a_good_path?
72
+ @options[:with_path] && current_line.start_with?(@options[:path])
73
+ end
74
+
75
+ def without_path?
76
+ !@options[:with_path]
77
+ end
78
+
79
+ def error(code, message)
80
+ warn(message)
81
+ exit(code)
82
+ end
83
+
84
+ end
85
+
86
+ end