Class: Markbridge::Renderers::Discourse::TagLibrary

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/markbridge/renderers/discourse/tag_library.rb

Overview

Library of rendering tags for different element types

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTagLibrary

Returns a new instance of TagLibrary.



10
11
12
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 10

def initialize
  @tags = {}
end

Class Method Details

.defaultTagLibrary

Create the default tag library for Discourse Markdown.

Each call returns a fresh instance — mutations made to one will not be visible to another.

Returns:



136
137
138
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 136

def self.default
  new.auto_register!
end

.shared_defaultTagLibrary

Shared, deep-frozen default library for the no-customization fast path. Built once per process; Renderer falls back to it when no tag_library: is given, skipping the constant-scan and ~30 tag instantiations of default on every render. Tags are stateless, so sharing is safe across renderers and threads. dup yields a mutable copy (see #initialize_copy).

Returns:

  • (TagLibrary)

    the same frozen instance on every call



148
149
150
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 148

def self.shared_default
  @shared_default ||= default.freeze
end

Instance Method Details

#[](element_class) ⇒ Tag?

Get tag for an element class

Parameters:

  • element_class (Class)

Returns:



76
77
78
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 76

def [](element_class)
  @tags[element_class]
end

#ast_class_for(tag_constant) ⇒ Class?

Look up the AST element class matching a XxxTag constant via the XxxTag → AST::Xxx naming convention.

Returns:

  • (Class, nil)


124
125
126
127
128
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 124

def ast_class_for(tag_constant)
  AST.const_get(tag_constant.to_s.sub(/Tag\z/, ""))
rescue NameError
  nil
end

#auto_register!self

Auto-register all tags using naming convention Convention: BoldTag handles AST::Bold, ItalicTag handles AST::Italic, etc.

Returns:

  • (self)


113
114
115
116
117
118
119
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 113

def auto_register!
  Tags.constants.each do |tag_constant|
    element_class = ast_class_for(tag_constant)
    register(element_class, Tags.const_get(tag_constant).new) if element_class
  end
  self
end

#each {|element_class, tag| ... } ⇒ Enumerator

Iterate over registered (element_class, tag) pairs. Useful for debugging custom libraries — e.g. confirming an override has stuck. Iteration order matches registration order.

Yield Parameters:

  • element_class (Class)
  • tag (Tag)

Returns:

  • (Enumerator)

    when no block is given



106
107
108
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 106

def each(&block)
  @tags.each(&block)
end

#freezeObject

Freeze the library together with its internal Hash so that registration on a shared instance fails loudly instead of silently mutating state visible to every renderer.

Freezing also flattens ancestry resolution: every known AST class without an explicit binding whose ancestry resolves to a tag gets that tag copied into the internal Hash, so lookup on a frozen library is a single exact Hash hit for those classes too. AST::Node.descendants is boot-time state, so only classes defined before the freeze are covered; later classes keep working through the renderer's lazy #resolve fallback.



163
164
165
166
167
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 163

def freeze
  flatten_ancestry!
  @tags.freeze
  super
end

#initialize_copy(other) ⇒ Object

When a TagLibrary is +dup+'d / +clone+'d, ensure the internal @tags Hash is independent of the source. Without this, both copies would share the same underlying Hash and mutations to one would silently affect the other.

A frozen source also carries flattened ancestry entries (see #freeze); the copy is mutable again, so those entries are dropped and the copy goes back to the lazy #resolve lookup. Keeping them would bake in inheritance decisions from before any changes made to the copy.



24
25
26
27
28
29
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 24

def initialize_copy(other)
  super
  @tags = @tags.dup
  @flattened_classes&.each { |klass| @tags.delete(klass) }
  @flattened_classes = nil
end

#merge!(mapping) ⇒ self

Merge a Hash of class → Tag mappings on top of this library in-place. A nil value unregisters the corresponding class (see #unregister for what lookup does then).

Named with a trailing ! because it mutates self — mirroring Ruby's Hash#merge / Hash#merge! convention. Use dup first if you need a non-destructive merge.

Parameters:

  • mapping (Hash{Class => Tag, nil})

Returns:

  • (self)


62
63
64
65
66
67
68
69
70
71
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 62

def merge!(mapping)
  mapping.each_pair do |klass, tag|
    if tag.nil?
      unregister(klass)
    else
      register(klass, tag)
    end
  end
  self
end

#register(element_class, tag) ⇒ Object

Register a tag for an element class

Parameters:

  • element_class (Class)

    the element class

  • tag (Tag)

    the tag instance



34
35
36
37
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 34

def register(element_class, tag)
  @tags[element_class] = tag
  self
end

#resolve(element_class) ⇒ Tag?

Find the tag for element_class through its ancestry: walk the superclass chain, starting at element_class.superclass, and return the first registered tag. The walk stays inside the AST::Node hierarchy, so a tag registered for a class outside the AST is never found. The exact-class lookup is #[]; callers check that first.

Parameters:

  • element_class (Class)

Returns:



89
90
91
92
93
94
95
96
97
98
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 89

def resolve(element_class)
  klass = element_class.superclass
  while klass && klass <= AST::Node
    tag = self[klass]
    return tag if tag

    klass = klass.superclass
  end
  # The while loop's own value is nil, so a miss returns nil.
end

#unregister(element_class) ⇒ self

Remove the binding for this exact element class. Lookup then falls back to the nearest ancestor class with a tag; when no ancestor has one — true for every built-in class, since nothing binds AST::Element or AST::Node — the renderer falls through to render_children. See Renderer#render.

Parameters:

  • element_class (Class)

Returns:

  • (self)


47
48
49
50
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 47

def unregister(element_class)
  @tags.delete(element_class)
  self
end