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.
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