harp 0.2.6 → 0.2.7
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/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:
|