hack_tree 0.1.0
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/.gitignore +10 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +74 -0
- data/Rakefile +1 -0
- data/hack_tree.gemspec +22 -0
- data/hacks/hack_tree/reload.rb +18 -0
- data/hacks/ls.rb +74 -0
- data/lib/generators/hack_tree/USAGE +7 -0
- data/lib/generators/hack_tree/hack_tree_generator.rb +9 -0
- data/lib/generators/hack_tree/templates/INSTALL +20 -0
- data/lib/generators/hack_tree/templates/hack_tree.rb +4 -0
- data/lib/generators/hack_tree/templates/hello.rb +16 -0
- data/lib/hack_tree/action_context.rb +125 -0
- data/lib/hack_tree/config.rb +27 -0
- data/lib/hack_tree/dsl_context.rb +95 -0
- data/lib/hack_tree/instance.rb +153 -0
- data/lib/hack_tree/node/base.rb +34 -0
- data/lib/hack_tree/node/group.rb +8 -0
- data/lib/hack_tree/node/hack.rb +10 -0
- data/lib/hack_tree/node.rb +13 -0
- data/lib/hack_tree/parser/base.rb +26 -0
- data/lib/hack_tree/parser/desc.rb +66 -0
- data/lib/hack_tree/tools.rb +26 -0
- data/lib/hack_tree.rb +233 -0
- data/spec/lib/hack_tree/parser/desc_spec/000,brief.txt +1 -0
- data/spec/lib/hack_tree/parser/desc_spec/000,full.txt +3 -0
- data/spec/lib/hack_tree/parser/desc_spec/000.txt +5 -0
- data/spec/lib/hack_tree/parser/desc_spec/010,brief.txt +1 -0
- data/spec/lib/hack_tree/parser/desc_spec/010,full.txt +3 -0
- data/spec/lib/hack_tree/parser/desc_spec/010.txt +8 -0
- data/spec/lib/hack_tree/parser/desc_spec.rb +50 -0
- data/spec/lib/hack_tree/parser/spec_helper.rb +3 -0
- data/spec/lib/hack_tree/spec_helper.rb +3 -0
- data/spec/lib/spec_helper.rb +3 -0
- data/spec/spec_helper.rb +53 -0
- metadata +94 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011-2012 Alex Fortuna
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
Organize and share your console hacks
|
3
|
+
=====================================
|
4
|
+
|
5
|
+
WARNING! THIS IS WORK IN PROGRESS, NOTHING IS GUARANDEED TO WORK AT ALL
|
6
|
+
-----------------------------------------------------------------------
|
7
|
+
|
8
|
+
Introduction
|
9
|
+
------------
|
10
|
+
|
11
|
+
HackTree lets you organize and share your console hacks in an effective and uniform way. Blah-blah-blah.
|
12
|
+
|
13
|
+
|
14
|
+
Setup (Rails 3)
|
15
|
+
---------------
|
16
|
+
|
17
|
+
Add to your `Gemfile`:
|
18
|
+
|
19
|
+
~~~
|
20
|
+
group :development do
|
21
|
+
gem "hack_tree"
|
22
|
+
end
|
23
|
+
~~~
|
24
|
+
|
25
|
+
Install the gem:
|
26
|
+
|
27
|
+
~~~
|
28
|
+
$ bundle install
|
29
|
+
~~~
|
30
|
+
|
31
|
+
|
32
|
+
Usage
|
33
|
+
-----
|
34
|
+
|
35
|
+
Start console:
|
36
|
+
|
37
|
+
~~~
|
38
|
+
$ rails console
|
39
|
+
~~~
|
40
|
+
|
41
|
+
List available hacks:
|
42
|
+
|
43
|
+
~~~
|
44
|
+
>> c
|
45
|
+
~~~
|
46
|
+
|
47
|
+
Request help on a hack:
|
48
|
+
|
49
|
+
~~~
|
50
|
+
>> c.hello?
|
51
|
+
~~~
|
52
|
+
|
53
|
+
Use the hack:
|
54
|
+
|
55
|
+
~~~
|
56
|
+
>> c.hello
|
57
|
+
>> c.hello "Ruby"
|
58
|
+
~~~
|
59
|
+
|
60
|
+
Place your application's hacks in `lib/hacks/`.
|
61
|
+
|
62
|
+
|
63
|
+
Copyright
|
64
|
+
---------
|
65
|
+
|
66
|
+
Copyright © 2012 Alex Fortuna.
|
67
|
+
|
68
|
+
Licensed under the MIT License.
|
69
|
+
|
70
|
+
|
71
|
+
Feedback
|
72
|
+
--------
|
73
|
+
|
74
|
+
Send bug reports, suggestions and criticisms through [project's page on GitHub](http://github.com/dadooda/hack_tree).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/hack_tree.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "lib")
|
2
|
+
require "hack_tree"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "hack_tree"
|
6
|
+
s.version = HackTree::VERSION
|
7
|
+
s.authors = ["Alex Fortuna"]
|
8
|
+
s.email = ["alex.r@askit.org"]
|
9
|
+
s.homepage = "http://github.com/dadooda/hack_tree"
|
10
|
+
|
11
|
+
# Copy these from class's description, adjust markup.
|
12
|
+
s.summary = %q{Organize and share your console hacks}
|
13
|
+
s.description = %q{HackTree lets you organize and share your console hacks in an effective and uniform way. Blah-blah-blah.}
|
14
|
+
# end of s.description=
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
HackTree.define do
|
2
|
+
desc "HackTree management hacks"
|
3
|
+
group :hack_tree do
|
4
|
+
desc <<-EOT
|
5
|
+
Reload application hacks
|
6
|
+
|
7
|
+
Load every file under `lib/hacks/`.
|
8
|
+
EOT
|
9
|
+
hack :reload do
|
10
|
+
Dir["lib/hacks/**/*.rb"].each do |fn|
|
11
|
+
load fn
|
12
|
+
end
|
13
|
+
|
14
|
+
# Signal something more positive than nil.
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/hacks/ls.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
HackTree.define do
|
2
|
+
desc <<-EOT
|
3
|
+
List groups/hacks globally
|
4
|
+
|
5
|
+
Examples:
|
6
|
+
|
7
|
+
>> c.ls /^ls$/
|
8
|
+
ls # List groups/hacks globally
|
9
|
+
|
10
|
+
>> c.ls /he/
|
11
|
+
hello # Say hello to the world or to a specific person
|
12
|
+
|
13
|
+
>> c.ls /person/
|
14
|
+
hello # Say hello to the world or to a specific person
|
15
|
+
|
16
|
+
>> c.ls "dsl"
|
17
|
+
dsl/ # Get domain-specific language contexts
|
18
|
+
dsl.gemfile # Get Bundler `Gemfile` context
|
19
|
+
dsl.rspec # Get RSpec specfile context
|
20
|
+
|
21
|
+
>> c.ls "dsl.r"
|
22
|
+
dsl.rspec # Get RSpec specfile context
|
23
|
+
EOT
|
24
|
+
hack :ls do |filter = nil|
|
25
|
+
nodes = @nodes
|
26
|
+
|
27
|
+
# Apply filter.
|
28
|
+
if filter
|
29
|
+
re = case filter
|
30
|
+
when Regexp
|
31
|
+
filter
|
32
|
+
when String
|
33
|
+
Regexp.compile(Regexp.escape(filter))
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Unsupported filter #{filter.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
nodes = nodes.select do |r|
|
39
|
+
[
|
40
|
+
# Logical order.
|
41
|
+
r.global_name =~ re,
|
42
|
+
r.brief_desc && r.brief_desc =~ re,
|
43
|
+
|
44
|
+
# TODO: Include full as an option, later.
|
45
|
+
#r.full_desc && r.full_desc =~ re, # Better without full_desc, or examples may match.
|
46
|
+
].any?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
nodes = nodes.sort_by do |node|
|
51
|
+
[
|
52
|
+
node.is_a?(::HackTree::Node::Group) ? 0 : 1, # Groups first.
|
53
|
+
node.name.to_s,
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Compute name alignment width.
|
58
|
+
names = nodes.map {|node| ::HackTree::Tools.format_node_name(node)}
|
59
|
+
name_align = ::HackTree::Tools.compute_name_align(names, @conf.global_name_align)
|
60
|
+
|
61
|
+
nodes.each do |node|
|
62
|
+
brief_desc = node.brief_desc || ::HackTree.conf.brief_desc_stub
|
63
|
+
|
64
|
+
fmt = "%-#{name_align}s%s"
|
65
|
+
|
66
|
+
::Kernel.puts(fmt % [
|
67
|
+
::HackTree::Tools.format_node_name(node, node.global_name),
|
68
|
+
brief_desc ? " # #{brief_desc}" : "",
|
69
|
+
])
|
70
|
+
end # nodes.each
|
71
|
+
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class HackTreeGenerator < Rails::Generators::Base #:nodoc:
|
2
|
+
source_root File.join(File.dirname(__FILE__), "templates")
|
3
|
+
|
4
|
+
def go
|
5
|
+
copy_file (bn = "hack_tree.rb"), "config/initializers/#{bn}"
|
6
|
+
copy_file (bn = "hello.rb"), "lib/hacks/#{bn}"
|
7
|
+
readme "INSTALL"
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
HackTree.define do
|
2
|
+
desc <<-EOT
|
3
|
+
Say hello to the world or to a specific person
|
4
|
+
|
5
|
+
Examples:
|
6
|
+
|
7
|
+
>> c.hello
|
8
|
+
Hello, world!
|
9
|
+
|
10
|
+
>> c.hello "Ruby"
|
11
|
+
Hello, Ruby!
|
12
|
+
EOT
|
13
|
+
hack :hello do |*args|
|
14
|
+
puts "Hello, %s!" % (args[0] || "world")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module HackTree
|
2
|
+
# Context to execute the actions.
|
3
|
+
class ActionContext
|
4
|
+
# NOTE: No useless methods here, please.
|
5
|
+
|
6
|
+
def initialize(instance, parent = nil)
|
7
|
+
@instance, @parent = instance, parent
|
8
|
+
|
9
|
+
# Suppress warnings.
|
10
|
+
vrb, $VERBOSE = $VERBOSE, nil
|
11
|
+
|
12
|
+
# Insert lookup routine for existing methods.
|
13
|
+
(methods.map(&:to_s) - Node::FORBIDDEN_NAMES.map(&:to_s)).each do |method_name|
|
14
|
+
next if not method_name =~ /\A(#{Node::NAME_REGEXP})\?{,1}\z/
|
15
|
+
node_name = $1.to_sym
|
16
|
+
instance_eval <<-EOT
|
17
|
+
def #{method_name}(*args)
|
18
|
+
if @instance.find_local_node(:#{node_name}, @parent)
|
19
|
+
_dispatch(:#{method_name}, *args)
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
EOT
|
25
|
+
end
|
26
|
+
|
27
|
+
# Restore warnings.
|
28
|
+
$VERBOSE = vrb
|
29
|
+
|
30
|
+
# Create direct methods. In case your console's completion is sane by itself, it will help.
|
31
|
+
# IRB's default completion isn't sane yet (2012-02-25).
|
32
|
+
@instance.nodes.select do |node|
|
33
|
+
node.parent == @parent
|
34
|
+
end.each do |node|
|
35
|
+
# NOTE: This is slightly different from "existing method lookup routine" used above. Let's keep them separate.
|
36
|
+
instance_eval <<-EOT
|
37
|
+
def #{node.name}(*args)
|
38
|
+
_dispatch(:#{node.name}, *args)
|
39
|
+
end
|
40
|
+
EOT
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
# NOTE: Exceptions raised here result in `(Object doesn't support #inspect)`. No other details are available, be careful.
|
46
|
+
|
47
|
+
nodes = @instance.nodes.select {|node| node.parent == @parent}
|
48
|
+
|
49
|
+
# Empty group?
|
50
|
+
if nodes.empty?
|
51
|
+
::Kernel.puts "No groups/hacks here"
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Not empty group, list contents.
|
56
|
+
|
57
|
+
nodes = nodes.sort_by do |node|
|
58
|
+
[
|
59
|
+
node.is_a?(Node::Group) ? 0 : 1, # Groups first.
|
60
|
+
node.name.to_s,
|
61
|
+
]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Compute name alignment width.
|
65
|
+
names = nodes.map {|node| Tools.format_node_name(node)}
|
66
|
+
name_align = Tools.compute_name_align(names, @instance.conf.local_name_align)
|
67
|
+
|
68
|
+
nodes.each do |node|
|
69
|
+
brief_desc = node.brief_desc || @instance.conf.brief_desc_stub
|
70
|
+
|
71
|
+
fmt = "%-#{name_align}s%s"
|
72
|
+
|
73
|
+
::Kernel.puts(fmt % [
|
74
|
+
Tools.format_node_name(node),
|
75
|
+
brief_desc ? " # #{brief_desc}" : "",
|
76
|
+
])
|
77
|
+
end # nodes.each
|
78
|
+
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def method_missing(method_name, *args)
|
83
|
+
_dispatch(method_name.to_sym, *args)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# _dispatch(:hello) # Group/hack request.
|
89
|
+
# _dispatch(:hello?) # Help request.
|
90
|
+
def _dispatch(request, *args)
|
91
|
+
raise ArgumentError, "Invalid request #{request.inspect}" if not request.to_s =~ /\A(#{Node::NAME_REGEXP})(\?{,1})\z/
|
92
|
+
node_name = $1.to_sym
|
93
|
+
is_question = ($2 != "")
|
94
|
+
|
95
|
+
node = @instance.find_local_node(node_name, @parent)
|
96
|
+
|
97
|
+
# NOTE: Method return result.
|
98
|
+
if node
|
99
|
+
if is_question
|
100
|
+
# Help request.
|
101
|
+
out = [
|
102
|
+
node.brief_desc || @instance.conf.brief_desc_stub,
|
103
|
+
(["", node.full_desc] if node.full_desc),
|
104
|
+
].flatten(1).compact
|
105
|
+
|
106
|
+
::Kernel.puts out.empty?? "No description, please provide one" : out
|
107
|
+
else
|
108
|
+
# Group/hack request.
|
109
|
+
case node
|
110
|
+
when Node::Group
|
111
|
+
# Create and return a new nested access context.
|
112
|
+
self.class.new(@instance, node)
|
113
|
+
when Node::Hack
|
114
|
+
# Invoke hack in the context of `HackTree` instance.
|
115
|
+
@instance.instance_exec(*args, &node.block)
|
116
|
+
else
|
117
|
+
raise "Unknown node class #{node.class}, SE"
|
118
|
+
end
|
119
|
+
end # if is_question
|
120
|
+
else
|
121
|
+
::Kernel.puts "Node not found: '#{node_name}'"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end # ActionContext
|
125
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HackTree
|
2
|
+
# Configuration object.
|
3
|
+
class Config
|
4
|
+
# If set, substitute missing brief descriptions with this text.
|
5
|
+
attr_accessor :brief_desc_stub
|
6
|
+
|
7
|
+
# Aligned width of full names, Range.
|
8
|
+
#
|
9
|
+
# global_name_align = 16..32
|
10
|
+
attr_accessor :global_name_align
|
11
|
+
|
12
|
+
# Mask to format group names (Kernel::sprintf).
|
13
|
+
attr_accessor :group_format
|
14
|
+
|
15
|
+
# Mask to format hack names (Kernel::sprintf).
|
16
|
+
attr_accessor :hack_format
|
17
|
+
|
18
|
+
# Aligned width of (namespaced) names, Range.
|
19
|
+
#
|
20
|
+
# local_name_align = 16..32
|
21
|
+
attr_accessor :local_name_align
|
22
|
+
|
23
|
+
def initialize(attrs = {})
|
24
|
+
attrs.each {|k, v| send("#{k}=", v)}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module HackTree
|
2
|
+
# Definition DSL context.
|
3
|
+
class DslContext
|
4
|
+
# NOTE: Please keep methods to an absolute minimum. This is the DSL, I want as little confusion as possible.
|
5
|
+
|
6
|
+
# Initialize self.
|
7
|
+
def initialize(instance, parent = nil)
|
8
|
+
@instance, @parent = instance, parent
|
9
|
+
@desc_parser = Parser::Desc.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def desc(text)
|
13
|
+
@brief_desc, @full_desc = @desc_parser[text]
|
14
|
+
end
|
15
|
+
|
16
|
+
def group(name, &block)
|
17
|
+
raise "Code block expected" if not block
|
18
|
+
|
19
|
+
name = name.to_sym
|
20
|
+
|
21
|
+
# TODO: Check forbidden names.
|
22
|
+
|
23
|
+
# It is allowed to reopen the groups. Find the named group.
|
24
|
+
group = @instance.nodes.find {|r| r.name == name}
|
25
|
+
|
26
|
+
if group
|
27
|
+
if not group.is_a? Node::Group
|
28
|
+
raise "Node '#{name}' already exists and it's not a group"
|
29
|
+
end
|
30
|
+
|
31
|
+
# It is allowed to redefine group description with another description.
|
32
|
+
# If there is no description, the original one is retained on reopen.
|
33
|
+
if @brief_desc
|
34
|
+
group.brief_desc = @brief_desc
|
35
|
+
group.full_desc = @full_desc
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# Create.
|
39
|
+
group = Node::Group.new({
|
40
|
+
:brief_desc => @brief_desc,
|
41
|
+
:full_desc => @full_desc,
|
42
|
+
:name => name,
|
43
|
+
:parent => @parent,
|
44
|
+
})
|
45
|
+
|
46
|
+
@instance.nodes << group
|
47
|
+
end
|
48
|
+
|
49
|
+
# Clear last used descriptions.
|
50
|
+
@brief_desc = @full_desc = nil
|
51
|
+
|
52
|
+
# Create sub-context and dive into it.
|
53
|
+
context = self.class.new(@instance, group)
|
54
|
+
context.instance_eval(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def hack(name, &block)
|
58
|
+
raise "Code block expected" if not block
|
59
|
+
|
60
|
+
name = name.to_sym
|
61
|
+
|
62
|
+
# TODO: Check forbidden names.
|
63
|
+
|
64
|
+
# It is allowed to redefine the hacks. Find the named hack.
|
65
|
+
hack = @instance.nodes.find {|r| r.name == name}
|
66
|
+
|
67
|
+
if hack
|
68
|
+
if not hack.is_a? Node::Hack
|
69
|
+
raise "Node '#{name}' already exists and it's not a hack"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Modify hack.
|
73
|
+
hack.brief_desc = @brief_desc
|
74
|
+
hack.full_desc = @full_desc
|
75
|
+
hack.block = block
|
76
|
+
else
|
77
|
+
# Create.
|
78
|
+
hack = Node::Hack.new({
|
79
|
+
:block => block,
|
80
|
+
:brief_desc => @brief_desc,
|
81
|
+
:full_desc => @full_desc,
|
82
|
+
:name => name,
|
83
|
+
:parent => @parent,
|
84
|
+
})
|
85
|
+
|
86
|
+
@instance.nodes << hack
|
87
|
+
end
|
88
|
+
|
89
|
+
# Clear last used descriptions.
|
90
|
+
@brief_desc = @full_desc = nil
|
91
|
+
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end # DslContext
|
95
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module HackTree
|
2
|
+
# Instance of the system.
|
3
|
+
class Instance
|
4
|
+
# Configuration object.
|
5
|
+
attr_accessor :conf
|
6
|
+
|
7
|
+
# Array of Node::Base successors.
|
8
|
+
attr_accessor :nodes
|
9
|
+
|
10
|
+
def initialize(attrs = {})
|
11
|
+
clear
|
12
|
+
attrs.each {|k, v| send("#{k}=", v)}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get action object.
|
16
|
+
#
|
17
|
+
# >> r.action
|
18
|
+
# hello # Say hello
|
19
|
+
# >> r.action.hello
|
20
|
+
# Hello, world!
|
21
|
+
def action
|
22
|
+
ActionContext.new(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create action object. See HackTree::action for examples.
|
26
|
+
def action
|
27
|
+
ActionContext.new(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
# List direct children of <tt>node</tt>. Return Array, possibly an empty one.
|
31
|
+
#
|
32
|
+
# children_of(nil) # => [...], children of root.
|
33
|
+
# children_of(node) # => [...], children of `node`.
|
34
|
+
def children_of(parent)
|
35
|
+
@nodes.select {|node| node.parent == parent}
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear
|
39
|
+
clear_conf
|
40
|
+
clear_nodes
|
41
|
+
end
|
42
|
+
|
43
|
+
def clear_conf
|
44
|
+
@conf = Config.new({
|
45
|
+
:brief_desc_stub => "(no description)",
|
46
|
+
:global_name_align => 16..32,
|
47
|
+
:group_format => "%s/",
|
48
|
+
:hack_format => "%s",
|
49
|
+
:local_name_align => 16..32,
|
50
|
+
})
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def clear_nodes
|
55
|
+
@nodes = []
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# Perform logic needed to do IRB completion. Returns Array if completion is handled successfully, <tt>nil</tt>
|
60
|
+
# if input is not related to HackTree.
|
61
|
+
#
|
62
|
+
# completion_logic("c.he", :enabled_as => :c) # => ["c.hello"]
|
63
|
+
def completion_logic(input, options = {})
|
64
|
+
o = {}
|
65
|
+
options = options.dup
|
66
|
+
|
67
|
+
o[k = :enabled_as] = options.delete(k)
|
68
|
+
|
69
|
+
raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty?
|
70
|
+
raise ArgumentError, "options[:enabled_as] must be given" if not o[:enabled_as]
|
71
|
+
|
72
|
+
# Check if this input is related to us.
|
73
|
+
if not mat = input.match(/\A#{o[:enabled_as]}\.((?:[^.].*)|)\z/)
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
lookup = mat[1]
|
78
|
+
|
79
|
+
# Parse lookup string into node name and prefix.
|
80
|
+
global_name, prefix = if mat = lookup.match(/\A(?:([^.]*)|(.+)\.(.*?))\z/)
|
81
|
+
if mat[1]
|
82
|
+
# "something".
|
83
|
+
["", mat[1]]
|
84
|
+
else
|
85
|
+
# "something.other", "something.other.other.".
|
86
|
+
[mat[2], mat[3]]
|
87
|
+
end
|
88
|
+
else
|
89
|
+
# Handle no match just in case.
|
90
|
+
["", ""]
|
91
|
+
end
|
92
|
+
|
93
|
+
base = if global_name == ""
|
94
|
+
# Base is root.
|
95
|
+
nil
|
96
|
+
else
|
97
|
+
# Find a named base node. If not found, return no candidates right away.
|
98
|
+
find_node(global_name) or return []
|
99
|
+
end
|
100
|
+
|
101
|
+
# Select sub-nodes.
|
102
|
+
candidates = children_of(base).select do |node|
|
103
|
+
# Select those matching `prefix`.
|
104
|
+
node.name.to_s.index(prefix) == 0
|
105
|
+
end.map do |node|
|
106
|
+
# Provide 1+ candidates per item.
|
107
|
+
case node
|
108
|
+
when Node::Group
|
109
|
+
# A neat trick to prevent IRB from appending a " " after group name.
|
110
|
+
[node.name, "#{node.name}."]
|
111
|
+
else
|
112
|
+
[node.name]
|
113
|
+
end.map(&:to_s)
|
114
|
+
end.flatten(1).map do |s|
|
115
|
+
# Convert to final names.
|
116
|
+
[
|
117
|
+
"#{o[:enabled_as]}.",
|
118
|
+
("#{global_name}." if global_name != ""),
|
119
|
+
s,
|
120
|
+
].compact.join
|
121
|
+
end
|
122
|
+
|
123
|
+
candidates
|
124
|
+
end
|
125
|
+
|
126
|
+
# Define groups/hacks via the DSL. See HackTree::define for examples.
|
127
|
+
def define(&block)
|
128
|
+
raise "Code block expected" if not block
|
129
|
+
DslContext.new(self).instance_eval(&block)
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# Search for the node by its global name, return <tt>Node::*</tt> or <tt>nil</tt>.
|
134
|
+
#
|
135
|
+
# find_node("hello") # => Node::* or nil
|
136
|
+
# find_node("do.some.stuff") # => Node::* or nil
|
137
|
+
#
|
138
|
+
# See also: #find_local_node.
|
139
|
+
def find_node(global_name)
|
140
|
+
@nodes.find {|node| node.global_name == global_name}
|
141
|
+
end
|
142
|
+
|
143
|
+
# Search for a local node, return <tt>Node::*</tt> or <tt>nil</tt>.
|
144
|
+
#
|
145
|
+
# find_node(:hello) # Search at root.
|
146
|
+
# find_node(:hello, :parent => grp) # Search in `grp` group.
|
147
|
+
#
|
148
|
+
# See also: #find_node.
|
149
|
+
def find_local_node(name, parent)
|
150
|
+
@nodes.find {|r| r.name == name.to_sym and r.parent == parent}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|