menutree 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -1
- data/README.md +11 -13
- data/Rakefile +0 -17
- data/examples/commands/leaf.rb +11 -0
- data/examples/commands/submenu_one/leaf.rb +11 -0
- data/examples/commands/submenu_two/leaf.rb +11 -0
- data/examples/example.rb +6 -0
- data/lib/menutree.rb +3 -3
- data/lib/menutree/help.rb +26 -0
- data/lib/menutree/leaf.rb +23 -0
- data/lib/menutree/tree.rb +73 -0
- data/menutree.gemspec +12 -28
- metadata +23 -13
- data/VERSION +0 -1
- data/lib/base.rb +0 -86
data/.gitignore
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
*.gem
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Menutree
|
2
2
|
|
3
|
-
Menutree is a framework for presenting a recursive
|
3
|
+
Menutree is a framework for presenting a recursive Read-Eval-Print Loop (REPL) shell on the command
|
4
4
|
line, inspired by the CLI found in Cisco IOS and other similar products.
|
5
5
|
Menutree lets users issue commands via an interactive nested shell interface,
|
6
6
|
and also directly from the command line.
|
@@ -11,26 +11,24 @@ readline support are all built in.
|
|
11
11
|
|
12
12
|
## Example:
|
13
13
|
|
14
|
-
Given a hypothetical program '
|
15
|
-
tickets, a sample interaction might look like:
|
14
|
+
Given a hypothetical program 'ticket' that uses the Menutree gem to manage a list of
|
15
|
+
tickets (such a program is included in `examples/`), a sample interaction might look like:
|
16
16
|
|
17
17
|
<pre>
|
18
|
-
$
|
19
|
-
>
|
20
|
-
|
21
|
-
>
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
Ticket 213 ("Do a little dance") removed
|
26
|
-
rm> ..
|
18
|
+
$ ruby ticket.rb
|
19
|
+
> action_one
|
20
|
+
One
|
21
|
+
> submenu_one
|
22
|
+
/submenu_one> action_one
|
23
|
+
Unknown command 'action_one'
|
24
|
+
/submenu_one> ..
|
27
25
|
> exit
|
28
26
|
</pre>
|
29
27
|
|
30
28
|
Commands could also be run from the shell:
|
31
29
|
|
32
30
|
<pre>
|
33
|
-
$
|
31
|
+
$ rubyticke add "Make a little love"
|
34
32
|
Ticket "Make a little love" added as ticket 214
|
35
33
|
</pre>
|
36
34
|
|
data/Rakefile
CHANGED
@@ -2,23 +2,6 @@ require 'rake'
|
|
2
2
|
require 'rake/testtask'
|
3
3
|
require 'rcov/rcovtask'
|
4
4
|
|
5
|
-
begin
|
6
|
-
require 'jeweler'
|
7
|
-
Jeweler::Tasks.new do |s|
|
8
|
-
s.name = "menutree"
|
9
|
-
s.summary = "a simple hierachical command line shell"
|
10
|
-
s.description = "Menutree is a framework for presenting a recursive menu shell on the command
|
11
|
-
line, inspired by the CLI found in Cisco IOS and similar products"
|
12
|
-
s.email = "mat@geeky.net"
|
13
|
-
s.homepage = "http://github.com/mtrudel/menutree"
|
14
|
-
s.authors = ["Mat Trudel", "Grant McInnes"]
|
15
|
-
s.add_dependency 'activesupport'
|
16
|
-
end
|
17
|
-
Jeweler::GemcutterTasks.new
|
18
|
-
rescue LoadError
|
19
|
-
puts "Jeweler not available. Install it with: gem install jeweler"
|
20
|
-
end
|
21
|
-
|
22
5
|
Rake::TestTask.new do |t|
|
23
6
|
t.libs << 'lib'
|
24
7
|
t.pattern = 'test/**/*_test.rb'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
leaf do |tree|
|
2
|
+
desc "This is action one's help"
|
3
|
+
def action_one(*args)
|
4
|
+
puts "This is action one, called with: #{args.join ' '}"
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "This is action two's help"
|
8
|
+
def action_two(*args)
|
9
|
+
puts "This is action two, called with: #{args.join ' '}"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
leaf do |tree|
|
2
|
+
desc "This is action one's help inside submenu one"
|
3
|
+
def action_one(*args)
|
4
|
+
puts "This is action one in submenu one, called with: #{args.join ' '}"
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "This is action two's help inside submenu one"
|
8
|
+
def action_two(*args)
|
9
|
+
puts "This is action two in submenu one, called with: #{args.join ' '}"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
leaf do |tree|
|
2
|
+
desc "This is action one's help inside submenu two"
|
3
|
+
def action_one(*args)
|
4
|
+
puts "This is action one in submenu two, called with: #{args.join ' '}"
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "This is action two's help inside submenu one"
|
8
|
+
def action_two(*args)
|
9
|
+
puts "This is action two in submenu two, called with: #{args.join ' '}"
|
10
|
+
end
|
11
|
+
end
|
data/examples/example.rb
ADDED
data/lib/menutree.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
-
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'menutree')) unless
|
2
|
+
$:.include?(File.join(File.dirname(__FILE__), 'menutree')) || $:.include?(File.expand_path(File.join(File.dirname(__FILE__), 'menutree')))
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'tree'
|
5
5
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MenuTree
|
2
|
+
INDENT = "\t"
|
3
|
+
COMMAND_WIDTH = -12
|
4
|
+
BUILTINS = {
|
5
|
+
"help" => "Displays this message",
|
6
|
+
"quit" => "Quits this session",
|
7
|
+
".." => "Goes up a level in the menu (or quits if at the root)",
|
8
|
+
}
|
9
|
+
|
10
|
+
module Help
|
11
|
+
def display_help
|
12
|
+
display_help_section("Built-in commands", BUILTINS)
|
13
|
+
display_help_section("Leaf commands", @leaf.commands)
|
14
|
+
display_help_section("Submenus", submenus)
|
15
|
+
end
|
16
|
+
|
17
|
+
def display_help_section(section, contents, indent = 0)
|
18
|
+
puts INDENT*indent + section
|
19
|
+
contents.each do |command, description|
|
20
|
+
puts INDENT*(indent+1) + sprintf("%#{COMMAND_WIDTH}s %s", command, description)
|
21
|
+
end
|
22
|
+
puts ""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MenuTree
|
2
|
+
class Leaf
|
3
|
+
attr_accessor :commands
|
4
|
+
|
5
|
+
def initialize(parent_tree)
|
6
|
+
@parent_tree = parent_tree
|
7
|
+
@commands = {}
|
8
|
+
@curent_description = ""
|
9
|
+
end
|
10
|
+
|
11
|
+
def desc(desc)
|
12
|
+
@current_description = desc
|
13
|
+
end
|
14
|
+
|
15
|
+
def singleton_method_added(symbol)
|
16
|
+
@commands[symbol] = @current_description
|
17
|
+
end
|
18
|
+
|
19
|
+
def leaf(&block)
|
20
|
+
self.instance_exec(@parent_tree, &block) if block_given?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'active_support'
|
3
|
+
require 'leaf'
|
4
|
+
require 'help'
|
5
|
+
|
6
|
+
module MenuTree
|
7
|
+
class Tree
|
8
|
+
include Help
|
9
|
+
attr_accessor :directory
|
10
|
+
attr_accessor :prompt
|
11
|
+
attr_accessor :leaf
|
12
|
+
|
13
|
+
def initialize(directory, parent = "")
|
14
|
+
@directory = directory
|
15
|
+
if parent.is_a? String
|
16
|
+
@prompt = parent
|
17
|
+
elsif !parent.prompt.empty?
|
18
|
+
@prompt = parent.prompt + "/" + File.basename(@directory)
|
19
|
+
else
|
20
|
+
@prompt = File.basename(@directory)
|
21
|
+
end
|
22
|
+
|
23
|
+
file = File.join(@directory, "leaf.rb")
|
24
|
+
@leaf = MenuTree::Leaf.new(self)
|
25
|
+
@leaf.instance_eval(File.read(file), file) if File.exists? file
|
26
|
+
end
|
27
|
+
|
28
|
+
def repl(cmds)
|
29
|
+
one_shot = true unless cmds.empty?
|
30
|
+
to_run = cmds.shift
|
31
|
+
while true do
|
32
|
+
Readline.completion_append_character = " "
|
33
|
+
Readline.completion_proc = Proc.new do |str|
|
34
|
+
commands(str)
|
35
|
+
end
|
36
|
+
if (to_run.nil?)
|
37
|
+
cmds = Readline.readline("#{@prompt}> ", true).split
|
38
|
+
to_run = cmds.shift
|
39
|
+
end
|
40
|
+
|
41
|
+
# If it's a builtin, run it
|
42
|
+
if (to_run.nil?)
|
43
|
+
next
|
44
|
+
elsif (to_run == "help" or to_run == "?")
|
45
|
+
display_help
|
46
|
+
elsif (to_run == "quit")
|
47
|
+
exit
|
48
|
+
elsif (to_run == "..")
|
49
|
+
return
|
50
|
+
elsif File.directory?(File.join(@directory, to_run))
|
51
|
+
menu = MenuTree::Tree.new(File.join(@directory, to_run), self)
|
52
|
+
menu.repl(cmds)
|
53
|
+
else
|
54
|
+
@leaf.send to_run, cmds rescue puts "Unknown command #{to_run}"
|
55
|
+
end
|
56
|
+
to_run = nil
|
57
|
+
return if one_shot
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def commands(prefix='')
|
62
|
+
(leaf_commands + submenus).select { |command| command =~ /^#{Regexp.escape(prefix)}/ }
|
63
|
+
end
|
64
|
+
|
65
|
+
def leaf_commands
|
66
|
+
@leaf.commands.keys.map { |x| x.to_s }
|
67
|
+
end
|
68
|
+
|
69
|
+
def submenus
|
70
|
+
Dir.new(@directory).select { |entry| entry =~ /^[^\.]/ and File.directory?(File.join(@directory, entry)) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/menutree.gemspec
CHANGED
@@ -1,17 +1,10 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
1
|
Gem::Specification.new do |s|
|
7
2
|
s.name = %q{menutree}
|
8
|
-
s.version = "0.
|
3
|
+
s.version = "0.1.1"
|
9
4
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
5
|
s.authors = ["Mat Trudel", "Grant McInnes"]
|
12
|
-
s.date = %q{
|
13
|
-
s.description = %q{Menutree is a framework for presenting a recursive
|
14
|
-
line, inspired by the CLI found in Cisco IOS and similar products}
|
6
|
+
s.date = %q{2011-01-05}
|
7
|
+
s.description = %q{Menutree is a framework for presenting a recursive REPL shell, inspired by the CLI found in Cisco IOS and similar products}
|
15
8
|
s.email = %q{mat@geeky.net}
|
16
9
|
s.extra_rdoc_files = [
|
17
10
|
"README.md"
|
@@ -20,28 +13,19 @@ Gem::Specification.new do |s|
|
|
20
13
|
".gitignore",
|
21
14
|
"README.md",
|
22
15
|
"Rakefile",
|
23
|
-
"VERSION",
|
24
|
-
"lib/base.rb",
|
25
16
|
"lib/menutree.rb",
|
17
|
+
"lib/menutree/tree.rb",
|
18
|
+
"lib/menutree/leaf.rb",
|
19
|
+
"lib/menutree/help.rb",
|
20
|
+
"examples/example.rb",
|
21
|
+
"examples/commands/leaf.rb",
|
22
|
+
"examples/commands/submenu_one/leaf.rb",
|
23
|
+
"examples/commands/submenu_two/leaf.rb",
|
26
24
|
"menutree.gemspec"
|
27
25
|
]
|
28
26
|
s.homepage = %q{http://github.com/mtrudel/menutree}
|
29
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
30
27
|
s.require_paths = ["lib"]
|
31
|
-
s.
|
32
|
-
s.
|
33
|
-
|
34
|
-
if s.respond_to? :specification_version then
|
35
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
36
|
-
s.specification_version = 3
|
37
|
-
|
38
|
-
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
39
|
-
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
40
|
-
else
|
41
|
-
s.add_dependency(%q<activesupport>, [">= 0"])
|
42
|
-
end
|
43
|
-
else
|
44
|
-
s.add_dependency(%q<activesupport>, [">= 0"])
|
45
|
-
end
|
28
|
+
s.summary = %q{a simple hierachical REPL shell}
|
29
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
46
30
|
end
|
47
31
|
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: menutree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Mat Trudel
|
@@ -15,24 +16,24 @@ autorequire:
|
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date:
|
19
|
+
date: 2011-01-05 00:00:00 -05:00
|
19
20
|
default_executable:
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
22
23
|
name: activesupport
|
23
24
|
prerelease: false
|
24
25
|
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
25
27
|
requirements:
|
26
28
|
- - ">="
|
27
29
|
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
28
31
|
segments:
|
29
32
|
- 0
|
30
33
|
version: "0"
|
31
34
|
type: :runtime
|
32
35
|
version_requirements: *id001
|
33
|
-
description:
|
34
|
-
Menutree is a framework for presenting a recursive menu shell on the command
|
35
|
-
line, inspired by the CLI found in Cisco IOS and similar products
|
36
|
+
description: Menutree is a framework for presenting a recursive REPL shell, inspired by the CLI found in Cisco IOS and similar products
|
36
37
|
email: mat@geeky.net
|
37
38
|
executables: []
|
38
39
|
|
@@ -44,39 +45,48 @@ files:
|
|
44
45
|
- .gitignore
|
45
46
|
- README.md
|
46
47
|
- Rakefile
|
47
|
-
- VERSION
|
48
|
-
- lib/base.rb
|
49
48
|
- lib/menutree.rb
|
49
|
+
- lib/menutree/tree.rb
|
50
|
+
- lib/menutree/leaf.rb
|
51
|
+
- lib/menutree/help.rb
|
52
|
+
- examples/example.rb
|
53
|
+
- examples/commands/leaf.rb
|
54
|
+
- examples/commands/submenu_one/leaf.rb
|
55
|
+
- examples/commands/submenu_two/leaf.rb
|
50
56
|
- menutree.gemspec
|
51
57
|
has_rdoc: true
|
52
58
|
homepage: http://github.com/mtrudel/menutree
|
53
59
|
licenses: []
|
54
60
|
|
55
61
|
post_install_message:
|
56
|
-
rdoc_options:
|
57
|
-
|
62
|
+
rdoc_options: []
|
63
|
+
|
58
64
|
require_paths:
|
59
65
|
- lib
|
60
66
|
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
61
68
|
requirements:
|
62
69
|
- - ">="
|
63
70
|
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
64
72
|
segments:
|
65
73
|
- 0
|
66
74
|
version: "0"
|
67
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
68
77
|
requirements:
|
69
78
|
- - ">="
|
70
79
|
- !ruby/object:Gem::Version
|
80
|
+
hash: 3
|
71
81
|
segments:
|
72
82
|
- 0
|
73
83
|
version: "0"
|
74
84
|
requirements: []
|
75
85
|
|
76
86
|
rubyforge_project:
|
77
|
-
rubygems_version: 1.3.
|
87
|
+
rubygems_version: 1.3.7
|
78
88
|
signing_key:
|
79
89
|
specification_version: 3
|
80
|
-
summary: a simple hierachical
|
90
|
+
summary: a simple hierachical REPL shell
|
81
91
|
test_files: []
|
82
92
|
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.0.4
|
data/lib/base.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
require 'readline'
|
2
|
-
require 'active_support'
|
3
|
-
|
4
|
-
class MenuTree
|
5
|
-
attr_accessor :directory
|
6
|
-
attr_accessor :parent
|
7
|
-
attr_accessor :prompt
|
8
|
-
|
9
|
-
def initialize(directory, parent = "")
|
10
|
-
@directory = directory
|
11
|
-
@parent = parent
|
12
|
-
if @parent.is_a? self.class
|
13
|
-
@prompt = @parent.prompt + "/" + File.basename(@directory)
|
14
|
-
else
|
15
|
-
@prompt = @parent
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def run_command(cmds)
|
20
|
-
Readline.completion_append_character = " "
|
21
|
-
Readline.completion_proc = Proc.new do |str|
|
22
|
-
commands(str)
|
23
|
-
end
|
24
|
-
|
25
|
-
one_shot = true unless cmds.empty?
|
26
|
-
to_run = cmds.shift
|
27
|
-
while true do
|
28
|
-
if (to_run.nil?)
|
29
|
-
cmds = Readline.readline("#{@prompt}> ", true).split
|
30
|
-
to_run = cmds.shift
|
31
|
-
end
|
32
|
-
|
33
|
-
# If it's a builtin, run it
|
34
|
-
if (to_run.nil?)
|
35
|
-
next
|
36
|
-
elsif (to_run == "help" or to_run == "?")
|
37
|
-
display_help
|
38
|
-
elsif (to_run == "quit")
|
39
|
-
exit
|
40
|
-
elsif (to_run == "..")
|
41
|
-
return
|
42
|
-
elsif File.exists?(File.join(@directory, "#{to_run}.rb"))
|
43
|
-
# If we have a .rb file, run it with the rest of cmd as arguments
|
44
|
-
# TODO -- use .extend?
|
45
|
-
require File.join(@directory, "#{to_run}")
|
46
|
-
eval(to_run.camelize).new.run(cmds)
|
47
|
-
elsif File.directory?(File.join(@directory, to_run))
|
48
|
-
menu = MenuTree.new(File.join(@directory, to_run), self)
|
49
|
-
menu.run_command(cmds)
|
50
|
-
elsif File.exists?(File.join(@directory, "default.rb"))
|
51
|
-
# TODO let default handle it, allowing us to have proxy menus
|
52
|
-
else
|
53
|
-
puts "Unknown command #{to_run}"
|
54
|
-
end
|
55
|
-
to_run = nil
|
56
|
-
return if one_shot
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def commands(prefix='')
|
61
|
-
completions = []
|
62
|
-
Dir.new(@directory).each do |command|
|
63
|
-
if command =~ /^[^\.]/ and command =~ /^#{Regexp.escape(prefix)}/ and File.file?(File.join(@directory, command))
|
64
|
-
completions << File.basename(command, '.rb')
|
65
|
-
end
|
66
|
-
end
|
67
|
-
completions
|
68
|
-
end
|
69
|
-
|
70
|
-
def display_help
|
71
|
-
puts <<EOF
|
72
|
-
Builtin commands:
|
73
|
-
help:\t\t\t\tDisplays this message
|
74
|
-
quit:\t\t\t\tQuits this session
|
75
|
-
..: \t\t\t\tGoes up a level in the menu (or quits if at the root)
|
76
|
-
|
77
|
-
#{@parent} commands:
|
78
|
-
EOF
|
79
|
-
|
80
|
-
# TODO display some help for each submenu
|
81
|
-
commands.each do |cmd|
|
82
|
-
require File.join(@directory, "#{cmd}")
|
83
|
-
puts " #{cmd}:\t\t\t\t#{eval(cmd.camelize).new.description}"
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|