Class: Gloo::Shell::Runner
- Inherits:
-
Object
- Object
- Gloo::Shell::Runner
- Defined in:
- lib/gloo/shell/runner.rb
Direct Known Subclasses
Constant Summary collapse
- DEFAULT_PROMPT =
' -> '.freeze
- UNKNOWN_COMMAND =
'Unknown command'.freeze
- QUIT_NAME =
'quit'.freeze
- QUIT_DESCRIPTION =
'Quit the application'.freeze
- QUIT_METHOD =
'cmd_quit'.freeze
Instance Method Summary collapse
-
#add_command_node(command_data) ⇒ Object
Add a command node to the root, dynamically.
-
#build_node_from_data(data) ⇒ Object
Build a command node from data.
-
#cmd_quit(_obj, context) ⇒ Object
Quit the shell.
-
#execute_command(command_node, _args, _parent_node = nil) ⇒ Object
Execute a command.
-
#execute_once(tokens) ⇒ Object
Execute a single command from the given tokens and return.
-
#handle_empty_command ⇒ Object
Handle an empty command - run the on_empty_command hook if given.
-
#handle_unknown_command ⇒ Object
Handle an unknown command - run the on_unknown_command hook if given, otherwise show the default message.
-
#initialize(engine, opts = {}) ⇒ Runner
constructor
Initialize the shell runner.
-
#prompt ⇒ Object
Get the prompt string.
-
#repl ⇒ Object
Run the REPL loop.
-
#run_after_action ⇒ Object
Run the after_action hook, if given.
-
#run_before_action ⇒ Object
Run the before_action hook, if given.
-
#run_on_error ⇒ Object
Run the on_error hook, if given.
-
#set_context(key, value_list) ⇒ Object
Set a context list, available to dynamic command nodes.
-
#setup_completion ⇒ Object
Setup readline completion.
-
#start ⇒ Object
Start the shell.
-
#stop ⇒ Object
Flag the shell as done.
-
#traverse(node, tokens) ⇒ Hash
Traverse the command tree to find the matching node.
Constructor Details
#initialize(engine, opts = {}) ⇒ Runner
Initialize the shell runner.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/gloo/shell/runner.rb', line 45 def initialize( engine, opts = {} ) @engine = engine @prompt_text = opts[ :prompt ] @on_error = opts[ :on_error ] @on_unknown_command = opts[ :on_unknown_command ] @on_empty_command = opts[ :on_empty_command ] @before_action = opts[ :before_action ] @after_action = opts[ :after_action ] @include_quit = opts[ :include_quit ] || false @context = Gloo::Shell::Context.new @root = Gloo::Shell::CommandNode.new( nil ) add_quit_command if @include_quit end |
Instance Method Details
#add_command_node(command_data) ⇒ Object
Add a command node to the root, dynamically.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/gloo/shell/runner.rb', line 187 def add_command_node( command_data ) node = build_node_from_data( command_data ) existing_block = @root.instance_variable_get( :@children_block ) if existing_block existing_nodes = existing_block.call( @context ) all_nodes = existing_nodes + [ node ] @root.instance_variable_set( :@children_block, proc { |_ctx| all_nodes } ) else @root.instance_variable_set( :@children_block, proc { |_ctx| [ node ] } ) end return node end |
#build_node_from_data(data) ⇒ Object
Build a command node from data.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/gloo/shell/runner.rb', line 161 def build_node_from_data( data ) if data[ :dynamic ] return Gloo::Shell::CommandNode.new( data[ :name ], description: data[ :description ], obj: data[ :obj ] ) do |ctx| ctx.send( data[ :source ] ).map do |item| Gloo::Shell::CommandNode.new( item, method: data[ :child_method ], obj: item ) end end elsif data[ :children ] return Gloo::Shell::CommandNode.new( data[ :name ], description: data[ :description ], method: data[ :method ], obj: data[ :obj ] ) do |_ctx| data[ :children ].map { |child_data| build_node_from_data( child_data ) } end else return Gloo::Shell::CommandNode.new( data[ :name ], description: data[ :description ], method: data[ :method ], obj: data[ :obj ] ) end end |
#cmd_quit(_obj, context) ⇒ Object
Quit the shell.
127 128 129 130 |
# File 'lib/gloo/shell/runner.rb', line 127 def cmd_quit( _obj, context ) puts 'Quitting…' context.done = true end |
#execute_command(command_node, _args, _parent_node = nil) ⇒ Object
Execute a command.
150 151 152 153 154 155 156 |
# File 'lib/gloo/shell/runner.rb', line 150 def execute_command( command_node, _args, _parent_node = nil ) if command_node.method send( command_node.method, command_node.obj, @context ) elsif command_node.name && !command_node.description.empty? puts "#{command_node.description}: #{command_node.name}" end end |
#execute_once(tokens) ⇒ Object
Execute a single command from the given tokens and return.
209 210 211 212 213 214 215 216 217 218 |
# File 'lib/gloo/shell/runner.rb', line 209 def execute_once( tokens ) result = traverse( @root, tokens ) if result[ :node ] run_before_action execute_command( result[ :node ], tokens, result[ :parent ] ) run_after_action else handle_unknown_command end end |
#handle_empty_command ⇒ Object
Handle an empty command - run the on_empty_command hook if given.
89 90 91 |
# File 'lib/gloo/shell/runner.rb', line 89 def handle_empty_command @on_empty_command&.call end |
#handle_unknown_command ⇒ Object
Handle an unknown command - run the on_unknown_command hook if given, otherwise show the default message.
97 98 99 100 101 |
# File 'lib/gloo/shell/runner.rb', line 97 def handle_unknown_command return if @on_unknown_command&.call puts UNKNOWN_COMMAND end |
#prompt ⇒ Object
Get the prompt string.
82 83 84 |
# File 'lib/gloo/shell/runner.rb', line 82 def prompt return @prompt_text ? "#{@prompt_text} " : DEFAULT_PROMPT end |
#repl ⇒ Object
Run the REPL loop.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/gloo/shell/runner.rb', line 277 def repl setup_completion while !@context.done && ( line = Readline.readline( prompt, true ) ) tokens = line.strip.split( ' ' ) if tokens.empty? handle_empty_command next end result = traverse( @root, tokens ) if result[ :node ] run_before_action execute_command( result[ :node ], tokens, result[ :parent ] ) run_after_action else handle_unknown_command end end end |
#run_after_action ⇒ Object
Run the after_action hook, if given.
113 114 115 |
# File 'lib/gloo/shell/runner.rb', line 113 def run_after_action @after_action&.call end |
#run_before_action ⇒ Object
Run the before_action hook, if given.
106 107 108 |
# File 'lib/gloo/shell/runner.rb', line 106 def run_before_action @before_action&.call end |
#run_on_error ⇒ Object
Run the on_error hook, if given.
120 121 122 |
# File 'lib/gloo/shell/runner.rb', line 120 def run_on_error @on_error&.call end |
#set_context(key, value_list) ⇒ Object
Set a context list, available to dynamic command nodes.
139 140 141 |
# File 'lib/gloo/shell/runner.rb', line 139 def set_context( key, value_list ) @context.set( key, value_list ) end |
#setup_completion ⇒ Object
Setup readline completion.
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 |
# File 'lib/gloo/shell/runner.rb', line 247 def setup_completion Readline.completion_append_character = ' ' Readline.basic_word_break_characters = " \t\n\"\\'`@$><=;|&{(" Readline.completion_proc = proc do |input| buffer = Readline.line_buffer tokens = buffer.split( ' ' ) tokens << '' if buffer.end_with?( ' ' ) result = traverse( @root, tokens[ 0..-2 ] ) current = result[ :node ] = current ? current.children( @context ).map( &:name ) : [] matches = .grep( /^#{Regexp.escape( input )}/ ) if matches.length > 1 puts current.children( @context ).each do |child| puts "#{child.name.ljust( 15 )} #{child.description}" if matches.include?( child.name ) end print "#{prompt}#{buffer}" end matches end end |
#start ⇒ Object
Start the shell.
68 69 70 |
# File 'lib/gloo/shell/runner.rb', line 68 def start repl end |
#stop ⇒ Object
Flag the shell as done. Next time through the loop it will stop.
75 76 77 |
# File 'lib/gloo/shell/runner.rb', line 75 def stop @context.done = true end |
#traverse(node, tokens) ⇒ Hash
Traverse the command tree to find the matching node.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/gloo/shell/runner.rb', line 228 def traverse( node, tokens ) current = node parent = nil tokens.each do |token| children = current.children( @context ) match = children.find { |c| c.name == token } return { node: nil, parent: nil } unless match parent = current current = match end return { node: current, parent: parent } end |