jekyll-agent-markdown 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +12 -1
- data/README.md +231 -42
- data/docs/deployment.md +138 -0
- data/examples/cloudflare/src/worker.js +254 -0
- data/examples/cloudflare/wrangler.toml +8 -0
- data/examples/netlify/netlify/edge-functions/markdown-negotiation.ts +281 -0
- data/examples/netlify/netlify.toml +6 -0
- data/examples/nginx/negotiation.js +267 -0
- data/examples/nginx/nginx.conf +40 -0
- data/lib/jekyll/agent_markdown/agent_markdown_link_tag.rb +20 -0
- data/lib/jekyll/agent_markdown/author_metadata.rb +28 -0
- data/lib/jekyll/agent_markdown/collection_validator.rb +73 -0
- data/lib/jekyll/agent_markdown/configuration.rb +67 -6
- data/lib/jekyll/agent_markdown/date_metadata.rb +2 -8
- data/lib/jekyll/agent_markdown/document_exporter.rb +113 -0
- data/lib/jekyll/agent_markdown/document_header.rb +59 -0
- data/lib/jekyll/agent_markdown/document_settings.rb +170 -0
- data/lib/jekyll/agent_markdown/exported_document.rb +17 -0
- data/lib/jekyll/agent_markdown/generator.rb +78 -70
- data/lib/jekyll/agent_markdown/llms_document_index.rb +125 -0
- data/lib/jekyll/agent_markdown/llms_document_ordering.rb +40 -0
- data/lib/jekyll/agent_markdown/llms_full_renderer.rb +72 -0
- data/lib/jekyll/agent_markdown/llms_headings.rb +10 -15
- data/lib/jekyll/agent_markdown/llms_index_renderer.rb +102 -0
- data/lib/jekyll/agent_markdown/llms_text.rb +30 -0
- data/lib/jekyll/agent_markdown/metadata_footer.rb +26 -0
- data/lib/jekyll/agent_markdown/source_documents.rb +66 -0
- data/lib/jekyll/agent_markdown/version.rb +1 -1
- data/lib/jekyll-agent-markdown.rb +3 -0
- metadata +25 -4
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jekyll"
|
|
4
|
+
require_relative "configuration"
|
|
5
|
+
require_relative "date_metadata"
|
|
6
|
+
require_relative "llms_document_index"
|
|
7
|
+
require_relative "llms_document_ordering"
|
|
8
|
+
require_relative "llms_text"
|
|
9
|
+
|
|
10
|
+
module Jekyll
|
|
11
|
+
module AgentMarkdown
|
|
12
|
+
class LlmsFullRenderer
|
|
13
|
+
include Jekyll::Filters::URLFilters
|
|
14
|
+
|
|
15
|
+
def initialize(site, settings, documents)
|
|
16
|
+
@site = site
|
|
17
|
+
@settings = settings
|
|
18
|
+
@index = LlmsDocumentIndex.new(settings, documents)
|
|
19
|
+
@context = Liquid::Context.new({}, {}, { site: site })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
sections = index.sections.map { |section| render_section(section) }
|
|
24
|
+
"#{([site_heading] + sections).join("\n\n")}\n"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :site, :settings, :index
|
|
30
|
+
|
|
31
|
+
def site_heading
|
|
32
|
+
"# #{LlmsText.one_line(site.config.fetch("title", ""))}".rstrip
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def render_section(section)
|
|
36
|
+
(["## #{LlmsText.literal(section.name)}"] + sorted(section.documents).map do |document|
|
|
37
|
+
render_document(document)
|
|
38
|
+
end)
|
|
39
|
+
.join("\n\n")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def render_document(document)
|
|
43
|
+
["### [#{title(document)}](#{escaped_link_url(absolute_markdown_url(document))})",
|
|
44
|
+
"Source: #{absolute_html_url(document)}", document.body].join("\n\n")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def sorted(documents)
|
|
48
|
+
ordering = LlmsDocumentOrdering.new(settings)
|
|
49
|
+
dates = proc { |document| published_date(document) }
|
|
50
|
+
return ordering.legacy_ordered(documents, &dates) if index.default_compatibility?
|
|
51
|
+
|
|
52
|
+
ordering.ordered(documents, &dates)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def absolute_markdown_url(document)
|
|
56
|
+
"#{site.config.fetch("url").sub(%r{/+\z}, "")}#{relative_url(document.markdown_url)}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def absolute_html_url(document)
|
|
60
|
+
return document.html_url if document.html_url.start_with?("http://", "https://")
|
|
61
|
+
|
|
62
|
+
"#{site.config.fetch("url").sub(%r{/+\z}, "")}#{relative_url(document.source_document.url)}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def title(document) = LlmsText.link_title(document.source_document)
|
|
66
|
+
|
|
67
|
+
def published_date(document) = DateMetadata.new(document.source_document.data).published_date
|
|
68
|
+
|
|
69
|
+
def escaped_link_url(url) = LlmsText.escaped_link_url(url)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "author_metadata"
|
|
3
4
|
require_relative "configuration"
|
|
5
|
+
require_relative "llms_text"
|
|
4
6
|
|
|
5
7
|
module Jekyll
|
|
6
8
|
module AgentMarkdown
|
|
@@ -10,38 +12,31 @@ module Jekyll
|
|
|
10
12
|
@settings = settings
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
def to_s
|
|
14
|
-
[title_heading, description_heading, author_heading
|
|
15
|
-
|
|
15
|
+
def to_s(include_articles: true)
|
|
16
|
+
headings = [title_heading, description_heading, author_heading]
|
|
17
|
+
headings += ["## Articles", articles_description] if include_articles
|
|
18
|
+
headings.compact.join("\n\n")
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
private
|
|
19
22
|
|
|
20
23
|
attr_reader :site, :settings
|
|
21
24
|
|
|
22
|
-
def title_heading = "# #{one_line(site.config.fetch("title", ""))}".rstrip
|
|
25
|
+
def title_heading = "# #{LlmsText.one_line(site.config.fetch("title", ""))}".rstrip
|
|
23
26
|
|
|
24
27
|
def description_heading
|
|
25
|
-
description = one_line(site.config["description"])
|
|
28
|
+
description = LlmsText.one_line(site.config["description"])
|
|
26
29
|
"> #{description}" unless description.empty?
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def author_heading
|
|
30
33
|
return unless Configuration.enabled?(settings, "include_author")
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def author_name
|
|
37
|
-
author = site.config["author"]
|
|
38
|
-
author = author["name"] || author[:name] if author.is_a?(Hash)
|
|
39
|
-
one_line(author)
|
|
35
|
+
metadata = AuthorMetadata.new(site.config).to_s
|
|
36
|
+
metadata unless metadata.empty?
|
|
40
37
|
end
|
|
41
38
|
|
|
42
39
|
def articles_description = "> Posts only. Pages and collections are not included."
|
|
43
|
-
|
|
44
|
-
def one_line(value) = value ? value.to_s.gsub(/\s+/, " ").strip : ""
|
|
45
40
|
end
|
|
46
41
|
end
|
|
47
42
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
require "jekyll"
|
|
5
|
+
require_relative "configuration"
|
|
6
|
+
require_relative "date_metadata"
|
|
7
|
+
require_relative "llms_document_index"
|
|
8
|
+
require_relative "llms_document_ordering"
|
|
9
|
+
require_relative "llms_headings"
|
|
10
|
+
require_relative "llms_text"
|
|
11
|
+
|
|
12
|
+
module Jekyll
|
|
13
|
+
module AgentMarkdown
|
|
14
|
+
class LlmsIndexRenderer
|
|
15
|
+
include Jekyll::Filters::URLFilters
|
|
16
|
+
include Liquid::StandardFilters
|
|
17
|
+
|
|
18
|
+
def initialize(site, settings, documents)
|
|
19
|
+
@site = site
|
|
20
|
+
@settings = settings
|
|
21
|
+
@index = LlmsDocumentIndex.new(settings, documents)
|
|
22
|
+
@context = Liquid::Context.new({}, {}, { site: site })
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_s
|
|
26
|
+
return legacy_index if index.default_compatibility?
|
|
27
|
+
|
|
28
|
+
sections = [LlmsHeadings.new(site, settings).to_s(include_articles: false)] +
|
|
29
|
+
index.sections.map { |section| render_section(section) }
|
|
30
|
+
"#{sections.reject(&:empty?).join("\n\n")}\n"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
attr_reader :site, :settings, :index
|
|
36
|
+
|
|
37
|
+
def legacy_index
|
|
38
|
+
headings = LlmsHeadings.new(site, settings).to_s
|
|
39
|
+
links = sorted(index.sections.first&.documents || []).map { |document| entry(document) }.join("\n")
|
|
40
|
+
"#{[headings, links].reject(&:empty?).join("\n\n")}\n"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_section(section)
|
|
44
|
+
entries = sorted(section.documents).map { |document| entry(document) }
|
|
45
|
+
(["## #{LlmsText.literal(section.name)}"] + entries).join("\n")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sorted(documents)
|
|
49
|
+
ordering = LlmsDocumentOrdering.new(settings)
|
|
50
|
+
dates = proc { |document| published_date(document) }
|
|
51
|
+
return ordering.legacy_ordered(documents, &dates) if index.default_compatibility?
|
|
52
|
+
|
|
53
|
+
ordering.ordered(documents, &dates)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def entry(document)
|
|
57
|
+
link = "- [#{title(document)}](#{escaped_link_url(absolute_markdown_url(document))})"
|
|
58
|
+
description = description_for(document)
|
|
59
|
+
link += ": #{description}" unless description.empty?
|
|
60
|
+
return link unless Configuration.enabled?(settings, "include_dates")
|
|
61
|
+
|
|
62
|
+
[link, DateMetadata.new(document.source_document.data).to_s].reject(&:empty?).join(" | ")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def absolute_markdown_url(document)
|
|
66
|
+
"#{site.config.fetch("url").sub(%r{/+\z}, "")}#{relative_url(document.markdown_url)}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def title(document) = LlmsText.link_title(document.source_document)
|
|
70
|
+
|
|
71
|
+
def published_date(document) = DateMetadata.new(document.source_document.data).published_date
|
|
72
|
+
|
|
73
|
+
def description_for(document)
|
|
74
|
+
return "" unless Configuration.enabled?(settings, "include_descriptions")
|
|
75
|
+
|
|
76
|
+
description = description_value(document)
|
|
77
|
+
return "" if description.empty?
|
|
78
|
+
|
|
79
|
+
LlmsText.literal(inline_text(CGI.unescapeHTML(strip_html(rendered_description(description)))))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def description_value(document)
|
|
83
|
+
source = document.source_document
|
|
84
|
+
value = source.data["description"] || source.data["excerpt"]
|
|
85
|
+
value = source.excerpt if value.nil? && source.respond_to?(:excerpt)
|
|
86
|
+
value.to_s.strip
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def rendered_description(description)
|
|
90
|
+
markdown_converter.convert(CGI.unescapeHTML(description))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def inline_text(value) = value.gsub(/\s+/, " ").strip
|
|
94
|
+
|
|
95
|
+
def markdown_converter
|
|
96
|
+
@markdown_converter ||= site.find_converter_instance(Jekyll::Converters::Markdown)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def escaped_link_url(url) = LlmsText.escaped_link_url(url)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module AgentMarkdown
|
|
5
|
+
module LlmsText
|
|
6
|
+
MARKDOWN_CHARACTERS = /[\\\[\]()*_~`<>!#|]/
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def one_line(value) = value ? value.to_s.gsub(/\s+/, " ").strip : ""
|
|
11
|
+
|
|
12
|
+
def literal(value)
|
|
13
|
+
one_line(value).gsub("&", "&").gsub(MARKDOWN_CHARACTERS) { |character| "\\#{character}" }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def link_title(source_document)
|
|
17
|
+
title = source_document.data.fetch("title", basename(source_document))
|
|
18
|
+
one_line(title.to_s).gsub(/[\\\[\]]/) { |character| "\\#{character}" }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def basename(source_document)
|
|
22
|
+
return source_document.basename_without_ext if source_document.respond_to?(:basename_without_ext)
|
|
23
|
+
|
|
24
|
+
source_document.basename
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def escaped_link_url(url) = url.gsub("(", "%28").gsub(")", "%29")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module AgentMarkdown
|
|
5
|
+
class MetadataFooter
|
|
6
|
+
def initialize(entries)
|
|
7
|
+
@text = entries.compact.reject(&:empty?).join(" | ")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def append_to(content)
|
|
11
|
+
return content if text.empty?
|
|
12
|
+
return "#{text}\n" if content.empty?
|
|
13
|
+
|
|
14
|
+
"#{content}#{separator_for(content)}---\n#{text}\n"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :text
|
|
20
|
+
|
|
21
|
+
def separator_for(content)
|
|
22
|
+
content.end_with?("\n") ? "\n" : "\n\n"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jekyll"
|
|
4
|
+
require_relative "configuration"
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module AgentMarkdown
|
|
8
|
+
class SourceDocuments
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
def initialize(site, settings, collection_validator:)
|
|
12
|
+
@site = site
|
|
13
|
+
@settings = settings
|
|
14
|
+
@collection_validator = collection_validator
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def each
|
|
18
|
+
return enum_for(__method__) unless block_given?
|
|
19
|
+
|
|
20
|
+
posts.each { |document| yield document, :post, nil }
|
|
21
|
+
pages.each { |document| yield document, :page, nil }
|
|
22
|
+
collections.each { |document, collection_name| yield document, :collection, collection_name }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :site, :settings, :collection_validator
|
|
28
|
+
|
|
29
|
+
def posts
|
|
30
|
+
return [] unless Configuration.enabled?(settings, "posts")
|
|
31
|
+
|
|
32
|
+
site.posts.docs
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def pages
|
|
36
|
+
return [] unless Configuration.enabled?(settings, "pages")
|
|
37
|
+
|
|
38
|
+
site.pages.select { |page| authored_markdown_page?(page) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def collections
|
|
42
|
+
Configuration.collection_names(settings).flat_map do |collection_name|
|
|
43
|
+
collection_validator.collection!(collection_name).docs.filter_map do |document|
|
|
44
|
+
[document, collection_name] if markdown_document?(document) && document.write?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def authored_markdown_page?(page)
|
|
50
|
+
page.is_a?(Jekyll::Page) &&
|
|
51
|
+
File.file?(site.in_source_dir(page.relative_path)) &&
|
|
52
|
+
markdown_extension?(page.ext)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def markdown_document?(document)
|
|
56
|
+
document.is_a?(Jekyll::Document) && markdown_extension?(document.extname)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def markdown_extension?(extension)
|
|
60
|
+
site.config.fetch("markdown_ext").split(",").any? do |markdown_extension|
|
|
61
|
+
".#{markdown_extension.strip.downcase}" == extension.to_s.downcase
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -3,3 +3,6 @@
|
|
|
3
3
|
require "jekyll"
|
|
4
4
|
require_relative "jekyll/agent_markdown/version"
|
|
5
5
|
require_relative "jekyll/agent_markdown/generator"
|
|
6
|
+
require_relative "jekyll/agent_markdown/agent_markdown_link_tag"
|
|
7
|
+
|
|
8
|
+
Liquid::Template.register_tag("agent_markdown_link", Jekyll::AgentMarkdown::AgentMarkdownLinkTag)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-agent-markdown
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lucian Ghinda
|
|
@@ -29,8 +29,8 @@ dependencies:
|
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '5'
|
|
32
|
-
description: A Jekyll plugin that
|
|
33
|
-
|
|
32
|
+
description: A Jekyll plugin that exports posts, pages, and output collections as
|
|
33
|
+
Markdown siblings plus curated llms.txt and llms-full.txt files.
|
|
34
34
|
email:
|
|
35
35
|
- lucian@ghinda.com
|
|
36
36
|
executables: []
|
|
@@ -40,14 +40,35 @@ files:
|
|
|
40
40
|
- CHANGELOG.md
|
|
41
41
|
- LICENSE.txt
|
|
42
42
|
- README.md
|
|
43
|
+
- docs/deployment.md
|
|
44
|
+
- examples/cloudflare/src/worker.js
|
|
45
|
+
- examples/cloudflare/wrangler.toml
|
|
46
|
+
- examples/netlify/netlify.toml
|
|
47
|
+
- examples/netlify/netlify/edge-functions/markdown-negotiation.ts
|
|
48
|
+
- examples/nginx/negotiation.js
|
|
49
|
+
- examples/nginx/nginx.conf
|
|
43
50
|
- lib/jekyll-agent-markdown.rb
|
|
51
|
+
- lib/jekyll/agent_markdown/agent_markdown_link_tag.rb
|
|
52
|
+
- lib/jekyll/agent_markdown/author_metadata.rb
|
|
53
|
+
- lib/jekyll/agent_markdown/collection_validator.rb
|
|
44
54
|
- lib/jekyll/agent_markdown/configuration.rb
|
|
45
55
|
- lib/jekyll/agent_markdown/date_metadata.rb
|
|
46
56
|
- lib/jekyll/agent_markdown/destination_claims.rb
|
|
57
|
+
- lib/jekyll/agent_markdown/document_exporter.rb
|
|
58
|
+
- lib/jekyll/agent_markdown/document_header.rb
|
|
59
|
+
- lib/jekyll/agent_markdown/document_settings.rb
|
|
60
|
+
- lib/jekyll/agent_markdown/exported_document.rb
|
|
47
61
|
- lib/jekyll/agent_markdown/generator.rb
|
|
62
|
+
- lib/jekyll/agent_markdown/llms_document_index.rb
|
|
63
|
+
- lib/jekyll/agent_markdown/llms_document_ordering.rb
|
|
64
|
+
- lib/jekyll/agent_markdown/llms_full_renderer.rb
|
|
48
65
|
- lib/jekyll/agent_markdown/llms_headings.rb
|
|
66
|
+
- lib/jekyll/agent_markdown/llms_index_renderer.rb
|
|
67
|
+
- lib/jekyll/agent_markdown/llms_text.rb
|
|
49
68
|
- lib/jekyll/agent_markdown/markdown_sibling_path.rb
|
|
69
|
+
- lib/jekyll/agent_markdown/metadata_footer.rb
|
|
50
70
|
- lib/jekyll/agent_markdown/raw_markdown_file.rb
|
|
71
|
+
- lib/jekyll/agent_markdown/source_documents.rb
|
|
51
72
|
- lib/jekyll/agent_markdown/version.rb
|
|
52
73
|
homepage: https://github.com/lucianghinda/jekyll-agent-markdown
|
|
53
74
|
licenses:
|
|
@@ -73,5 +94,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
94
|
requirements: []
|
|
74
95
|
rubygems_version: 4.0.11
|
|
75
96
|
specification_version: 4
|
|
76
|
-
summary: Exports Jekyll
|
|
97
|
+
summary: Exports Jekyll content as Markdown siblings and llms.txt
|
|
77
98
|
test_files: []
|