nabokov 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +1 -0
  3. data/.gitignore +43 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +113 -0
  6. data/.travis.yml +14 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +9 -0
  9. data/LICENSE +21 -0
  10. data/README.md +16 -0
  11. data/Rakefile +22 -0
  12. data/bin/nabokov +5 -0
  13. data/lib/nabokov/commands/runner.rb +22 -0
  14. data/lib/nabokov/commands/setup.rb +75 -0
  15. data/lib/nabokov/commands/syncers/localizations_repo_syncer.rb +101 -0
  16. data/lib/nabokov/commands/syncers/project_syncer.rb +106 -0
  17. data/lib/nabokov/commands/syncers/syncer.rb +68 -0
  18. data/lib/nabokov/core/file_manager.rb +36 -0
  19. data/lib/nabokov/core/nabokovfile.rb +66 -0
  20. data/lib/nabokov/core/nabokovfile_content_validator.rb +51 -0
  21. data/lib/nabokov/core/nabokovfile_keys.rb +34 -0
  22. data/lib/nabokov/git/git_repo.rb +137 -0
  23. data/lib/nabokov/helpers/informator.rb +83 -0
  24. data/lib/nabokov/helpers/merger.rb +86 -0
  25. data/lib/nabokov/models/strings_file.rb +7 -0
  26. data/lib/nabokov/version.rb +8 -0
  27. data/lib/nabokov.rb +14 -0
  28. data/nabokov.gemspec +31 -0
  29. data/spec/fixtures/.DS_Store +0 -0
  30. data/spec/fixtures/README.md +1 -0
  31. data/spec/fixtures/de.strings +1 -0
  32. data/spec/fixtures/en.strings +1 -0
  33. data/spec/fixtures/nabokovfile_example.yaml +9 -0
  34. data/spec/fixtures/nabokovfile_example_invalid.yaml +2 -0
  35. data/spec/fixtures/nabokovfile_example_without_master_branch.yaml +8 -0
  36. data/spec/fixtures/test_git_setup/existed_pre_commit_file +0 -0
  37. data/spec/fixtures/test_git_setup/existed_pre_commit_file_alias +0 -0
  38. data/spec/fixtures/test_git_setup/not_executable_pre_commit_file +0 -0
  39. data/spec/fixtures/test_localizations_repo_syncer/localizations_repo_fixtures/README.md +1 -0
  40. data/spec/fixtures/test_localizations_repo_syncer/nabokovfile.yaml +8 -0
  41. data/spec/fixtures/test_project_syncer/localizations_repo_fixtures/de.strings +2 -0
  42. data/spec/fixtures/test_project_syncer/localizations_repo_fixtures/en.strings +2 -0
  43. data/spec/fixtures/test_project_syncer/project_repo_fixtures/de.strings +2 -0
  44. data/spec/fixtures/test_project_syncer/project_repo_fixtures/en.strings +2 -0
  45. data/spec/fixtures/test_project_syncer/project_repo_fixtures/nabokovfile.yaml +9 -0
  46. data/spec/lib/nabokov/commands/localizations_repo_syncer_spec.rb +137 -0
  47. data/spec/lib/nabokov/commands/project_syncer_spec.rb +61 -0
  48. data/spec/lib/nabokov/commands/runner_spec.rb +7 -0
  49. data/spec/lib/nabokov/commands/setup_spec.rb +101 -0
  50. data/spec/lib/nabokov/commands/syncer_spec.rb +23 -0
  51. data/spec/lib/nabokov/core/file_manager_spec.rb +115 -0
  52. data/spec/lib/nabokov/core/nabokovfile_content_validator_spec.rb +155 -0
  53. data/spec/lib/nabokov/core/nabokovfile_keyes_spec.rb +31 -0
  54. data/spec/lib/nabokov/core/nabokovfile_spec.rb +53 -0
  55. data/spec/lib/nabokov/git/git_repo_spec.rb +670 -0
  56. data/spec/lib/nabokov/helpers/informator_spec.rb +49 -0
  57. data/spec/lib/nabokov/helpers/merger_spec.rb +114 -0
  58. data/spec/lib/nabokov/models/strings_file_spec.rb +7 -0
  59. data/spec/spec_helper.rb +11 -0
  60. 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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
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
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ cache:
3
+ directories:
4
+ - bundle
5
+
6
+ rvm:
7
+ - 2.0
8
+ - 2.1.3
9
+ - 2.3.1
10
+
11
+ bundler_args: "--without documentation --path bundle"
12
+
13
+ before_install:
14
+ - gem install bundler
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## master
2
+
3
+ ## 0.1
4
+
5
+ * The very beginning of the nabokov
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rspec", "~> 3.4"
4
+ gem "claide", "~> 1.0"
5
+ gem "cork", "~> 0.1"
6
+ gem "rake", "~> 10.0"
7
+ gem "git", git: "https://github.com/Antondomashnev/ruby-git.git"
8
+
9
+ gem "rubocop", "~> 0.42"
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,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.push File.expand_path("../../lib", __FILE__)
3
+
4
+ require "nabokov"
5
+ Nabokov::Runner.run ARGV
@@ -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