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