json-csv 0.5.1 → 0.5.2

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.
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: []