AXElements 0.6.0beta2 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/.yardopts +1 -2
  2. data/README.markdown +152 -88
  3. data/Rakefile +8 -103
  4. data/docs/Debugging.markdown +9 -2
  5. data/docs/KeyboardEvents.markdown +114 -49
  6. data/docs/Setting.markdown +1 -0
  7. data/docs/images/next_version.png +0 -0
  8. data/ext/accessibility/key_coder/extconf.rb +22 -0
  9. data/ext/accessibility/key_coder/key_coder.c +113 -0
  10. data/lib/AXElements.rb +2 -0
  11. data/lib/accessibility/core.rb +897 -0
  12. data/lib/accessibility/debug.rb +168 -0
  13. data/lib/accessibility/dsl.rb +697 -0
  14. data/lib/accessibility/enumerators.rb +104 -0
  15. data/lib/accessibility/errors.rb +32 -0
  16. data/lib/accessibility/factory.rb +153 -0
  17. data/lib/accessibility/graph.rb +150 -0
  18. data/lib/{ax_elements/inspector.rb → accessibility/pp_inspector.rb} +39 -28
  19. data/lib/accessibility/qualifier.rb +158 -0
  20. data/lib/accessibility/string.rb +494 -0
  21. data/lib/accessibility/translator.rb +178 -0
  22. data/lib/accessibility/version.rb +7 -0
  23. data/lib/accessibility.rb +79 -0
  24. data/lib/ax/application.rb +234 -0
  25. data/lib/{ax_elements/elements → ax}/button.rb +2 -0
  26. data/lib/ax/element.rb +518 -0
  27. data/lib/{ax_elements/elements → ax}/radio_button.rb +2 -0
  28. data/lib/ax/row.rb +37 -0
  29. data/lib/{ax_elements/elements → ax}/static_text.rb +2 -0
  30. data/lib/ax/systemwide.rb +86 -0
  31. data/lib/ax_elements/awesome_print.rb +25 -0
  32. data/lib/ax_elements/exception_workaround.rb +8 -0
  33. data/lib/ax_elements/nsarray_compat.rb +64 -0
  34. data/lib/ax_elements/vendor/inflection_data.rb +65 -0
  35. data/lib/ax_elements/vendor/inflections.rb +172 -0
  36. data/lib/ax_elements/vendor/inflector.rb +306 -0
  37. data/lib/ax_elements.rb +14 -25
  38. data/lib/minitest/ax_elements.rb +112 -12
  39. data/lib/mouse.rb +72 -46
  40. data/lib/rspec/expectations/ax_elements.rb +133 -6
  41. data/rakelib/doc.rake +13 -0
  42. data/rakelib/ext.rake +61 -0
  43. data/rakelib/gem.rake +28 -0
  44. data/rakelib/test.rake +53 -0
  45. data/test/helper.rb +11 -97
  46. data/test/integration/accessibility/test_core.rb +18 -0
  47. data/test/integration/accessibility/test_debug.rb +44 -0
  48. data/test/integration/accessibility/test_dsl.rb +225 -0
  49. data/test/integration/accessibility/test_enumerators.rb +122 -0
  50. data/test/integration/accessibility/test_errors.rb +38 -0
  51. data/test/integration/accessibility/test_notifications.rb +22 -0
  52. data/test/integration/accessibility/test_qualifier.rb +148 -0
  53. data/test/integration/ax/test_application.rb +56 -0
  54. data/test/integration/ax/test_element.rb +46 -0
  55. data/test/integration/ax/test_row.rb +23 -0
  56. data/test/integration/ax_elements/test_nsarray_compat.rb +43 -0
  57. data/test/integration/minitest/test_ax_elements.rb +98 -0
  58. data/test/integration/rspec/expectations/test_ax_elements.rb +58 -0
  59. data/test/integration/test_mouse.rb +35 -0
  60. data/test/sanity/accessibility/test_core.rb +553 -0
  61. data/test/sanity/accessibility/test_debug.rb +63 -0
  62. data/test/sanity/accessibility/test_dsl.rb +75 -0
  63. data/test/sanity/accessibility/test_errors.rb +10 -0
  64. data/test/sanity/accessibility/test_factory.rb +88 -0
  65. data/test/sanity/accessibility/test_pp_inspector.rb +110 -0
  66. data/test/sanity/accessibility/test_qualifier.rb +13 -0
  67. data/test/sanity/accessibility/test_string.rb +238 -0
  68. data/test/sanity/accessibility/test_translator.rb +145 -0
  69. data/test/sanity/ax/test_application.rb +90 -0
  70. data/test/sanity/ax/test_element.rb +80 -0
  71. data/test/sanity/ax/test_systemwide.rb +66 -0
  72. data/test/sanity/ax_elements/test_nsarray_compat.rb +16 -0
  73. data/test/sanity/ax_elements/test_nsobject_inspect.rb +11 -0
  74. data/test/sanity/minitest/test_ax_elements.rb +15 -0
  75. data/test/sanity/rspec/expectations/test_ax_elements.rb +12 -0
  76. data/test/sanity/test_ax_elements.rb +10 -0
  77. data/test/sanity/test_mouse.rb +19 -0
  78. metadata +111 -93
  79. data/LICENSE.txt +0 -25
  80. data/ext/key_coder/extconf.rb +0 -6
  81. data/ext/key_coder/key_coder.m +0 -77
  82. data/lib/ax_elements/accessibility/enumerators.rb +0 -104
  83. data/lib/ax_elements/accessibility/graph.rb +0 -118
  84. data/lib/ax_elements/accessibility/language.rb +0 -347
  85. data/lib/ax_elements/accessibility/qualifier.rb +0 -73
  86. data/lib/ax_elements/accessibility.rb +0 -166
  87. data/lib/ax_elements/core.rb +0 -541
  88. data/lib/ax_elements/element.rb +0 -593
  89. data/lib/ax_elements/elements/application.rb +0 -88
  90. data/lib/ax_elements/elements/row.rb +0 -30
  91. data/lib/ax_elements/elements/systemwide.rb +0 -46
  92. data/lib/ax_elements/macruby_extensions.rb +0 -255
  93. data/lib/ax_elements/notification.rb +0 -37
  94. data/lib/ax_elements/version.rb +0 -9
  95. data/test/elements/test_application.rb +0 -72
  96. data/test/elements/test_row.rb +0 -27
  97. data/test/elements/test_systemwide.rb +0 -38
  98. data/test/test_accessibility.rb +0 -127
  99. data/test/test_blankness.rb +0 -26
  100. data/test/test_core.rb +0 -448
  101. data/test/test_element.rb +0 -939
  102. data/test/test_enumerators.rb +0 -81
  103. data/test/test_inspector.rb +0 -130
  104. data/test/test_language.rb +0 -157
  105. data/test/test_macruby_extensions.rb +0 -303
  106. data/test/test_mouse.rb +0 -5
  107. data/test/test_search_semantics.rb +0 -143
@@ -0,0 +1,104 @@
1
+ ##
2
+ # Namespace for enumerators used to navigate accessibility hierarchies.
3
+ module Accessibility::Enumerators
4
+
5
+ ##
6
+ # Enumerator for visiting each element in a UI hierarchy in breadth
7
+ # first order.
8
+ class BreadthFirst
9
+ include Enumerable
10
+
11
+ # @param [AX::Element]
12
+ def initialize root
13
+ @root = root
14
+ end
15
+
16
+ ##
17
+ # Semi-lazily iterate through the tree.
18
+ #
19
+ # @yieldparam [AX::Element]
20
+ def each
21
+ # @todo Lazy-wrap element refs, might make things a bit faster
22
+ # for fat trees; what is impact on thin trees?
23
+ # @todo See if we can implement the method in a single loop
24
+ queue = [@root]
25
+ until queue.empty?
26
+ queue.shift.attribute(:children).each do |x|
27
+ queue << x if x.attributes.include? :children
28
+ yield x
29
+ end
30
+ end
31
+ end
32
+
33
+ ##
34
+ # @note Explicitly defined so that escaping at the first found element
35
+ # actually works. Since only a single `break` is called when an
36
+ # item is found it does not fully escape the method. Technically,
37
+ # we need to do this with other 'escape-early' iteraters, but
38
+ # they aren't being used...yet.
39
+ #
40
+ # Override `Enumerable#find` for performance reasons.
41
+ def find
42
+ each { |x| return x if yield x }
43
+ end
44
+
45
+ end
46
+
47
+ ##
48
+ # Enumerator for visitng each element in a UI hierarchy in
49
+ # depth first order.
50
+ class DepthFirst
51
+ include Enumerable
52
+
53
+ # @param [AX::Element]
54
+ def initialize root
55
+ @root = root
56
+ end
57
+
58
+ # @yieldparam [AX::Element]
59
+ def each
60
+ stack = @root.attribute(:children)
61
+ until stack.empty?
62
+ current = stack.shift
63
+ yield current
64
+ if current.attributes.include? :children
65
+ # need to reverse it since child ordering seems to matter in practice
66
+ stack.unshift *current.attribute(:children)
67
+ end
68
+ end
69
+ end
70
+
71
+ ##
72
+ # Walk the UI element tree and yield both the element and the level
73
+ # that the element is at relative to the root.
74
+ #
75
+ # @yieldparam [AX::Element]
76
+ # @yieldparam [Number]
77
+ def each_with_level &block
78
+ # @todo A bit of a hack that I would like to fix one day...
79
+ @root.attribute(:children).each do |element|
80
+ recursive_each_with_level element, 1, block
81
+ end
82
+ end
83
+
84
+
85
+ private
86
+
87
+ ##
88
+ # Recursive implementation of a depth first iterator.
89
+ #
90
+ # @param [AX::Element]
91
+ # @param [Number]
92
+ # @param [#call]
93
+ def recursive_each_with_level element, depth, block
94
+ block.call element, depth
95
+ if element.respond_to? :children
96
+ element.attribute(:children).each do |x|
97
+ recursive_each_with_level x, depth + 1, block
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,32 @@
1
+ require 'accessibility/debug'
2
+
3
+ ##
4
+ # Error raised when an implicit search fails to return a result.
5
+ class Accessibility::SearchFailure < NoMethodError
6
+
7
+ def initialize searcher, searchee, filters
8
+ filters = {} unless filters.kind_of? Hash
9
+ msg = "Could not find `#{pp_searchee searchee, filters}` "
10
+ msg << "as a child of #{searcher.class}\n"
11
+ msg << "Element Path:\n\t" << path_to(searcher)
12
+ # @todo Consider turning this on by default
13
+ msg << "\nSubtree:\n\t" << debug(searcher) if Accessibility::Debug.on?
14
+ super msg
15
+ end
16
+
17
+
18
+ private
19
+
20
+ def pp_searchee searchee, filters
21
+ Accessibility::Qualifier.new(searchee, filters).describe
22
+ end
23
+
24
+ def path_to element
25
+ Accessibility::Debug.path(element).map! { |x| x.inspect }.join("\n\t")
26
+ end
27
+
28
+ def debug searcher
29
+ Accessibility::Debug.text_subtree(searcher)
30
+ end
31
+
32
+ end
@@ -0,0 +1,153 @@
1
+ require 'accessibility/core'
2
+ require 'accessibility/translator'
3
+
4
+ ##
5
+ # Namespace container for all the accessibility objects.
6
+ module AX; end
7
+
8
+ ##
9
+ # Mixin made for processing low level data from AXAPI methods.
10
+ module Accessibility::Factory
11
+
12
+ ##
13
+ # Processes any given data from an AXAPI method and wraps it if
14
+ # needed. Meant for taking a return value from {Accessibility::Core#attr:for:}
15
+ # and friends.
16
+ #
17
+ # Generally, used to process an `AXValue` into a `CGPoint` or an
18
+ # `AXUIElementRef` into some kind of {AX::Element} object.
19
+ def process value
20
+ return nil if value.nil? # CFGetTypeID(nil) crashes runtime
21
+ case CFGetTypeID(value)
22
+ when ARRAY_TYPE then process_array value
23
+ when REF_TYPE then process_element value
24
+ else
25
+ value
26
+ end
27
+ end
28
+
29
+
30
+ private
31
+
32
+ ##
33
+ # @private
34
+ #
35
+ # Reference to the singleton instance of the translator.
36
+ #
37
+ # @return [Accessibility::Translator]
38
+ TRANSLATOR = Accessibility::Translator.instance
39
+
40
+ ##
41
+ # @private
42
+ #
43
+ # Type ID for `AXUIElementRef` objects.
44
+ #
45
+ # @return [Number]
46
+ REF_TYPE = AXUIElementGetTypeID()
47
+
48
+ ##
49
+ # @private
50
+ #
51
+ # Type ID for `CFArrayRef` objects.
52
+ #
53
+ # @return [Number]
54
+ ARRAY_TYPE = CFArrayGetTypeID()
55
+
56
+ ##
57
+ # @todo Should we handle cases where a subrole has a value of
58
+ # 'Unknown'? What is the performance impact?
59
+ #
60
+ # Takes an `AXUIElementRef` and gives you some kind of wrapped
61
+ # accessibility object.
62
+ #
63
+ # Some code paths have been unrolled for efficiency. Don't hate player,
64
+ # hate the game.
65
+ #
66
+ # @param [AXUIElementRef]
67
+ # @return [AX::Element]
68
+ def process_element ref
69
+ role = TRANSLATOR.unprefix ref.role
70
+ attrs = ref.attributes
71
+ klass = if attrs.include? KAXSubroleAttribute
72
+ subrole = ref.subrole
73
+ # Some objects claim to have a subrole but return nil
74
+ if subrole
75
+ class_for TRANSLATOR.unprefix(subrole), and: role
76
+ else
77
+ class_for role
78
+ end
79
+ else
80
+ class_for role
81
+ end
82
+ klass.new ref
83
+ end
84
+
85
+ ##
86
+ # We assume a homogeneous array and only wrap element arrays right now.
87
+ #
88
+ # @return [Array]
89
+ def process_array vals
90
+ return vals if vals.empty?
91
+ return vals if CFGetTypeID(vals.first) != REF_TYPE
92
+ return vals.map { |val| process_element val }
93
+ end
94
+
95
+ ##
96
+ # @todo Consider using {AX.const_missing} instead.
97
+ #
98
+ # Find the class for a given role. If the class does not exist it will
99
+ # be created on demand.
100
+ #
101
+ # @param [#to_s]
102
+ # @return [Class]
103
+ def class_for role
104
+ if AX.const_defined? role, false
105
+ AX.const_get role
106
+ else
107
+ create_class role
108
+ end
109
+ end
110
+
111
+ ##
112
+ # Find the class for a given subrole and role. If the class does not
113
+ # exist it will be created on demand.
114
+ #
115
+ # @param [#to_s]
116
+ # @param [#to_s]
117
+ # @return [Class]
118
+ def class_for subrole, and: role
119
+ # @todo it would be nice if we didn't have to lookup twice
120
+ if AX.const_defined? subrole, false
121
+ AX.const_get subrole
122
+ else
123
+ create_class subrole, with_superclass: role
124
+ end
125
+ end
126
+
127
+ ##
128
+ # Create a new class in the {AX} namespace that has {AX::Element}
129
+ # as the superclass.
130
+ #
131
+ # @param [#to_s]
132
+ # @return [Class]
133
+ def create_class name
134
+ klass = Class.new AX::Element
135
+ AX.const_set name, klass
136
+ end
137
+
138
+ ##
139
+ # Create a new class in the {AX} namesapce that has the given
140
+ # `superklass` as the superclass..
141
+ #
142
+ # @param [#to_s] name
143
+ # @param [#to_s] superklass
144
+ # @return [Class]
145
+ def create_class name, with_superclass: superklass
146
+ unless AX.const_defined? superklass, false
147
+ create_class superklass
148
+ end
149
+ klass = Class.new AX.const_get(superklass)
150
+ AX.const_set name, klass
151
+ end
152
+
153
+ end
@@ -0,0 +1,150 @@
1
+ ##
2
+ # DOT graph generator for AXElements. It can generate the digraph code
3
+ # for a UI subtree. That code can then be given to GraphViz to generate
4
+ # an image for the graph.
5
+ #
6
+ # You can learn more about generating graphs in the
7
+ # {file:docs/Debugging.markdown Debugging} tutorial.
8
+ class Accessibility::Graph
9
+
10
+ ##
11
+ # @todo Graphs could be a lot nicer looking. That is, nodes could be much
12
+ # more easily identifiable, by allowing different classes to tell
13
+ # the node more about itself. A mixin module/protocol should
14
+ # probably be created, just as with the inspector mixin, and added
15
+ # to abstract base and overridden as needed in subclasses. In this
16
+ # way, an object can be more specific about what shape it is, how
17
+ # it is coloured, etc.
18
+ # Reference: http://www.graphviz.org/doc/info/attrs.html
19
+ #
20
+ # A node in the UI hierarchy. Used by {Accessibility::Graph} in order
21
+ # to build Graphviz DOT graphs.
22
+ class Node
23
+
24
+ # @return [String]
25
+ attr_reader :id
26
+
27
+ # @return [AX::Element]
28
+ attr_reader :element
29
+
30
+ # @param [AX::Element]
31
+ def initialize element
32
+ @element = element
33
+ @id = "element_#{element.object_id}"
34
+ end
35
+
36
+ # @return [String]
37
+ def to_dot
38
+ "#{@id} #{identifier} #{shape}"
39
+ end
40
+
41
+
42
+ private
43
+
44
+ EMPTY_STRING = ''
45
+ NAMESPACE = '::'
46
+
47
+ def identifier
48
+ klass = @element.class.to_s.split(NAMESPACE).last
49
+ ident = @element.pp_identifier
50
+ ident.gsub! /"/, '\"'
51
+ "[label = \"#{klass}#{ident}\"]"
52
+ end
53
+
54
+ def shape
55
+ @element.actions.empty? ? OVAL : BOX
56
+ end
57
+
58
+ def enabled
59
+ FILL if @element.respond_to?(:enabled) && !@element.enabled?
60
+ end
61
+
62
+ def focus
63
+ BOLD if @element.respond_to?(:focused) && @element.focused?
64
+ end
65
+
66
+ OVAL = '[shape = oval]'
67
+ BOX = '[shape = box]'
68
+ BOLD = '[style = bold]'
69
+ FILL = '[style = filled] [color = "grey"]'
70
+ end
71
+
72
+ ##
73
+ # An edge in the UI hierarchy. Used by {Accessibility::Graph} in order
74
+ # to build Graphviz DOT graphs.
75
+ class Edge
76
+
77
+ ##
78
+ # The style of arrowhead to use
79
+ #
80
+ # @return [String]
81
+ attr_accessor :style
82
+
83
+ # @param [Accessibility::Graph::Node]
84
+ # @param [Accessibility::Graph::Node]
85
+ def initialize head, tail
86
+ @head = head
87
+ @tail = tail
88
+ end
89
+
90
+ # @return [String]
91
+ def to_dot
92
+ arrow = style ? style : 'normal'
93
+ "#{@head.id} -> #{@tail.id} [arrowhead = #{arrow}]"
94
+ end
95
+
96
+ end
97
+
98
+
99
+ ##
100
+ # List of nodes in the UI hierarchy.
101
+ #
102
+ # @return [Array<Accessibility::Graph::Node>]
103
+ attr_reader :nodes
104
+
105
+ ##
106
+ # List of edges in the graph.
107
+ #
108
+ # @return [Array<Accessibility::Graph::Edge>]
109
+ attr_reader :edges
110
+
111
+ # @param [AX::Element]
112
+ def initialize root
113
+ root_node = Node.new(root)
114
+ @nodes = [root_node]
115
+ @edges = []
116
+
117
+ # exploit the ordering of a breadth-first enumeration to simplify
118
+ # the creation of edges for the graph. This only works because
119
+ # the UI hiearchy is a simple tree.
120
+ @edge_queue = Array.new(root.size_of(:children), root_node)
121
+ end
122
+
123
+ ##
124
+ # Construct the list of nodes and edges for the graph.
125
+ #
126
+ # The secret sauce is that we create an edge queue to exploit the
127
+ # breadth first ordering of the enumerator, which makes building the
128
+ # edges very easy.
129
+ def build!
130
+ Accessibility::Enumerators::BreadthFirst.new(nodes.last.element).each do |element|
131
+ nodes << node = Node.new(element)
132
+ edges << Edge.new(node, @edge_queue.shift)
133
+ @edge_queue.concat Array.new(element.size_of(:children), node)
134
+ end
135
+ end
136
+
137
+ ##
138
+ # Generate the `dot` graph code. You should take this string and
139
+ # feed it to the `dot` program to have it generate the graph.
140
+ #
141
+ # @return [String]
142
+ def to_dot
143
+ graph = "digraph {\n"
144
+ graph << nodes.map(&:to_dot).join("\n")
145
+ graph << "\n\n"
146
+ graph << edges.map(&:to_dot).join("\n")
147
+ graph << "\n}\n"
148
+ end
149
+
150
+ end
@@ -12,27 +12,16 @@
12
12
  #
13
13
  module Accessibility::PPInspector
14
14
 
15
-
16
- protected
17
-
18
- ##
19
- # Added for backwards compatability with Snow Leopard.
20
- #
21
- # @return [String]
22
- KAXIdentifierAttribute = 'AXIdentifier'.freeze
23
-
24
15
  ##
25
- # @todo I feel a bit bad about having such a large method that has
26
- # some inefficiencies.
27
- #
28
16
  # Create an identifier for `self` using various attributes that should
29
17
  # make it very easy to identify the element.
30
18
  #
31
19
  # @return [String]
32
20
  def pp_identifier
33
- # use or lack of use of #inspect is intentional for visual effect
21
+ # @todo Break this method up into chunks
22
+ # @note use, or lack of use, of #inspect is intentional for visual effect
34
23
 
35
- if attributes.include? KAXValueAttribute
24
+ if attributes.include? :value
36
25
  val = attribute :value
37
26
  if val.kind_of? NSString
38
27
  return " #{val.inspect}" unless val.empty?
@@ -42,27 +31,27 @@ module Accessibility::PPInspector
42
31
  end
43
32
  end
44
33
 
45
- if attributes.include? KAXTitleAttribute
34
+ if attributes.include? :title
46
35
  val = attribute(:title)
47
36
  return " #{val.inspect}" if val && !val.empty?
48
37
  end
49
38
 
50
- if attributes.include? KAXTitleUIElementAttribute
39
+ if attributes.include? :title_ui_element
51
40
  val = attribute :title_ui_element
52
- return BUFFER + val.inspect if val
41
+ return " #{val.inspect}" if val
53
42
  end
54
43
 
55
- if attributes.include? KAXDescriptionAttribute
44
+ if attributes.include? :description
56
45
  val = attribute(:description).to_s
57
- return BUFFER + val unless val.empty?
46
+ return " #{val}" unless val.empty?
58
47
  end
59
48
 
60
- if attributes.include? KAXIdentifierAttribute
49
+ if attributes.include? :identifier
61
50
  return " id=#{attribute(:identifier)}"
62
51
  end
63
52
 
64
53
  # @todo should we have other fallbacks?
65
- return ::EMPTY_STRING
54
+ return EMPTY_STRING
66
55
  end
67
56
 
68
57
  ##
@@ -75,7 +64,7 @@ module Accessibility::PPInspector
75
64
  if position
76
65
  " (#{position.x}, #{position.y})"
77
66
  else
78
- ::EMPTY_STRING
67
+ EMPTY_STRING
79
68
  end
80
69
  end
81
70
 
@@ -89,9 +78,9 @@ module Accessibility::PPInspector
89
78
  if child_count > 1
90
79
  " #{child_count} children"
91
80
  elsif child_count == 1
92
- ' 1 child'
81
+ ONE_CHILD
93
82
  else # there are some odd edge cases
94
- ::EMPTY_STRING
83
+ EMPTY_STRING
95
84
  end
96
85
  end
97
86
 
@@ -103,7 +92,7 @@ module Accessibility::PPInspector
103
92
  # @param [Symbol]
104
93
  # @return [String]
105
94
  def pp_checkbox attr
106
- " #{attr}[#{attribute(attr) ? '✔' : '✘'}]"
95
+ " #{attr}[#{attribute(attr) ? CHECKMARK : CROSS }]"
107
96
  end
108
97
 
109
98
 
@@ -112,10 +101,32 @@ module Accessibility::PPInspector
112
101
  ##
113
102
  # @private
114
103
  #
115
- # A string with a single space, used as a buffer. This is a
116
- # performance hack.
104
+ # Constant string used by {#pp_checkbox}.
105
+ #
106
+ # @return [String]
107
+ CHECKMARK = '✔'
108
+
109
+ ##
110
+ # @private
111
+ #
112
+ # Constant string used by {#pp_checkbox}.
113
+ #
114
+ # @return [String]
115
+ CROSS = '✘'
116
+
117
+ ##
118
+ # @private
119
+ #
120
+ # Constant string used by {#pp_children}.
117
121
  #
118
122
  # @return [String]
119
- BUFFER = ' '.freeze
123
+ ONE_CHILD = ' 1 child'
120
124
 
125
+ ##
126
+ # @private
127
+ #
128
+ # Constant used all over the place.
129
+ #
130
+ # @return [String]
131
+ EMPTY_STRING = ''
121
132
  end