percy-cli 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a79b07a50003692a4126836dca74ca72787c479c
4
- data.tar.gz: 44b4607a5fe3267aae10e772c7b7057f33ded2eb
3
+ metadata.gz: 7b25363b82da9f2043e7e5c748bc63d7e7e84535
4
+ data.tar.gz: f511e267c41c23f9b3cb04263ae6491919a811ce
5
5
  SHA512:
6
- metadata.gz: 29b2632be605f99a12265a798b7afb40552934bc7445f514a48723ed9842d84672d1524a2f7f1f32fdc1f2615043ed96d14bdb526c3165961a03a0e5590a4549
7
- data.tar.gz: 14b7f5941ca563a9bf5c3df841638a162d5c16dd3e3ede57fc644e9fe1190d39ba63dc6e10893f6e6a1e3a644c387e6f652a0bad0f57b0a1035b0b829cd57639
6
+ metadata.gz: 7b230e067e0ec50a21f27bd6fe0e53152f229a6502f1df9db3e5c50168f0091558d1bf73f5447de4468fef190420c8ee04b8909f7fa3df5161b8a766b4977b6a
7
+ data.tar.gz: 117c24852676b9870e6baebd3120697db9c1d4ffd609f79819cca57547d7e479cba3a370629897b17f07bf66265eef32b12acd0ee3b74091e90a6e4234ea3d6d
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Percy CLI
2
2
 
3
+ [![Build Status](https://travis-ci.org/percy/percy-cli.svg?branch=master)](https://travis-ci.org/percy/percy-cli)
4
+ [![Gem Version](https://badge.fury.io/rb/percy-cli.svg)](http://badge.fury.io/rb/percy-cli)
5
+
3
6
  Command-line interface for [Percy](https://percy.io).
4
7
 
5
8
  ## Installation
data/bin/percy CHANGED
@@ -3,4 +3,4 @@
3
3
  require 'commander'
4
4
  require 'percy/cli'
5
5
 
6
- Percy::Cli.new.run if $0 == __FILE__
6
+ Percy::Cli.new.run
@@ -1,6 +1,9 @@
1
1
  require 'find'
2
2
  require 'digest'
3
3
  require 'uri'
4
+ require 'thread/pool'
5
+
6
+ Thread::Pool.abort_on_exception = true
4
7
 
5
8
  module Percy
6
9
  class Cli
@@ -44,9 +47,9 @@ module Percy
44
47
  # resource path
45
48
  "(?:/[^\\s\"']*)?"
46
49
  )
47
- HTML_REMOTE_URL_REGEX = Regexp.new("<link.*?href=['\"](" + REMOTE_URL_REGEX_STRING + ")")
50
+ HTML_REMOTE_URL_REGEX = Regexp.new("(<link.*?href=['\"](" + REMOTE_URL_REGEX_STRING + ")[^>]+)")
48
51
 
49
- # Match all url("https://...") calls, with whitespace and quote variatinos.
52
+ # Match all url("https://...") styles, with whitespace and quote variatinos.
50
53
  CSS_REMOTE_URL_REGEX = Regexp.new(
51
54
  "url\\s*\\([\"'\s]*(" + REMOTE_URL_REGEX_STRING + ")[\"'\s]*\\)"
52
55
  )
@@ -55,9 +58,8 @@ module Percy
55
58
  repo = options[:repo] || Percy.config.repo
56
59
  strip_prefix = File.absolute_path(options[:strip_prefix] || root_dir)
57
60
  autoload_remote_resources = options[:autoload_remote_resources] || false
58
-
59
- # Create a Percy build for the snapshots.
60
- build = Percy.create_build(repo)
61
+ num_threads = options[:threads] || 10
62
+ snapshot_limit = options[:snapshot_limit]
61
63
 
62
64
  # Find all the static files in the given root directory.
63
65
  root_paths = find_root_paths(root_dir, snapshots_regex: options[:snapshots_regex])
@@ -70,14 +72,42 @@ module Percy
70
72
  related_resources += build_remote_resources(remote_urls)
71
73
  end
72
74
 
75
+ all_resources = root_resources + related_resources
76
+
77
+ if root_resources.empty?
78
+ say "No root resource files found. Are there HTML files in the given directory?"
79
+ exit(-1)
80
+ end
81
+
82
+ say 'Creating build...'
83
+ build = Percy.create_build(repo, resources: related_resources)
84
+
85
+ say 'Uploading build resources...'
86
+ upload_missing_resources(build, build, all_resources, {num_threads: num_threads})
87
+
73
88
  # Upload a snapshot for every root resource, and associate the related_resources.
89
+ output_lock = Mutex.new
90
+ snapshot_thread_pool = Thread.pool(num_threads)
91
+ total = snapshot_limit ? [root_resources.length, snapshot_limit].min : root_resources.length
74
92
  root_resources.each_with_index do |root_resource, i|
75
- say "Uploading snapshot (#{i+1}/#{root_resources.length}): #{root_resource.resource_url}"
76
- upload_snapshot(build, root_resource, related_resources)
93
+ break if snapshot_limit && i + 1 > snapshot_limit
94
+ snapshot_thread_pool.process do
95
+ output_lock.synchronize do
96
+ say "Uploading snapshot (#{i+1}/#{total}): #{root_resource.resource_url}"
97
+ end
98
+ snapshot = Percy.create_snapshot(build['data']['id'], [root_resource])
99
+ upload_missing_resources(build, snapshot, all_resources, {num_threads: num_threads})
100
+ Percy.finalize_snapshot(snapshot['data']['id'])
101
+ end
77
102
  end
103
+ snapshot_thread_pool.wait
104
+ snapshot_thread_pool.shutdown
78
105
 
79
106
  # Finalize the build.
107
+ say 'Finalizing build...'
80
108
  Percy.finalize_build(build['data']['id'])
109
+ say "Done! Percy is now processing, you can view the visual diffs here:"
110
+ say build['data']['attributes']['web-url']
81
111
  end
82
112
 
83
113
  private
@@ -121,13 +151,16 @@ module Percy
121
151
  case extension
122
152
  when '.html'
123
153
  content = File.read(path)
124
- urls += content.scan(HTML_REMOTE_URL_REGEX).map { |match| maybe_add_protocol(match[0]) }
154
+ urls += content.scan(HTML_REMOTE_URL_REGEX).map do |match|
155
+ next if !match[0].include?('stylesheet') # Only include links with rel="stylesheet".
156
+ maybe_add_protocol(match[1])
157
+ end
125
158
  when '.css'
126
159
  content = File.read(path)
127
160
  urls += content.scan(CSS_REMOTE_URL_REGEX).map { |match| maybe_add_protocol(match[0]) }
128
161
  end
129
162
  end
130
- urls.uniq
163
+ urls.compact.uniq
131
164
  end
132
165
 
133
166
  def maybe_add_protocol(url)
@@ -156,8 +189,8 @@ module Percy
156
189
  remote_urls.length,
157
190
  title: 'Fetching remote resources...',
158
191
  format: ':title |:progress_bar| :percent_complete% complete - :url',
159
- width: 40,
160
- complete_message: nil,
192
+ width: 20,
193
+ complete_message: "Fetched #{remote_urls.length} remote resources.",
161
194
  )
162
195
 
163
196
  remote_urls.each do |url|
@@ -169,7 +202,7 @@ module Percy
169
202
  next
170
203
  end
171
204
  if response.status != 200
172
- say_error "Remote resource failed, skipping: #{url}"
205
+ say_error "Remote resource failed, skipping (#{response.status}): #{url}"
173
206
  next
174
207
  end
175
208
 
@@ -179,34 +212,37 @@ module Percy
179
212
  resources
180
213
  end
181
214
 
182
- def upload_snapshot(build, root_resource, related_resources)
183
- all_resources = [root_resource] + related_resources
184
-
185
- # Create the snapshot for this page. For simplicity, include all non-HTML resources in the
186
- # snapshot as related resources. May seem inefficient, but they will only be uploaded once.
187
- snapshot = Percy.create_snapshot(build['data']['id'], all_resources)
188
-
215
+ # Uploads missing resources either for a build or snapshot.
216
+ def upload_missing_resources(build, obj, potential_resources, options = {})
189
217
  # Upload the content for any missing resources.
190
- missing_resources = snapshot['data']['relationships']['missing-resources']['data']
218
+ missing_resources = obj['data']['relationships']['missing-resources']['data']
191
219
  bar = Commander::UI::ProgressBar.new(
192
220
  missing_resources.length,
193
221
  title: 'Uploading resources...',
194
222
  format: ':title |:progress_bar| :percent_complete% complete - :resource_url',
195
- width: 40,
223
+ width: 20,
196
224
  complete_message: nil,
197
225
  )
226
+ output_lock = Mutex.new
227
+ uploader_thread_pool = Thread.pool(options[:num_threads] || 10)
198
228
  missing_resources.each do |missing_resource|
199
- missing_resource_sha = missing_resource['id']
200
- resource = all_resources.find { |r| r.sha == missing_resource_sha }
201
- path = resource.resource_url
202
- bar.increment resource_url: resource.resource_url
203
-
204
- # Remote resources are stored in 'content', local resources are read from the filesystem.
205
- content = resource.content || File.read("#{resource.path}")
206
-
207
- Percy.upload_resource(build['data']['id'], content)
229
+ uploader_thread_pool.process do
230
+ missing_resource_sha = missing_resource['id']
231
+ resource = potential_resources.find { |r| r.sha == missing_resource_sha }
232
+ path = resource.resource_url
233
+ output_lock.synchronize do
234
+ bar.increment resource_url: resource.resource_url
235
+ end
236
+
237
+ # Remote resources are stored in 'content', local resources are read from the filesystem.
238
+ content = resource.content || File.read("#{resource.path}")
239
+
240
+ Percy.upload_resource(build['data']['id'], content)
241
+ end
208
242
  end
243
+ uploader_thread_pool.wait
244
+ uploader_thread_pool.shutdown
209
245
  end
210
246
  end
211
247
  end
212
- end
248
+ end
@@ -1,5 +1,5 @@
1
1
  module Percy
2
2
  class Cli
3
- VERSION = '0.0.2'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
data/lib/percy/cli.rb CHANGED
@@ -8,6 +8,9 @@ module Percy
8
8
  include Commander::Methods
9
9
  include Percy::Cli::Snapshot
10
10
 
11
+ DEFAULT_NUM_THREADS = 10
12
+ MAX_NUM_THREADS = 50
13
+
11
14
  def say(*args)
12
15
  $terminal.say(*args)
13
16
  end
@@ -38,13 +41,25 @@ module Percy
38
41
  '--snapshots_regex REGEX',
39
42
  String,
40
43
  'Regular expression for matching the files to snapshot. Defaults to: "\.(html|htm)$"'
44
+ c.option \
45
+ '--snapshot_limit NUM',
46
+ Integer,
47
+ "Max number of snapshots to upload, useful for testing. Default is unlimited."
41
48
  c.option \
42
49
  '--autoload_remote_resources',
43
50
  'Attempts to parse HTML and CSS for remote resources, fetch them, and include in ' +
44
51
  'snapshots. This can be very useful if your static website relies on remote resources.'
52
+ c.option \
53
+ '--threads NUM',
54
+ Integer,
55
+ "Number of threads in pools for snapshot and resource uploads. " +
56
+ "Defaults to #{DEFAULT_NUM_THREADS}, max #{MAX_NUM_THREADS}."
45
57
 
46
58
  c.action do |args, options|
47
59
  options.default autoload_remote_resources: false
60
+ options.default threads: DEFAULT_NUM_THREADS
61
+ options.threads = MAX_NUM_THREADS if options.threads > MAX_NUM_THREADS
62
+
48
63
  raise OptionParser::MissingArgument, 'root folder path is required' if args.empty?
49
64
  if args.length > 1
50
65
  raise OptionParser::MissingArgument, 'only a single root folder path can be given'
data/percy-cli.gemspec CHANGED
@@ -19,8 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_dependency 'commander', '~> 4'
22
- spec.add_dependency 'percy-client', '~> 0.2'
22
+ spec.add_dependency 'percy-client', '>= 0.2.4'
23
23
  spec.add_dependency 'faraday', '>= 0.8'
24
+ spec.add_dependency 'thread', '~> 0.2'
24
25
 
25
26
  spec.add_development_dependency 'bundler', '~> 1.7'
26
27
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -12,6 +12,8 @@
12
12
 
13
13
  <link rel="stylesheet" type="text/css" href="http://example.com:12345/test-diff-tag-order.css">
14
14
 
15
+ <link rel="author" href="https://example.com/should-not-be-included"/>
16
+
15
17
  <h1>Hello World!</h1>
16
18
 
17
19
  Just some text, should not be included:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: percy-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Perceptual Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-05 00:00:00.000000000 Z
11
+ date: 2015-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: percy-client
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: 0.2.4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
40
+ version: 0.2.4
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: faraday
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thread
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement