Class: Agentilda::Ordinal
- Inherits:
-
Object
- Object
- Agentilda::Ordinal
- Includes:
- Comparable
- Defined in:
- lib/agentilda/ordinal.rb
Overview
A plan's number, and therefore its identity. Set once when the folder is
created and never changed: branch names, pull request titles and every
pull-requests.md join on it, and renumbering breaks all of them silently.
Always rendered NNN.MM. 002.00 is an ordinary plan, specified before it
was built. 002.01 is retroactive — work that shipped between 002 and 003
and was documented afterwards. The decimal marks a sibling, not
containment: 002.01 is not part of 002.
000 is where the sequence starts, so the first plan of a project is
000.00. It also absorbs the older meaning — work that predates the plan
discipline — since either way it sorts first, which is where it belongs.
Constant Summary collapse
- PATTERN =
The canonical form, plus the bare
NNNthat trees written before this rule still use and must remain readable. /\A(\d{1,3})(?:\.(\d{1,2}))?\z/- MAX_MINOR =
The most retroactive slots a single gap can hold.
99
Instance Attribute Summary collapse
-
#major ⇒ Integer
readonly
0..999.
-
#minor ⇒ Integer
readonly
0..99; zero for an ordinary plan.
Class Method Summary collapse
-
.from_dirname(dirname) ⇒ Agentilda::Ordinal?
Pull the number off the front of a folder name.
-
.next_major(existing) ⇒ Agentilda::Ordinal
The next ordinary plan after everything in
existing. -
.next_minor(existing, major:) ⇒ Agentilda::Ordinal
The next retroactive slot in the gap after
major. -
.parse(text) ⇒ Agentilda::Ordinal?
Nil when it is not a plan number.
Instance Method Summary collapse
- #<=>(other) ⇒ Integer?
-
#retroactive? ⇒ Boolean
Whether this plan was documented after the fact.
-
#to_prefix ⇒ String
What a pull request title carries.
-
#to_s ⇒ String
The canonical
NNN.MM.
Instance Attribute Details
#major ⇒ Integer (readonly)
Returns 0..999.
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 |
# File 'lib/agentilda/ordinal.rb', line 21 class Ordinal < Data.define(:major, :minor) include Comparable # The canonical form, plus the bare `NNN` that trees written before this # rule still use and must remain readable. PATTERN = /\A(\d{1,3})(?:\.(\d{1,2}))?\z/ # The most retroactive slots a single gap can hold. MAX_MINOR = 99 # @param text [String, nil] e.g. "3", "003", "003.00", "018.01" # @return [Agentilda::Ordinal, nil] nil when it is not a plan number def self.parse(text) m = PATTERN.match(text.to_s.strip) m && new(major: m[1].to_i, minor: m[2].to_i) end # Pull the number off the front of a folder name. # # @param dirname [String] e.g. "018.01-✅--verify-against-filed-returns" # @return [Agentilda::Ordinal, nil] def self.from_dirname(dirname) = parse(dirname.to_s[/\A[\d.]+/]) # The next ordinary plan after everything in +existing+. # # The first plan in an empty tree is `000.00`, not `001.00`: the sequence # counts from zero so that the very first specification — usually the # project's own — sorts above everything and needs no gap reserved for it. # # @param existing [Array<Agentilda::Ordinal>] # @return [Agentilda::Ordinal] def self.next_major(existing) return new(major: 0, minor: 0) if existing.empty? new(major: existing.map(&:major).max + 1, minor: 0) end # The next retroactive slot in the gap after +major+. # # @param existing [Array<Agentilda::Ordinal>] # @param major [Integer] the plan the work landed after # @return [Agentilda::Ordinal] # @raise [Agentilda::Error] when the gap is full def self.next_minor(existing, major:) taken = existing.select { |o| o.major == major }.map(&:minor).max || 0 if taken >= MAX_MINOR raise Error, "all #{MAX_MINOR} retroactive slots after #{format("%03d", major)} are taken" end new(major:, minor: taken + 1) end # @return [Boolean] whether this plan was documented after the fact def retroactive? = minor.positive? # @return [String] the canonical `NNN.MM` def to_s = format("%03d.%02d", major, minor) # @return [String] what a pull request title carries def to_prefix = "[#{self}]" # @param other [Object] # @return [Integer, nil] def <=>(other) return nil unless other.is_a?(self.class) [major, minor] <=> [other.major, other.minor] end end |
#minor ⇒ Integer (readonly)
Returns 0..99; zero for an ordinary plan.
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 |
# File 'lib/agentilda/ordinal.rb', line 21 class Ordinal < Data.define(:major, :minor) include Comparable # The canonical form, plus the bare `NNN` that trees written before this # rule still use and must remain readable. PATTERN = /\A(\d{1,3})(?:\.(\d{1,2}))?\z/ # The most retroactive slots a single gap can hold. MAX_MINOR = 99 # @param text [String, nil] e.g. "3", "003", "003.00", "018.01" # @return [Agentilda::Ordinal, nil] nil when it is not a plan number def self.parse(text) m = PATTERN.match(text.to_s.strip) m && new(major: m[1].to_i, minor: m[2].to_i) end # Pull the number off the front of a folder name. # # @param dirname [String] e.g. "018.01-✅--verify-against-filed-returns" # @return [Agentilda::Ordinal, nil] def self.from_dirname(dirname) = parse(dirname.to_s[/\A[\d.]+/]) # The next ordinary plan after everything in +existing+. # # The first plan in an empty tree is `000.00`, not `001.00`: the sequence # counts from zero so that the very first specification — usually the # project's own — sorts above everything and needs no gap reserved for it. # # @param existing [Array<Agentilda::Ordinal>] # @return [Agentilda::Ordinal] def self.next_major(existing) return new(major: 0, minor: 0) if existing.empty? new(major: existing.map(&:major).max + 1, minor: 0) end # The next retroactive slot in the gap after +major+. # # @param existing [Array<Agentilda::Ordinal>] # @param major [Integer] the plan the work landed after # @return [Agentilda::Ordinal] # @raise [Agentilda::Error] when the gap is full def self.next_minor(existing, major:) taken = existing.select { |o| o.major == major }.map(&:minor).max || 0 if taken >= MAX_MINOR raise Error, "all #{MAX_MINOR} retroactive slots after #{format("%03d", major)} are taken" end new(major:, minor: taken + 1) end # @return [Boolean] whether this plan was documented after the fact def retroactive? = minor.positive? # @return [String] the canonical `NNN.MM` def to_s = format("%03d.%02d", major, minor) # @return [String] what a pull request title carries def to_prefix = "[#{self}]" # @param other [Object] # @return [Integer, nil] def <=>(other) return nil unless other.is_a?(self.class) [major, minor] <=> [other.major, other.minor] end end |
Class Method Details
.from_dirname(dirname) ⇒ Agentilda::Ordinal?
Pull the number off the front of a folder name.
42 |
# File 'lib/agentilda/ordinal.rb', line 42 def self.from_dirname(dirname) = parse(dirname.to_s[/\A[\d.]+/]) |
.next_major(existing) ⇒ Agentilda::Ordinal
The next ordinary plan after everything in existing.
The first plan in an empty tree is 000.00, not 001.00: the sequence
counts from zero so that the very first specification — usually the
project's own — sorts above everything and needs no gap reserved for it.
52 53 54 55 56 |
# File 'lib/agentilda/ordinal.rb', line 52 def self.next_major(existing) return new(major: 0, minor: 0) if existing.empty? new(major: existing.map(&:major).max + 1, minor: 0) end |
.next_minor(existing, major:) ⇒ Agentilda::Ordinal
The next retroactive slot in the gap after major.
64 65 66 67 68 69 70 71 |
# File 'lib/agentilda/ordinal.rb', line 64 def self.next_minor(existing, major:) taken = existing.select { |o| o.major == major }.map(&:minor).max || 0 if taken >= MAX_MINOR raise Error, "all #{MAX_MINOR} retroactive slots after #{format("%03d", major)} are taken" end new(major:, minor: taken + 1) end |
.parse(text) ⇒ Agentilda::Ordinal?
Returns nil when it is not a plan number.
33 34 35 36 |
# File 'lib/agentilda/ordinal.rb', line 33 def self.parse(text) m = PATTERN.match(text.to_s.strip) m && new(major: m[1].to_i, minor: m[2].to_i) end |
Instance Method Details
#<=>(other) ⇒ Integer?
84 85 86 87 88 |
# File 'lib/agentilda/ordinal.rb', line 84 def <=>(other) return nil unless other.is_a?(self.class) [major, minor] <=> [other.major, other.minor] end |
#retroactive? ⇒ Boolean
Returns whether this plan was documented after the fact.
74 |
# File 'lib/agentilda/ordinal.rb', line 74 def retroactive? = minor.positive? |
#to_prefix ⇒ String
Returns what a pull request title carries.
80 |
# File 'lib/agentilda/ordinal.rb', line 80 def to_prefix = "[#{self}]" |
#to_s ⇒ String
Returns the canonical NNN.MM.
77 |
# File 'lib/agentilda/ordinal.rb', line 77 def to_s = format("%03d.%02d", major, minor) |