Class: HeadMusic::Utilities::HashKey
- Inherits:
-
Object
- Object
- HeadMusic::Utilities::HashKey
- Defined in:
- lib/head_music/utilities/hash_key.rb
Overview
Util for converting an object to a consistent hash key
Class Method Summary collapse
-
.desymbolized_string(identifier) ⇒ Object
private
Accidentals become word suffixes, so that “C♯” and “C sharp” reach the same key.
-
.for(identifier) ⇒ Object
Keyed on the identifier as given, so a symbol and its string spelling occupy separate entries for the same result.
-
.word_suffixes ⇒ Object
private
Derived from Alteration so that alterations.yml stays the only place the glyph inventory lives.
Class Method Details
.desymbolized_string(identifier) ⇒ Object (private)
Accidentals become word suffixes, so that “C♯” and “C sharp” reach the same key.
ASCII spellings are normalized to their unicode glyphs first rather than mapped here. Mapping ASCII “b” directly is what a naive implementation gets wrong — it turns :bass_clarinet into :b_flatass_clarinet — and avoiding that by handling ASCII “#” but not ASCII “b” left the two sides asymmetric, with “C#” keying to :c_sharp while “Bb” keyed to :bb. Accidentals.to_unicode is prose-safe, so it closes that gap without the mangling.
The two ASCII sharp rules that follow are a fallback, not a second inventory. They catch a “#” that Accidentals.to_unicode leaves alone because it is not attached to a letter name, and they are safe to apply bare because “#” cannot occur in a word — which is exactly why the corresponding ASCII flat rules must not exist here. Doubles first: the single rule would otherwise consume the first “#” of a “##”.
32 33 34 35 36 37 38 |
# File 'lib/head_music/utilities/hash_key.rb', line 32 def self.desymbolized_string(identifier) normalized = HeadMusic::Utilities::Accidentals.to_unicode(identifier) word_suffixes .reduce(normalized) { |string, (glyph, suffix)| string.gsub(glyph, suffix) } .gsub("##", "_double_sharp") .gsub("#", "_sharp") end |
.for(identifier) ⇒ Object
Keyed on the identifier as given, so a symbol and its string spelling occupy separate entries for the same result. Never evicted: the gem’s identifiers come from finite catalogs, so the vocabulary is bounded. A caller generating identifiers dynamically would grow this without limit.
10 11 12 13 14 15 16 |
# File 'lib/head_music/utilities/hash_key.rb', line 10 def self.for(identifier) @hash_keys ||= {} @hash_keys[identifier] ||= HeadMusic::Utilities::Case.to_snake_case( I18n.transliterate(desymbolized_string(identifier)) ).to_sym end |
.word_suffixes ⇒ Object (private)
Derived from Alteration so that alterations.yml stays the only place the glyph inventory lives. Each alteration’s identifier is already the word form a key wants (:double_sharp -> “_double_sharp”). Every unicode glyph is a single character, so unlike the ASCII spellings they need no longest-first ordering.
Resolved lazily, and it must stay that way: Alteration includes Named, whose .get_by_name calls back into HashKey. Nothing on Alteration’s initialization path reaches that method today, which is what keeps the cycle from closing — so if Named ever starts building a hash key while constructing an object, this memo would recurse through a half-built Alteration.all.
51 52 53 54 |
# File 'lib/head_music/utilities/hash_key.rb', line 51 def self.word_suffixes @word_suffixes ||= HeadMusic::Rudiment::Alteration.all.to_h { |alteration| [alteration.unicode, "_#{alteration.identifier}"] } end |