Module: Ace::Assign::Atoms::StepNumbering
- Defined in:
- lib/ace/assign/atoms/step_numbering.rb
Overview
Pure functions for hierarchical step numbering operations.
Supports nested step structure where steps can have sub-steps:
-
Main steps: 010, 020, 030
-
Nested steps: 010.01, 010.02, 010.03
-
Deeply nested: 010.01.01 (if needed)
This enables verification-as-step patterns where parent steps wait for all children to complete before advancing.
Constant Summary collapse
- MAX_DEPTH =
Maximum allowed nesting depth for step numbers. Prevents unbounded hierarchy (e.g., 010.01.01.01.01…). Depth 0 = top-level (010), 1 = first nest (010.01), 2 = second nest (010.01.01). Maximum is 010.01.01 (3 levels total).
2- MAX_SIBLINGS_TOP_LEVEL =
Maximum siblings per level. Child indexes use %02d format (01-99). Top-level uses %03d (001-999). Exceeding these limits will cause lexicographical sorting issues.
999- MAX_SIBLINGS_NESTED =
99
Class Method Summary collapse
-
.child_of?(child, parent) ⇒ Boolean
Check if a step number is a child (direct or nested) of another.
-
.direct_child_of?(child, parent) ⇒ Boolean
Check if a step number is a direct (immediate) child of another.
-
.direct_children(parent, all_numbers) ⇒ Array<String>
Get all direct children of a parent from a list of numbers.
-
.first_child(number) ⇒ String
Generate the first child step number.
-
.insert_after(after) ⇒ String
Generate a step number to insert after another step.
-
.next_child(parent, existing_children = []) ⇒ String
Generate the next child step number based on existing children.
-
.next_sibling(number) ⇒ String
Generate the next sibling step number.
-
.parent_of(number) ⇒ String?
Get the parent number of a step, if it has one.
-
.parse(number) ⇒ Hash
Parse a step number into its components.
-
.shift_number(number, shift = 1) ⇒ String
Generate the shifted number for a step being renumbered.
-
.steps_to_renumber(at_number, existing) ⇒ Array<String>
Find step numbers that need to be renumbered when inserting at a position.
-
.top_level?(number) ⇒ Boolean
Check if a number is a top-level (root) step.
Class Method Details
.child_of?(child, parent) ⇒ Boolean
Check if a step number is a child (direct or nested) of another.
127 128 129 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 127 def self.child_of?(child, parent) child.to_s.start_with?("#{parent}.") end |
.direct_child_of?(child, parent) ⇒ Boolean
Check if a step number is a direct (immediate) child of another.
136 137 138 139 140 141 142 143 144 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 136 def self.direct_child_of?(child, parent) return false unless child_of?(child, parent) # Direct child has exactly one more level child_parsed = parse(child) parent_parsed = parse(parent) child_parsed[:depth] == parent_parsed[:depth] + 1 end |
.direct_children(parent, all_numbers) ⇒ Array<String>
Get all direct children of a parent from a list of numbers.
151 152 153 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 151 def self.direct_children(parent, all_numbers) all_numbers.select { |n| direct_child_of?(n, parent) } end |
.first_child(number) ⇒ String
Generate the first child step number.
87 88 89 90 91 92 93 94 95 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 87 def self.first_child(number) parent_depth = parse(number)[:depth] child_depth = parent_depth + 1 if child_depth > MAX_DEPTH raise ArgumentError, "Cannot create child: would exceed maximum nesting depth of #{MAX_DEPTH} " \ "(parent '#{number}' is at depth #{parent_depth})" end "#{number}.01" end |
.insert_after(after) ⇒ String
Generate a step number to insert after another step. This creates a sibling at the same nesting level.
Note: This method does not check for collisions. Use steps_to_renumber to determine which existing steps need to be shifted when inserting.
179 180 181 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 179 def self.insert_after(after) next_sibling(after) end |
.next_child(parent, existing_children = []) ⇒ String
Generate the next child step number based on existing children.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 103 def self.next_child(parent, existing_children = []) parent_depth = parse(parent)[:depth] child_depth = parent_depth + 1 if child_depth > MAX_DEPTH raise ArgumentError, "Cannot create child: would exceed maximum nesting depth of #{MAX_DEPTH} " \ "(parent '#{parent}' is at depth #{parent_depth})" end return first_child(parent) if existing_children.empty? # Find highest existing child index max_index = existing_children .select { |n| child_of?(n, parent) && direct_child_of?(n, parent) } .map { |n| parse(n)[:index] } .max || 0 "#{parent}.#{format("%02d", max_index + 1)}" end |
.next_sibling(number) ⇒ String
Generate the next sibling step number.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 64 def self.next_sibling(number) parsed = parse(number) new_index = parsed[:index] + 1 limit = parsed[:parent] ? MAX_SIBLINGS_NESTED : MAX_SIBLINGS_TOP_LEVEL if new_index > limit raise ArgumentError, "Cannot create sibling: would exceed maximum siblings " \ "(#{limit}) at this level (current index: #{parsed[:index]})" end if parsed[:parent] "#{parsed[:parent]}.#{format("%02d", new_index)}" else # Top-level numbers use 3-digit padding format("%03d", new_index) end end |
.parent_of(number) ⇒ String?
Get the parent number of a step, if it has one.
159 160 161 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 159 def self.parent_of(number) parse(number)[:parent] end |
.parse(number) ⇒ Hash
Parse a step number into its components.
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 49 def self.parse(number) parts = number.to_s.split(".") { parent: (parts.length > 1) ? parts[0..-2].join(".") : nil, index: parts.last.to_i, depth: parts.length - 1, full: number.to_s } end |
.shift_number(number, shift = 1) ⇒ String
Generate the shifted number for a step being renumbered.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 208 def self.shift_number(number, shift = 1) parsed = parse(number) new_index = parsed[:index] + shift limit = parsed[:parent] ? MAX_SIBLINGS_NESTED : MAX_SIBLINGS_TOP_LEVEL if new_index > limit raise ArgumentError, "Cannot shift step number: would exceed maximum siblings " \ "(#{limit}) at this level (new index would be: #{new_index})" end if parsed[:parent] "#{parsed[:parent]}.#{format("%02d", new_index)}" else format("%03d", new_index) end end |
.steps_to_renumber(at_number, existing) ⇒ Array<String>
Find step numbers that need to be renumbered when inserting at a position. Returns steps that have numbers >= the insertion point.
189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 189 def self.steps_to_renumber(at_number, existing) parsed_at = parse(at_number) parent = parsed_at[:parent] existing .select { |n| parsed = parse(n) # Same parent (or both top-level) and index >= insertion point parsed[:parent] == parent && parsed[:index] >= parsed_at[:index] } .sort_by { |n| parse(n)[:index] } end |
.top_level?(number) ⇒ Boolean
Check if a number is a top-level (root) step.
167 168 169 |
# File 'lib/ace/assign/atoms/step_numbering.rb', line 167 def self.top_level?(number) parse(number)[:depth] == 0 end |