rcommand 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/CHANGELOG +2 -0
  2. data/README +42 -0
  3. data/install.rb +30 -0
  4. data/lib/rcommand.rb +287 -0
  5. data/rakefile +113 -0
  6. data/test/command_test.rb +63 -0
  7. metadata +47 -0
@@ -0,0 +1,2 @@
1
+ == RCommand 0.1.0
2
+ * Initial release, probably isn't very portable.
data/README ADDED
@@ -0,0 +1,42 @@
1
+ RCommand is a generic way for ruby scripts to present a full-featured
2
+ command interface to users, complete with command history and tab completion.
3
+
4
+ == Example
5
+ require 'rcommand'
6
+
7
+ command_line = RCommand.new(STDIN, STDOUT)
8
+ command_line.for_prompt do
9
+ command_line.io_write.print("#{command_line.history.size}:> ")
10
+ end
11
+
12
+ # Default events shown here in full, for demonstration purposes. Omitting
13
+ # them will give the same behavior.
14
+ command_line.on_single_tab do |partial|
15
+ matches = RCommand.matches(partial, command_line.history)
16
+ common_prefix = RCommand.common_prefix(matches)
17
+ common_prefix.nil? ? partial : common_prefix
18
+ end
19
+ command_line.on_double_tab do |partial|
20
+ matches = RCommand.matches(partial, command_line.history)
21
+ if matches.size > 1
22
+ command_line.io_write.puts()
23
+ for match in matches.uniq
24
+ command_line.io_write.puts(match)
25
+ end
26
+ command_line.prompt()
27
+ command_line.io_write.print(partial)
28
+ end
29
+ partial
30
+ end
31
+ command_line.on_key_up do |partial|
32
+ command_line.prev()
33
+ end
34
+ command_line.on_key_down do |partial|
35
+ command_line.next()
36
+ end
37
+
38
+ loop do
39
+ command_line.prompt()
40
+ result = command_line.gets()
41
+ command_line.io_write.puts("Example command: #{result}")
42
+ end
@@ -0,0 +1,30 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ # this was adapted from rdoc's install.rb by ways of Log4r
8
+
9
+ $sitedir = CONFIG["sitelibdir"]
10
+ unless $sitedir
11
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
12
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
13
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
14
+ if !$sitedir
15
+ $sitedir = File.join($libdir, "site_ruby")
16
+ elsif $sitedir !~ Regexp.quote(version)
17
+ $sitedir = File.join($sitedir, version)
18
+ end
19
+ end
20
+
21
+ # the acual gruntwork
22
+ Dir.chdir("lib")
23
+
24
+ Find.find("rcommand.rb") { |f|
25
+ if f[-3..-1] == ".rb"
26
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
27
+ else
28
+ File::makedirs(File.join($sitedir, *f.split(/\//)))
29
+ end
30
+ }
@@ -0,0 +1,287 @@
1
+ #--
2
+ # Copyright (c) 2005 Robert Aman
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ RCOMMAND_VERSION = "0.1.0"
25
+
26
+ $:.unshift(File.dirname(__FILE__))
27
+
28
+ # RCommand is a generic way for ruby scripts to present a full-featured
29
+ # command interface to users with command history and tab completion.
30
+ class RCommand
31
+
32
+ # Returns all commands in a command list that match a partial command.
33
+ def self.matches(partial, command_list)
34
+ matches = []
35
+ for command in command_list
36
+ unless command.kind_of? String
37
+ raise "Expecting String, got #{command.class.name} instead."
38
+ end
39
+ matches << command if command[0...(partial.size)] == partial
40
+ end
41
+ return matches
42
+ end
43
+
44
+ # Returns any prefix shared by all commands in a list, or the empty
45
+ # string if there was no prefix.
46
+ def self.common_prefix(command_list)
47
+ return "" unless command_list.kind_of? Enumerable
48
+ return "" if command_list.size == 0
49
+ command_list.each do |command|
50
+ unless command.kind_of? String
51
+ raise "Expecting String, got #{command.class.name} instead."
52
+ end
53
+ end
54
+ prefix = ""
55
+ command_list = command_list.sort do |a, b|
56
+ a.size <=> b.size
57
+ end
58
+ for i in 0..command_list[0].size
59
+ for command in command_list
60
+ return prefix if command_list[0][i] != command[i]
61
+ end
62
+ prefix = command_list[0][0..i]
63
+ end
64
+ return prefix
65
+ end
66
+
67
+ # Creates a new RCommand interface object.
68
+ def initialize(io_read, io_write)
69
+ unless io_read.kind_of? IO
70
+ raise "Expecting object of type IO, got #{io_read.class.name}."
71
+ end
72
+ unless io_write.kind_of? IO
73
+ raise "Expecting object of type IO, got #{io_write.class.name}."
74
+ end
75
+ @io_read = io_read
76
+ @io_write = io_write
77
+ @history = []
78
+ @history_index = nil
79
+ @tab_count = 0
80
+ @prompt_proc = nil
81
+ @single_tab_proc = lambda do |partial|
82
+ matches = RCommand.matches(partial, self.history)
83
+ common_prefix = RCommand.common_prefix(matches)
84
+ common_prefix.nil? ? partial : common_prefix
85
+ end
86
+ @double_tab_proc = lambda do |partial|
87
+ matches = RCommand.matches(partial, self.history)
88
+ if matches.size > 1
89
+ self.io_write.puts()
90
+ for match in matches.uniq
91
+ self.io_write.puts(match)
92
+ end
93
+ self.prompt()
94
+ self.io_write.print(partial)
95
+ end
96
+ partial
97
+ end
98
+ @key_up_proc = lambda do |partial|
99
+ self.prev()
100
+ end
101
+ @key_down_proc = lambda do |partial|
102
+ self.next()
103
+ end
104
+ @key_left_proc = nil
105
+ @key_right_proc = nil
106
+ @interupt_proc = lambda do
107
+ self.io_write.print("\n")
108
+ system("stty sane")
109
+ exit
110
+ end
111
+ end
112
+
113
+ attr_reader :io_read
114
+ attr_reader :io_write
115
+ attr_reader :history
116
+ attr_accessor :history_index
117
+
118
+ def for_prompt(&block); @prompt_proc = block; end
119
+ def on_single_tab(&block); @single_tab_proc = block; end
120
+ def on_double_tab(&block); @double_tab_proc = block; end
121
+ def on_key_up(&block); @key_up_proc = block; end
122
+ def on_key_down(&block); @key_down_proc = block; end
123
+ def on_key_left(&block); @key_left_proc = block; end
124
+ def on_key_right(&block); @key_right_proc = block; end
125
+ def on_interupt(&block); @interupt_proc = block; end
126
+
127
+ # Displays the prompt, if any.
128
+ def prompt()
129
+ @prompt_proc.call() unless @prompt_proc.nil?
130
+ end
131
+
132
+ # Sets the command line to the previous command.
133
+ def prev()
134
+ if self.history_index.nil?
135
+ self.history_index = self.history.size - 1
136
+ else
137
+ self.history_index -= 1
138
+ self.history_index = 0 if self.history_index < 0
139
+ end
140
+ self.history[self.history_index]
141
+ end
142
+
143
+ # Sets the command line to the next command.
144
+ def next()
145
+ if self.history_index.nil?
146
+ nil
147
+ else
148
+ self.history_index += 1
149
+ if self.history_index > self.history.size - 1
150
+ self.history_index = self.history.size - 1
151
+ ""
152
+ else
153
+ self.history[self.history_index]
154
+ end
155
+ end
156
+ end
157
+
158
+ # Adds a command to the command line.
159
+ def <<(command)
160
+ command = command[0..-1] if command[-1] == "\n"
161
+ command = command[0..-1] if command[-1] == "\r"
162
+ if @history[-1] == ""
163
+ @history.pop()
164
+ end
165
+ @history << command if command != ""
166
+ @history_index = nil
167
+ end
168
+
169
+ # Returns the command string entered by the user.
170
+ def gets()
171
+ command = ""
172
+ char = nil
173
+ loop do
174
+ char = getc()
175
+ next if char.nil?
176
+ next if char == 0
177
+ if char != 9
178
+ @tab_count = 0
179
+ end
180
+ if char == 3
181
+ @interupt_proc.call() unless @interupt_proc.nil?
182
+ elsif char == 10
183
+ @io_write.print("\n")
184
+ self << command
185
+ break
186
+ elsif char == 9
187
+ @tab_count += 1
188
+ replacement = nil
189
+ if @tab_count == 1
190
+ unless @single_tab_proc.nil?
191
+ replacement =
192
+ @single_tab_proc.call(command)
193
+ end
194
+ elsif @tab_count > 1
195
+ unless @double_tab_proc.nil?
196
+ replacement =
197
+ @double_tab_proc.call(command)
198
+ end
199
+ end
200
+ if replacement != nil && replacement.size > command.size
201
+ @io_write.print(
202
+ 8.chr * command.size +
203
+ " " * command.size +
204
+ 8.chr * command.size)
205
+ @io_write.print(replacement)
206
+ command = replacement
207
+ end
208
+ elsif char == 8 || char == 127
209
+ if command.size > 0
210
+ command = command[0..-2]
211
+ @io_write.print(8.chr + " " + 8.chr)
212
+ end
213
+ else
214
+ command << char.chr
215
+ replacement = nil
216
+ if command.index("\e[A") != nil
217
+ command.gsub!("\e[A", "")
218
+ unless @key_up_proc.nil?
219
+ replacement =
220
+ @key_up_proc.call(command)
221
+ end
222
+ elsif command.index("\e[B") != nil
223
+ command.gsub!("\e[B", "")
224
+ unless @key_down_proc.nil?
225
+ replacement =
226
+ @key_down_proc.call(command)
227
+ end
228
+ elsif command.index("\e[C") != nil
229
+ command.gsub!("\e[C", "")
230
+ unless @key_right_proc.nil?
231
+ replacement =
232
+ @key_right_proc.call(command)
233
+ end
234
+ elsif command.index("\e[D") != nil
235
+ command.gsub!("\e[D", "")
236
+ unless @key_left_proc.nil?
237
+ replacement =
238
+ @key_left_proc.call(command)
239
+ end
240
+ else
241
+ if (char == "["[0] && command[-2] != "\e"[0]) ||
242
+ (char != "\e"[0] && char != "["[0])
243
+ @io_write.print(char.chr)
244
+ end
245
+ end
246
+ if replacement != nil
247
+ @io_write.print(
248
+ 8.chr * command.size +
249
+ " " * command.size +
250
+ 8.chr * command.size)
251
+ @io_write.print(replacement)
252
+ command = replacement
253
+ end
254
+ end
255
+ end
256
+ return command
257
+ end
258
+
259
+ private
260
+ # Returns a single character without waiting for the enter key to be
261
+ # pressed.
262
+ def getc()
263
+ char = nil
264
+ begin
265
+ if @io_read == STDIN
266
+ if RUBY_PLATFORM =~ /mswin/
267
+ require "Win32API"
268
+ char = Win32API.new("crtdll", "_getch", [], "L").Call
269
+ else
270
+ begin
271
+ system("stty cbreak -echo")
272
+ char = @io_read.readchar()
273
+ ensure
274
+ system("stty -cbreak -echo")
275
+ end
276
+ end
277
+ else
278
+ char = @io_read.readchar()
279
+ end
280
+ rescue Interrupt
281
+ return 3
282
+ rescue Exception
283
+ return nil
284
+ end
285
+ return char
286
+ end
287
+ end
@@ -0,0 +1,113 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/contrib/rubyforgepublisher'
8
+
9
+ PKG_NAME = 'rcommand'
10
+ PKG_VERSION = '0.1.0'
11
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
12
+
13
+ RELEASE_NAME = "REL #{PKG_VERSION}"
14
+
15
+ RUBY_FORGE_PROJECT = "rcommand"
16
+ RUBY_FORGE_USER = "sporkmonger"
17
+
18
+ PKG_FILES = FileList[
19
+ "lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "rakefile"
20
+ ].exclude(/\bCVS\b|~$/).exclude(/database\.yml/)
21
+
22
+ desc "Default Task"
23
+ task :default => [ :test_all ]
24
+
25
+ # Run the unit tests
26
+
27
+ Rake::TestTask.new("test_all") { |t|
28
+ t.libs << "test"
29
+ t.pattern = 'test/*_test.rb'
30
+ t.verbose = true
31
+ }
32
+
33
+ # Generate the RDoc documentation
34
+
35
+ Rake::RDocTask.new { |rdoc|
36
+ rdoc.rdoc_dir = 'doc'
37
+ rdoc.title = "RCommand -- generic interactive command handler"
38
+ rdoc.options << '--line-numbers' << '--inline-source' <<
39
+ '--accessor' << 'cattr_accessor=object'
40
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
41
+ rdoc.rdoc_files.include('README', 'CHANGELOG')
42
+ rdoc.rdoc_files.include('lib/**/*.rb')
43
+ }
44
+
45
+ # Create compressed packages
46
+
47
+ dist_dirs = [ "lib", "test" ]
48
+
49
+ spec = Gem::Specification.new do |s|
50
+ s.name = PKG_NAME
51
+ s.version = PKG_VERSION
52
+ s.summary = "Command handler."
53
+ s.description = "A generic interactive command handler."
54
+
55
+ s.files = [ "rakefile", "install.rb", "README", "CHANGELOG" ]
56
+ dist_dirs.each do |dir|
57
+ s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if do |item|
58
+ item.include?( "\.svn" ) || item.include?( "database\.yml" )
59
+ end
60
+ end
61
+
62
+ s.require_path = 'lib'
63
+ s.autorequire = 'rcommand'
64
+
65
+ s.has_rdoc = true
66
+ s.extra_rdoc_files = %w( README )
67
+ s.rdoc_options.concat ['--main', 'README']
68
+
69
+ s.author = "Bob Aman"
70
+ s.email = "bob@sporkmonger.com"
71
+ s.homepage = "http://sporkmonger.com/projects/rcommand"
72
+ s.rubyforge_project = "rcommand"
73
+ end
74
+
75
+ Rake::GemPackageTask.new(spec) do |p|
76
+ p.gem_spec = spec
77
+ p.need_tar = true
78
+ p.need_zip = true
79
+ end
80
+
81
+ task :lines do
82
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
83
+
84
+ for file_name in FileList["lib/**/*.rb"]
85
+ f = File.open(file_name)
86
+
87
+ while line = f.gets
88
+ lines += 1
89
+ next if line =~ /^\s*$/
90
+ next if line =~ /^\s*#/
91
+ codelines += 1
92
+ end
93
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
94
+
95
+ total_lines += lines
96
+ total_codelines += codelines
97
+
98
+ lines, codelines = 0, 0
99
+ end
100
+
101
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
102
+ end
103
+
104
+
105
+ # Publishing ------------------------------------------------------
106
+
107
+ desc "Publish the API documentation"
108
+ task :pdoc => [:rdoc] do
109
+ Rake::SshDirPublisher.new(
110
+ "vacindak@sporkmonger.com",
111
+ "public_html/projects/rcommand/api",
112
+ "doc").upload
113
+ end
@@ -0,0 +1,63 @@
1
+ require 'test/unit'
2
+ require 'rcommand'
3
+
4
+ class CommandTest < Test::Unit::TestCase
5
+ def setup
6
+ end
7
+
8
+ def test_command_history
9
+ command_line = RCommand.new(STDIN, STDOUT)
10
+
11
+ command_line << "first command"
12
+ command_line << "second command"
13
+ command_line << "third command"
14
+
15
+ assert_equal(nil, command_line.next())
16
+ assert_equal("third command", command_line.prev())
17
+ assert_equal("second command", command_line.prev())
18
+ assert_equal("first command", command_line.prev())
19
+ assert_equal("second command", command_line.next())
20
+ assert_equal("third command", command_line.next())
21
+ assert_equal("", command_line.next())
22
+ assert_equal(3, command_line.history.size)
23
+
24
+ command_line << "fourth command"
25
+
26
+ assert_equal(nil, command_line.next())
27
+ assert_equal("fourth command", command_line.prev())
28
+ assert_equal(4, command_line.history.size)
29
+ end
30
+
31
+ def test_command_matches
32
+ history = [
33
+ "good",
34
+ "better",
35
+ "best",
36
+ "bestest"
37
+ ]
38
+
39
+ assert_equal(3, RCommand.matches("b", history).size)
40
+ assert_equal(3, RCommand.matches("be", history).size)
41
+ assert_equal(2, RCommand.matches("bes", history).size)
42
+ assert_equal(2, RCommand.matches("best", history).size)
43
+ assert_equal(1, RCommand.matches("beste", history).size)
44
+ assert_equal(1, RCommand.matches("bestes", history).size)
45
+ assert_equal(1, RCommand.matches("bestest", history).size)
46
+
47
+ assert_equal(1, RCommand.matches("g", history).size)
48
+ assert_equal(1, RCommand.matches("go", history).size)
49
+ assert_equal(1, RCommand.matches("goo", history).size)
50
+ assert_equal(1, RCommand.matches("good", history).size)
51
+ end
52
+
53
+ def test_command_common_prefix
54
+ assert_equal("",
55
+ RCommand.common_prefix(["good", "better", "best", "bestest"]))
56
+ assert_equal("be",
57
+ RCommand.common_prefix(["better", "best", "bestest"]))
58
+ assert_equal("best",
59
+ RCommand.common_prefix(["best", "bestest"]))
60
+ assert_equal("bestest",
61
+ RCommand.common_prefix(["bestest"]))
62
+ end
63
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: rcommand
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-03-27 00:00:00 -05:00
8
+ summary: Command handler.
9
+ require_paths:
10
+ - lib
11
+ email: bob@sporkmonger.com
12
+ homepage: http://sporkmonger.com/projects/rcommand
13
+ rubyforge_project: rcommand
14
+ description: A generic interactive command handler.
15
+ autorequire: rcommand
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Bob Aman
31
+ files:
32
+ - rakefile
33
+ - install.rb
34
+ - README
35
+ - CHANGELOG
36
+ - lib/rcommand.rb
37
+ - test/command_test.rb
38
+ test_files: []
39
+ rdoc_options:
40
+ - "--main"
41
+ - README
42
+ extra_rdoc_files:
43
+ - README
44
+ executables: []
45
+ extensions: []
46
+ requirements: []
47
+ dependencies: []