jekyll-documents 0.7.1 → 0.7.3
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 +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +2 -0
- data/_includes/documents_search.html +3 -1
- data/lib/jekyll/documents/generator.rb +43 -32
- data/lib/jekyll/documents/options_parser.rb +47 -0
- data/lib/jekyll/documents/tags/doc_category.rb +12 -12
- data/lib/jekyll/documents/tags/doc_link.rb +16 -16
- data/lib/jekyll/documents/tags/document_icon.rb +1 -7
- data/lib/jekyll/documents/tags/latest_documents.rb +16 -20
- data/lib/jekyll/documents/version.rb +1 -1
- data/lib/jekyll-documents.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa6c0e2569d3772c02fe174700001a8290d7b313e7b068781d21627172d9e785
|
|
4
|
+
data.tar.gz: 407cc042bf5b2de1fe1b01cf9c9043bd961c811de7d0e6ee2dc896b1ab29e1dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9016ce9608b013d8b0b6ac4d3756512d99a17cb55d5c2d2d67e096ccb5175e07ca40a17c68629dd09c684b5291fcad0ea1b876d74e7e58b0fedff0a7d1b97c7
|
|
7
|
+
data.tar.gz: f13021dccbed117f29e6c3f2c3d70f9ba9c499f3008ba30083f69c710ba3b30753a72009e3b84fa7fed64c3c73e8a85d20630cbe26de46623a38fb7766cd3986
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.3] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added Semgrep CE security scanning with a custom ReDoS detection rule for pre-commit and CI
|
|
7
|
+
- Added CodeFactor code quality analysis badge and integration
|
|
8
|
+
- Added SEMGREP_APP_TOKEN integration for Cloud dashboard sync from CI
|
|
9
|
+
- Added `rake version:pre_release` task for pre-release security gate with FORCE=1 override
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Updated CI to Node.js 26 and current Node 24-based GitHub Actions runtimes
|
|
13
|
+
- Pinned all GitHub Actions to commit SHAs to prevent supply-chain attacks
|
|
14
|
+
- Added cooldown period to Dependabot configuration
|
|
15
|
+
- Pinned Lunr.js CDN to version 2.3.9 with SRI integrity hash
|
|
16
|
+
- Bumped actions/checkout to v7.0.1 (SHA-pinned)
|
|
17
|
+
- Reduced method complexity across 6 flagged CodeFactor findings:
|
|
18
|
+
- `generator.rb`: Extracted `process_file` and `merge_passthrough_fields` from `generate` and `configure_client_search`
|
|
19
|
+
- `doc_link.rb`: Extracted `match_documents` and `report_ambiguous` from `find_document`
|
|
20
|
+
- `doc_category.rb`: Extracted `match_categories` and `report_ambiguous_category` from `resolve_category`
|
|
21
|
+
- `latest_documents.rb`: Extracted `render_list` and `list_item` from `render`
|
|
22
|
+
- `options_parser.rb`: Extracted `read_value` from `parse_options`
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Replaced dangerous subshell in system spec with Open3.capture2
|
|
26
|
+
|
|
27
|
+
## [0.7.2] - 2026-09-09
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Replaced polynomial-time Liquid tag option regexes with a shared linear-time parser
|
|
31
|
+
|
|
3
32
|
## [0.7.1] - 2026-09-09
|
|
4
33
|
|
|
5
34
|
### Changed
|
data/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
[](https://rubygems.org/gems/jekyll-documents)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://deepwiki.com/gundestrup/jekyll-documents)
|
|
9
|
+
[](https://www.codefactor.io/repository/github/gundestrup/jekyll-documents)
|
|
10
|
+
[](https://github.com/gundestrup/jekyll-documents/security/code-scanning)
|
|
9
11
|
|
|
10
12
|
Turn files in `assets/documents/` into browsable document pages.
|
|
11
13
|
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
window.__jekyllDocumentsIndexPath = {{ index_path | relative_url | jsonify }};
|
|
10
10
|
window.__jekyllDocumentsBaseurl = {{ site.baseurl | jsonify }};
|
|
11
11
|
</script>
|
|
12
|
-
<script src="https://unpkg.com/lunr/lunr.js"
|
|
12
|
+
<script src="https://unpkg.com/lunr@2.3.9/lunr.js"
|
|
13
|
+
integrity="sha384-Mrc0XsDxndfJgV5jLnEeThch4j9dNEXAE1vbWeyzmERxSvF8siQfuEczzgUelQQH"
|
|
14
|
+
crossorigin="anonymous"></script>
|
|
13
15
|
<script src="{{ '/assets/js/documents-search.js' | relative_url }}"></script>
|
|
14
16
|
{% endif %}
|
|
@@ -43,39 +43,10 @@ module Jekyll
|
|
|
43
43
|
Dir.glob("#{root}/**/*").each do |path|
|
|
44
44
|
next unless File.file?(path)
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if @config["strict_extensions"] && !@config["include_extensions"].include?(ext)
|
|
49
|
-
::Jekyll.logger.abort_with "jekyll-documents",
|
|
50
|
-
"Unsupported file type: #{path} (#{ext})"
|
|
51
|
-
end
|
|
52
|
-
next unless @config["include_extensions"].include?(ext)
|
|
46
|
+
doc, rel_path = process_file(path, root, collection)
|
|
47
|
+
next unless doc
|
|
53
48
|
|
|
54
|
-
source_path = source_path_for(path, root)
|
|
55
|
-
category_path = document_category_path(source_path)
|
|
56
|
-
category = remap_category(category_path)
|
|
57
|
-
rel_path = normalize_path(File.join(@config["root"], source_path))
|
|
58
49
|
current_paths << rel_path
|
|
59
|
-
basename = File.basename(path, source_extension)
|
|
60
|
-
|
|
61
|
-
date, title, valid = parse_filename(basename)
|
|
62
|
-
if !valid && @config["strict_filename"]
|
|
63
|
-
::Jekyll.logger.abort_with "jekyll-documents",
|
|
64
|
-
"Filename must be 'YYYY-MM-DD_Title.ext' → #{rel_path}"
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
doc = ::Jekyll::Document.new(
|
|
68
|
-
source_stub_for(source_path),
|
|
69
|
-
site: site,
|
|
70
|
-
collection: collection
|
|
71
|
-
)
|
|
72
|
-
file_info = {
|
|
73
|
-
title: title, date: date, category: category, category_path: category_path,
|
|
74
|
-
category_slug: slugify(category, "uncategorized"), source_path: source_path,
|
|
75
|
-
rel_path: rel_path, ext: ext, file_type: ext.delete_prefix("."),
|
|
76
|
-
icon_set: @config["icon_set"], slug: build_slug(basename), path: path
|
|
77
|
-
}
|
|
78
|
-
bake_document_data(doc, file_info)
|
|
79
50
|
collection.docs << doc
|
|
80
51
|
generated_docs << doc
|
|
81
52
|
end
|
|
@@ -87,6 +58,42 @@ module Jekyll
|
|
|
87
58
|
|
|
88
59
|
private
|
|
89
60
|
|
|
61
|
+
def process_file(path, root, collection)
|
|
62
|
+
source_extension = File.extname(path)
|
|
63
|
+
ext = source_extension.downcase
|
|
64
|
+
if @config["strict_extensions"] && !@config["include_extensions"].include?(ext)
|
|
65
|
+
::Jekyll.logger.abort_with "jekyll-documents",
|
|
66
|
+
"Unsupported file type: #{path} (#{ext})"
|
|
67
|
+
end
|
|
68
|
+
return [nil, nil] unless @config["include_extensions"].include?(ext)
|
|
69
|
+
|
|
70
|
+
source_path = source_path_for(path, root)
|
|
71
|
+
rel_path = normalize_path(File.join(@config["root"], source_path))
|
|
72
|
+
basename = File.basename(path, source_extension)
|
|
73
|
+
|
|
74
|
+
date, title, valid = parse_filename(basename)
|
|
75
|
+
if !valid && @config["strict_filename"]
|
|
76
|
+
::Jekyll.logger.abort_with "jekyll-documents",
|
|
77
|
+
"Filename must be 'YYYY-MM-DD_Title.ext' → #{rel_path}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
category_path = document_category_path(source_path)
|
|
81
|
+
category = remap_category(category_path)
|
|
82
|
+
doc = ::Jekyll::Document.new(
|
|
83
|
+
source_stub_for(source_path),
|
|
84
|
+
site: @site,
|
|
85
|
+
collection: collection
|
|
86
|
+
)
|
|
87
|
+
file_info = {
|
|
88
|
+
title: title, date: date, category: category, category_path: category_path,
|
|
89
|
+
category_slug: slugify(category, "uncategorized"), source_path: source_path,
|
|
90
|
+
rel_path: rel_path, ext: ext, file_type: ext.delete_prefix("."),
|
|
91
|
+
icon_set: @config["icon_set"], slug: build_slug(basename), path: path
|
|
92
|
+
}
|
|
93
|
+
bake_document_data(doc, file_info)
|
|
94
|
+
[doc, rel_path]
|
|
95
|
+
end
|
|
96
|
+
|
|
90
97
|
# Auto-injects passthrough_fields into client_search config when the
|
|
91
98
|
# documents collection is indexed. Only acts if client_search is already
|
|
92
99
|
# configured with +documents+ in its collections list — does nothing if
|
|
@@ -96,13 +103,17 @@ module Jekyll
|
|
|
96
103
|
return unless search_config.is_a?(Hash)
|
|
97
104
|
return unless Array(search_config["collections"]).include?("documents")
|
|
98
105
|
|
|
106
|
+
merge_passthrough_fields(search_config)
|
|
107
|
+
search_config["icon_field"] = "icon_url" unless search_config.key?("icon_field")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def merge_passthrough_fields(search_config)
|
|
99
111
|
fields = search_config["passthrough_fields"] || []
|
|
100
112
|
existing = fields.flat_map { |f| f.is_a?(Hash) ? f.keys : [f.to_s] }
|
|
101
113
|
%w[file_type icon_url icon_set].each do |field|
|
|
102
114
|
fields << field unless existing.include?(field)
|
|
103
115
|
end
|
|
104
116
|
search_config["passthrough_fields"] = fields
|
|
105
|
-
search_config["icon_field"] = "icon_url" unless search_config.key?("icon_field")
|
|
106
117
|
end
|
|
107
118
|
|
|
108
119
|
# Ensures a collection exists and is configured for output
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module Documents
|
|
7
|
+
# Linear-time parser for Liquid tag markup key:value pairs.
|
|
8
|
+
# Replaces polynomial-redos-vulnerable regex scanning with
|
|
9
|
+
# anchored scans that advance through the input once.
|
|
10
|
+
module OptionsParser
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def parse_options(markup)
|
|
14
|
+
scanner = StringScanner.new(markup.to_s)
|
|
15
|
+
options = {}
|
|
16
|
+
|
|
17
|
+
until scanner.eos?
|
|
18
|
+
scanner.skip(/\s+/)
|
|
19
|
+
break if scanner.eos?
|
|
20
|
+
|
|
21
|
+
key = scanner.scan(/\w+/)
|
|
22
|
+
unless key
|
|
23
|
+
scanner.getch
|
|
24
|
+
next
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
scanner.skip(/\s*/)
|
|
28
|
+
next unless scanner.scan(":")
|
|
29
|
+
|
|
30
|
+
value = read_value(scanner)
|
|
31
|
+
options[key] = value unless value.nil?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
options
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def read_value(scanner)
|
|
38
|
+
scanner.skip(/\s*/)
|
|
39
|
+
if scanner.scan(/'([^']*)'/) || scanner.scan(/"([^"]*)"/)
|
|
40
|
+
scanner[1]
|
|
41
|
+
else
|
|
42
|
+
scanner.scan(/\S+/)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -13,18 +13,24 @@ module Jekyll
|
|
|
13
13
|
normalized = query.to_s.strip.downcase
|
|
14
14
|
return nil if normalized.empty?
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
category_values(category).include?(normalized)
|
|
18
|
-
end
|
|
19
|
-
matches = exact.empty? ? partial_categories(categories, normalized) : exact
|
|
16
|
+
matches = match_categories(categories, normalized)
|
|
20
17
|
aggregate = options["aggregate"] == "true" && options["list"] == "true"
|
|
21
18
|
return matches if matches.one? || (aggregate && matches.any?)
|
|
22
19
|
return nil if matches.empty?
|
|
23
20
|
|
|
21
|
+
report_ambiguous_category(site, query, matches)
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def match_categories(categories, normalized)
|
|
26
|
+
exact = categories.select { |cat| category_values(cat).include?(normalized) }
|
|
27
|
+
exact.empty? ? partial_categories(categories, normalized) : exact
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def report_ambiguous_category(site, query, matches)
|
|
24
31
|
paths = matches.map { |category| category["path"] }.sort.join(", ")
|
|
25
32
|
message = "Ambiguous doc_category #{query.inspect}; matches: #{paths}. Rendering nothing."
|
|
26
33
|
report_resolution_issue(site, message)
|
|
27
|
-
nil
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
def resolve_category_path(path, categories, site)
|
|
@@ -152,13 +158,7 @@ module Jekyll
|
|
|
152
158
|
end
|
|
153
159
|
|
|
154
160
|
def extract_options(text)
|
|
155
|
-
|
|
156
|
-
text.to_s.scan(
|
|
157
|
-
/(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|([^\s]+))/
|
|
158
|
-
) do |key, single, double, bare|
|
|
159
|
-
options[key] = single || double || bare
|
|
160
|
-
end
|
|
161
|
-
options
|
|
161
|
+
OptionsParser.parse_options(text)
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
def escape_html(value)
|
|
@@ -13,21 +13,27 @@ module Jekyll
|
|
|
13
13
|
normalized = query.to_s.strip.downcase
|
|
14
14
|
return nil if normalized.empty?
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
matches = if exact.empty?
|
|
18
|
-
docs.select do |doc|
|
|
19
|
-
document_values(doc).any? { |value| value.include?(normalized) }
|
|
20
|
-
end
|
|
21
|
-
else
|
|
22
|
-
exact
|
|
23
|
-
end
|
|
16
|
+
matches = match_documents(docs, normalized)
|
|
24
17
|
return matches.first if matches.one?
|
|
25
18
|
return nil if matches.empty?
|
|
26
19
|
|
|
20
|
+
report_ambiguous(site, query, matches)
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def match_documents(docs, normalized)
|
|
25
|
+
exact = docs.select { |doc| document_values(doc).include?(normalized) }
|
|
26
|
+
return exact unless exact.empty?
|
|
27
|
+
|
|
28
|
+
docs.select do |doc|
|
|
29
|
+
document_values(doc).any? { |value| value.include?(normalized) }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def report_ambiguous(site, query, matches)
|
|
27
34
|
paths = matches.map { |doc| doc.data["source_path"] || doc.path }.sort.join(", ")
|
|
28
35
|
message = "Ambiguous doc_link #{query.inspect}; matches: #{paths}. Rendering nothing."
|
|
29
36
|
report_resolution_issue(site, message)
|
|
30
|
-
nil
|
|
31
37
|
end
|
|
32
38
|
|
|
33
39
|
def find_document_by_path(docs, path, site)
|
|
@@ -148,13 +154,7 @@ module Jekyll
|
|
|
148
154
|
end
|
|
149
155
|
|
|
150
156
|
def extract_options(text)
|
|
151
|
-
|
|
152
|
-
text.to_s.scan(
|
|
153
|
-
/(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|([^\s]+))/
|
|
154
|
-
) do |key, single, double, bare|
|
|
155
|
-
options[key] = single || double || bare
|
|
156
|
-
end
|
|
157
|
-
options
|
|
157
|
+
OptionsParser.parse_options(text)
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
def escape_html(value)
|
|
@@ -45,13 +45,7 @@ module Jekyll
|
|
|
45
45
|
|
|
46
46
|
def parse_markup(markup)
|
|
47
47
|
expression = markup.to_s.strip[/\A[^\s]+/]
|
|
48
|
-
|
|
49
|
-
markup.to_s.scan(
|
|
50
|
-
/(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|([^\s]+))/
|
|
51
|
-
) do |key, single, double, bare|
|
|
52
|
-
options[key] = single || double || bare
|
|
53
|
-
end
|
|
54
|
-
[expression, options]
|
|
48
|
+
[expression, OptionsParser.parse_options(markup)]
|
|
55
49
|
end
|
|
56
50
|
|
|
57
51
|
def escape_html(value)
|
|
@@ -20,19 +20,25 @@ module Jekyll
|
|
|
20
20
|
docs = docs.select { |doc| doc.data["category"] == category } if category
|
|
21
21
|
docs = docs.sort_by { |doc| doc.data["date"] || Time.at(0) }.reverse.first(count)
|
|
22
22
|
|
|
23
|
+
render_list(docs)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def render_list(docs)
|
|
23
29
|
out = +"<ul class=\"latest-documents\">\n"
|
|
24
|
-
docs.each
|
|
25
|
-
data = doc.data
|
|
26
|
-
title = escape_html(data["title"])
|
|
27
|
-
url = escape_html(doc.url)
|
|
28
|
-
date = (data["date"] || Time.at(0)).strftime("%Y-%m-%d")
|
|
29
|
-
out << %(<li><a href="#{url}">#{title}</a> <small>(#{date})</small></li>\n)
|
|
30
|
-
end
|
|
30
|
+
docs.each { |doc| out << list_item(doc) }
|
|
31
31
|
out << "</ul>\n"
|
|
32
32
|
out
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
def list_item(doc)
|
|
36
|
+
data = doc.data
|
|
37
|
+
title = escape_html(data["title"])
|
|
38
|
+
url = escape_html(doc.url)
|
|
39
|
+
date = (data["date"] || Time.at(0)).strftime("%Y-%m-%d")
|
|
40
|
+
%(<li><a href="#{url}">#{title}</a> <small>(#{date})</small></li>\n)
|
|
41
|
+
end
|
|
36
42
|
|
|
37
43
|
def escape_html(text)
|
|
38
44
|
return "" unless text
|
|
@@ -46,19 +52,9 @@ module Jekyll
|
|
|
46
52
|
})
|
|
47
53
|
end
|
|
48
54
|
|
|
55
|
+
# supports: count:5 category:'referat'
|
|
49
56
|
def parse_args(markup)
|
|
50
|
-
|
|
51
|
-
args = {}
|
|
52
|
-
# Safe: markup comes from Jekyll template authors (trusted), not end users
|
|
53
|
-
# Runs only during static site generation, not on user requests
|
|
54
|
-
markup.scan(/(\w+)\s*:\s*'([^']*)'|(\w+)\s*:\s*([^\s]+)/).each do |match|
|
|
55
|
-
if match[0]
|
|
56
|
-
args[match[0]] = match[1]
|
|
57
|
-
else
|
|
58
|
-
args[match[2]] = match[3]
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
args
|
|
57
|
+
OptionsParser.parse_options(markup)
|
|
62
58
|
end
|
|
63
59
|
end
|
|
64
60
|
end
|
data/lib/jekyll-documents.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative "jekyll/documents/text_extraction_manifest"
|
|
|
11
11
|
require_relative "jekyll/documents/assets_generator"
|
|
12
12
|
require_relative "jekyll/documents/json_index_generator"
|
|
13
13
|
require_relative "jekyll/documents/layout_registrar"
|
|
14
|
+
require_relative "jekyll/documents/options_parser"
|
|
14
15
|
|
|
15
16
|
# Liquid tag(s)
|
|
16
17
|
require_relative "jekyll/documents/tags/latest_documents"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-documents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svend Gundestrup
|
|
@@ -154,6 +154,7 @@ files:
|
|
|
154
154
|
- lib/jekyll/documents/generator.rb
|
|
155
155
|
- lib/jekyll/documents/json_index_generator.rb
|
|
156
156
|
- lib/jekyll/documents/layout_registrar.rb
|
|
157
|
+
- lib/jekyll/documents/options_parser.rb
|
|
157
158
|
- lib/jekyll/documents/tags/doc_category.rb
|
|
158
159
|
- lib/jekyll/documents/tags/doc_link.rb
|
|
159
160
|
- lib/jekyll/documents/tags/document_icon.rb
|