yard-fence 0.1.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.
data/RUBOCOP.md ADDED
@@ -0,0 +1,71 @@
1
+ # RuboCop Usage Guide
2
+
3
+ ## Overview
4
+
5
+ A tale of two RuboCop plugin gems.
6
+
7
+ ### RuboCop Gradual
8
+
9
+ This project uses `rubocop_gradual` instead of vanilla RuboCop for code style checking. The `rubocop_gradual` tool allows for gradual adoption of RuboCop rules by tracking violations in a lock file.
10
+
11
+ ### RuboCop LTS
12
+
13
+ This project uses `rubocop-lts` to ensure, on a best-effort basis, compatibility with Ruby >= 1.9.2.
14
+ RuboCop rules are meticulously configured by the `rubocop-lts` family of gems to ensure that a project is compatible with a specific version of Ruby. See: https://rubocop-lts.gitlab.io for more.
15
+
16
+ ## Checking RuboCop Violations
17
+
18
+ To check for RuboCop violations in this project, always use:
19
+
20
+ ```bash
21
+ bundle exec rake rubocop_gradual:check
22
+ ```
23
+
24
+ **Do not use** the standard RuboCop commands like:
25
+ - `bundle exec rubocop`
26
+ - `rubocop`
27
+
28
+ ## Understanding the Lock File
29
+
30
+ The `.rubocop_gradual.lock` file tracks all current RuboCop violations in the project. This allows the team to:
31
+
32
+ 1. Prevent new violations while gradually fixing existing ones
33
+ 2. Track progress on code style improvements
34
+ 3. Ensure CI builds don't fail due to pre-existing violations
35
+
36
+ ## Common Commands
37
+
38
+ - **Check violations**
39
+ - `bundle exec rake rubocop_gradual`
40
+ - `bundle exec rake rubocop_gradual:check`
41
+ - **(Safe) Autocorrect violations, and update lockfile if no new violations**
42
+ - `bundle exec rake rubocop_gradual:autocorrect`
43
+ - **Force update the lock file (w/o autocorrect) to match violations present in code**
44
+ - `bundle exec rake rubocop_gradual:force_update`
45
+
46
+ ## Workflow
47
+
48
+ 1. Before submitting a PR, run `bundle exec rake rubocop_gradual:autocorrect`
49
+ a. or just the default `bundle exec rake`, as autocorrection is a pre-requisite of the default task.
50
+ 2. If there are new violations, either:
51
+ - Fix them in your code
52
+ - Run `bundle exec rake rubocop_gradual:force_update` to update the lock file (only for violations you can't fix immediately)
53
+ 3. Commit the updated `.rubocop_gradual.lock` file along with your changes
54
+
55
+ ## Never add inline RuboCop disables
56
+
57
+ Do not add inline `rubocop:disable` / `rubocop:enable` comments anywhere in the codebase (including specs, except when following the few existing `rubocop:disable` patterns for a rule already being disabled elsewhere in the code). We handle exceptions in two supported ways:
58
+
59
+ - Permanent/structural exceptions: prefer adjusting the RuboCop configuration (e.g., in `.rubocop.yml`) to exclude a rule for a path or file pattern when it makes sense project-wide.
60
+ - Temporary exceptions while improving code: record the current violations in `.rubocop_gradual.lock` via the gradual workflow:
61
+ - `bundle exec rake rubocop_gradual:autocorrect` (preferred; will autocorrect what it can and update the lock only if no new violations were introduced)
62
+ - If needed, `bundle exec rake rubocop_gradual:force_update` (as a last resort when you cannot fix the newly reported violations immediately)
63
+
64
+ In general, treat the rules as guidance to follow; fix violations rather than ignore them. For example, RSpec conventions in this project expect `described_class` to be used in specs that target a specific class under test.
65
+
66
+ ## Benefits of rubocop_gradual
67
+
68
+ - Allows incremental adoption of code style rules
69
+ - Prevents CI failures due to pre-existing violations
70
+ - Provides a clear record of code style debt
71
+ - Enables focused efforts on improving code quality over time
data/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |----------|-----------|
7
+ | 1.latest | ✅ |
8
+
9
+ ## Security contact information
10
+
11
+ To report a security vulnerability, please use the
12
+ [Tidelift security contact](https://tidelift.com/security).
13
+ Tidelift will coordinate the fix and disclosure.
14
+
15
+ ## Additional Support
16
+
17
+ If you are interested in support for versions older than the latest release,
18
+ please consider sponsoring the project / maintainer @ https://liberapay.com/pboling/donate,
19
+ or find other sponsorship links in the [README].
20
+
21
+ [README]: README.md
@@ -0,0 +1,25 @@
1
+ # YARD GFM support shim: ensure Kramdown GFM provider is first in the provider list
2
+ # so markdown code fences and GitHub-style markdown render as expected. This does not
3
+ # alter content; it influences the order in which YARD tries markdown engines.
4
+ # See test_gfm.rb for a quick sanity check of provider order and <pre><code> rendering.
5
+
6
+ # Gratefully and liberally taken from the MIT-licensed https://github.com/bensheldon/good_job/pull/113/files
7
+ require "kramdown"
8
+ require "kramdown-parser-gfm"
9
+
10
+ # Custom markup provider class that always renders Kramdown using GFM (Github Flavored Markdown).
11
+ # GFM is needed to render markdown tables and fenced code blocks in the README.
12
+ module Yard
13
+ module Fence
14
+ class KramdownGfmDocument < Kramdown::Document
15
+ def initialize(source, options = {})
16
+ options[:input] = "GFM" unless options.key?(:input)
17
+ super(source, options)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ # Note:
24
+ # We intentionally do NOT auto-register here to avoid circular require warnings when
25
+ # YARD is loading. Prefer calling Yard::Fence.use_kramdown_gfm! from your .yardopts.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Fence
5
+ module Version
6
+ VERSION = "0.1.0"
7
+ end
8
+ VERSION = Version::VERSION
9
+ end
10
+ end
data/lib/yard/fence.rb ADDED
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Prepare sanitized copies of Markdown/TXT files in tmp/ for YARD consumption.
4
+ #
5
+ # Why this exists
6
+ # - YARD treats any `{…}` sequence in extra files (like README.md) as a potential link
7
+ # to a code object (its "reference tag" syntax). This scanning happens outside of
8
+ # the Markdown renderer and does not reliably respect fenced code blocks.
9
+ # - As a result, normal hash literals in code examples, e.g. {get: :query} or
10
+ # {"Authorization" => "Basic ..."}, trigger lots of `[InvalidLink] Cannot resolve link` warnings.
11
+ #
12
+ # Our constraints
13
+ # - We want to keep the source Markdown clean and readable on GitHub (no HTML entities,
14
+ # no backslash escapes sprinkled throughout examples).
15
+ # - We need YARD to stop interpreting code-fenced braces as links.
16
+ #
17
+ # Strategy (tmp/ preprocessor)
18
+ # - At doc build time, copy top-level *.md/*.txt into tmp/ and transform only the risky
19
+ # brace pairs in contexts YARD misinterprets:
20
+ # * inside triple‑backtick fenced code blocks
21
+ # * inside single‑backtick inline code spans
22
+ # * simple brace placeholders in prose like {issuer} or {{TEMPLATE}}
23
+ # - The transformation uses Unicode "fullwidth" braces: { and }.
24
+ # These look like normal ASCII braces when rendered, but importantly they do not match
25
+ # YARD's `{…}` reference tag regex, so they are ignored by the link resolver.
26
+ # - After YARD finishes generating HTML into docs/, we post‑process the HTML and convert
27
+ # fullwidth braces back to ASCII braces so copying code from the docs works as expected.
28
+ #
29
+ # Key lines to see:
30
+ # - Transform to fullwidth: str.tr('{}', '{}')
31
+ # This replaces ASCII { and } with their fullwidth Unicode counterparts in sanitized tmp files.
32
+ # - Restore to ASCII (HTML): out = src.tr('{}', '{}')
33
+ # This runs after docs are generated and rewrites the HTML contents back to normal { and }.
34
+ #
35
+ # This keeps README.md pristine while eliminating YARD InvalidLink noise and ensuring
36
+ # the published docs remain copy‑pastable.
37
+
38
+ # includes modules from stdlib
39
+ require "fileutils"
40
+
41
+ # third party gems
42
+ require "version_gem"
43
+
44
+ # includes gem files
45
+ require_relative "fence/version"
46
+ require_relative "fence/kramdown_gfm_document"
47
+
48
+ module Yard
49
+ module Fence
50
+ ASCII_BRACES = "{}"
51
+ FULLWIDTH_BRACES = "{}"
52
+ KRAMDOWN_PROVIDER = {lib: :kramdown, const: "KramdownGfmDocument"}
53
+ GLOB_PATTERN = "*.{md,MD,txt,TXT}"
54
+ TRIPLE_TICK_FENCE = /^\s*```/
55
+ INLINE_TICK_FENCE = /`([^`]+)`/
56
+ DOUBLE_BRACE_PLACEHOLDER_REGEX = /{{([^{}]+)}}/
57
+ SINGLE_BRACE_PLACEHOLDER_REGEX = /{([A-Za-z0-9_:\-]+)}/
58
+
59
+ class Error < StandardError; end
60
+ raise Error, "ASCII braces are not the same as Unicode Fullwidth braces" if ASCII_BRACES == FULLWIDTH_BRACES
61
+
62
+ module_function
63
+
64
+ # Replace ASCII { } with Unicode fullwidth { }.
65
+ # Visual effect in browsers is the same, but YARD's link matcher won't recognize them.
66
+ def fullwidth_braces(str)
67
+ str.tr(ASCII_BRACES, FULLWIDTH_BRACES)
68
+ end
69
+
70
+ # Escape braces inside inline `code` spans only.
71
+ def sanitize_inline_code(line)
72
+ line.gsub(INLINE_TICK_FENCE) { |_| "`#{fullwidth_braces($1)}`" }
73
+ end
74
+
75
+ # Walk the text, toggling a simple in_fence state on ``` lines.
76
+ # While inside a fence, convert braces to fullwidth; outside, also sanitize inline code
77
+ # and disarm simple prose placeholders like {issuer} or {{something}}.
78
+ def sanitize_fenced_blocks(text)
79
+ in_fence = false
80
+
81
+ text.each_line.map do |line|
82
+ if line.match?(TRIPLE_TICK_FENCE)
83
+ in_fence = !in_fence
84
+ line
85
+ elsif in_fence
86
+ fullwidth_braces(line)
87
+ else
88
+ ln = sanitize_inline_code(line)
89
+ # IMPORTANT: handle double-brace placeholders first so we don't partially
90
+ # convert the inner {TOKEN} and leave outer ASCII braces from `{{TOKEN}}`.
91
+ ln = ln.gsub(DOUBLE_BRACE_PLACEHOLDER_REGEX) { |m| fullwidth_braces(m) }
92
+ ln.gsub(SINGLE_BRACE_PLACEHOLDER_REGEX) { |m| fullwidth_braces(m) }
93
+ end
94
+ end.join
95
+ end
96
+
97
+ def sanitize_text(text)
98
+ return text unless text.is_a?(String)
99
+ sanitize_fenced_blocks(text)
100
+ end
101
+
102
+ # Copy top-level *.md/*.txt into tmp/ with the above sanitization applied.
103
+ def prepare_tmp_files
104
+ root = Dir.pwd
105
+ outdir = File.join(root, "tmp")
106
+ FileUtils.mkdir_p(outdir)
107
+
108
+ candidates = Dir.glob(File.join(root, GLOB_PATTERN))
109
+ candidates.each do |src|
110
+ next unless File.file?(src)
111
+ content = File.read(src)
112
+ sanitized = sanitize_text(content)
113
+ dst = File.join(outdir, File.basename(src))
114
+ File.write(dst, sanitized)
115
+ end
116
+ end
117
+
118
+ def restore_ascii_braces_in_html_file(html_filepath)
119
+ return unless File.file?(html_filepath)
120
+ content = File.read(html_filepath)
121
+ restored = content.tr(FULLWIDTH_BRACES, ASCII_BRACES)
122
+ File.write(html_filepath, restored)
123
+ end
124
+
125
+ def postprocess_html_docs
126
+ docs = File.join(Dir.pwd, "docs")
127
+ return unless Dir.exist?(docs)
128
+ Dir.glob(File.join(docs, "**", "*.html")).each do |html|
129
+ restore_ascii_braces_in_html_file(html)
130
+ end
131
+ rescue => e
132
+ warn("Yard::Fence.postprocess_html_docs failed: #{e.class}: #{e.message}")
133
+ end
134
+
135
+ # Register Kramdown GFM as the highest priority Markdown provider in YARD.
136
+ # Returns true if registration succeeded, false otherwise.
137
+ #
138
+ # This method is intentionally not executed at file load to avoid circular
139
+ # require warnings when YARD is in the middle of loading itself. Call this
140
+ # from a file loaded via .yardopts (e.g. `-e 'require "yard/fence/kramdown_gfm"; Yard::Fence.use_kramdown_gfm!'`).
141
+ def use_kramdown_gfm!
142
+ providers = ::YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown]
143
+ providers.unshift(KRAMDOWN_PROVIDER)
144
+ providers.uniq! { |p| [p[:lib].to_s, p[:const].to_s] }
145
+ true
146
+ rescue NameError => e
147
+ warn("Yard::Fence.use_kramdown_gfm!: failed to load YARD helper: #{e.class}: #{e.message}")
148
+ false
149
+ end
150
+ end
151
+
152
+ # Execute at load-time so files exist before YARD scans tmp/*.md
153
+ begin
154
+ Yard::Fence.prepare_tmp_files
155
+ rescue => e
156
+ warn("Yard::Fence: failed to prepare tmp files: #{e.class}: #{e.message}")
157
+ end
158
+ end
159
+
160
+ # Extend the Version with VersionGem::Basic to provide semantic version helpers.
161
+ Yard::Fence::Version.class_eval do
162
+ extend VersionGem::Basic
163
+ end
164
+
165
+ # After YARD completes, restore ASCII braces in generated HTML docs.
166
+ # This guarantees the published docs (docs/*.html) show and copy normal { }.
167
+ at_exit do
168
+ Yard::Fence.postprocess_html_docs
169
+ end
data/lib/yard-fence.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # YARD plugin loader for `--plugin fence`.
4
+ # YARD tries requiring several patterns; providing `yard-fence` ensures
5
+ # it can be loaded regardless of whether YARD attempts `yard-fence` or `yard/fence`.
6
+ require_relative "yard/fence"
data/sig/kramdown.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Kramdown
2
+ class Document
3
+ def initialize: (String source, ?::Hash[Symbol, untyped] options) -> void
4
+ end
5
+ end
6
+
@@ -0,0 +1,37 @@
1
+ module Yard
2
+ module Fence
3
+ # Constants
4
+ VERSION: String
5
+ ASCII_BRACES: String
6
+ FULLWIDTH_BRACES: String
7
+ KRAMDOWN_PROVIDER: Hash[Symbol, untyped]
8
+ GLOB_PATTERN: String
9
+ TRIPLE_TICK_FENCE: ::Regexp
10
+ INLINE_TICK_FENCE: ::Regexp
11
+ DOUBLE_BRACE_PLACEHOLDER_REGEX: ::Regexp
12
+ SINGLE_BRACE_PLACEHOLDER_REGEX: ::Regexp
13
+
14
+ # Error class
15
+ class Error < ::StandardError
16
+ end
17
+
18
+ # Module functions
19
+ def self.fullwidth_braces: (String str) -> String
20
+ def self.sanitize_inline_code: (String line) -> String
21
+ def self.sanitize_fenced_blocks: (String text) -> String
22
+ def self.sanitize_text: (String text) -> String
23
+ | (untyped text) -> untyped
24
+ def self.prepare_tmp_files: () -> void
25
+ def self.restore_ascii_braces_in_html_file: (String html_filepath) -> void
26
+ def self.postprocess_html_docs: () -> void
27
+ def self.use_kramdown_gfm!: () -> bool
28
+
29
+ module Version
30
+ VERSION: String
31
+ end
32
+
33
+ class KramdownGfmDocument < ::Kramdown::Document
34
+ def initialize: (String source, ?Hash[Symbol, untyped] options) -> void
35
+ end
36
+ end
37
+ end
data.tar.gz.sig ADDED
Binary file