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