json-csv 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/json-csv +74 -30
  3. data/lib/json-csv.rb +74 -30
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 149d44bb6b05763481545d60fcfbe168849a73e5
4
- data.tar.gz: e09fbc056bba47cf68731d6ba36ed805a4363ac0
3
+ metadata.gz: 8c1924dbb8fd7b4a477a13c6cfef62d737d25cb3
4
+ data.tar.gz: 828aebf03a83c06d86b9dd2d5412b0f0e6345b1b
5
5
  SHA512:
6
- metadata.gz: f92db5bd62890c330e801d052f58e3571972d6c3ee3eb6a4377dca37b8f78935cc63e51eed6f11c2d12351cfcb37bfd6532bd14b38d14ab640845a02ce59ddb0
7
- data.tar.gz: 034b6007c8ef2d7a3043d907f2e4d70c15eb70d27186ede5228ced8cf5f42efc0903a4ef1beec8865efdb8978ea205f5f076b8396ef4eaa04537da68eb5206f7
6
+ metadata.gz: 86959ae536415a661d3cd586590e281509f09cddccf91f54a6f9bb4f8b282a4922bd092ece930295b87a907afb0bdfaa766481964a23f6bee8d196d70d0d2979
7
+ data.tar.gz: a1ee53fd352df70a2ba8a13fd7dd74efb6c66582af6fd7a3755fbba470de2e6247cab51e103a58b31cac3300630a47cb7643a703c55295abc0d7e07afc4e7f3d
@@ -15,7 +15,7 @@ require 'optparse'
15
15
  require 'json'
16
16
 
17
17
  class JsonCsv
18
- VERSION = "0.5.1"
18
+ VERSION = "0.5.2"
19
19
  VERSION_DATE = "2017-06-25"
20
20
 
21
21
  DEFAULT_OPTS = {
@@ -29,13 +29,13 @@ class JsonCsv
29
29
  }
30
30
 
31
31
  class << self
32
- def new_from_argv(argv)
32
+ def parse_argv(argv)
33
33
  opts = DEFAULT_OPTS
34
34
 
35
- OptionParser.new do |op|
35
+ argv = OptionParser.new do |op|
36
36
  op.banner = <<-EOT
37
- Converts JSON to CSV, and vice versa.
38
- Usage: #{$0} [options] [--] [input-file [output-file]]
37
+ Converts JSON to CSV, and vice versa.
38
+ Usage: #{$0} [options] [--] [input-file [output-file]]
39
39
  EOT
40
40
 
41
41
  op.on("-i input-file", "--input input-file", "Input file (default STDIN)") do |input_file|
@@ -52,7 +52,6 @@ class JsonCsv
52
52
 
53
53
  op.on("-d depth", "--depth depth", "Maximum depth of JSON-to-CSV conversion (default -1, unlimited)") do |depth|
54
54
  opts[:depth] = depth.to_i
55
- opts[:depth] += 1 if opts[:depth] > 0 # this is a fudge to use -1 as infinity
56
55
  end
57
56
 
58
57
  op.on("-e crlf|cr|lf", "--line-ending crlf|cr|lf", "Line endings for output file (default crlf).") do |ending|
@@ -78,12 +77,16 @@ class JsonCsv
78
77
  exit
79
78
  end
80
79
 
81
- end.parse!(argv)
82
-
80
+ end.parse(argv)
83
81
 
84
82
  opts[:input_file] = argv.shift if argv.count > 0
85
83
  opts[:output_file] = argv.shift if argv.count > 0
86
84
 
85
+ opts
86
+ end
87
+
88
+ def new_from_argv(argv)
89
+ opts = parse_argv(argv)
87
90
  self.new(opts)
88
91
  end
89
92
 
@@ -96,10 +99,14 @@ class JsonCsv
96
99
  end
97
100
  end
98
101
 
102
+
99
103
  def initialize(opts)
100
104
  @opts = DEFAULT_OPTS.merge(opts)
101
105
  end
102
106
 
107
+
108
+ # Performs the JSON-to-CSV or CSV-to-JSON conversion, as specified in
109
+ # `opts` and the options passed in during `JsonCsv.new`.
103
110
  def run(opts = {})
104
111
  opts = @opts.merge(opts)
105
112
  enc = opts[:source_encoding]
@@ -113,6 +120,7 @@ class JsonCsv
113
120
  end
114
121
  end
115
122
 
123
+
116
124
  def convert_json_to_csv(opts = {})
117
125
  opts = @opts.merge(opts)
118
126
 
@@ -133,7 +141,9 @@ class JsonCsv
133
141
  end
134
142
 
135
143
  debug(opts, "Getting headers from JSON data.")
136
- headers = get_headers_from_json(input_fh, tmp_fh, opts[:depth])
144
+ depth = opts[:depth]
145
+ depth += 1 if depth > 0 # a fudge, in order to use -1 as infinity
146
+ headers = get_headers_from_json(input_fh, tmp_fh, depth)
137
147
 
138
148
  input_fh.close
139
149
  tmp_fh.close if tmp_fh
@@ -150,7 +160,7 @@ class JsonCsv
150
160
  end
151
161
 
152
162
  debug(opts, "Writing CSV output.")
153
- output_csv(headers, data_fh, output_fh)
163
+ output_csv(headers, data_fh, output_fh, opts[:line_ending])
154
164
  data_fh.close
155
165
  output_fh.close
156
166
 
@@ -169,6 +179,10 @@ private
169
179
  STDERR.puts("#{Time.now}\t#{msg}") if opts[:debug]
170
180
  end
171
181
 
182
+
183
+
184
+ # Scans a JSON file at `input_fh` to determine the headers
185
+ # to use when writing CSV data.
172
186
  # Returns a hash of `'header' => index` pairs, sorted.
173
187
  def get_headers_from_json(input_fh, tmp_fh, depth)
174
188
  headers = {}
@@ -182,6 +196,7 @@ private
182
196
  sort_keys(headers)
183
197
  end
184
198
 
199
+ # Helper function to get_headers_from_json --
185
200
  # Sorts a hash with string keys by number of dots in the string,
186
201
  # then alphabetically.
187
202
  # Returns a hash of `'key' => index` pairs, in order of index.
@@ -197,22 +212,31 @@ private
197
212
  sorted
198
213
  end
199
214
 
215
+ # Helper function to sort_keys --
216
+ # Counts the number of dots in a string.
200
217
  def count_dots(str)
201
218
  str.chars.select{|c| c == "."}.count
202
219
  end
203
220
 
204
- def flat_assign(dest, key, value, depth)
205
- flat_value = flatten_json(value, depth - 1)
206
- if flat_value.is_a?(Hash)
207
- flat_value.each do |k,v|
208
- dest["#{key}.#{k}"] = v
209
- end
210
- else
211
- dest["#{key}"] = flat_value
212
- end
213
- dest
214
- end
215
221
 
222
+
223
+ # Returns a flattened representation of the given JSON-encodable
224
+ # data (that is: hashes, arrays, numbers, strings, and `nil`).
225
+ # Dot-separated string keys are used to encode nested hash and
226
+ # array structures.
227
+ #
228
+ # Hashes get flattened like so:
229
+ #
230
+ # flatten_json({a: {b: {c: 1, d: "x"}, c: nil}})
231
+ # #=> {"a.b.c" => 1, "a.b.d" => "x", "a.c" => nil}
232
+ #
233
+ # Arrays are turned into hashes like:
234
+ #
235
+ # flatten_json([0, 1, 2, {a: "x"])
236
+ # #=> {"0" => 0, "1" => 1, "2" => 2, "3.a" => "x"}
237
+ #
238
+ # Simple data (numbers, strings, nil) passes through unchanged.
239
+ #
216
240
  def flatten_json(json, depth = -1)
217
241
  return {} if depth == 0
218
242
 
@@ -230,23 +254,31 @@ private
230
254
  end
231
255
  flat
232
256
 
233
- else # number or string
257
+ else # number or string or nil
234
258
  json
235
259
  end
236
260
  end
237
261
 
238
- def armor(val)
239
- str = val.to_s.gsub('"', '""')
240
- if str.match(/[",\n]/)
241
- '"' + str + '"'
262
+ ## Helper function to flatten_json --
263
+ ## Assigns a flattened value at the current key.
264
+ def flat_assign(dest, key, value, depth)
265
+ flat_value = flatten_json(value, depth - 1)
266
+ if flat_value.is_a?(Hash)
267
+ flat_value.each do |k,v|
268
+ dest["#{key}.#{k}"] = v
269
+ end
242
270
  else
243
- str
271
+ dest["#{key}"] = flat_value
244
272
  end
273
+ dest
245
274
  end
246
275
 
276
+
277
+
278
+ ## Reads JSON data from data_fh, and writes CSV data (with header) to
279
+ ## output_fh.
247
280
  def output_csv(headers, data_fh, output_fh, line_ending)
248
- # Write header line
249
- output_fh.write(headers.map{|h| armor(h[0])}.join(","))
281
+ output_fh.write(headers.map{|h| csv_armor(h[0])}.join(","))
250
282
  output_fh.write(line_ending)
251
283
 
252
284
  header_count = headers.count
@@ -257,10 +289,22 @@ private
257
289
  flat.each do |key, value|
258
290
  output[headers[key]] = value if headers[key]
259
291
  end
260
- output_fh.write(output.map{|x| armor(x)}.join(","))
292
+ output_fh.write(output.map{|x| csv_armor(x)}.join(","))
261
293
  output_fh.write(line_ending)
262
294
  end
263
295
  end
296
+
297
+ ## Helper function to output_csv --
298
+ ## Returns a CSV-armored version of `val`.
299
+ ## Escapes special characters and adds double-quotes if necessary.
300
+ def csv_armor(val)
301
+ str = val.to_s.gsub('"', '""')
302
+ if str.match(/[",\n]/)
303
+ '"' + str + '"'
304
+ else
305
+ str
306
+ end
307
+ end
264
308
  end
265
309
 
266
310
 
@@ -15,7 +15,7 @@ require 'optparse'
15
15
  require 'json'
16
16
 
17
17
  class JsonCsv
18
- VERSION = "0.5.1"
18
+ VERSION = "0.5.2"
19
19
  VERSION_DATE = "2017-06-25"
20
20
 
21
21
  DEFAULT_OPTS = {
@@ -29,13 +29,13 @@ class JsonCsv
29
29
  }
30
30
 
31
31
  class << self
32
- def new_from_argv(argv)
32
+ def parse_argv(argv)
33
33
  opts = DEFAULT_OPTS
34
34
 
35
- OptionParser.new do |op|
35
+ argv = OptionParser.new do |op|
36
36
  op.banner = <<-EOT
37
- Converts JSON to CSV, and vice versa.
38
- Usage: #{$0} [options] [--] [input-file [output-file]]
37
+ Converts JSON to CSV, and vice versa.
38
+ Usage: #{$0} [options] [--] [input-file [output-file]]
39
39
  EOT
40
40
 
41
41
  op.on("-i input-file", "--input input-file", "Input file (default STDIN)") do |input_file|
@@ -52,7 +52,6 @@ class JsonCsv
52
52
 
53
53
  op.on("-d depth", "--depth depth", "Maximum depth of JSON-to-CSV conversion (default -1, unlimited)") do |depth|
54
54
  opts[:depth] = depth.to_i
55
- opts[:depth] += 1 if opts[:depth] > 0 # this is a fudge to use -1 as infinity
56
55
  end
57
56
 
58
57
  op.on("-e crlf|cr|lf", "--line-ending crlf|cr|lf", "Line endings for output file (default crlf).") do |ending|
@@ -78,12 +77,16 @@ class JsonCsv
78
77
  exit
79
78
  end
80
79
 
81
- end.parse!(argv)
82
-
80
+ end.parse(argv)
83
81
 
84
82
  opts[:input_file] = argv.shift if argv.count > 0
85
83
  opts[:output_file] = argv.shift if argv.count > 0
86
84
 
85
+ opts
86
+ end
87
+
88
+ def new_from_argv(argv)
89
+ opts = parse_argv(argv)
87
90
  self.new(opts)
88
91
  end
89
92
 
@@ -96,10 +99,14 @@ class JsonCsv
96
99
  end
97
100
  end
98
101
 
102
+
99
103
  def initialize(opts)
100
104
  @opts = DEFAULT_OPTS.merge(opts)
101
105
  end
102
106
 
107
+
108
+ # Performs the JSON-to-CSV or CSV-to-JSON conversion, as specified in
109
+ # `opts` and the options passed in during `JsonCsv.new`.
103
110
  def run(opts = {})
104
111
  opts = @opts.merge(opts)
105
112
  enc = opts[:source_encoding]
@@ -113,6 +120,7 @@ class JsonCsv
113
120
  end
114
121
  end
115
122
 
123
+
116
124
  def convert_json_to_csv(opts = {})
117
125
  opts = @opts.merge(opts)
118
126
 
@@ -133,7 +141,9 @@ class JsonCsv
133
141
  end
134
142
 
135
143
  debug(opts, "Getting headers from JSON data.")
136
- headers = get_headers_from_json(input_fh, tmp_fh, opts[:depth])
144
+ depth = opts[:depth]
145
+ depth += 1 if depth > 0 # a fudge, in order to use -1 as infinity
146
+ headers = get_headers_from_json(input_fh, tmp_fh, depth)
137
147
 
138
148
  input_fh.close
139
149
  tmp_fh.close if tmp_fh
@@ -150,7 +160,7 @@ class JsonCsv
150
160
  end
151
161
 
152
162
  debug(opts, "Writing CSV output.")
153
- output_csv(headers, data_fh, output_fh)
163
+ output_csv(headers, data_fh, output_fh, opts[:line_ending])
154
164
  data_fh.close
155
165
  output_fh.close
156
166
 
@@ -169,6 +179,10 @@ private
169
179
  STDERR.puts("#{Time.now}\t#{msg}") if opts[:debug]
170
180
  end
171
181
 
182
+
183
+
184
+ # Scans a JSON file at `input_fh` to determine the headers
185
+ # to use when writing CSV data.
172
186
  # Returns a hash of `'header' => index` pairs, sorted.
173
187
  def get_headers_from_json(input_fh, tmp_fh, depth)
174
188
  headers = {}
@@ -182,6 +196,7 @@ private
182
196
  sort_keys(headers)
183
197
  end
184
198
 
199
+ # Helper function to get_headers_from_json --
185
200
  # Sorts a hash with string keys by number of dots in the string,
186
201
  # then alphabetically.
187
202
  # Returns a hash of `'key' => index` pairs, in order of index.
@@ -197,22 +212,31 @@ private
197
212
  sorted
198
213
  end
199
214
 
215
+ # Helper function to sort_keys --
216
+ # Counts the number of dots in a string.
200
217
  def count_dots(str)
201
218
  str.chars.select{|c| c == "."}.count
202
219
  end
203
220
 
204
- def flat_assign(dest, key, value, depth)
205
- flat_value = flatten_json(value, depth - 1)
206
- if flat_value.is_a?(Hash)
207
- flat_value.each do |k,v|
208
- dest["#{key}.#{k}"] = v
209
- end
210
- else
211
- dest["#{key}"] = flat_value
212
- end
213
- dest
214
- end
215
221
 
222
+
223
+ # Returns a flattened representation of the given JSON-encodable
224
+ # data (that is: hashes, arrays, numbers, strings, and `nil`).
225
+ # Dot-separated string keys are used to encode nested hash and
226
+ # array structures.
227
+ #
228
+ # Hashes get flattened like so:
229
+ #
230
+ # flatten_json({a: {b: {c: 1, d: "x"}, c: nil}})
231
+ # #=> {"a.b.c" => 1, "a.b.d" => "x", "a.c" => nil}
232
+ #
233
+ # Arrays are turned into hashes like:
234
+ #
235
+ # flatten_json([0, 1, 2, {a: "x"])
236
+ # #=> {"0" => 0, "1" => 1, "2" => 2, "3.a" => "x"}
237
+ #
238
+ # Simple data (numbers, strings, nil) passes through unchanged.
239
+ #
216
240
  def flatten_json(json, depth = -1)
217
241
  return {} if depth == 0
218
242
 
@@ -230,23 +254,31 @@ private
230
254
  end
231
255
  flat
232
256
 
233
- else # number or string
257
+ else # number or string or nil
234
258
  json
235
259
  end
236
260
  end
237
261
 
238
- def armor(val)
239
- str = val.to_s.gsub('"', '""')
240
- if str.match(/[",\n]/)
241
- '"' + str + '"'
262
+ ## Helper function to flatten_json --
263
+ ## Assigns a flattened value at the current key.
264
+ def flat_assign(dest, key, value, depth)
265
+ flat_value = flatten_json(value, depth - 1)
266
+ if flat_value.is_a?(Hash)
267
+ flat_value.each do |k,v|
268
+ dest["#{key}.#{k}"] = v
269
+ end
242
270
  else
243
- str
271
+ dest["#{key}"] = flat_value
244
272
  end
273
+ dest
245
274
  end
246
275
 
276
+
277
+
278
+ ## Reads JSON data from data_fh, and writes CSV data (with header) to
279
+ ## output_fh.
247
280
  def output_csv(headers, data_fh, output_fh, line_ending)
248
- # Write header line
249
- output_fh.write(headers.map{|h| armor(h[0])}.join(","))
281
+ output_fh.write(headers.map{|h| csv_armor(h[0])}.join(","))
250
282
  output_fh.write(line_ending)
251
283
 
252
284
  header_count = headers.count
@@ -257,10 +289,22 @@ private
257
289
  flat.each do |key, value|
258
290
  output[headers[key]] = value if headers[key]
259
291
  end
260
- output_fh.write(output.map{|x| armor(x)}.join(","))
292
+ output_fh.write(output.map{|x| csv_armor(x)}.join(","))
261
293
  output_fh.write(line_ending)
262
294
  end
263
295
  end
296
+
297
+ ## Helper function to output_csv --
298
+ ## Returns a CSV-armored version of `val`.
299
+ ## Escapes special characters and adds double-quotes if necessary.
300
+ def csv_armor(val)
301
+ str = val.to_s.gsub('"', '""')
302
+ if str.match(/[",\n]/)
303
+ '"' + str + '"'
304
+ else
305
+ str
306
+ end
307
+ end
264
308
  end
265
309
 
266
310
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - pete gamache
@@ -42,5 +42,5 @@ rubyforge_project:
42
42
  rubygems_version: 2.4.8
43
43
  signing_key:
44
44
  specification_version: 4
45
- summary: A command-line JSON/CSV converter
45
+ summary: A customizable command-line JSON/CSV converter
46
46
  test_files: []