prompt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,143 @@
1
+ # Prompt
2
+
3
+ ## What is this?
4
+
5
+ Prompt makes it easy to build slick command-line applications with [Tab Completion](#tabcompletion), [Command History](#commandhistory), and [Built-in Help](#built-inhelp)
6
+
7
+ ## Installation
8
+
9
+ gem install prompt
10
+
11
+ ## An example
12
+
13
+ Commands are defined with a Sinatra-inspired DSL:
14
+
15
+ ```ruby
16
+ require 'prompt'
17
+ extend Prompt::DSL
18
+
19
+ desc "Move"
20
+
21
+ variable :direction, "A cardinal direction", %w(north east south west)
22
+
23
+ command "go :direction", "Walk in the specified direction" do |direction|
24
+ puts "You walked #{direction} and were eaten by a grue."
25
+ end
26
+
27
+ desc "Interact"
28
+
29
+ command "look", "Look around" do
30
+ puts "You're in a dark room."
31
+ end
32
+
33
+ command "say :something", "Say something" do |something|
34
+ puts "You say '#{something}'"
35
+ end
36
+
37
+ Prompt::Console.start
38
+ ```
39
+
40
+ ### Tab completion
41
+
42
+ Tab completion is hooked up automatically after you define your commands and variables
43
+
44
+ $ my_app
45
+ > g<TAB>
46
+ > go <TAB>
47
+ go east go north go south go west
48
+ > go n<TAB>
49
+ > go north
50
+
51
+ ### Command history
52
+
53
+ Command history is enabled automatically. You can scroll through the history with the UP and DOWN keys. You can search the history with CTRL-R.
54
+
55
+ You can preserve the history between runs by specifying a history filename when starting the console
56
+
57
+ ```ruby
58
+ history_file = File.join(ENV["HOME"], ".my-history")
59
+ Prompt::Console.start history_file
60
+ ```
61
+
62
+
63
+ ### Built-in help
64
+
65
+ The `help` command is built-in. It will print all of the commands that you've defined in your app.
66
+
67
+ $ my_app
68
+ > help
69
+ Console commands
70
+
71
+ help -v
72
+ help
73
+ exit
74
+
75
+ Move
76
+
77
+ go <direction> Walk in the specified direction
78
+
79
+ Interact
80
+
81
+ look Look around
82
+ say <something> Say something
83
+
84
+ ## Grouping commands
85
+
86
+ You can put commands in logical groups. This only affects how help is printed.
87
+
88
+ ```ruby
89
+ desc "Taco commands"
90
+
91
+ command ...
92
+ command ...
93
+
94
+ desc "Burger commands"
95
+
96
+ command ...
97
+ ```
98
+
99
+ ## Using Variables
100
+
101
+ Variables can be used in a command.
102
+
103
+ ```ruby
104
+ command "name :first :last" do |first, last|
105
+ puts "Hi #{first} #{last}"
106
+ end
107
+ ```
108
+
109
+ Here, the variables are named `first` and `last`. Their values are be passed as arguments to the command's block, in the order in which they appear.
110
+
111
+ ### Defining variables
112
+
113
+ It's not necessary to define a variable before using it in a command, but doing so will allow you to provide a useful description and valid values for the variable.
114
+
115
+ ```ruby
116
+ variable :name, "Description"
117
+ ```
118
+
119
+ ### Specifying a static list of valid values
120
+
121
+ You can specify a static list of valid values for a variable. These will be expanded when using tab completion.
122
+
123
+ ```ruby
124
+ variable :name, "Description", %w(value1 value2)
125
+ ```
126
+
127
+ ### Specifying a dynamic list of valid values
128
+
129
+ Instead of specifying a static list, you can specify a block that will dynamically return a list of valid values for a variable. These will be expanded when using tab completion.
130
+
131
+ ```ruby
132
+ dynamic_variable :file, "JPG file" do
133
+ Dir.glob "*.jpg"
134
+ end
135
+ ```
136
+
137
+ ## Configuration options
138
+
139
+ The default prompt `"> "` can be changed before starting the console.
140
+
141
+ ```ruby
142
+ Prompt::Console.prompt = "#{Dir.pwd}> "
143
+ ```
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'prompt'
4
+
5
+ extend Prompt::DSL
6
+
7
+ @pwd = File.absolute_path Dir.pwd
8
+
9
+ dynamic_variable :dir, "Directory" do
10
+ Dir.entries(@pwd).select do |e|
11
+ File.directory? e
12
+ end
13
+ end
14
+
15
+ command "cd :dir", "Change directory" do |dir|
16
+ @pwd = File.absolute_path File.join(@pwd, dir)
17
+ end
18
+
19
+ command "ls", "List files" do
20
+ puts @pwd
21
+ puts Dir.entries(@pwd)
22
+ end
23
+
24
+ command "pwd", "Print current directory" do
25
+ puts @pwd
26
+ end
27
+
28
+ Prompt::Console.start
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'prompt'
4
+
5
+ extend Prompt::DSL
6
+
7
+ GRUE = 3
8
+ @moves = 0
9
+
10
+ desc "Move"
11
+
12
+ variable :direction, "A cardinal direction", %w(north east south west)
13
+
14
+ command "go :direction", "Walk in the specified direction" do |direction|
15
+ puts "You walked #{direction}"
16
+ @moves += 1
17
+ if @moves > GRUE
18
+ puts "You have been eaten by a grue"
19
+ exit
20
+ end
21
+ end
22
+
23
+ desc "Interact"
24
+
25
+ command "look", "Look around" do
26
+ if @moves < GRUE
27
+ puts "You're in a nice, well-lit room"
28
+ else
29
+ puts "It is pitch black. You are likely to be eaten by a grue."
30
+ end
31
+ end
32
+
33
+ command "say :something", "Say something" do |something|
34
+ puts "You say '#{something}'"
35
+ end
36
+
37
+ Prompt::Console.start
@@ -0,0 +1,5 @@
1
+ require 'prompt/application'
2
+ require 'prompt/dsl'
3
+ require 'prompt/prompt_module'
4
+ require 'prompt/command_not_found'
5
+ require 'prompt/console'
@@ -0,0 +1,53 @@
1
+ require 'prompt/command_group'
2
+ require 'prompt/command'
3
+
4
+ module Prompt
5
+ class Application
6
+ attr :command_groups
7
+
8
+ def initialize
9
+ @command_groups = []
10
+ end
11
+
12
+ def use_command_group desc
13
+ @current_command_group_name = desc
14
+ end
15
+
16
+ def define_command name, desc = nil, variables, &block
17
+ current_command_group.commands << Command.new(name, desc, variables, &block)
18
+ end
19
+
20
+ def exec command_str
21
+ commands.each do |command|
22
+ args = command.match(command_str)
23
+ return command.run(args) if args
24
+ end
25
+ raise CommandNotFound.new(command_str)
26
+ end
27
+
28
+ def completions starting_with = nil
29
+ return all_expansions unless starting_with
30
+
31
+ all_expansions.grep /^#{Regexp.escape(starting_with)}/
32
+ end
33
+
34
+ private
35
+
36
+ def commands
37
+ @command_groups.map(&:commands).reduce [] { |a, b| a + b }
38
+ end
39
+
40
+ def all_expansions
41
+ commands.map(&:expansions).flatten
42
+ end
43
+
44
+ def current_command_group
45
+ command_groups.find { |cg| cg.name == @current_command_group_name } || begin
46
+ cg = CommandGroup.new(@current_command_group_name)
47
+ @command_groups << cg
48
+ cg
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,95 @@
1
+ module Prompt
2
+ class Command
3
+
4
+ attr :name
5
+ attr :desc
6
+
7
+ def initialize(name, desc = nil, all_variables = [], &block)
8
+ @name = name
9
+ @desc = desc
10
+ @all_variables = all_variables
11
+ @action = block
12
+ end
13
+
14
+ def run args
15
+ @action.call *args
16
+ end
17
+
18
+ def match(str)
19
+ if m = regex.match(str.strip)
20
+ m[1..-1]
21
+ end
22
+ end
23
+
24
+ def variables
25
+ @variables ||= words.select {|w| w.kind_of? Variable}
26
+ end
27
+
28
+ def expansions
29
+ expand words
30
+ end
31
+
32
+ def usage
33
+ words.map do |word|
34
+ case word
35
+ when Variable
36
+ "<#{word.name}>"
37
+ else
38
+ word
39
+ end
40
+ end.join(" ")
41
+ end
42
+
43
+ private
44
+
45
+ def regex
46
+ @regex ||= begin
47
+ regex_strs = words.map do |word|
48
+ case word
49
+ when Variable
50
+ word.regex
51
+ else
52
+ Regexp.escape(word)
53
+ end
54
+ end
55
+ Regexp.new("^#{regex_strs.join("\s+")}$")
56
+ end
57
+ end
58
+
59
+ def words
60
+ @words ||= @name.split(/\s/).map do |word|
61
+ if word[0] == ":"
62
+ sym = word[1..-1].to_sym
63
+ @all_variables.find {|v| v.name == sym} || Variable.new(sym, sym.to_s)
64
+ else
65
+ word
66
+ end
67
+ end
68
+ end
69
+
70
+ def expand a
71
+ return [] if a.empty?
72
+
73
+ head = a[0]
74
+ tail = a[1..-1]
75
+
76
+ case head
77
+ when Variable
78
+ head = head.expansions
79
+ else
80
+ head = [head]
81
+ end
82
+
83
+ return head if tail.empty?
84
+
85
+ result = []
86
+ head.each do |h|
87
+ expand(tail).each do |e|
88
+ result << "#{h} #{e}"
89
+ end
90
+ end
91
+ result
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,12 @@
1
+ module Prompt
2
+ class CommandGroup
3
+ attr_accessor :name
4
+ attr :commands
5
+
6
+ def initialize name
7
+ @name = name
8
+ @commands = []
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ module Prompt
2
+ class CommandNotFound < RuntimeError
3
+ def initialize(command_str)
4
+ super "Command not found: #{command_str}"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ require 'prompt/console/console_module'
2
+ require 'prompt/console/builtins'
@@ -0,0 +1,44 @@
1
+ require 'prompt/dsl'
2
+
3
+ module Prompt
4
+ module Console
5
+
6
+ class Builtins
7
+ extend DSL
8
+
9
+ desc "Console commands"
10
+
11
+ command "help -v" do
12
+ print_help true
13
+ end
14
+
15
+ command "help" do
16
+ print_help
17
+ end
18
+
19
+ command "exit" do
20
+ exit
21
+ end
22
+
23
+ private
24
+
25
+ def self.print_help verbose = false
26
+ Prompt.application.command_groups.each do |cg|
27
+ puts
28
+ puts cg.name
29
+ puts
30
+ cg.commands.each do |cmd|
31
+ puts " %-40s %s" % [cmd.usage, cmd.desc]
32
+ if verbose
33
+ cmd.variables.each do |v|
34
+ puts " "*43 + ("%-10s %s" % ["<#{v.name}>", "#{v.desc}"])
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ end # class Builtins
42
+
43
+ end
44
+ end
@@ -0,0 +1,66 @@
1
+ require 'readline'
2
+ require 'prompt'
3
+
4
+ module Prompt
5
+ module Console
6
+
7
+ HISTORY_MAX_SIZE = 100
8
+ PROMPT = "> "
9
+
10
+ CompletionProc = proc do |s|
11
+ Prompt.application.completions(s)
12
+ end
13
+
14
+ def self.start(history_file = nil)
15
+ @prompt = PROMPT
16
+
17
+ # Store the state of the terminal
18
+ stty_save = `stty -g`.chomp
19
+ # and restore it when exiting
20
+ at_exit do
21
+ system('stty', stty_save)
22
+ save_history history_file if history_file
23
+ end
24
+
25
+ Readline.basic_word_break_characters = ""
26
+ Readline.completion_append_character = " "
27
+ Readline.completion_proc = CompletionProc
28
+
29
+ load_history history_file if history_file
30
+
31
+ while line = Readline.readline(@prompt, true)
32
+ begin
33
+ Prompt.application.exec(line).nil?
34
+ rescue ::Prompt::CommandNotFound => e
35
+ STDERR.puts e.message
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.prompt= prompt
41
+ @prompt = prompt
42
+ end
43
+
44
+ def self.prompt
45
+ @prompt
46
+ end
47
+
48
+ def self.save_history file
49
+ history_no_dups = Readline::HISTORY.to_a.reverse.uniq[0,HISTORY_MAX_SIZE].reverse
50
+ File.open(file, "w") do |f|
51
+ history_no_dups.each do |line|
52
+ f.puts line
53
+ end
54
+ end
55
+ end
56
+
57
+ def self.load_history file
58
+ if File.exist? file
59
+ File.readlines(file).each do |line|
60
+ Readline::HISTORY.push line.strip
61
+ end
62
+ end
63
+ end
64
+
65
+ end # module Console
66
+ end
@@ -0,0 +1,37 @@
1
+ require 'prompt/variable'
2
+ require 'prompt/proc_variable'
3
+
4
+ module Prompt
5
+ module DSL
6
+
7
+ def self.extended(base)
8
+ name = if base.respond_to? :name
9
+ base.name
10
+ else
11
+ "Commands"
12
+ end
13
+ Prompt.application.use_command_group(name)
14
+ end
15
+
16
+ def desc desc
17
+ Prompt.application.use_command_group(desc)
18
+ end
19
+
20
+ def command(name, desc = nil, &block)
21
+ Prompt.application.define_command(name, desc, @variables || {}, &block)
22
+ end
23
+
24
+ def variable(name, desc, values = nil)
25
+ @variables = [] unless defined? @variables
26
+ raise "variable :#{name} is already defined" if @variables.find {|v| v.name == name}
27
+ @variables << Variable.new(name, desc, values)
28
+ end
29
+
30
+ def dynamic_variable(name, desc, &block)
31
+ @variables = [] unless defined? @variables
32
+ raise "variable :#{name} is already defined" if @variables.find {|v| v.name == name}
33
+ @variables << ProcVariable.new(name, desc, &block)
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ module Prompt
2
+ class ProcVariable < Variable
3
+
4
+ def initialize(name, desc, &block)
5
+ super(name, desc, nil)
6
+ @proc = block
7
+ end
8
+
9
+ def values
10
+ @proc.call
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module Prompt
2
+ class << self
3
+
4
+ # Singleton instance
5
+ #
6
+ def application
7
+ @application ||= Application.new
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ module Prompt
2
+ class Variable
3
+
4
+ attr :name
5
+ attr :desc
6
+ attr :values
7
+
8
+ def initialize(name, desc, values = nil)
9
+ @name = name
10
+ @desc = desc
11
+ @values = values
12
+ end
13
+
14
+ def regex
15
+ if values
16
+ "(#{values.map{|v| Regexp.escape(v)}.join("|")})"
17
+ else
18
+ "([^\s]+)"
19
+ end
20
+ end
21
+
22
+ def expansions
23
+ values || ["<#{name}>"]
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,105 @@
1
+ require 'prompt/command'
2
+ require 'prompt/variable'
3
+
4
+ include Prompt
5
+
6
+ describe Prompt::Command do
7
+
8
+ DIRECTIONS = %w{n e s w}
9
+ SPEEDS = %w{quickly slowly}
10
+
11
+ describe "#match" do
12
+ it "matches correctly" do
13
+ c = Command.new("hi")
14
+
15
+ c.match("hi").should == []
16
+ c.match("hi ").should == []
17
+ c.match(" hi").should == []
18
+
19
+ c.match("bye").should be_nil
20
+ c.match("h").should be_nil
21
+ c.match("").should be_nil
22
+ end
23
+
24
+ it "matches correctly with a variable" do
25
+ c = Command.new("hi :name")
26
+
27
+ c.match("hi guy").should == ["guy"]
28
+ c.match("higuy").should be_nil
29
+ c.match("higuy guy").should be_nil
30
+ end
31
+
32
+ it "matches correctly with a variable and multiple spaces" do
33
+ c = Command.new("hi :name")
34
+
35
+ c.match(" hi guy").should == ["guy"]
36
+ c.match("hi guy").should == ["guy"]
37
+ c.match("hi guy ").should == ["guy"]
38
+ end
39
+
40
+ it "matches correctly with 2 variables" do
41
+ c = Command.new("hi :first :last")
42
+
43
+ c.match("hi agent smith").should == ["agent", "smith"]
44
+ c.match("hi agent").should be_nil
45
+ c.match("hi agent smith guy").should be_nil
46
+ end
47
+
48
+ it "matches correctly with variable value constraint" do
49
+ v = [Variable.new(:dir, "", DIRECTIONS)]
50
+ c = Command.new("go :dir", nil, v)
51
+
52
+ c.match("go n").should == ["n"]
53
+ c.match("go s").should == ["s"]
54
+ c.match("go x").should be_nil
55
+ c.match("go nn").should be_nil
56
+ end
57
+ end
58
+
59
+ describe "#variables" do
60
+ it "returns correctly" do
61
+ color = Variable.new(:color, "")
62
+ flavor = Variable.new(:flavor, "")
63
+
64
+ Command.new("one").variables.should == []
65
+ vs = Command.new("one :color").variables
66
+ vs.length.should == 1
67
+ vs.first.name.should == :color
68
+ Command.new("one :color", nil, [color]).variables.should == [color]
69
+ Command.new("one :color", nil, [color, flavor]).variables.should == [color]
70
+ end
71
+ end
72
+
73
+ describe "#expansions" do
74
+ it "expands correctly with no variables" do
75
+ Command.new("one").expansions.should == ["one"]
76
+ end
77
+
78
+ it "expands correctly with undefined variables" do
79
+ Command.new("go :dir").expansions.should == ["go <dir>"]
80
+ end
81
+
82
+ it "expands correctly with defined variables" do
83
+ v = [Variable.new(:dir, "", DIRECTIONS), Variable.new(:speed, "", SPEEDS)]
84
+ Command.new("go :dir", nil, v).expansions.should == ["go n", "go e", "go s", "go w"]
85
+ Command.new("go :dir :speed", nil, v).expansions.should ==
86
+ ["go n quickly", "go n slowly",
87
+ "go e quickly", "go e slowly",
88
+ "go s quickly", "go s slowly",
89
+ "go w quickly", "go w slowly"]
90
+ end
91
+ end
92
+
93
+ describe "#usage" do
94
+ it "returns correctly" do
95
+ color = Variable.new(:color, "")
96
+
97
+ Command.new("one").usage.should == "one"
98
+ Command.new("one :color").usage.should == "one <color>"
99
+ Command.new("one :color", nil, [color]).usage.should == "one <color>"
100
+ Command.new("one :color three").usage.should == "one <color> three"
101
+ end
102
+ end
103
+
104
+ end
105
+
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: prompt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mike Smith
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-17 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Prompt makes it easy to build slick command-line applications with Tab
15
+ Completion, Command History, and Built-in Help
16
+ email: mike@sticknet.net
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - lib/prompt/application.rb
23
+ - lib/prompt/command.rb
24
+ - lib/prompt/command_group.rb
25
+ - lib/prompt/command_not_found.rb
26
+ - lib/prompt/console/builtins.rb
27
+ - lib/prompt/console/console_module.rb
28
+ - lib/prompt/console.rb
29
+ - lib/prompt/dsl.rb
30
+ - lib/prompt/proc_variable.rb
31
+ - lib/prompt/prompt_module.rb
32
+ - lib/prompt/variable.rb
33
+ - lib/prompt.rb
34
+ - spec/prompt/command_spec.rb
35
+ - examples/file_manager
36
+ - examples/mud
37
+ homepage: http://github.com/mudynamics/prompt
38
+ licenses: []
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.6
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 1.8.10
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: A small framework that makes it easy to build slick command-line applications
61
+ test_files: []