harp 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/examples/usage.rb +8 -13
- data/lib/harp.rb +12 -94
- data/lib/harp/cli.rb +24 -0
- data/lib/harp/command_manager.rb +74 -0
- data/lib/harp/repl.rb +94 -0
- metadata +6 -3
data/examples/usage.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
$:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
|
1
2
|
require "harp"
|
2
3
|
|
3
4
|
class UsefulThing
|
@@ -12,32 +13,26 @@ class UsefulThing
|
|
12
13
|
|
13
14
|
# Set it up
|
14
15
|
setup_harp do |harp|
|
16
|
+
command_names = harp.command_names.select {|name| name.size > 1 }
|
15
17
|
|
16
|
-
command("help") do
|
17
|
-
commands
|
18
|
-
puts "* Available commands: " << commands.sort.join(" ")
|
18
|
+
command("help", :alias => "h") do
|
19
|
+
puts "* Available commands: " << command_names.sort.join(", ")
|
19
20
|
puts "* Tab completion works for commands."
|
20
21
|
end
|
21
22
|
|
22
|
-
# Harp provides a "quit" command by default, but you can
|
23
|
+
# Harp's repl provides a "quit" command by default, but you can
|
23
24
|
# override it to add value.
|
24
|
-
command("quit") do
|
25
|
+
command("quit", :alias => "q") do
|
25
26
|
puts "Farewell to the girl with the sun in her eyes."
|
26
27
|
exit
|
27
28
|
end
|
28
29
|
|
29
|
-
## Set up a handler for a command where the first token is "!"
|
30
|
-
## I.e., shell out like Vim does.
|
31
|
-
#on_bang do |args|
|
32
|
-
#system args.first
|
33
|
-
#end
|
34
|
-
|
35
30
|
# define a command that calls an instance method of your class.
|
36
31
|
# The block parameter is always an array, even if your regex
|
37
32
|
# had only one match group.
|
38
33
|
# This command will only accept a single-word argument (no
|
39
34
|
# whitespace allowed).
|
40
|
-
command("use", :adverb) do |args|
|
35
|
+
command("use", :adverb, :alias => "abuse") do |args|
|
41
36
|
self.use(args.first)
|
42
37
|
end
|
43
38
|
|
@@ -45,5 +40,5 @@ class UsefulThing
|
|
45
40
|
|
46
41
|
end
|
47
42
|
|
48
|
-
UsefulThing.new.repl
|
43
|
+
#UsefulThing.new.repl
|
49
44
|
|
data/lib/harp.rb
CHANGED
@@ -1,119 +1,37 @@
|
|
1
|
-
|
2
|
-
require "
|
3
|
-
require "
|
4
|
-
|
5
|
-
Readline.completion_append_character = nil
|
6
|
-
#Readline.basic_word_break_characters = ""
|
7
|
-
|
8
|
-
require "harp/dispatcher"
|
1
|
+
require "harp/command_manager"
|
2
|
+
require "harp/repl"
|
3
|
+
require "harp/cli"
|
9
4
|
|
10
5
|
module Harp
|
11
6
|
def self.included(mod)
|
12
7
|
mod.module_eval do
|
13
|
-
@
|
8
|
+
@command_manager = CommandManager.new
|
14
9
|
|
15
10
|
def self.setup_harp(&block)
|
16
|
-
|
11
|
+
command_manager = @command_manager
|
17
12
|
# This should either be baked in to REPL, or non-existent.
|
18
|
-
@
|
13
|
+
@command_manager.command("quit") do
|
19
14
|
exit
|
20
15
|
end
|
21
|
-
@
|
16
|
+
@command_manager.instance_exec(command_manager, &block)
|
22
17
|
end
|
23
18
|
|
24
19
|
def self.repl
|
25
|
-
REPL.new(@
|
20
|
+
REPL.new(@command_manager)
|
26
21
|
end
|
27
22
|
|
28
23
|
def repl
|
29
24
|
self.class.repl.run(self)
|
30
25
|
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class REPL
|
35
|
-
|
36
|
-
attr_reader :store, :commands
|
37
|
-
def initialize(dispatcher)
|
38
|
-
@dispatcher = dispatcher
|
39
|
-
@commands = dispatcher.commands.keys
|
40
|
-
Readline.completion_proc = self.method(:complete)
|
41
|
-
end
|
42
|
-
|
43
|
-
def complete(str)
|
44
|
-
case Readline.line_buffer
|
45
|
-
when /^\s*!/
|
46
|
-
# if we're in the middle of a bang-exec command, completion
|
47
|
-
# should look at the file system.
|
48
|
-
self.complete_path(str)
|
49
|
-
else
|
50
|
-
# otherwise use the internal dict.
|
51
|
-
self.complete_term(str)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def complete_path(str)
|
56
|
-
Dir.glob("#{str}*")
|
57
|
-
end
|
58
26
|
|
59
|
-
|
60
|
-
|
61
|
-
# data structure. No command contains a ".", so that's the test
|
62
|
-
# we use to distinguish.
|
63
|
-
bits = str.split(".")
|
64
|
-
if bits.size > 1
|
65
|
-
# An attempt to allow completion of either full configuration index
|
66
|
-
# strings, or of component parts. E.g., if the configuration contains
|
67
|
-
# foo.bar.baz, this code will offer both "foo" and "foo.bar.baz"
|
68
|
-
# as completions for "fo".
|
69
|
-
v1 = @completions.grep(/^#{Regexp.escape(str)}/)
|
70
|
-
v2 = @completions.grep(/^#{Regexp.escape(bits.last)}/)
|
71
|
-
(v1 + v2.map {|x| (bits.slice(0..-2) << x).join(".") }).uniq
|
72
|
-
else
|
73
|
-
self.command_complete(str) +
|
74
|
-
@completions.grep(/^#{Regexp.escape(str)}/)
|
27
|
+
def self.cli
|
28
|
+
CLI.new(@command_manager)
|
75
29
|
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def command_complete(str)
|
79
|
-
@commands.grep(/^#{Regexp.escape(str)}/)
|
80
|
-
end
|
81
|
-
|
82
|
-
def sanitize(str)
|
83
|
-
# ANSI code stripper regex cargo culted from
|
84
|
-
# http://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed
|
85
|
-
str.gsub(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/, "")
|
86
|
-
end
|
87
|
-
|
88
|
-
def run(context)
|
89
|
-
@completions = context.completions rescue Set.new
|
90
|
-
@run = true
|
91
|
-
puts
|
92
|
-
while @run && (line = Readline.readline("<3: ", true).strip)
|
93
|
-
if line[0] == "!"
|
94
|
-
system(line.slice(1..-1))
|
95
|
-
next
|
96
|
-
end
|
97
|
-
|
98
|
-
if line.empty?
|
99
|
-
next
|
100
|
-
end
|
101
30
|
|
102
|
-
|
103
|
-
|
104
|
-
# TODO: check for bang command
|
105
|
-
if command = @dispatcher.commands[name]
|
106
|
-
if block = command.block_for(args)
|
107
|
-
context.instance_exec(args, &block)
|
108
|
-
else
|
109
|
-
puts "invalid arguments for command"
|
110
|
-
end
|
111
|
-
else
|
112
|
-
puts "command not found"
|
113
|
-
end
|
31
|
+
def cli
|
32
|
+
self.class.cli.run(self)
|
114
33
|
end
|
115
34
|
end
|
116
|
-
|
117
35
|
end
|
118
36
|
|
119
37
|
end
|
data/lib/harp/cli.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Harp
|
3
|
+
|
4
|
+
class CLI
|
5
|
+
|
6
|
+
def initialize(command_manager)
|
7
|
+
@command_manager = command_manager
|
8
|
+
@commands = command_manager.commands.keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(context)
|
12
|
+
name, *args = parse(ARGV)
|
13
|
+
@command_manager.handle(name, args, context)
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(array)
|
17
|
+
array
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Harp
|
2
|
+
|
3
|
+
class CommandManager
|
4
|
+
|
5
|
+
attr_reader :commands
|
6
|
+
def initialize
|
7
|
+
@commands = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle(name, args, context)
|
11
|
+
block = find_command(name, args)
|
12
|
+
context.instance_exec(args, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_command(name, args)
|
16
|
+
if command = @commands[name]
|
17
|
+
if block = command.block_for(args)
|
18
|
+
return block
|
19
|
+
else
|
20
|
+
raise ArgumentError, "Invalid arguments for command."
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Command not found: '#{name}'."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def command(command_name, *spec, &block)
|
28
|
+
names = [command_name]
|
29
|
+
if spec.last.is_a?(Hash)
|
30
|
+
options = spec.pop
|
31
|
+
if command_alias = options[:alias]
|
32
|
+
names << command_alias
|
33
|
+
end
|
34
|
+
end
|
35
|
+
names.each do |name|
|
36
|
+
command = @commands[name] ||= Command.new(name)
|
37
|
+
command.add_spec(spec, block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def command_names
|
42
|
+
@commands.keys.sort
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class Command
|
48
|
+
def initialize(name)
|
49
|
+
@name = name
|
50
|
+
@blocks = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_spec(spec, block)
|
54
|
+
# TODO validate block and arity
|
55
|
+
@blocks[spec]= block
|
56
|
+
end
|
57
|
+
|
58
|
+
def block_for(args)
|
59
|
+
@blocks.each do |spec, block|
|
60
|
+
if match(spec, args)
|
61
|
+
return block
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def match(spec, args)
|
68
|
+
spec.size == args.size
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
data/lib/harp/repl.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# stdlib
|
2
|
+
require "set"
|
3
|
+
require "readline"
|
4
|
+
|
5
|
+
Readline.completion_append_character = nil
|
6
|
+
#Readline.basic_word_break_characters = ""
|
7
|
+
|
8
|
+
module Harp
|
9
|
+
|
10
|
+
class REPL
|
11
|
+
|
12
|
+
def initialize(command_manager)
|
13
|
+
@command_manager = command_manager
|
14
|
+
Readline.completion_proc = self.method(:complete)
|
15
|
+
end
|
16
|
+
|
17
|
+
def commands
|
18
|
+
@command_manager.commands.keys
|
19
|
+
end
|
20
|
+
|
21
|
+
def complete(str)
|
22
|
+
case Readline.line_buffer
|
23
|
+
when /^\s*!/
|
24
|
+
# if we're in the middle of a bang-exec command, completion
|
25
|
+
# should look at the file system.
|
26
|
+
self.complete_path(str)
|
27
|
+
else
|
28
|
+
# otherwise use the internal dict.
|
29
|
+
self.complete_term(str)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def complete_path(str)
|
34
|
+
Dir.glob("#{str}*")
|
35
|
+
end
|
36
|
+
|
37
|
+
def complete_term(str)
|
38
|
+
# Terms can be either commands or indexes into the configuration
|
39
|
+
# data structure. No command contains a ".", so that's the test
|
40
|
+
# we use to distinguish.
|
41
|
+
bits = str.split(".")
|
42
|
+
if bits.size > 1
|
43
|
+
# An attempt to allow completion of either full configuration index
|
44
|
+
# strings, or of component parts. E.g., if the configuration contains
|
45
|
+
# foo.bar.baz, this code will offer both "foo" and "foo.bar.baz"
|
46
|
+
# as completions for "fo".
|
47
|
+
v1 = @completions.grep(/^#{Regexp.escape(str)}/)
|
48
|
+
v2 = @completions.grep(/^#{Regexp.escape(bits.last)}/)
|
49
|
+
(v1 + v2.map {|x| (bits.slice(0..-2) << x).join(".") }).uniq
|
50
|
+
else
|
51
|
+
self.command_complete(str) +
|
52
|
+
@completions.grep(/^#{Regexp.escape(str)}/)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def command_complete(str)
|
57
|
+
commands.grep(/^#{Regexp.escape(str)}/)
|
58
|
+
end
|
59
|
+
|
60
|
+
def sanitize(str)
|
61
|
+
# ANSI code stripper regex cargo culted from
|
62
|
+
# http://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed
|
63
|
+
str.gsub(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/, "")
|
64
|
+
end
|
65
|
+
|
66
|
+
def run(context)
|
67
|
+
@completions = context.completions rescue Set.new
|
68
|
+
@run = true
|
69
|
+
puts
|
70
|
+
while @run && (line = Readline.readline("<3: ", true).strip)
|
71
|
+
# Treat ! as the shell out command
|
72
|
+
if line[0] == "!"
|
73
|
+
system(line.slice(1..-1))
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
if line.empty?
|
78
|
+
next
|
79
|
+
end
|
80
|
+
|
81
|
+
name, *args = line.split(/\s+/)
|
82
|
+
|
83
|
+
begin
|
84
|
+
@command_manager.handle(name, args, context)
|
85
|
+
rescue ArgumentError => e
|
86
|
+
puts e.message
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: harp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: starter
|
@@ -35,8 +35,11 @@ extra_rdoc_files: []
|
|
35
35
|
files:
|
36
36
|
- README.md
|
37
37
|
- LICENSE
|
38
|
-
- lib/harp.rb
|
39
38
|
- examples/usage.rb
|
39
|
+
- lib/harp/cli.rb
|
40
|
+
- lib/harp/command_manager.rb
|
41
|
+
- lib/harp/repl.rb
|
42
|
+
- lib/harp.rb
|
40
43
|
homepage: https://github.com/automatthew/harp
|
41
44
|
licenses: []
|
42
45
|
post_install_message:
|