hack_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +10 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +74 -0
  6. data/Rakefile +1 -0
  7. data/hack_tree.gemspec +22 -0
  8. data/hacks/hack_tree/reload.rb +18 -0
  9. data/hacks/ls.rb +74 -0
  10. data/lib/generators/hack_tree/USAGE +7 -0
  11. data/lib/generators/hack_tree/hack_tree_generator.rb +9 -0
  12. data/lib/generators/hack_tree/templates/INSTALL +20 -0
  13. data/lib/generators/hack_tree/templates/hack_tree.rb +4 -0
  14. data/lib/generators/hack_tree/templates/hello.rb +16 -0
  15. data/lib/hack_tree/action_context.rb +125 -0
  16. data/lib/hack_tree/config.rb +27 -0
  17. data/lib/hack_tree/dsl_context.rb +95 -0
  18. data/lib/hack_tree/instance.rb +153 -0
  19. data/lib/hack_tree/node/base.rb +34 -0
  20. data/lib/hack_tree/node/group.rb +8 -0
  21. data/lib/hack_tree/node/hack.rb +10 -0
  22. data/lib/hack_tree/node.rb +13 -0
  23. data/lib/hack_tree/parser/base.rb +26 -0
  24. data/lib/hack_tree/parser/desc.rb +66 -0
  25. data/lib/hack_tree/tools.rb +26 -0
  26. data/lib/hack_tree.rb +233 -0
  27. data/spec/lib/hack_tree/parser/desc_spec/000,brief.txt +1 -0
  28. data/spec/lib/hack_tree/parser/desc_spec/000,full.txt +3 -0
  29. data/spec/lib/hack_tree/parser/desc_spec/000.txt +5 -0
  30. data/spec/lib/hack_tree/parser/desc_spec/010,brief.txt +1 -0
  31. data/spec/lib/hack_tree/parser/desc_spec/010,full.txt +3 -0
  32. data/spec/lib/hack_tree/parser/desc_spec/010.txt +8 -0
  33. data/spec/lib/hack_tree/parser/desc_spec.rb +50 -0
  34. data/spec/lib/hack_tree/parser/spec_helper.rb +3 -0
  35. data/spec/lib/hack_tree/spec_helper.rb +3 -0
  36. data/spec/lib/spec_helper.rb +3 -0
  37. data/spec/spec_helper.rb +53 -0
  38. metadata +94 -0
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ # General Ruby.
2
+ .ref-*
3
+ .old*
4
+ *-old*
5
+
6
+ # Project-specific.
7
+ /*.rb
8
+ /doc/
9
+ /pkg/
10
+ /.rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ -fn # Tree-like progress.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem dependencies in `PROJECT.gemspec`.
4
+ gemspec
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,7 @@
1
+ Description:
2
+ Generate HackTree files.
3
+
4
+ Example:
5
+ `rails generate hack_tree`
6
+
7
+ This will create essential files for HackTree initialization in your project.
@@ -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,20 @@
1
+
2
+ Start console:
3
+
4
+ $ rails console
5
+
6
+ List available hacks:
7
+
8
+ >> c
9
+
10
+ Request help on a hack:
11
+
12
+ >> c.hello?
13
+
14
+ Use the hack:
15
+
16
+ >> c.hello
17
+ >> c.hello "Ruby"
18
+
19
+ Place your application's hacks in `lib/hacks/`.
20
+
@@ -0,0 +1,4 @@
1
+ if defined? IRB
2
+ HackTree.enable
3
+ c.hack_tree.reload
4
+ 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