-
-
Notifications
You must be signed in to change notification settings - Fork 39
/
generator.rb
176 lines (137 loc) · 5.13 KB
/
generator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# frozen_string_literal: true
module JekyllRelativeLinks
class Generator < Jekyll::Generator
attr_accessor :site, :config
# Use Jekyll's native relative_url filter
include Jekyll::Filters::URLFilters
LINK_TEXT_REGEX = %r!(.*?)!.freeze
FRAGMENT_REGEX = %r!(#.+?|)?!.freeze
TITLE_REGEX = %r{(\s+"(?:\\"|[^"])*(?<!\\)"|\s+"(?:\\'|[^'])*(?<!\\)')?}.freeze
FRAG_AND_TITLE_REGEX = %r!#{FRAGMENT_REGEX}#{TITLE_REGEX}!.freeze
INLINE_LINK_REGEX = %r!\[#{LINK_TEXT_REGEX}\]\(([^)]+?)#{FRAG_AND_TITLE_REGEX}\)!.freeze
REFERENCE_LINK_REGEX = %r!^\s*?\[#{LINK_TEXT_REGEX}\]: (.+?)#{FRAG_AND_TITLE_REGEX}\s*?$!.freeze
LINK_REGEX = %r!(#{INLINE_LINK_REGEX}|#{REFERENCE_LINK_REGEX})!.freeze
CONVERTER_CLASS = Jekyll::Converters::Markdown
CONFIG_KEY = "relative_links"
ENABLED_KEY = "enabled"
COLLECTIONS_KEY = "collections"
LOG_KEY = "Relative Links:"
safe true
priority :lowest
def initialize(config)
@config = config
end
def generate(site)
return if disabled?
@site = site
@context = context
documents = site.pages
documents = site.pages + site.docs_to_write if collections?
documents.each do |document|
next unless markdown_extension?(document.extname)
next if document.is_a?(Jekyll::StaticFile)
next if excluded?(document)
replace_relative_links!(document)
end
end
def replace_relative_links!(document)
url_base = File.dirname(document.relative_path)
return document if document.content.nil?
document.content = document.content.dup.gsub(LINK_REGEX) do |original|
link = link_parts(Regexp.last_match)
next original unless replaceable_link?(link.path)
path = path_from_root(link.path, url_base)
url = url_for_path(path)
next original unless url
link.path = url
replacement_text(link)
end
replace_relative_links_excerpt!(document)
rescue ArgumentError => e
raise e unless e.to_s.start_with?("invalid byte sequence in UTF-8")
end
private
# Stores info on a Markdown Link (avoid rubocop's Metrics/ParameterLists warning)
Link = Struct.new(:link_type, :text, :path, :fragment, :title)
def link_parts(matches)
last_inline = 5
link_type = matches[2] ? :inline : :reference
link_text = matches[link_type == :inline ? 2 : last_inline + 1]
relative_path = matches[link_type == :inline ? 3 : last_inline + 2]
fragment = matches[link_type == :inline ? 4 : last_inline + 3]
title = matches[link_type == :inline ? 5 : last_inline + 4]
Link.new(link_type, link_text, relative_path, fragment, title)
end
def context
@context ||= JekyllRelativeLinks::Context.new(site)
end
def markdown_extension?(extension)
markdown_converter.matches(extension)
end
def markdown_converter
@markdown_converter ||= site.find_converter_instance(CONVERTER_CLASS)
end
def url_for_path(path)
path = CGI.unescape(path)
target = potential_targets.find { |p| p.relative_path.sub(%r!\A/!, "") == path }
relative_url(target.url) if target&.url
end
def potential_targets
@potential_targets ||= site.pages + site.static_files + site.docs_to_write
end
def path_from_root(relative_path, url_base)
is_absolute = relative_path.start_with? "/"
relative_path.sub!(%r!\A/!, "")
base = is_absolute ? "" : url_base
absolute_path = File.expand_path(relative_path, base)
absolute_path.sub(%r!\A#{Regexp.escape(Dir.pwd)}/!, "")
end
# @param link [Link] A Link object describing the markdown link to make
def replacement_text(link)
link.path << link.fragment if link.fragment
if link.link_type == :inline
"[#{link.text}](#{link.path}#{link.title})"
else
"\n[#{link.text}]: #{link.path}#{link.title}"
end
end
def absolute_url?(string)
return unless string
Addressable::URI.parse(string).absolute?
rescue Addressable::URI::InvalidURIError
nil
end
def fragment?(string)
string&.start_with?("#")
end
def replaceable_link?(string)
!fragment?(string) && !absolute_url?(string)
end
def option(key)
config[CONFIG_KEY] && config[CONFIG_KEY][key]
end
def disabled?
option(ENABLED_KEY) == false
end
def collections?
option(COLLECTIONS_KEY) == true
end
def excluded?(document)
return false unless option("exclude")
entry_filter = if document.respond_to?(:collection)
document.collection.entry_filter
else
global_entry_filter
end
entry_filter.glob_include?(option("exclude"), document.relative_path).tap do |excluded|
Jekyll.logger.debug(LOG_KEY, "excluded #{document.relative_path}") if excluded
end
end
def global_entry_filter
@global_entry_filter ||= Jekyll::EntryFilter.new(site)
end
def replace_relative_links_excerpt!(document)
document.data["excerpt"] = Jekyll::Excerpt.new(document) if document.data["excerpt"]
end
end
end