jekyll-md 0.1.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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.md +22 -0
- data/README.md +103 -0
- data/lib/jekyll/md/configuration.rb +60 -0
- data/lib/jekyll/md/converter.rb +80 -0
- data/lib/jekyll/md/generator.rb +53 -0
- data/lib/jekyll/md/version.rb +7 -0
- data/lib/jekyll/md.rb +6 -0
- data/lib/jekyll-md.rb +3 -0
- metadata +98 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9b38e647be1b229a45a26dc56ff22daa49501775f7be57ffeb5f109167342ae1
|
|
4
|
+
data.tar.gz: 5938e251a981da7e8e8c6934515edb6050a0bc7df213ccf729e4bc1c3fc441ae
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4de38715b596bdb012af865144ee385d83b2428e85f813322cdfac1f34f61f4c75c3fc9ef73e598839724b35c7ed6c5e747c3bc697cd7313e78d1d8819828e52
|
|
7
|
+
data.tar.gz: 5a0876b411b7215de3b8a0ce006b8b9a0a45b11b7fadb7f758a2608fd784e3d5e4dd003e1f0575c35e43053731a455e6d63aa7a289be271549f876d672c6dadc
|
data/CHANGELOG.md
ADDED
data/LICENSE.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Doubrovkine and Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# jekyll-md
|
|
2
|
+
|
|
3
|
+
[](https://github.com/dblock/jekyll-md/actions/workflows/test.yml)
|
|
4
|
+
[](https://coveralls.io/github/dblock/jekyll-md?branch=main)
|
|
5
|
+
|
|
6
|
+
A Jekyll plugin that serves a clean Markdown version of every page, for AI agents and other machine readers.
|
|
7
|
+
|
|
8
|
+
For every rendered HTML page, `jekyll-md` writes a sibling `.md` file (e.g. `/about/index.html` -> `/about.md`) and adds a `<link rel="alternate" type="text/markdown">` tag to the page's `<head>` so agents can discover it.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Add this line to your Jekyll site's `Gemfile`:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
group :jekyll_plugins do
|
|
16
|
+
gem 'jekyll-md'
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
And then run `bundle install`.
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
> This plugin requires a custom Ruby gem and therefore cannot run in GitHub Pages' default build (which only allows a fixed [whitelist of plugins](https://pages.github.com/versions/)). Deploy via a [GitHub Actions workflow](https://jekyllrb.com/docs/continuous-integration/github-actions/) that runs `bundle exec jekyll build` instead (GitHub Pages' "GitHub Actions" build type), and it will work.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
No configuration is required to get started; every rendered HTML page gets a Markdown counterpart.
|
|
28
|
+
|
|
29
|
+
### Configuring the CSS Selector
|
|
30
|
+
|
|
31
|
+
By default (no `selector` configured), `jekyll-md` looks for a `<main>` element or `[role="main"]` in the rendered page (the closest thing HTML has to a convention for "this is the content, not the header/nav/footer chrome"), and converts that. If your layouts don't use either of these, it falls back to converting the entire `<body>`, including navigation, headers, footers, and anything else on the page — this is simple but rarely what you want for a real site, since it dumps your header/nav/footer HTML into every single `.md` file.
|
|
32
|
+
|
|
33
|
+
Many themes (including Jekyll's default `minima`) already wrap page content in `<main>`, so this default may work with no configuration at all. Otherwise, set `selector` to a CSS selector that scopes the conversion to just your content, e.g. the wrapper `div` around `{{ content }}` in your layout:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
md:
|
|
37
|
+
selector: "#markdown-content"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<!-- _layouts/post.html -->
|
|
42
|
+
<article>
|
|
43
|
+
<div id="markdown-content">
|
|
44
|
+
{{ content }}
|
|
45
|
+
</div>
|
|
46
|
+
</article>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You can override the selector for an individual page via front matter:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
---
|
|
53
|
+
md_selector: "#post-body"
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Other Configuration
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
md:
|
|
61
|
+
enabled: true # master on/off switch, default true
|
|
62
|
+
selector: "#markdown-content" # CSS selector to convert; default nil (try <main>/[role=main], then the whole page)
|
|
63
|
+
strip: [script, style] # elements always removed from the selected content before conversion
|
|
64
|
+
link: true # inject <link rel="alternate" type="text/markdown"> into <head>, default true
|
|
65
|
+
exclude: # array of URL glob patterns to skip entirely
|
|
66
|
+
- /404.html
|
|
67
|
+
- /assets/**
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Per-Page Front Matter
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
---
|
|
74
|
+
md: false # opt this page out of Markdown generation entirely
|
|
75
|
+
md_link: false # generate the .md file, but don't add the <link> tag to this page
|
|
76
|
+
md_selector: "#x" # override the selector for this page only
|
|
77
|
+
---
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Avoiding Clobbering Hand-Authored Markdown Pages
|
|
81
|
+
|
|
82
|
+
If a page at the derived destination path already exists after Jekyll writes the site (for example, you hand-author `/tags.md` yourself from a data-driven Liquid template), `jekyll-md` will not overwrite it.
|
|
83
|
+
|
|
84
|
+
## How It Works
|
|
85
|
+
|
|
86
|
+
`jekyll-md` hooks into two points in the Jekyll build:
|
|
87
|
+
|
|
88
|
+
1. `:pages`/`:documents`, `:post_render` — after a page's layout and Liquid have fully rendered, inject the `<link rel="alternate">` tag into its `<head>`.
|
|
89
|
+
2. `:site`, `:post_write` — after Jekyll has written the whole site to disk, walk every page and document, extract the configured selector (or the whole `<body>`) from its rendered HTML, convert it to Markdown, and write it next to the HTML output.
|
|
90
|
+
|
|
91
|
+
## Similar Projects
|
|
92
|
+
|
|
93
|
+
### jekyll-llms
|
|
94
|
+
|
|
95
|
+
Unlike `jekyll-md`, [jekyll-llms](https://github.com/skatkov/jekyll-llms) generates Markdown sidecars from each page's **source** — your original Markdown/HTML file, with Liquid resolved but otherwise untouched — plus an `llms.txt` index. Inline HTML (`<a>`, `<img>`, tables, embeds, etc.) leaks through verbatim, and only pages with Markdown/HTML source get a sidecar, not generated pages like tag or pagination pages.
|
|
96
|
+
|
|
97
|
+
## Contributing
|
|
98
|
+
|
|
99
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
|
100
|
+
|
|
101
|
+
## Copyright and License
|
|
102
|
+
|
|
103
|
+
MIT License, see [LICENSE](LICENSE.md) for details.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Md
|
|
5
|
+
# Reads and merges site-wide and per-page configuration.
|
|
6
|
+
class Configuration
|
|
7
|
+
DEFAULTS = {
|
|
8
|
+
'enabled' => true,
|
|
9
|
+
'selector' => nil,
|
|
10
|
+
'strip' => %w[script style],
|
|
11
|
+
'link' => true,
|
|
12
|
+
'exclude' => []
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(site_config)
|
|
16
|
+
@config = DEFAULTS.merge(site_config || {})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def enabled?
|
|
20
|
+
@config['enabled'] != false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def link?
|
|
24
|
+
@config['link'] != false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def selector
|
|
28
|
+
@config['selector']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def strip_selectors
|
|
32
|
+
Array(@config['strip'])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def excluded?(url)
|
|
36
|
+
Array(@config['exclude']).any? { |pattern| File.fnmatch(pattern, url, File::FNM_PATHNAME) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Per-page overrides via front matter:
|
|
40
|
+
# md: false -- opt this page out entirely
|
|
41
|
+
# md_selector: "#main" -- override the CSS selector for this page
|
|
42
|
+
# md_link: false -- don't inject the <link rel="alternate"> tag for this page
|
|
43
|
+
def enabled_for?(item)
|
|
44
|
+
return false unless enabled?
|
|
45
|
+
|
|
46
|
+
item.data['md'] != false && !excluded?(item.url)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def link_for?(item)
|
|
50
|
+
return false unless link?
|
|
51
|
+
|
|
52
|
+
item.data['md_link'] != false && !excluded?(item.url)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def selector_for(item)
|
|
56
|
+
item.data['md_selector'] || selector
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
require 'reverse_markdown'
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module Md
|
|
8
|
+
# Converts the final, fully-rendered HTML of a page into Markdown.
|
|
9
|
+
#
|
|
10
|
+
# Unlike plugins that render Markdown from the page's *source*
|
|
11
|
+
# (with Liquid tags resolved but HTML left as-is), this converts the
|
|
12
|
+
# actual rendered HTML output, so links, images and other inline
|
|
13
|
+
# HTML end up as clean Markdown syntax instead of leaking through
|
|
14
|
+
# verbatim, and any Liquid that only makes sense in the context of a
|
|
15
|
+
# full page render (includes, site variables, conditionals) is
|
|
16
|
+
# already fully resolved.
|
|
17
|
+
class Converter
|
|
18
|
+
# When no selector is configured, try these, in order, before
|
|
19
|
+
# falling back to the whole <body>. `<main>`/`[role="main"]` are
|
|
20
|
+
# the closest thing to an HTML convention for "this is the page's
|
|
21
|
+
# content, not its header/nav/footer chrome".
|
|
22
|
+
DEFAULT_SELECTORS = ['main', '[role="main"]'].freeze
|
|
23
|
+
|
|
24
|
+
def initialize(strip_selectors: [])
|
|
25
|
+
@strip_selectors = strip_selectors
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the converted Markdown for +html+, scoped to +selector+
|
|
29
|
+
# (a CSS selector), or nil if the selector doesn't match anything.
|
|
30
|
+
#
|
|
31
|
+
# When +selector+ is nil, tries DEFAULT_SELECTORS in turn, falling
|
|
32
|
+
# back to the entire page's <body> if none of them match.
|
|
33
|
+
def convert(html, selector: nil)
|
|
34
|
+
doc = Nokogiri::HTML(html)
|
|
35
|
+
node = selector ? doc.at_css(selector) : default_node_for(doc)
|
|
36
|
+
return nil unless node
|
|
37
|
+
|
|
38
|
+
@strip_selectors.each { |s| node.css(s).remove }
|
|
39
|
+
|
|
40
|
+
markdown = ReverseMarkdown.convert(
|
|
41
|
+
node.inner_html,
|
|
42
|
+
unknown_tags: :bypass,
|
|
43
|
+
github_flavored: true
|
|
44
|
+
).strip
|
|
45
|
+
|
|
46
|
+
return nil if markdown.empty?
|
|
47
|
+
|
|
48
|
+
"#{markdown}\n"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Derives the relative Markdown URL for a page's URL, e.g.:
|
|
52
|
+
# "/" -> "/index.md"
|
|
53
|
+
# "/about/" -> "/about.md"
|
|
54
|
+
# "/2026/foo.html" -> "/2026/foo.md"
|
|
55
|
+
# "/tags/ruby/" -> "/tags/ruby.md"
|
|
56
|
+
def self.md_url_for(url)
|
|
57
|
+
return '/index.md' if url.nil? || url.empty? || url == '/'
|
|
58
|
+
|
|
59
|
+
if url.end_with?('/')
|
|
60
|
+
"#{url.chomp('/')}.md"
|
|
61
|
+
elsif url.end_with?('.html', '.htm')
|
|
62
|
+
url.sub(/\.html?\z/, '.md')
|
|
63
|
+
else
|
|
64
|
+
"#{url}.md"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def default_node_for(doc)
|
|
71
|
+
DEFAULT_SELECTORS.each do |selector|
|
|
72
|
+
node = doc.at_css(selector)
|
|
73
|
+
return node if node
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
doc.at_css('body')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'jekyll'
|
|
5
|
+
require_relative 'configuration'
|
|
6
|
+
require_relative 'converter'
|
|
7
|
+
|
|
8
|
+
module Jekyll
|
|
9
|
+
module Md
|
|
10
|
+
# Runs after Jekyll has written the site: converts every eligible
|
|
11
|
+
# page's rendered HTML output into a sibling Markdown file.
|
|
12
|
+
Jekyll::Hooks.register :site, :post_write do |site|
|
|
13
|
+
config = Configuration.new(site.config['md'])
|
|
14
|
+
next unless config.enabled?
|
|
15
|
+
|
|
16
|
+
converter = Converter.new(strip_selectors: config.strip_selectors)
|
|
17
|
+
|
|
18
|
+
(site.pages + site.docs_to_write).each do |item|
|
|
19
|
+
next unless item.destination(site.dest).end_with?('.html')
|
|
20
|
+
next unless item.output.is_a?(String)
|
|
21
|
+
next unless config.enabled_for?(item)
|
|
22
|
+
|
|
23
|
+
md_url = Converter.md_url_for(item.url)
|
|
24
|
+
dest_path = File.join(site.dest, md_url)
|
|
25
|
+
|
|
26
|
+
# Don't clobber a page that Jekyll itself already wrote to this
|
|
27
|
+
# path, e.g. a hand-authored /tags.md or /posts.md.
|
|
28
|
+
next if File.exist?(dest_path)
|
|
29
|
+
|
|
30
|
+
markdown = converter.convert(item.output, selector: config.selector_for(item))
|
|
31
|
+
next unless markdown
|
|
32
|
+
|
|
33
|
+
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
34
|
+
File.write(dest_path, markdown)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Runs after each page/document is rendered (before layouts are
|
|
39
|
+
# written to disk isn't quite right -- :post_render fires after the
|
|
40
|
+
# full layout chain has been applied, so `item.output` is the final
|
|
41
|
+
# HTML) and injects a <link rel="alternate" type="text/markdown">
|
|
42
|
+
# tag pointing at the page's Markdown counterpart.
|
|
43
|
+
Jekyll::Hooks.register %i[pages documents], :post_render do |item|
|
|
44
|
+
config = Configuration.new(item.site.config['md'])
|
|
45
|
+
next unless config.link_for?(item)
|
|
46
|
+
next unless item.output.is_a?(String) && item.output.include?('</head>')
|
|
47
|
+
|
|
48
|
+
md_url = Converter.md_url_for(item.url)
|
|
49
|
+
link_tag = %(<link href="#{md_url}" type="text/markdown" rel="alternate" title="Markdown">\n)
|
|
50
|
+
item.output = item.output.sub('</head>', "#{link_tag}</head>")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/jekyll/md.rb
ADDED
data/lib/jekyll-md.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jekyll-md
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Daniel Doubrovkine
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-19 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: jekyll
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: nokogiri
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: reverse_markdown
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.0'
|
|
55
|
+
description: |2
|
|
56
|
+
A Jekyll plugin that converts each page's fully rendered HTML output into a Markdown
|
|
57
|
+
sibling file (e.g. /about/index.html -> /about.md) and adds a discovery
|
|
58
|
+
<link rel="alternate" type="text/markdown"> tag to every page's <head>.
|
|
59
|
+
email: dblock@dblock.org
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- LICENSE.md
|
|
66
|
+
- README.md
|
|
67
|
+
- lib/jekyll-md.rb
|
|
68
|
+
- lib/jekyll/md.rb
|
|
69
|
+
- lib/jekyll/md/configuration.rb
|
|
70
|
+
- lib/jekyll/md/converter.rb
|
|
71
|
+
- lib/jekyll/md/generator.rb
|
|
72
|
+
- lib/jekyll/md/version.rb
|
|
73
|
+
homepage: http://github.com/dblock/jekyll-md
|
|
74
|
+
licenses:
|
|
75
|
+
- MIT
|
|
76
|
+
metadata:
|
|
77
|
+
rubygems_mfa_required: 'true'
|
|
78
|
+
post_install_message:
|
|
79
|
+
rdoc_options: []
|
|
80
|
+
require_paths:
|
|
81
|
+
- lib
|
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '3.0'
|
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '2.5'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubygems_version: 3.5.16
|
|
94
|
+
signing_key:
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: Serves a clean Markdown version of every page, converted from the rendered
|
|
97
|
+
HTML.
|
|
98
|
+
test_files: []
|