raindown 0.1.0 → 0.2.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/bin/raindown +6 -0
- data/lib/elements.rb +6 -6
- data/lib/lexemes/endpoint_lexeme.rb +23 -0
- data/lib/lexemes/list_lexeme.rb +1 -1
- data/lib/nodes/endpoint_node.rb +33 -0
- data/lib/nodes/list_node.rb +1 -1
- data/lib/raindown/bare_directives.rb +91 -0
- data/lib/raindown/cli.rb +94 -0
- data/lib/raindown/code.rb +36 -0
- data/lib/raindown/code_blocks.rb +48 -0
- data/lib/raindown/config.rb +23 -0
- data/lib/raindown/directives.rb +29 -0
- data/lib/raindown/endpoint/curl.rb +154 -0
- data/lib/raindown/endpoint/endpoint_runner.rb +104 -0
- data/lib/raindown/files.rb +22 -0
- data/lib/raindown/front_matter.rb +27 -0
- data/lib/raindown/node.rb +7 -0
- data/lib/raindown/node_blocks.rb +133 -0
- data/lib/raindown/node_injector.rb +64 -0
- data/lib/raindown/nodes.rb +120 -0
- data/lib/raindown/page.rb +37 -0
- data/lib/raindown/pages.rb +102 -0
- data/lib/raindown/pre_renderer.rb +54 -0
- data/lib/raindown/rewriter.rb +32 -0
- data/lib/raindown/table_formatter.rb +134 -0
- data/lib/raindown/toc.rb +38 -0
- data/lib/raindown/variables.rb +48 -0
- data/lib/raindown/watcher.rb +59 -0
- data/lib/raindown.rb +83 -16
- data/lib/version.rb +1 -1
- metadata +117 -9
- data/lib/lexemes/toc_lexeme.rb +0 -21
- data/lib/nodes/toc_node.rb +0 -41
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'node_injector'
|
|
4
|
+
require_relative 'endpoint/endpoint_runner'
|
|
5
|
+
require_relative 'table_formatter'
|
|
6
|
+
|
|
7
|
+
module Raindown
|
|
8
|
+
# Rewrites the generated parts of a Markdown file in place before it's rendered:
|
|
9
|
+
# node output, like the TOC, endpoint responses and table alignment.
|
|
10
|
+
# Each rewriter responds to rewrite(content:, path:, bare:, execute:, metadata:) and returns the new content.
|
|
11
|
+
module Rewriter
|
|
12
|
+
REWRITERS = [NodeInjector, EndpointRunner, TableFormatter].freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def rewrite(content:, path:, bare: false, execute: false, metadata: NoMetadata)
|
|
17
|
+
REWRITERS.reduce(content) do |rewritten, rewriter|
|
|
18
|
+
rewriter.rewrite(content: rewritten, path:, bare:, execute:, metadata:)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns true when the file changed.
|
|
23
|
+
def update(path:, bare: false, execute: false, metadata: NoMetadata)
|
|
24
|
+
content = File.read(path)
|
|
25
|
+
rewritten = rewrite(content:, path:, bare:, execute:, metadata:)
|
|
26
|
+
return false if rewritten == content
|
|
27
|
+
|
|
28
|
+
File.write(path, rewritten)
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'unicode/display_width'
|
|
4
|
+
|
|
5
|
+
module Raindown
|
|
6
|
+
# Pads every column of each Markdown table to the width of its widest cell.
|
|
7
|
+
class TableFormatter
|
|
8
|
+
DELIMITER_CELL = /\A:?-+:?\z/
|
|
9
|
+
FENCE = /\A\s*(```|~~~)/
|
|
10
|
+
|
|
11
|
+
def self.rewrite(content:, **)
|
|
12
|
+
new(content:).format
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(content:)
|
|
16
|
+
@lines = content.lines
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def format
|
|
20
|
+
output = []
|
|
21
|
+
index = 0
|
|
22
|
+
in_fence = false
|
|
23
|
+
|
|
24
|
+
while index < @lines.length
|
|
25
|
+
line = @lines[index]
|
|
26
|
+
in_fence = !in_fence if line.match?(FENCE)
|
|
27
|
+
|
|
28
|
+
if !in_fence && table_start?(index)
|
|
29
|
+
table_end = find_table_end(index)
|
|
30
|
+
output.concat(align(@lines[index...table_end]))
|
|
31
|
+
index = table_end
|
|
32
|
+
else
|
|
33
|
+
output << line
|
|
34
|
+
index += 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
output.join
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def table_start?(index)
|
|
44
|
+
header = @lines[index]
|
|
45
|
+
delimiter = @lines[index + 1]
|
|
46
|
+
return false unless header&.include?('|') && delimiter
|
|
47
|
+
|
|
48
|
+
delimiter_cells = cells(delimiter)
|
|
49
|
+
delimiter_cells.any? && delimiter_cells.all? { |cell| cell.match?(DELIMITER_CELL) } &&
|
|
50
|
+
delimiter_cells.length == cells(header).length
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def find_table_end(index)
|
|
54
|
+
table_end = index + 2
|
|
55
|
+
table_end += 1 while @lines[table_end]&.include?('|') && !@lines[table_end].strip.empty?
|
|
56
|
+
table_end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def align(table)
|
|
60
|
+
indent = table.first[/\A\s*/]
|
|
61
|
+
rows = table.map { |line| cells(line) }
|
|
62
|
+
header, delimiter, *body = rows
|
|
63
|
+
return table if body.any? { |row| row.length > header.length }
|
|
64
|
+
|
|
65
|
+
table_lines(indent:, header:, body:, alignments: delimiter.map { |cell| alignment(cell) })
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def table_lines(indent:, header:, body:, alignments:)
|
|
69
|
+
body = body.map { |row| row + ([''] * (header.length - row.length)) }
|
|
70
|
+
widths = column_widths(rows: [header, *body])
|
|
71
|
+
|
|
72
|
+
[
|
|
73
|
+
row_line(indent:, cells: header, widths:, alignments:),
|
|
74
|
+
delimiter_line(indent:, widths:, alignments:),
|
|
75
|
+
*body.map { |row| row_line(indent:, cells: row, widths:, alignments:) }
|
|
76
|
+
].map { |line| "#{line}\n" }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Split on pipes that aren't escaped, dropping the optional outer pipes.
|
|
80
|
+
def cells(line)
|
|
81
|
+
content = line.strip
|
|
82
|
+
content = content.delete_prefix('|')
|
|
83
|
+
content = content.delete_suffix('|') unless content.end_with?('\|')
|
|
84
|
+
|
|
85
|
+
content.split(/(?<!\\)\|/, -1).map(&:strip)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def alignment(cell)
|
|
89
|
+
if cell.start_with?(':') && cell.end_with?(':') then :center
|
|
90
|
+
elsif cell.end_with?(':') then :right
|
|
91
|
+
elsif cell.start_with?(':') then :left
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Three is the shortest delimiter cell, which also fits ':-:'.
|
|
96
|
+
def column_widths(rows:)
|
|
97
|
+
rows.transpose.map { |column| [column.map { |cell| display_width(cell) }.max, 3].max }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Wide characters like CJK and emoji take two columns. Emoji are always measured as two,
|
|
101
|
+
# instead of guessing from the terminal, so a file formats the same on every machine.
|
|
102
|
+
def display_width(cell)
|
|
103
|
+
Unicode::DisplayWidth.of(cell, emoji: :all)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def row_line(indent:, cells:, widths:, alignments:)
|
|
107
|
+
padded = cells.each_with_index.map { |cell, i| pad(cell, widths[i], alignments[i]) }
|
|
108
|
+
"#{indent}| #{padded.join(' | ')} |"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def delimiter_line(indent:, widths:, alignments:)
|
|
112
|
+
dashes = widths.each_with_index.map do |width, i|
|
|
113
|
+
case alignments[i]
|
|
114
|
+
when :center then ":#{'-' * (width - 2)}:"
|
|
115
|
+
when :right then "#{'-' * (width - 1)}:"
|
|
116
|
+
when :left then ":#{'-' * (width - 1)}"
|
|
117
|
+
else '-' * width
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
"#{indent}| #{dashes.join(' | ')} |"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def pad(cell, width, alignment)
|
|
125
|
+
padding = width - display_width(cell)
|
|
126
|
+
|
|
127
|
+
case alignment
|
|
128
|
+
when :center then "#{' ' * (padding / 2)}#{cell}#{' ' * (padding - (padding / 2))}"
|
|
129
|
+
when :right then "#{' ' * padding}#{cell}"
|
|
130
|
+
else "#{cell}#{' ' * padding}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/raindown/toc.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
require_relative 'node'
|
|
5
|
+
|
|
6
|
+
module Raindown
|
|
7
|
+
# The node behind toc: and :toc, a table of contents of the document's headings.
|
|
8
|
+
# A project can replace it with its own TOC node in .raindown, like "class TOC < Raindown::TOC".
|
|
9
|
+
class TOC < Node
|
|
10
|
+
def replace(document:, metadata: NoMetadata, bare: false)
|
|
11
|
+
headings = Raindown.headings(markdown: document, metadata:, bare:)
|
|
12
|
+
return '' if headings.empty?
|
|
13
|
+
|
|
14
|
+
min_level = headings.map { |heading| heading[:level] }.min
|
|
15
|
+
|
|
16
|
+
headings.map do |heading|
|
|
17
|
+
"#{' ' * (heading[:level] - min_level)}- [#{heading[:text]}](##{heading[:id]})"
|
|
18
|
+
end.join("\n")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def render(document:, metadata: NoMetadata, bare: false)
|
|
22
|
+
items = Raindown.headings(markdown: document, metadata:, bare:).map do |heading|
|
|
23
|
+
"<li class='h#{heading[:level]}'><a href='##{heading[:id]}'>#{ERB::Util.html_escape(heading[:text])}</a></li>"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
<<~HTML
|
|
27
|
+
<div class="floating">
|
|
28
|
+
<details id="toc" open>
|
|
29
|
+
<summary>Table of contents</summary>
|
|
30
|
+
<ul>
|
|
31
|
+
#{items.join("\n")}
|
|
32
|
+
</ul>
|
|
33
|
+
</details>
|
|
34
|
+
</div>
|
|
35
|
+
HTML
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Raindown
|
|
4
|
+
# The front matter values that Antlers variables like {title} and <{ title }> render.
|
|
5
|
+
# Antlers reads a variable from the binding's locals, then by sending it to the binding's receiver, which is this.
|
|
6
|
+
# It's a BasicObject so a key like "hash" or "display" isn't shadowed by a method, and unknown names render nothing.
|
|
7
|
+
# Nested values work too, like {author.name} and <{ for: tag in: tags }>.
|
|
8
|
+
class Variables < BasicObject
|
|
9
|
+
# Braces in a value become the placeholders that render as braces, so Antlers doesn't read them as a variable.
|
|
10
|
+
def self.wrap(value)
|
|
11
|
+
case value
|
|
12
|
+
when ::Hash then new(value)
|
|
13
|
+
when ::Array then value.map { |item| wrap(item) }
|
|
14
|
+
when ::String then value.gsub(/[{}]/, CODE_BRACES)
|
|
15
|
+
else value
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(values)
|
|
20
|
+
@values = values.to_h { |key, value| [key.to_sym, Variables.wrap(value)] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def respond_to?(name, _include_all = false) # rubocop:disable Style/OptionalBooleanParameter -- Ruby's signature
|
|
24
|
+
@values.key?(name.to_sym)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def send(name, *)
|
|
28
|
+
@values[name.to_sym]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def binding
|
|
32
|
+
::Kernel.binding
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
@values.to_s
|
|
37
|
+
end
|
|
38
|
+
alias inspect to_s
|
|
39
|
+
|
|
40
|
+
def method_missing(*)
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def respond_to_missing?(*)
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../raindown'
|
|
4
|
+
require_relative 'rewriter'
|
|
5
|
+
|
|
6
|
+
module Raindown
|
|
7
|
+
class Watcher
|
|
8
|
+
attr_reader :path, :output, :bare, :execute
|
|
9
|
+
|
|
10
|
+
def initialize(path:, output: nil, bare: false, execute: false)
|
|
11
|
+
@path = path
|
|
12
|
+
@output = output || default_output
|
|
13
|
+
@bare = bare
|
|
14
|
+
@execute = execute
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def start
|
|
18
|
+
require 'listen'
|
|
19
|
+
|
|
20
|
+
$stdout.sync = true
|
|
21
|
+
|
|
22
|
+
rewrite
|
|
23
|
+
render
|
|
24
|
+
puts "Watching #{path} for changes (writing to #{output})..."
|
|
25
|
+
|
|
26
|
+
listener.start
|
|
27
|
+
sleep
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def listener
|
|
33
|
+
directory = File.dirname(File.expand_path(path))
|
|
34
|
+
filename = File.basename(path)
|
|
35
|
+
|
|
36
|
+
Listen.to(directory) do |modified, added, _removed|
|
|
37
|
+
next unless (modified + added).any? { |f| File.basename(f) == filename }
|
|
38
|
+
# Formatting saves the file again, and that change triggers the render.
|
|
39
|
+
next if rewrite
|
|
40
|
+
|
|
41
|
+
render
|
|
42
|
+
puts "Re-rendered #{path} -> #{output}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The files' metadata is loaded again each time, so new and renamed files are listed.
|
|
47
|
+
def rewrite
|
|
48
|
+
Rewriter.update(path:, bare:, execute:, metadata: Pages.for(path))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def render
|
|
52
|
+
File.write(output, Raindown.render_file(path:, bare:, metadata: Pages.for(path)))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def default_output
|
|
56
|
+
"#{File.dirname(path)}/#{File.basename(path, '.*')}.html"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/raindown.rb
CHANGED
|
@@ -1,31 +1,98 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'commonmarker'
|
|
4
|
+
require 'antlers'
|
|
4
5
|
require 'antlers/api'
|
|
6
|
+
require 'nokogiri'
|
|
5
7
|
require_relative 'elements'
|
|
8
|
+
require_relative 'raindown/bare_directives'
|
|
9
|
+
require_relative 'raindown/code'
|
|
10
|
+
require_relative 'raindown/config'
|
|
11
|
+
require_relative 'raindown/directives'
|
|
12
|
+
require_relative 'raindown/front_matter'
|
|
13
|
+
require_relative 'raindown/node_blocks'
|
|
14
|
+
require_relative 'raindown/pages'
|
|
15
|
+
require_relative 'raindown/pre_renderer'
|
|
16
|
+
require_relative 'raindown/variables'
|
|
6
17
|
|
|
7
|
-
module
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
module Raindown
|
|
19
|
+
# Antlers evaluates any braces in the HTML, so braces in code are swapped for placeholders
|
|
20
|
+
# then HTML entities, which browsers still display as braces.
|
|
21
|
+
CODE_BRACES = { '{' => "\u{E000}", '}' => "\u{E001}" }.freeze
|
|
22
|
+
CODE_BRACE_ENTITIES = { "\u{E000}" => '{', "\u{E001}" => '}' }.freeze
|
|
10
23
|
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
# Front matter is metadata, so it's left out of the HTML and the headings.
|
|
25
|
+
MARKDOWN_OPTIONS = { extension: { alerts: true, front_matter_delimiter: '---' } }.freeze
|
|
26
|
+
# Directives are HTML comments until Antlers renders them, and escaped characters like "\#" stay plain text.
|
|
27
|
+
HTML_OPTIONS = { render: { unsafe: true, escaped_char_spans: false } }.freeze
|
|
13
28
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
# Stands in for an app's metadata when there isn't any, so list: and files: list nothing.
|
|
30
|
+
module NoMetadata
|
|
31
|
+
def self.list(**) = []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Headings with an id, which are the ones a table of contents links to.
|
|
35
|
+
HEADINGS = 'h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]'
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
def headings(markdown:, metadata: NoMetadata, bare: false)
|
|
40
|
+
html = html(markdown:, variables: variables(markdown:), metadata:, bare:)
|
|
41
|
+
|
|
42
|
+
Nokogiri::HTML(html).css(HEADINGS).map do |heading|
|
|
43
|
+
{ level: heading.name[1].to_i, text: heading.text.strip, id: heading['id'] }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
# Directives show in bold, unless the file's project hides them in .raindown/config.yml.
|
|
48
|
+
def render_file(path:, metadata: NoMetadata, bare: false, hide_directives: Config.for(path)[:hide_directives])
|
|
49
|
+
render(markdown: File.read(path), metadata:, bare: bare || BareDirectives.file?(path), hide_directives:)
|
|
50
|
+
end
|
|
23
51
|
|
|
24
|
-
|
|
52
|
+
# Front matter keys are variables, like {title} or <{ title }>.
|
|
53
|
+
# Directives like a node's show in bold, or as HTML comments with hide_directives.
|
|
54
|
+
def render(markdown:, metadata: NoMetadata, bare: false, hide_directives: false)
|
|
55
|
+
variables = variables(markdown:)
|
|
56
|
+
template = html(markdown:, variables:, metadata:, bare:)
|
|
57
|
+
context = { document: markdown, metadata:, bare: }
|
|
58
|
+
|
|
59
|
+
if template.include?('<{')
|
|
60
|
+
# Directives whose output is HTML. The rest rendered before Commonmarker, in PreRenderer.
|
|
61
|
+
ast = Antlers.ast(template:, elements: Antlers::Elements[:html, :prop, :slot] + Elements[:endpoint])
|
|
62
|
+
template = "#{Antlers.render(ast:, current_binding: variables.binding, metadata:).chomp}\n"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Nodes and directives render last, so their HTML isn't read as Markdown or directives.
|
|
66
|
+
Directives.render(Nodes.render_placeholders(template, **context), hide: hide_directives)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The HTML from Commonmarker, after PreRenderer and before the directives whose output is HTML.
|
|
70
|
+
def html(markdown:, variables:, metadata:, bare:)
|
|
71
|
+
markdown = NodeBlocks.to_rendered(markdown.dup.force_encoding('UTF-8'), metadata:, bare:)
|
|
72
|
+
markdown = PreRenderer.render(markdown, variables:, metadata:, bare:)
|
|
73
|
+
doc = Commonmarker.parse(hide_directives(markdown:), options: MARKDOWN_OPTIONS)
|
|
74
|
+
preserve_code(doc:)
|
|
75
|
+
|
|
76
|
+
doc.to_html(options: HTML_OPTIONS).gsub('<!-- ANTLERS', '<{').gsub('ANTLERS -->', '}>')
|
|
77
|
+
.gsub(/[\u{E000}\u{E001}]/, CODE_BRACE_ENTITIES)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def variables(markdown:)
|
|
81
|
+
Variables.new(FrontMatter.parse(markdown).first)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Commonmarker escapes directives, so they're hidden in HTML comments until it's done.
|
|
85
|
+
def hide_directives(markdown:)
|
|
86
|
+
markdown.gsub('<{', '<!-- ANTLERS').gsub('}>', 'ANTLERS -->').force_encoding('UTF-8')
|
|
87
|
+
end
|
|
25
88
|
|
|
26
|
-
|
|
89
|
+
# Restore directives in code and hide its braces from Antlers.
|
|
90
|
+
def preserve_code(doc:)
|
|
91
|
+
doc.walk do |node|
|
|
92
|
+
next unless %i[code code_block].include?(node.type)
|
|
27
93
|
|
|
28
|
-
|
|
94
|
+
node.string_content = node.string_content.gsub('<!-- ANTLERS', '<{').gsub('ANTLERS -->', '}>')
|
|
95
|
+
.gsub(/[{}]/, CODE_BRACES)
|
|
29
96
|
end
|
|
30
97
|
end
|
|
31
98
|
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: raindown
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: antlers
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 1.2.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 1.2.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: commonmarker
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
15
29
|
requirements:
|
|
16
30
|
- - ">="
|
|
@@ -24,7 +38,49 @@ dependencies:
|
|
|
24
38
|
- !ruby/object:Gem::Version
|
|
25
39
|
version: '0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
41
|
+
name: dirload
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: listen
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: lownode
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: nokogiri
|
|
28
84
|
requirement: !ruby/object:Gem::Requirement
|
|
29
85
|
requirements:
|
|
30
86
|
- - ">="
|
|
@@ -51,21 +107,73 @@ dependencies:
|
|
|
51
107
|
- - ">="
|
|
52
108
|
- !ruby/object:Gem::Version
|
|
53
109
|
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: trees
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: 0.3.5
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: 0.3.5
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: unicode-display_width
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '3.1'
|
|
131
|
+
type: :runtime
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '3.1'
|
|
54
138
|
description: |
|
|
55
139
|
Raindown is a templating layer for static site generation
|
|
56
140
|
and an annotation framework for Markdown files.
|
|
57
141
|
email:
|
|
58
142
|
- maediprichard@gmail.com
|
|
59
|
-
executables:
|
|
143
|
+
executables:
|
|
144
|
+
- raindown
|
|
60
145
|
extensions: []
|
|
61
146
|
extra_rdoc_files: []
|
|
62
147
|
files:
|
|
148
|
+
- bin/raindown
|
|
63
149
|
- lib/elements.rb
|
|
150
|
+
- lib/lexemes/endpoint_lexeme.rb
|
|
64
151
|
- lib/lexemes/list_lexeme.rb
|
|
65
|
-
- lib/
|
|
152
|
+
- lib/nodes/endpoint_node.rb
|
|
66
153
|
- lib/nodes/list_node.rb
|
|
67
|
-
- lib/nodes/toc_node.rb
|
|
68
154
|
- lib/raindown.rb
|
|
155
|
+
- lib/raindown/bare_directives.rb
|
|
156
|
+
- lib/raindown/cli.rb
|
|
157
|
+
- lib/raindown/code.rb
|
|
158
|
+
- lib/raindown/code_blocks.rb
|
|
159
|
+
- lib/raindown/config.rb
|
|
160
|
+
- lib/raindown/directives.rb
|
|
161
|
+
- lib/raindown/endpoint/curl.rb
|
|
162
|
+
- lib/raindown/endpoint/endpoint_runner.rb
|
|
163
|
+
- lib/raindown/files.rb
|
|
164
|
+
- lib/raindown/front_matter.rb
|
|
165
|
+
- lib/raindown/node.rb
|
|
166
|
+
- lib/raindown/node_blocks.rb
|
|
167
|
+
- lib/raindown/node_injector.rb
|
|
168
|
+
- lib/raindown/nodes.rb
|
|
169
|
+
- lib/raindown/page.rb
|
|
170
|
+
- lib/raindown/pages.rb
|
|
171
|
+
- lib/raindown/pre_renderer.rb
|
|
172
|
+
- lib/raindown/rewriter.rb
|
|
173
|
+
- lib/raindown/table_formatter.rb
|
|
174
|
+
- lib/raindown/toc.rb
|
|
175
|
+
- lib/raindown/variables.rb
|
|
176
|
+
- lib/raindown/watcher.rb
|
|
69
177
|
- lib/version.rb
|
|
70
178
|
homepage: https://github.com/raindeer-rb/raindown
|
|
71
179
|
licenses: []
|
|
@@ -79,14 +187,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
187
|
requirements:
|
|
80
188
|
- - ">="
|
|
81
189
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 3.
|
|
190
|
+
version: 3.4.2
|
|
83
191
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
192
|
requirements:
|
|
85
193
|
- - ">="
|
|
86
194
|
- !ruby/object:Gem::Version
|
|
87
195
|
version: '0'
|
|
88
196
|
requirements: []
|
|
89
|
-
rubygems_version:
|
|
197
|
+
rubygems_version: 3.6.2
|
|
90
198
|
specification_version: 4
|
|
91
199
|
summary: Markup your markdown with a template and annotation layer
|
|
92
200
|
test_files: []
|
data/lib/lexemes/toc_lexeme.rb
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'antlers/api'
|
|
4
|
-
|
|
5
|
-
module Rain
|
|
6
|
-
module TOCLexeme
|
|
7
|
-
include Antlers::Lexeme
|
|
8
|
-
|
|
9
|
-
module_function
|
|
10
|
-
|
|
11
|
-
KEYWORDS = [':toc'].freeze
|
|
12
|
-
|
|
13
|
-
def match?(keywords:, **)
|
|
14
|
-
keywords.keys.include?(':toc')
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def lexeme(**)
|
|
18
|
-
{ toc: :default }
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
data/lib/nodes/toc_node.rb
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'antlers/api'
|
|
4
|
-
require 'nokogiri'
|
|
5
|
-
|
|
6
|
-
module Rain
|
|
7
|
-
class TOCNode < Antlers::LeafNode
|
|
8
|
-
def initialize(name:, template:)
|
|
9
|
-
super(name:)
|
|
10
|
-
|
|
11
|
-
@template = template
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
|
|
15
|
-
doc = Nokogiri::HTML(@template)
|
|
16
|
-
|
|
17
|
-
<<~HTML
|
|
18
|
-
<div class="floating">
|
|
19
|
-
<details id="toc" open>
|
|
20
|
-
<summary>Table of contents</summary>
|
|
21
|
-
<ul>
|
|
22
|
-
#{doc.css('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').map do |h|
|
|
23
|
-
"<li class='#{h.name}'><a href='\##{h['id']}'>#{h.text.strip}</a></li>"
|
|
24
|
-
end.join("\n")}
|
|
25
|
-
</ul>
|
|
26
|
-
</details>
|
|
27
|
-
</div>
|
|
28
|
-
HTML
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
class << self
|
|
32
|
-
def match?(segment:)
|
|
33
|
-
segment[:toc]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def build(template:, **)
|
|
37
|
-
new(name: :toc, template:)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|