summaryrefslogtreecommitdiff
path: root/lib/menu.rb
blob: b5861517dc6841e317e6429800a6f9c8b919b576 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#
# A navigation menu class
#

class Menu
  attr_accessor :parent
  attr_accessor :children
  attr_accessor :name

  #
  # load the menu.txt file and build the in-memory menu array
  #
  def load(menu_file_path)
    File.open(menu_file_path) do |file|
      parse_menu(file)
    end
  end

  def initialize(name, parent=nil)
    self.name = name
    self.parent = parent
    self.children = []
  end

  ##
  ## public methods
  ##

  #
  # returns the menu under the item that matches item_name.
  #
  def submenu(item_name=nil)
    if item_name
      self.children.detect {|child| child.name == item_name}
    else
      self.children
    end
  end

  #
  # returns path from root to this leaf as an array
  #
  def path
    @path ||= begin
      if parent == nil
        []
      else
        parent.path + [name]
      end
    end
  end

  def path_str
    @path_str ||= path.join('/')
  end

  def each(&block)
    children.each(&block)
  end

  def size
    children.size
  end

  #
  # returns true if menu's path starts with +path_prefix+
  #
  def path_starts_with?(path_prefix)
    array_starts_with?(path, path_prefix)
  end

  def path_prefix_of?(full_path)
    array_starts_with?(full_path, path)
  end

  #
  # returns true if this menu item is the terminus menu item for path.
  # (meaning that there are no children that match more path segments)
  #
  def leaf_for_path?(path)
    return false unless path_prefix_of?(path)
    next_path_segment = (path - self.path).first
    return false if next_path_segment.nil?
    return !children.detect {|i| i.name == next_path_segment}
  end

  def inspect(indent=0)
    lines = []
    lines << '  '*indent + '- ' + self.name
    self.children.each do |child|
      lines << child.inspect(indent+1)
    end
    lines.join("\n")
  end

  #
  # private & protected methods
  #

  protected

  def add_child(name)
    self.children << Menu.new(name, self)
  end

  private

  def array_starts_with?(big_array, small_array)
    small_array.length.times do |i|
      if small_array[i] != big_array[i]
        return false
      end
    end
    return true
  end


  def parse_menu(file)
    while true
      item = file.readline
      if item.strip.chars.any? && item !~ /^\s*#/
        depth = item.scan("  ").size
        last_menu_at_depth(depth).add_child(item.strip)
      end
    end
  rescue EOFError
    # done loading
  end

  #
  # returns the last list of children at the specified depth
  #
  def last_menu_at_depth(depth)
    menu = self
    depth.times { menu = menu.children.last }
    menu
  end

end