harp 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +9 -0
- data/README.md +81 -0
- data/examples/usage.rb +49 -0
- data/lib/harp.rb +153 -0
- metadata +80 -0
data/LICENSE
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright (c) 2012 Matthew King
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
|
9
|
+
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# HARP
|
2
|
+
|
3
|
+
A mixin for creating application REPLs with Readline support.
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
gem install harp
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require "harp"
|
11
|
+
|
12
|
+
class UsefulThing
|
13
|
+
|
14
|
+
# define all the useful methods and behaviors you need
|
15
|
+
def use(adverb)
|
16
|
+
puts "You used me #{adverb}!"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Mix it in
|
20
|
+
include Harp
|
21
|
+
|
22
|
+
# Set it up
|
23
|
+
setup_repl do |repl|
|
24
|
+
|
25
|
+
on("help") do
|
26
|
+
commands = repl.commands
|
27
|
+
puts "* Available commands: " << commands.sort.join(" ")
|
28
|
+
puts "* Tab completion works for commands."
|
29
|
+
end
|
30
|
+
|
31
|
+
# Harp provides a "quit" command by default, but you can
|
32
|
+
# override it to add value.
|
33
|
+
on("quit") do
|
34
|
+
puts "Farewell to the girl with the sun in her eyes."
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set up a handler for a command where the first token is "!"
|
39
|
+
# I.e., shell out like Vim does.
|
40
|
+
on_bang do |args|
|
41
|
+
system args.first
|
42
|
+
end
|
43
|
+
|
44
|
+
# define a command that calls an instance method of your class.
|
45
|
+
# The block parameter is always an array, even if your regex
|
46
|
+
# had only one match group.
|
47
|
+
# This command will only accept a single-word argument (no
|
48
|
+
# whitespace allowed).
|
49
|
+
on(/use (\S+)$/) do |args|
|
50
|
+
self.use(args.first)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
UsefulThing.new.repl
|
58
|
+
```
|
59
|
+
|
60
|
+
|
61
|
+
## Usage
|
62
|
+
|
63
|
+
|
64
|
+
$ ruby -I lib/ examples/usage.rb
|
65
|
+
|
66
|
+
<3: help
|
67
|
+
* Available commands: help quit use
|
68
|
+
* Tab completion works for commands.
|
69
|
+
<3: use well
|
70
|
+
You used me well!
|
71
|
+
<3: use badly
|
72
|
+
You used me badly!
|
73
|
+
<3: use without proper care
|
74
|
+
command not found
|
75
|
+
<3: ! ls
|
76
|
+
LICENSE README.md consolize.gemspec examples lib
|
77
|
+
<3: quit
|
78
|
+
Farewell to the girl with the sun in her eyes.
|
79
|
+
|
80
|
+
|
81
|
+
|
data/examples/usage.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require "harp"
|
2
|
+
|
3
|
+
class UsefulThing
|
4
|
+
|
5
|
+
# define all the useful methods and behaviors you need
|
6
|
+
def use(adverb)
|
7
|
+
puts "You used me #{adverb}!"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Mix it in
|
11
|
+
include Harp
|
12
|
+
|
13
|
+
# Set it up
|
14
|
+
setup_repl do |repl|
|
15
|
+
|
16
|
+
on("help") do
|
17
|
+
commands = repl.commands
|
18
|
+
puts "* Available commands: " << commands.sort.join(" ")
|
19
|
+
puts "* Tab completion works for commands."
|
20
|
+
end
|
21
|
+
|
22
|
+
# Harp provides a "quit" command by default, but you can
|
23
|
+
# override it to add value.
|
24
|
+
on("quit") do
|
25
|
+
puts "Farewell to the girl with the sun in her eyes."
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
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
|
+
# define a command that calls an instance method of your class.
|
36
|
+
# The block parameter is always an array, even if your regex
|
37
|
+
# had only one match group.
|
38
|
+
# This command will only accept a single-word argument (no
|
39
|
+
# whitespace allowed).
|
40
|
+
on(/use (\S+)$/) do |args|
|
41
|
+
self.use(args.first)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
UsefulThing.new.repl
|
49
|
+
|
data/lib/harp.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# stdlib
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
require "readline"
|
5
|
+
|
6
|
+
Readline.completion_append_character = nil
|
7
|
+
#Readline.basic_word_break_characters = ""
|
8
|
+
|
9
|
+
|
10
|
+
module Harp
|
11
|
+
def self.included(mod)
|
12
|
+
mod.module_eval do
|
13
|
+
@repl = REPL.new
|
14
|
+
def self.repl
|
15
|
+
@repl
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.setup_repl(&block)
|
19
|
+
repl = @repl
|
20
|
+
@repl.on("quit") do
|
21
|
+
repl.exit
|
22
|
+
end
|
23
|
+
@repl.on("") do
|
24
|
+
puts "Giving me the silent treatment, eh?"
|
25
|
+
end
|
26
|
+
@repl.instance_exec(repl, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def repl
|
30
|
+
self.class.repl.run(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class REPL
|
36
|
+
|
37
|
+
attr_reader :store, :commands
|
38
|
+
def initialize
|
39
|
+
@patterns = {}
|
40
|
+
@commands = Set.new
|
41
|
+
Readline.completion_proc = self.method(:complete)
|
42
|
+
end
|
43
|
+
|
44
|
+
def complete(str)
|
45
|
+
case Readline.line_buffer
|
46
|
+
when /^\s*!/
|
47
|
+
# if we're in the middle of a bang-exec command, completion
|
48
|
+
# should look at the file system.
|
49
|
+
self.dir_complete(str)
|
50
|
+
else
|
51
|
+
# otherwise use the internal dict.
|
52
|
+
self.term_complete(str)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def dir_complete(str)
|
57
|
+
Dir.glob("#{str}*")
|
58
|
+
end
|
59
|
+
|
60
|
+
def term_complete(str)
|
61
|
+
# Terms can be either commands or indexes into the configuration
|
62
|
+
# data structure. No command contains a ".", so that's the test
|
63
|
+
# we use to distinguish.
|
64
|
+
bits = str.split(".")
|
65
|
+
if bits.size > 1
|
66
|
+
# Somebody should have documented this when he wrote it, because
|
67
|
+
# he now does not remember exactly what he was trying to achieve.
|
68
|
+
# He thinks that it's an attempt to allow completion of either
|
69
|
+
# full configuration index strings, or of component parts.
|
70
|
+
# E.g., if the configuration contains foo.bar.baz, this code
|
71
|
+
# will offer both "foo" and "foo.bar.baz" as completions for "fo".
|
72
|
+
v1 = @completions.grep(/^#{Regexp.escape(str)}/)
|
73
|
+
v2 = @completions.grep(/^#{Regexp.escape(bits.last)}/)
|
74
|
+
(v1 + v2.map {|x| (bits.slice(0..-2) << x).join(".") }).uniq
|
75
|
+
else
|
76
|
+
self.command_complete(str) +
|
77
|
+
@completions.grep(/^#{Regexp.escape(str)}/)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def command_complete(str)
|
82
|
+
@commands.grep(/^#{Regexp.escape(str)}/)
|
83
|
+
end
|
84
|
+
|
85
|
+
def sanitize(str)
|
86
|
+
# ANSI code stripper regex cargo culted from
|
87
|
+
# http://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed
|
88
|
+
str.gsub(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/, "")
|
89
|
+
end
|
90
|
+
|
91
|
+
def on(*pattern, &block)
|
92
|
+
pattern.flatten.each do |pattern|
|
93
|
+
@patterns[pattern] = block
|
94
|
+
self.add_command(pattern)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Helper for defining the action for the "!" command.
|
99
|
+
# Typically used to shell out, a la Vim.
|
100
|
+
def on_bang(&block)
|
101
|
+
on(/^\!\s*(.*)$/, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_command(pattern)
|
105
|
+
if pattern.is_a?(String)
|
106
|
+
@commands << pattern
|
107
|
+
else
|
108
|
+
bits = pattern.source.split(" ")
|
109
|
+
# TODO: figure out why you did this, then document it.
|
110
|
+
if bits.size > 1
|
111
|
+
@commands << bits.first
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def run(context)
|
117
|
+
@completions = context.completions rescue Set.new
|
118
|
+
@run = true
|
119
|
+
puts
|
120
|
+
while @run && line = Readline.readline("<3: ", true)
|
121
|
+
self.parse(context, line.chomp)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def exit
|
126
|
+
@run = false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Attempt to find a registered command that matches the input
|
130
|
+
# string. Upon failure, print an encouraging message.
|
131
|
+
def parse(context, input_string)
|
132
|
+
_p, block= @patterns.detect do |pattern, block|
|
133
|
+
pattern === input_string
|
134
|
+
end
|
135
|
+
if block
|
136
|
+
# Perlish global ugliness necessitated by the use of
|
137
|
+
# Enumerable#detect above. FIXME.
|
138
|
+
if $1
|
139
|
+
# if the regex had a group (based on the assumption that $1
|
140
|
+
# represents the result of the === that matched), call the block
|
141
|
+
# with all the group matches as arguments.
|
142
|
+
context.instance_exec($~[1..-1], &block)
|
143
|
+
else
|
144
|
+
context.instance_eval(&block)
|
145
|
+
end
|
146
|
+
else
|
147
|
+
puts "command not found"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: harp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matthew King
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rb-readline
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.4.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.4.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: starter
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.0
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.0
|
46
|
+
description:
|
47
|
+
email:
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- README.md
|
53
|
+
- LICENSE
|
54
|
+
- lib/harp.rb
|
55
|
+
- examples/usage.rb
|
56
|
+
homepage: https://github.com/automatthew/harp
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.23
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: A mixin for creating simple application repls with Readline support
|
80
|
+
test_files: []
|