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,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
+