copycopter_client 1.1.2 → 2.0.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.
@@ -24,6 +24,12 @@ module CopycopterClient
24
24
  client.deploy
25
25
  end
26
26
 
27
+ # Issues a new export, returning yaml representation of blurb cache.
28
+ # This is called when the copycopter:export rake task is invoked.
29
+ def self.export
30
+ cache.export
31
+ end
32
+
27
33
  # Starts the polling process.
28
34
  def self.start_poller
29
35
  poller.start
@@ -47,7 +53,8 @@ module CopycopterClient
47
53
  # @example
48
54
  # CopycopterClient.configure do |config|
49
55
  # config.api_key = '1234567890abcdef'
50
- # config.secure = false
56
+ # config.host = 'your-copycopter-server.herokuapp.com'
57
+ # config.secure = true
51
58
  # end
52
59
  #
53
60
  # @param apply [Boolean] (internal) whether the configuration should be applied yet.
@@ -55,12 +62,15 @@ module CopycopterClient
55
62
  # @yield [Configuration] the configuration to be modified
56
63
  def self.configure(apply = true)
57
64
  self.configuration ||= Configuration.new
58
- yield(configuration)
59
- configuration.apply if apply
65
+ yield configuration
66
+
67
+ if apply
68
+ configuration.apply
69
+ end
60
70
  end
61
71
  end
62
72
 
63
- if defined?(Rails)
73
+ if defined? Rails
64
74
  require 'copycopter_client/rails'
65
75
  end
66
76
 
@@ -13,13 +13,13 @@ module CopycopterClient
13
13
  # @param options [Hash]
14
14
  # @option options [Logger] :logger where errors should be logged
15
15
  def initialize(client, options)
16
- @client = client
17
- @blurbs = {}
18
- @queued = {}
19
- @mutex = Mutex.new
20
- @logger = options[:logger]
21
- @started = false
16
+ @blurbs = {}
17
+ @client = client
22
18
  @downloaded = false
19
+ @logger = options[:logger]
20
+ @mutex = Mutex.new
21
+ @queued = {}
22
+ @started = false
23
23
  end
24
24
 
25
25
  # Returns content for the given blurb.
@@ -43,33 +43,67 @@ module CopycopterClient
43
43
  lock { @blurbs.keys }
44
44
  end
45
45
 
46
+ # Yaml representation of all blurbs
47
+ # @return [String] yaml
48
+ def export
49
+ keys = {}
50
+ lock do
51
+ @blurbs.sort.each do |(blurb_key, value)|
52
+ current = keys
53
+ yaml_keys = blurb_key.split('.')
54
+
55
+ 0.upto(yaml_keys.size - 2) do |i|
56
+ key = yaml_keys[i]
57
+
58
+ # Overwrite en.key with en.sub.key
59
+ unless current[key].class == Hash
60
+ current[key] = {}
61
+ end
62
+
63
+ current = current[key]
64
+ end
65
+
66
+ current[yaml_keys.last] = value
67
+ end
68
+ end
69
+
70
+ unless keys.size < 1
71
+ keys.to_yaml
72
+ end
73
+ end
74
+
46
75
  # Waits until the first download has finished.
47
76
  def wait_for_download
48
77
  if pending?
49
- logger.info("Waiting for first download")
50
- logger.flush if logger.respond_to?(:flush)
78
+ logger.info 'Waiting for first download'
79
+
80
+ if logger.respond_to? :flush
81
+ logger.flush
82
+ end
83
+
51
84
  while pending?
52
- sleep(0.1)
85
+ sleep 0.1
53
86
  end
54
87
  end
55
88
  end
56
89
 
57
90
  def flush
58
91
  with_queued_changes do |queued|
59
- client.upload(queued)
92
+ client.upload queued
60
93
  end
61
94
  rescue ConnectionError => error
62
- logger.error(error.message)
95
+ logger.error error.message
63
96
  end
64
97
 
65
98
  def download
66
99
  @started = true
100
+
67
101
  client.download do |downloaded_blurbs|
68
- downloaded_blurbs.reject! { |key, value| value == "" }
102
+ downloaded_blurbs.reject! { |key, value| value == '' }
69
103
  lock { @blurbs = downloaded_blurbs }
70
104
  end
71
105
  rescue ConnectionError => error
72
- logger.error(error.message)
106
+ logger.error error.message
73
107
  ensure
74
108
  @downloaded = true
75
109
  end
@@ -86,17 +120,21 @@ module CopycopterClient
86
120
 
87
121
  def with_queued_changes
88
122
  changes_to_push = nil
123
+
89
124
  lock do
90
125
  unless @queued.empty?
91
126
  changes_to_push = @queued
92
127
  @queued = {}
93
128
  end
94
129
  end
95
- yield(changes_to_push) if changes_to_push
130
+
131
+ if changes_to_push
132
+ yield changes_to_push
133
+ end
96
134
  end
97
135
 
98
136
  def lock(&block)
99
- @mutex.synchronize(&block)
137
+ @mutex.synchronize &block
100
138
  end
101
139
 
102
140
  def pending?
@@ -28,7 +28,7 @@ module CopycopterClient
28
28
  def initialize(options)
29
29
  [:api_key, :host, :port, :public, :http_read_timeout,
30
30
  :http_open_timeout, :secure, :logger, :ca_file].each do |option|
31
- instance_variable_set("@#{option}", options[option])
31
+ instance_variable_set "@#{option}", options[option]
32
32
  end
33
33
  end
34
34
 
@@ -47,12 +47,14 @@ module CopycopterClient
47
47
  request = Net::HTTP::Get.new(uri(download_resource))
48
48
  request['If-None-Match'] = @etag
49
49
  response = http.request(request)
50
- if check(response)
51
- log("Downloaded translations")
50
+
51
+ if check response
52
+ log 'Downloaded translations'
52
53
  yield JSON.parse(response.body)
53
54
  else
54
- log("No new translations")
55
+ log 'No new translations'
55
56
  end
57
+
56
58
  @etag = response['ETag']
57
59
  end
58
60
  end
@@ -62,9 +64,9 @@ module CopycopterClient
62
64
  # @raise [ConnectionError] if the connection fails
63
65
  def upload(data)
64
66
  connect do |http|
65
- response = http.post(uri("draft_blurbs"), data.to_json, 'Content-Type' => 'application/json')
66
- check(response)
67
- log("Uploaded missing translations")
67
+ response = http.post(uri('draft_blurbs'), data.to_json, 'Content-Type' => 'application/json')
68
+ check response
69
+ log 'Uploaded missing translations'
68
70
  end
69
71
  end
70
72
 
@@ -72,9 +74,9 @@ module CopycopterClient
72
74
  # @raise [ConnectionError] if the connection fails
73
75
  def deploy
74
76
  connect do |http|
75
- response = http.post(uri("deploys"), "")
76
- check(response)
77
- log("Deployed")
77
+ response = http.post(uri('deploys'), '')
78
+ check response
79
+ log 'Deployed'
78
80
  end
79
81
  end
80
82
 
@@ -93,9 +95,9 @@ module CopycopterClient
93
95
 
94
96
  def download_resource
95
97
  if public?
96
- "published_blurbs"
98
+ 'published_blurbs'
97
99
  else
98
- "draft_blurbs"
100
+ 'draft_blurbs'
99
101
  end
100
102
  end
101
103
 
@@ -103,11 +105,12 @@ module CopycopterClient
103
105
  http = Net::HTTP.new(host, port)
104
106
  http.open_timeout = http_open_timeout
105
107
  http.read_timeout = http_read_timeout
106
- http.use_ssl = secure
107
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
108
- http.ca_file = ca_file
108
+ http.use_ssl = secure
109
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
110
+ http.ca_file = ca_file
111
+
109
112
  begin
110
- yield(http)
113
+ yield http
111
114
  rescue *HTTP_ERRORS => exception
112
115
  raise ConnectionError, "#{exception.class.name}: #{exception.message}"
113
116
  end
@@ -127,7 +130,7 @@ module CopycopterClient
127
130
  end
128
131
 
129
132
  def log(message)
130
- logger.info(message)
133
+ logger.info message
131
134
  end
132
135
  end
133
136
  end
@@ -18,9 +18,6 @@ module CopycopterClient
18
18
  :proxy_port, :proxy_user, :secure, :polling_delay, :logger,
19
19
  :framework, :middleware, :ca_file].freeze
20
20
 
21
- # Default root certificate used to verify ssl sessions.
22
- CA_FILE = File.expand_path('../../../AddTrustExternalCARoot.crt', __FILE__).freeze
23
-
24
21
  # @return [String] The API key for your project, found on the project edit form.
25
22
  attr_accessor :api_key
26
23
 
@@ -87,25 +84,24 @@ module CopycopterClient
87
84
  # @return [Cache] instance used internally to synchronize changes.
88
85
  attr_accessor :cache
89
86
 
90
- # @return [Client] instance used to communicate with the Copycopter server.
87
+ # @return [Client] instance used to communicate with a Copycopter Server.
91
88
  attr_accessor :client
92
89
 
93
90
  alias_method :secure?, :secure
94
91
 
95
92
  # Instantiated from {CopycopterClient.configure}. Sets defaults.
96
93
  def initialize
97
- self.secure = true
98
- self.host = 'copycopter.com'
99
- self.http_open_timeout = 2
100
- self.http_read_timeout = 5
94
+ self.client_name = 'Copycopter Client'
95
+ self.client_url = 'https://rubygems.org/gems/copycopter_client'
96
+ self.client_version = VERSION
101
97
  self.development_environments = %w(development staging)
102
- self.test_environments = %w(test cucumber)
103
- self.client_name = 'Copycopter Client'
104
- self.client_version = VERSION
105
- self.client_url = 'http://copycopter.com'
106
- self.polling_delay = 300
107
- self.logger = Logger.new($stdout)
108
- self.ca_file = CA_FILE
98
+ self.host = 'copycopter.com'
99
+ self.http_open_timeout = 2
100
+ self.http_read_timeout = 5
101
+ self.logger = Logger.new($stdout)
102
+ self.polling_delay = 300
103
+ self.secure = false
104
+ self.test_environments = %w(test cucumber)
109
105
  @applied = false
110
106
  end
111
107
 
@@ -121,8 +117,9 @@ module CopycopterClient
121
117
  # @return [Hash] configuration attributes
122
118
  def to_hash
123
119
  base_options = { :public => public? }
120
+
124
121
  OPTIONS.inject(base_options) do |hash, option|
125
- hash.merge(option.to_sym => send(option))
122
+ hash.merge option.to_sym => send(option)
126
123
  end
127
124
  end
128
125
 
@@ -131,7 +128,7 @@ module CopycopterClient
131
128
  # @param [Hash] hash A set of configuration options that will take precedence over the defaults
132
129
  # @return [Hash] the merged configuration hash
133
130
  def merge(hash)
134
- to_hash.merge(hash)
131
+ to_hash.merge hash
135
132
  end
136
133
 
137
134
  # Determines if the published or draft content will be used
@@ -144,13 +141,13 @@ module CopycopterClient
144
141
  # Determines if the content will be editable
145
142
  # @return [Boolean] Returns +true+ if in a development environment, +false+ otherwise.
146
143
  def development?
147
- development_environments.include?(environment_name)
144
+ development_environments.include? environment_name
148
145
  end
149
146
 
150
147
  # Determines if the content will fetched from the server
151
148
  # @return [Boolean] Returns +true+ if in a test environment, +false+ otherwise.
152
149
  def test?
153
- test_environments.include?(environment_name)
150
+ test_environments.include? environment_name
154
151
  end
155
152
 
156
153
  # Determines if the configuration has been applied (internal)
@@ -168,15 +165,22 @@ module CopycopterClient
168
165
  # When {#test?} returns +false+, the poller will be started.
169
166
  def apply
170
167
  self.client ||= Client.new(to_hash)
171
- self.cache ||= Cache.new(client, to_hash)
172
- poller = Poller.new(cache, to_hash)
168
+ self.cache ||= Cache.new(client, to_hash)
169
+ poller = Poller.new(cache, to_hash)
173
170
  process_guard = ProcessGuard.new(cache, poller, to_hash)
174
- I18n.backend = I18nBackend.new(cache)
175
- middleware.use(RequestSync, :cache => cache) if middleware && development?
171
+ I18n.backend = I18nBackend.new(cache)
172
+
173
+ if middleware && development?
174
+ middleware.use RequestSync, :cache => cache
175
+ end
176
+
176
177
  @applied = true
177
- logger.info("Client #{VERSION} ready")
178
- logger.info("Environment Info: #{environment_info}")
179
- process_guard.start unless test?
178
+ logger.info "Client #{VERSION} ready"
179
+ logger.info "Environment Info: #{environment_info}"
180
+
181
+ unless test?
182
+ process_guard.start
183
+ end
180
184
  end
181
185
 
182
186
  def port
@@ -1,8 +1,7 @@
1
1
  module CopycopterClient
2
2
  # Client version
3
- VERSION = "1.1.2"
3
+ VERSION = '2.0.0'
4
4
 
5
5
  # API version being used to communicate with the server
6
- API_VERSION = "2.0".freeze
6
+ API_VERSION = '2.0'.freeze
7
7
  end
8
-
@@ -4,4 +4,17 @@ namespace :copycopter do
4
4
  CopycopterClient.deploy
5
5
  puts "Successfully marked all blurbs as published."
6
6
  end
7
+
8
+ desc "Export Copycopter blurbs to yaml."
9
+ task :export => :environment do
10
+ CopycopterClient.cache.sync
11
+
12
+ if yml = CopycopterClient.export
13
+ PATH = "config/locales/copycopter.yml"
14
+ File.new("#{Rails.root}/#{PATH}", 'w').write(yml)
15
+ puts "Successfully exported blurbs to #{PATH}."
16
+ else
17
+ puts "No blurbs have been cached."
18
+ end
19
+ end
7
20
  end
@@ -205,5 +205,70 @@ describe CopycopterClient::Cache do
205
205
 
206
206
  cache.should have_received(:flush)
207
207
  end
208
+
209
+ describe "#export" do
210
+ before do
211
+ save_blurbs
212
+ @cache = build_cache
213
+ @cache.download
214
+ end
215
+
216
+ let(:save_blurbs) {}
217
+
218
+ it "can be invoked from the top-level constant" do
219
+ CopycopterClient.configure do |config|
220
+ config.cache = @cache
221
+ end
222
+ @cache.stubs(:export)
223
+
224
+ CopycopterClient.export
225
+
226
+ @cache.should have_received(:export)
227
+ end
228
+
229
+ it "returns no yaml with no blurb keys" do
230
+ @cache.export.should == nil
231
+ end
232
+
233
+ context "with single-level blurb keys" do
234
+ let(:save_blurbs) do
235
+ client['key'] = 'test value'
236
+ client['other_key'] = 'other test value'
237
+ end
238
+
239
+ it "returns blurbs as yaml" do
240
+ exported = YAML.load(@cache.export)
241
+ exported['key'].should == 'test value'
242
+ exported['other_key'].should == 'other test value'
243
+ end
244
+ end
245
+
246
+ context "with multi-level blurb keys" do
247
+ let(:save_blurbs) do
248
+ client['en.test.key'] = 'en test value'
249
+ client['en.test.other_key'] = 'en other test value'
250
+ client['fr.test.key'] = 'fr test value'
251
+ end
252
+
253
+ it "returns blurbs as yaml" do
254
+ exported = YAML.load(@cache.export)
255
+ exported['en']['test']['key'].should == 'en test value'
256
+ exported['en']['test']['other_key'].should == 'en other test value'
257
+ exported['fr']['test']['key'].should == 'fr test value'
258
+ end
259
+ end
260
+
261
+ context "with conflicting blurb keys" do
262
+ let(:save_blurbs) do
263
+ client['en.test'] = 'test value'
264
+ client['en.test.key'] = 'other test value'
265
+ end
266
+
267
+ it "retains the new key" do
268
+ exported = YAML.load(@cache.export)
269
+ exported['en']['test']['key'].should == 'other test value'
270
+ end
271
+ end
272
+ end
208
273
  end
209
274