command-set 0.8.0

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.
@@ -0,0 +1,243 @@
1
+ module Command
2
+ module StandardCommand
3
+ class Resume < Command
4
+ @name = "resume"
5
+ optional.argument :deck, "Resume from name"
6
+
7
+ doesnt_undo
8
+
9
+ subject_methods :chain_of_command, :pause_decks
10
+
11
+ action do
12
+ subject.chain_of_command.insert(0, *(subject.pause_decks[deck]))
13
+ end
14
+ defined
15
+ end
16
+
17
+ class Quit < Command
18
+ @name = "quit"
19
+ subject_methods :interpreter
20
+
21
+ doesnt_undo
22
+
23
+ action do
24
+ subject.interpreter.stop
25
+ end
26
+ defined
27
+ end
28
+ end
29
+
30
+ #This is a collection of useful commands that are often useful to add to
31
+ #DSL::CommandSetDefinition#include_commands
32
+ #
33
+ #For instance
34
+ #
35
+ # set = CommandSet::define_commands do
36
+ # include_commands Command::StandardCommands::Quit
37
+ # include_commands Command::StandardCommands::Undo
38
+ # include_commands Command::StandardCommands::Help
39
+ # end
40
+ #
41
+ #Some notes:
42
+ #
43
+ #0. Resume is usually handy for use in +chain+ calls, not as something to
44
+ # present for the user to access directly. It resumes paused commands.
45
+ #0. Mode is useful in subcommands. Including it lets the user use the
46
+ # subcommand by itself to switch into a mode where that subcommand is
47
+ # the root command set. Then "exit" lets them leave and return to the
48
+ # regular set. Consider IOS's config mode, for instance.
49
+ #0. Set is useful for allowing the user to update program settings on the
50
+ # fly. It assumes a set of nested hashes that represent the settings.
51
+ # It functions as you'd (hopefully) expect. +set+ +optionname+ returns
52
+ # the current value, +set+ +optionname+ +value+ sets the value of
53
+ # +optionname+
54
+ module StandardCommands
55
+ help = CommandSet.define_commands do
56
+ command("help") do
57
+ optional.multiword_argument(:terms) do |terms,subject|
58
+ prefix = terms.pop
59
+ subject.command_set.completion_list(terms, prefix, subject)
60
+ end
61
+
62
+ subject_methods :interpreter_behavior, :command_set
63
+
64
+ action do
65
+ width = (subject.interpreter_behavior)[:screen_width]
66
+ puts
67
+ if(terms.nil? || terms.empty?)
68
+ subject.command_set.documentation(width).each do |docline|
69
+ puts docline
70
+ end
71
+ else
72
+ command,remains = subject.command_set.find_command(*terms)
73
+ docs = command.documentation(width)
74
+ docs.each do |docline|
75
+ puts docline
76
+ end
77
+ end
78
+ puts
79
+ end
80
+
81
+ doesnt_undo
82
+
83
+ document <<-EOH
84
+ Returns a hopefully helpful description of the command indicated
85
+ EOH
86
+ end
87
+ end
88
+ Help = help
89
+
90
+ #Resume is very rarely used in user-facing command sets, but can be handy to chain in
91
+ #as a synthetic command
92
+ resume = CommandSet.define_commands do
93
+ command StandardCommand::Resume
94
+ end
95
+ Resume = resume
96
+
97
+ quit = CommandSet.define_commands do
98
+ command StandardCommand::Quit
99
+ end
100
+ Quit = quit
101
+
102
+ undo_redo = CommandSet.define_commands do
103
+ command("undo") do
104
+ doesnt_undo
105
+ action do
106
+ command=subject.undo_stack.get_undo
107
+ command.undo
108
+ end
109
+
110
+ subject_methods :undo_stack
111
+
112
+ document <<-EOH
113
+ Undoes the most recently used command, if possible.
114
+ EOH
115
+ end
116
+
117
+ command("redo") do
118
+ doesnt_undo
119
+ action do
120
+ command=subject.undo_stack.get_redo
121
+ command.execute
122
+ end
123
+
124
+ document <<-EOH
125
+ Redoes the most recently used command, if possible.
126
+ EOH
127
+ end
128
+ end
129
+ Undo = undo_redo
130
+
131
+ mode_exit = CommandSet.define_commands do
132
+ root_command do
133
+ argument(FiddlyArgument.new(:mode) do |argument|
134
+ argument.completion do |t,s|
135
+ []
136
+ end
137
+
138
+ argument.validation do |term,s|
139
+ CommandSet === term
140
+ end
141
+ end)
142
+ subject_methods :interpreter
143
+ doesnt_undo
144
+
145
+ action do
146
+ subject.interpreter.push_mode(mode)
147
+ end
148
+ end
149
+
150
+ command(:exit) do
151
+ subject_methods :interpreter
152
+ doesnt_undo
153
+
154
+ action do
155
+ subject.interpreter.pop_mode
156
+ end
157
+ end
158
+ end
159
+ Mode = mode_exit
160
+
161
+
162
+ set = CommandSet.define_commands do
163
+ command(:set) do
164
+ optional.multiword_argument(:address) do |terms, subject|
165
+ last_word = terms.pop
166
+ hash = value_set(terms, subject)
167
+ if(Hash === hash)
168
+ return hash.keys.grep(%r{^#{last_word}.*})
169
+ else
170
+ return []
171
+ end
172
+ end
173
+
174
+ optional.argument(:value, "VALUE")
175
+
176
+ def value_set(terms, subject)
177
+ knobs = subject.knobs
178
+ return [] if knobs.nil?
179
+
180
+ terms.each do |term|
181
+ unless knobs.respond_to? :[]
182
+ raise CommandError, "can't find settings under #{term}"
183
+ end
184
+ knobs = knobs[term]
185
+ end
186
+
187
+ return knobs
188
+ end
189
+
190
+ define_method :value_set do |terms, subject|
191
+ self.class.value_set(terms, subject)
192
+ end
193
+
194
+ subject_methods :knobs
195
+
196
+ #TODO: This is a big mess in here
197
+ #AFAICT, there should be an address, followed by an optional name
198
+ #followed by an optional value...
199
+
200
+ action do
201
+ if address.nil?
202
+ dont_undo
203
+ puts subject.knobs.keys.sort.join("\n")
204
+ return
205
+ else
206
+ @knob_name = address.pop
207
+ @knobs = value_set(address, subject)
208
+ end
209
+
210
+ if Hash === @knobs[@knob_name]
211
+ dont_undo
212
+ puts @knobs[@knob_name].keys.sort.join("\n")
213
+ return
214
+ elsif @knobs.has_key? @knob_name
215
+ if value.nil?
216
+ dont_undo
217
+ puts "#@knob_name: #{@knobs[@knob_name].inspect}"
218
+ else
219
+ @original_value = @knobs[@knob_name]
220
+ @knobs[@knob_name]=value
221
+ #I'm only keeping this because I trust you meant something
222
+ #@knobs[@knob_name]=convert_value(@original_value, value)
223
+ end
224
+ end
225
+ end
226
+
227
+ undo do
228
+ @knobs[@knob_name] = @original_value
229
+ end
230
+
231
+ document <<-EOH
232
+ Without arugments, lists the available variables.
233
+
234
+ With one argument, prints the value of the variable <name>.
235
+
236
+ Sets <name> to <value>. Most settings should be obvious. But
237
+ some important ones probably won't be.
238
+ EOH
239
+ end
240
+ end
241
+ Set = set
242
+ end
243
+ end
@@ -0,0 +1,91 @@
1
+ module Command
2
+ #This object represents the subject of a command set. To expose parts of
3
+ #the application to the command set, commands should call subject_methods
4
+ #with the names of methods it expects to use.
5
+ #
6
+ #Furthermore, Subject maintains the state of the command set, which helps
7
+ #put all of the logic in the Command objects by letting them maintain
8
+ #state in one place
9
+ #
10
+ #Subjects are very picky about their fields. The motivation here is to
11
+ #fail fast. Commands can't access fields they don't declare with
12
+ #DSL::CommandDefinition#subject_methods, and the interpreter will fail
13
+ #fast unless the required fields have been assigned.
14
+ #
15
+ #Finally, Commands can't set fields - but the fields are the same for each
16
+ #command, so they can change the fields. For drastic changes, try
17
+ #Array#replace or Hash#replace
18
+ class Subject
19
+ class UndefinedField; end
20
+ Undefined = UndefinedField.new
21
+
22
+ def required_fields(*field_names)
23
+ field_names.map! {|name| name.to_s}
24
+ field_names -= instance_variables.map {|var| var.sub(/^@/, "")}
25
+ field_names.each do |field|
26
+ add_accessor(field)
27
+ end
28
+ end
29
+
30
+ def verify
31
+ missing = instance_variables.find_all do |var|
32
+ UndefinedField === instance_variable_get(var)
33
+ end
34
+ unless missing.empty?
35
+ raise RuntimeError, "Undefined fields: #{missing.join(", ")}"
36
+ end
37
+ return nil
38
+ end
39
+
40
+ def get_image(with_fields)
41
+ image = SubjectImage.new
42
+ missing_fields = []
43
+ with_fields.each do |field|
44
+ begin
45
+ field = field.to_s
46
+ value = instance_variable_get("@#{field}")
47
+ image.add_field(field, value)
48
+ rescue NameError
49
+ missing_fields << field
50
+ end
51
+ end
52
+ unless missing_fields.empty?
53
+ raise CommandException, "Subject is missing fields: #{missing_fields.join(", ")}"
54
+ end
55
+ image
56
+ end
57
+
58
+ protected
59
+ def add_accessor(name)
60
+ self.instance_variable_set("@#{name}", Undefined)
61
+ meta = class << self; self; end
62
+
63
+ meta.instance_eval do
64
+
65
+ define_method("#{name}=") do |value|
66
+ return instance_variable_set("@#{name}", value)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ #This is the object type that's actually passed to a command. It's
73
+ #populated using the subject_methods that the command declared, using values
74
+ #from the application Subject.
75
+ class SubjectImage
76
+
77
+ #You shouldn't really need to ever call this - it's used by the
78
+ #interpreter to set up the image before it's passed to the command
79
+ def add_field(name, value)
80
+ meta = class << self; self; end
81
+
82
+ meta.instance_eval do
83
+ define_method("#{name}") do
84
+ return value
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+
91
+ end
@@ -0,0 +1,171 @@
1
+ require 'readline'
2
+ require 'command-set'
3
+ require 'dl/import'
4
+ require 'logger'
5
+ require 'command-set/interpreter'
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
+ extend DL::Importable
12
+ dlload "/lib/libreadline.so"
13
+ RLLB = symbol("rl_line_buffer")
14
+ def self.line_buffer
15
+ p = RLLB.ptr
16
+ if p.nil?
17
+ return p
18
+ else
19
+ return p.to_s
20
+ end
21
+ end
22
+ end
23
+
24
+ module Command
25
+ class TextInterpreter < BaseInterpreter
26
+ def initialize
27
+ super
28
+ @complete_line = false
29
+ @behavior.merge!(
30
+ :prompt => [/(?:: )?$/, "> "]
31
+ )
32
+ end
33
+
34
+ attr_accessor :complete_line
35
+
36
+ def go
37
+ if @command_set.nil? or @subject.nil?
38
+ raise "command_set or subject unset!"
39
+ end
40
+
41
+ Readline.completion_proc = lambda do |prefix|
42
+ parsed_input = Readline.line_buffer.split(" ")
43
+ unless prefix.empty?
44
+ parsed_input.pop
45
+ end
46
+
47
+ list = current_command_set.completion_list(parsed_input,
48
+ prefix, build_subject)
49
+ list
50
+ end
51
+
52
+ @stop = false
53
+
54
+ begin
55
+ line = Readline.readline(get_prompt, true)
56
+ next if line.empty?
57
+ process_line(line)
58
+ rescue Interrupt
59
+ puts "Interrupt: please use \"quit\""
60
+ rescue CommandException => ce
61
+ @out_io.puts "Error: " + ce.message
62
+ @out_io.puts ce.backtrace if @behavior[:debug_commands]
63
+ logger.warn ce.message
64
+ ce.backtrace.each do |line|
65
+ logger.debug line
66
+ end
67
+
68
+ rescue Exception => e
69
+ @out_io.puts "Exception: " + e.message
70
+ @out_io.puts e.backtrace.join("\n") if @behavior[:debug_commands]
71
+ logger.warn e.message
72
+ e.backtrace.each do |line|
73
+ logger.debug line
74
+ end
75
+ puts "Waiting for return"
76
+ $stdin.gets
77
+ @stop = true
78
+ end until @stop
79
+ end
80
+
81
+ def cook_input(line)
82
+ line = split_line(line)
83
+ if(@complete_line)
84
+ new_line = []
85
+ line.each do |word|
86
+ word = complete(word, new_line)
87
+ new_line << word
88
+ end
89
+ line = new_line
90
+ end
91
+ return CommandSetup.new(line)
92
+ end
93
+
94
+ def assign_terms(cmd)
95
+ terms = super
96
+ unless terms.empty?
97
+ puts "(Discarding extraneous: #{terms.join(" ")}"
98
+ end
99
+ end
100
+
101
+ alias single_command process_input
102
+ alias process_line process_input
103
+
104
+ def complete(word, line)
105
+ list = current_command_set.completion_list(line, word, build_subject)
106
+
107
+ if list.length == 0
108
+ raise CommandException, "Unrecognized: #{word}"
109
+ end
110
+
111
+ return word if list[-1].empty?
112
+
113
+ if list.length == 1
114
+ return list[0]
115
+ else
116
+ raise CommandException, "Ambiguous: #{word}"
117
+ end
118
+ end
119
+
120
+ def split_line(line)
121
+ line_array = []
122
+ scanner = StringScanner.new(line)
123
+
124
+ until scanner.eos?
125
+ scanner.scan(/\s*/)
126
+ quote = scanner.scan(/['"]/)
127
+ unless quote.nil?
128
+ line_array << ""
129
+ until scanner.eos?
130
+ line_array.last << scanner.scan(/[^#{quote}\\]*/)
131
+ stopped_by = scanner.scan(/[#{quote}\\]/)
132
+ break unless stopped_by == '\\'
133
+ line_array.last << scanner.getch
134
+ end
135
+ else
136
+ line_array << ""
137
+ until scanner.eos?
138
+ line_array.last << scanner.scan(/[^\s\\]*/)
139
+ stopped_by = scanner.scan(/[\s\\]/)
140
+ break unless stopped_by == '\\'
141
+ line_array.last << scanner.getch
142
+ end
143
+ end
144
+ end
145
+
146
+ return line_array
147
+ end
148
+
149
+ def get_formatter
150
+ return Results::TextFormatter.new(::Command::raw_stdout)
151
+ end
152
+
153
+ def get_prompt
154
+ prompt = ""
155
+
156
+ prompt.sub!(*(@command_set.prompt))
157
+ @sub_modes.each do |mode|
158
+ prompt.sub!(*(mode.prompt))
159
+ end
160
+ prompt.sub!(*(@behavior[:prompt]))
161
+ end
162
+
163
+ def stop
164
+ @stop = true
165
+ end
166
+
167
+ def prompt_user(message)
168
+ Readline.readline(message)
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,3 @@
1
+ require 'command-set/command'
2
+ require 'command-set/command-set'
3
+ require 'command-set/text-interpreter'
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: command-set
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.8.0
7
+ date: 2007-11-27 00:00:00 -08:00
8
+ summary: Framework for interactive programs focused around a DSL for commands
9
+ require_paths:
10
+ - lib
11
+ email: judson@redfivellc.com
12
+ homepage:
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.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message: Another tidy package brought to you by Judson
29
+ authors:
30
+ - Judson Lester
31
+ files:
32
+ - lib/command-set.rb
33
+ - lib/command-set
34
+ - lib/command-set/command-set.rb
35
+ - lib/command-set/arguments.rb
36
+ - lib/command-set/og.rb
37
+ - lib/command-set/text-interpreter.rb
38
+ - lib/command-set/subject.rb
39
+ - lib/command-set/result-list.rb
40
+ - lib/command-set/dsl.rb
41
+ - lib/command-set/command.rb
42
+ - lib/command-set/interpreter.rb
43
+ - lib/command-set/standard-commands.rb
44
+ - lib/command-set/results.rb
45
+ - lib/command-set/quick-interpreter.rb
46
+ - lib/command-set/batch-interpreter.rb
47
+ - doc/README
48
+ - doc/Specifications
49
+ - doc/argumentDSL
50
+ test_files: []
51
+
52
+ rdoc_options:
53
+ - --diagram
54
+ - --inline-source
55
+ - --main
56
+ - Command
57
+ - --title
58
+ - command-set-0.8.0 RDoc
59
+ extra_rdoc_files:
60
+ - doc/README
61
+ - doc/Specifications
62
+ - doc/argumentDSL
63
+ executables: []
64
+
65
+ extensions: []
66
+
67
+ requirements: []
68
+
69
+ dependencies: []
70
+