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,282 @@
1
+ require 'strscan'
2
+ require 'thread'
3
+ require 'command-set/arguments'
4
+ require 'command-set/command'
5
+ require 'command-set/subject'
6
+ require 'command-set/results'
7
+ require 'command-set/dsl'
8
+
9
+
10
+ #:stopdoc:
11
+ class String
12
+ def wrap(width=80)
13
+ scanner = StringScanner.new(self)
14
+ result=[]
15
+
16
+ scanner.skip(/\s*/)
17
+
18
+ current_line = scanner.scan(/\S+/)
19
+
20
+ until scanner.eos?
21
+ scanner.skip(/\s*/)
22
+ word = scanner.scan(/\S+/)
23
+ next if word.nil?
24
+ if word.length > (width - current_line.length)
25
+ result << current_line
26
+ current_line = word
27
+ else
28
+ current_line << (" " + word)
29
+ end
30
+ end
31
+
32
+ unless /^\s*$/ =~ current_line
33
+ result << current_line
34
+ end
35
+
36
+ return result
37
+ end
38
+ end
39
+
40
+ Command::wrap_stdout
41
+ #Command::wrap_stderr
42
+
43
+ #:startdoc:
44
+
45
+ #Command::CommandSet is a tight little library that let's you clearly and
46
+ #easily describe a set of commands for an interactive application. The
47
+ #command set can then be handed to one of a number of interpreters that will
48
+ #facilitate interaction with the user.
49
+ #
50
+ #CommandSet has a number of pretty neat features:
51
+ #- A command line text interpreter, with tab completion etc. compliments of
52
+ # readline.
53
+ #
54
+ #- A handy little DSL for describing commands and what they do. There are
55
+ # other CLI engines that map to ruby methods, but frankly, I'm not sure
56
+ # that's the most useful mapping. The CommandSet DSL lets you specify the
57
+ # type of commands, control how they tab complete, mark some arguments as
58
+ # optional, etc.
59
+ #
60
+ #- Modularized commands. The StandardCommands class is a good example.
61
+ # Basically, any time you have a command that might be generally
62
+ # applicable, you can compose it into another set, and cherrypick specific
63
+ # commands out. For example:
64
+ #
65
+ # CommandSet.define_commands do
66
+ # command(:example) do |example|
67
+ # ...stuff...
68
+ # end
69
+ #
70
+ # include(other_set)
71
+ # include(cluttered_set, :useful)
72
+ #
73
+ # sub_command(:sub) do |sub|
74
+ # sub.include(yet_another_set)
75
+ # end
76
+ # end
77
+ #
78
+ #- Results processing. Basically, any +puts+, +p+ or +print+ call in the
79
+ # context of a command will (instead of outputing directly to +$stdout+)
80
+ # instead fire events in Formatter objects. The default behavior of which is
81
+ # ... to output directly to +STDOUT+. The catch here is that that behavior can
82
+ # be changed, and the events can include the beginnings and ends of nesting
83
+ # lists, so you have this whole tree of results from your command execution
84
+ # that can be manipulated on it's way to the user.
85
+ # As a for instance, you can spin off threads to do processing of parts of
86
+ # a command, and be confidant that you'll be able to make sense of the
87
+ # output for the user.
88
+ #
89
+ #- Extensible Command, Argument, and BaseInterpreter make power
90
+ # functionality easy to add. The modular design means that a CommandSet
91
+ # written for use with the TextInterpreter, can also be used to process the
92
+ # command line arguments to the program or passed to a batch interpreter.
93
+ # WebApp (a separate gem) uses this feature so that a web application
94
+ # automatically gets a command line version, for testing or administrator's
95
+ # convenience.
96
+ #
97
+ #:include: GUIDED_TOUR
98
+ module Command
99
+ class CommandError < ScriptError; end
100
+ class CommandException < RuntimeError
101
+ def initialize(msg=nil)
102
+ super
103
+ @raw_input = nil
104
+ @command = nil
105
+ end
106
+
107
+ def message
108
+ if @command.nil?
109
+ if @raw_input.nil?
110
+ super
111
+ else
112
+ return @raw_input.inspect() +": "+ super
113
+ end
114
+ else
115
+ return @command.class.path.join(" ") +": "+ super
116
+ end
117
+ end
118
+
119
+ attr_accessor :raw_input, :command
120
+ end
121
+ class OutOfArgumentsException < CommandException; end
122
+ class ArgumentInvalidException < CommandException
123
+ def initialize(pairs)
124
+ @pairs = pairs.dup
125
+ super("Bad arguments: #{pairs.inspect}")
126
+ end
127
+
128
+ attr_reader :pairs
129
+ end
130
+ class ArgumentUnrecognizedException < CommandException; end
131
+
132
+ #This class packs up a set of commands, for presentation to an
133
+ #interpreter. CommandSet objects are defined using methods from
134
+ #DSL::CommandSetDefinition
135
+ class CommandSet
136
+ def initialize(parent=nil, name="")
137
+ @parent = parent
138
+ @name = name
139
+ @command_list = Hash.new
140
+ @subject_template = nil
141
+ @documentation = ""
142
+ @prompt = nil
143
+ end
144
+
145
+ attr_accessor :documentation, :parent
146
+
147
+ class << self
148
+ #The preferred way to use a CommandSet is to call CommandSet::define_commands with
149
+ #a block, and then call #command, #include_commands
150
+ #and #sub_command on it.
151
+ def define_commands(&block)
152
+ set = self.new
153
+ set.define_commands(&block)
154
+ return set
155
+ end
156
+ end
157
+
158
+ include DSL::CommandSetDefinition
159
+
160
+ #:section: Workhorse methods - not usually used by client code
161
+ #
162
+
163
+ def paths_update(command, name)
164
+ command.add_path(self, [], name)
165
+
166
+ @paths.each do |set, path|
167
+ command.add_path(set, path, name)
168
+ end
169
+ end
170
+
171
+ def get_root
172
+ command = @command_list[nil]
173
+ end
174
+
175
+ def prompt
176
+ if @prompt.nil?
177
+ if @name.empty?
178
+ return [/$/, ""]
179
+ else
180
+ return [/$/, "#@name : "]
181
+ end
182
+ else
183
+ return @prompt
184
+ end
185
+ end
186
+
187
+ def set_prompt(match, replace)
188
+ @prompt = [match, replace]
189
+ end
190
+
191
+ def add_requirements(subject)
192
+ @command_list.each_value do |command|
193
+ command.add_requirements subject
194
+ end
195
+ return subject
196
+ end
197
+
198
+ #Given a set of terms, returns a Command or CommandSet, plus an extraneous (probably argument) terms
199
+ def find_command(*terms)
200
+ if terms.empty?
201
+ return self,[]
202
+ end
203
+
204
+ command_name = terms.shift
205
+
206
+ command = @command_list[command_name]
207
+
208
+ if Class === command && Command > command
209
+ if command.defined?
210
+ return command,terms
211
+ else
212
+ raise CommandError, "#{command_name}: unfinalized command: #{command.inspect}"
213
+ end
214
+ elsif CommandSet === command
215
+ return command.find_command(*terms)
216
+ elsif command.nil?
217
+ if @command_list[nil].nil?
218
+ raise CommandException, "Unknown command: #{command_name.inspect}"
219
+ else
220
+ return @command_list[nil], terms.unshift(command_name)
221
+ end
222
+ else
223
+ raise ScriptError, "Somehow a non-command made it's " +
224
+ "way into the command registry. -- (#{command.inspect})"
225
+ end
226
+ end
227
+
228
+ def completion_list(terms, prefix, subject)
229
+ if terms.length == 0
230
+ list = @command_list.keys.grep(%r{^#{prefix}.*})
231
+ if @command_list.has_key?(nil)
232
+ list += @command_list[nil].completion_list([], prefix, subject)
233
+ end
234
+ return list
235
+ end
236
+
237
+ begin
238
+ command,terms = find_command(*terms)
239
+ return command.completion_list(terms, prefix, subject)
240
+ rescue CommandException => ce
241
+ return []
242
+ end
243
+ end
244
+
245
+ def path
246
+ if @parent.nil?
247
+ return []
248
+ else
249
+ return @parent.path + [@name]
250
+ end
251
+ end
252
+
253
+ def documentation(width, parent="")
254
+ docs = ""
255
+ docs = @command_list.to_a.reject do |el|
256
+ el[0].nil?
257
+ end
258
+
259
+ parent = [parent, @name].join(" ")
260
+ docs = docs.sort_by{|el| el[0]}.inject([]) do |doclist,cmd|
261
+ doclist += cmd[1].short_docs(width, parent)
262
+ end
263
+
264
+ return docs
265
+ end
266
+
267
+ alias short_docs documentation
268
+
269
+ def instance(subject)
270
+ if @command_list.has_key?(nil)
271
+ return @command_list[nil].new(subject)
272
+ else
273
+ raise CommandException, "incomplete command"
274
+ end
275
+ end
276
+
277
+ def command_list
278
+ return @command_list.dup
279
+ end
280
+ end
281
+ end
282
+