Class: AsciiChem::Model::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/asciichem/model/node.rb

Overview

Base class for every model node. Provides:

- structural equality (class + declared attributes)
- visitor dispatch (`accept`)
- convenience formatter shortcuts (`to_mathml`, `to_text`, ...)

Subclasses declare their comparable attributes by overriding value_attributes (returns a hash of { name => value }). This avoids reflective instance-variable access and keeps equality correct as fields are added.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.short_nameObject

Snake-case form of the class basename, used to derive the visitor method (Atom -> visit_atom, ElectronConfiguration -> visit_electron_configuration). Memoised per class so the string transformation runs once per class, not once per visit.



78
79
80
81
82
83
84
85
# File 'lib/asciichem/model/node.rb', line 78

def self.short_name
  @short_name ||= begin
    snake = name.split("::").last
                .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
                .gsub(/([a-z\d])([A-Z])/, '\1_\2')
    snake.downcase
  end
end

Instance Method Details

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



24
25
26
# File 'lib/asciichem/model/node.rb', line 24

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

#accept(visitor) ⇒ Object



15
16
17
18
19
20
21
22
# File 'lib/asciichem/model/node.rb', line 15

def accept(visitor)
  visitor.public_send(:"visit_#{self.class.short_name}", self)
rescue NoMethodError => e
  raise unless e.name == :"visit_#{self.class.short_name}"

  raise NotImplementedError,
        "#{visitor.class} does not implement visit_#{self.class.short_name}"
end

#childrenObject

Child nodes for traversal. Default: no children. Container classes (Formula, Molecule, Group, Reaction, ReactionCascade) override to expose their contents; leaves (Atom, Bond, EmbeddedMath, Text) inherit the empty default.

Used by Linter::Base#walk. Adding a new container class means defining children on it — no edits to the linter.



70
71
72
# File 'lib/asciichem/model/node.rb', line 70

def children
  []
end

#diagnostic_labelObject

Human-readable label for linter diagnostics. Default: the class basename with words separated by spaces ("Reaction Cascade", "Electron Configuration"). Subclasses with identifying fields override (e.g. Atom includes its element symbol). Keeping this on the model keeps the linter OCP-clean — adding a new model class means overriding the method on that class, not editing Linter::Diagnostic's case statement.



95
96
97
# File 'lib/asciichem/model/node.rb', line 95

def diagnostic_label
  self.class.short_name.split("_").map(&:capitalize).join(" ")
end

#hashObject



29
30
31
# File 'lib/asciichem/model/node.rb', line 29

def hash
  [self.class, value_attributes].hash
end

#to_cmlObject



53
54
55
# File 'lib/asciichem/model/node.rb', line 53

def to_cml
  AsciiChem::Cml.from_asciichem(self)
end

#to_htmlObject



41
42
43
# File 'lib/asciichem/model/node.rb', line 41

def to_html
  AsciiChem::Formatter.render(:html, self)
end

#to_latexObject



45
46
47
# File 'lib/asciichem/model/node.rb', line 45

def to_latex
  AsciiChem::Formatter.render(:latex, self)
end

#to_mathmlObject



33
34
35
# File 'lib/asciichem/model/node.rb', line 33

def to_mathml
  AsciiChem::Formatter.render(:mathml, self)
end

#to_svgObject



49
50
51
# File 'lib/asciichem/model/node.rb', line 49

def to_svg
  AsciiChem::Formatter.render(:svg, self)
end

#to_textObject



37
38
39
# File 'lib/asciichem/model/node.rb', line 37

def to_text
  AsciiChem::Formatter.render(:text, self)
end

#value_attributesObject

Subclasses override to expose the attributes that participate in equality. Default: empty (so two bare Nodes are equal).



59
60
61
# File 'lib/asciichem/model/node.rb', line 59

def value_attributes
  {}
end