@@ -34,9 +34,41 @@ def self.yellow(value)
3434 end
3535 end
3636
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+
3769 # The parent action class for the CLI that implements the basics.
3870 class Action
39- def run ( handler , filepath , source )
71+ def run ( item )
4072 end
4173
4274 def success
@@ -48,8 +80,8 @@ def failure
4880
4981 # An action of the CLI that prints out the AST for the given source.
5082 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 )
5385 end
5486 end
5587
@@ -59,10 +91,11 @@ class Check < Action
5991 class UnformattedError < StandardError
6092 end
6193
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 )
6497 rescue StandardError
65- warn ( "[#{ Color . yellow ( "warn" ) } ] #{ filepath } " )
98+ warn ( "[#{ Color . yellow ( "warn" ) } ] #{ item . filepath } " )
6699 raise
67100 end
68101
@@ -81,9 +114,11 @@ class Debug < Action
81114 class NonIdempotentFormatError < StandardError
82115 end
83116
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 )
87122
88123 raise NonIdempotentFormatError if formatted != handler . format ( formatted )
89124 rescue StandardError
@@ -102,53 +137,56 @@ def failure
102137
103138 # An action of the CLI that prints out the doc tree IR for the given source.
104139 class Doc < Action
105- def run ( handler , _filepath , source )
140+ def run ( item )
141+ source = item . source
142+
106143 formatter = Formatter . new ( source , [ ] )
107- handler . parse ( source ) . format ( formatter )
144+ item . handler . parse ( source ) . format ( formatter )
108145 pp formatter . groups . first
109146 end
110147 end
111148
112149 # An action of the CLI that formats the input source and prints it out.
113150 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 )
116153 end
117154 end
118155
119156 # An action of the CLI that converts the source into its equivalent JSON
120157 # representation.
121158 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 ) )
124161 puts JSON . pretty_generate ( object )
125162 end
126163 end
127164
128165 # An action of the CLI that outputs a pattern-matching Ruby expression that
129166 # would match the input given.
130167 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
133170 end
134171 end
135172
136173 # An action of the CLI that formats the input source and writes the
137174 # formatted output back to the file.
138175 class Write < Action
139- def run ( handler , filepath , source )
140- print filepath
176+ def run ( item )
177+ filepath = item . filepath
141178 start = Time . now
142179
143- formatted = handler . format ( source )
180+ source = item . source
181+ formatted = item . handler . format ( source )
144182 File . write ( filepath , formatted ) if filepath != :stdin
145183
146184 color = source == formatted ? Color . gray ( filepath ) : filepath
147185 delta = ( ( Time . now - start ) * 1000 ) . round
148186
149- puts "\r #{ color } #{ delta } ms"
187+ puts "#{ color } #{ delta } ms"
150188 rescue StandardError
151- puts " \r #{ filepath } "
189+ puts filepath
152190 raise
153191 end
154192 end
@@ -258,24 +296,41 @@ def run(argv)
258296 plugins . split ( "," ) . each { |plugin | require "syntax_tree/#{ plugin } " }
259297 end
260298
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
277314 end
278315
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+
279334 if errored
280335 action . failure
281336 1
@@ -287,22 +342,33 @@ def run(argv)
287342
288343 private
289344
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
302369 end
303- else
304- yield HANDLERS [ ".rb" ] , :stdin , $stdin. read
305- end
370+
371+ workers . inject ( false ) { |accum , thread | accum || thread . value }
306372 end
307373
308374 # Highlights a snippet from a source and parse error.
0 commit comments