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

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



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/shell_runner.rb', line 248

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



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/shell_runner.rb', line 214

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



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/shell_runner.rb', line 195

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