module HackTree # Instance of the system. class Instance # Configuration object. attr_accessor :conf # Array of Node::Base successors. attr_accessor :nodes def initialize(attrs = {}) clear attrs.each {|k, v| send("#{k}=", v)} end # Create action object. # # >> r.action # hello # Say hello # # >> r.action.hello # Hello, world! def action ActionContext.new(self) end # List direct children of node. Return Array, possibly an empty one. # # children_of(nil) # => [...], children of root. # children_of(node) # => [...], children of `node`. def children_of(parent) @nodes.select {|node| node.parent == parent} end def clear clear_conf clear_nodes end def clear_conf @conf = Config.new({ :brief_desc_stub => "(no description)", :global_name_align => 16..32, :group_format => "%s/", :hack_format => "%s", :local_name_align => 16..32, }) nil end def clear_nodes @nodes = [] nil end # Perform logic needed to do IRB completion. Returns Array if completion is handled successfully, nil # if input is not related to HackTree. # # completion_logic("c.he", :enabled_as => :c) # => ["c.hello"] def completion_logic(input, options = {}) o = {} options = options.dup o[k = :enabled_as] = options.delete(k) raise ArgumentError, "Unknown option(s): #{options.inspect}" if not options.empty? raise ArgumentError, "options[:enabled_as] must be given" if not o[:enabled_as] # Check if this input is related to us. if not mat = input.match(/\A#{o[:enabled_as]}\.((?:[^.].*)|)\z/) return nil end lookup = mat[1] # Parse lookup string into node name and prefix. global_name, prefix = if mat = lookup.match(/\A(?:([^.]*)|(.+)\.(.*?))\z/) if mat[1] # "something". ["", mat[1]] else # "something.other", "something.other.other.". [mat[2], mat[3]] end else # Handle no match just in case. ["", ""] end base = if global_name == "" # Base is root. nil else # Find a named base node. If not found, return no candidates right away. find_node(global_name) or return [] end # Select sub-nodes. candidates = children_of(base).select do |node| # Select those matching `prefix`. node.name.to_s.index(prefix) == 0 end.map do |node| # Provide 1+ candidates per item. case node when Node::Group # A neat trick to prevent IRB from appending a " " after group name. [node.name, "#{node.name}."] else [node.name] end.map(&:to_s) end.flatten(1).map do |s| # Convert to final names. [ "#{o[:enabled_as]}.", ("#{global_name}." if global_name != ""), s, ].compact.join end candidates end # Define groups/hacks via the DSL. See HackTree::define for examples. def define(&block) raise "Code block expected" if not block DslContext.new(self).instance_eval(&block) nil end # Search for the node by its global name, return Node::* or nil. # # find_node("hello") # => Node::* or nil # find_node("do.some.stuff") # => Node::* or nil # # See also: #find_local_node. def find_node(global_name) @nodes.find {|node| node.global_name == global_name} end # Search for a local node, return Node::* or nil. # # find_node(:hello) # Search at root. # find_node(:hello, :parent => grp) # Search in `grp` group. # # See also: #find_node. def find_local_node(name, parent) @nodes.find {|r| r.name == name.to_sym and r.parent == parent} end end end