Class: ShellRunner

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

Constant Summary collapse

DEFAULT_PROMPT =
" -> "

Instance Method Summary collapse

Constructor Details

#initialize(engine, obj) ⇒ ShellRunner

Initialize the shell runner

Parameters:

  • obj (Object)

    The shell obj.



17
18
19
20
21
22
# File 'lib/shell_runner.rb', line 17

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



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

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.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/shell_runner.rb', line 118

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.



62
63
64
65
66
67
68
# File 'lib/shell_runner.rb', line 62

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



70
71
72
73
74
75
76
77
78
79
# File 'lib/shell_runner.rb', line 70

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
      command.run_action_with_context( cmd_node.name )
    end
  end

end

#cmd_quit(obj, context) ⇒ Object

Quit the shell.



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

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

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

Execute a command.



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/shell_runner.rb', line 101

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

#promptObject

Get the prompt string



46
47
48
49
# File 'lib/shell_runner.rb', line 46

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

#replObject

Run the REPL loop.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/shell_runner.rb', line 226

def repl
  setup_completion

  while ( ! @context.done && (line = Readline.readline(prompt, true)) )
    tokens = line.strip.split(" ")
    next if tokens.empty?

    result = traverse( @root, tokens )

    if result[:node]
      execute_command( result[:node], tokens, result[:parent] )
    else
      puts "Unknown command"
    end
  end
end

#set_context(key, value_list) ⇒ Object

Set a context list.



89
90
91
# File 'lib/shell_runner.rb', line 89

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

#setup_completionObject

Setup readline completion



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/shell_runner.rb', line 192

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.



32
33
34
# File 'lib/shell_runner.rb', line 32

def start
  repl
end

#stopObject

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



39
40
41
# File 'lib/shell_runner.rb', line 39

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



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/shell_runner.rb', line 173

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