switch-cli 0.0.1
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.
- checksums.yaml +7 -0
- data/bin/switch +4 -0
- data/lib/switch.rb +77 -0
- data/lib/switch/cloud_sync.rb +30 -0
- data/lib/switch/csv2json.rb +71 -0
- data/lib/switch/extensions.rb +29 -0
- data/lib/switch/json2csv.rb +108 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3f8fcb1f12f7dc805ecbf5217aec396bc08efe4a
|
4
|
+
data.tar.gz: 8fcb03c39148dd3b8d26faa3edda1b82e17f43ec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b32828a72c687de3be6f464d8e35322e73771688fe3739d48e1e35ae056d133d3bc9a36694ac5d8a0955a1bb3b7f1f797e0a63d89c455356381990109c02b0c
|
7
|
+
data.tar.gz: 8cb1271d9471f3cb94cb2fe0f7b5ffec75d9bca38b560784226224a57791ee8ce1104fb552c5ceb6fba1b88ab202c46de26cf435ab2d9cfb8d3f9ce4d4fba0cc
|
data/bin/switch
ADDED
data/lib/switch.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'csv'
|
3
|
+
require 'google_drive'
|
4
|
+
require 'launchy'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
require 'dotenv'
|
8
|
+
Dotenv.load
|
9
|
+
|
10
|
+
require 'switch/extensions'
|
11
|
+
require 'switch/json2csv'
|
12
|
+
require 'switch/csv2json'
|
13
|
+
require 'switch/cloud_sync'
|
14
|
+
|
15
|
+
module Switch
|
16
|
+
if ENV['TEST']
|
17
|
+
@logger = Logger.new File.open('test.log', 'a')
|
18
|
+
else
|
19
|
+
@logger = Logger.new STDOUT
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_accessor :logger
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO: need to ask for these on first run and store
|
27
|
+
# in the secret file too
|
28
|
+
CLIENT_ID = ENV['GOOGLE_DRIVE_CLIENT_ID']
|
29
|
+
CLIENT_SECRET = ENV['GOOGLE_DRIVE_CLIENT_SECRET']
|
30
|
+
SAVE_SESSION_FILE = "#{ENV['HOME']}/.googledrivesecret"
|
31
|
+
|
32
|
+
FILE_NAME = File.basename(Dir.pwd) + '.csv'
|
33
|
+
OUTPUT_DIR = "locales"
|
34
|
+
|
35
|
+
# TODO:
|
36
|
+
# * better file not found errors
|
37
|
+
# * allow .switch files
|
38
|
+
# --google-drive --no-local-file
|
39
|
+
# `switch sync` runs a .switch file in the directory with
|
40
|
+
# json2csv: locales locales.csv --google-drive --no-open
|
41
|
+
# csv2json: locales.csv locales --google-drive --no-open
|
42
|
+
# live switch - create a live replica
|
43
|
+
# issue with order, when adding stuff in the middle to json file
|
44
|
+
|
45
|
+
def self.run
|
46
|
+
command = ARGV[0]
|
47
|
+
input = ARGV[1]
|
48
|
+
output = ARGV[2]
|
49
|
+
|
50
|
+
case command
|
51
|
+
when "json2csv"
|
52
|
+
input ||= './locales/*'
|
53
|
+
output ||= FILE_NAME
|
54
|
+
|
55
|
+
csv_file = Json2Csv.new(input).convert(output)
|
56
|
+
|
57
|
+
# if google drive option is on
|
58
|
+
client = CloudSync.new
|
59
|
+
client.upload_to_drive(csv_file, output)
|
60
|
+
when "csv2json"
|
61
|
+
input ||= FILE_NAME
|
62
|
+
output_dir = output || OUTPUT_DIR
|
63
|
+
|
64
|
+
# if google drive option is on
|
65
|
+
client = CloudSync.new
|
66
|
+
local_input_csv = "#{output_dir}/#{input}"
|
67
|
+
client.download_from_drive(input, local_input_csv)
|
68
|
+
|
69
|
+
json_files = Csv2Json.new(local_input_csv, output_dir).convert
|
70
|
+
else
|
71
|
+
puts "Unknown option!"
|
72
|
+
puts "Please use either"
|
73
|
+
puts " json2csv - converts multiple json files to csv files and uplaods them to google drive"
|
74
|
+
puts " csv2json - converts a single csv file to multiple json files"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Switch
|
2
|
+
class CloudSync
|
3
|
+
def initialize
|
4
|
+
Switch.logger.info "Connecting to google drive"
|
5
|
+
@session = GoogleDrive.saved_session(SAVE_SESSION_FILE, nil, CLIENT_ID, CLIENT_SECRET)
|
6
|
+
end
|
7
|
+
|
8
|
+
def upload_to_drive(input, output)
|
9
|
+
Switch.logger.info "Uploading csv for json files"
|
10
|
+
if file = @session.spreadsheet_by_title(output)
|
11
|
+
Switch.logger.info "Detected old version of #{output}, updating file!"
|
12
|
+
file.update_from_file(input)
|
13
|
+
else
|
14
|
+
file = @session.upload_from_file(input, output)
|
15
|
+
end
|
16
|
+
|
17
|
+
Switch.logger.info "You can find the new file here"
|
18
|
+
Switch.logger.info file.human_url
|
19
|
+
|
20
|
+
Launchy.open(file.human_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
def download_from_drive(input, output)
|
24
|
+
file = @session.spreadsheet_by_title(input)
|
25
|
+
file.export_as_file(output)
|
26
|
+
|
27
|
+
Switch.logger.info "Downloaded file #{input} from google drive to #{output}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'pry'
|
2
|
+
module Switch
|
3
|
+
class Csv2Json
|
4
|
+
def initialize(input, output)
|
5
|
+
@input = input
|
6
|
+
@output = output
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert
|
10
|
+
@rows = CSV.read(@input)
|
11
|
+
@header = @rows.shift
|
12
|
+
|
13
|
+
order_index = @header.index 'order'
|
14
|
+
@rows.sort! {|a, b| a[order_index].to_i <=> b[order_index].to_i }
|
15
|
+
|
16
|
+
files = []
|
17
|
+
languages = @header - ['keys', 'order']
|
18
|
+
for lang in languages
|
19
|
+
data = extract_hash_for(lang)
|
20
|
+
files.push write_json_file(lang, data)
|
21
|
+
end
|
22
|
+
|
23
|
+
files
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def extract_hash_for(lang)
|
28
|
+
hash = {}
|
29
|
+
lang_index = @header.index lang
|
30
|
+
key_index = @header.index 'keys'
|
31
|
+
en_lang_index = @header.index 'en'
|
32
|
+
|
33
|
+
raise "No key column found!" unless key_index
|
34
|
+
|
35
|
+
@rows.each do |row|
|
36
|
+
hash[row[key_index]] = get_value(lang, row[lang_index], row[en_lang_index])
|
37
|
+
end
|
38
|
+
|
39
|
+
hash = dot_notation_to_nested_hash(hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_value(lang, value, en_value)
|
43
|
+
en_value ||= ""
|
44
|
+
value = value || "#{lang.upcase}_#{en_value}"
|
45
|
+
# multi-line json string support
|
46
|
+
value = value.split("\n")
|
47
|
+
value = value[0] if value.count == 1
|
48
|
+
value
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_json_file(lang, data)
|
52
|
+
file_path = "#{@output}/#{lang}.json"
|
53
|
+
File.open(file_path, "w") do |f|
|
54
|
+
f.write(JSON.pretty_generate(data))
|
55
|
+
end
|
56
|
+
|
57
|
+
Switch.logger.info "Wrote file #{file_path}"
|
58
|
+
|
59
|
+
file_path
|
60
|
+
end
|
61
|
+
|
62
|
+
# http://stackoverflow.com/a/4366196/1321251
|
63
|
+
def dot_notation_to_nested_hash(hash)
|
64
|
+
hash.map do |main_key, main_value|
|
65
|
+
main_key.to_s.split(".").reverse.inject(main_value) do |value, key|
|
66
|
+
{key.to_sym => value}
|
67
|
+
end
|
68
|
+
end.inject(&:deep_merge)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Enumerable
|
2
|
+
# flatten a nested hash using a dot notation for the keys
|
3
|
+
# http://stackoverflow.com/questions/10712679/flatten-a-nested-json-object
|
4
|
+
def flatten_with_path(parent_prefix = nil)
|
5
|
+
res = {}
|
6
|
+
|
7
|
+
self.each_with_index do |elem, i|
|
8
|
+
if elem.is_a?(Array)
|
9
|
+
k, v = elem
|
10
|
+
# support for multiline strings in json, maybe we need a parameter here? on/off?
|
11
|
+
if v.is_a?(Array) && v.all? {|c| c.is_a?(String)}
|
12
|
+
v = v.join("\n")
|
13
|
+
end
|
14
|
+
else
|
15
|
+
k, v = i, elem
|
16
|
+
end
|
17
|
+
|
18
|
+
key = parent_prefix ? "#{parent_prefix}.#{k}" : k # assign key name for result hash
|
19
|
+
|
20
|
+
if v.is_a? Enumerable
|
21
|
+
res.merge!(v.flatten_with_path(key)) # recursive call to flatten child elements
|
22
|
+
else
|
23
|
+
res[key] = v
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
res
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Switch
|
2
|
+
class Json2Csv
|
3
|
+
def initialize(dir)
|
4
|
+
@dir = dir
|
5
|
+
end
|
6
|
+
|
7
|
+
def convert(csv_file)
|
8
|
+
files = dir_files.select{|f| File.extname(f) == '.json'}
|
9
|
+
columns = columns_for_json(files)
|
10
|
+
|
11
|
+
keys = get_keys_from(columns)
|
12
|
+
columns = remove_missing_translations(keys, columns)
|
13
|
+
columns = move_english_as_first_column(columns)
|
14
|
+
|
15
|
+
rows = columns_to_rows(keys, columns)
|
16
|
+
rows = add_order_column(rows)
|
17
|
+
add_header(rows, columns)
|
18
|
+
|
19
|
+
Switch.logger.info "Writing local file to #{csv_file}"
|
20
|
+
write_file(rows, csv_file)
|
21
|
+
|
22
|
+
Switch.logger.info "Successfully converted #{files.count}. File #{csv_file}"
|
23
|
+
csv_file
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def columns_for_json(files)
|
28
|
+
columns = []
|
29
|
+
|
30
|
+
files.each do |file_path|
|
31
|
+
Switch.logger.info "Generating column for json file #{file_path}"
|
32
|
+
language = File.basename(file_path, '.json')
|
33
|
+
columns.push name: language, data: column_for_json(file_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
columns
|
37
|
+
end
|
38
|
+
|
39
|
+
def column_for_json(file_path)
|
40
|
+
data = JSON.parse(open(file_path).read)
|
41
|
+
data = data.flatten_with_path
|
42
|
+
data
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_keys_from(columns)
|
46
|
+
keys = []
|
47
|
+
columns.each do |language|
|
48
|
+
keys |= language[:data].keys
|
49
|
+
end
|
50
|
+
|
51
|
+
keys
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_missing_translations(keys, columns)
|
55
|
+
columns = columns.dup
|
56
|
+
keys.each do |key|
|
57
|
+
columns.each do |lang|
|
58
|
+
# e.g. 'CN_', 'ES_', etc
|
59
|
+
pattern = Regexp.new('\A' + lang[:name].upcase + '_')
|
60
|
+
if lang[:data][key] && lang[:data][key].match(pattern)
|
61
|
+
lang[:data][key] = nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
columns
|
66
|
+
end
|
67
|
+
|
68
|
+
def move_english_as_first_column(columns)
|
69
|
+
columns.unshift columns.delete_at(columns.index { |lang| lang[:name] == 'en' })
|
70
|
+
end
|
71
|
+
|
72
|
+
def columns_to_rows(keys, columns)
|
73
|
+
rows = keys.map do |key|
|
74
|
+
row = columns.map {|lang| lang[:data][key] }
|
75
|
+
row.push(key)
|
76
|
+
row
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_order_column(rows)
|
81
|
+
rows.each_with_index do |row, i|
|
82
|
+
row.push(i + 1)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_header(rows, columns)
|
87
|
+
rows.unshift (columns.map { |l| l[:name] } + ["keys", "order"])
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_file(rows, csv_file)
|
91
|
+
CSV.open(csv_file, "wb") do |csv|
|
92
|
+
rows.each do |row|
|
93
|
+
csv << row
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def dir_files
|
99
|
+
dir = @dir
|
100
|
+
unless dir.match(/\*\z/)
|
101
|
+
dir += '/' unless dir.match(/\/\z/)
|
102
|
+
dir += '*'
|
103
|
+
end
|
104
|
+
|
105
|
+
Dir[dir]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: switch-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Yagudaev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dotenv
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: google_drive
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: launchy
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: deep_merge
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.9.12
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.9.12
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.4'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.4'
|
97
|
+
description: "Your application likely consumes .json or .yml files, editing then by
|
98
|
+
hand\n as a developer is fine. If you are a translator/client without programming
|
99
|
+
background editing\n these files is difficult. Moreover, when you get more than
|
100
|
+
5 languages it is hard to keep track\n what translations are still pending.\n\n
|
101
|
+
\ What you really want is a simple spreadsheet you can all collaberate on.\n With
|
102
|
+
a simple command you can make it all come true:\n\n ```\n switch json2csv ./locales
|
103
|
+
./tmp --google-drive\n ```\n\n This will take all the json files in the locale
|
104
|
+
directory and convert them\n to a single csv file under ./tmp/locales.csv it will
|
105
|
+
then upload the file\n to google drive and open it\n "
|
106
|
+
email: michael@yagudaev.com
|
107
|
+
executables:
|
108
|
+
- switch
|
109
|
+
extensions: []
|
110
|
+
extra_rdoc_files: []
|
111
|
+
files:
|
112
|
+
- bin/switch
|
113
|
+
- lib/switch.rb
|
114
|
+
- lib/switch/cloud_sync.rb
|
115
|
+
- lib/switch/csv2json.rb
|
116
|
+
- lib/switch/extensions.rb
|
117
|
+
- lib/switch/json2csv.rb
|
118
|
+
homepage: http://rubygems.org/gems/switch
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.4.5.1
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: Command-line utility to convert translation files to spreadsheet and vice-versa
|
142
|
+
test_files: []
|