command-set 0.10.1 → 0.10.2
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.
- data/lib/command-set/dsl.rb +31 -5
- data/lib/command-set/formatter/base.rb +20 -15
- data/lib/command-set/formatter/hash-array.rb +41 -0
- data/lib/command-set/formatter/strategy.rb +1 -1
- data/lib/command-set/formatter/xml.rb +1 -1
- data/lib/command-set/interpreter/quick.rb +1 -1
- data/lib/command-set/interpreter/text.rb +2 -61
- data/lib/command-set/readline.rb +60 -0
- data/lib/command-set/result-list.rb +1 -1
- data/lib/command-set/results.rb +48 -69
- metadata +5 -3
data/lib/command-set/dsl.rb
CHANGED
@@ -626,12 +626,38 @@ Action:: utility functions within the command action block
|
|
626
626
|
raise CommandException, "#{@name} cannot be undone"
|
627
627
|
end
|
628
628
|
|
629
|
-
#
|
630
|
-
#
|
631
|
-
#
|
632
|
-
def
|
633
|
-
|
629
|
+
#For big jobs - splitting them into subthreads and
|
630
|
+
#such. But they need to be debugged, and IIRC there's a deadlock
|
631
|
+
#condition
|
632
|
+
def action_thread(&block)
|
633
|
+
collector = sub_collector
|
634
|
+
return Thread.new do
|
635
|
+
$stdout.set_thread_collector(collector)
|
636
|
+
block.call
|
637
|
+
$stdout.remove_thread_collector(collector)
|
638
|
+
end
|
634
639
|
end
|
640
|
+
|
641
|
+
def fan_out(threads_at_a_time, array, &block)
|
642
|
+
require 'thwait'
|
643
|
+
|
644
|
+
array = array.to_a
|
645
|
+
first_batch = (array[0...threads_at_a_time]||[]).map do |item|
|
646
|
+
action_thread { block.call(item) }
|
647
|
+
end
|
648
|
+
|
649
|
+
rest = (array[threads_at_a_time..-1] || [])
|
650
|
+
|
651
|
+
waiter = ThreadsWait.new(*first_batch)
|
652
|
+
|
653
|
+
rest.each do |item|
|
654
|
+
waiter.next_wait
|
655
|
+
waiter.join_nowait(action_thread{block.call(item)})
|
656
|
+
end
|
657
|
+
|
658
|
+
waiter.join
|
659
|
+
end
|
660
|
+
|
635
661
|
end
|
636
662
|
end
|
637
663
|
end
|
@@ -109,9 +109,7 @@ module Command::Results
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
def initialize(
|
113
|
-
@out_to = out || ::Command::raw_stdout
|
114
|
-
@err_to = err || ::Command::raw_stderr
|
112
|
+
def initialize()
|
115
113
|
@advisor = FormatAdvisor.new(self)
|
116
114
|
@advice = {:list => [], :item => [], :output => []}
|
117
115
|
end
|
@@ -141,15 +139,6 @@ module Command::Results
|
|
141
139
|
{}
|
142
140
|
end
|
143
141
|
|
144
|
-
def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
|
145
|
-
|
146
|
-
def self.inherited(sub)
|
147
|
-
sub.extend Forwardable
|
148
|
-
sub.class_eval do
|
149
|
-
def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
142
|
#Presenter callback: output is beginning
|
154
143
|
def start; end
|
155
144
|
|
@@ -175,10 +164,26 @@ module Command::Results
|
|
175
164
|
def finish; end
|
176
165
|
end
|
177
166
|
|
178
|
-
#The simplest useful Formatter: it outputs the value of every item in tree
|
179
|
-
#it as what would happen if you just let puts and p go
|
180
|
-
#annoying consequences of threading,
|
167
|
+
#The simplest useful Formatter: it outputs the value of every item in tree
|
168
|
+
#order. Think of it as what would happen if you just let puts and p go
|
169
|
+
#directly to the screen, without the annoying consequences of threading,
|
170
|
+
#etc.
|
181
171
|
class TextFormatter < Formatter
|
172
|
+
def initialize(out = nil, err = nil)
|
173
|
+
@out_to = out || ::Command::raw_stdout
|
174
|
+
@err_to = err || ::Command::raw_stderr
|
175
|
+
super()
|
176
|
+
end
|
177
|
+
|
178
|
+
def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
|
179
|
+
|
180
|
+
def self.inherited(sub)
|
181
|
+
sub.extend Forwardable
|
182
|
+
sub.class_eval do
|
183
|
+
def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
182
187
|
def closed_item(value)
|
183
188
|
puts value
|
184
189
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'command-set/results'
|
2
|
+
|
3
|
+
module Command::Results
|
4
|
+
class HashArrayFormatter < Formatter
|
5
|
+
def initialize
|
6
|
+
@hash_stack = [{:array => []}]
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def hash
|
11
|
+
@hash_stack.last
|
12
|
+
end
|
13
|
+
|
14
|
+
def array
|
15
|
+
hash[:array]
|
16
|
+
end
|
17
|
+
|
18
|
+
def closed_begin_list(list)
|
19
|
+
list_array = []
|
20
|
+
list_hash = {:array => list_array}
|
21
|
+
array.push(list_array)
|
22
|
+
hash[array().length.to_s] = list_hash
|
23
|
+
hash[list.name] = list_hash
|
24
|
+
@hash_stack.push(list_hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def closed_item(item)
|
28
|
+
thing = item.value
|
29
|
+
array().push(thing)
|
30
|
+
hash()[array().length.to_s] = thing
|
31
|
+
end
|
32
|
+
|
33
|
+
def closed_end_list(list)
|
34
|
+
@hash_stack.pop
|
35
|
+
end
|
36
|
+
|
37
|
+
def structure
|
38
|
+
@hash_stack.first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -4,7 +4,7 @@ module Command::Results
|
|
4
4
|
#A trivial and obvious Formatter: produces well-formed XML fragments based on events. It even
|
5
5
|
#indents. Might be handy for more complicated output processing, since you could feed the document
|
6
6
|
#to a XSLT processor.
|
7
|
-
class XMLFormatter <
|
7
|
+
class XMLFormatter < TextFormatter
|
8
8
|
def initialize(out = nil, err = nil, indent=" ", newline="\n")
|
9
9
|
super(out, err)
|
10
10
|
@indent = indent
|
@@ -1,66 +1,7 @@
|
|
1
|
-
require 'readline'
|
2
1
|
require 'command-set'
|
3
|
-
require 'dl/import'
|
4
2
|
require 'command-set/interpreter/base'
|
5
|
-
require 'command-set/formatter/
|
6
|
-
|
7
|
-
#This really locks text-interpreter down to Linux, maybe Unix-like
|
8
|
-
#platforms, I'm thinking. A more flexible way of doing this would rock,
|
9
|
-
#regardless of length.
|
10
|
-
module Readline
|
11
|
-
begin
|
12
|
-
extend DL::Importable
|
13
|
-
found_libreadline = false
|
14
|
-
ls_so_dirs = [
|
15
|
-
%w{lib},
|
16
|
-
%w{usr lib},
|
17
|
-
%w{usr local lib}
|
18
|
-
].each{|path| path.unshift("")}
|
19
|
-
|
20
|
-
begin
|
21
|
-
File::open("/etc/ld.so.conf", "r") do |ld_so_conf|
|
22
|
-
ld_so_conf.each do |line|
|
23
|
-
unless /^#/ =~ line or /^%s*$/ =~ line
|
24
|
-
ls_so_dirs << line.chomp.split(File::Separator)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
rescue Exception
|
29
|
-
end
|
30
|
-
|
31
|
-
libreadline_names = %w{libreadline.so libreadline.dylib libreadline.dll}
|
32
|
-
|
33
|
-
libreadline_paths = ls_so_dirs.inject([]) do |list, dir|
|
34
|
-
list + libreadline_names.map do |name|
|
35
|
-
File::join(*(dir + [name]))
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
libreadline_paths.each do |path|
|
40
|
-
begin
|
41
|
-
dlload path
|
42
|
-
RLLB = symbol("rl_line_buffer")
|
43
|
-
found_libreadline = true
|
44
|
-
break
|
45
|
-
rescue RuntimeError
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
raise RuntimeError,"couldn't find libreadline.so" unless found_libreadline
|
50
|
-
|
51
|
-
def self.line_buffer
|
52
|
-
p = RLLB.ptr
|
53
|
-
if p.nil?
|
54
|
-
return p
|
55
|
-
else
|
56
|
-
return p.to_s
|
57
|
-
end
|
58
|
-
end
|
59
|
-
rescue RuntimeError => rte
|
60
|
-
warn "Couldn't find libreadline - tab-completion will be unpredictable at best."
|
61
|
-
warn "The problem was: " + rte.message
|
62
|
-
end
|
63
|
-
end
|
3
|
+
require 'command-set/formatter/base'
|
4
|
+
require 'command-set/readline'
|
64
5
|
|
65
6
|
module Command
|
66
7
|
class TextInterpreter < BaseInterpreter
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'dl/import'
|
3
|
+
|
4
|
+
#This really locks text-interpreter down to Linux, maybe Unix-like
|
5
|
+
#platforms, I'm thinking. A more flexible way of doing this would rock,
|
6
|
+
#regardless of length.
|
7
|
+
module Readline
|
8
|
+
begin
|
9
|
+
extend DL::Importable
|
10
|
+
found_libreadline = false
|
11
|
+
ls_so_dirs = [
|
12
|
+
%w{lib},
|
13
|
+
%w{usr lib},
|
14
|
+
%w{usr local lib}
|
15
|
+
].each{|path| path.unshift("")}
|
16
|
+
|
17
|
+
begin
|
18
|
+
File::open("/etc/ld.so.conf", "r") do |ld_so_conf|
|
19
|
+
ld_so_conf.each do |line|
|
20
|
+
unless /^#/ =~ line or /^%s*$/ =~ line
|
21
|
+
ls_so_dirs << line.chomp.split(File::Separator)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue Exception
|
26
|
+
end
|
27
|
+
|
28
|
+
libreadline_names = %w{libreadline.so libreadline.dylib libreadline.dll}
|
29
|
+
|
30
|
+
libreadline_paths = ls_so_dirs.inject([]) do |list, dir|
|
31
|
+
list + libreadline_names.map do |name|
|
32
|
+
File::join(*(dir + [name]))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
libreadline_paths.each do |path|
|
37
|
+
begin
|
38
|
+
dlload path
|
39
|
+
RLLB = symbol("rl_line_buffer")
|
40
|
+
found_libreadline = true
|
41
|
+
break
|
42
|
+
rescue RuntimeError
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
raise RuntimeError,"couldn't find libreadline" unless found_libreadline
|
47
|
+
|
48
|
+
def self.line_buffer
|
49
|
+
p = RLLB.ptr
|
50
|
+
if p.nil?
|
51
|
+
return p
|
52
|
+
else
|
53
|
+
return p.to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue RuntimeError => rte
|
57
|
+
warn "Couldn't find libreadline - tab-completion will be unpredictable at best."
|
58
|
+
warn "The problem was: " + rte.message
|
59
|
+
end
|
60
|
+
end
|
data/lib/command-set/results.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'command-set/result-list'
|
2
2
|
require 'Win32/Console/ANSI' if PLATFORM =~ /win32/
|
3
|
+
require 'thread'
|
3
4
|
|
4
5
|
module Kernel
|
5
6
|
def puts(*args)
|
@@ -172,9 +173,9 @@ module Command
|
|
172
173
|
#understand that other Collectors could be running at the same time - that's the
|
173
174
|
#Presenter's job.
|
174
175
|
class Collector
|
175
|
-
def initialize(presenter)
|
176
|
+
def initialize(presenter, list_root)
|
176
177
|
@presenter = presenter
|
177
|
-
@nesting = []
|
178
|
+
@nesting = [list_root]
|
178
179
|
end
|
179
180
|
|
180
181
|
def initialize_copy(original)
|
@@ -194,23 +195,11 @@ module Command
|
|
194
195
|
end
|
195
196
|
|
196
197
|
def item( obj, options={} )
|
197
|
-
@presenter.item(@nesting.
|
198
|
+
@presenter.item(@nesting.last, obj, options)
|
198
199
|
end
|
199
200
|
|
200
201
|
def begin_list( name, options={} )
|
201
|
-
@nesting.
|
202
|
-
@presenter.begin_list(@nesting.dup, options)
|
203
|
-
if block_given?
|
204
|
-
yield
|
205
|
-
end_list
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
def open_list(name, options={})
|
210
|
-
@nesting.push(name)
|
211
|
-
unless @presenter.list_open?(@nesting)
|
212
|
-
@presenter.begin_list(@nesting.dup, options)
|
213
|
-
end
|
202
|
+
@nesting << @presenter.begin_list(@nesting.last, name, options)
|
214
203
|
if block_given?
|
215
204
|
yield
|
216
205
|
end_list
|
@@ -218,8 +207,7 @@ module Command
|
|
218
207
|
end
|
219
208
|
|
220
209
|
def end_list
|
221
|
-
@presenter.end_list(@nesting.
|
222
|
-
@nesting.pop
|
210
|
+
@presenter.end_list(@nesting.pop)
|
223
211
|
end
|
224
212
|
|
225
213
|
@dispatches = {}
|
@@ -236,8 +224,8 @@ module Command
|
|
236
224
|
self.class.dispatches
|
237
225
|
end
|
238
226
|
|
239
|
-
#Use to register an IO +method+ to handle. The block will be passed a
|
240
|
-
#arguments passed to +method+.
|
227
|
+
#Use to register an IO +method+ to handle. The block will be passed a
|
228
|
+
#Collector and the arguments passed to +method+.
|
241
229
|
def self.dispatch(method, &block)
|
242
230
|
@dispatches[method] = true
|
243
231
|
define_method(method, &block)
|
@@ -263,7 +251,7 @@ module Command
|
|
263
251
|
end
|
264
252
|
|
265
253
|
#Gets item and list events from Collectors, and emits two kinds of
|
266
|
-
#events to Formatters:
|
254
|
+
#events to Formatters:
|
267
255
|
#[+saw+ events] occur in chronological order, with no guarantee regarding timing.
|
268
256
|
#[+closed+ events] occur in tree order.
|
269
257
|
#
|
@@ -272,14 +260,15 @@ module Command
|
|
272
260
|
#soon as the relevant output element enters the system.
|
273
261
|
#
|
274
262
|
#On the other hand, +closed+ events will be generated in the natural
|
275
|
-
#order you'd expect the output to appear in. Most Formatter subclasses
|
276
|
-
|
263
|
+
#order you'd expect the output to appear in. Most Formatter subclasses
|
264
|
+
#use +closed+ events.
|
277
265
|
#
|
278
266
|
#A list which has not received a "list_end" event from upstream will
|
279
267
|
#block lists later in tree order until it closes. A Formatter that
|
280
268
|
#listens only to +closed+ events can present them to the user in a way
|
281
269
|
#that should be reasonable, although output might be halting for any
|
282
270
|
#process that takes noticeable time.
|
271
|
+
#
|
283
272
|
class Presenter
|
284
273
|
class Exception < ::Exception; end
|
285
274
|
|
@@ -287,57 +276,48 @@ module Command
|
|
287
276
|
@results = List.new("")
|
288
277
|
@leading_edge = @results
|
289
278
|
@formatters = []
|
279
|
+
@list_lock = Mutex.new
|
290
280
|
end
|
291
281
|
|
292
282
|
def create_collector
|
293
|
-
return Collector.new(self)
|
283
|
+
return Collector.new(self, @results)
|
294
284
|
end
|
295
285
|
|
296
|
-
def
|
297
|
-
|
298
|
-
|
299
|
-
item = home.add item
|
300
|
-
item.options = home.options.merge(options)
|
301
|
-
item.depth = item_path.length
|
302
|
-
notify(:saw, item)
|
303
|
-
advance_leading_edge
|
286
|
+
def register_formatter(formatter)
|
287
|
+
@formatters << formatter
|
288
|
+
formatter.notify(:start, nil)
|
304
289
|
end
|
305
290
|
|
306
291
|
def leading_edge?(list)
|
307
292
|
return list == @leading_edge
|
308
293
|
end
|
309
294
|
|
310
|
-
def
|
311
|
-
|
295
|
+
def item( home, value, options={} )
|
296
|
+
item = ListItem.new(value)
|
312
297
|
|
313
|
-
|
298
|
+
add_item(home, item, options)
|
299
|
+
|
300
|
+
notify(:saw, item)
|
301
|
+
return nil
|
314
302
|
end
|
315
303
|
|
316
|
-
def begin_list(
|
317
|
-
list =
|
318
|
-
|
319
|
-
list
|
320
|
-
|
321
|
-
list.depth = list_path.length
|
304
|
+
def begin_list( home, name, options={} )
|
305
|
+
list = List.new(name)
|
306
|
+
|
307
|
+
add_item(home, list, options)
|
308
|
+
|
322
309
|
notify(:saw_begin, list)
|
323
|
-
|
324
|
-
advance_leading_edge
|
310
|
+
return list
|
325
311
|
end
|
326
312
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
rescue Exception
|
332
|
-
return false
|
313
|
+
def end_list( list )
|
314
|
+
@list_lock.synchronize do
|
315
|
+
list.close
|
316
|
+
advance_leading_edge
|
333
317
|
end
|
334
|
-
end
|
335
318
|
|
336
|
-
def end_list( list_path )
|
337
|
-
list = get_collection( list_path )
|
338
319
|
notify(:saw_end, list)
|
339
|
-
|
340
|
-
advance_leading_edge
|
320
|
+
return nil
|
341
321
|
end
|
342
322
|
|
343
323
|
def done
|
@@ -346,8 +326,9 @@ module Command
|
|
346
326
|
notify(:done, nil)
|
347
327
|
end
|
348
328
|
|
349
|
-
#Returns the current list of results. A particularly advanced
|
350
|
-
#like notifications, and then use
|
329
|
+
#Returns the current list of results. A particularly advanced
|
330
|
+
#Formatter might treat +saw_*+ events like notifications, and then use
|
331
|
+
#the List#filter functionality to discover the specifics about the
|
351
332
|
#item or list just closed.
|
352
333
|
def output
|
353
334
|
@results
|
@@ -372,23 +353,21 @@ module Command
|
|
372
353
|
end
|
373
354
|
end
|
374
355
|
|
375
|
-
def
|
376
|
-
|
377
|
-
|
356
|
+
def add_item(home, item, options)
|
357
|
+
item.depth = home.depth + 1
|
358
|
+
|
359
|
+
@list_lock.synchronize do
|
360
|
+
#home = get_collection(path)
|
361
|
+
item.options = home.options.merge(options)
|
362
|
+
home.add(item)
|
363
|
+
advance_leading_edge
|
378
364
|
end
|
379
365
|
end
|
380
366
|
|
381
|
-
def
|
382
|
-
|
383
|
-
|
384
|
-
path.each do |step|
|
385
|
-
list = thumb.find{|member| List === member && member.open? && member.name == step}
|
386
|
-
if list.nil?
|
387
|
-
raise Exception, "#{step} in #{path.inspect} missing from #{thumb}!"
|
388
|
-
end
|
389
|
-
thumb = list.values
|
367
|
+
def notify(msg, item)
|
368
|
+
@formatters.each do |f|
|
369
|
+
f.notify(msg, item)
|
390
370
|
end
|
391
|
-
return list
|
392
371
|
end
|
393
372
|
end
|
394
373
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: command-set
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.10.
|
7
|
-
date: 2008-02-
|
6
|
+
version: 0.10.2
|
7
|
+
date: 2008-02-19 00:00:00 -08:00
|
8
8
|
summary: Framework for interactive programs focused around a DSL for commands
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -45,11 +45,13 @@ files:
|
|
45
45
|
- lib/command-set/dsl.rb
|
46
46
|
- lib/command-set/formatter
|
47
47
|
- lib/command-set/formatter/strategy.rb
|
48
|
+
- lib/command-set/formatter/hash-array.rb
|
48
49
|
- lib/command-set/formatter/base.rb
|
49
50
|
- lib/command-set/formatter/xml.rb
|
50
51
|
- lib/command-set/command.rb
|
51
52
|
- lib/command-set/standard-commands.rb
|
52
53
|
- lib/command-set/results.rb
|
54
|
+
- lib/command-set/readline.rb
|
53
55
|
- doc/README
|
54
56
|
- doc/GUIDED_TOUR
|
55
57
|
- doc/Specifications
|
@@ -61,7 +63,7 @@ rdoc_options:
|
|
61
63
|
- --main
|
62
64
|
- Command
|
63
65
|
- --title
|
64
|
-
- command-set-0.10.
|
66
|
+
- command-set-0.10.2 RDoc
|
65
67
|
extra_rdoc_files:
|
66
68
|
- doc/README
|
67
69
|
- doc/GUIDED_TOUR
|