command-set 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+