copycopter_client 1.1.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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