rosette-core 1.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.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/History.txt +3 -0
  4. data/README.md +94 -0
  5. data/Rakefile +18 -0
  6. data/lib/rosette/core.rb +110 -0
  7. data/lib/rosette/core/branch_utils.rb +152 -0
  8. data/lib/rosette/core/commands.rb +139 -0
  9. data/lib/rosette/core/commands/errors.rb +17 -0
  10. data/lib/rosette/core/commands/git/commit_command.rb +65 -0
  11. data/lib/rosette/core/commands/git/diff_base_command.rb +301 -0
  12. data/lib/rosette/core/commands/git/diff_command.rb +188 -0
  13. data/lib/rosette/core/commands/git/diff_entry.rb +44 -0
  14. data/lib/rosette/core/commands/git/fetch_command.rb +27 -0
  15. data/lib/rosette/core/commands/git/repo_snapshot_command.rb +40 -0
  16. data/lib/rosette/core/commands/git/show_command.rb +70 -0
  17. data/lib/rosette/core/commands/git/snapshot_command.rb +50 -0
  18. data/lib/rosette/core/commands/git/status_command.rb +128 -0
  19. data/lib/rosette/core/commands/git/with_non_merge_ref.rb +48 -0
  20. data/lib/rosette/core/commands/git/with_ref.rb +92 -0
  21. data/lib/rosette/core/commands/git/with_refs.rb +92 -0
  22. data/lib/rosette/core/commands/git/with_repo_name.rb +50 -0
  23. data/lib/rosette/core/commands/git/with_snapshots.rb +45 -0
  24. data/lib/rosette/core/commands/queuing/enqueue_commit_command.rb +37 -0
  25. data/lib/rosette/core/commands/queuing/requeue_commit_command.rb +46 -0
  26. data/lib/rosette/core/commands/translations/export_command.rb +257 -0
  27. data/lib/rosette/core/commands/translations/translation_lookup_command.rb +66 -0
  28. data/lib/rosette/core/commands/translations/with_locale.rb +47 -0
  29. data/lib/rosette/core/configurator.rb +160 -0
  30. data/lib/rosette/core/error_reporters/buffered_error_reporter.rb +96 -0
  31. data/lib/rosette/core/error_reporters/error_reporter.rb +31 -0
  32. data/lib/rosette/core/error_reporters/nil_error_reporter.rb +25 -0
  33. data/lib/rosette/core/error_reporters/printing_error_reporter.rb +58 -0
  34. data/lib/rosette/core/error_reporters/raising_error_reporter.rb +27 -0
  35. data/lib/rosette/core/errors.rb +93 -0
  36. data/lib/rosette/core/extractor/commit_log.rb +33 -0
  37. data/lib/rosette/core/extractor/commit_log_status.rb +57 -0
  38. data/lib/rosette/core/extractor/commit_processor.rb +109 -0
  39. data/lib/rosette/core/extractor/extractor.rb +72 -0
  40. data/lib/rosette/core/extractor/extractor_config.rb +74 -0
  41. data/lib/rosette/core/extractor/locale.rb +118 -0
  42. data/lib/rosette/core/extractor/phrase.rb +76 -0
  43. data/lib/rosette/core/extractor/phrase/phrase_index_policy.rb +108 -0
  44. data/lib/rosette/core/extractor/phrase/phrase_to_hash.rb +33 -0
  45. data/lib/rosette/core/extractor/repo_config.rb +339 -0
  46. data/lib/rosette/core/extractor/serializer_config.rb +55 -0
  47. data/lib/rosette/core/extractor/static_extractor.rb +44 -0
  48. data/lib/rosette/core/extractor/translation.rb +44 -0
  49. data/lib/rosette/core/extractor/translation/translation_to_hash.rb +28 -0
  50. data/lib/rosette/core/git/diff_finder.rb +131 -0
  51. data/lib/rosette/core/git/ref.rb +116 -0
  52. data/lib/rosette/core/git/repo.rb +378 -0
  53. data/lib/rosette/core/path_matcher_factory.rb +330 -0
  54. data/lib/rosette/core/resolvers/extractor_id.rb +37 -0
  55. data/lib/rosette/core/resolvers/integration_id.rb +37 -0
  56. data/lib/rosette/core/resolvers/preprocessor_id.rb +38 -0
  57. data/lib/rosette/core/resolvers/resolver.rb +115 -0
  58. data/lib/rosette/core/resolvers/serializer_id.rb +37 -0
  59. data/lib/rosette/core/snapshots/cached_head_snapshot_factory.rb +51 -0
  60. data/lib/rosette/core/snapshots/cached_snapshot_factory.rb +67 -0
  61. data/lib/rosette/core/snapshots/head_snapshot_factory.rb +58 -0
  62. data/lib/rosette/core/snapshots/repo_config_path_filter.rb +83 -0
  63. data/lib/rosette/core/snapshots/snapshot_factory.rb +184 -0
  64. data/lib/rosette/core/string_utils.rb +23 -0
  65. data/lib/rosette/core/translation_status.rb +81 -0
  66. data/lib/rosette/core/validators.rb +18 -0
  67. data/lib/rosette/core/validators/commit_validator.rb +62 -0
  68. data/lib/rosette/core/validators/commits_validator.rb +32 -0
  69. data/lib/rosette/core/validators/encoding_validator.rb +32 -0
  70. data/lib/rosette/core/validators/locale_validator.rb +37 -0
  71. data/lib/rosette/core/validators/repo_validator.rb +33 -0
  72. data/lib/rosette/core/validators/serializer_validator.rb +37 -0
  73. data/lib/rosette/core/validators/validator.rb +31 -0
  74. data/lib/rosette/core/version.rb +8 -0
  75. data/lib/rosette/data_stores.rb +11 -0
  76. data/lib/rosette/data_stores/errors.rb +26 -0
  77. data/lib/rosette/data_stores/phrase_status.rb +59 -0
  78. data/lib/rosette/integrations.rb +12 -0
  79. data/lib/rosette/integrations/errors.rb +15 -0
  80. data/lib/rosette/integrations/integratable.rb +58 -0
  81. data/lib/rosette/integrations/integration.rb +23 -0
  82. data/lib/rosette/preprocessors.rb +11 -0
  83. data/lib/rosette/preprocessors/errors.rb +14 -0
  84. data/lib/rosette/preprocessors/preprocessor.rb +48 -0
  85. data/lib/rosette/queuing.rb +14 -0
  86. data/lib/rosette/queuing/commits.rb +19 -0
  87. data/lib/rosette/queuing/commits/commit_conductor.rb +90 -0
  88. data/lib/rosette/queuing/commits/commit_job.rb +93 -0
  89. data/lib/rosette/queuing/commits/commits_queue_configurator.rb +60 -0
  90. data/lib/rosette/queuing/commits/extract_stage.rb +46 -0
  91. data/lib/rosette/queuing/commits/fetch_stage.rb +51 -0
  92. data/lib/rosette/queuing/commits/finalize_stage.rb +76 -0
  93. data/lib/rosette/queuing/commits/phrase_storage_granularity.rb +20 -0
  94. data/lib/rosette/queuing/commits/push_stage.rb +91 -0
  95. data/lib/rosette/queuing/commits/stage.rb +96 -0
  96. data/lib/rosette/queuing/job.rb +74 -0
  97. data/lib/rosette/queuing/queue.rb +28 -0
  98. data/lib/rosette/queuing/queue_configurator.rb +76 -0
  99. data/lib/rosette/queuing/worker.rb +30 -0
  100. data/lib/rosette/serializers.rb +10 -0
  101. data/lib/rosette/serializers/serializer.rb +98 -0
  102. data/lib/rosette/tms.rb +9 -0
  103. data/lib/rosette/tms/repository.rb +95 -0
  104. data/rosette-core.gemspec +24 -0
  105. data/spec/core/branch_utils_spec.rb +110 -0
  106. data/spec/core/commands/git/commit_command_spec.rb +60 -0
  107. data/spec/core/commands/git/diff_command_spec.rb +263 -0
  108. data/spec/core/commands/git/fetch_command_spec.rb +61 -0
  109. data/spec/core/commands/git/repo_snapshot_command_spec.rb +72 -0
  110. data/spec/core/commands/git/show_command_spec.rb +128 -0
  111. data/spec/core/commands/git/snapshot_command_spec.rb +86 -0
  112. data/spec/core/commands/git/status_command_spec.rb +154 -0
  113. data/spec/core/commands/queuing/enqueue_commit_command_spec.rb +34 -0
  114. data/spec/core/commands/queuing/requeue_commit_command_spec.rb +46 -0
  115. data/spec/core/commands/translations/export_command_spec.rb +113 -0
  116. data/spec/core/commands/translations/translation_lookup_command_spec.rb +58 -0
  117. data/spec/core/configurator_spec.rb +47 -0
  118. data/spec/core/error_reporters/buffered_error_reporter_spec.rb +61 -0
  119. data/spec/core/error_reporters/nil_error_reporter_spec.rb +16 -0
  120. data/spec/core/error_reporters/printing_error_reporter_spec.rb +60 -0
  121. data/spec/core/extractor/commit_log_status_spec.rb +216 -0
  122. data/spec/core/extractor/commit_processor_spec.rb +68 -0
  123. data/spec/core/extractor/extractor_config_spec.rb +47 -0
  124. data/spec/core/extractor/extractor_spec.rb +26 -0
  125. data/spec/core/extractor/locale_spec.rb +92 -0
  126. data/spec/core/extractor/phrase/phrase_index_policy_spec.rb +116 -0
  127. data/spec/core/extractor/phrase/phrase_to_hash_spec.rb +18 -0
  128. data/spec/core/extractor/repo_config_spec.rb +147 -0
  129. data/spec/core/extractor/translation/translation_to_hash_spec.rb +25 -0
  130. data/spec/core/git/diff_finder_spec.rb +74 -0
  131. data/spec/core/git/ref_spec.rb +118 -0
  132. data/spec/core/git/repo_spec.rb +216 -0
  133. data/spec/core/path_matcher_factory_spec.rb +139 -0
  134. data/spec/core/resolvers/extractor_id_spec.rb +47 -0
  135. data/spec/core/resolvers/integration_id_spec.rb +47 -0
  136. data/spec/core/resolvers/preprocessor_id_spec.rb +47 -0
  137. data/spec/core/resolvers/serializer_id_spec.rb +47 -0
  138. data/spec/core/snapshots/snapshot_factory_spec.rb +145 -0
  139. data/spec/core/string_utils_spec.rb +19 -0
  140. data/spec/core/translation_status_spec.rb +91 -0
  141. data/spec/core/validators/commit_validator_spec.rb +40 -0
  142. data/spec/core/validators/encoding_validator_spec.rb +30 -0
  143. data/spec/core/validators/locale_validator_spec.rb +31 -0
  144. data/spec/core/validators/repo_validator_spec.rb +30 -0
  145. data/spec/core/validators/serializer_validator_spec.rb +31 -0
  146. data/spec/integrations/integratable_spec.rb +58 -0
  147. data/spec/queuing/commits/commit_conductor_spec.rb +71 -0
  148. data/spec/queuing/commits/commit_job_spec.rb +87 -0
  149. data/spec/queuing/commits/extract_stage_spec.rb +68 -0
  150. data/spec/queuing/commits/fetch_stage_spec.rb +101 -0
  151. data/spec/queuing/commits/finalize_stage_spec.rb +88 -0
  152. data/spec/queuing/commits/push_stage_spec.rb +145 -0
  153. data/spec/queuing/commits/stage_spec.rb +80 -0
  154. data/spec/queuing/job_spec.rb +33 -0
  155. data/spec/queuing/queue_configurator_spec.rb +44 -0
  156. data/spec/spec_helper.rb +90 -0
  157. data/spec/test_helpers/fake_commit_stage.rb +17 -0
  158. metadata +257 -0
@@ -0,0 +1,92 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'thread'
4
+
5
+ module Rosette
6
+ module Core
7
+ module Commands
8
+
9
+ # Mixin that handles configuration and validation of a set of git refs or
10
+ # commit ids. Meant to be mixed into the classes in {Rosette::Core::Commands}.
11
+ # Required to be used in combination with {Rosette::Core::Commands::WithRepoName}.
12
+ #
13
+ # @see Rosette::Core::Commands::WithRepoName
14
+ #
15
+ # @example
16
+ # class MyCommand < Rosette::Core::Commands::Command
17
+ # include WithRepoName
18
+ # include WithRefs
19
+ # end
20
+ #
21
+ # cmd = MyCommand.new
22
+ # .set_repo_name('my_repo')
23
+ # .set_refs(['master'])
24
+ #
25
+ # cmd.commit_strs # => ["master"]
26
+ # cmd.commit_ids # => ["67f0e9a60dfe39430b346086f965e6c94a8ddd24"]
27
+ # cmd.valid? # => true
28
+ #
29
+ # cmd.set_refs(['non_existent_ref'])
30
+ # cmd.valid? # => false
31
+ # cmd.messages # => { commit_str: ["Unable to find commit 'non_existent_ref'"] }
32
+ #
33
+ # @!attribute [r] commit_str
34
+ # @return [String] the raw value as set by either {#set_ref} or {#set_commit_id}.
35
+ module WithRefs
36
+ attr_reader :commit_strs
37
+
38
+ # Set the git refs (i.e. a branch names). Calling this method after {#set_commit_ids}
39
+ # will overwrite the commit id values. In other words, it's generally a good idea to
40
+ # only call one of {#set_commit_ids} or {#set_refs} but not both.
41
+ #
42
+ # @param [Array<String>] ref_strs The git refs.
43
+ # @return [self]
44
+ def set_refs(ref_strs)
45
+ @commit_strs = ref_strs
46
+ self
47
+ end
48
+
49
+ # Set the git commit ids. Calling this method after {#set_refs} will overwrite the ref values.
50
+ # In other words, it's generally a good idea to only call one of {#set_commit_ids} or {#set_refs}
51
+ # but not both.
52
+ #
53
+ # @param [Array<String>] commit_ids The commit ids.
54
+ # @return [self]
55
+ def set_commit_ids(commit_ids)
56
+ @commit_strs = commit_ids
57
+ self
58
+ end
59
+
60
+ # Resolves the git ref or commit id at +index+ and returns the corresponding commit id.
61
+ # If {#set_refs} was used to set git refs (i.e. branch names), this method looks up
62
+ # and returns the corresponding commit id. If {#set_commit_ids} was used to set
63
+ # commit ids, then that commit id is validated and returned.
64
+ #
65
+ # @param [Fixnum] index The index of the ref or commit id to return.
66
+ # @return [String] The commit id set via either {#set_refs} or {#set_commit_ids}.
67
+ # @raise [Java::OrgEclipseJgitErrors::MissingObjectException, Java::JavaLang::IllegalArgumentException]
68
+ # If either the commit id doesn't exist or the ref can't be found.
69
+ def commit_ids
70
+ @commit_ids ||= @commit_strs.map do |commit_str|
71
+ REV_COMMIT_MUTEX.synchronize do
72
+ get_repo(repo_name)
73
+ .repo.get_rev_commit(commit_str)
74
+ .getId.name
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ REV_COMMIT_MUTEX = Mutex.new
82
+
83
+ def self.included(base)
84
+ if base.respond_to?(:validate)
85
+ base.validate :commit_strs, type: :commits
86
+ end
87
+ end
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+ module Commands
6
+
7
+ # Mixin that handles configuration and validation of a repository name.
8
+ # Meant to be mixed into the classes in {Rosette::Core::Commands}.
9
+ #
10
+ # @example
11
+ # class MyCommand < Rosette::Core::Commands::Command
12
+ # include WithRepoName
13
+ # end
14
+ #
15
+ # cmd = MyCommand.new
16
+ # .set_repo_name('my_repo')
17
+ #
18
+ # cmd.repo_name # => "my_repo"
19
+ # cmd.valid? # => true
20
+ #
21
+ # cmd.set_repo_name('non_existent_repo')
22
+ # cmd.valid? # => false
23
+ # cmd.messages # => { repo_name: ["Unable to find repo 'non_existent_repo'."] }
24
+ #
25
+ # @!attribute [r] repo_name
26
+ # @return [String] the repository name.
27
+ module WithRepoName
28
+ attr_reader :repo_name
29
+
30
+ # Set the repository name.
31
+ #
32
+ # @param [String] repo_name The repository name.
33
+ # @return [self]
34
+ def set_repo_name(repo_name)
35
+ @repo_name = repo_name
36
+ self
37
+ end
38
+
39
+ protected
40
+
41
+ def self.included(base)
42
+ if base.respond_to?(:validate)
43
+ base.validate :repo_name, type: :repo
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+ module Commands
6
+
7
+ # Mixin capable of taking snapshots of git repositories. Meant to be mixed
8
+ # into the classes in {Rosette::Core::Commands}.
9
+ #
10
+ # @see Rosette::Core::SnapshotFactory
11
+ #
12
+ # @example
13
+ # class MyCommand < Rosette::Core::Commands::Command
14
+ # include WithSnapshots
15
+ # end
16
+ #
17
+ # cmd = MyCommand.new
18
+ # snap = cmd.take_snapshot(repo_config, commit_id, paths)
19
+ # snap # => { 'path/to/file.rb' => '67f0e9a60dfe39430b346086f965e6c94a8ddd24', ... }
20
+ module WithSnapshots
21
+ # Takes and returns a snapshot hash for the given repo and commit id. Limits
22
+ # the paths returned via the +paths+ argument. If no paths are passed,
23
+ # {#take_snapshot} returns a snapshot containing all the paths in the repository.
24
+ #
25
+ # @param [Rosette::Core::RepoConfig] repo_config The repository configuration
26
+ # to take the snapshot from.
27
+ # @param [String] commit_id The git commit id to take the snapshot of.
28
+ # @param [Array] paths The list of paths to consider when taking the snapshot.
29
+ # Only those paths included in this list will appear in the snapshot hash.
30
+ # @return [Hash] the snapshot hash (path to commit id pairs).
31
+ def take_snapshot(repo_config, commit_id, paths = [])
32
+ __snapshot_factory__.take_snapshot(repo_config, commit_id, paths)
33
+ end
34
+
35
+ private
36
+
37
+ def __snapshot_factory__
38
+ @@__snapshot_factory__ ||=
39
+ CachedSnapshotFactory.new(configuration.cache)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+ module Commands
6
+
7
+ # Enqueues a commit for processing on Rosette's configured queue.
8
+ #
9
+ # @see Rosette::Queuing
10
+ #
11
+ # @example
12
+ # EnqueueCommitCommand.new(configuration)
13
+ # .set_repo_name('my_repo')
14
+ # .set_ref('master')
15
+ # .execute
16
+ #
17
+ # @example
18
+ # EnqueueCommitCommand.new(configuration)
19
+ # .set_repo_name('my_repo')
20
+ # .set_commit_id('67f0e9a60dfe39430b346086f965e6c94a8ddd24')
21
+ # .execute
22
+ class EnqueueCommitCommand < GitCommand
23
+ include WithRepoName
24
+ include WithRef
25
+
26
+ def execute
27
+ conductor = Rosette::Queuing::Commits::CommitConductor.new(
28
+ configuration, repo_name, Rosette.logger
29
+ )
30
+
31
+ conductor.enqueue(commit_id)
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Core
5
+ module Commands
6
+
7
+ # Sets the commit's status to NOT_FOUND, then enqueues it for processing
8
+ # on Rosette's configured queue. In other words, this command will
9
+ # re-process the commit, which should be an idempotent operation.
10
+ #
11
+ # @see Rosette::Queuing
12
+ #
13
+ # @example
14
+ # RequeueCommitCommand.new(configuration)
15
+ # .set_repo_name('my_repo')
16
+ # .set_ref('master')
17
+ # .execute
18
+ #
19
+ # @example
20
+ # RequeueCommitCommand.new(configuration)
21
+ # .set_repo_name('my_repo')
22
+ # .set_commit_id('67f0e9a60dfe39430b346086f965e6c94a8ddd24')
23
+ # .execute
24
+ class RequeueCommitCommand < GitCommand
25
+ include WithRepoName
26
+ include WithRef
27
+
28
+ def execute
29
+ commit_log = datastore.lookup_commit_log(repo_name, commit_id)
30
+
31
+ datastore.add_or_update_commit_log(
32
+ commit_log.repo_name, commit_log.commit_id,
33
+ commit_log.commit_datetime, PhraseStatus::NOT_FOUND,
34
+ commit_log.phrase_count, commit_log.branch_name
35
+ )
36
+
37
+ EnqueueCommitCommand.new(configuration)
38
+ .set_repo_name(repo_name)
39
+ .set_commit_id(commit_id)
40
+ .execute
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,257 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'stringio'
4
+ require 'base64'
5
+ require 'digest/md5'
6
+
7
+ module Rosette
8
+ module Core
9
+ module Commands
10
+
11
+ # Finds, encodes, and serializes the translations identified by a
12
+ # snapshot of the given git ref or commit id. In other words, this
13
+ # command exports the translations for a git branch or commit. This
14
+ # command also applies any configured pre-processors to the
15
+ # translations before serializing them. As a better visualization,
16
+ # here's the pipeline translations go through when exported:
17
+ #
18
+ # preprocessed -> serialized/encoded -> base 64 encoded (if
19
+ # requested) -> returned
20
+ #
21
+ # @!attribute [r] locale
22
+ # @return [String] the locale to export translations for.
23
+ # @!attribute [r] serializer
24
+ # @return [String] the serializer to use when exporting the
25
+ # translations. Must be recognizable as a serializer id, eg.
26
+ # 'yaml/rails' or 'json/key-value'.
27
+ # @!attribute [r] base_64_encode
28
+ # @return [Boolean] whether or not the serialized translations
29
+ # should be returned encoded in base 64.
30
+ # @!attribute [r] encoding
31
+ # @return [String, Encoding] the encoding translations are
32
+ # expected to be in. This attribute refers to string encoding
33
+ # and is distinct from base 64 encoding.
34
+ # @!attribute [r] include_snapshot
35
+ # @return [Boolean] whether or not the snapshot used to identify
36
+ # translations is returned alongside the serialized phrases.
37
+ # @!attribute [r] include_checksum
38
+ # @return [Boolean] whether or not the checksum of translations
39
+ # is returned alongside the serialized phrases.
40
+ # @!attribute [r] paths
41
+ # @return [Array<String>] the list of paths to export translations
42
+ # for. Any translations that belong to phrases that did not come
43
+ # from a path in this list will not be included in the export.
44
+ #
45
+ # @example
46
+ # cmd = ExportCommand.new(configuration)
47
+ # .set_repo_name('my_repo')
48
+ # .set_ref('master')
49
+ # .set_locale('pt-BR')
50
+ # .set_serializer('json/key-value')
51
+ # .set_base_64_encode(true)
52
+ # .set_encoding(Encoding::UTF_8)
53
+ # .set_include_snapshot(false)
54
+ #
55
+ # cmd.execute
56
+ # # =>
57
+ # # {
58
+ # # payload: "<base 64 encoded string>",
59
+ # # encoding: "UTF_8"
60
+ # # translation_count: 105,
61
+ # # base_64_encoded: true
62
+ # # locale: "pt-BR"
63
+ # # }
64
+ class ExportCommand < GitCommand
65
+ attr_reader :locale, :serializer, :base_64_encode
66
+ attr_reader :encoding, :include_snapshot, :include_checksum
67
+ attr_reader :paths
68
+
69
+ include WithRepoName
70
+ include WithRef
71
+ include WithLocale
72
+
73
+ include WithSnapshots
74
+
75
+ validate :serializer, type: :serializer
76
+ validate :encoding, type: :encoding
77
+
78
+ def initialize(*args)
79
+ super
80
+ @paths = []
81
+ @encoding = Encoding::UTF_8
82
+ @base_64_encode = false
83
+ @include_snapshot = false
84
+ @include_checksum = false
85
+ end
86
+
87
+ # Sets the serializer used to export translations. Must be recognizable
88
+ # as a serializer id, eg. 'yaml/rails' or 'json/key-value'.
89
+ #
90
+ # @param [String] serializer The serializer to use.
91
+ # @return [self]
92
+ def set_serializer(serializer)
93
+ @serializer = serializer
94
+ self
95
+ end
96
+
97
+ # Sets whether or not the serialized translations should be returned
98
+ # encoded in base 64.
99
+ #
100
+ # @param [Boolean] should_encode To encode or not encode, that is
101
+ # the question.
102
+ # @return [self]
103
+ def set_base_64_encode(should_encode)
104
+ @base_64_encode = should_encode
105
+ self
106
+ end
107
+
108
+ # Sets the encoding translations are expected to be in. Not to be
109
+ # confused with base 64 encoding.
110
+ #
111
+ # @param [String, Encoding] encoding The encoding to use. Can be
112
+ # either a +String+ or a Ruby +Encoding+, eg. +Encoding::UTF_8+.
113
+ # @return [self]
114
+ def set_encoding(encoding)
115
+ @encoding = encoding
116
+ self
117
+ end
118
+
119
+ # Sets whether or not to include the snapshot in the return value.
120
+ #
121
+ # @param [Boolean] should_include_snapshot whether or not to
122
+ # return the snapshot.
123
+ # @return [self]
124
+ def set_include_snapshot(should_include_snapshot)
125
+ @include_snapshot = should_include_snapshot
126
+ self
127
+ end
128
+
129
+ # Sets whether or not to include a checksum of the phrases in the
130
+ # return value.
131
+ #
132
+ # @param [Boolean] should_include_checksum whether or not to include
133
+ # the checksum.
134
+ # @return [self]
135
+ def set_include_checksum(should_include_checksum)
136
+ @include_checksum = should_include_checksum
137
+ self
138
+ end
139
+
140
+ # A list of files or paths to filter translations by. Only translations
141
+ # matching these paths will be included in the export payload.
142
+ def set_paths(paths)
143
+ @paths = Array(paths)
144
+ self
145
+ end
146
+
147
+ # Perform the export.
148
+ #
149
+ # @return [Hash] containing the following attributes:
150
+ # * +payload+: The serialized +String+ blob of all the translations.
151
+ # * +encoding+: The encoding of the strings in +payload+.
152
+ # * +translation_count+: The number of translations in +payload+.
153
+ # * +base_64_encoded+: A boolean indicating if +payload+ is base
154
+ # 64 encoded.
155
+ # * +locale+: The locale the translations in +payload+ are written in.
156
+ # * +snapshot+: The snapshot used to identify the translations in
157
+ def execute
158
+ stream = StringIO.new
159
+ snapshot = take_snapshot(repo_config, commit_id, paths)
160
+ translation_count = 0
161
+ checksum_list = []
162
+
163
+ serializer_instance = serializer_config.klass.new(
164
+ stream, locale_obj, encoding
165
+ )
166
+
167
+ write_translations_for(snapshot, serializer_instance) do |trans|
168
+ translation_count += 1
169
+
170
+ if include_checksum
171
+ checksum_list << "#{trans.phrase.index_value}#{trans.translation}"
172
+ end
173
+ end
174
+
175
+ params = {
176
+ payload: encode(stream.string),
177
+ encoding: serializer_instance.encoding.to_s,
178
+ translation_count: translation_count,
179
+ base_64_encoded: base_64_encode,
180
+ locale: locale,
181
+ paths: paths
182
+ }
183
+
184
+ if include_snapshot
185
+ params.merge!(snapshot: snapshot)
186
+ end
187
+
188
+ if include_checksum
189
+ params.merge!(checksum: checksum_for(checksum_list))
190
+ end
191
+
192
+ params
193
+ end
194
+
195
+ private
196
+
197
+ def write_translations_for(snapshot, serializer_instance)
198
+ each_translation(snapshot) do |trans|
199
+ next unless include_trans?(trans)
200
+ trans = apply_preprocessors(trans, serializer_config)
201
+ yield trans if block_given?
202
+
203
+ serializer_instance.write_key_value(
204
+ trans.phrase.index_value, trans.translation
205
+ )
206
+ end
207
+
208
+ serializer_instance.flush
209
+ end
210
+
211
+ def include_trans?(trans)
212
+ paths.size == 0 || paths.include?(trans.phrase.file)
213
+ end
214
+
215
+ def checksum_for(list)
216
+ Digest::MD5.hexdigest(list.sort.join)
217
+ end
218
+
219
+ def locale_obj
220
+ @locale_obj ||= repo_config.get_locale(locale)
221
+ end
222
+
223
+ def apply_preprocessors(translation, serializer_config)
224
+ serializer_config.preprocessors.inject(translation) do |trans, preprocessor|
225
+ preprocessor.process(trans)
226
+ end
227
+ end
228
+
229
+ def encode(string)
230
+ if base_64_encode
231
+ Base64.encode64(string)
232
+ else
233
+ string
234
+ end
235
+ end
236
+
237
+ def serializer_config
238
+ @serializer_config ||= repo_config.get_serializer_config(serializer)
239
+ end
240
+
241
+ def repo_config
242
+ @repo_config ||= get_repo(repo_name)
243
+ end
244
+
245
+ def each_translation(snapshot)
246
+ datastore.phrases_by_commits(repo_name, snapshot) do |phrase|
247
+ # only yield if a translation exists
248
+ if text = repo_config.tms.lookup_translation(locale_obj, phrase)
249
+ yield Translation.new(phrase, locale, text)
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ end
256
+ end
257
+ end