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