jekyll-link-decorator 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58e0cbaffc8dddaa356b97b7f9523197377c98862ec3398cef26b7436b69d8d1
4
- data.tar.gz: 5fab208572e92de8982ed60159799b02b729f196a54ba5f0465c86dee212275a
3
+ metadata.gz: 7edfd6ee252b912cb6d3c30f50fcf6c0da0f0e5dc49694f3d2990ac6511e4584
4
+ data.tar.gz: 8cf8478f0a431f2b436356d5cd905586629bc3eea130cd777516435f9260284c
5
5
  SHA512:
6
- metadata.gz: fa74e76b3aed60bd4be998dd4b227268323aabae2f00ec67d718ec71c2b04f85bb356bdea7ac8622b6d4caabbc778cff534d2796dbd06bd55989611db4760ca7
7
- data.tar.gz: 233d5cde8f2bfdf617b595a2783633e37d4b3794ba3ccfd8b8e838f8c4906348c4a8cd8979f78ea543861ab648633f004bc81c18aa7b44f6cec3170585d75c4d
6
+ metadata.gz: 8ece38e9ce327d4c4868ff3d1273cee59abcf0b02f6e7171b4bae061c175eaec359e6a359549f2c9f534a73356f8024a4cf1141ff9b8bb7a38e5a179b55b8994
7
+ data.tar.gz: 3327e7a86426bc7c43931f579fa9924b30970e7a8f6ee38c2a5dbf7d2a3345e388f157e34a89fd069945d2a41513514db82bf02e7a9f8e6032ea85f44530525c
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # jekyll-link-decorator
2
2
 
3
- A Jekyll plugin that automatically enhances Markdown links with Bootstrap CSS
4
- classes, security attributes, Font Awesome icons, and context-aware styling.
5
- Simplifies link management across Jekyll sites by handling styling, external
6
- link protection, and icon injection without manual intervention.
3
+ `jekyll-link-decorator` is a Jekyll plugin that automatically enhances Markdown
4
+ links with Bootstrap CSS classes, security attributes, Font Awesome icons, and
5
+ context-aware styling. It simplifies link management across Jekyll sites by
6
+ handling styling, external link protection, and icon injection without manual
7
+ intervention.
7
8
 
8
9
  ## Documentation
9
10
 
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module LinkDecorator
5
+ # OpenTelemetry instrumentation facade for jekyll-link-decorator.
6
+ #
7
+ # All tracing is configured centrally in TRACED_METHODS — no span code lives
8
+ # in business logic classes. To add a span: append one entry. To rename a
9
+ # method: update the one entry. When a method is removed from its class the
10
+ # stale entry raises NoMethodError in tests, signalling the entry to delete.
11
+ #
12
+ # Requires opentelemetry-api at runtime; falls back to a no-op if absent.
13
+ # Users opt in to real tracing by adding opentelemetry-sdk and
14
+ # opentelemetry-exporter-otlp to their site Gemfile and exporting:
15
+ #
16
+ # OTEL_SERVICE_NAME=jekyll-link-decorator
17
+ # OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
18
+ module Instrumentation
19
+ TRACER_NAME = 'jekyll-link-decorator'
20
+
21
+ # Central instrumentation registry.
22
+ #
23
+ # Each row: [class_name, method_type, method_name, span_name, attribute_proc]
24
+ #
25
+ # method_type:
26
+ # :instance — public instance method
27
+ # :private — private instance method (visibility is preserved on the wrapper)
28
+ # :class — class / module method (prepended on the singleton class)
29
+ #
30
+ # attribute_proc: ->(span, this, args, result) or nil
31
+ # span — OTel span (or NoopSpan); call span.set_attribute after super returns
32
+ # this — receiver object (instance or nil for class methods)
33
+ # args — positional args array as passed to the method
34
+ # result — return value of the original method
35
+ # :nocov: — attribute procs are configuration data; coverage comes from integration tests
36
+ TRACED_METHODS = [
37
+ # decorate_html is the converter's entry point — one span per page converted
38
+ ['Jekyll::Converters::LinkDecorator', :instance, :decorate_html, 'link.decorate_page',
39
+ lambda { |span, _this, args, result|
40
+ span.set_attribute('link.html_size_bytes', args[0].to_s.bytesize)
41
+ span.set_attribute('link.output_size_bytes', result.to_s.bytesize)
42
+ }],
43
+
44
+ ['Jekyll::Converters::LinkDecorator', :instance, :apply_link_styles, 'link.style_apply',
45
+ lambda { |span, _this, args, _result|
46
+ doc = args[0]
47
+ span.set_attribute('link.styled_link_count', doc.respond_to?(:css) ? doc.css('a:not(.btn)').size : 0)
48
+ }],
49
+
50
+ ['Jekyll::Converters::LinkDecorator', :instance, :add_external_link_features,
51
+ 'link.external_link_features',
52
+ lambda { |span, _this, args, _result|
53
+ doc = args[0]
54
+ span.set_attribute('link.external_link_count',
55
+ doc.respond_to?(:css) ? doc.css('a[target="_blank"]').size : 0)
56
+ }],
57
+
58
+ # add_heading_anchors is private on LinkDecorator; visibility is preserved on the wrapper
59
+ ['Jekyll::Converters::LinkDecorator', :private, :add_heading_anchors, 'link.heading_anchors', nil]
60
+ ].freeze
61
+ # :nocov:
62
+
63
+ # Returns the active OTel tracer, or a no-op tracer if opentelemetry-api is absent.
64
+ def self.tracer
65
+ @tracer ||=
66
+ if defined?(OpenTelemetry)
67
+ # :nocov:
68
+ OpenTelemetry.tracer_provider.tracer(TRACER_NAME, Jekyll::LinkDecorator::VERSION)
69
+ # :nocov:
70
+ else
71
+ NoopTracer.new
72
+ end
73
+ end
74
+
75
+ # Wraps a block in an OTel span.
76
+ #
77
+ # @param span_name [String] Dot-separated span name (e.g. 'link.style_apply')
78
+ # @param attributes [Hash] Initial span attributes
79
+ # @yieldparam span [OpenTelemetry::Trace::Span, NoopSpan] Active span
80
+ # @return [Object] The return value of the block
81
+ def self.instrument(span_name, attributes: {}, &block)
82
+ tracer.in_span(span_name, attributes: attributes, &block)
83
+ end
84
+
85
+ # Resets the cached tracer and installation flag. Call in tests after changing OTel configuration.
86
+ def self.reset!
87
+ @tracer = nil
88
+ @installed = false
89
+ end
90
+
91
+ # Returns true when OTel is requested via standard environment variables.
92
+ #
93
+ # install! guards on this so the prepend wrappers are only applied when
94
+ # a real exporter is configured. In CI and local tests (no OTel env vars)
95
+ # business logic classes are untouched, keeping allow_any_instance_of stubs
96
+ # and other RSpec mechanics fully functional.
97
+ def self.enabled?
98
+ ENV.key?('OTEL_EXPORTER_OTLP_ENDPOINT') || ENV.key?('OTEL_SERVICE_NAME')
99
+ end
100
+
101
+ # Installs wrappers on all classes listed in TRACED_METHODS using Module#prepend.
102
+ #
103
+ # Called once at plugin load time (end of jekyll-link-decorator.rb, after all
104
+ # requires), but only when enabled? returns true. Use OTEL_EXPORTER_OTLP_ENDPOINT
105
+ # or OTEL_SERVICE_NAME to activate tracing.
106
+ def self.install!
107
+ return if @installed
108
+
109
+ @installed = true
110
+ setup_sdk!
111
+ TRACED_METHODS.group_by { |e| e[0] }.each do |class_name, entries|
112
+ prepend_wrappers(resolve_class(class_name), entries)
113
+ end
114
+ end
115
+
116
+ # Configures the OTel SDK from standard env vars if the SDK gem is available.
117
+ #
118
+ # Called from install! so that spans are exported to the configured backend
119
+ # without requiring sites to add a separate initializer. Silently no-ops when
120
+ # opentelemetry-sdk or opentelemetry-exporter-otlp is not installed.
121
+ #
122
+ # SimpleSpanProcessor is used instead of the SDK default (BatchSpanProcessor)
123
+ # because Jekyll is a short-lived CLI process. BatchSpanProcessor exports on a
124
+ # 5-second schedule; outer spans that close last (decorate_page) are silently
125
+ # dropped when the process exits before the next flush. Simple exports each
126
+ # span synchronously the moment it closes, guaranteeing no span loss.
127
+ #
128
+ # jekyll-theme-centos loads several instrumented plugins in one process
129
+ # (jekyll-l10n uses the 'l10n.' tracer, jekyll-minify uses 'minify.'), and
130
+ # OpenTelemetry::SDK.configure unconditionally overwrites the global
131
+ # tracer_provider on every call — so only the first plugin to reach this
132
+ # method (see sdk_already_configured?) actually configures the SDK; every
133
+ # later plugin's call becomes a no-op and reuses that provider. The
134
+ # at_exit and post_write hooks below are therefore registered once, by
135
+ # whichever plugin wins, and cover every plugin's spans, not just this one's.
136
+ def self.setup_sdk!
137
+ return if sdk_already_configured?
138
+
139
+ # :nocov:
140
+ require 'opentelemetry/sdk'
141
+ require 'opentelemetry/exporter/otlp'
142
+ exporter = OpenTelemetry::Exporter::OTLP::Exporter.new
143
+ processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)
144
+ OpenTelemetry::SDK.configure { |c| c.add_span_processor(processor) }
145
+
146
+ at_exit { OpenTelemetry.tracer_provider&.shutdown }
147
+ Jekyll::Hooks.register(:site, :post_write, priority: 0) { OpenTelemetry.tracer_provider&.force_flush }
148
+ rescue LoadError
149
+ nil
150
+ # :nocov:
151
+ end
152
+
153
+ # True when some plugin (this one or another) already installed a real
154
+ # SDK-backed tracer_provider, in which case setup_sdk! must not run again.
155
+ def self.sdk_already_configured?
156
+ defined?(OpenTelemetry::SDK::Trace::TracerProvider) &&
157
+ OpenTelemetry.tracer_provider.is_a?(OpenTelemetry::SDK::Trace::TracerProvider)
158
+ end
159
+
160
+ # Resolves a dot-separated class name to a constant; returns nil on NameError.
161
+ def self.resolve_class(name)
162
+ name.split('::').reduce(Object) { |m, c| m.const_get(c) }
163
+ rescue NameError
164
+ nil
165
+ end
166
+
167
+ # Applies instance and class-method wrappers to klass; skips if klass is nil.
168
+ def self.prepend_wrappers(klass, entries)
169
+ return unless klass
170
+
171
+ class_entries, instance_entries = entries.partition { |e| e[1] == :class }
172
+ klass.prepend(build_wrapper_module(instance_entries)) unless instance_entries.empty?
173
+ klass.singleton_class.prepend(build_wrapper_module(class_entries)) unless class_entries.empty?
174
+ end
175
+
176
+ # Builds an anonymous module that wraps each listed method in a span.
177
+ def self.build_wrapper_module(entries)
178
+ Module.new do
179
+ entries.each do |_class_name, method_type, method_name, span_name, attr_proc|
180
+ define_method(method_name) do |*args, **kwargs, &blk|
181
+ Instrumentation.instrument(span_name) do |span|
182
+ result = super(*args, **kwargs, &blk)
183
+ attr_proc&.call(span, self, args, result)
184
+ result
185
+ rescue StandardError => e
186
+ span.record_exception(e)
187
+ raise
188
+ end
189
+ end
190
+ private method_name if method_type == :private
191
+ end
192
+ end
193
+ end
194
+
195
+ private_class_method :build_wrapper_module, :prepend_wrappers, :setup_sdk!, :sdk_already_configured?
196
+
197
+ # No-op tracer used when opentelemetry-api is not loaded.
198
+ class NoopTracer
199
+ def in_span(_name, **_opts)
200
+ yield NoopSpan.new
201
+ end
202
+ end
203
+
204
+ # No-op span that silently accepts all attribute and event calls.
205
+ class NoopSpan
206
+ def set_attribute(*)
207
+ self
208
+ end
209
+
210
+ def record_exception(*)
211
+ self
212
+ end
213
+
214
+ def status=(*); end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module LinkDecorator
5
+ # Autogenerated by jekyll-link-decorator.gemspec.rb — do not edit by hand.
6
+ # Regenerated alongside the gemspec on every `make gemspec` run (locally
7
+ # or in CI), so it always matches spec.version in the built gem. Any
8
+ # hand edit is overwritten on the next run.
9
+ VERSION = '1.4.0'
10
+ end
11
+ end
@@ -51,7 +51,7 @@
51
51
  # are injected. Set to true to enable.
52
52
  #
53
53
  # * `with_heading_anchor_data`: A dictionary containing optional configuration keys:
54
- # - `icon`: Font Awesome icon classes (default: "fa-solid fa-link")
54
+ # - `icon`: Font Awesome icon classes (default: "fa-solid fa-hashtag")
55
55
  # - `icon_size`: Font Awesome size modifier appended after icon classes (optional)
56
56
  # - `copy_success_message`: Feedback text after copying (consumed by heading-anchor.js,
57
57
  # default: "Copied!")
@@ -75,12 +75,14 @@
75
75
  #
76
76
  # with_heading_anchor: true
77
77
  # with_heading_anchor_data:
78
- # icon: "fa-solid fa-link"
78
+ # icon: "fa-solid fa-hashtag"
79
79
  # copy_success_message: "Copied!"
80
80
  # reset_delay: 2000
81
81
  # ```
82
82
 
83
83
  require 'nokogiri'
84
+ require_relative 'jekyll-link-decorator/version'
85
+ require_relative 'jekyll-link-decorator/instrumentation'
84
86
 
85
87
  EXISTING_ICON_SELECTORS = [
86
88
  'i[class*="fa-external-link"]',
@@ -103,7 +105,7 @@ module Jekyll
103
105
  DEFAULT_EXTERNAL_LINK_ICON = true
104
106
  DEFAULT_EXTERNAL_LINK_ICON_EXCLUDED_TAGS = [].freeze
105
107
  DEFAULT_HEADING_ANCHOR = false
106
- DEFAULT_HEADING_ANCHOR_ICON = 'fa-solid fa-link'
108
+ DEFAULT_HEADING_ANCHOR_ICON = 'fa-solid fa-hashtag'
107
109
 
108
110
  def self.name
109
111
  'LinkDecorator'
@@ -381,3 +383,5 @@ module Jekyll
381
383
  end
382
384
 
383
385
  Liquid::Template.register_filter(Jekyll::Filters::LinkDecoratorFilter)
386
+
387
+ Jekyll::LinkDecorator::Instrumentation.install! if Jekyll::LinkDecorator::Instrumentation.enabled?
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-link-decorator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
- - ReleaseBot
7
+ - Alain Reguera Delgado
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -49,6 +49,20 @@ dependencies:
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
51
  version: '2.0'
52
+ - !ruby/object:Gem::Dependency
53
+ name: opentelemetry-api
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: '1.4'
59
+ type: :runtime
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: '1.4'
52
66
  - !ruby/object:Gem::Dependency
53
67
  name: bundler
54
68
  requirement: !ruby/object:Gem::Requirement
@@ -194,7 +208,7 @@ description: A Jekyll plugin that automatically enhances Markdown links with Boo
194
208
  Simplifies link management across Jekyll sites by handling styling, external link
195
209
  protection, and icon injection without manual intervention.
196
210
  email:
197
- - group_58921183_bot_c5520aeba366578e2e444dd181ff3e23@noreply.gitlab.com
211
+ - alain.reguera@gmail.com
198
212
  executables: []
199
213
  extensions: []
200
214
  extra_rdoc_files: []
@@ -202,6 +216,8 @@ files:
202
216
  - LICENSE
203
217
  - README.md
204
218
  - lib/jekyll-link-decorator.rb
219
+ - lib/jekyll-link-decorator/instrumentation.rb
220
+ - lib/jekyll-link-decorator/version.rb
205
221
  homepage: https://gitlab.com/CentOS/artwork/centos-web/jekyll-link-decorator
206
222
  licenses:
207
223
  - MIT