jekyll-documents 0.7.2 → 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 +24 -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 +10 -6
- data/lib/jekyll/documents/tags/doc_category.rb +11 -5
- data/lib/jekyll/documents/tags/doc_link.rb +15 -9
- data/lib/jekyll/documents/tags/latest_documents.rb +14 -8
- data/lib/jekyll/documents/version.rb +1 -1
- metadata +1 -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,29 @@
|
|
|
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
|
+
|
|
3
27
|
## [0.7.2] - 2026-09-09
|
|
4
28
|
|
|
5
29
|
### Fixed
|
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
|
|
@@ -27,17 +27,21 @@ module Jekyll
|
|
|
27
27
|
scanner.skip(/\s*/)
|
|
28
28
|
next unless scanner.scan(":")
|
|
29
29
|
|
|
30
|
-
scanner
|
|
31
|
-
value = if scanner.scan(/'([^']*)'/) || scanner.scan(/"([^"]*)"/)
|
|
32
|
-
scanner[1]
|
|
33
|
-
else
|
|
34
|
-
scanner.scan(/\S+/)
|
|
35
|
-
end
|
|
30
|
+
value = read_value(scanner)
|
|
36
31
|
options[key] = value unless value.nil?
|
|
37
32
|
end
|
|
38
33
|
|
|
39
34
|
options
|
|
40
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
|
|
41
45
|
end
|
|
42
46
|
end
|
|
43
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)
|
|
@@ -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)
|
|
@@ -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
|