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 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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'switch'
4
+ Switch.run
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: []