diff --git a/bin/json2csv b/bin/json2csv new file mode 100755 index 0000000..3e9d8ef --- /dev/null +++ b/bin/json2csv @@ -0,0 +1,82 @@ +#!/usr/bin/env ruby + +require "rubygems" +require 'optparse' +require 'ostruct' +require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'json2csv.rb') # this form is important for local development + +module JSON2CSVRunner + + # command-line parsing + COMMAND = File.basename($0) + USAGE = "Usage: #{COMMAND} [INPUT] [OPTIONS]" + + options = OpenStruct.new + options.output = "-" + options.separator = "," + options.pretty = false + options.headers = nil + options.skipFirstRow = false + + opts = OptionParser.new do |o| + o.banner = USAGE + o.separator "" + o.separator "Specific options:" + + o.on("-s", "--separator SEP", "Set separator character surrounded by single quotes (default is ',')") do |sep| + options.separator = sep + end + + o.on("-o", "--output FILE", "Write output to a file") do |fn| + options.output = fn + end + + o.on("-H", "--force-headers HEADERS", "Supply list of headers, and skip first row of input; use to override headers in file") do |headers| + if headers then + options.headers = headers.split(",") + options.skipFirstRow = true + end + end + + o.on("-h", "--headers HEADERS", "Supply list of headers, where no headers exist in the file") do |headers| + if headers then + options.headers = headers.split(",") + end + end + + o.on_tail("-h", "--help", "Show this message") do + puts o + exit + end + + o.on_tail("-v", "--version", "Show version") do + puts CSV2JSON::VERSION + exit + end + end + + begin + opts.parse!(ARGV) + rescue + raise "Unable to parse options: #{$!}" + end + + # initialize output handle + if options.output == "-" + OUT = $stdout.clone + else + OUT = File.open(options.output, "w") + end + + if ARGV.size > 0 + IN = File.read(ARGV[0]) + else + IN = StringIO.new($stdin.read) # cannot be just $stdin.clone because FasterCSV is seeking in file :-( + end + + # run the command + JSON2CSV.parse(IN, OUT, options.headers, nil) + + # leave in peace + OUT.flush +end diff --git a/lib/json2csv.rb b/lib/json2csv.rb new file mode 100644 index 0000000..9ed04a2 --- /dev/null +++ b/lib/json2csv.rb @@ -0,0 +1,32 @@ +require 'rubygems' +require 'fastercsv' +require 'json' +require 'csv2json-version.rb' + +module JSON2CSV + + # convert an input string value to integer or float if applicable + def convert(val) + return Integer(val) if val.to_i.to_s == val + Float(val) rescue val + end + + + def parse(input, output, headers=nil, options={}) + inputJSON = JSON.parse(input) + + outputCSV = FasterCSV.generate do |csv| + csv << inputJSON[0].keys + inputJSON.each do |row| + csv << row.values + end + end + + output << outputCSV + end + + + module_function :parse + module_function :convert + +end diff --git a/test/test_json2csv.rb b/test/test_json2csv.rb new file mode 100644 index 0000000..4e526b6 --- /dev/null +++ b/test/test_json2csv.rb @@ -0,0 +1,22 @@ +require 'helper' +require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'csv2json.rb') # this form is important for local development + +class TestCsv2json < Test::Unit::TestCase + SEPS = {:comma => ',', :pipe => '|', :semicolon => ';'} + should "parse some test files" do + fixtures_dir = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures')) + Dir.chdir(fixtures_dir) do + Dir.glob('*.csv') do |filename| + filename_parts = File.basename(filename, ".csv").split('_') + json_template = filename_parts[0] + '.json' + File.open(filename, "r") do |input| + output = StringIO.new() + CSV2JSON.parse(input, output, nil, :col_sep => SEPS[filename_parts[1].to_sym] ) + template = File.read(json_template) + output.pos = 0 + assert template == output.read + end + end + end + end +end diff --git a/translated.json b/translated.json new file mode 100644 index 0000000..d8c41a9 --- /dev/null +++ b/translated.json @@ -0,0 +1 @@ +[{"columnC":"value1C","columnB":"value1B","columnA":"value1A"},{"columnC":"value2C","columnB":"value2B","columnA":"value2A"}] \ No newline at end of file