jekyll-relative-links 0.7.0 → 0.8.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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ebb41a644bab20c56e388f5b63e9fcaea9e8405de7c6af7853b7b30ca80b5bd
|
|
4
|
+
data.tar.gz: 12fd6b72f8fe9acde53c5fe8059a409610b6d808d63c888e7585de25b93a2160
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb15617682d0df090db9ab9a25ae69c9fd8d50188a2ec49d7fa1a873d01e3ddbf638085ec0620c2a6cdc1a65f38e15f7615385c0132da974c496d0dab7f1b455
|
|
7
|
+
data.tar.gz: 5acc91f26ba6c9ee15341bf0575fc72c7ec4174166cbbc1c602ed80384a518702b74558b61dae9b0ac8f1cbcbb3c2c01eefc98d136dad5b04e0ed8b8170b0447
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllRelativeLinks
|
|
4
|
+
module Filter
|
|
5
|
+
# This filter processes HTML content that's already been converted by the markdownify
|
|
6
|
+
# filter and updates any relative links to markdown files to point to their HTML equivalents.
|
|
7
|
+
# Usage: {{ content | markdownify | rellinks }}
|
|
8
|
+
def rellinks(html)
|
|
9
|
+
return html if html.nil? || html.empty?
|
|
10
|
+
return html if @context.registers[:site].nil?
|
|
11
|
+
|
|
12
|
+
process_links(html, @context.registers[:site])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def process_links(html, site)
|
|
16
|
+
page = @context.registers[:page]
|
|
17
|
+
url_base = page ? File.dirname(page["path"].to_s) : ""
|
|
18
|
+
|
|
19
|
+
html.gsub(%r!<a href="([^"]+\.md)(#([^"]+))?"!) do |match|
|
|
20
|
+
process_link(match, Regexp.last_match, url_base, site)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def process_link(match, regex_match, url_base, site)
|
|
25
|
+
relative_path = regex_match[1]
|
|
26
|
+
fragment = regex_match[3] ? "##{regex_match[3]}" : ""
|
|
27
|
+
|
|
28
|
+
return match if absolute_url?(relative_path) || !relative_path.end_with?(".md")
|
|
29
|
+
|
|
30
|
+
path = path_from_root(relative_path, url_base)
|
|
31
|
+
url = url_for_path(path, site)
|
|
32
|
+
url ? "<a href=\"#{url}#{fragment}\"" : match
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def absolute_url?(string)
|
|
38
|
+
return false unless string
|
|
39
|
+
|
|
40
|
+
Addressable::URI.parse(string).absolute?
|
|
41
|
+
rescue Addressable::URI::InvalidURIError
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def path_from_root(relative_path, url_base)
|
|
46
|
+
is_absolute = relative_path.start_with? "/"
|
|
47
|
+
|
|
48
|
+
relative_path.delete_prefix!("/")
|
|
49
|
+
base = is_absolute ? "" : url_base
|
|
50
|
+
absolute_path = File.expand_path(relative_path, base)
|
|
51
|
+
absolute_path.sub(%r!\A#{Regexp.escape(Dir.pwd)}/!, "")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def url_for_path(path, site)
|
|
55
|
+
path = CGI.unescape(path)
|
|
56
|
+
potential_targets = site.pages + site.static_files + site.docs_to_write
|
|
57
|
+
target = potential_targets.find { |p| p.relative_path.delete_prefix("/") == path }
|
|
58
|
+
relative_url(target.url) if target&.url
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Liquid::Template.register_filter(JekyllRelativeLinks::Filter)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
3
5
|
module JekyllRelativeLinks
|
|
4
6
|
class Generator < Jekyll::Generator
|
|
5
7
|
attr_accessor :site, :config
|
|
@@ -7,7 +9,12 @@ module JekyllRelativeLinks
|
|
|
7
9
|
# Use Jekyll's native relative_url filter
|
|
8
10
|
include Jekyll::Filters::URLFilters
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
# Matches link text, including images like  or ![alt]
|
|
13
|
+
# Pattern explanation:
|
|
14
|
+
# - !\[[^\]]*\](?:\([^\)]*\))? matches complete image patterns:  or ![...]
|
|
15
|
+
# - (?!\]\() uses negative lookahead to match any char except when followed by ](
|
|
16
|
+
# This allows nested ] while stopping at the correct closing bracket
|
|
17
|
+
LINK_TEXT_REGEX = %r{((?:!\[[^\]]*\](?:\([^\)]*\))?|(?!\]\().)*?)}.freeze
|
|
11
18
|
FRAGMENT_REGEX = %r!(#.+?|)?!.freeze
|
|
12
19
|
TITLE_REGEX = %r{(\s+"(?:\\"|[^"])*(?<!\\)"|\s+"(?:\\'|[^'])*(?<!\\)')?}.freeze
|
|
13
20
|
FRAG_AND_TITLE_REGEX = %r!#{FRAGMENT_REGEX}#{TITLE_REGEX}!.freeze
|
|
@@ -53,7 +60,7 @@ module JekyllRelativeLinks
|
|
|
53
60
|
link = link_parts(Regexp.last_match)
|
|
54
61
|
next original unless replaceable_link?(link.path)
|
|
55
62
|
|
|
56
|
-
path = path_from_root(link.path, url_base)
|
|
63
|
+
path = path_from_root(CGI.unescape(link.path), url_base)
|
|
57
64
|
url = url_for_path(path)
|
|
58
65
|
next original unless url
|
|
59
66
|
|
|
@@ -94,8 +101,7 @@ module JekyllRelativeLinks
|
|
|
94
101
|
end
|
|
95
102
|
|
|
96
103
|
def url_for_path(path)
|
|
97
|
-
|
|
98
|
-
target = potential_targets.find { |p| p.relative_path.sub(%r!\A/!, "") == path }
|
|
104
|
+
target = potential_targets_by_path[path]
|
|
99
105
|
relative_url(target.url) if target&.url
|
|
100
106
|
end
|
|
101
107
|
|
|
@@ -103,6 +109,19 @@ module JekyllRelativeLinks
|
|
|
103
109
|
@potential_targets ||= site.pages + site.static_files + site.docs_to_write
|
|
104
110
|
end
|
|
105
111
|
|
|
112
|
+
# Index `potential_targets` by the same key the previous linear `find`
|
|
113
|
+
# compared against, so each `url_for_path` lookup is O(1) instead of
|
|
114
|
+
# O(N). On a site with M markdown link matches and N potential
|
|
115
|
+
# targets, total link-resolution cost goes from O(M*N) to O(M+N).
|
|
116
|
+
# First-wins semantics are preserved against the (unlikely) case of
|
|
117
|
+
# two targets sharing the same `relative_path`.
|
|
118
|
+
def potential_targets_by_path
|
|
119
|
+
@potential_targets_by_path ||= potential_targets.each_with_object({}) do |p, h|
|
|
120
|
+
key = p.relative_path.sub(%r!\A/!, "")
|
|
121
|
+
h[key] = p unless h.key?(key)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
106
125
|
def path_from_root(relative_path, url_base)
|
|
107
126
|
is_absolute = relative_path.start_with? "/"
|
|
108
127
|
|
|
@@ -124,11 +143,11 @@ module JekyllRelativeLinks
|
|
|
124
143
|
end
|
|
125
144
|
|
|
126
145
|
def absolute_url?(string)
|
|
127
|
-
return unless string
|
|
146
|
+
return false unless string
|
|
128
147
|
|
|
129
148
|
Addressable::URI.parse(string).absolute?
|
|
130
149
|
rescue Addressable::URI::InvalidURIError
|
|
131
|
-
|
|
150
|
+
false
|
|
132
151
|
end
|
|
133
152
|
|
|
134
153
|
def fragment?(string)
|
|
@@ -170,7 +189,9 @@ module JekyllRelativeLinks
|
|
|
170
189
|
end
|
|
171
190
|
|
|
172
191
|
def replace_relative_links_excerpt!(document)
|
|
173
|
-
document.data["excerpt"]
|
|
192
|
+
return unless document.data["excerpt"] && !document.data["excerpt"].is_a?(String)
|
|
193
|
+
|
|
194
|
+
document.data["excerpt"] = Jekyll::Excerpt.new(document)
|
|
174
195
|
end
|
|
175
196
|
end
|
|
176
197
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllRelativeLinks
|
|
4
|
+
# Register Jekyll hooks to process HTML output after conversion
|
|
5
|
+
Jekyll::Hooks.register :pages, :post_render do |page|
|
|
6
|
+
next unless JekyllRelativeLinks::Hooks.should_process?(page, page.site.config)
|
|
7
|
+
|
|
8
|
+
page.output = JekyllRelativeLinks::Hooks.process_html_links(page.output, page, page.site)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Jekyll::Hooks.register :documents, :post_render do |document|
|
|
12
|
+
next unless JekyllRelativeLinks::Hooks.should_process_document?(document, document.site.config)
|
|
13
|
+
|
|
14
|
+
document.output = JekyllRelativeLinks::Hooks.process_html_links(document.output, document,
|
|
15
|
+
document.site)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Hooks
|
|
19
|
+
CONVERTER_CLASS = Jekyll::Converters::Markdown
|
|
20
|
+
CONFIG_KEY = "relative_links"
|
|
21
|
+
ENABLED_KEY = "enabled"
|
|
22
|
+
COLLECTIONS_KEY = "collections"
|
|
23
|
+
|
|
24
|
+
# Regex to match markdown links in HTML: <a href="*.md">
|
|
25
|
+
# Capture groups:
|
|
26
|
+
# (1) attributes before href, (2) the .md path,
|
|
27
|
+
# (3) optional fragment, (4) attributes after href
|
|
28
|
+
MARKDOWN_LINK_IN_HTML = %r!<a\s+([^>]*?\s+)?href="([^"]+\.md)(#[^"]*)?"\s*([^>]*)>!m.freeze
|
|
29
|
+
|
|
30
|
+
def self.should_process?(page, config)
|
|
31
|
+
return false if disabled?(config)
|
|
32
|
+
return false unless markdown_extension?(page.extname, page.site)
|
|
33
|
+
return false if excluded?(page, config, page.site)
|
|
34
|
+
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.should_process_document?(document, config)
|
|
39
|
+
return false if disabled?(config)
|
|
40
|
+
return false unless collections_enabled?(config)
|
|
41
|
+
return false unless markdown_extension?(document.extname, document.site)
|
|
42
|
+
return false if excluded?(document, config, document.site)
|
|
43
|
+
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.disabled?(config)
|
|
48
|
+
config[CONFIG_KEY] && config[CONFIG_KEY][ENABLED_KEY] == false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.collections_enabled?(config)
|
|
52
|
+
config[CONFIG_KEY] && config[CONFIG_KEY][COLLECTIONS_KEY] == true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.markdown_extension?(extension, site)
|
|
56
|
+
converter = site.find_converter_instance(CONVERTER_CLASS)
|
|
57
|
+
converter.matches(extension)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.excluded?(document, config, site)
|
|
61
|
+
return false unless config[CONFIG_KEY] && config[CONFIG_KEY]["exclude"]
|
|
62
|
+
|
|
63
|
+
entry_filter = if document.respond_to?(:collection)
|
|
64
|
+
document.collection.entry_filter
|
|
65
|
+
else
|
|
66
|
+
Jekyll::EntryFilter.new(site)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
entry_filter.glob_include?(config[CONFIG_KEY]["exclude"], document.relative_path)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
73
|
+
def self.process_html_links(html, document, site)
|
|
74
|
+
return html if html.nil? || html.empty?
|
|
75
|
+
|
|
76
|
+
url_base = File.dirname(document.relative_path)
|
|
77
|
+
potential_targets = site.pages + site.static_files + site.docs_to_write
|
|
78
|
+
|
|
79
|
+
# Process <a href="*.md"> links that were added via includes
|
|
80
|
+
# rubocop:disable Metrics/BlockLength
|
|
81
|
+
html.gsub(MARKDOWN_LINK_IN_HTML) do |match|
|
|
82
|
+
attributes_before = Regexp.last_match[1] || ""
|
|
83
|
+
relative_path = Regexp.last_match[2]
|
|
84
|
+
fragment = Regexp.last_match[3] || ""
|
|
85
|
+
attributes_after = Regexp.last_match[4] || ""
|
|
86
|
+
|
|
87
|
+
# Skip absolute URLs
|
|
88
|
+
begin
|
|
89
|
+
next match if Addressable::URI.parse(relative_path).absolute?
|
|
90
|
+
rescue Addressable::URI::InvalidURIError
|
|
91
|
+
next match
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Calculate path from root
|
|
95
|
+
is_absolute = relative_path.start_with?("/")
|
|
96
|
+
relative_path_clean = relative_path.delete_prefix("/")
|
|
97
|
+
base = is_absolute ? "" : url_base
|
|
98
|
+
absolute_path = File.expand_path(relative_path_clean, base)
|
|
99
|
+
path = absolute_path.sub(%r!\A#{Regexp.escape(Dir.pwd)}/!, "")
|
|
100
|
+
|
|
101
|
+
# Find the target page and get its URL
|
|
102
|
+
path = CGI.unescape(path)
|
|
103
|
+
target = potential_targets.find { |p| p.relative_path.delete_prefix("/") == path }
|
|
104
|
+
|
|
105
|
+
if target&.url
|
|
106
|
+
# Use Jekyll's URL
|
|
107
|
+
url = target.url
|
|
108
|
+
url = "/#{url}" unless url.start_with?("/")
|
|
109
|
+
|
|
110
|
+
# Build the replacement ensuring proper spacing
|
|
111
|
+
attrs = []
|
|
112
|
+
attrs << attributes_before.strip unless attributes_before.empty?
|
|
113
|
+
attrs << "href=\"#{url}#{fragment}\""
|
|
114
|
+
attrs << attributes_after.strip unless attributes_after.empty?
|
|
115
|
+
"<a #{attrs.join(" ")}>"
|
|
116
|
+
else
|
|
117
|
+
match
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
# rubocop:enable Metrics/BlockLength
|
|
121
|
+
end
|
|
122
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
123
|
+
end
|
|
124
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-relative-links
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben Balter
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: jekyll
|
|
@@ -72,6 +71,20 @@ dependencies:
|
|
|
72
71
|
- - "~>"
|
|
73
72
|
- !ruby/object:Gem::Version
|
|
74
73
|
version: '1.0'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: rubocop-factory_bot
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: 2.26.0
|
|
81
|
+
type: :development
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - "~>"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 2.26.0
|
|
75
88
|
- !ruby/object:Gem::Dependency
|
|
76
89
|
name: rubocop-jekyll
|
|
77
90
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -106,15 +119,70 @@ dependencies:
|
|
|
106
119
|
requirements:
|
|
107
120
|
- - "~>"
|
|
108
121
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '
|
|
122
|
+
version: '3.0'
|
|
110
123
|
type: :development
|
|
111
124
|
prerelease: false
|
|
112
125
|
version_requirements: !ruby/object:Gem::Requirement
|
|
113
126
|
requirements:
|
|
114
127
|
- - "~>"
|
|
115
128
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '
|
|
117
|
-
|
|
129
|
+
version: '3.0'
|
|
130
|
+
- !ruby/object:Gem::Dependency
|
|
131
|
+
name: base64
|
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '0'
|
|
137
|
+
type: :development
|
|
138
|
+
prerelease: false
|
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - ">="
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '0'
|
|
144
|
+
- !ruby/object:Gem::Dependency
|
|
145
|
+
name: benchmark
|
|
146
|
+
requirement: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - ">="
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '0'
|
|
151
|
+
type: :development
|
|
152
|
+
prerelease: false
|
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - ">="
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '0'
|
|
158
|
+
- !ruby/object:Gem::Dependency
|
|
159
|
+
name: ostruct
|
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
|
161
|
+
requirements:
|
|
162
|
+
- - ">="
|
|
163
|
+
- !ruby/object:Gem::Version
|
|
164
|
+
version: '0'
|
|
165
|
+
type: :development
|
|
166
|
+
prerelease: false
|
|
167
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
168
|
+
requirements:
|
|
169
|
+
- - ">="
|
|
170
|
+
- !ruby/object:Gem::Version
|
|
171
|
+
version: '0'
|
|
172
|
+
- !ruby/object:Gem::Dependency
|
|
173
|
+
name: tsort
|
|
174
|
+
requirement: !ruby/object:Gem::Requirement
|
|
175
|
+
requirements:
|
|
176
|
+
- - ">="
|
|
177
|
+
- !ruby/object:Gem::Version
|
|
178
|
+
version: '0'
|
|
179
|
+
type: :development
|
|
180
|
+
prerelease: false
|
|
181
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
182
|
+
requirements:
|
|
183
|
+
- - ">="
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: '0'
|
|
118
186
|
email:
|
|
119
187
|
- ben.balter@github.com
|
|
120
188
|
executables: []
|
|
@@ -123,13 +191,14 @@ extra_rdoc_files: []
|
|
|
123
191
|
files:
|
|
124
192
|
- lib/jekyll-relative-links.rb
|
|
125
193
|
- lib/jekyll-relative-links/context.rb
|
|
194
|
+
- lib/jekyll-relative-links/filter.rb
|
|
126
195
|
- lib/jekyll-relative-links/generator.rb
|
|
196
|
+
- lib/jekyll-relative-links/hooks.rb
|
|
127
197
|
- lib/jekyll-relative-links/version.rb
|
|
128
198
|
homepage: https://github.com/benbalter/jekyll-relative-links
|
|
129
199
|
licenses:
|
|
130
200
|
- MIT
|
|
131
201
|
metadata: {}
|
|
132
|
-
post_install_message:
|
|
133
202
|
rdoc_options: []
|
|
134
203
|
require_paths:
|
|
135
204
|
- lib
|
|
@@ -144,8 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
144
213
|
- !ruby/object:Gem::Version
|
|
145
214
|
version: '0'
|
|
146
215
|
requirements: []
|
|
147
|
-
rubygems_version:
|
|
148
|
-
signing_key:
|
|
216
|
+
rubygems_version: 4.0.16
|
|
149
217
|
specification_version: 4
|
|
150
218
|
summary: A Jekyll plugin to convert relative links to markdown files to their rendered
|
|
151
219
|
equivalents.
|