fci 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d3a1f7a7cdd1414f54ea6bcb540c2773d5d6d709
4
+ data.tar.gz: 4f9966373cceb847965fa8e14fee574327882b48
5
+ SHA512:
6
+ metadata.gz: 35fdca0463444bdcb87d78e7226fef51544d585e5c3f90d9b3474e091a7c92b9db5a7e4020fd179a9862dbd7a87260f66be4ccefce0fc807b1f4e812d99dd317
7
+ data.tar.gz: 30abeaba691a5d96ed8a25bbf5c6baf895716add0c4d1da12f671b8ba748bab7135644a65213529673ef5fb76a8a754ebd7b0ecac35d79625280a55ec2dd407c
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+ /fci.yml
12
+
13
+ ## Specific to RubyMotion:
14
+ .dat*
15
+ .repl_history
16
+ build/
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalisation:
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ # Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ freshdesk_api
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.2.1
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Anton Maminov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # FCI
2
+
3
+ Freshdesk and Crowdin integration Command Line Interface (CLI)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```
10
+ gem 'fci'
11
+ ```
12
+
13
+ And then execute:
14
+ ```
15
+ $ bundle
16
+ ```
17
+
18
+ Or install it manually as:
19
+
20
+ ```
21
+ $ gem install fci
22
+ ```
23
+
24
+ ## Use
25
+
26
+ The simplest way to get started is to create a scaffold project:
27
+
28
+ ```
29
+ > fci init todo
30
+ ```
31
+
32
+ A new ./todo directory is created containing the sample config `fci.yml`. View the basic output of the scaffold with:
33
+
34
+ ```
35
+ > cd todo
36
+ > fci help
37
+ ```
38
+
39
+ Which will output:
40
+
41
+ ```
42
+ NAME
43
+ fci - is a command line tool that allows you to manage and synchronize your Freshdesk localization with Crowdin project
44
+
45
+ SYNOPSIS
46
+ fci [global options] command [command options] [arguments...]
47
+
48
+ VERSION
49
+ 0.0.1
50
+
51
+ GLOBAL OPTIONS
52
+ -c, --config=<s> - Project-specific configuration file (default: /home/user/project/fci.yml)
53
+ --version - Display the program version
54
+ -v, --verbose - Be verbose
55
+ --help - Show this message
56
+
57
+ COMMANDS
58
+ help - Shows a list of commands or help for one command
59
+ init:project - Create a new FCI-based project
60
+ import:sources - Read folders/articles from Freshdesk and upload resource files to Crowdin
61
+ download:translations - Build and download last exported translation resources from Crowdin
62
+ export:translations - Add or update localized resource files(folders and articles) in Freshdesk
63
+ ```
64
+
65
+ ## Configuration
66
+
67
+ The scaffold project that was created in ./todo comes with a `fci.yml` shell.
68
+
69
+ ```
70
+ ---
71
+ crowdin_project_id: '<%your-crowdin-project-id%>'
72
+ crowdin_api_key: '<%your-crowdin-api-key%>'
73
+ crowdin_base_url: 'https://api.crowdin.com'
74
+
75
+ freshdesk_base_url: 'https://<%subdomain%>.freshdesk.com'
76
+ freshdesk_username: '<%your-freshdek-username%>'
77
+ freshdesk_password: '<%your-freshdesk-password%>'
78
+
79
+ freshdesk_category: '<%category-id%>'
80
+
81
+ translations:
82
+ -
83
+ crowdin_language_code: '<%crowdin-two-letters-code%>'
84
+ freshdesk_category_id: '<%freshdesk-category-id%>'
85
+ -
86
+ crowdin_language_code: '<%crowdin-two-letters-code%>'
87
+ freshdesk_category_id: '<%freshdesk-category-id%>'
88
+
89
+ ```
90
+
91
+ ## Supported Rubies
92
+
93
+ Tested with the following Ruby versions:
94
+
95
+ - MRI 2.2.0
96
+
97
+ ## Contributing
98
+
99
+ 1. Fork it
100
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
101
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
102
+ 4. Push to the branch (`git push origin my-new-feature`)
103
+ 5. Create new Pull Request
104
+
105
+ ## License and Author
106
+
107
+ Author: Anton Maminov (anton.maminov@gmail.com)
108
+
109
+ Copyright: 2015 [crowdin.com](http://crowdin.com/)
110
+
111
+ This project is licensed under the MIT license, a copy of which can be found in the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rake/clean'
2
+ require 'rubygems'
3
+ require 'rubygems/package_task'
4
+ require 'rdoc/task'
5
+ require 'cucumber'
6
+ require 'cucumber/rake/task'
7
+ Rake::RDocTask.new do |rd|
8
+ rd.main = "README.rdoc"
9
+ rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
10
+ rd.title = 'Your application title'
11
+ end
12
+
13
+ spec = eval(File.read('fci.gemspec'))
14
+
15
+ Gem::PackageTask.new(spec) do |pkg|
16
+ end
17
+ CUKE_RESULTS = 'results.html'
18
+ CLEAN << CUKE_RESULTS
19
+ desc 'Run features'
20
+ Cucumber::Rake::Task.new(:features) do |t|
21
+ opts = "features --format html -o #{CUKE_RESULTS} --format progress -x"
22
+ opts += " --tags #{ENV['TAGS']}" if ENV['TAGS']
23
+ t.cucumber_opts = opts
24
+ t.fork = false
25
+ end
26
+
27
+ desc 'Run features tagged as work-in-progress (@wip)'
28
+ Cucumber::Rake::Task.new('features:wip') do |t|
29
+ tag_opts = ' --tags ~@pending'
30
+ tag_opts = ' --tags @wip'
31
+ t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x -s#{tag_opts}"
32
+ t.fork = false
33
+ end
34
+
35
+ task :cucumber => :features
36
+ task 'cucumber:wip' => 'features:wip'
37
+ task :wip => 'features:wip'
38
+ require 'rake/testtask'
39
+ Rake::TestTask.new do |t|
40
+ t.libs << "test"
41
+ t.test_files = FileList['test/*_test.rb']
42
+ end
43
+
44
+ task :default => [:test,:features]
data/bin/fci ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gli'
3
+ begin # XXX: Remove this begin/rescue before distributing your app
4
+ require 'fci'
5
+ rescue LoadError
6
+ STDERR.puts "In development, you need to use `bundle exec bin/fci` to run your app"
7
+ STDERR.puts "At install-time, RubyGems will make sure lib, etc. are in the load path"
8
+ STDERR.puts "Feel free to remove this message from bin/fci now"
9
+ exit 64
10
+ end
11
+
12
+ include GLI::App
13
+ include FCI
14
+
15
+ program_desc 'is a command line tool that allows you to manage and synchronize your Freshdesk localization with Crowdin project'
16
+
17
+ sort_help :manually # help commands are ordered in the order declared
18
+
19
+ version FCI::VERSION
20
+
21
+ subcommand_option_handling :normal
22
+ arguments :strict
23
+
24
+ desc 'Be verbose'
25
+ switch [:v, :verbose], negatable: false
26
+
27
+ desc 'Project-specific configuration file'
28
+ default_value File.join(Dir.pwd, 'fci.yml')
29
+ arg_name '<s>'
30
+ flag [:c, :config]
31
+
32
+ commands_from 'fci/commands'
33
+
34
+ pre do |global, command, options, args|
35
+ # Pre logic here
36
+ # Return true to proceed; false to abort and not call the
37
+ # chosen command
38
+ # Use skips_pre before a command to skip this block
39
+ # on that command only
40
+
41
+ unless File.exists?(global[:config])
42
+ exit_now! <<-EOS.strip_heredoc
43
+ Can't find configuration file (default `fci.yml').
44
+ Type `fci help` to know how to specify custom configuration file
45
+ EOS
46
+ end
47
+
48
+ # load project-specific configuration
49
+ begin
50
+ @fci_config = YAML.load(File.read(global[:config])) || {}
51
+ rescue Psych::SyntaxError => err
52
+ exit_now! <<-EOS
53
+ Could not parse YAML: #{err.message}
54
+ EOS
55
+ end
56
+
57
+ @freshdesk = FreshdeskAPI::Client.new do |c|
58
+ c.base_url = @fci_config['freshdesk_base_url']
59
+ c.username = @fci_config['freshdesk_username']
60
+ c.password = @fci_config['freshdesk_password']
61
+ end
62
+
63
+ @crowdin = Crowdin::API.new(api_key: @fci_config['crowdin_api_key'], project_id: @fci_config['crowdin_project_id'], base_url: @fci_config['crowdin_base_url'])
64
+
65
+ true
66
+ end
67
+
68
+ post do |global,command,options,args|
69
+ # Post logic here
70
+ # Use skips_post before a command to skip this
71
+ # block on that command only
72
+ end
73
+
74
+ on_error do |exception|
75
+ # Error logic here
76
+ # return false to skip default error handling
77
+ true
78
+ end
79
+
80
+ exit run(ARGV)
data/fci.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # Ensure we require the local version and not one we might have installed already
2
+ require File.join([File.dirname(__FILE__),'lib','fci','version.rb'])
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'fci'
5
+ s.version = FCI::VERSION
6
+ s.author = 'Anton Maminov'
7
+ s.email = 'anton.maminov@gmail.com'
8
+ s.homepage = 'https://github.com/mamantoha/fci'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'Freshdesk and Crowdin integration Command Line Interface (CLI)'
11
+ s.files = `git ls-files`.split("\n")
12
+ s.require_paths << 'lib'
13
+ # s.has_rdoc = true
14
+ # s.extra_rdoc_files = ['README.rdoc','fci.rdoc']
15
+ # s.rdoc_options << '--title' << 'fci' << '--main' << 'README.rdoc' << '-ri'
16
+ s.bindir = 'bin'
17
+ s.executables << 'fci'
18
+ s.add_runtime_dependency('nokogiri')
19
+ s.add_runtime_dependency('rubyzip')
20
+ s.add_runtime_dependency('byebug')
21
+ s.add_runtime_dependency('crowdin-api')
22
+ s.add_runtime_dependency('freshdesk_api')
23
+ s.add_development_dependency('rake')
24
+ s.add_development_dependency('rdoc')
25
+ s.add_development_dependency('aruba')
26
+ s.add_runtime_dependency('gli','2.13.0')
27
+ end
data/fci.yml.example ADDED
@@ -0,0 +1,20 @@
1
+ ---
2
+ # Crowdin API credentials
3
+ crowdin_project_id: '<%your-crowdin-project-id%>'
4
+ crowdin_api_key: '<%your-crowdin-api-key%>'
5
+ crowdin_base_url: 'https://api.crowdin.com'
6
+
7
+ # Freshdesk API credentials
8
+ freshdesk_base_url: 'https://<%subdomain%>.freshdesk.com'
9
+ freshdesk_username: '<%your-freshdek-username%>'
10
+ freshdesk_password: '<%your-freshdesk-password%>'
11
+
12
+ freshdesk_category: '<%freshdesk-category-id%>'
13
+
14
+ translations:
15
+ -
16
+ crowdin_language_code: '<%crowdin-two-letters-code%>'
17
+ freshdesk_category_id: '<%freshdesk-category-id%>'
18
+ -
19
+ crowdin_language_code: '<%crowdin-two-letters-code%>'
20
+ freshdesk_category_id: '<%freshdesk-category-id%>'
@@ -0,0 +1,8 @@
1
+ Feature: My bootstrapped app kinda works
2
+ In order to get going on coding my awesome app
3
+ I want to have aruba and cucumber setup
4
+ So I don't have to do it myself
5
+
6
+ Scenario: App just runs
7
+ When I get help for "fci"
8
+ Then the exit status should be 0
@@ -0,0 +1,6 @@
1
+ When /^I get help for "([^"]*)"$/ do |app_name|
2
+ @app_name = app_name
3
+ step %(I run `#{app_name} help`)
4
+ end
5
+
6
+ # Add more step definitions here
@@ -0,0 +1,15 @@
1
+ require 'aruba/cucumber'
2
+
3
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
4
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
5
+
6
+ Before do
7
+ # Using "announce" causes massive warnings on 1.9.2
8
+ @puts = true
9
+ @original_rubylib = ENV['RUBYLIB']
10
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
11
+ end
12
+
13
+ After do
14
+ ENV['RUBYLIB'] = @original_rubylib
15
+ end
@@ -0,0 +1,29 @@
1
+
2
+ desc 'Create a new FCI-based project'
3
+ arg 'project_name'
4
+ arg_name 'project_name'
5
+ skips_pre
6
+ command :'init:project' do |c|
7
+ c.desc 'Root dir of project'
8
+ c.long_desc <<-EOS.strip_heredoc
9
+ This is the directory where the project's directory will be made, so if you
10
+ specify a project name `foo` and the root dir of `.`, the directory
11
+ `./foo` will be created
12
+ EOS
13
+ c.flag :r, :root, :default_value => '.'
14
+
15
+ c.switch :force, :desc => 'Overwrite/ignore existing files and directories'
16
+
17
+ c.action do |global_options, options, args|
18
+ if args.length < 1
19
+ raise 'You must specify the name of your project'
20
+ end
21
+
22
+ root_dir = options[:root]
23
+ force = options[:force]
24
+ project_name = args.first
25
+
26
+ create_scaffold(root_dir, project_name, force)
27
+ end
28
+ end
29
+
@@ -0,0 +1,170 @@
1
+ desc 'Read folders/articles from Freshdesk and upload resource files to Crowdin'
2
+ command :'import:sources' do |c|
3
+ c.desc 'Directory of resource files'
4
+ c.long_desc <<-EOS.strip_heredoc
5
+ This is the directory where the project's files will be store.
6
+ EOS
7
+ c.default_value 'resources'
8
+ c.arg_name 'dir'
9
+ c.flag [:resources_dir]
10
+
11
+ c.desc 'Resources config file'
12
+ c.long_desc <<-EOS.strip_heredoc
13
+ This is the config file where the information about project's files will be store.
14
+ EOS
15
+ c.default_value 'resources/.resources.yml'
16
+ c.arg_name 'file'
17
+ c.flag [:resources_config]
18
+
19
+ c.action do |global_options, options, args|
20
+ resources_dir = options[:resources_dir]
21
+ unless File.exists?(resources_dir)
22
+ FileUtils.mkdir(resources_dir)
23
+ end
24
+ resources_config_file = options[:resources_config]
25
+
26
+ File.open(resources_config_file, 'a+') do |f|
27
+ config = YAML.load(f)
28
+ unless config # config file empty
29
+ config = {}
30
+ # initialize empty config file
31
+ f.write config.to_yaml
32
+ end
33
+ end
34
+
35
+ # for store information about folders/articles ids
36
+ resources_config = YAML.load(File.open(resources_config_file))
37
+
38
+ # Source Category
39
+ source_category_id = @fci_config['freshdesk_category'].to_i
40
+
41
+ # Check if Category exists in Freshdesk
42
+ source_category = FreshdeskAPI::SolutionCategory.find!(@freshdesk, id: source_category_id)
43
+ raise('No such category') unless source_category.id == source_category_id
44
+
45
+ # Get category's folders in Freshdesk
46
+ puts "[Freshdesk] Get folders for Category with id #{source_category_id}"
47
+ folders = @freshdesk.solution_folders(category_id: source_category_id).all!
48
+
49
+ folders_builder = []
50
+ folders.each do |folder|
51
+ folder_xml = build_folder_xml(folder)
52
+
53
+ # write to resources config file
54
+ unless folder_xml.nil?
55
+ resources_config[:folders] = [] unless resources_config[:folders]
56
+ unless resources_config[:folders].detect { |f| f[:id] == folder.id }
57
+ resources_config[:folders] << { id: folder.id }
58
+ end
59
+ end
60
+
61
+ unless folder_xml.nil?
62
+ folders_builder << build_folder_hash(folder).merge({ xml: folder_xml })
63
+ end
64
+ end
65
+
66
+ # Get folders articles
67
+ articles_builder = []
68
+ folders.each do |folder|
69
+ puts "[Freshdesk] Get articles for Folder with id #{folder.id}"
70
+ articles = @freshdesk.solution_articles(category_id: source_category_id, folder_id: folder.id).all!
71
+
72
+ articles.each do |article|
73
+ article_xml = build_article_xml(article)
74
+
75
+ # write to resources config file
76
+ if config_folder = resources_config[:folders].detect { |f| f[:id] == folder.id }
77
+ (config_folder[:articles] ||= []) << { id: article.id }
78
+ else
79
+ abort 'No such folder!'
80
+ end
81
+
82
+ unless article_xml.nil?
83
+ articles_builder << build_article_hash(article).merge({ xml: article_xml })
84
+ end
85
+ end
86
+ end
87
+
88
+
89
+ crowdin_project_info = @crowdin.project_info
90
+
91
+ # Creates xml files for folders and upload to Crowdin
92
+ folders_builder.each do |folder|
93
+ file_name = "folder_#{folder[:id]}.xml"
94
+
95
+ o = File.new(File.join(resources_dir, file_name), 'w')
96
+ o.write folder[:xml].to_xml
97
+ o.close
98
+
99
+ if crowdin_project_info['files'].detect { |file| file['name'] == file_name }
100
+ puts "[Crowdin] Update file `#{file_name}`"
101
+ @crowdin.update_file(
102
+ files = [
103
+ {
104
+ source: File.join(resources_dir, file_name),
105
+ dest: file_name,
106
+ export_pattert: '/%two_letters_code%/%original_file_name%',
107
+ title: folder[:name]
108
+ }
109
+ ], type: 'webxml'
110
+ )
111
+ else
112
+ puts "[Crowdin] Add file `#{file_name}`"
113
+ @crowdin.add_file(
114
+ files = [
115
+ {
116
+ source: File.join(resources_dir, file_name),
117
+ dest: file_name,
118
+ export_pattert: '/%two_letters_code%/%original_file_name%',
119
+ title: folder[:name]
120
+ }
121
+ ], type: 'webxml'
122
+ )
123
+ end
124
+ end
125
+
126
+ # Creates xml files for articles and upload to Crowdin
127
+ articles_builder.each do |article|
128
+ file_name = "article_#{article[:id]}.xml"
129
+
130
+ o = File.new(File.join(resources_dir, file_name), 'w')
131
+ o.write article[:xml].to_xml
132
+ o.close
133
+
134
+ if crowdin_project_info['files'].detect { |file| file['name'] == file_name }
135
+ puts "[Crowdin] Update file `#{file_name}`"
136
+ @crowdin.update_file(
137
+ files = [
138
+ {
139
+ source: File.join(resources_dir, file_name),
140
+ dest: file_name,
141
+ export_pattert: '/%two_letters_code%/%original_file_name%',
142
+ title: article[:title]
143
+ }
144
+ ], type: 'webxml'
145
+ )
146
+ else
147
+ puts "[Crowdin] Add file `#{file_name}`"
148
+ @crowdin.add_file(
149
+ files = [
150
+ {
151
+ source: File.join(resources_dir, file_name),
152
+ dest: file_name,
153
+ export_pattert: '/%two_letters_code%/%original_file_name%',
154
+ title: article[:title]
155
+ }
156
+ ], type: 'webxml'
157
+ )
158
+
159
+ end
160
+ end
161
+
162
+ # Write resources config file
163
+ puts "Write config file"
164
+ File.open(resources_config_file, 'w') do |f|
165
+ f.write resources_config.to_yaml
166
+ end
167
+
168
+ end
169
+ end
170
+
@@ -0,0 +1,32 @@
1
+ desc 'Build and download last exported translation resources from Crowdin'
2
+ command :'download:translations' do |c|
3
+ c.desc 'Directory of resource files'
4
+ c.long_desc <<-EOS.strip_heredoc
5
+ This is the directory where the project's files will be store.
6
+ EOS
7
+ c.default_value 'resources'
8
+ c.arg_name 'dir'
9
+ c.flag [:resources_dir]
10
+
11
+ c.action do |global_options, options, args|
12
+ language = 'all'
13
+ tempfile = Tempfile.new(language)
14
+ zipfile_name = tempfile.path
15
+ resources_dir = options[:resources_dir]
16
+
17
+ base_path = File.join(Dir.pwd, resources_dir)
18
+ begin
19
+ export_translations!(@crowdin)
20
+
21
+ puts 'Downloading translations'
22
+ @crowdin.download_translation(language, output: zipfile_name)
23
+
24
+ base_path = File.join(Dir.pwd, resources_dir)
25
+ unzip_file_with_translations(zipfile_name, base_path)
26
+ ensure
27
+ tempfile.close
28
+ tempfile.unlink # delete the tempfile
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,207 @@
1
+ desc 'Add or update localized resource files(folders and articles) in Freshdesk'
2
+ command :'export:translations' do |c|
3
+ c.desc 'Directory of resource files'
4
+ c.long_desc <<-EOS.strip_heredoc
5
+ This is the directory where the project's files will be store.
6
+ EOS
7
+ c.default_value 'resources'
8
+ c.arg_name 'dir'
9
+ c.flag [:resources_dir]
10
+
11
+ c.desc 'Resources config file'
12
+ c.long_desc <<-EOS.strip_heredoc
13
+ This is the config file where the information about project's files will be store.
14
+ EOS
15
+ c.default_value 'resources/.resources.yml'
16
+ c.arg_name 'file'
17
+ c.flag [:resources_config]
18
+
19
+ c.action do |global_options, options, args|
20
+ # DO NOT EDIT THIS FILE BY HAND!
21
+ resources_config_file = options[:resources_config]
22
+ resources_dir = options[:resources_dir]
23
+
24
+ # for store information about folders/articles ids
25
+ unless File.exists?(resources_config_file)
26
+ raise "Error! Config file does not exist. First run `push` command"
27
+ end
28
+
29
+ resources_config = YAML.load(File.open(resources_config_file))
30
+
31
+ if !resources_config || resources_config.nil? || resources_config.empty?
32
+ raise "Error! Resources config empty. First run `push` command"
33
+ end
34
+
35
+ @fci_config['translations'].each do |lang|
36
+ folder_xml_files = Dir["#{resources_dir}/#{lang['crowdin_language_code']}/folder_*.xml"]
37
+ article_xml_files = Dir["#{resources_dir}/#{lang['crowdin_language_code']}/article_*.xml"]
38
+
39
+ unless freshdesk_category = FreshdeskAPI::SolutionCategory.find(@freshdesk, id: lang['freshdesk_category_id'].to_i)
40
+ raise "Not such Category ID for language `#{lang['crowdin_language_code']}`in Freshdesk. Please create new one and set ID in config file."
41
+ end
42
+
43
+ all_folders = []
44
+ all_articles = []
45
+
46
+ # Read folders from XML files
47
+ folder_xml_files.each do |file|
48
+ # Load the xml file into a String
49
+ folder_xml_file = File.read(file)
50
+
51
+ folder = parse_folder_xml(folder_xml_file)
52
+
53
+ all_folders << folder
54
+ end
55
+
56
+ # Read articles from XML filse
57
+ article_xml_files.each do |file|
58
+ article_xml_file = File.read(file)
59
+
60
+ article = parse_article_xml(article_xml_file)
61
+
62
+ all_articles << article
63
+ end
64
+
65
+ ### Folders ###
66
+ #
67
+ all_folders.each do |folder|
68
+ if config_folder = resources_config[:folders].detect { |f| f[:id].to_s == folder[:id].to_s }
69
+
70
+ config_folder[:translations] = [] unless config_folder[:translations]
71
+
72
+ # if Folder translation ID exists in config
73
+ if folder_translation = config_folder[:translations].detect { |t| t[:lang] == lang['crowdin_language_code'] }
74
+ # Get folder from Freshdesk and update it
75
+ freshdesk_folder = FreshdeskAPI::SolutionFolder.find(
76
+ @freshdesk,
77
+ category_id: lang['freshdesk_category_id'].to_i,
78
+ id: folder_translation[:id]
79
+ )
80
+
81
+ # Remove Folder translation from config it it not found in Freshdesk
82
+ if freshdesk_folder.nil?
83
+ puts "Remove undefined Folder from config"
84
+ config_folder[:translations].delete_if { |tr| tr[:lang] == lang['crowdin_language_code'] }
85
+
86
+ puts "[Freshdesk] Create new Folder"
87
+ freshdesk_folder = FreshdeskAPI::SolutionFolder.create!(
88
+ @freshdesk,
89
+ category_id: lang['freshdesk_category_id'].to_i,
90
+ name: folder[:name],
91
+ description: folder[:description],
92
+ visibility: 1
93
+ )
94
+
95
+ config_folder[:translations] << { lang: lang['crowdin_language_code'], id: freshdesk_folder.id }
96
+
97
+ end
98
+
99
+ if freshdesk_folder.attributes[:name] != folder[:name] || freshdesk_folder.attributes[:description] != folder[:description]
100
+ puts "[Freshdesk] Update existing Folder"
101
+ freshdesk_folder.update!(name: folder[:name], description: folder[:description])
102
+ else
103
+ puts "[Freshdesk] Nothing to update. An existing Folder still the same."
104
+ end
105
+
106
+ else
107
+ # create new folder in Freshdesk and save id to config file
108
+ puts "[Freshdesk] Create new Folder"
109
+ freshdesk_folder = FreshdeskAPI::SolutionFolder.create!(
110
+ @freshdesk,
111
+ category_id: lang['freshdesk_category_id'].to_i,
112
+ name: folder[:name],
113
+ description: folder[:description],
114
+ visibility: 1
115
+ )
116
+
117
+ config_folder[:translations] << { lang: lang['crowdin_language_code'], id: freshdesk_folder.id }
118
+ end
119
+ else
120
+ abort "No such folder!"
121
+ end
122
+ end # all_folders
123
+
124
+ ### Articles ###
125
+ #
126
+ all_articles.each do |article|
127
+ if config_folder = resources_config[:folders].detect { |f| f[:id].to_s == article[:folder_id].to_s }
128
+ unless folder_translation = config_folder[:translations].detect { |t| t[:lang] == lang['crowdin_language_code'] }
129
+ abort "No `#{lang['crowdin_language_code']}` translations for folder"
130
+ end
131
+
132
+ if config_article = config_folder[:articles].detect { |f| f[:id].to_s == article[:id].to_s }
133
+ config_article[:translations] = [] unless config_article[:translations]
134
+
135
+ # if Article translation ID exists in config - update article on Freshdesk
136
+ if article_translation = config_article[:translations].detect { |t| t[:lang] == lang['crowdin_language_code'] }
137
+ freshdesk_article = FreshdeskAPI::SolutionArticle.find(
138
+ @freshdesk,
139
+ category_id: lang['freshdesk_category_id'].to_i,
140
+ folder_id: folder_translation[:id],
141
+ id: article_translation[:id]
142
+ )
143
+
144
+ # Remove Article translation from config if it not found in Freshdesk
145
+ if freshdesk_article.nil?
146
+ puts "Remove undefined Article from config"
147
+ config_article[:translations].delete_if { |tr| tr[:lang] == lang['crowdin_language_code'] }
148
+
149
+ puts "[Freshdesk] Create new Article"
150
+ freshdesk_article = FreshdeskAPI::SolutionArticle.create!(
151
+ @freshdesk,
152
+ category_id: lang['freshdesk_category_id'].to_i,
153
+ folder_id: folder_translation[:id],
154
+ title: article[:title],
155
+ description: article[:description]
156
+ )
157
+ config_article[:translations] << { lang: lang['crowdin_language_code'], id: freshdesk_article.id }
158
+ next
159
+ end
160
+
161
+ if freshdesk_article.attributes[:title] != article[:title] || freshdesk_article.attributes[:description] != article[:description]
162
+ puts "[Freshdesk] Update existing Article"
163
+
164
+ freshdesk_article.update!(
165
+ title: article[:title],
166
+ description: article[:description]
167
+ )
168
+ else
169
+ puts "[Freshdesk] Nothing to update. An existing Article still the same."
170
+ end
171
+
172
+ else
173
+ # creates new article on Freshdesk and save ID to config file
174
+ if folder_translation = config_folder[:translations].detect { |t| t[:lang] == lang['crowdin_language_code'] }
175
+ # do nothing for now
176
+ else
177
+ abort "No translation for this folder"
178
+ end
179
+
180
+ puts "[Freshdesk] Create new Article"
181
+ freshdesk_article = FreshdeskAPI::SolutionArticle.create!(
182
+ @freshdesk,
183
+ category_id: lang['freshdesk_category_id'].to_i,
184
+ folder_id: folder_translation[:id],
185
+ title: article[:title],
186
+ description: article[:description]
187
+ )
188
+ config_article[:translations] << { lang: lang['crowdin_language_code'], id: freshdesk_article.id }
189
+ end
190
+ else
191
+ abort "No such article!"
192
+ end
193
+ else
194
+ abort "No such folder!"
195
+ end
196
+ end # all_articles
197
+
198
+ puts "Write info about localization to config"
199
+ File.open(resources_config_file, 'w') do |f|
200
+ f.write resources_config.to_yaml
201
+ end
202
+
203
+ end # @fci_config['translations']
204
+
205
+ end
206
+ end
207
+
@@ -0,0 +1,31 @@
1
+ module FCI
2
+ def unzip_file_with_translations(zipfile_name, dest_path)
3
+ # overwrite files if they already exist inside of the extracted path
4
+ Zip.on_exists_proc = true
5
+
6
+ Zip::File.open(zipfile_name) do |zip_file|
7
+ zip_file.select { |zip_entry| zip_entry.file? }.each do |f|
8
+ # `f' - relative path in archive
9
+ fpath = File.join(dest_path, f.name)
10
+ FileUtils.mkdir_p(File.dirname(fpath))
11
+ puts "Extracting: `#{dest_path}/#{f.name}'"
12
+ zip_file.extract(f, fpath)
13
+ end
14
+ end
15
+ end
16
+
17
+ # use export API method before to download the most recent translations
18
+ def export_translations!(crowdin)
19
+ print 'Building ZIP archive with the latest translations '
20
+ export_translations = crowdin.export_translations
21
+ if export_translations['success']
22
+ if export_translations['success']['status'] == 'built'
23
+ puts "- OK"
24
+ elsif export_translations['success']['status'] == 'skipped'
25
+ puts "- Skipped"
26
+ puts "Warning: Export was skipped. Please note that this method can be invoked only once per 30 minutes."
27
+ end
28
+ end
29
+ end
30
+
31
+ end
data/lib/fci/export.rb ADDED
@@ -0,0 +1,31 @@
1
+ module FCI
2
+ def parse_folder_xml(folder_xml_file)
3
+ doc = Nokogiri::XML.parse(folder_xml_file)
4
+ folder_xml = doc.xpath("//folder").first
5
+
6
+ folder = {
7
+ id: folder_xml[:id],
8
+ name: folder_xml.xpath('name').text,
9
+ description: folder_xml.xpath('name').text,
10
+ position: folder_xml[:position],
11
+ }
12
+
13
+ return folder
14
+ end
15
+
16
+ def parse_article_xml(article_xml_file)
17
+ doc = Nokogiri::XML.parse(article_xml_file)
18
+ article_xml = doc.xpath('//article').first
19
+
20
+ article = {
21
+ id: article_xml[:id],
22
+ position: article_xml[:position],
23
+ folder_id: article_xml[:folder_id],
24
+ title: article_xml.xpath('title').text,
25
+ description: article_xml.xpath('description').text,
26
+ }
27
+
28
+ return article
29
+ end
30
+
31
+ end
@@ -0,0 +1,6 @@
1
+ class String
2
+ def strip_heredoc
3
+ indent = scan(/^[ \t]*(?=\S)/).min.size || 0
4
+ gsub(/^[ \t]{#{indent}}/, '')
5
+ end
6
+ end
data/lib/fci/import.rb ADDED
@@ -0,0 +1,66 @@
1
+ module FCI
2
+ def build_folder_xml(folder)
3
+ attr = folder.attributes
4
+
5
+ folder_xml = Nokogiri::XML::Builder.new do |xml|
6
+ xml.root {
7
+ # id - id of the original folder
8
+ xml.folder(id: folder.id, position: attr[:position], identifier: 'folder', type: 'document') {
9
+ xml.name {
10
+ xml.cdata attr[:name]
11
+ }
12
+ xml.description {
13
+ xml.cdata attr[:description]
14
+ }
15
+ }
16
+ }
17
+ end
18
+
19
+ return folder_xml
20
+ end
21
+
22
+ def build_article_xml(article)
23
+ attr = article.attributes
24
+
25
+ article_xml = Nokogiri::XML::Builder.new do |xml|
26
+ xml.root {
27
+ # id - id of the original acticle
28
+ # folder_id - id of the original folder
29
+ xml.article(id: article.id, folder_id: attr[:folder_id], position: attr[:position], identifier: 'article', type: 'document') {
30
+ xml.title {
31
+ xml.cdata attr[:title]
32
+ }
33
+ xml.description {
34
+ xml.cdata attr[:description]
35
+ }
36
+ }
37
+ }
38
+ end
39
+
40
+ return article_xml
41
+ end
42
+
43
+ def build_folder_hash(folder)
44
+ attr = folder.attributes
45
+
46
+ return {
47
+ id: folder.id,
48
+ position: attr[:position],
49
+ name: attr[:name],
50
+ description: attr[:description],
51
+ }
52
+ end
53
+
54
+ def build_article_hash(article)
55
+ attr = article.attributes
56
+
57
+ return {
58
+ id: article.id,
59
+ folder_id: attr[:folder_id],
60
+ position: attr[:position],
61
+ title: attr[:title],
62
+ description: attr[:description],
63
+ }
64
+ end
65
+
66
+ end
data/lib/fci/init.rb ADDED
@@ -0,0 +1,62 @@
1
+ require 'fileutils'
2
+
3
+ module FCI
4
+ def create_scaffold(root_dir, project_name, force)
5
+ dir = File.join(root_dir, project_name)
6
+
7
+ if mkdir(dir, force)
8
+ mk_config(root_dir, project_name)
9
+ end
10
+ end
11
+
12
+ def mkdir(dir, force)
13
+ exists = false
14
+ if !force
15
+ if File.exist? dir
16
+ raise "#{dir} exists; use --force to override"
17
+ exists = true
18
+ end
19
+ end
20
+
21
+ if !exists
22
+ puts "Creating dir #{dir}..."
23
+ FileUtils.mkdir_p dir
24
+ else
25
+ puts "Exiting..."
26
+ false
27
+ end
28
+
29
+ true
30
+ end
31
+
32
+ def mk_config(root_dir, project_name)
33
+ config = <<-EOS.strip_heredoc
34
+ ---
35
+ # Crowdin API credentials
36
+ crowdin_project_id: '<%your-crowdin-project-id%>'
37
+ crowdin_api_key: '<%your-crowdin-api-key%>'
38
+ crowdin_base_url: 'https://api.crowdin.com'
39
+
40
+ # Freshdesk API credentials
41
+ freshdesk_base_url: 'https://<%subdomain%>.freshdesk.com'
42
+ freshdesk_username: '<%your-freshdek-username%>'
43
+ freshdesk_password: '<%your-freshdesk-password%>'
44
+
45
+ freshdesk_category: '<%freshdesk-category-id%>'
46
+
47
+ translations:
48
+ -
49
+ crowdin_language_code: '<%crowdin-two-letters-code%>'
50
+ freshdesk_category_id: '<%freshdesk-category-id%>'
51
+ -
52
+ crowdin_language_code: '<%crowdin-two-letters-code%>'
53
+ freshdesk_category_id: '<%freshdesk-category-id%>'
54
+ EOS
55
+
56
+ File.open("#{root_dir}/#{project_name}/fci.yml", 'w') do |file|
57
+ file << config
58
+ end
59
+
60
+ puts "Created #{root_dir}/#{project_name}/fci.yml"
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ module FCI
2
+ VERSION = '0.0.1'
3
+ end
data/lib/fci.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'freshdesk_api'
2
+ require 'crowdin-api'
3
+ require 'byebug'
4
+ require 'nokogiri'
5
+ require 'zip'
6
+ require 'fci/version.rb'
7
+
8
+ # Add requires for other files you add to your project here, so
9
+ # you just need to require this one file in your bin file
10
+ require 'fci/helpers.rb'
11
+ require 'fci/init.rb'
12
+ require 'fci/import.rb'
13
+ require 'fci/download.rb'
14
+ require 'fci/export.rb'
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+
3
+ class DefaultTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def teardown
9
+ end
10
+
11
+ def test_the_truth
12
+ assert true
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ require 'test/unit'
2
+
3
+ # Add test libraries you want to use here, e.g. mocha
4
+
5
+ class Test::Unit::TestCase
6
+
7
+ # Add global extensions to the test case class here
8
+ end
metadata ADDED
@@ -0,0 +1,196 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fci
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Anton Maminov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubyzip
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: crowdin-api
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: freshdesk_api
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rdoc
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: aruba
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: gli
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 2.13.0
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 2.13.0
139
+ description:
140
+ email: anton.maminov@gmail.com
141
+ executables:
142
+ - fci
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".ruby-gemset"
148
+ - ".ruby-version"
149
+ - Gemfile
150
+ - LICENSE
151
+ - README.md
152
+ - Rakefile
153
+ - bin/fci
154
+ - fci.gemspec
155
+ - fci.yml.example
156
+ - features/fci.feature
157
+ - features/step_definitions/fci_steps.rb
158
+ - features/support/env.rb
159
+ - lib/fci.rb
160
+ - lib/fci/commands/01_init.rb
161
+ - lib/fci/commands/02_import.rb
162
+ - lib/fci/commands/03_download.rb
163
+ - lib/fci/commands/04_export.rb
164
+ - lib/fci/download.rb
165
+ - lib/fci/export.rb
166
+ - lib/fci/helpers.rb
167
+ - lib/fci/import.rb
168
+ - lib/fci/init.rb
169
+ - lib/fci/version.rb
170
+ - test/default_test.rb
171
+ - test/test_helper.rb
172
+ homepage: https://github.com/mamantoha/fci
173
+ licenses: []
174
+ metadata: {}
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 2.4.6
193
+ signing_key:
194
+ specification_version: 4
195
+ summary: Freshdesk and Crowdin integration Command Line Interface (CLI)
196
+ test_files: []