Class: Sevgi::Graphics::Element

Inherits:
Object
  • Object
show all
Extended by:
Ident
Defined in:
lib/sevgi/graphics/element.rb

Overview

SVG element node used by the graphics DSL.

Direct Known Subclasses

Document::Proto

Defined Under Namespace

Modules: Ident

Constant Summary collapse

RootParent =

Sentinel parent used by root SVG elements.

Object.new.tap { def it.inspect = "RootParent" }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Ident

id

Constructor Details

#initialize(name, parent:, attributes: {}, contents: []) { ... } ⇒ void

Creates an element.

Parameters:

  • name (Symbol)

    SVG element name

  • parent (Sevgi::Graphics::Element, Object)

    parent element or root sentinel

  • attributes (Hash) (defaults to: {})

    SVG attributes

  • contents (Array<Sevgi::Graphics::Content>) (defaults to: [])

    content objects

Yields:

  • evaluates the drawing DSL in the new element

Yield Returns:

  • (Object)

    ignored block result

Raises:

  • (Sevgi::ArgumentError)

    when the element name or an attribute is not valid XML



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/sevgi/graphics/element.rb', line 94

def initialize(name, parent:, attributes: {}, contents: [], &block)
  unless name.is_a?(::String) || name.is_a?(::Symbol)
    ArgumentError.("XML element name must be a String or Symbol")
  end

  @name = XML.name(name, context: "XML element name").to_sym
  @attributes = Attributes.new(attributes)
  @children = []
  @contents = contents
  @parent = parent

  parent.children << self unless self.class.root?(self)

  instance_exec(&block) if block
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name) { ... } ⇒ Sevgi::Graphics::Element

Dispatches SVG element DSL calls and caches valid element methods.

Parameters:

  • name (Symbol)

    missing method name

Yields:

  • evaluates the drawing DSL in the dispatched child element

Yield Returns:

  • (Object)

    ignored block result

Returns:

Raises:

  • (NameError)

    when the name is not a valid SVG element

  • (Sevgi::ArgumentError)

    when an argument cannot be parsed as attributes or content



117
118
119
# File 'lib/sevgi/graphics/element.rb', line 117

def method_missing(name, *, &block)
  Element.valid?(tag = Element.id(name)) ? Dispatch.(self, name, tag, *, &block) : super
end

Instance Attribute Details

#attributesSevgi::Graphics::Attributes

Returns the attribute store.



71
72
73
# File 'lib/sevgi/graphics/element.rb', line 71

def attributes
  @attributes
end

#childrenArray<Sevgi::Graphics::Element>

Returns child elements.

Returns:



75
76
77
# File 'lib/sevgi/graphics/element.rb', line 75

def children
  @children
end

#contentsArray<Sevgi::Graphics::Content>

Returns element content objects.

Returns:



79
80
81
# File 'lib/sevgi/graphics/element.rb', line 79

def contents
  @contents
end

#nameSymbol (readonly)

Returns the SVG element name.

Returns:

  • (Symbol)


67
68
69
# File 'lib/sevgi/graphics/element.rb', line 67

def name
  @name
end

#parentSevgi::Graphics::Element, Object

Returns the parent element or root sentinel.

Returns:



83
84
85
# File 'lib/sevgi/graphics/element.rb', line 83

def parent
  @parent
end

Class Method Details

.element(name, parent:) { ... } ⇒ Sevgi::Graphics::Element

Builds an element node.

Parameters:

Yields:

  • evaluates the drawing DSL in the new element

Yield Returns:

  • (Object)

    ignored block result

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when an argument cannot be parsed as attributes or content

  • (Sevgi::ArgumentError)

    when the element name or an attribute is not valid XML



20
# File 'lib/sevgi/graphics/element.rb', line 20

def self.element(name, *, parent:, &block) = new(name, **Dispatch.parse(name, *), parent:, &block)

.root { ... } ⇒ Sevgi::Graphics::Element

Builds an SVG root element.

Yields:

  • evaluates the drawing DSL in the root element

Yield Returns:

  • (Object)

    ignored block result

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when a root attribute is not valid XML



27
# File 'lib/sevgi/graphics/element.rb', line 27

def self.root(*, &block) = element(:svg, *, parent: RootParent, &block)

.root?(element) ⇒ Boolean

Reports whether an element is the root element.

Parameters:

Returns:

  • (Boolean)


32
# File 'lib/sevgi/graphics/element.rb', line 32

def self.root?(element) = element.parent == RootParent

.valid?(name) ⇒ Boolean

Reports whether an SVG element name is known.

Parameters:

  • name (Symbol)

    SVG element name

Returns:

  • (Boolean)


11
12
13
14
15
16
17
18
19
20
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/sevgi/graphics/element.rb', line 11

class Element
  # Builds an element node.
  # @param name [Symbol, String] SVG element name
  # @param parent [Sevgi::Graphics::Element] parent element
  # @yield evaluates the drawing DSL in the new element
  # @yieldreturn [Object] ignored block result
  # @return [Sevgi::Graphics::Element]
  # @raise [Sevgi::ArgumentError] when an argument cannot be parsed as attributes or content
  # @raise [Sevgi::ArgumentError] when the element name or an attribute is not valid XML
  def self.element(name, *, parent:, &block) = new(name, **Dispatch.parse(name, *), parent:, &block)

  # Builds an SVG root element.
  # @yield evaluates the drawing DSL in the root element
  # @yieldreturn [Object] ignored block result
  # @return [Sevgi::Graphics::Element]
  # @raise [Sevgi::ArgumentError] when a root attribute is not valid XML
  def self.root(*, &block) = element(:svg, *, parent: RootParent, &block)

  # Reports whether an element is the root element.
  # @param element [Sevgi::Graphics::Element] element to test
  # @return [Boolean]
  def self.root?(element) = element.parent == RootParent

  class << self
    require "sevgi/standard"

    # Reports whether an SVG element name is known.
    # @param name [Symbol] SVG element name
    # @return [Boolean]
    def valid?(name) = Standard.element?(name)
  rescue ::LoadError => e
    raise unless e.path == "sevgi/standard"

    # Reports whether an SVG element name is known.
    # @return [Boolean]
    def valid?(...) = true
  end

  private_class_method :new

  # Sentinel parent used by root SVG elements.
  RootParent = Object.new.tap { def it.inspect = "RootParent" }.freeze

  # SVG element method-name normalization.
  # @api private
  module Ident
    # Normalizes a Ruby method name into an SVG element id.
    # @param given [Symbol, String] method name
    # @return [Symbol]
    def id(given) = (@id ||= {})[given] ||= given.to_s.tr("_", "-").to_sym
  end

  extend Ident

  # Returns the SVG element name.
  # @return [Symbol]
  attr_reader :name

  # Returns the attribute store.
  # @return [Sevgi::Graphics::Attributes]
  attr_reader :attributes

  # Returns child elements.
  # @return [Array<Sevgi::Graphics::Element>]
  attr_reader :children

  # Returns element content objects.
  # @return [Array<Sevgi::Graphics::Content>]
  attr_reader :contents

  # Returns the parent element or root sentinel.
  # @return [Sevgi::Graphics::Element, Object] parent element or root sentinel
  attr_reader :parent

  # Creates an element.
  # @param name [Symbol] SVG element name
  # @param parent [Sevgi::Graphics::Element, Object] parent element or root sentinel
  # @param attributes [Hash] SVG attributes
  # @param contents [Array<Sevgi::Graphics::Content>] content objects
  # @yield evaluates the drawing DSL in the new element
  # @yieldreturn [Object] ignored block result
  # @return [void]
  # @raise [Sevgi::ArgumentError] when the element name or an attribute is not valid XML
  def initialize(name, parent:, attributes: {}, contents: [], &block)
    unless name.is_a?(::String) || name.is_a?(::Symbol)
      ArgumentError.("XML element name must be a String or Symbol")
    end

    @name = XML.name(name, context: "XML element name").to_sym
    @attributes = Attributes.new(attributes)
    @children = []
    @contents = contents
    @parent = parent

    parent.children << self unless self.class.root?(self)

    instance_exec(&block) if block
  end

  # Dispatches SVG element DSL calls and caches valid element methods.
  # @param name [Symbol] missing method name
  # @yield evaluates the drawing DSL in the dispatched child element
  # @yieldreturn [Object] ignored block result
  # @return [Sevgi::Graphics::Element]
  # @raise [NameError] when the name is not a valid SVG element
  # @raise [Sevgi::ArgumentError] when an argument cannot be parsed as attributes or content
  def method_missing(name, *, &block)
    Element.valid?(tag = Element.id(name)) ? Dispatch.(self, name, tag, *, &block) : super
  end

  # Reports whether a missing method can dispatch to an SVG element.
  # @param name [Symbol] queried method name
  # @param include_private [Boolean] standard Ruby respond_to? flag
  # @return [Boolean]
  # @api private
  def respond_to_missing?(name, include_private = false)
    Element.valid?(Element.id(name)) || super
  end

  # Element method-missing parser and cache.
  # @api private
  module Dispatch
    extend self

    # Dispatches an element DSL call.
    # @param element [Sevgi::Graphics::Element] parent element
    # @param method [Symbol] Ruby method name
    # @param tag [Symbol] SVG element name
    # @return [Sevgi::Graphics::Element]
    def call(element, method, tag, *, &)
      unless Element.method_defined?(method)
        Element.class_exec do
          define_method(method) { |*args, &block| self.class.element(tag, *args, parent: self, &block) }
        end
      end

      element.public_send(method, *, &)
    end

    # Parses element DSL arguments.
    # @param name [Symbol] SVG element name
    # @param args [Array<Object>] positional DSL arguments
    # @return [Hash] parsed :attributes and :contents
    # @raise [Sevgi::ArgumentError] when an argument is not a Hash, String, or Content
    def parse(name, *args)
      attributes, contents = {}, []

      args.each do |arg|
        case arg
        when ::Hash
          attributes = arg
        when ::String
          contents << Content.encoded(arg)
        when Content
          contents << arg
        else
          ArgumentError.("Argument of element '#{name}' must be a Hash or String: #{arg}")
        end
      end

      {attributes:, contents:}
    end
  end

  private_constant :Dispatch

  protected

  attr_writer :attributes, :children, :contents, :parent
end

Instance Method Details

#respond_to_missing?(name, include_private = false) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Reports whether a missing method can dispatch to an SVG element.

Parameters:

  • name (Symbol)

    queried method name

  • include_private (Boolean) (defaults to: false)

    standard Ruby respond_to? flag

Returns:

  • (Boolean)


126
127
128
# File 'lib/sevgi/graphics/element.rb', line 126

def respond_to_missing?(name, include_private = false)
  Element.valid?(Element.id(name)) || super
end