nabokov 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +1 -0
- data/.gitignore +43 -0
- data/.rspec +2 -0
- data/.rubocop.yml +113 -0
- data/.travis.yml +14 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +16 -0
- data/Rakefile +22 -0
- data/bin/nabokov +5 -0
- data/lib/nabokov/commands/runner.rb +22 -0
- data/lib/nabokov/commands/setup.rb +75 -0
- data/lib/nabokov/commands/syncers/localizations_repo_syncer.rb +101 -0
- data/lib/nabokov/commands/syncers/project_syncer.rb +106 -0
- data/lib/nabokov/commands/syncers/syncer.rb +68 -0
- data/lib/nabokov/core/file_manager.rb +36 -0
- data/lib/nabokov/core/nabokovfile.rb +66 -0
- data/lib/nabokov/core/nabokovfile_content_validator.rb +51 -0
- data/lib/nabokov/core/nabokovfile_keys.rb +34 -0
- data/lib/nabokov/git/git_repo.rb +137 -0
- data/lib/nabokov/helpers/informator.rb +83 -0
- data/lib/nabokov/helpers/merger.rb +86 -0
- data/lib/nabokov/models/strings_file.rb +7 -0
- data/lib/nabokov/version.rb +8 -0
- data/lib/nabokov.rb +14 -0
- data/nabokov.gemspec +31 -0
- data/spec/fixtures/.DS_Store +0 -0
- data/spec/fixtures/README.md +1 -0
- data/spec/fixtures/de.strings +1 -0
- data/spec/fixtures/en.strings +1 -0
- data/spec/fixtures/nabokovfile_example.yaml +9 -0
- data/spec/fixtures/nabokovfile_example_invalid.yaml +2 -0
- data/spec/fixtures/nabokovfile_example_without_master_branch.yaml +8 -0
- data/spec/fixtures/test_git_setup/existed_pre_commit_file +0 -0
- data/spec/fixtures/test_git_setup/existed_pre_commit_file_alias +0 -0
- data/spec/fixtures/test_git_setup/not_executable_pre_commit_file +0 -0
- data/spec/fixtures/test_localizations_repo_syncer/localizations_repo_fixtures/README.md +1 -0
- data/spec/fixtures/test_localizations_repo_syncer/nabokovfile.yaml +8 -0
- data/spec/fixtures/test_project_syncer/localizations_repo_fixtures/de.strings +2 -0
- data/spec/fixtures/test_project_syncer/localizations_repo_fixtures/en.strings +2 -0
- data/spec/fixtures/test_project_syncer/project_repo_fixtures/de.strings +2 -0
- data/spec/fixtures/test_project_syncer/project_repo_fixtures/en.strings +2 -0
- data/spec/fixtures/test_project_syncer/project_repo_fixtures/nabokovfile.yaml +9 -0
- data/spec/lib/nabokov/commands/localizations_repo_syncer_spec.rb +137 -0
- data/spec/lib/nabokov/commands/project_syncer_spec.rb +61 -0
- data/spec/lib/nabokov/commands/runner_spec.rb +7 -0
- data/spec/lib/nabokov/commands/setup_spec.rb +101 -0
- data/spec/lib/nabokov/commands/syncer_spec.rb +23 -0
- data/spec/lib/nabokov/core/file_manager_spec.rb +115 -0
- data/spec/lib/nabokov/core/nabokovfile_content_validator_spec.rb +155 -0
- data/spec/lib/nabokov/core/nabokovfile_keyes_spec.rb +31 -0
- data/spec/lib/nabokov/core/nabokovfile_spec.rb +53 -0
- data/spec/lib/nabokov/git/git_repo_spec.rb +670 -0
- data/spec/lib/nabokov/helpers/informator_spec.rb +49 -0
- data/spec/lib/nabokov/helpers/merger_spec.rb +114 -0
- data/spec/lib/nabokov/models/strings_file_spec.rb +7 -0
- data/spec/spec_helper.rb +11 -0
- metadata +238 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dd71abc02d1c0469d5514b59900199790b2e9b8b
|
4
|
+
data.tar.gz: 355a0707bbe1ea7196595098da5450a948868be1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dc9ce5a4b6104c0b69074553d3a72ef7f49ae69ab83dd00a7051d44cd54ef69c0642abe87d5ad9d3e4c987fb1541be7a1cb181b1ad7712313ef411d2d7fbdd1e
|
7
|
+
data.tar.gz: 828af0acfaa649d4713954b55d711a3c4a7fc32553ed47e79b87f5249a0b1e8ed1ee0b901243631db111d4d1b0c8851d039c83aaef7fa2a86c00ea2d7f4fec89
|
data/.gitattributes
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
CHANGELOG.md merge=union
|
data/.gitignore
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
.DS_Store
|
2
|
+
|
3
|
+
/.bundle/
|
4
|
+
/.yardoc
|
5
|
+
/Gemfile.lock
|
6
|
+
/_yardoc/
|
7
|
+
/coverage/
|
8
|
+
/doc/
|
9
|
+
/pkg/
|
10
|
+
/spec/reports/
|
11
|
+
/tmp/
|
12
|
+
/test/tmp/
|
13
|
+
/test/version_tmp/
|
14
|
+
/tmp/
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
|
21
|
+
## Documentation cache and generated files:
|
22
|
+
/.yardoc/
|
23
|
+
/_yardoc/
|
24
|
+
/doc/
|
25
|
+
/rdoc/
|
26
|
+
|
27
|
+
## Environment normalisation:
|
28
|
+
/.bundle/
|
29
|
+
/vendor/bundle
|
30
|
+
/lib/bundler/man/
|
31
|
+
/vendor/
|
32
|
+
|
33
|
+
# for a library or gem, you might want to ignore these files since the code is
|
34
|
+
# intended to run in multiple environments; otherwise, check them in:
|
35
|
+
# Gemfile.lock
|
36
|
+
# .ruby-version
|
37
|
+
# .ruby-gemset
|
38
|
+
|
39
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
40
|
+
.rvmrc
|
41
|
+
fastlane/report.xml
|
42
|
+
.keys
|
43
|
+
.idea
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- 'spec/fixtures/**/*'
|
4
|
+
|
5
|
+
Style/StringLiterals:
|
6
|
+
EnforcedStyle: double_quotes
|
7
|
+
Enabled: true
|
8
|
+
|
9
|
+
# kind_of? is a good way to check a type
|
10
|
+
Style/ClassCheck:
|
11
|
+
EnforcedStyle: kind_of?
|
12
|
+
|
13
|
+
# It's better to be more explicit about the type
|
14
|
+
Style/BracesAroundHashParameters:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
# specs sometimes have useless assignments, which is fine
|
18
|
+
Lint/UselessAssignment:
|
19
|
+
Exclude:
|
20
|
+
- '**/spec/**/*'
|
21
|
+
|
22
|
+
# We could potentially enable the 2 below:
|
23
|
+
Style/IndentHash:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/AlignHash:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
# HoundCI doesn't like this rule
|
30
|
+
Style/DotPosition:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
# We allow !! as it's an easy way to convert ot boolean
|
34
|
+
Style/DoubleNegation:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
# Cop supports --auto-correct.
|
38
|
+
Lint/UnusedBlockArgument:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
# We want to allow class Fastlane::Class
|
42
|
+
Style/ClassAndModuleChildren:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Metrics/AbcSize:
|
46
|
+
Max: 60
|
47
|
+
|
48
|
+
# The %w might be confusing for new users
|
49
|
+
Style/WordArray:
|
50
|
+
MinSize: 19
|
51
|
+
|
52
|
+
# raise and fail are both okay
|
53
|
+
Style/SignalException:
|
54
|
+
Enabled: false
|
55
|
+
|
56
|
+
# Better too much 'return' than one missing
|
57
|
+
Style/RedundantReturn:
|
58
|
+
Enabled: false
|
59
|
+
|
60
|
+
# Having if in the same line might not always be good
|
61
|
+
Style/IfUnlessModifier:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
# and and or is okay
|
65
|
+
Style/AndOr:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
# Configuration parameters: CountComments.
|
69
|
+
Metrics/ClassLength:
|
70
|
+
Max: 320
|
71
|
+
|
72
|
+
Metrics/CyclomaticComplexity:
|
73
|
+
Max: 17
|
74
|
+
|
75
|
+
# Configuration parameters: AllowURI, URISchemes.
|
76
|
+
Metrics/LineLength:
|
77
|
+
Max: 370
|
78
|
+
|
79
|
+
# Configuration parameters: CountKeywordArgs.
|
80
|
+
Metrics/ParameterLists:
|
81
|
+
Max: 10
|
82
|
+
|
83
|
+
Metrics/PerceivedComplexity:
|
84
|
+
Max: 18
|
85
|
+
|
86
|
+
# Sometimes it's easier to read without guards
|
87
|
+
Style/GuardClause:
|
88
|
+
Enabled: false
|
89
|
+
|
90
|
+
# something = if something_else
|
91
|
+
# that's confusing
|
92
|
+
Style/ConditionalAssignment:
|
93
|
+
Enabled: false
|
94
|
+
|
95
|
+
# Better to have too much self than missing a self
|
96
|
+
Style/RedundantSelf:
|
97
|
+
Enabled: false
|
98
|
+
|
99
|
+
Metrics/MethodLength:
|
100
|
+
Max: 60
|
101
|
+
|
102
|
+
# We're not there yet
|
103
|
+
Style/Documentation:
|
104
|
+
Enabled: false
|
105
|
+
|
106
|
+
# Adds complexity
|
107
|
+
Style/IfInsideElse:
|
108
|
+
Enabled: false
|
109
|
+
|
110
|
+
# danger specific
|
111
|
+
|
112
|
+
Style/BlockComments:
|
113
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Anton Domashnev
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Nabokov
|
2
|
+
Move mobile localization process up to the next level 🚀
|
3
|
+
|
4
|
+
|
5
|
+
[![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/Antondomashnev/nabokov/blob/master/LICENSE)
|
6
|
+
|
7
|
+
## Current status
|
8
|
+
This project is on the early development stage and is *not ready for production*. The `master` branch is empty and now the current status can be checked in `develop` branch. Also readme is not ready at all and we'll be updated as soon as the first functional becomes to the `master` branch.
|
9
|
+
|
10
|
+
## Behind the scene
|
11
|
+
This is my first ruby project except small experiments on the local machine before. So please bare it in mind and I would really appreciate any point about code style or even better way to implement something
|
12
|
+
|
13
|
+
## License
|
14
|
+
This project is open source under the MIT license, which means you have full access to the source code and can modify it to fit your own needs.
|
15
|
+
|
16
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rubocop/rake_task"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "rspec/core/rake_task"
|
6
|
+
RSpec::Core::RakeTask.new(:specs)
|
7
|
+
rescue LoadError
|
8
|
+
puts "Please use `bundle exec` to get all the rake commands"
|
9
|
+
end
|
10
|
+
|
11
|
+
task default: :spec
|
12
|
+
|
13
|
+
desc "Nabokov's tests"
|
14
|
+
task :spec do
|
15
|
+
Rake::Task["specs"].invoke
|
16
|
+
Rake::Task["rubocop"].invoke
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Run RuboCop on the lib/specs directory"
|
20
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
21
|
+
task.patterns = Dir.glob(["lib/**/*.rb", "spec/**/*.rb"]) - Dir.glob(["spec/fixtures/**/*"])
|
22
|
+
end
|
data/bin/nabokov
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "nabokov/version"
|
2
|
+
require "nabokov/helpers/informator"
|
3
|
+
require "claide"
|
4
|
+
require "cork"
|
5
|
+
|
6
|
+
module Nabokov
|
7
|
+
class Runner < CLAide::Command
|
8
|
+
self.abstract_command = true
|
9
|
+
self.summary = "Nabokov, the simple tool to keep your mobile app localization up to date."
|
10
|
+
self.version = Nabokov::VERSION
|
11
|
+
self.command = "nabokov"
|
12
|
+
|
13
|
+
def initialize(argv)
|
14
|
+
super
|
15
|
+
@cork = Cork::Board.new(silent: argv.option("silent", false), verbose: argv.option("verbose", false))
|
16
|
+
end
|
17
|
+
|
18
|
+
def ui
|
19
|
+
@ui ||= Informator.new(@cork)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "nabokov/commands/runner"
|
2
|
+
require "fileutils"
|
3
|
+
require "nabokov/core/nabokovfile"
|
4
|
+
require "nabokov/core/file_manager"
|
5
|
+
require "nabokov/git/git_repo"
|
6
|
+
|
7
|
+
module Nabokov
|
8
|
+
# Command to setup the project repo to use nabokov
|
9
|
+
# It setups the pre commit hook to start Nabokov::LocalizationsRepoSyncer
|
10
|
+
class Setup < Runner
|
11
|
+
self.summary = "Setups the repository hook to sync app localizations."
|
12
|
+
self.command = "setup"
|
13
|
+
self.description = "Installs the pre-commit git hook with the logic to run 'nabokov sync localizations'"
|
14
|
+
|
15
|
+
attr_reader :pre_commit_file
|
16
|
+
|
17
|
+
def initialize(argv)
|
18
|
+
@pre_commit_file = argv.option("pre_commit_file")
|
19
|
+
@pre_commit_file ||= default_pre_commit_file
|
20
|
+
@git_path = argv.option("git_path")
|
21
|
+
@git_path ||= default_git_path
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
ensure_pre_commit_file_exists
|
27
|
+
ensure_pre_commit_file_is_executable
|
28
|
+
ensure_hook_is_installed
|
29
|
+
ui.important "nabokov pre commit git hook is installed"
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def ensure_pre_commit_file_exists
|
36
|
+
@pre_commit_file = File.realpath(@pre_commit_file) if File.symlink?(@pre_commit_file)
|
37
|
+
return if File.exist?(@pre_commit_file)
|
38
|
+
|
39
|
+
raise ".git folder is not found at '#{@git_path}'" unless Dir.exist?(@git_path)
|
40
|
+
|
41
|
+
FileUtils.mkdir_p("#{@git_path}/hooks")
|
42
|
+
@pre_commit_file = "#{@git_path}/hooks/pre-commit"
|
43
|
+
FileUtils.touch(@pre_commit_file)
|
44
|
+
FileUtils.chmod("u=xwr", @pre_commit_file)
|
45
|
+
end
|
46
|
+
|
47
|
+
def ensure_pre_commit_file_is_executable
|
48
|
+
raise "pre commit file at '#{@pre_commit_file}' is not executable by the effective user id of this process" unless File.executable?(@pre_commit_file)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ensure_hook_is_installed
|
52
|
+
git_repo_path = ""
|
53
|
+
IO.popen("git rev-parse --show-toplevel", "r+") do |pipe|
|
54
|
+
git_repo_path = pipe.read
|
55
|
+
end
|
56
|
+
return if File.foreach(@pre_commit_file).grep(/git_repo_path/).any?
|
57
|
+
|
58
|
+
File.open(@pre_commit_file, "r+") do |f|
|
59
|
+
f.puts("#!/usr/bin/env bash")
|
60
|
+
f.puts("current_repo_path=\$(git rev-parse --show-toplevel)")
|
61
|
+
f.puts("nabokovfile_path=\"$current_repo_path/Nabokovfile.yaml\"")
|
62
|
+
f.puts("tracking_repo_path=\"#{git_repo_path.strip}\"")
|
63
|
+
f.puts("if [ \"$current_repo_path\" == \"$tracking_repo_path\" ] && gem list -i nabokov && [ -e \"$nabokovfile_path\" ]; then nabokov sync localizations --nabokovfile=$nabokovfile_path || exit 1; fi")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def default_git_path
|
68
|
+
".git"
|
69
|
+
end
|
70
|
+
|
71
|
+
def default_pre_commit_file
|
72
|
+
"#{default_git_path}/hooks/pre-commit"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "nabokov/commands/syncers/syncer"
|
2
|
+
require "nabokov/core/file_manager"
|
3
|
+
require "nabokov/helpers/merger"
|
4
|
+
|
5
|
+
module Nabokov
|
6
|
+
class LocalizationsRepoSyncer < Syncer
|
7
|
+
self.abstract_command = false
|
8
|
+
self.command = "localizations"
|
9
|
+
self.summary = "Synchronize remote localizations repo with the project localization strings."
|
10
|
+
|
11
|
+
def initialize(argv)
|
12
|
+
super
|
13
|
+
@synchronized_file_names = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate!
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.options
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
ui.important("Nabokov starts localizations repo synchronization")
|
26
|
+
super
|
27
|
+
checkout_temporary_branch
|
28
|
+
has_changes = update_localization_files
|
29
|
+
checkout_master_branch
|
30
|
+
fetch_master_branch_changes
|
31
|
+
if has_changes && merge_master_branch_with_temporary(@rescue_commit_sha) == Nabokov::MergerResult::SUCCEEDED
|
32
|
+
push_changes_to_remote
|
33
|
+
end
|
34
|
+
delete_temporary_branch
|
35
|
+
notify_user_about_finish
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def notify_user_about_finish
|
41
|
+
if @synchronized_file_names.count > 0
|
42
|
+
ui.say("#{@synchronized_file_names} have been updated on remote, your are all set for now 🎉")
|
43
|
+
else
|
44
|
+
ui.say("Nothing to synchronize, localizations in the remote repo and project repo are the same.")
|
45
|
+
end
|
46
|
+
ui.important("Nabokov has finished localizations remote repo synchronization")
|
47
|
+
end
|
48
|
+
|
49
|
+
def checkout_temporary_branch
|
50
|
+
ui.say("Checkout a temporary branch for new localization strings...") if self.verbose
|
51
|
+
self.git_repo.checkout_branch(temporary_branch)
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_localization_files
|
55
|
+
has_changes = false
|
56
|
+
self.nabokovfile.project_localization_file_paths.each do |localization_file_name, localization_file_path|
|
57
|
+
ui.say("Copying strings file from '#{localization_file_path}' to the localization repo...") if self.verbose
|
58
|
+
new_file_path = FileManager.copy_and_rename(localization_file_path, self.git_repo.local_path, localization_file_name.to_s)
|
59
|
+
self.git_repo.add(new_file_path)
|
60
|
+
if self.git_repo.changes?
|
61
|
+
@synchronized_file_names << localization_file_name
|
62
|
+
self.git_repo.commit("Nabokov localization file '#{localization_file_name}' update...")
|
63
|
+
has_changes = true
|
64
|
+
else
|
65
|
+
ui.say("'#{localization_file_name}' file doesn't have any changes to commit...") if self.verbose
|
66
|
+
end
|
67
|
+
end
|
68
|
+
has_changes
|
69
|
+
end
|
70
|
+
|
71
|
+
def checkout_master_branch
|
72
|
+
ui.say("Checkout master branch...") if self.verbose
|
73
|
+
self.git_repo.checkout_branch(self.nabokovfile.localizations_repo_master_branch)
|
74
|
+
@rescue_commit_sha = self.git_repo.log(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
def fetch_master_branch_changes
|
78
|
+
ui.say("Fetching remote master branch changes...") if self.verbose
|
79
|
+
self.git_repo.pull
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete_temporary_branch
|
83
|
+
ui.say("Deleting temporary branch...") if self.verbose
|
84
|
+
self.git_repo.delete_branch(temporary_branch)
|
85
|
+
end
|
86
|
+
|
87
|
+
def push_changes_to_remote
|
88
|
+
ui.say("Pushing changes to remote...") if self.verbose
|
89
|
+
self.git_repo.push
|
90
|
+
end
|
91
|
+
|
92
|
+
def merge_master_branch_with_temporary(rescue_commit_sha = nil)
|
93
|
+
merger = Merger.new(ui, self.git_repo, rescue_commit_sha)
|
94
|
+
merger.merge(self.nabokovfile.localizations_repo_master_branch, temporary_branch)
|
95
|
+
end
|
96
|
+
|
97
|
+
def temporary_branch
|
98
|
+
"nabokov/temporary_branch"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "nabokov/commands/syncers/syncer"
|
2
|
+
require "nabokov/core/file_manager"
|
3
|
+
require "nabokov/helpers/merger"
|
4
|
+
require "nabokov/models/strings_file"
|
5
|
+
|
6
|
+
module Nabokov
|
7
|
+
class ProjectSyncer < Syncer
|
8
|
+
self.abstract_command = false
|
9
|
+
self.command = "project"
|
10
|
+
self.summary = "Synchronize project localization strings with the remote localizations repo."
|
11
|
+
|
12
|
+
def initialize(argv)
|
13
|
+
super
|
14
|
+
@synchronized_file_names = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate!
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.options
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
ui.important("Nabokov starts project repo synchronization")
|
27
|
+
super
|
28
|
+
fetch_localization_repo_master_branch_changes
|
29
|
+
init_project_git_repo
|
30
|
+
checkout_project_repo_temporary_branch
|
31
|
+
has_changes = update_localization_files_in_project_repo
|
32
|
+
checkout_project_repo_original_branch
|
33
|
+
if has_changes
|
34
|
+
merge_project_repo_original_branch_with_temporary
|
35
|
+
end
|
36
|
+
delete_temporary_branch
|
37
|
+
notify_user_about_finish
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def notify_user_about_finish
|
43
|
+
if @synchronized_file_names.count > 0
|
44
|
+
ui.say("#{@synchronized_file_names} have been updated in project, your are all set for now 🎉")
|
45
|
+
else
|
46
|
+
ui.say("Nothing to synchronize, localizations in the remote repo and project repo are the same.")
|
47
|
+
end
|
48
|
+
ui.important("Nabokov has finished project repo synchronization")
|
49
|
+
end
|
50
|
+
|
51
|
+
def init_project_git_repo
|
52
|
+
@project_git_repo = GitRepo.new(@nabokovfile.project_local_path)
|
53
|
+
raise "Could not find the project repo at '#{Dir.exist?(@project_git_repo.local_path)}'" unless Dir.exist?(@project_git_repo.local_path)
|
54
|
+
ui.say("Found existed project repo at #{@project_git_repo.local_path}...") if self.verbose
|
55
|
+
@project_git_repo.init
|
56
|
+
@project_repo_original_branch = @project_git_repo.current_branch
|
57
|
+
end
|
58
|
+
|
59
|
+
def merge_project_repo_original_branch_with_temporary
|
60
|
+
merger = Merger.new(ui, @project_git_repo)
|
61
|
+
merger.merge(@project_repo_original_branch, temporary_branch)
|
62
|
+
end
|
63
|
+
|
64
|
+
def checkout_project_repo_temporary_branch
|
65
|
+
ui.say("Checkout porject repo temporary branch...") if self.verbose
|
66
|
+
@project_git_repo.checkout_branch(temporary_branch)
|
67
|
+
end
|
68
|
+
|
69
|
+
def checkout_project_repo_original_branch
|
70
|
+
ui.say("Checkout project repo #{@project_repo_original_branch} branch...") if self.verbose
|
71
|
+
@project_git_repo.checkout_branch(@project_repo_original_branch)
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_localization_files_in_project_repo
|
75
|
+
has_changes = false
|
76
|
+
self.nabokovfile.project_localization_file_paths.each do |localization_file_name, localization_file_path|
|
77
|
+
localization_file_path_in_localization_repo = "#{self.git_repo.local_path}/#{localization_file_name}.#{Nabokov::StringsFile.extension}"
|
78
|
+
ui.say("Copying strings file from '#{localization_file_path_in_localization_repo}' to the project repo...") if self.verbose
|
79
|
+
new_file_path = FileManager.copy(localization_file_path_in_localization_repo, localization_file_path)
|
80
|
+
@project_git_repo.add(new_file_path)
|
81
|
+
if @project_git_repo.changes?
|
82
|
+
@synchronized_file_names << localization_file_name
|
83
|
+
@project_git_repo.commit("Nabokov has updated localization file '#{localization_file_name}'...")
|
84
|
+
has_changes = true
|
85
|
+
else
|
86
|
+
ui.say("'#{localization_file_name}' file doesn't have any changes to commit...") if self.verbose
|
87
|
+
end
|
88
|
+
end
|
89
|
+
has_changes
|
90
|
+
end
|
91
|
+
|
92
|
+
def fetch_localization_repo_master_branch_changes
|
93
|
+
ui.say("Fetching localization repo remote master branch changes...") if self.verbose
|
94
|
+
self.git_repo.pull
|
95
|
+
end
|
96
|
+
|
97
|
+
def delete_temporary_branch
|
98
|
+
ui.say("Deleting temporary branch...") if self.verbose
|
99
|
+
@project_git_repo.delete_branch(temporary_branch)
|
100
|
+
end
|
101
|
+
|
102
|
+
def temporary_branch
|
103
|
+
"nabokov/temporary_branch"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "nabokov/commands/runner"
|
2
|
+
require "nabokov/core/nabokovfile"
|
3
|
+
require "nabokov/core/file_manager"
|
4
|
+
require "nabokov/git/git_repo"
|
5
|
+
|
6
|
+
module Nabokov
|
7
|
+
class Syncer < Runner
|
8
|
+
attr_reader :nabokovfile
|
9
|
+
attr_reader :git_repo
|
10
|
+
|
11
|
+
self.abstract_command = true
|
12
|
+
self.summary = "Synchronization between localizations and project."
|
13
|
+
self.command = "sync"
|
14
|
+
|
15
|
+
def initialize(argv)
|
16
|
+
nabokovfile = argv.option("nabokovfile")
|
17
|
+
unless nabokovfile
|
18
|
+
pwd_nabokovfile = Pathname.pwd + "Nabokovfile.yaml"
|
19
|
+
nabokovfile = pwd_nabokovfile if File.exist?(pwd_nabokovfile)
|
20
|
+
end
|
21
|
+
raise "--nabokovfile is a required parameter and could not be nil" if nabokovfile.nil?
|
22
|
+
|
23
|
+
@nabokovfile_path = nabokovfile if File.exist?(nabokovfile)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate!
|
28
|
+
super
|
29
|
+
if self.class == Syncer && !@nabokovfile_path
|
30
|
+
help! "Could not find a Nabokovfile."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.options
|
35
|
+
[
|
36
|
+
["--nabokovfile=<path/to/nabokovfile>", "The location of your Nabokovfile"]
|
37
|
+
].concat(super)
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
initialize_nabokov_file
|
42
|
+
init_git_repo
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize_nabokov_file
|
47
|
+
@nabokovfile = Nabokovfile.new(@nabokovfile_path)
|
48
|
+
ui.say("Hooray, your Nabokovfile is valid...") if self.verbose
|
49
|
+
end
|
50
|
+
|
51
|
+
def init_git_repo
|
52
|
+
@git_repo = GitRepo.new(@nabokovfile.localizations_repo_local_path, @nabokovfile.localizations_repo_url)
|
53
|
+
if Dir.exist?(@git_repo.local_path)
|
54
|
+
ui.say("Found existed repo at #{@git_repo.local_path}...") if self.verbose
|
55
|
+
@git_repo.init
|
56
|
+
else
|
57
|
+
ui.say("Cloning the localization repo from #{@git_repo.remote_url} into #{@git_repo.local_path}...") if self.verbose
|
58
|
+
@git_repo.clone
|
59
|
+
end
|
60
|
+
checkout_master_branch
|
61
|
+
end
|
62
|
+
|
63
|
+
def checkout_master_branch
|
64
|
+
ui.say("Checkout master branch...") if self.verbose
|
65
|
+
@git_repo.checkout_branch(@nabokovfile.localizations_repo_master_branch)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Nabokov
|
5
|
+
# This class is the wrapper around the FileUtils
|
6
|
+
class FileManager
|
7
|
+
# Copies given file to the given destination
|
8
|
+
def self.copy(original_file_path, destination_file_path)
|
9
|
+
raise "Couldn't find file at '#{original_file_path}'" unless File.exist?(original_file_path)
|
10
|
+
FileUtils.cp(original_file_path, destination_file_path)
|
11
|
+
File.expand_path(destination_file_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Copies given file to the given destination and renames to the given name
|
15
|
+
# Note: the extension of the file remains the same that's why new name can not contain '.'
|
16
|
+
def self.copy_and_rename(original_file_path, to_directory, new_name)
|
17
|
+
raise "Couldn't find file at '#{original_file_path}'" unless File.exist?(original_file_path)
|
18
|
+
raise "Couldn't find directory at '#{to_directory}'" unless Dir.exist?(to_directory)
|
19
|
+
raise "New name of the file could not be empty" if new_name.empty?
|
20
|
+
raise "New name of the file '#{new_name}' contains invalid character '.'" if new_name.include?(".")
|
21
|
+
|
22
|
+
original_file_pathname = Pathname.new(original_file_path)
|
23
|
+
original_file_extension = original_file_pathname.extname
|
24
|
+
new_file_pathname = Pathname.new(to_directory) + Pathname.new(new_name + original_file_extension)
|
25
|
+
new_file_path = new_file_pathname.to_s
|
26
|
+
FileUtils.cp(original_file_path, new_file_path)
|
27
|
+
File.expand_path(new_file_path)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Removes the filve at the given path
|
31
|
+
def self.remove(path)
|
32
|
+
raise "Can not file neither file nor directory at '#{path}'" unless File.exist?(path) or Dir.exist?(path)
|
33
|
+
FileUtils.rm_rf(path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|