command-set 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/README +2 -0
- data/doc/Specifications +219 -0
- data/doc/argumentDSL +36 -0
- data/lib/command-set/arguments.rb +547 -0
- data/lib/command-set/batch-interpreter.rb +0 -0
- data/lib/command-set/command-set.rb +282 -0
- data/lib/command-set/command.rb +456 -0
- data/lib/command-set/dsl.rb +526 -0
- data/lib/command-set/interpreter.rb +196 -0
- data/lib/command-set/og.rb +615 -0
- data/lib/command-set/quick-interpreter.rb +91 -0
- data/lib/command-set/result-list.rb +300 -0
- data/lib/command-set/results.rb +754 -0
- data/lib/command-set/standard-commands.rb +243 -0
- data/lib/command-set/subject.rb +91 -0
- data/lib/command-set/text-interpreter.rb +171 -0
- data/lib/command-set.rb +3 -0
- metadata +70 -0
@@ -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
|
+
|