Class: HeadMusic::Style::GuideItem

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/style/guide_item.rb

Overview

A guideline plus the configuration one guide gives it, so the same guideline configured two ways is two items.

Tier is deliberately absent: an item is shared between guides and has no single standing, so tier is stamped onto the assessment instead. Strength is present, because a rule’s severity does not change between guides – the override exists only for the tradition-dependent case, where ApproachPerfectionContrarily is prohibited in Fux and merely cautioned later.

Constant Summary collapse

REMOVED_OPTIONS =

Removed options, mapped to what replaces them. An unrecognized key rides along in config and is dropped at render, so a consumer who kept passing message: would watch their sentence vanish without a word.

{message: :violation_key}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(guideline, config = {}, strength: nil) ⇒ GuideItem

Strength is resolved eagerly, before the freeze: asking the guideline for it lazily during grading would write a class ivar mid-assessment.



26
27
28
29
30
31
32
# File 'lib/head_music/style/guide_item.rb', line 26

def initialize(guideline, config = {}, strength: nil)
  reject_removed_options!(config)
  @guideline = guideline
  @config = deep_freeze(config)
  @strength = resolve_strength(guideline, strength)
  freeze
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



13
14
15
# File 'lib/head_music/style/guide_item.rb', line 13

def config
  @config
end

#guidelineObject (readonly)

Returns the value of attribute guideline.



13
14
15
# File 'lib/head_music/style/guide_item.rb', line 13

def guideline
  @guideline
end

#strengthObject (readonly)

Returns the value of attribute strength.



13
14
15
# File 'lib/head_music/style/guide_item.rb', line 13

def strength
  @strength
end

Class Method Details

.wrap(entry) ⇒ Object



15
16
17
# File 'lib/head_music/style/guide_item.rb', line 15

def self.wrap(entry)
  entry.is_a?(self) ? entry : new(entry)
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?

By value: identity matching is what made CORE - [Guideline] silently remove nothing.

Strength is deliberately omitted from both == and hash. reject_duplicates asks one question – is this rule graded twice – and strength cannot change the answer. Were it part of equality, declaring a rule primary-strong and secondary-weak would slip past the guard and be double-counted.



57
58
59
# File 'lib/head_music/style/guide_item.rb', line 57

def ==(other)
  other.is_a?(self.class) && guideline == other.guideline && config == other.config
end

#assess(voice, tier) ⇒ Object



46
47
48
# File 'lib/head_music/style/guide_item.rb', line 46

def assess(voice, tier)
  guideline.assess(voice, self, tier)
end

#deep_freeze(value) ⇒ Object (private)

Deep, because items are shared between guides: a mutable array inside config lets one caller corrupt every guide reusing a core, and changes the hash that value equality and duplicate rejection rest on.

Containers and strings only. A config value can be a rudiment, and those memoize lazily, so freezing one breaks it the first time it is asked.



110
111
112
113
114
115
116
117
# File 'lib/head_music/style/guide_item.rb', line 110

def deep_freeze(value)
  case value
  when Hash then value.each_value { |nested| deep_freeze(nested) }.freeze
  when Array then value.each { |nested| deep_freeze(nested) }.freeze
  when String then value.freeze
  else value
  end
end

#hashObject



62
63
64
# File 'lib/head_music/style/guide_item.rb', line 62

def hash
  [guideline, config].hash
end

#inspectObject Also known as: to_s

Distinguishes two configurations of one guideline, which the class name could not. Strength shows only when it differs from the declaration, so the common case stays readable.



77
78
79
80
81
82
# File 'lib/head_music/style/guide_item.rb', line 77

def inspect
  overridden = (strength == guideline.strength) ? "" : "<#{strength}>"
  return "#{guideline.name}#{overridden}" if config.empty?

  "#{guideline.name}#{overridden}#{config.inspect}"
end

#instructionObject



70
71
72
# File 'lib/head_music/style/guide_item.rb', line 70

def instruction
  guideline.render_instruction(config)
end

#nameObject



66
67
68
# File 'lib/head_music/style/guide_item.rb', line 66

def name
  guideline.render_name(config)
end

#reject_removed_options!(config) ⇒ Object (private)

At declaration time, so this fails on require rather than rendering the wrong sentence for the life of the process.

Raises:

  • (ArgumentError)


95
96
97
98
99
100
101
102
# File 'lib/head_music/style/guide_item.rb', line 95

def reject_removed_options!(config)
  removed = config.keys & REMOVED_OPTIONS.keys
  return if removed.empty?

  raise ArgumentError, removed.map { |option|
    "#{option}: is no longer a guideline option -- name a locale entry with #{REMOVED_OPTIONS[option]}:"
  }.join(" ")
end

#resolve_strength(guideline, override) ⇒ Object (private)



87
88
89
90
91
# File 'lib/head_music/style/guide_item.rb', line 87

def resolve_strength(guideline, override)
  return guideline.strength if override.nil?

  HeadMusic::Style::Guideline::Strength.normalized(override, guideline.name)
end

#violation_previewObject

An assessment says nothing when adherent, so this is the only way to read the sentence an item carries without a voice that breaks it.



36
37
38
# File 'lib/head_music/style/guide_item.rb', line 36

def violation_preview
  guideline.render_violation(config)
end

#violation_previewsObject

Every sentence the item can produce, including the branches an analysis chooses between. What load-time verification sweeps.



42
43
44
# File 'lib/head_music/style/guide_item.rb', line 42

def violation_previews
  guideline.render_violations(config)
end