Class: Markbridge::Renderers::Discourse::TagLibrary
- Inherits:
-
Object
- Object
- Markbridge::Renderers::Discourse::TagLibrary
- Includes:
- Enumerable
- Defined in:
- lib/markbridge/renderers/discourse/tag_library.rb
Overview
Library of rendering tags for different element types
Class Method Summary collapse
-
.default ⇒ TagLibrary
Create the default tag library for Discourse Markdown.
-
.shared_default ⇒ TagLibrary
Shared, deep-frozen default library for the no-customization fast path.
Instance Method Summary collapse
-
#[](element_class) ⇒ Tag?
Get tag for an element class.
-
#ast_class_for(tag_constant) ⇒ Class?
Look up the AST element class matching a
XxxTagconstant via theXxxTag → AST::Xxxnaming convention. -
#auto_register! ⇒ self
Auto-register all tags using naming convention Convention: BoldTag handles AST::Bold, ItalicTag handles AST::Italic, etc.
-
#each {|element_class, tag| ... } ⇒ Enumerator
Iterate over registered (element_class, tag) pairs.
-
#freeze ⇒ Object
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.
-
#initialize ⇒ TagLibrary
constructor
A new instance of TagLibrary.
-
#initialize_copy(other) ⇒ Object
When a TagLibrary is +dup+'d / +clone+'d, ensure the internal
@tagsHash is independent of the source. -
#merge!(mapping) ⇒ self
Merge a Hash of class → Tag mappings on top of this library in-place.
-
#register(element_class, tag) ⇒ Object
Register a tag for an element class.
-
#resolve(element_class) ⇒ Tag?
Find the tag for
element_classthrough its ancestry: walk the superclass chain, starting atelement_class.superclass, and return the first registered tag. -
#unregister(element_class) ⇒ self
Remove the binding for this exact element class.
Constructor Details
#initialize ⇒ TagLibrary
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
.default ⇒ TagLibrary
Create the default tag library for Discourse Markdown.
Each call returns a fresh instance — mutations made to one will not be visible to another.
136 137 138 |
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 136 def self.default new.auto_register! end |
.shared_default ⇒ TagLibrary
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).
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
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.
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.
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.
106 107 108 |
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 106 def each(&block) @tags.each(&block) end |
#freeze ⇒ Object
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.
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
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.
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.
47 48 49 50 |
# File 'lib/markbridge/renderers/discourse/tag_library.rb', line 47 def unregister(element_class) @tags.delete(element_class) self end |