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