rhcp_shell 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +12 -0
- data/README +3 -0
- data/README.txt +48 -0
- data/Rakefile +71 -0
- data/lib/base_shell.rb +100 -0
- data/lib/rhcp_shell.rb +108 -0
- data/lib/rhcp_shell_backend.rb +513 -0
- data/lib/shell_backend.rb +29 -0
- data/start_shell.sh +3 -0
- data/test/rhcp_shell_backend_test.rb +236 -0
- data/test/setup_test_registry.rb +109 -0
- metadata +77 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README
ADDED
data/README.txt
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= sow_test
|
2
|
+
|
3
|
+
* FIX (url)
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
FIX (describe your package)
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* FIX (list of requirements)
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* FIX (sudo gem install, anything else)
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright (c) 2009 FIX
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/packagetask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
PKG_NAME = 'rhcp_shell'
|
8
|
+
PKG_VERSION = '0.0.1'
|
9
|
+
|
10
|
+
desc "Default Task"
|
11
|
+
task :default => [ :test ]
|
12
|
+
|
13
|
+
###############################################
|
14
|
+
### TESTS
|
15
|
+
Rake::TestTask.new() { |t|
|
16
|
+
t.libs << "test"
|
17
|
+
t.test_files = FileList['test/*_test.rb']
|
18
|
+
t.verbose = true
|
19
|
+
}
|
20
|
+
|
21
|
+
###############################################
|
22
|
+
### RDOC
|
23
|
+
Rake::RDocTask.new { |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'doc'
|
25
|
+
rdoc.title = "RHCP Shell"
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source' <<
|
27
|
+
'--accessor' << 'cattr_accessor=object'
|
28
|
+
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
29
|
+
#rdoc.rdoc_files.include('README', 'CHANGELOG')
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
|
+
}
|
32
|
+
|
33
|
+
###############################################
|
34
|
+
### METRICS
|
35
|
+
task :lines do
|
36
|
+
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
|
37
|
+
|
38
|
+
for file_name in FileList["lib/**/*.rb"]
|
39
|
+
f = File.open(file_name)
|
40
|
+
|
41
|
+
while line = f.gets
|
42
|
+
lines += 1
|
43
|
+
next if line =~ /^\s*$/
|
44
|
+
next if line =~ /^\s*#/
|
45
|
+
codelines += 1
|
46
|
+
end
|
47
|
+
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
48
|
+
|
49
|
+
total_lines += lines
|
50
|
+
total_codelines += codelines
|
51
|
+
|
52
|
+
lines, codelines = 0, 0
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
|
56
|
+
end
|
57
|
+
|
58
|
+
#rcov -I lib/ -I test/ -x rcov.rb -x var/lib -x lib/shell_backend.rb test/*test.rb
|
59
|
+
|
60
|
+
task :update_manifest do
|
61
|
+
system "rake check_manifest 2>/dev/null | grep -vE 'qooxdoo|nbproject|coverage' | grep -E '^\+' | grep -vE '^$' | grep -v '(in ' | grep -vE '^\+\+\+' | cut -b 2-200 | patch"
|
62
|
+
end
|
63
|
+
|
64
|
+
require 'rubygems'
|
65
|
+
require 'hoe'
|
66
|
+
|
67
|
+
Hoe.new('rhcp_shell', PKG_VERSION) do |p|
|
68
|
+
p.rubyforge_name = 'rhcp' # if different than lowercase project name
|
69
|
+
p.developer('Philipp T.', 'philipp@hitchhackers.net')
|
70
|
+
end
|
71
|
+
|
data/lib/base_shell.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# This class is an abstract implementation of a command shell
|
2
|
+
# It handles command completion and history functions.
|
3
|
+
# For the actual business logic, you need to pass it an implementation of ShellBackend
|
4
|
+
class BaseShell
|
5
|
+
attr_reader :backend
|
6
|
+
|
7
|
+
def initialize(backend)
|
8
|
+
@logger = $logger
|
9
|
+
@backend = backend
|
10
|
+
|
11
|
+
at_exit { console.close }
|
12
|
+
|
13
|
+
trap("INT") {
|
14
|
+
Thread.kill(@thread)
|
15
|
+
@backend.process_ctrl_c
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
class SimpleConsole
|
20
|
+
def initialize(input = $stdin)
|
21
|
+
@input = input
|
22
|
+
end
|
23
|
+
|
24
|
+
def readline
|
25
|
+
begin
|
26
|
+
line = @input.readline
|
27
|
+
line.chomp! if line
|
28
|
+
line
|
29
|
+
rescue EOFError
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ReadlineConsole
|
39
|
+
HISTORY_FILE = ".jscmd_history"
|
40
|
+
MAX_HISTORY = 200
|
41
|
+
|
42
|
+
def history_path
|
43
|
+
File.join(ENV['HOME'] || ENV['USERPROFILE'], HISTORY_FILE)
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(shell)
|
47
|
+
@shell = shell
|
48
|
+
if File.exist?(history_path)
|
49
|
+
hist = File.readlines(history_path).map{|line| line.chomp}
|
50
|
+
Readline::HISTORY.push(*hist)
|
51
|
+
end
|
52
|
+
if Readline.methods.include?("basic_word_break_characters=")
|
53
|
+
#Readline.basic_word_break_characters = " \t\n\\`@><=;|&{([+-*/%"
|
54
|
+
Readline.basic_word_break_characters = " \t\n\\`@><=;|&{([+*%"
|
55
|
+
end
|
56
|
+
Readline.completion_append_character = nil
|
57
|
+
Readline.completion_proc = @shell.backend.method(:complete).to_proc
|
58
|
+
end
|
59
|
+
|
60
|
+
def close
|
61
|
+
open(history_path, "wb") do |f|
|
62
|
+
history = Readline::HISTORY.to_a
|
63
|
+
if history.size > MAX_HISTORY
|
64
|
+
history = history[history.size - MAX_HISTORY, MAX_HISTORY]
|
65
|
+
end
|
66
|
+
history.each{|line| f.puts(line)}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def readline
|
71
|
+
line = Readline.readline(@shell.backend.prompt, true)
|
72
|
+
Readline::HISTORY.pop if /^\s*$/ =~ line
|
73
|
+
line
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def console
|
78
|
+
@console ||= $stdin.tty? ? ReadlineConsole.new(self) : SimpleConsole.new
|
79
|
+
end
|
80
|
+
|
81
|
+
def run
|
82
|
+
backend.show_banner
|
83
|
+
loop do
|
84
|
+
@thread = Thread.new {
|
85
|
+
break unless line = console.readline
|
86
|
+
|
87
|
+
if line then
|
88
|
+
@logger.debug "got : #{line}"
|
89
|
+
else
|
90
|
+
@logger.debug "got an empty line"
|
91
|
+
end
|
92
|
+
|
93
|
+
backend.process_input line
|
94
|
+
}
|
95
|
+
@thread.join
|
96
|
+
end
|
97
|
+
$stderr.puts "Exiting shell..."
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
data/lib/rhcp_shell.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "readline"
|
4
|
+
require "logger"
|
5
|
+
require "getoptlong"
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
|
9
|
+
require 'rhcp'
|
10
|
+
|
11
|
+
require 'base_shell'
|
12
|
+
require 'rhcp_shell_backend'
|
13
|
+
|
14
|
+
# TODO version number
|
15
|
+
SHELL_VERSION = "0.1"
|
16
|
+
|
17
|
+
class RhcpShell
|
18
|
+
|
19
|
+
HELP_STRING = <<EOF
|
20
|
+
|
21
|
+
RHCP Command Shell v #{SHELL_VERSION} (using RHCP library v #{RHCP::Version.to_s})
|
22
|
+
|
23
|
+
Usage:
|
24
|
+
rhcp_shell.rb [--hostname=<hostname>] [--username=<username> --password=<password>]
|
25
|
+
[--help]
|
26
|
+
|
27
|
+
Options:
|
28
|
+
--hostname=<hostname>
|
29
|
+
the URL to the RHCP server you want to connect against, e.g.
|
30
|
+
http://server.local.network/rhcp
|
31
|
+
If the specified hostname does not start with "http", it is automatically expanded to
|
32
|
+
http://<hostname>:42000/rhcp
|
33
|
+
You can optionally specify a port number to connect against like this:
|
34
|
+
http://myserver:42000
|
35
|
+
If you do not specify a hostname, the shell will try to connect against
|
36
|
+
http://localhost:42000
|
37
|
+
|
38
|
+
--username/--password
|
39
|
+
the authentication data you want to use for connecting to the RHCP server
|
40
|
+
|
41
|
+
--help
|
42
|
+
displays this help screen.
|
43
|
+
|
44
|
+
EOF
|
45
|
+
|
46
|
+
def run
|
47
|
+
opts = GetoptLong.new(
|
48
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
49
|
+
[ '--username', '-u', GetoptLong::REQUIRED_ARGUMENT ],
|
50
|
+
[ '--password', '-p', GetoptLong::REQUIRED_ARGUMENT ],
|
51
|
+
[ '--hostname', GetoptLong::REQUIRED_ARGUMENT ]
|
52
|
+
)
|
53
|
+
|
54
|
+
options = Hash.new
|
55
|
+
opts.each do |opt, arg|
|
56
|
+
case opt
|
57
|
+
when '--help'
|
58
|
+
puts HELP_STRING
|
59
|
+
Kernel.exit(1)
|
60
|
+
else
|
61
|
+
opt =~ /--(.+)/
|
62
|
+
$logger.debug "setting #{arg} for #{$1}" unless $1 == "password"
|
63
|
+
options[$1] = arg
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
host = options["hostname"]
|
68
|
+
if host == nil then
|
69
|
+
host = "http://localhost:42000"
|
70
|
+
else
|
71
|
+
if host !~ /http:/ then
|
72
|
+
host = "http://#{host}:42000/rhcp"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
$logger.debug "now connecting to #{host}"
|
76
|
+
|
77
|
+
# TODO add interactive query for password!
|
78
|
+
|
79
|
+
begin
|
80
|
+
url = URI.parse(host)
|
81
|
+
@http_broker = RHCP::Client::HttpBroker.new(url)
|
82
|
+
|
83
|
+
backend = RHCPShellBackend.new(@http_broker)
|
84
|
+
backend.banner = <<EOF
|
85
|
+
Good morning, this is the generic RHCP Shell.
|
86
|
+
Press <tab> for command completion or type "help" for a list of commands.
|
87
|
+
If you want to exit this shell, please press Ctrl+C or type "exit".
|
88
|
+
|
89
|
+
EOF
|
90
|
+
$logger.debug "backend has been instantiated : #{backend}"
|
91
|
+
|
92
|
+
shell = BaseShell.new(backend)
|
93
|
+
shell.run
|
94
|
+
rescue => ex
|
95
|
+
puts "There occurred an HTTP error while connecting to the RHCP: #{ex}"
|
96
|
+
puts "Please connect against another server or fix the connection problem."
|
97
|
+
puts ex.backtrace.join("\n")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# TODO introduce something like the RAILS_ENVs
|
104
|
+
$logger = Logger.new("rhcp_shell.log")
|
105
|
+
RHCP::ModuleHelper.instance().logger = $logger
|
106
|
+
|
107
|
+
shell = RhcpShell.new
|
108
|
+
shell.run
|
@@ -0,0 +1,513 @@
|
|
1
|
+
require 'shell_backend.rb'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rhcp'
|
5
|
+
|
6
|
+
# This shell presents RHCP commands to the user and handles all the parameter
|
7
|
+
# lookup, validation and command completion stuff
|
8
|
+
#
|
9
|
+
# It uses a RHCP registry/broker as a data backend and possibly for communication with
|
10
|
+
# a server.
|
11
|
+
#
|
12
|
+
# This shell implementation handles two modes - one for entering/selecting
|
13
|
+
# a command, another for entering/selecting parameter values.
|
14
|
+
# If a command and all mandatory parameters are entered/selected, the command is
|
15
|
+
# executed.
|
16
|
+
class RHCPShellBackend < ShellBackend
|
17
|
+
|
18
|
+
attr_reader :prompt
|
19
|
+
attr_accessor :banner
|
20
|
+
|
21
|
+
def initialize(command_broker)
|
22
|
+
super()
|
23
|
+
|
24
|
+
local_broker = setup_local_broker
|
25
|
+
@command_broker = RHCP::DispatchingBroker.new()
|
26
|
+
@command_broker.add_broker(command_broker)
|
27
|
+
@command_broker.add_broker(local_broker)
|
28
|
+
|
29
|
+
reset_to_command_mode
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup_local_broker
|
33
|
+
broker = RHCP::Broker.new()
|
34
|
+
begin
|
35
|
+
broker.register_command RHCP::Command.new("exit", "closes the shell",
|
36
|
+
lambda { |req,res|
|
37
|
+
puts "Have a nice day"
|
38
|
+
Kernel.exit(0)
|
39
|
+
}
|
40
|
+
)
|
41
|
+
|
42
|
+
def param_example(param, suffix = "")
|
43
|
+
result = ""
|
44
|
+
result += "<" if param.is_default_param
|
45
|
+
|
46
|
+
if param.is_default_param then
|
47
|
+
result += "#{param.name}#{suffix}"
|
48
|
+
else
|
49
|
+
result += "#{param.name}"
|
50
|
+
result += "=<value#{suffix}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
result += ">" if param.is_default_param
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
command = RHCP::Command.new("help", "displays help about this shell",
|
58
|
+
lambda {
|
59
|
+
|req,res|
|
60
|
+
|
61
|
+
if (req.has_param_value("command"))
|
62
|
+
command_name = req.get_param_value("command")
|
63
|
+
puts "Syntax:"
|
64
|
+
command_line = " #{command_name}"
|
65
|
+
command = @command_broker.get_command(command_name)
|
66
|
+
|
67
|
+
command.params.values.sort { |a,b| a.name <=> b.name }.each do |param|
|
68
|
+
command_line += " "
|
69
|
+
command_line += "[" unless param.mandatory
|
70
|
+
|
71
|
+
command_line += param_example(param)
|
72
|
+
|
73
|
+
if param.allows_multiple_values then
|
74
|
+
command_line += ", "
|
75
|
+
command_line += param_example(param, "2")
|
76
|
+
command_line += ", ..."
|
77
|
+
end
|
78
|
+
|
79
|
+
command_line += "]" unless param.mandatory
|
80
|
+
end
|
81
|
+
puts command_line
|
82
|
+
puts "Description:"
|
83
|
+
puts " #{command.description}"
|
84
|
+
if command.params.size > 0 then
|
85
|
+
puts "Parameters:"
|
86
|
+
command.params.values.sort { |a,b| a.name <=> b.name }.each do |param|
|
87
|
+
puts sprintf(" %-20s %s\n", param.name, param.description)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
puts ""
|
91
|
+
else
|
92
|
+
puts "The following commands are available:"
|
93
|
+
@command_broker.get_command_list.values.sort { |a,b| a.name <=> b.name }.each do |command|
|
94
|
+
# TODO calculate the maximum command name length dynamically
|
95
|
+
puts sprintf(" %-40s %s\n", command.name, command.description)
|
96
|
+
end
|
97
|
+
puts ""
|
98
|
+
puts "Type help <command name> for detailed information about a command."
|
99
|
+
end
|
100
|
+
}
|
101
|
+
).add_param(RHCP::CommandParam.new("command", "the name of the command to display help for",
|
102
|
+
{
|
103
|
+
:mandatory => false,
|
104
|
+
:is_default_param => true,
|
105
|
+
:lookup_method => lambda {
|
106
|
+
@command_broker.get_command_list.values.map { |c| c.name }
|
107
|
+
}
|
108
|
+
}
|
109
|
+
)
|
110
|
+
)
|
111
|
+
command.result_hints[:display_type] = "hidden"
|
112
|
+
broker.register_command command
|
113
|
+
|
114
|
+
command = RHCP::Command.new("detail", "shows details about a single record of the last response (makes sense if you executed a command that returned a table)",
|
115
|
+
lambda { |req,res|
|
116
|
+
if @last_response == nil
|
117
|
+
puts "did not find any old response data...is it possible that you did not execute a command yet that returned a table?"
|
118
|
+
return
|
119
|
+
end
|
120
|
+
row_count = @last_response.data.length
|
121
|
+
begin
|
122
|
+
row_index = req.get_param_value("row_index").to_i
|
123
|
+
raise "invalid index" if (row_index < 1 || row_index > row_count)
|
124
|
+
rescue
|
125
|
+
puts "invalid row index - please specify a number between 1 and #{row_count}"
|
126
|
+
return
|
127
|
+
end
|
128
|
+
puts "displaying details about row \# #{row_index}"
|
129
|
+
@last_response.data[row_index - 1].each do |k,v|
|
130
|
+
puts " #{k}\t#{v}"
|
131
|
+
end
|
132
|
+
|
133
|
+
}
|
134
|
+
).add_param(RHCP::CommandParam.new("row_index", "the index of the row you want to see details about",
|
135
|
+
{
|
136
|
+
:is_default_param => true,
|
137
|
+
:mandatory => true
|
138
|
+
}
|
139
|
+
)
|
140
|
+
)
|
141
|
+
command.result_hints[:display_type] = "hidden"
|
142
|
+
broker.register_command command
|
143
|
+
|
144
|
+
rescue RHCP::RhcpException => ex
|
145
|
+
# TODO do we really want to catch this here?
|
146
|
+
raise ex unless /duplicate command name/ =~ ex.to_s
|
147
|
+
end
|
148
|
+
broker
|
149
|
+
end
|
150
|
+
|
151
|
+
def reset_to_command_mode
|
152
|
+
set_prompt nil
|
153
|
+
|
154
|
+
# this shell has two modes that determine the available tab completion proposals
|
155
|
+
# command_mode
|
156
|
+
# we're waiting for the user to pick a command that should be executed
|
157
|
+
# parameter mode
|
158
|
+
# the command to execute has already been selected, but the user needs to specify additional parameters
|
159
|
+
# we'll start in the mode where no command has been selected yet
|
160
|
+
@command_selected = nil
|
161
|
+
|
162
|
+
# if the user selected a command already, we'll have to collect parameters for this command until
|
163
|
+
# we've got all mandatory parameters so that we can execute the command
|
164
|
+
@collected_params = Hash.new
|
165
|
+
|
166
|
+
# the mandatory params that are still missing (valid in parameter mode only)
|
167
|
+
@missing_params = Array.new
|
168
|
+
|
169
|
+
# the parameter that we're asking for right now
|
170
|
+
@current_param = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def execute_command_if_possible
|
174
|
+
# check if we got all mandatory params now
|
175
|
+
mandatory_params = @command_selected.params.select { |name,param| param.mandatory }.map { |k,v| v }
|
176
|
+
@missing_params = mandatory_params.select { |p| ! @collected_params.include? p.name }
|
177
|
+
|
178
|
+
if (@missing_params.size > 0) then
|
179
|
+
$logger.debug "got #{@missing_params.size} missing params : #{@missing_params.map{|param| param.name}}"
|
180
|
+
@current_param = @missing_params[0]
|
181
|
+
set_prompt "#{@command_selected.name}.#{@current_param.name}"
|
182
|
+
else
|
183
|
+
execute_command
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def pre_process_param_value(new_value)
|
189
|
+
$logger.debug "resolving wildcards for param '#{@current_param.name}'"
|
190
|
+
|
191
|
+
# we can only resolve wildcards if we have lookup values
|
192
|
+
if (@current_param.has_lookup_values)
|
193
|
+
# TODO this is only necessary if we've got multiple values, right?
|
194
|
+
# TODO maybe we want to check if 'new_value' holds suspicious characters that necessitate wildcard resolution?
|
195
|
+
# TODO is the range handling possible only with lookup values?
|
196
|
+
# convert "*" into regexp notation ".*"
|
197
|
+
regex_str = new_value.gsub(/\*/, '.*')
|
198
|
+
|
199
|
+
# handle ranges (x..y)
|
200
|
+
result = /(.+?)(\d+)(\.{2})(\d+)(.*)/.match(regex_str)
|
201
|
+
ranged_regex = nil
|
202
|
+
if result then
|
203
|
+
$logger.debug "captures : #{result.captures.map { |v| " #{v} "}}"
|
204
|
+
result.captures[1].upto(result.captures[3]) do |loop|
|
205
|
+
regex_for_this_number = "#{result.captures[0]}#{loop}#{result.captures[4]}"
|
206
|
+
$logger.debug "regex for #{loop} : #{regex_for_this_number}"
|
207
|
+
if ranged_regex == nil then
|
208
|
+
ranged_regex = Regexp.new(regex_for_this_number)
|
209
|
+
else
|
210
|
+
ranged_regex = Regexp.union(ranged_regex, Regexp.new(regex_for_this_number))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
else
|
214
|
+
ranged_regex = Regexp.new(regex_str)
|
215
|
+
end
|
216
|
+
|
217
|
+
$logger.debug "wildcard regexp : #{ranged_regex}"
|
218
|
+
|
219
|
+
re = ranged_regex
|
220
|
+
|
221
|
+
# get lookup values, filter and return them
|
222
|
+
lookup_values = @current_param.get_lookup_values()
|
223
|
+
lookup_values.select { |lookup_value| re.match(lookup_value) }
|
224
|
+
else
|
225
|
+
[ new_value ]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# checks param values for validity and adds them to our value collection
|
230
|
+
# expands wildcard parameters if appropriate
|
231
|
+
# returns the values that have been added (might be more than 'new_value' when wildcards are used)
|
232
|
+
def add_parameter_value(new_value)
|
233
|
+
# pre-process the value if necessary
|
234
|
+
processed_param_values = pre_process_param_value(new_value)
|
235
|
+
# TODO this check is already part of check_param_is_valid (which is called later in this method and when the request is created) - we do not want to check this three times...?
|
236
|
+
if processed_param_values.size == 0
|
237
|
+
raise RHCP::RhcpException.new("invalid value '#{new_value}' for parameter '#{@current_param.name}'")
|
238
|
+
end
|
239
|
+
#processed_param_values = [ new_value ]
|
240
|
+
processed_param_values.each do |value|
|
241
|
+
# TODO we need to pass a value array here, and we should include the already selected param values
|
242
|
+
@current_param.check_param_is_valid([ value ])
|
243
|
+
$logger.debug "accepted value #{value} for param #{@current_param.name}"
|
244
|
+
|
245
|
+
@collected_params[@current_param.name] = Array.new if @collected_params[@current_param.name] == nil
|
246
|
+
@collected_params[@current_param.name] << value
|
247
|
+
end
|
248
|
+
processed_param_values
|
249
|
+
end
|
250
|
+
|
251
|
+
def print_cell(col_name, the_value)
|
252
|
+
result = "| "
|
253
|
+
result += the_value.to_s
|
254
|
+
1.upto(@max_width[col_name] - the_value.to_s.length) { |i|
|
255
|
+
result += " "
|
256
|
+
}
|
257
|
+
result += " "
|
258
|
+
result
|
259
|
+
end
|
260
|
+
|
261
|
+
def print_line
|
262
|
+
result = ""
|
263
|
+
@total_width.times { |i| result += "-" }
|
264
|
+
result += "\n"
|
265
|
+
result
|
266
|
+
end
|
267
|
+
|
268
|
+
def execute_command
|
269
|
+
begin
|
270
|
+
command = @command_broker.get_command(@command_selected.name)
|
271
|
+
request = RHCP::Request.new(command, @collected_params)
|
272
|
+
response = command.execute_request(request)
|
273
|
+
if (response.status == RHCP::Response::Status::OK)
|
274
|
+
$logger.debug "raw result : #{response.data}"
|
275
|
+
$logger.debug "display_type : #{command.result_hints[:display_type]}"
|
276
|
+
if command.result_hints[:display_type] == "table"
|
277
|
+
@last_response = response # we might want to access this response in further commands
|
278
|
+
# TODO make sure that the response really holds the correct data types and that we've got at least one column
|
279
|
+
# TODO check that all columns in overview_columns are valid
|
280
|
+
# TODO check that all columns in column_titles are valid and match overview_columns
|
281
|
+
output = ""
|
282
|
+
|
283
|
+
# let's find out which columns we want to display
|
284
|
+
$logger.debug "overview columns : #{command.result_hints[:overview_columns]}"
|
285
|
+
columns_to_display = command.result_hints.has_key?(:overview_columns) ?
|
286
|
+
command.result_hints[:overview_columns].clone() :
|
287
|
+
# by default, we'll display all columns, sorted alphabetically
|
288
|
+
columns_to_display = response.data[0].keys.sort
|
289
|
+
|
290
|
+
# and which titles they should have (default : column names)
|
291
|
+
column_title_list = command.result_hints.has_key?(:column_titles) ?
|
292
|
+
command.result_hints[:column_titles].clone() :
|
293
|
+
column_title_list = columns_to_display
|
294
|
+
|
295
|
+
# TODO the sorting column should be configurable
|
296
|
+
first_column = columns_to_display[0]
|
297
|
+
$logger.debug "sorting by #{first_column}"
|
298
|
+
response.data = response.data.sort { |a,b| a[first_column] <=> b[first_column] }
|
299
|
+
|
300
|
+
# add the index column
|
301
|
+
columns_to_display.unshift "__idx"
|
302
|
+
column_title_list.unshift "\#"
|
303
|
+
count = 1
|
304
|
+
response.data.each do |row|
|
305
|
+
row["__idx"] = count
|
306
|
+
count = count+1
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
column_titles = {}
|
311
|
+
0.upto(column_title_list.length - 1) do |i|
|
312
|
+
column_titles[columns_to_display[i]] = column_title_list[i]
|
313
|
+
end
|
314
|
+
$logger.debug "column title : #{column_titles}"
|
315
|
+
|
316
|
+
# find the maximum column width for each column
|
317
|
+
@max_width = {}
|
318
|
+
response.data.each do |row|
|
319
|
+
row.each do |k,v|
|
320
|
+
if ! @max_width.has_key?(k) || v.to_s.length > @max_width[k]
|
321
|
+
@max_width[k] = v.to_s.length
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# check the column_title
|
327
|
+
columns_to_display.each do |col_name|
|
328
|
+
if column_titles[col_name].length > @max_width[col_name]
|
329
|
+
@max_width[col_name] = column_titles[col_name].length
|
330
|
+
end
|
331
|
+
end
|
332
|
+
#@max_width["row_count"] = response.data.length.to_s.length
|
333
|
+
$logger.debug "max width : #{@max_width}"
|
334
|
+
|
335
|
+
# and build headers
|
336
|
+
@total_width = 2 + columns_to_display.length-1 # separators at front and end of table and between the values
|
337
|
+
columns_to_display.each do |col|
|
338
|
+
@total_width += @max_width[col] + 2 # each column has a space in front and behind the value
|
339
|
+
end
|
340
|
+
output += print_line
|
341
|
+
|
342
|
+
columns_to_display.each do |col|
|
343
|
+
output += print_cell(col, column_titles[col])
|
344
|
+
end
|
345
|
+
output += "|\n"
|
346
|
+
|
347
|
+
output += print_line
|
348
|
+
|
349
|
+
# print the table values
|
350
|
+
response.data.each do |row|
|
351
|
+
columns_to_display.each do |col|
|
352
|
+
output += print_cell(col, row[col])
|
353
|
+
end
|
354
|
+
output += "|\n"
|
355
|
+
end
|
356
|
+
output += print_line
|
357
|
+
|
358
|
+
puts output
|
359
|
+
elsif command.result_hints[:display_type] == "list"
|
360
|
+
output = ""
|
361
|
+
response.data.each do |row|
|
362
|
+
output += "#{row}\n"
|
363
|
+
end
|
364
|
+
puts output
|
365
|
+
elsif command.result_hints[:display_type] == "hidden"
|
366
|
+
$logger.debug "suppressing output due to display_type 'hidden'"
|
367
|
+
else
|
368
|
+
puts "executed '#{@command_selected.name}' successfully : #{response.data}"
|
369
|
+
end
|
370
|
+
else
|
371
|
+
puts "could not execute '#{@command_selected.name}' : #{response.error_text}"
|
372
|
+
$logger.error "#{response.error_text} : #{response.error_detail}"
|
373
|
+
end
|
374
|
+
reset_to_command_mode
|
375
|
+
rescue
|
376
|
+
puts "got an error : #{$!}"
|
377
|
+
raise
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
## the following methods are overridden from ShellBackend
|
382
|
+
|
383
|
+
def process_input(command_line)
|
384
|
+
$logger.debug "processing input '#{command_line}'"
|
385
|
+
|
386
|
+
if (@command_selected) then
|
387
|
+
# we're in parameter processing mode - so check which parameter
|
388
|
+
# we've got now and switch modes if necessary
|
389
|
+
|
390
|
+
# we might have been waiting for multiple param values - check if the user finished
|
391
|
+
# adding values by selecting an empty string as value
|
392
|
+
if (@current_param.allows_multiple_values and command_line == "") then
|
393
|
+
$logger.debug "finished multiple parameter input mode for param #{@current_param.name}"
|
394
|
+
@missing_params.shift
|
395
|
+
execute_command_if_possible
|
396
|
+
else
|
397
|
+
accepted_params = add_parameter_value(command_line)
|
398
|
+
if accepted_params
|
399
|
+
# stop asking for more values if
|
400
|
+
# a) the parameter does not allow more than one value
|
401
|
+
# b) the user entered a wildcard parameter that has been expanded to multiple values
|
402
|
+
if (! @current_param.allows_multiple_values or accepted_params.length > 1) then
|
403
|
+
$logger.debug "finished parameter input mode for param #{@current_param.name}"
|
404
|
+
@missing_params.shift
|
405
|
+
execute_command_if_possible
|
406
|
+
else
|
407
|
+
$logger.debug "param '#{@current_param.name}' expects multiple values...deferring mode switching"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
else
|
412
|
+
# we're waiting for the user to enter a command
|
413
|
+
# we might have a command with params
|
414
|
+
command, *params = command_line.split
|
415
|
+
$logger.debug "got command '#{command}' (params: #{params})"
|
416
|
+
|
417
|
+
# remember what the user specified so far
|
418
|
+
begin
|
419
|
+
@command_selected = @command_broker.get_command(command)
|
420
|
+
#if (@command_selected != nil) then
|
421
|
+
$logger.debug "command_selected: #{@command_selected}"
|
422
|
+
|
423
|
+
# apply the preset param values to this new command
|
424
|
+
# TODO reactivate parameter presets
|
425
|
+
# @command_broker.get_global_parameter_presets().each do |name, values|
|
426
|
+
# # check if the selected command needs this param
|
427
|
+
# @current_param = @command_broker.get_param(command, name)
|
428
|
+
# if @current_param and not @current_param.ignore_global_presets then
|
429
|
+
# $logger.debug "presetting global parameter '#{name}' to '#{values}'"
|
430
|
+
# values.each do |value|
|
431
|
+
# add_parameter_value(value)
|
432
|
+
# end
|
433
|
+
# end
|
434
|
+
# end
|
435
|
+
|
436
|
+
# process the params specified on the command line
|
437
|
+
if (params != nil) then
|
438
|
+
params.each do |param|
|
439
|
+
if param =~ /(.+?)=(.+)/ then
|
440
|
+
# --> named param
|
441
|
+
key = $1
|
442
|
+
value = $2
|
443
|
+
else
|
444
|
+
# TODO if there's only one param, we can always use this as default param (maybe do this in the command?)
|
445
|
+
# --> unnamed param
|
446
|
+
value = param
|
447
|
+
default_param = @command_selected.default_param
|
448
|
+
if default_param != nil then
|
449
|
+
key = default_param.name
|
450
|
+
$logger.debug "collecting value '#{value}' for default param '#{default_param.name}'"
|
451
|
+
else
|
452
|
+
$logger.info "ignoring param '#{value}' because there's no default param"
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
if key then
|
457
|
+
begin
|
458
|
+
@current_param = @command_selected.get_param(key)
|
459
|
+
add_parameter_value(value)
|
460
|
+
rescue RHCP::RhcpException => ex
|
461
|
+
puts ex.to_s
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
$logger.debug "selected command #{@command_selected.name}"
|
468
|
+
execute_command_if_possible
|
469
|
+
rescue RHCP::RhcpException => ex
|
470
|
+
puts "#{ex}"
|
471
|
+
end
|
472
|
+
end
|
473
|
+
rescue => err
|
474
|
+
$logger.error err
|
475
|
+
puts "exception raised: #{err.to_s}"
|
476
|
+
end
|
477
|
+
|
478
|
+
def complete(word = "")
|
479
|
+
$logger.debug "collection completion values for '#{word}'"
|
480
|
+
|
481
|
+
if (@command_selected) then
|
482
|
+
# TODO include partial_value here?
|
483
|
+
props = @command_selected.params[@current_param.name].get_lookup_values()
|
484
|
+
#props = @command_broker.get_lookup_values(@command_selected.name, @current_param.name)
|
485
|
+
else
|
486
|
+
props = @command_broker.get_command_list.values.map{|command| command.name}.sort
|
487
|
+
end
|
488
|
+
|
489
|
+
proposal_list = props.map { |p| "'#{p}'" }.join(" ")
|
490
|
+
$logger.debug "completion proposals: #{proposal_list}"
|
491
|
+
|
492
|
+
prefix = word
|
493
|
+
props.select{|name|name[0...(prefix.size)] == prefix}
|
494
|
+
end
|
495
|
+
|
496
|
+
def show_banner
|
497
|
+
puts @banner
|
498
|
+
end
|
499
|
+
|
500
|
+
def set_prompt(string)
|
501
|
+
@prompt = "#{string ? string + " " : ""}$ "
|
502
|
+
end
|
503
|
+
|
504
|
+
def process_ctrl_c
|
505
|
+
puts ""
|
506
|
+
if @command_selected then
|
507
|
+
reset_to_command_mode
|
508
|
+
else
|
509
|
+
Kernel.exit
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# responsible for what should happen when the user interacts with the shell
|
2
|
+
# provides tab completion options and processes the user's input
|
3
|
+
class ShellBackend
|
4
|
+
|
5
|
+
# is called whenever the user submits a command (hitting enter)
|
6
|
+
def process_input(command_line)
|
7
|
+
raise "not implemented in ShellBackend!"
|
8
|
+
end
|
9
|
+
|
10
|
+
# is called whenever the user requests tab completion
|
11
|
+
# should return an array of completion proposals
|
12
|
+
def complete(word)
|
13
|
+
raise "not implemented in ShellBackend!"
|
14
|
+
end
|
15
|
+
|
16
|
+
# is called by the shell to get the prompt that should be displayed
|
17
|
+
def prompt
|
18
|
+
">"
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_ctrl_c
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def show_banner
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/start_shell.sh
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
#
|
2
|
+
# To change this template, choose Tools | Templates
|
3
|
+
# and open the template in the editor.
|
4
|
+
|
5
|
+
|
6
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'rhcp_shell_backend'
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'rhcp'
|
13
|
+
require 'logger'
|
14
|
+
|
15
|
+
require 'setup_test_registry'
|
16
|
+
|
17
|
+
class RhcpShellBackendTest < Test::Unit::TestCase
|
18
|
+
|
19
|
+
# Backend that "remembers" everything it ever printed to the user
|
20
|
+
class BackendMock < RHCPShellBackend
|
21
|
+
|
22
|
+
def initialize(broker, on_receive)
|
23
|
+
super(broker)
|
24
|
+
@on_receive = on_receive
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_input(line)
|
28
|
+
$stdout.puts "[user] #{line}"
|
29
|
+
super(line)
|
30
|
+
end
|
31
|
+
|
32
|
+
def puts(what)
|
33
|
+
$stdout.puts "[shell] #{what}"
|
34
|
+
@on_receive.call(what)
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_prompt(new_prompt)
|
38
|
+
$stdout.puts "[shell: switching prompt to '#{new_prompt}']"
|
39
|
+
super(new_prompt)
|
40
|
+
end
|
41
|
+
|
42
|
+
def complete(word = "")
|
43
|
+
result = super(word)
|
44
|
+
proposal_list = result.map { |p| "'#{p}'" }.join(" ")
|
45
|
+
$stdout.puts "[completion proposals : #{proposal_list}]"
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def setup
|
52
|
+
# set up a local broker that we'll use for testing
|
53
|
+
# TODO do something about this - it shouldn't be necessary to instantiate this beforehand
|
54
|
+
$logger = Logger.new($stdout)
|
55
|
+
@log = Array.new()
|
56
|
+
@backend = BackendMock.new($broker, self.method(:add_to_log))
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_to_log(new_line)
|
60
|
+
#puts "adding to log (old size : #{@log.size}): *****#{new_line}*****"
|
61
|
+
@log << new_line
|
62
|
+
# TODO CAREFUL: if you un-comment the following line, @log will get screwed up
|
63
|
+
#puts "log now : >>#{@log.join("\n")}<<"
|
64
|
+
end
|
65
|
+
|
66
|
+
def assert_received(expected)
|
67
|
+
assert_equal(expected, @log.slice(- expected.length, expected.length))
|
68
|
+
# clean the log so that assert_no_error works
|
69
|
+
#@log.clear
|
70
|
+
end
|
71
|
+
|
72
|
+
def assert_no_error
|
73
|
+
assert_equal 0, @log.select { |line|
|
74
|
+
/error/i =~ line || /exception/i =~ line || /failed/i =~ line
|
75
|
+
}.size
|
76
|
+
end
|
77
|
+
|
78
|
+
def assert_log_contains(stuff)
|
79
|
+
is_ok = @log.grep(/#{stuff}/).size > 0
|
80
|
+
puts "checking log for #{stuff}"
|
81
|
+
assert is_ok, "log should contain '#{stuff}', but it doesn't : >>#{@log.join("\n")}<<"
|
82
|
+
end
|
83
|
+
|
84
|
+
def assert_prompt(expected)
|
85
|
+
if (expected == '')
|
86
|
+
assert_equal "$ ", @backend.prompt
|
87
|
+
else
|
88
|
+
assert_equal "#{expected} $ ", @backend.prompt
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_banner
|
93
|
+
@backend.banner = "This is a test backend"
|
94
|
+
@backend.show_banner
|
95
|
+
assert_received [ "This is a test backend" ]
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_simple_execute
|
99
|
+
@backend.process_input "test"
|
100
|
+
assert_received [ "executed 'test' successfully : 42" ]
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_invalid_command
|
104
|
+
@backend.process_input "does not exist"
|
105
|
+
assert_received [ "no such command : does" ]
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_missing_mandatory_param
|
109
|
+
@backend.process_input "reverse"
|
110
|
+
assert_prompt 'reverse.input'
|
111
|
+
@backend.process_input "zaphod"
|
112
|
+
assert_received [ "executed 'reverse' successfully : dohpaz" ]
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_completion
|
116
|
+
@backend.process_input "reverse"
|
117
|
+
assert_prompt 'reverse.input'
|
118
|
+
assert_equal [ "zaphod", "beeblebrox" ], @backend.complete
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_command_completion
|
122
|
+
commands = $broker.get_command_list.values.map { |command| command.name }
|
123
|
+
# we should have all remote commands plus "help" and "exit"
|
124
|
+
commands << "help"
|
125
|
+
commands << "exit"
|
126
|
+
commands << "detail"
|
127
|
+
assert_equal commands.sort, @backend.complete.sort
|
128
|
+
assert_no_error
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_params_on_command_line
|
132
|
+
@backend.process_input "reverse input=zaphod"
|
133
|
+
assert_received [ "executed 'reverse' successfully : dohpaz" ]
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_invalid_param_value
|
137
|
+
@backend.process_input "reverse input=bla"
|
138
|
+
assert_received [ "invalid value 'bla' for parameter 'input'" ]
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_multi_params
|
142
|
+
@backend.process_input "cook"
|
143
|
+
assert_prompt 'cook.ingredient'
|
144
|
+
@backend.process_input "mascarpone"
|
145
|
+
@backend.process_input "chocolate"
|
146
|
+
@backend.process_input ""
|
147
|
+
assert_no_error
|
148
|
+
assert_received [ "executed 'cook' successfully : mascarpone chocolate" ]
|
149
|
+
end
|
150
|
+
|
151
|
+
# if the user is in command mode, he should be able to exit to command mode
|
152
|
+
# by pressing ctrl+c
|
153
|
+
def test_abort_param_mode
|
154
|
+
@backend.process_input "cook"
|
155
|
+
assert_prompt 'cook.ingredient'
|
156
|
+
@backend.process_ctrl_c
|
157
|
+
assert_prompt ''
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_failing_command
|
161
|
+
@backend.process_input "perpetuum_mobile"
|
162
|
+
assert_received [ "could not execute 'perpetuum_mobile' : don't know how to do this" ]
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_wildcard_support
|
166
|
+
@backend.process_input "cook ingredient=m*"
|
167
|
+
assert_received [ "executed 'cook' successfully : mascarpone marzipan" ]
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_preprocess_without_lookup_values
|
171
|
+
@backend.process_input "test thoroughly=yes"
|
172
|
+
assert_no_error
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_wildcard_ranges
|
176
|
+
@backend.process_input "echo input=string01..05"
|
177
|
+
assert_received [ "executed 'echo' successfully : string01 string02 string03 string04 string05" ]
|
178
|
+
|
179
|
+
@backend.process_input "echo input=string17..20"
|
180
|
+
assert_received [ "executed 'echo' successfully : string17 string18 string19 string20" ]
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_help
|
184
|
+
@backend.process_input "help"
|
185
|
+
assert_log_contains "The following commands are available"
|
186
|
+
$broker.get_command_list.values.each do |command|
|
187
|
+
assert_log_contains command.name
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_help_command
|
192
|
+
@log.clear
|
193
|
+
@backend.process_input "help cook"
|
194
|
+
puts "LOG >>#{@log}<<"
|
195
|
+
assert_log_contains "Syntax:"
|
196
|
+
assert_log_contains "cook ingredient=<value>, ingredient=<value2>, ..."
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_help_with_default_param
|
200
|
+
@backend.process_input "help help"
|
201
|
+
puts "LOG >>#{@log}<<"
|
202
|
+
assert_log_contains "Syntax:"
|
203
|
+
assert_log_contains "help [<command>]"
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_default_param
|
207
|
+
@backend.process_input "reverse zaphod"
|
208
|
+
assert_received [ "executed 'reverse' successfully : dohpaz" ]
|
209
|
+
end
|
210
|
+
|
211
|
+
# unnamed params should be ignored if no default params are specified
|
212
|
+
def test_unnamed_param_without_default_param
|
213
|
+
@backend.process_input "echo bla"
|
214
|
+
assert_received [ "executed 'echo' successfully : hello world" ]
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_complete_without_lookup_values
|
218
|
+
@backend.process_input "length"
|
219
|
+
assert_prompt "length.input"
|
220
|
+
assert_equal [], @backend.complete
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_table
|
224
|
+
# TODO write a separate test for this stuff
|
225
|
+
p $broker.get_command("build_a_table")
|
226
|
+
@backend.process_input "build_a_table"
|
227
|
+
# assert_received [
|
228
|
+
# "Zaphod\tBeeblebrox",
|
229
|
+
# "Arthur\tDent",
|
230
|
+
# "Prostetnik\tYoltz(?)"
|
231
|
+
# ]
|
232
|
+
end
|
233
|
+
|
234
|
+
# TODO test behaviour after an internal error occurred in the shell (e.g. some problem while formatting the result)
|
235
|
+
|
236
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rhcp'
|
3
|
+
|
4
|
+
|
5
|
+
broker = RHCP::Broker.new()
|
6
|
+
broker.clear()
|
7
|
+
broker.register_command RHCP::Command.new("test", "just a test command",
|
8
|
+
lambda { |req,res|
|
9
|
+
42
|
10
|
+
}
|
11
|
+
).add_param(RHCP::CommandParam.new("thoroughly", "an optional param",
|
12
|
+
{
|
13
|
+
:mandatory => false
|
14
|
+
}
|
15
|
+
)
|
16
|
+
)
|
17
|
+
broker.register_command RHCP::Command.new("echo", "prints a string",
|
18
|
+
lambda { |req,res|
|
19
|
+
strings = req.has_param_value("input") ? req.get_param_value("input") : [ "hello world" ]
|
20
|
+
result = Array.new
|
21
|
+
strings.each do |s|
|
22
|
+
puts s
|
23
|
+
result << s
|
24
|
+
end
|
25
|
+
result.join(" ")
|
26
|
+
}
|
27
|
+
).add_param(RHCP::CommandParam.new("input", "an optional param",
|
28
|
+
{
|
29
|
+
:mandatory => false,
|
30
|
+
:allows_multiple_values => true,
|
31
|
+
:lookup_method => lambda {
|
32
|
+
values = Array.new()
|
33
|
+
1.upto(20) do |i|
|
34
|
+
values << "string#{sprintf("%02d", i)}"
|
35
|
+
end
|
36
|
+
values
|
37
|
+
}
|
38
|
+
}
|
39
|
+
)
|
40
|
+
)
|
41
|
+
broker.register_command RHCP::Command.new("reverse", "reversing input strings",
|
42
|
+
lambda { |req,res|
|
43
|
+
req.get_param_value("input").reverse
|
44
|
+
}
|
45
|
+
).add_param(RHCP::CommandParam.new("input", "the string to reverse",
|
46
|
+
{
|
47
|
+
:lookup_method => lambda { [ "zaphod", "beeblebrox" ] },
|
48
|
+
:mandatory => true,
|
49
|
+
:is_default_param => true
|
50
|
+
}
|
51
|
+
)
|
52
|
+
)
|
53
|
+
broker.register_command RHCP::Command.new("cook", "cook something nice out of some ingredients",
|
54
|
+
lambda { |req,res|
|
55
|
+
ingredients = req.get_param_value("ingredient").join(" ")
|
56
|
+
puts "cooking something with #{ingredients}"
|
57
|
+
ingredients
|
58
|
+
}
|
59
|
+
).add_param(RHCP::CommandParam.new("ingredient", "something to cook with",
|
60
|
+
{
|
61
|
+
:lookup_method => lambda { [ "mascarpone", "chocolate", "eggs", "butter", "marzipan" ] },
|
62
|
+
:allows_multiple_values => true,
|
63
|
+
:mandatory => true
|
64
|
+
}
|
65
|
+
)
|
66
|
+
)
|
67
|
+
broker.register_command RHCP::Command.new("perpetuum_mobile", "this command will fail",
|
68
|
+
lambda { |req,res|
|
69
|
+
raise "don't know how to do this"
|
70
|
+
}
|
71
|
+
)
|
72
|
+
broker.register_command RHCP::Command.new("length", "returns the length of a string",
|
73
|
+
lambda { |req,res|
|
74
|
+
req.get_param_value("input").length
|
75
|
+
}
|
76
|
+
).add_param(RHCP::CommandParam.new("input", "the string to reverse",
|
77
|
+
{
|
78
|
+
:mandatory => true,
|
79
|
+
:is_default_param => true
|
80
|
+
}
|
81
|
+
)
|
82
|
+
)
|
83
|
+
|
84
|
+
command = RHCP::Command.new("list_stuff", "this command lists stuff",
|
85
|
+
lambda { |req,res|
|
86
|
+
[ "peace", "aquaeduct", "education" ]
|
87
|
+
}
|
88
|
+
)
|
89
|
+
command.mark_as_read_only()
|
90
|
+
command.result_hints[:display_type] = "list"
|
91
|
+
broker.register_command command
|
92
|
+
|
93
|
+
command2 = RHCP::Command.new("build_a_table", "this command returns tabular data",
|
94
|
+
lambda { |req,res|
|
95
|
+
[
|
96
|
+
{ :first_name => "Zaphod", :last_name => "Beeblebrox", :heads => 2, :character => "dangerous" },
|
97
|
+
{ :first_name => "Arthur", :last_name => "Dent", :heads => 1, :character => "harmless (mostly)" },
|
98
|
+
{ :first_name => "Prostetnik", :last_name => "Yoltz (?)", :heads => 1, :character => "ugly" }
|
99
|
+
]
|
100
|
+
}
|
101
|
+
)
|
102
|
+
command2.mark_as_read_only()
|
103
|
+
command2.result_hints[:display_type] = "table"
|
104
|
+
command2.result_hints[:overview_columns] = [ "first_name", "last_name" ]
|
105
|
+
broker.register_command command2
|
106
|
+
|
107
|
+
p broker.get_command("build_a_table")
|
108
|
+
|
109
|
+
$broker = broker
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rhcp_shell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philipp T.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-23 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.3
|
24
|
+
version:
|
25
|
+
description: FIX (describe your package)
|
26
|
+
email:
|
27
|
+
- philipp@hitchhackers.net
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- History.txt
|
34
|
+
- Manifest.txt
|
35
|
+
- README.txt
|
36
|
+
files:
|
37
|
+
- History.txt
|
38
|
+
- Manifest.txt
|
39
|
+
- README.txt
|
40
|
+
- Rakefile
|
41
|
+
- README
|
42
|
+
- lib/base_shell.rb
|
43
|
+
- lib/rhcp_shell.rb
|
44
|
+
- lib/rhcp_shell_backend.rb
|
45
|
+
- lib/shell_backend.rb
|
46
|
+
- start_shell.sh
|
47
|
+
- test/rhcp_shell_backend_test.rb
|
48
|
+
- test/setup_test_registry.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: FIX (url)
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --main
|
54
|
+
- README.txt
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project: rhcp
|
72
|
+
rubygems_version: 1.3.1
|
73
|
+
signing_key:
|
74
|
+
specification_version: 2
|
75
|
+
summary: FIX (describe your package)
|
76
|
+
test_files: []
|
77
|
+
|