Class: ShellRunner

Inherits:
Object
  • Object
show all
Defined in:
lib/shell_runner.rb

Constant Summary collapse

DEFAULT_PROMPT =
" -> "
UNKNOWN_COMMAND =
"Unknown command".freeze

Instance Method Summary collapse

Constructor Details

#initialize(engine, obj) ⇒ ShellRunner

Initialize the shell runner

Parameters:

  • obj (Object)

    The shell obj.



19
20
21
22
23
24
# File 'lib/shell_runner.rb', line 19

def initialize( engine, obj )
  @engine = engine
  @obj = obj
  @context = ShellContext.new
  @root = CommandNode.new( nil )
end

Instance Method Details

#add_command_node(command_data) ⇒ Object

Add a command node to the root dynamically

Add a single command dynamically

Parameters:

  • command_data (Hash)

    The command data hash



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/shell_runner.rb', line 163

def add_command_node( command_data)
  node = build_node_from_data(command_data)
  
  # Get existing children block or create new one
  existing_block = @root.instance_variable_get(:@children_block)
  
  if existing_block
    # Store existing nodes and add new one
    existing_nodes = existing_block.call(@context)
    all_nodes = existing_nodes + [node]
    @root.instance_variable_set(:@children_block, proc { |ctx| all_nodes })
  else
    # Create new children block with just this node
    @root.instance_variable_set(:@children_block, proc { |ctx| [node] })
  end
  
  node
end

#build_node_from_data(data) ⇒ Object

Build a command node from data.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/shell_runner.rb', line 140

def build_node_from_data( data )
  if data[:dynamic]
    CommandNode.new(data[:name], description: data[:description], obj: data[:obj]) do |ctx|
      ctx.send(data[:source]).map do |item|
        CommandNode.new(item)
      end
    end
  elsif data[:children]
    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
    CommandNode.new(data[:name], description: data[:description], method: data[:method], obj: data[:obj])
  end
end

#cmd_obj_action(obj, context) ⇒ Object

Run an action on an object.



78
79
80
81
82
83
84
# File 'lib/shell_runner.rb', line 78

def cmd_obj_action( obj, context )
  pn = Gloo::Core::Pn.new( @engine, obj )
  command = pn.resolve
  if command
    command.run_action
  end
end

#cmd_obj_action_with_context(cmd_node, parent_node = nil) ⇒ Object

Run an action on an object with context.



89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/shell_runner.rb', line 89

def cmd_obj_action_with_context( cmd_node, parent_node = nil )
  if parent_node
    pn = Gloo::Core::Pn.new( @engine, parent_node.obj )
    command = pn.resolve
    if command
      begin
        command.run_action_with_context( cmd_node.name )
      rescue => e
        command.run_on_error || @obj.run_on_error
      end
    end
  end
end

#cmd_quit(obj, context) ⇒ Object

Quit the shell.



70
71
72
73
# File 'lib/shell_runner.rb', line 70

def cmd_quit( obj, context )
  puts "Quitting…"
  context.done = true
end

#execute_command(command_node, args, parent_node = nil) ⇒ Object

Execute a command.



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/shell_runner.rb', line 123

def execute_command( command_node, args, parent_node = nil )
  if command_node.respond_to?( :method ) && command_node.method
    send( command_node.method, command_node.obj, @context )
  else
    if command_node.name && command_node.name != "" && !command_node.description.empty?
      puts "#{command_node.description}: #{command_node.name}"
    elsif command_node.name
      # puts "Showing: #{command_node.name}"
      cmd_obj_action_with_context( command_node, parent_node )
    end
  end
end

#execute_once(tokens) ⇒ Object

Execute a single command from the given tokens and return. Used when a command is passed directly from the CLI.



191
192
193
194
195
196
197
198
199
200
# File 'lib/shell_runner.rb', line 191

def execute_once( tokens )
  result = traverse( @root, tokens )
  if result[:node]
    @obj.run_before_action
    execute_command( result[:node], tokens, result[:parent] )
    @obj.run_after_action
  else
    handle_unknown_command
  end
end

#handle_empty_commandObject

Handle an empty command — run on_empty_command if defined, otherwise do nothing.



56
57
58
# File 'lib/shell_runner.rb', line 56

def handle_empty_command
  @obj.run_on_empty_cmd
end

#handle_unknown_commandObject

Handle an unknown command — run on_unknown_cmd if defined, otherwise show default message.



63
64
65
# File 'lib/shell_runner.rb', line 63

def handle_unknown_command
  @obj.run_on_unknown_cmd || puts( UNKNOWN_COMMAND )
end

#promptObject

Get the prompt string



48
49
50
51
# File 'lib/shell_runner.rb', line 48

def prompt
  p = @obj.prompt 
  return p ? p + ' ' : DEFAULT_PROMPT
end

#replObject

Run the REPL loop.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/shell_runner.rb', line 263

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]
      @obj.run_before_action
      execute_command( result[:node], tokens, result[:parent] )
      @obj.run_after_action
    else
      handle_unknown_command
    end
  end
end

#set_context(key, value_list) ⇒ Object

Set a context list.



111
112
113
# File 'lib/shell_runner.rb', line 111

def set_context key, value_list
  @context.set( key, value_list )
end

#setup_completionObject

Setup readline completion



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/shell_runner.rb', line 229

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]
    options = current.children( @context ).map( &:name )

    matches = options.grep(/^#{Regexp.escape(input)}/)

    if matches.length > 1
      puts
      current.children( @context ).each do |child|
        if matches.include?( child.name )
          puts "#{child.name.ljust(15)} #{child.description}"
        end
      end
      print "#{prompt}#{buffer}"
    end

    matches
  end
end

#startObject

Start the shell.



34
35
36
# File 'lib/shell_runner.rb', line 34

def start
  repl
end

#stopObject

Flag the shell as done, next time through the loop it will stop



41
42
43
# File 'lib/shell_runner.rb', line 41

def stop
  @context.done = true
end

#traverse(node, tokens) ⇒ Hash

Traverse the command tree to find the matching node

Parameters:

  • node (CommandNode)

    The current node

  • tokens (Array<String>)

    The tokens to traverse

Returns:

  • (Hash)

    Hash with :node and :parent keys



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/shell_runner.rb', line 210

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

  { node: current, parent: parent }
end