@@ -34,9 +34,41 @@ def self.yellow(value)
34
34
end
35
35
end
36
36
37
+ # An item of work that corresponds to a file to be processed.
38
+ class FileItem
39
+ attr_reader :filepath
40
+
41
+ def initialize ( filepath )
42
+ @filepath = filepath
43
+ end
44
+
45
+ def handler
46
+ HANDLERS [ File . extname ( filepath ) ]
47
+ end
48
+
49
+ def source
50
+ handler . read ( filepath )
51
+ end
52
+ end
53
+
54
+ # An item of work that corresponds to the stdin content.
55
+ class STDINItem
56
+ def handler
57
+ HANDLERS [ ".rb" ]
58
+ end
59
+
60
+ def filepath
61
+ :stdin
62
+ end
63
+
64
+ def source
65
+ $stdin. read
66
+ end
67
+ end
68
+
37
69
# The parent action class for the CLI that implements the basics.
38
70
class Action
39
- def run ( handler , filepath , source )
71
+ def run ( item )
40
72
end
41
73
42
74
def success
@@ -48,8 +80,8 @@ def failure
48
80
49
81
# An action of the CLI that prints out the AST for the given source.
50
82
class AST < Action
51
- def run ( handler , _filepath , source )
52
- pp handler . parse ( source )
83
+ def run ( item )
84
+ pp item . handler . parse ( item . source )
53
85
end
54
86
end
55
87
@@ -59,10 +91,11 @@ class Check < Action
59
91
class UnformattedError < StandardError
60
92
end
61
93
62
- def run ( handler , filepath , source )
63
- raise UnformattedError if source != handler . format ( source )
94
+ def run ( item )
95
+ source = item . source
96
+ raise UnformattedError if source != item . handler . format ( source )
64
97
rescue StandardError
65
- warn ( "[#{ Color . yellow ( "warn" ) } ] #{ filepath } " )
98
+ warn ( "[#{ Color . yellow ( "warn" ) } ] #{ item . filepath } " )
66
99
raise
67
100
end
68
101
@@ -81,9 +114,11 @@ class Debug < Action
81
114
class NonIdempotentFormatError < StandardError
82
115
end
83
116
84
- def run ( handler , filepath , source )
85
- warning = "[#{ Color . yellow ( "warn" ) } ] #{ filepath } "
86
- formatted = handler . format ( source )
117
+ def run ( item )
118
+ handler = item . handler
119
+
120
+ warning = "[#{ Color . yellow ( "warn" ) } ] #{ item . filepath } "
121
+ formatted = handler . format ( item . source )
87
122
88
123
raise NonIdempotentFormatError if formatted != handler . format ( formatted )
89
124
rescue StandardError
@@ -102,53 +137,56 @@ def failure
102
137
103
138
# An action of the CLI that prints out the doc tree IR for the given source.
104
139
class Doc < Action
105
- def run ( handler , _filepath , source )
140
+ def run ( item )
141
+ source = item . source
142
+
106
143
formatter = Formatter . new ( source , [ ] )
107
- handler . parse ( source ) . format ( formatter )
144
+ item . handler . parse ( source ) . format ( formatter )
108
145
pp formatter . groups . first
109
146
end
110
147
end
111
148
112
149
# An action of the CLI that formats the input source and prints it out.
113
150
class Format < Action
114
- def run ( handler , _filepath , source )
115
- puts handler . format ( source )
151
+ def run ( item )
152
+ puts item . handler . format ( item . source )
116
153
end
117
154
end
118
155
119
156
# An action of the CLI that converts the source into its equivalent JSON
120
157
# representation.
121
158
class Json < Action
122
- def run ( handler , _filepath , source )
123
- object = Visitor ::JSONVisitor . new . visit ( handler . parse ( source ) )
159
+ def run ( item )
160
+ object = Visitor ::JSONVisitor . new . visit ( item . handler . parse ( item . source ) )
124
161
puts JSON . pretty_generate ( object )
125
162
end
126
163
end
127
164
128
165
# An action of the CLI that outputs a pattern-matching Ruby expression that
129
166
# would match the input given.
130
167
class Match < Action
131
- def run ( handler , _filepath , source )
132
- puts handler . parse ( source ) . construct_keys
168
+ def run ( item )
169
+ puts item . handler . parse ( item . source ) . construct_keys
133
170
end
134
171
end
135
172
136
173
# An action of the CLI that formats the input source and writes the
137
174
# formatted output back to the file.
138
175
class Write < Action
139
- def run ( handler , filepath , source )
140
- print filepath
176
+ def run ( item )
177
+ filepath = item . filepath
141
178
start = Time . now
142
179
143
- formatted = handler . format ( source )
180
+ source = item . source
181
+ formatted = item . handler . format ( source )
144
182
File . write ( filepath , formatted ) if filepath != :stdin
145
183
146
184
color = source == formatted ? Color . gray ( filepath ) : filepath
147
185
delta = ( ( Time . now - start ) * 1000 ) . round
148
186
149
- puts "\r #{ color } #{ delta } ms"
187
+ puts "#{ color } #{ delta } ms"
150
188
rescue StandardError
151
- puts " \r #{ filepath } "
189
+ puts filepath
152
190
raise
153
191
end
154
192
end
@@ -258,24 +296,41 @@ def run(argv)
258
296
plugins . split ( "," ) . each { |plugin | require "syntax_tree/#{ plugin } " }
259
297
end
260
298
261
- # Track whether or not there are any errors from any of the files that
262
- # we take action on so that we can properly clean up and exit.
263
- errored = false
264
-
265
- each_file ( arguments ) do |handler , filepath , source |
266
- action . run ( handler , filepath , source )
267
- rescue Parser ::ParseError => error
268
- warn ( "Error: #{ error . message } " )
269
- highlight_error ( error , source )
270
- errored = true
271
- rescue Check ::UnformattedError , Debug ::NonIdempotentFormatError
272
- errored = true
273
- rescue StandardError => error
274
- warn ( error . message )
275
- warn ( error . backtrace )
276
- errored = true
299
+ # We're going to build up a queue of items to process.
300
+ queue = Queue . new
301
+
302
+ # If we're reading from stdin, then we'll just add the stdin object to
303
+ # the queue. Otherwise, we'll add each of the filepaths to the queue.
304
+ if $stdin. tty? || arguments . any?
305
+ arguments . each do |pattern |
306
+ Dir
307
+ . glob ( pattern )
308
+ . each do |filepath |
309
+ queue << FileItem . new ( filepath ) if File . file? ( filepath )
310
+ end
311
+ end
312
+ else
313
+ queue << STDINItem . new
277
314
end
278
315
316
+ # At the end, we're going to return whether or not this worker ever
317
+ # encountered an error.
318
+ errored =
319
+ with_workers ( queue ) do |item |
320
+ action . run ( item )
321
+ false
322
+ rescue Parser ::ParseError => error
323
+ warn ( "Error: #{ error . message } " )
324
+ highlight_error ( error , item . source )
325
+ true
326
+ rescue Check ::UnformattedError , Debug ::NonIdempotentFormatError
327
+ true
328
+ rescue StandardError => error
329
+ warn ( error . message )
330
+ warn ( error . backtrace )
331
+ true
332
+ end
333
+
279
334
if errored
280
335
action . failure
281
336
1
@@ -287,22 +342,33 @@ def run(argv)
287
342
288
343
private
289
344
290
- def each_file ( arguments )
291
- if $stdin. tty? || arguments . any?
292
- arguments . each do |pattern |
293
- Dir
294
- . glob ( pattern )
295
- . each do |filepath |
296
- next unless File . file? ( filepath )
297
-
298
- handler = HANDLERS [ File . extname ( filepath ) ]
299
- source = handler . read ( filepath )
300
- yield handler , filepath , source
301
- end
345
+ def with_workers ( queue )
346
+ # If the queue is just 1 item, then we're not going to bother going
347
+ # through the whole ceremony of parallelizing the work.
348
+ return yield queue . shift if queue . size == 1
349
+
350
+ workers =
351
+ Etc . nprocessors . times . map do
352
+ Thread . new do
353
+ # Propagate errors in the worker threads up to the parent thread.
354
+ Thread . current . abort_on_exception = true
355
+
356
+ # Track whether or not there are any errors from any of the files
357
+ # that we take action on so that we can properly clean up and
358
+ # exit.
359
+ errored = false
360
+
361
+ # While there is still work left to do, shift off the queue and
362
+ # process the item.
363
+ ( errored ||= yield queue . shift ) until queue . empty?
364
+
365
+ # At the end, we're going to return whether or not this worker
366
+ # ever encountered an error.
367
+ errored
368
+ end
302
369
end
303
- else
304
- yield HANDLERS [ ".rb" ] , :stdin , $stdin. read
305
- end
370
+
371
+ workers . inject ( false ) { |accum , thread | accum || thread . value }
306
372
end
307
373
308
374
# Highlights a snippet from a source and parse error.
0 commit comments