Class: Remailer::Interpreter
- Inherits:
-
Object
- Object
- Remailer::Interpreter
- Defined in:
- lib/remailer/interpreter.rb
Direct Known Subclasses
Remailer::IMAP::Client::Interpreter, Remailer::IMAP::Server::Interpreter, SMTP::Client::Interpreter, SMTP::Server::Interpreter, SOCKS5::Client::Interpreter
Defined Under Namespace
Classes: DefinitionException, StateProxy
Instance Attribute Summary collapse
-
#delegate ⇒ Object
readonly
Properties ===========================================================.
-
#error ⇒ Object
readonly
Returns the value of attribute error.
-
#state ⇒ Object
readonly
Returns the value of attribute state.
Class Method Summary collapse
- .config ⇒ Object
-
.create_parser_for_spec(spec, &block) ⇒ Object
This is a method to convert a spec and a block into a proper parser method.
-
.default(&block) ⇒ Object
Assigns the default interpreter.
-
.default_interpreter ⇒ Object
Returns the current default_interpreter.
-
.default_parser ⇒ Object
Returns the parser used when no state-specific parser has been defined.
-
.initial_state ⇒ Object
Defines the initial state for objects of this class.
-
.initial_state=(state) ⇒ Object
Can be used to reassign the initial state for this class.
-
.on_error(&block) ⇒ Object
Assigns the error handler for when a specific interpretation could not be found and a default was not specified.
-
.on_error_handler ⇒ Object
Returns the defined error handler.
-
.parse(spec = nil, &block) ⇒ Object
Defines a parser for this interpreter.
-
.state(state, &block) ⇒ Object
Defines a new state for this class.
-
.state_defined?(state) ⇒ Boolean
Returns true if a given state is defined, false otherwise.
-
.states ⇒ Object
Returns the states that are defined as a has with their associated options.
- .states_default ⇒ Object
-
.states_defined ⇒ Object
Returns a list of the defined states.
- .states_empty? ⇒ Boolean
Instance Method Summary collapse
-
#enter_state(state) ⇒ Object
Enters the given state.
-
#error? ⇒ Boolean
Returns true if an error has been generated, false otherwise.
-
#finished? ⇒ Boolean
Should return true if this interpreter no longer wants any data, false otherwise.
-
#initialize(options = nil) {|_self| ... } ⇒ Interpreter
constructor
Creates a new interpreter with an optional set of options.
-
#interpret(*args) ⇒ Object
Interprets a given object with an optional set of arguments.
-
#parse(buffer) ⇒ Object
Parses a given string and returns the first interpretable token, if any, or nil otherwise.
-
#parser ⇒ Object
Returns the parser defined for the current state, or the default parser.
-
#process(s) ⇒ Object
Processes a given input string into interpretable tokens, processes these tokens, and removes them from the input string.
-
#will_interpret?(proc, args) ⇒ Boolean
This method is used by interpret to determine if the supplied block should be executed or not.
Constructor Details
#initialize(options = nil) {|_self| ... } ⇒ Interpreter
Creates a new interpreter with an optional set of options. Valid options include:
-
:delegate => Which object to use as a delegate, if applicable.
-
:state => What the initial state should be. The default is :initalized
If a block is supplied, the interpreter object is supplied as an argument to give the caller an opportunity to perform any initial configuration before the first state is entered.
169 170 171 172 173 174 175 |
# File 'lib/remailer/interpreter.rb', line 169 def initialize( = nil) @delegate = ( and [:delegate]) yield(self) if (block_given?) enter_state( && [:state] || self.class.initial_state) end |
Instance Attribute Details
#delegate ⇒ Object (readonly)
Properties ===========================================================
14 15 16 |
# File 'lib/remailer/interpreter.rb', line 14 def delegate @delegate end |
#error ⇒ Object (readonly)
Returns the value of attribute error.
16 17 18 |
# File 'lib/remailer/interpreter.rb', line 16 def error @error end |
#state ⇒ Object (readonly)
Returns the value of attribute state.
15 16 17 |
# File 'lib/remailer/interpreter.rb', line 15 def state @state end |
Class Method Details
.config ⇒ Object
31 32 33 34 35 36 37 38 |
# File 'lib/remailer/interpreter.rb', line 31 def self.config { states: @states, default_interpreter: @default, default_parser: @parser, on_error_handler: @on_error } end |
.create_parser_for_spec(spec, &block) ⇒ Object
This is a method to convert a spec and a block into a proper parser method. If spec is specified, it should be a Fixnum, or a Regexp. A Fixnum defines a minimum size to process, useful for packed binary streams, while a Regexp defines a pattern that must match before the parser is engaged.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/remailer/interpreter.rb', line 89 def self.create_parser_for_spec(spec, &block) case (spec) when nil block when Fixnum lambda do |s| if (s.length >= spec) part = s.slice!(0, spec) block.call(part) end end when Regexp lambda do |s| if (m = spec.match(s)) part = m.to_s part = s.slice!(0, part.length) block.call(part) end end else raise DefinitionException, "Invalid specification for parse declaration: #{spec.inspect}" end end |
.default(&block) ⇒ Object
Assigns the default interpreter.
120 121 122 |
# File 'lib/remailer/interpreter.rb', line 120 def self.default(&block) @default = block if (block_given?) end |
.default_interpreter ⇒ Object
Returns the current default_interpreter.
141 142 143 144 145 146 147 148 |
# File 'lib/remailer/interpreter.rb', line 141 def self.default_interpreter @default ||= if (superclass.respond_to?(:default_interpreter)) superclass.default_interpreter else nil end end |
.default_parser ⇒ Object
Returns the parser used when no state-specific parser has been defined.
131 132 133 134 135 136 137 138 |
# File 'lib/remailer/interpreter.rb', line 131 def self.default_parser @parser ||= if (superclass.respond_to?(:default_parser)) superclass.default_parser else lambda { |s| _s = s.dup; s.replace(''); _s } end end |
.initial_state ⇒ Object
Defines the initial state for objects of this class.
21 22 23 |
# File 'lib/remailer/interpreter.rb', line 21 def self.initial_state @initial_state || :initialized end |
.initial_state=(state) ⇒ Object
Can be used to reassign the initial state for this class. May be easier than re-defining the initial_state method.
27 28 29 |
# File 'lib/remailer/interpreter.rb', line 27 def self.initial_state=(state) @initial_state = state end |
.on_error(&block) ⇒ Object
Assigns the error handler for when a specific interpretation could not be found and a default was not specified.
126 127 128 |
# File 'lib/remailer/interpreter.rb', line 126 def self.on_error(&block) @on_error = block end |
.on_error_handler ⇒ Object
Returns the defined error handler
151 152 153 154 155 156 157 158 |
# File 'lib/remailer/interpreter.rb', line 151 def self.on_error_handler @on_error ||= if (superclass.respond_to?(:on_error_handler)) superclass.on_error_handler else nil end end |
.parse(spec = nil, &block) ⇒ Object
Defines a parser for this interpreter. The supplied block is executed in the context of a parser instance.
115 116 117 |
# File 'lib/remailer/interpreter.rb', line 115 def self.parse(spec = nil, &block) @parser = create_parser_for_spec(spec, &block) end |
.state(state, &block) ⇒ Object
Defines a new state for this class. A block will be executed in the context of a StateProxy that is used to provide a simple interface to the underlying options. A block can contain calls to enter and leave, or default, which do not require arguments, or interpret, which requries at least one argument that will be the class-specific object to interpret. Other paramters may be supplied by the class.
78 79 80 81 82 |
# File 'lib/remailer/interpreter.rb', line 78 def self.state(state, &block) config = self.states[state] = { } StateProxy.new(config, &block) end |
.state_defined?(state) ⇒ Boolean
Returns true if a given state is defined, false otherwise.
63 64 65 |
# File 'lib/remailer/interpreter.rb', line 63 def self.state_defined?(state) !!self.states[state] end |
.states ⇒ Object
Returns the states that are defined as a has with their associated options. The default keys are :initialized and :terminated.
49 50 51 52 53 54 55 56 |
# File 'lib/remailer/interpreter.rb', line 49 def self.states @states ||= if (superclass.respond_to?(:states)) superclass.states.dup else self.states_default end end |
.states_default ⇒ Object
40 41 42 43 44 45 |
# File 'lib/remailer/interpreter.rb', line 40 def self.states_default { :initialized => { }, :terminated => { } } end |
.states_defined ⇒ Object
Returns a list of the defined states.
68 69 70 |
# File 'lib/remailer/interpreter.rb', line 68 def self.states_defined self.states.keys end |
.states_empty? ⇒ Boolean
58 59 60 |
# File 'lib/remailer/interpreter.rb', line 58 def self.states_empty? self.states == self.states_default end |
Instance Method Details
#enter_state(state) ⇒ Object
Enters the given state. Will call the appropriate leave_state trigger if one is defined for the previous state, and will trigger the callbacks for entry into the new state. If this state is set as a terminate state, then an immediate transition to the :terminate state will be performed after these callbacks.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/remailer/interpreter.rb', line 182 def enter_state(state) if (@state) leave_state(@state) end @state = state delegate_call(:interpreter_entered_state, self, @state) trigger_callbacks(state, :enter) # :terminated is the state, :terminate is the trigger. if (@state != :terminated) if (trigger_callbacks(state, :terminate)) enter_state(:terminated) end end end |
#error? ⇒ Boolean
Returns true if an error has been generated, false otherwise. The error content can be retrived by calling error.
315 316 317 |
# File 'lib/remailer/interpreter.rb', line 315 def error? !!@error end |
#finished? ⇒ Boolean
Should return true if this interpreter no longer wants any data, false otherwise. Subclasses should implement their own behavior here.
309 310 311 |
# File 'lib/remailer/interpreter.rb', line 309 def finished? false end |
#interpret(*args) ⇒ Object
Interprets a given object with an optional set of arguments. The actual interpretation should be defined by declaring a state with an interpret block defined.
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/remailer/interpreter.rb', line 236 def interpret(*args) object = args[0] config = self.class.states[@state] interpreters = (config and config[:interpret]) if (interpreters) match_result = nil matched, proc = interpreters.find do |response, proc| case (response) when Regexp match_result = response.match(object) when Range response.include?(object) else response === object end end if (matched) case (matched) when Regexp match_result = match_result.to_a if (match_result.length > 1) match_string = match_result.shift args[0, 1] = match_result else args[0].sub!(match_result[0], '') end when String args[0].sub!(matched, '') when Range # Keep as-is else args.shift end # Specifying a block with no arguments will mean that it waits until # all pieces are collected before transitioning to a new state, # waiting until the continue flag is false. will_interpret?(proc, args) and instance_exec(*args, &proc) return true end end if (trigger_callbacks(@state, :default, *args)) # Handled by default true elsif (proc = self.class.default_interpreter) instance_exec(*args, &proc) else if (proc = self.class.on_error_handler) instance_exec(*args, &proc) end @error = "No handler for response #{object.inspect} in state #{@state.inspect}" enter_state(:terminated) false end end |
#parse(buffer) ⇒ Object
Parses a given string and returns the first interpretable token, if any, or nil otherwise. If an interpretable token is found, the supplied string will be modified to have that matching portion removed.
204 205 206 |
# File 'lib/remailer/interpreter.rb', line 204 def parse(buffer) instance_exec(buffer, &parser) end |
#parser ⇒ Object
Returns the parser defined for the current state, or the default parser. The default parser simply accepts everything but this can be re-defined using the class-level parse method.
211 212 213 214 215 |
# File 'lib/remailer/interpreter.rb', line 211 def parser config = self.class.states[@state] config and config[:parser] or self.class.default_parser end |
#process(s) ⇒ Object
Processes a given input string into interpretable tokens, processes these tokens, and removes them from the input string. An optional block can be given that will be called as each interpretable token is discovered with the token provided as the argument.
221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/remailer/interpreter.rb', line 221 def process(s) _parser = parser while (parsed = instance_exec(s, &_parser)) yield(parsed) if (block_given?) interpret(*parsed) break if (s.empty? or self.finished?) end end |
#will_interpret?(proc, args) ⇒ Boolean
This method is used by interpret to determine if the supplied block should be executed or not. The default behavior is to always execute but this can be modified in sub-classes.
303 304 305 |
# File 'lib/remailer/interpreter.rb', line 303 def will_interpret?(proc, args) true end |