nabokov 0.1.0

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.
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