command-set 0.10.2 → 0.10.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ #!/bin/env ruby
2
+ # vim: filetype=ruby
3
+
4
+ require 'optparse'
5
+ require 'command-set/interpreter/recording'
6
+
7
+ recording = nil
8
+ pause_for = 0
9
+
10
+ option_parser = OptionParser.new do |opts|
11
+ opts.on("-i", "--input RECORDING", "The path to load the session recording from") do |path|
12
+ recording = path
13
+ end
14
+
15
+ opts.on("-d", "--delay [DURATION]", Float, "How long to pause before next input - helps present the illusion of watching a playback.") do |pause|
16
+ if pause.nil?
17
+ pause_for = 0.2
18
+ else
19
+ pause_for = pause
20
+ end
21
+ end
22
+
23
+ opts.on_tail("-h", "--help", "Show this help") do
24
+ puts opts
25
+ exit
26
+ end
27
+ end
28
+
29
+ option_parser.parse!(ARGV)
30
+
31
+ if(recording.nil?)
32
+ puts "Session recording required!"
33
+ exit 1
34
+ end
35
+
36
+ interpreter = Command::PlaybackInterpreter.new(recording, pause_for)
37
+ interpreter.behavior(:debug_commands => true)
38
+
39
+ puts "Beginning playback:"
40
+
41
+ interpreter.go
42
+ puts "Done."
@@ -0,0 +1,54 @@
1
+ #!/bin/env ruby
2
+ # vim: filetype=ruby
3
+
4
+
5
+ require 'optparse'
6
+ require 'command-set/interpreter/recording'
7
+
8
+ file = nil
9
+ mod = nil
10
+ recording = nil
11
+
12
+ option_parser = OptionParser.new do |opts|
13
+ opts.on("-f", "--file SET_FILE", "The file that defines the command set") do |path|
14
+ file = path
15
+ end
16
+
17
+ opts.on("-o", "--output RECORDING", "The path to dump the session recording to") do |path|
18
+ recording = path
19
+ end
20
+
21
+ opts.on("-m", "--module SET_MODULE", "The name of the module that declares define_commands") do |modname|
22
+ mod = modname
23
+ end
24
+
25
+ opts.on_tail("-h", "--help", "Show this help") do
26
+ puts opts
27
+ exit
28
+ end
29
+ end
30
+
31
+ option_parser.parse!(ARGV)
32
+
33
+ if(file.nil?)
34
+ puts "Command set file required!"
35
+ exit 1
36
+ end
37
+
38
+ if(mod.nil?)
39
+ puts "Command set module name required!"
40
+ exit 1
41
+ end
42
+
43
+ if(recording.nil?)
44
+ recording = "script.out"
45
+ end
46
+
47
+
48
+ interpreter = Command::RecordingInterpreter.new(file, mod)
49
+ interpreter.behavior(:debug_commands => true)
50
+ interpreter.go
51
+
52
+ File::open(recording, "w") do |record|
53
+ interpreter.dump_to(record)
54
+ end
@@ -35,6 +35,14 @@ Command::CommandSet with subject defaults
35
35
  - should not raise an error when included sets collide
36
36
  - should raise an error when included sets collide
37
37
 
38
+ Command::CommandSet with mode commands
39
+ - should not allow access outside of the mode
40
+ - should allow access in the mode
41
+ - should include mode_commands from included sets
42
+
43
+ Command::CommandSet with aliased commands
44
+ - should have identical commands with different names
45
+
38
46
  Command::CommandSet with contextualized inclusions
39
47
  - should properly assign context to commands
40
48
  - should keep items neatly in contexts
@@ -123,6 +131,7 @@ Command::TextInterpreter
123
131
  Command::TextInterpreter has a readline completion routine that
124
132
  - should complete prefixes of space-embedding arguments
125
133
  - should return tricky error message instead of raising an exception
134
+ - should complete 'quit'
126
135
 
127
136
  Command::TextInterpreter all set up
128
137
  - should complete words in the commmand-line
@@ -214,10 +223,21 @@ A chain of commands
214
223
  - should chain with a path to the command and hashed args
215
224
  - should chain with a class and an arg hash
216
225
 
226
+ Command::Command chaining from a deep subcommand
227
+ - should chain from immediate parent by default
228
+ - should access immediate parent of parent
229
+ - should access relative subcommands
230
+ - should access the absolute root command set
231
+
217
232
  Command::Command with a subject argument basis
218
233
  - should use the subject to accept input
219
234
  - should use the subject to reject input
220
235
 
236
+ Command::Command that fans out to threads
237
+ - should fan out to one thread
238
+ - should fan out to a few threads
239
+ - should fan out to lots of threads
240
+
221
241
  Command::Results::Formatter
222
242
  - should process into the correct list
223
243
  - should allow explicit options to come through from command
@@ -225,6 +245,12 @@ Command::Results::Formatter
225
245
  - should mark up items as directed by Command#format_advice
226
246
  - should mark up items as directed by Command#format_advice
227
247
 
248
+ Command::Results::HashArrayFormatter
249
+ - should assemble a set of items
250
+ - should pack a list into a hash with an array
251
+ - should usefully structure mixed lists and items
252
+ - should nest arrays for nested lists
253
+
228
254
  A command set with the Set command
229
255
  - should actually have the set command
230
256
  - should reply to empty arguments with first level keys
@@ -269,22 +295,23 @@ Command::OutputStandin dispatched via thread
269
295
  - should send #p to the collector
270
296
  - should go back to sending puts to IO if collector removed
271
297
 
272
- Command::Results::Collector
273
- - should spawn dispatchers
274
-
275
298
  Command::Results::Collector sending messages
276
299
  - should send simple #item calls to its presenter
277
300
  - should send begin_list and end list calls to presenter
278
301
  - should prefix items with list nesting
279
302
 
303
+ A pair of Command::Results::Collector
304
+ - should collect results when used in sequence
305
+ - should collect results when calls are scrambled
306
+ - should collect results when calls appear nested
307
+ - should collect results when one gets delayed
308
+
280
309
  Command::Results::Presenter
281
310
  - should collect items
282
311
  - should nest lists
283
- - should complain about lists not begun
284
- - should complain about mis-nested item
285
312
 
286
313
  Command::Results::Presenter driving a Formatter
287
- - should emit #saw_item before #closed_item
314
+ - should emit #saw_item and #closed_item
288
315
  - should emit #closed_item within known tree order
289
316
  - should emit #closed_item only once per item
290
317
 
@@ -303,6 +330,6 @@ Command::Results::StrategyFormatter with chatty strategy
303
330
  Command::Results::StrategyFormatter with progress strategy
304
331
  - should format two lists
305
332
 
306
- Finished in 0.677035 seconds
333
+ Finished in 1.365823 seconds
307
334
 
308
- 190 examples, 0 failures
335
+ 207 examples, 0 failures
@@ -343,7 +343,7 @@ module Command
343
343
  end
344
344
 
345
345
  def command_names
346
- return @command_list.keys.compact
346
+ return @command_list.keys.compact + @mode_commands.keys.compact
347
347
  end
348
348
  end
349
349
  end
@@ -0,0 +1,105 @@
1
+ require 'spec/matchers'
2
+ require 'spec/example'
3
+ require 'spec/extensions'
4
+ require 'spec/runner'
5
+
6
+ describe "A well written command", :shared => true do
7
+ it "should have useful documentation" do
8
+ @command.documentation(70).length.should be > @command.short_docs(70).length
9
+ end
10
+
11
+ it "should have a classy response to undo" do
12
+ unless @command.allocate.undoable?
13
+ pending "#{@command.name} handling undo"
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "A well written command set", :shared => true do
19
+ it "should include quit" do
20
+ @set.command_names.should include("quit")
21
+ end
22
+
23
+ it "should include help" do
24
+ @set.command_names.should include("help")
25
+ end
26
+ end
27
+
28
+ module Command
29
+ require 'rake'
30
+ require 'spec/rake/spectask'
31
+ class CritiqueLoader < ::Spec::Runner::ExampleGroupRunner
32
+ def initialize(opts, command_set)
33
+ super(opts)
34
+ @command_set_module = command_set
35
+ end
36
+
37
+ def load_files(files)
38
+ set = CommandSet::new
39
+ set.require_commands(@command_set_module)
40
+ Critic.criticize(set)
41
+ end
42
+ end
43
+
44
+ module Critic
45
+ def self.criticize(set, path = ["Main"])
46
+ Kernel::describe "Command set: #{path.join(" ")}" do
47
+ it_should_behave_like "A well written command set"
48
+ before do
49
+ @set = set
50
+ end
51
+ end
52
+
53
+ set.command_list.each_pair do |name, cmd|
54
+ if CommandSet === cmd
55
+ Command::Spec::Critic.criticize(cmd, path + [name])
56
+ else
57
+ Kernel::describe "Command: #{path.join(" ")} #{name}" do
58
+ it_should_behave_like "A well written command"
59
+ before do
60
+ @command = cmd
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ class Task < ::Rake::TaskLib
68
+ def initialize(name = :critique)
69
+ @name = name
70
+ @spec_opts = []
71
+ @ruby_opts = []
72
+
73
+ @command_set_file = nil
74
+ @command_set_module = nil
75
+ yield self if block_given?
76
+ define
77
+ end
78
+
79
+ attr_accessor :spec_opts, :command_set_file, :command_set_module, :ruby_opts
80
+
81
+ def define
82
+ spec_opts = @spec_opts
83
+ unless spec_opts.include?("-f") or spec_opts.include?("--format")
84
+ spec_opts += %w{--format specdoc}
85
+ end
86
+
87
+ spec_opts = @spec_opts + ["-r", @command_set_file,
88
+ "-r", __FILE__,
89
+ "-U", "Command::CritiqueLoader:" + @command_set_module,
90
+ "--color"] #Because color is nice...
91
+
92
+ csf = @command_set_file
93
+
94
+ desc "Critique the command set defined in #@command_set_module"
95
+ Spec::Rake::SpecTask.new(@name) do |t|
96
+ t.spec_opts = spec_opts
97
+ t.ruby_opts = self.ruby_opts
98
+ t.spec_files = [csf]
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+
@@ -93,7 +93,7 @@ module Command
93
93
  #If your interpreter needs extra fields in the subject, alter
94
94
  #subject_requirements to return an array of those fields.
95
95
  def subject_requirements
96
- return [:undo_stack, :interpreter, :interpreter_behavior,
96
+ return [:undo_stack, :command_set, :interpreter, :interpreter_behavior,
97
97
  :chain_of_command, :pause_decks, :mode_stack]
98
98
  end
99
99
 
@@ -108,6 +108,7 @@ module Command
108
108
  @command_set.add_defaults(subject)
109
109
  subject.required_fields(subject_requirements())
110
110
  subject.undo_stack = @undo_stack
111
+ subject.command_set = @command_set
111
112
  subject.interpreter = self
112
113
  subject.interpreter_behavior = @behavior
113
114
  subject.chain_of_command = @commands_pending
@@ -138,8 +139,10 @@ module Command
138
139
  @commands_pending << cook_input(raw_input)
139
140
 
140
141
  presenter = Results::Presenter.new
141
- formatter = get_formatter
142
- presenter.register_formatter(formatter)
142
+ formatters = get_formatters
143
+ formatters.each do |formatter|
144
+ presenter.register_formatter(formatter)
145
+ end
143
146
  collector = presenter.create_collector
144
147
 
145
148
  begin
@@ -157,7 +160,7 @@ module Command
157
160
  end
158
161
  end
159
162
  begin
160
- execute(cmd, formatter, collector)
163
+ execute(cmd, formatters, collector)
161
164
  rescue CommandException => ce
162
165
  ce.command = cmd
163
166
  raise
@@ -183,13 +186,19 @@ module Command
183
186
  #Gets the final Results::Formatter object to output to. Override this
184
187
  #if you won't be reporting output to the standard output.
185
188
  def get_formatter
186
- return Results::Formatter.new(::Command::raw_stdout)
189
+ return Results::TextFormatter.new(::Command::raw_stdout)
190
+ end
191
+
192
+ def get_formatters
193
+ return [get_formatter]
187
194
  end
188
195
 
189
196
  protected
190
197
 
191
- def execute(command, formatter, collector)
192
- command.advise_formatter(formatter)
198
+ def execute(command, formatters, collector)
199
+ formatters.each do |formatter|
200
+ command.advise_formatter(formatter)
201
+ end
193
202
  command.go(collector)
194
203
  return nil
195
204
  end
@@ -0,0 +1,128 @@
1
+ require 'command-set/interpreter/text'
2
+ require 'command-set/formatter/hash-array'
3
+ require 'yaml'
4
+ require 'enumerator'
5
+
6
+ module Command
7
+ class RecordingInterpreter < TextInterpreter
8
+ class Event
9
+ def playback(prompt, interpreter, previous)
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+
14
+ class Complete < Event
15
+ def initialize(buffer, prefix, result)
16
+ @buffer, @prefix, @result = buffer, prefix, result
17
+ end
18
+
19
+ attr_reader :buffer, :prefix, :result
20
+
21
+ def eql?(other)
22
+ return (self.buffer.eql?(other.buffer) and self.prefix.eql?(other.prefix))
23
+ end
24
+
25
+ def playback(prompt, interpreter, previous)
26
+ complete_list = interpreter.readline_complete(@buffer, @prefix)
27
+ if complete_list.nil?
28
+ puts prompt + @buffer + "<TAB>"
29
+ puts
30
+ elsif complete_list.length > 1
31
+ if previous.eql?(self)
32
+ puts "<TAB>"
33
+ complete_list.map{|item| item.ljust(15)}.each_slice(5) do|cons|
34
+ puts cons.join
35
+ end
36
+ else
37
+ print "\n" + prompt + @buffer + "<TAB>"
38
+ end
39
+ else
40
+ puts "\n" + prompt + @buffer + "<TAB>"
41
+ complete_list.map{|item| item.ljust(15)}.each_slice(5) do|cons|
42
+ puts cons.join
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ class Execute < Event
49
+ def initialize(line)
50
+ @line = line
51
+ @result = nil
52
+ end
53
+
54
+ def playback(prompt, interpreter, previous)
55
+ puts "\n" + prompt + @line
56
+ interpreter.process_input(@line)
57
+ end
58
+
59
+ attr_writer :result
60
+ end
61
+
62
+ def initialize(file, mod)
63
+ @events = []
64
+ @current_hash_array = nil
65
+ @set_path = File::expand_path(file)
66
+ @module_name = mod
67
+ super()
68
+ self.command_set = CommandSet::define_commands do
69
+ require_commands(mod, file)
70
+ end
71
+ end
72
+
73
+ def readline_complete(buffer, prefix)
74
+ result = super
75
+ @events << Complete.new(buffer, prefix, result)
76
+ return result
77
+ end
78
+
79
+ def process_line(line)
80
+ event = Execute.new(line)
81
+ @events << event
82
+ super
83
+ event.result = @current_hasharray.structure
84
+ end
85
+
86
+ def get_formatters
87
+ @current_hasharray = Results::HashArrayFormatter.new
88
+ return [get_formatter, @current_hasharray]
89
+ end
90
+
91
+ def dump_to(io)
92
+ setup = {
93
+ 'events' => @events,
94
+ 'set_path' => @set_path,
95
+ 'module_name' => @module_name
96
+ }
97
+ io.write(YAML::dump(setup))
98
+ end
99
+ end
100
+
101
+ class PlaybackInterpreter < TextInterpreter
102
+ def initialize(recording, pause_for)
103
+ setup = nil
104
+ File::open(recording) do |record|
105
+ setup = YAML::load(record)
106
+ end
107
+
108
+ @events = setup['events']
109
+ module_name = setup['module_name']
110
+ command_set_path = setup['set_path']
111
+
112
+ super()
113
+ self.command_set = CommandSet::define_commands do
114
+ require_commands(module_name, command_set_path)
115
+ end
116
+
117
+ @pause_for = pause_for
118
+ end
119
+
120
+ def go
121
+ ([nil] + @events).each_cons(2) do |last, event|
122
+ sleep(@pause_for) unless @pause_for.nil?
123
+ event.playback(get_prompt, self, last)
124
+ $stdout.flush
125
+ end
126
+ end
127
+ end
128
+ end
@@ -1,7 +1,7 @@
1
1
  require 'command-set'
2
2
  require 'command-set/interpreter/base'
3
- require 'command-set/formatter/base'
4
- require 'command-set/readline'
3
+ require 'command-set/formatter/strategy'
4
+ require 'compatible-readline/readline'
5
5
 
6
6
  module Command
7
7
  class TextInterpreter < BaseInterpreter
@@ -146,8 +146,8 @@ module Command
146
146
  return line_array
147
147
  end
148
148
 
149
- def get_formatter
150
- return Results::StrategyFormatter.new(::Command::raw_stdout)
149
+ def get_formatters
150
+ return [Results::StrategyFormatter.new(::Command::raw_stdout)]
151
151
  end
152
152
 
153
153
  def get_prompt
metadata CHANGED
@@ -3,15 +3,15 @@ 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.2
7
- date: 2008-02-19 00:00:00 -08:00
8
- summary: Framework for interactive programs focused around a DSL for commands
6
+ version: 0.10.4
7
+ date: 2008-03-10 00:00:00 -07:00
8
+ summary: User interface framework for with a focus on a DSL for discrete commands
9
9
  require_paths:
10
10
  - lib
11
11
  email: nyarly@gmail.com
12
12
  homepage:
13
13
  rubyforge_project:
14
- description: CommandSet is a interactive program framework. Its focus is a DSL for defining commands, much like Rake or RSpec. The actual interpreter is left as an open question, although a default readline based terminal interpreter is included, it could very well be adapted to interact with CGI or a GUI.
14
+ description: "CommandSet is a user interface framework. Its focus is a DSL for defining commands, much like Rake or RSpec. A default readline based terminal interpreter (complete with context sensitive tab completion, and the amenities of readline: history editing, etc) is included. It could very well be adapted to interact with CGI or a GUI - both are planned. CommandSet has a lot of very nice features. First is the domain-specific language for defining commands and sets of commands. Those sets can further be neatly composed into larger interfaces, so that useful or standard commands can be resued. Optional application modes, much like Cisco's IOS, with a little bit more flexibility. Arguments have their own sub-language, that allows them to provide interface hints (like tab completion) as well as input validation. On the output side of things, CommandSet has a very flexible output capturing mechanism, which generates a tree of data as it's generated, even capturing writes to multiple places at once (even from multiple threads) and keeping everything straight. Methods that normally write to stdout are interposed and fed into the tree, so you can hack in existing scripts with minimal adjustment. The final output can be presented to the user in a number of formats, including contextual coloring and indentation, or even progress hashes. XML is also provided, although it needs some work. Templates are on the way. While you're developing your application, you might find the record and playback utilities useful. cmdset-record will start up with your defaults for your command set, and spit out an interaction script. Then you can replay the script against the live set with cmdset-playback. Great for ad hoc testing, usability surveys and general demos."
15
15
  autorequire:
16
16
  default_executable:
17
17
  bindir: bin
@@ -35,6 +35,7 @@ files:
35
35
  - lib/command-set/arguments.rb
36
36
  - lib/command-set/og.rb
37
37
  - lib/command-set/interpreter
38
+ - lib/command-set/interpreter/recording.rb
38
39
  - lib/command-set/interpreter/text.rb
39
40
  - lib/command-set/interpreter/base.rb
40
41
  - lib/command-set/interpreter/quick.rb
@@ -43,6 +44,7 @@ files:
43
44
  - lib/command-set/result-list.rb
44
45
  - lib/command-set/structural.rb
45
46
  - lib/command-set/dsl.rb
47
+ - lib/command-set/critic.rb
46
48
  - lib/command-set/formatter
47
49
  - lib/command-set/formatter/strategy.rb
48
50
  - lib/command-set/formatter/hash-array.rb
@@ -51,7 +53,8 @@ files:
51
53
  - lib/command-set/command.rb
52
54
  - lib/command-set/standard-commands.rb
53
55
  - lib/command-set/results.rb
54
- - lib/command-set/readline.rb
56
+ - bin/cmdset-playback
57
+ - bin/cmdset-record
55
58
  - doc/README
56
59
  - doc/GUIDED_TOUR
57
60
  - doc/Specifications
@@ -63,17 +66,26 @@ rdoc_options:
63
66
  - --main
64
67
  - Command
65
68
  - --title
66
- - command-set-0.10.2 RDoc
69
+ - command-set-0.10.4 RDoc
67
70
  extra_rdoc_files:
68
71
  - doc/README
69
72
  - doc/GUIDED_TOUR
70
73
  - doc/Specifications
71
74
  - doc/argumentDSL
72
- executables: []
73
-
75
+ executables:
76
+ - cmdset-record
77
+ - cmdset-playback
74
78
  extensions: []
75
79
 
76
80
  requirements: []
77
81
 
78
- dependencies: []
79
-
82
+ dependencies:
83
+ - !ruby/object:Gem::Dependency
84
+ name: orichalcum
85
+ version_requirement:
86
+ version_requirements: !ruby/object:Gem::Version::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 0.5.0
91
+ version:
@@ -1,60 +0,0 @@
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