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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -1
  3. data/README.md +231 -42
  4. data/docs/deployment.md +138 -0
  5. data/examples/cloudflare/src/worker.js +254 -0
  6. data/examples/cloudflare/wrangler.toml +8 -0
  7. data/examples/netlify/netlify/edge-functions/markdown-negotiation.ts +281 -0
  8. data/examples/netlify/netlify.toml +6 -0
  9. data/examples/nginx/negotiation.js +267 -0
  10. data/examples/nginx/nginx.conf +40 -0
  11. data/lib/jekyll/agent_markdown/agent_markdown_link_tag.rb +20 -0
  12. data/lib/jekyll/agent_markdown/author_metadata.rb +28 -0
  13. data/lib/jekyll/agent_markdown/collection_validator.rb +73 -0
  14. data/lib/jekyll/agent_markdown/configuration.rb +67 -6
  15. data/lib/jekyll/agent_markdown/date_metadata.rb +2 -8
  16. data/lib/jekyll/agent_markdown/document_exporter.rb +113 -0
  17. data/lib/jekyll/agent_markdown/document_header.rb +59 -0
  18. data/lib/jekyll/agent_markdown/document_settings.rb +170 -0
  19. data/lib/jekyll/agent_markdown/exported_document.rb +17 -0
  20. data/lib/jekyll/agent_markdown/generator.rb +78 -70
  21. data/lib/jekyll/agent_markdown/llms_document_index.rb +125 -0
  22. data/lib/jekyll/agent_markdown/llms_document_ordering.rb +40 -0
  23. data/lib/jekyll/agent_markdown/llms_full_renderer.rb +72 -0
  24. data/lib/jekyll/agent_markdown/llms_headings.rb +10 -15
  25. data/lib/jekyll/agent_markdown/llms_index_renderer.rb +102 -0
  26. data/lib/jekyll/agent_markdown/llms_text.rb +30 -0
  27. data/lib/jekyll/agent_markdown/metadata_footer.rb +26 -0
  28. data/lib/jekyll/agent_markdown/source_documents.rb +66 -0
  29. data/lib/jekyll/agent_markdown/version.rb +1 -1
  30. data/lib/jekyll-agent-markdown.rb +3 -0
  31. 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, "## Articles", articles_description]
15
- .compact.join("\n\n")
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
- name = author_name
33
- "Author: #{name}" unless name.empty?
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("&", "&amp;").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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module AgentMarkdown
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  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.3.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 makes post Markdown available alongside rendered
33
- HTML.
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 posts as raw Markdown siblings and llms.txt
97
+ summary: Exports Jekyll content as Markdown siblings and llms.txt
77
98
  test_files: []