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