Permalink
Browse files

Major refactor. Sorry for the huge commit

  • Loading branch information...
1 parent 5b98901 commit 2ab2b1bcb4bd88cd42f58a0ac4f3a711786f0292 @mtrudel committed Jan 5, 2011
View
@@ -1,6 +1,6 @@
# Menutree
-Menutree is a framework for presenting a recursive menu shell on the command
+Menutree is a framework for presenting a recursive Read-Eval-Print Loop (REPL) shell on the command
line, inspired by the CLI found in Cisco IOS and other similar products.
Menutree lets users issue commands via an interactive nested shell interface,
and also directly from the command line.
@@ -11,39 +11,52 @@ readline support are all built in.
## Example:
-Given a hypothetical program 'ticker' that uses the Menutree gem to manage a list of
-tickets, a sample interaction might look like:
-
<pre>
-$ ticker
-> init
-Ticker created an empty ticket database
-> add "Do a little dance"
-Ticket "Do a little dance" added as ticket 213
-> rm
-rm> 213
-Ticket 213 ("Do a little dance") removed
-rm> ..
-> exit
+$ ./examples/example.rb
+> action_one a b c
+This is action one, called with: a b c
+> submenu_one
+submenu_one> action_one
+This is action one in submenu one, called with:
+submenu_one> ..
+> submenu_two
+submenu_two> action_one
+This is action one in submenu two, called with:
+submenu_two> quit
</pre>
Commands could also be run from the shell:
<pre>
-$ ticker add "Make a little love"
-Ticket "Make a little love" added as ticket 214
+$ ./examples/example.rb submenu_one action_two a b c
+This is action two in submenu one, called with: a b c
</pre>
-To implement this, ticker would provide Menutree with a directory that looked
+To implement this, one would provide Menutree with a directory that looked
like:
<pre>
commands/
- init.rb
- add/
- default.rb
- rm/
- default.rb
+ leaf.rb
+ submenu_one/
+ leaf.rb
+ submenu_two/
+ leaf.rb
</pre>
+Commands are defined inside a leaf.rb file in each directory, which adheres to
+a minimal DSL like so:
+<pre>
+leaf do |tree|
+ desc "This is action one's help"
+ def action_one(*args)
+ puts "This is action one, called with: #{args.join ' '}"
+ end
+
+ desc "This is action two's help"
+ def action_two(*args)
+ puts "This is action two, called with: #{args.join ' '}"
+ end
+end
+</pre>
View
@@ -0,0 +1,11 @@
+leaf do |tree|
+ desc "This is action one's help"
+ def action_one(*args)
+ puts "This is action one, called with: #{args.join ' '}"
+ end
+
+ desc "This is action two's help"
+ def action_two(*args)
+ puts "This is action two, called with: #{args.join ' '}"
+ end
+end
@@ -0,0 +1,11 @@
+leaf do |tree|
+ desc "This is action one's help inside submenu one"
+ def action_one(*args)
+ puts "This is action one in submenu one, called with: #{args.join ' '}"
+ end
+
+ desc "This is action two's help inside submenu one"
+ def action_two(*args)
+ puts "This is action two in submenu one, called with: #{args.join ' '}"
+ end
+end
@@ -0,0 +1,11 @@
+leaf do |tree|
+ desc "This is action one's help inside submenu two"
+ def action_one(*args)
+ puts "This is action one in submenu two, called with: #{args.join ' '}"
+ end
+
+ desc "This is action two's help inside submenu one"
+ def action_two(*args)
+ puts "This is action two in submenu two, called with: #{args.join ' '}"
+ end
+end
View
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'menutree'
+
+MenuTree::Tree.new(File.join(File.dirname(__FILE__), 'commands')).repl(ARGV)
View
@@ -1,9 +0,0 @@
-menutree do |tree|
- def action_one(*args)
- puts "One"
- end
-
- def action_two(*args)
- puts "Two"
- end
-end
@@ -1,4 +0,0 @@
-require 'rubygems'
-require 'menutree'
-
-MenuTree::Base.new(File.dirname(__FILE__)).repl(ARGV)
View
@@ -1,91 +0,0 @@
-require 'readline'
-require 'active_support'
-require 'menu'
-
-module MenuTree
- class Base
- attr_accessor :directory
- attr_accessor :parent
- attr_accessor :prompt
- attr_accessor :instance
-
- def initialize(directory, parent = "")
- @directory = directory
- @parent = parent
- if @parent.is_a? self.class
- @prompt = @parent.prompt + "/" + File.basename(@directory)
- else
- @prompt = @parent
- end
- file = File.join(@directory, "menu.rb")
- @instance = MenuTree::Menu.new(self)
- @instance.instance_eval(File.read(file), file) if File.exists? file
- end
-
- def repl(cmds)
- Readline.completion_append_character = " "
- Readline.completion_proc = Proc.new do |str|
- commands(str)
- end
-
- one_shot = true unless cmds.empty?
- to_run = cmds.shift
- while true do
- if (to_run.nil?)
- cmds = Readline.readline("#{@prompt}> ", true).split
- to_run = cmds.shift
- end
-
- # If it's a builtin, run it
- if (to_run.nil?)
- next
- elsif (to_run == "help" or to_run == "?")
- display_help
- elsif (to_run == "quit")
- exit
- elsif (to_run == "..")
- return
- elsif File.directory?(File.join(@directory, to_run))
- menu = MenuTree::Base.new(File.join(@directory, to_run), self)
- menu.repl(cmds)
- else
- @instance.send to_run, cmds rescue puts "Unknown command #{to_run}"
- end
- to_run = nil
- return if one_shot
- end
- end
-
- def commands(prefix='')
- (instance_commands + submenus).select { |command| command =~ /^#{Regexp.escape(prefix)}/ }
- end
-
- def instance_commands
- @instance.methods - MenuTree::Menu.instance_methods
- end
-
- def submenus
- Dir.new(@directory).select { |entry| entry =~ /^[^\.]/ and File.directory?(entry) }
- end
-
- def display_help
- # TODO
- puts <<EOF
- Builtin commands:
- help:\t\t\t\tDisplays this message
- quit:\t\t\t\tQuits this session
- ..: \t\t\t\tGoes up a level in the menu (or quits if at the root)
-
- #{@prompt} commands:
-EOF
- instance_commands.each do |cmd|
- puts "#{cmd}"
- end
-
- puts "Submenus:"
- submenus.each do |submenu|
- puts "#{submenu}"
- end
- end
- end
-end
View
@@ -1,11 +0,0 @@
-module MenuTree
- class Menu
- def initialize(parent_tree)
- @parent_tree = parent_tree
- end
-
- def menutree(&block)
- self.instance_exec(@parent_tree, &block) if block_given?
- end
- end
-end
View
@@ -1,5 +1,5 @@
-$:.unshift(File.dirname(__FILE__)) unless
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+$:.unshift(File.join(File.dirname(__FILE__), 'menutree')) unless
+ $:.include?(File.join(File.dirname(__FILE__), 'menutree')) || $:.include?(File.expand_path(File.join(File.dirname(__FILE__), 'menutree')))
-require 'base'
+require 'tree'
View
@@ -0,0 +1,26 @@
+module MenuTree
+ INDENT = "\t"
+ COMMAND_WIDTH = -12
+ BUILTINS = {
+ "help" => "Displays this message",
+ "quit" => "Quits this session",
+ ".." => "Goes up a level in the menu (or quits if at the root)",
+ }
+
+ module Help
+ def display_help
+ display_help_section("Built-in commands", BUILTINS)
+ display_help_section("Leaf commands", @leaf.commands)
+ display_help_section("Submenus", submenus)
+ end
+
+ def display_help_section(section, contents, indent = 0)
+ puts INDENT*indent + section
+ contents.each do |command, description|
+ puts INDENT*(indent+1) + sprintf("%#{COMMAND_WIDTH}s %s", command, description)
+ end
+ puts ""
+ end
+ end
+end
+
View
@@ -0,0 +1,23 @@
+module MenuTree
+ class Leaf
+ attr_accessor :commands
+
+ def initialize(parent_tree)
+ @parent_tree = parent_tree
+ @commands = {}
+ @curent_description = ""
+ end
+
+ def desc(desc)
+ @current_description = desc
+ end
+
+ def singleton_method_added(symbol)
+ @commands[symbol] = @current_description
+ end
+
+ def leaf(&block)
+ self.instance_exec(@parent_tree, &block) if block_given?
+ end
+ end
+end
View
@@ -0,0 +1,73 @@
+require 'readline'
+require 'active_support'
+require 'leaf'
+require 'help'
+
+module MenuTree
+ class Tree
+ include Help
+ attr_accessor :directory
+ attr_accessor :prompt
+ attr_accessor :leaf
+
+ def initialize(directory, parent = "")
+ @directory = directory
+ if parent.is_a? String
+ @prompt = parent
+ elsif !parent.prompt.empty?
+ @prompt = parent.prompt + "/" + File.basename(@directory)
+ else
+ @prompt = File.basename(@directory)
+ end
+
+ file = File.join(@directory, "leaf.rb")
+ @leaf = MenuTree::Leaf.new(self)
+ @leaf.instance_eval(File.read(file), file) if File.exists? file
+ end
+
+ def repl(cmds)
+ one_shot = true unless cmds.empty?
+ to_run = cmds.shift
+ while true do
+ Readline.completion_append_character = " "
+ Readline.completion_proc = Proc.new do |str|
+ commands(str)
+ end
+ if (to_run.nil?)
+ cmds = Readline.readline("#{@prompt}> ", true).split
+ to_run = cmds.shift
+ end
+
+ # If it's a builtin, run it
+ if (to_run.nil?)
+ next
+ elsif (to_run == "help" or to_run == "?")
+ display_help
+ elsif (to_run == "quit")
+ exit
+ elsif (to_run == "..")
+ return
+ elsif File.directory?(File.join(@directory, to_run))
+ menu = MenuTree::Tree.new(File.join(@directory, to_run), self)
+ menu.repl(cmds)
+ else
+ @leaf.send to_run, cmds rescue puts "Unknown command #{to_run}"
+ end
+ to_run = nil
+ return if one_shot
+ end
+ end
+
+ def commands(prefix='')
+ (leaf_commands + submenus).select { |command| command =~ /^#{Regexp.escape(prefix)}/ }
+ end
+
+ def leaf_commands
+ @leaf.commands.keys.map { |x| x.to_s }
+ end
+
+ def submenus
+ Dir.new(@directory).select { |entry| entry =~ /^[^\.]/ and File.directory?(File.join(@directory, entry)) }
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 2ab2b1b

Please sign in to comment.