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,196 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Command
|
4
|
+
#This is the base interpreter class. By itself it'll raise a bunch of
|
5
|
+
#NoMethodErrors.
|
6
|
+
#
|
7
|
+
#Interpreters manage the Subject object(s) and CommandSet and process
|
8
|
+
#input.
|
9
|
+
#
|
10
|
+
#Subclasses of BaseInterpreter (like TextInterpreter, for
|
11
|
+
#instance, must implement #cook_input(raw) that converts raw input (of
|
12
|
+
#whatever form is appropriate to the interpreter) into CommandSetup
|
13
|
+
#objects. Other methods can be overridden - especially consider
|
14
|
+
# #get_formatter, #prompt_user, and #assign_terms
|
15
|
+
class BaseInterpreter
|
16
|
+
attr_accessor :command_set, :out_io, :logger
|
17
|
+
attr_reader :subject
|
18
|
+
|
19
|
+
#:section: Client app methods
|
20
|
+
|
21
|
+
#Subject has an intentionally very tight interface (q.v.) the gist of
|
22
|
+
#which is that you can only assign to fields that commands require, and
|
23
|
+
#you can only access fields within a command that you declared that
|
24
|
+
#command required.
|
25
|
+
def subject_template
|
26
|
+
raise "CommandSet unset!" if @command_set.nil?
|
27
|
+
return prep_subject(get_subject)
|
28
|
+
end
|
29
|
+
|
30
|
+
#Before running an interpreter on input, you must set the subject.
|
31
|
+
#Get a subject object by calling subject_template, assign it's fields,
|
32
|
+
#and then pass it into subject=
|
33
|
+
def subject= (subject)
|
34
|
+
begin
|
35
|
+
subject.get_image(subject_requirements())
|
36
|
+
rescue CommandException
|
37
|
+
prep_subject(subject)
|
38
|
+
end
|
39
|
+
|
40
|
+
subject.verify
|
41
|
+
@subject = subject
|
42
|
+
end
|
43
|
+
|
44
|
+
#Any options that the interpreter might have can be set by passing a
|
45
|
+
#hash to behavior to be merged with the defaults
|
46
|
+
def behavior(hash)
|
47
|
+
@behavior.merge!(hash)
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@command_set=nil
|
52
|
+
@sub_modes = []
|
53
|
+
@behavior = {
|
54
|
+
:screen_width => 76,
|
55
|
+
:warn_no_undo => true
|
56
|
+
}
|
57
|
+
@out_io = $stdout
|
58
|
+
@stop = false
|
59
|
+
@subject = nil
|
60
|
+
@logger = Logger.new($stderr)
|
61
|
+
@logger.level=Logger::FATAL
|
62
|
+
@undo_stack = UndoStack.new
|
63
|
+
@commands_pending = []
|
64
|
+
@pause_decks = Hash.new {|h,k| h[k]=[]}
|
65
|
+
end
|
66
|
+
|
67
|
+
#:section: Command behavior related method
|
68
|
+
|
69
|
+
# Puts a CommandSet ahead of the current one for processing. Useful for command
|
70
|
+
# modes, like Cisco's IOS with configure modes, et al.
|
71
|
+
def push_mode(mode)
|
72
|
+
unless CommandSet === mode
|
73
|
+
raise RuntimeError, "Sub-modes must be CommandSets!"
|
74
|
+
end
|
75
|
+
|
76
|
+
@sub_modes.push(mode)
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# The compliment to #push_mode. Removes the most recent command set.
|
81
|
+
def pop_mode
|
82
|
+
@sub_modes.pop
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
|
86
|
+
#:section: Extension related methods
|
87
|
+
|
88
|
+
#If your interpreter needs extra fields in the subject, alter
|
89
|
+
#subject_requirements to return an array of those fields.
|
90
|
+
def subject_requirements
|
91
|
+
return [:undo_stack, :interpreter_behavior,
|
92
|
+
:chain_of_command, :pause_decks]
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_subject
|
96
|
+
return Subject.new
|
97
|
+
end
|
98
|
+
|
99
|
+
#This method sets up the fields in the subject required by the
|
100
|
+
#interpreter.
|
101
|
+
def prep_subject(subject)
|
102
|
+
@command_set.add_requirements(subject)
|
103
|
+
subject.required_fields(*subject_requirements())
|
104
|
+
subject.undo_stack = @undo_stack
|
105
|
+
subject.interpreter_behavior = @behavior
|
106
|
+
subject.chain_of_command = @commands_pending
|
107
|
+
subject.pause_decks = @pause_decks
|
108
|
+
return subject
|
109
|
+
end
|
110
|
+
|
111
|
+
#Gets the next command in the queue - related to DSL::Action#chain.
|
112
|
+
#You'll almost never want to override this method.
|
113
|
+
def next_command
|
114
|
+
setup = @commands_pending.shift
|
115
|
+
return setup.command_instance(current_command_set, build_subject)
|
116
|
+
end
|
117
|
+
|
118
|
+
#Present +message+ to the user, and get a response - usually yes or no.
|
119
|
+
#Non-interactive interpreters, or ones where that level of interaction
|
120
|
+
#is undesirable should not override this method, which returns "no".
|
121
|
+
def prompt_user(message)
|
122
|
+
"no"
|
123
|
+
end
|
124
|
+
|
125
|
+
#Process a single unit of input from the user. Relies on cook input to
|
126
|
+
#convert +raw_input+ into a CommandSetup
|
127
|
+
def process_input(raw_input)
|
128
|
+
@commands_pending << cook_input(raw_input)
|
129
|
+
|
130
|
+
presenter = Results::Presenter.new
|
131
|
+
formatter = get_formatter
|
132
|
+
presenter.register_formatter(formatter)
|
133
|
+
collector = presenter.create_collector
|
134
|
+
|
135
|
+
begin
|
136
|
+
raise "Ack! No OutputStandin in place!" unless $stdout.respond_to?(:add_dispatcher)
|
137
|
+
$stdout.add_dispatcher(collector)
|
138
|
+
until @commands_pending.empty?
|
139
|
+
cmd = next_command
|
140
|
+
if ( @behavior[:warn_no_undo] and not cmd.undoable? )
|
141
|
+
confirm = prompt_user("\"#{raw_input}\" cannot be undone. Continue? ")
|
142
|
+
if not ["yes", "y", "sure", "i suppose", "okay"].include? confirm.strip.downcase
|
143
|
+
return
|
144
|
+
end
|
145
|
+
end
|
146
|
+
begin
|
147
|
+
execute(cmd, formatter, collector)
|
148
|
+
rescue CommandException => ce
|
149
|
+
ce.command = cmd
|
150
|
+
raise
|
151
|
+
rescue ResumeFrom => rf
|
152
|
+
deck = rf.pause_deck
|
153
|
+
@pause_decks[deck].push rf.setup
|
154
|
+
raise unless ResumeFromOnlyThis === rf
|
155
|
+
end
|
156
|
+
end
|
157
|
+
rescue ResumeFrom => rf
|
158
|
+
@pause_decks[deck] += @commands_pending
|
159
|
+
rescue CommandException => ce
|
160
|
+
ce.raw_input = raw_input
|
161
|
+
raise
|
162
|
+
ensure
|
163
|
+
$stdout.remove_dispatcher(collector)
|
164
|
+
end
|
165
|
+
presenter.done
|
166
|
+
|
167
|
+
@commands_pending.clear
|
168
|
+
end
|
169
|
+
|
170
|
+
#Gets the final Results::Formatter object to output to. Override this
|
171
|
+
#if you won't be reporting output to the standard output.
|
172
|
+
def get_formatter
|
173
|
+
return Results::Formatter.new(::Command::raw_stdout)
|
174
|
+
end
|
175
|
+
|
176
|
+
protected
|
177
|
+
|
178
|
+
def execute(command, formatter, collector)
|
179
|
+
command.advise_formatter(formatter)
|
180
|
+
command.go(collector)
|
181
|
+
return nil
|
182
|
+
end
|
183
|
+
|
184
|
+
def current_command_set
|
185
|
+
return @command_set if @sub_modes.empty?
|
186
|
+
return @sub_modes.last
|
187
|
+
end
|
188
|
+
|
189
|
+
def build_subject
|
190
|
+
if @subject.nil?
|
191
|
+
subject=(subject_template())
|
192
|
+
end
|
193
|
+
return @subject
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|