raindown 0.9.1 → 0.11.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.
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'antlers/api'
4
+
3
5
  module Raindown
4
6
  # The front matter values that Antlers variables like {title} and <{ title }> render.
5
7
  # Antlers reads a variable from the binding's locals, then by sending it to the binding's receiver, which is this.
6
8
  # 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 }>.
9
+ # Nested values are hashes and arrays like {author.name}, {tags.0} or <{ for: name, url in: links }>.
8
10
  class Variables < BasicObject
9
11
  # Braces in a value become the placeholders that render as braces, so Antlers doesn't read them as a variable.
10
12
  def self.wrap(value)
11
13
  case value
12
- when ::Hash then new(value)
14
+ when ::Hash then value.transform_values { |item| wrap(item) }
13
15
  when ::Array then value.map { |item| wrap(item) }
14
16
  when ::String then value.gsub(/[{}]/, CODE_BRACES)
15
17
  else value
@@ -20,6 +22,7 @@ module Raindown
20
22
  def self.unwrap(value)
21
23
  case value
22
24
  when Variables then value.to_h
25
+ when ::Hash then value.transform_values { |item| unwrap(item) }
23
26
  when ::Array then value.map { |item| unwrap(item) }
24
27
  when ::String then value.tr(CODE_BRACES.values.join, CODE_BRACES.keys.join)
25
28
  else value
@@ -28,7 +31,8 @@ module Raindown
28
31
 
29
32
  # A variable's value, like author.name, read the way {author.name} reads it, unwrapped. Unknown names are nil.
30
33
  def self.value(variables, name)
31
- unwrap(name.split('.').reduce(variables) { |value, key| value.respond_to?(key) ? value.send(key) : nil })
34
+ first, *path = name.split('.')
35
+ unwrap(::Antlers::Queries.read_path(value: variables.send(first), path:))
32
36
  end
33
37
 
34
38
  def initialize(values)
@@ -51,7 +51,9 @@ module Raindown
51
51
 
52
52
  # The files' metadata is loaded again each time, so new and renamed files are listed.
53
53
  def rewrite
54
- Rewriter.update(path:, bare:, execute:, metadata: Pages.for(path))
54
+ changed = Rewriter.update(path:, bare:, execute:, metadata: Pages.for(path))
55
+ EndpointRunner.failures(content: Utils.read_markdown(path), path:, bare:).each { warn "Endpoint check failed: #{it}" }
56
+ changed
55
57
  end
56
58
  end
57
59
  end
@@ -30,6 +30,8 @@ module Raindown
30
30
  def update(path)
31
31
  changed = Rewriter.update(path:, bare: @bare, execute: @execute, metadata: Pages.for(path))
32
32
  puts "[#{name}] Updated #{relative(path)}" if changed
33
+ EndpointRunner.failures(content: Utils.read_markdown(path), path:, bare: @bare)
34
+ .each { warn "[#{name}] Endpoint check failed: #{it}" }
33
35
  rescue StandardError => e
34
36
  raise if ENV['RAINDOWN_DEBUG']
35
37
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'code'
4
+ require_relative 'front_matter'
5
+
6
+ module Raindown
7
+ # Obsidian's links between notes, which render as Markdown links to the file they name, by its path in the project,
8
+ # like files: links, while the file keeps them as they are:
9
+ # [[Reading list]] [Reading list](<notes/Reading list.md>)
10
+ # [[Reading list|books]] [books](<notes/Reading list.md>)
11
+ # [[Reading list#To read]] [Reading list > To read](<notes/Reading list.md#to-read>)
12
+ # A name matches a file's name, or the end of its path, like [[notes/Reading list]], without its extension and
13
+ # whatever its case. One that matches no file, and ones in code or front matter, are left as they are, as are
14
+ # embeds, like ![[photo.png]].
15
+ module WikiLinks
16
+ LINK = /(?<!!)\[\[([^\[\]|#\n]+)(?:#([^\[\]|\n]+))?(?:\|([^\[\]\n]+))?\]\]/
17
+
18
+ module_function
19
+
20
+ def to_markdown(markdown, metadata:)
21
+ return markdown unless markdown.include?('[[')
22
+
23
+ pages = metadata.list
24
+ links(markdown).reverse_each.with_object(markdown.b) do |link, bytes|
25
+ page = page_for(link[1], pages)
26
+ bytes[link.byteoffset(0)[0]...link.byteoffset(0)[1]] = markdown_link(link, page).b if page
27
+ end.force_encoding('UTF-8')
28
+ end
29
+
30
+ # The links in the Markdown, outside its code and front matter, as matches.
31
+ def links(markdown)
32
+ start = markdown.bytesize - FrontMatter.parse(markdown).last.bytesize
33
+ code = Code.ranges(markdown)
34
+ found = []
35
+
36
+ markdown.scan(LINK) do
37
+ link = ::Regexp.last_match
38
+ offset = link.byteoffset(0)[0]
39
+ found << link if offset >= start && code.none? { it.cover?(offset) }
40
+ end
41
+
42
+ found
43
+ end
44
+
45
+ # The page a name links to: one whose path without its extension is the name, or ends with it.
46
+ def page_for(name, pages)
47
+ name = name.strip.downcase.sub(/\.(?:md|rd|markdown|raindown)\z/, '')
48
+ stems = pages.to_h { [it, it.path.to_s.downcase.sub(%r{\.[^./]+\z}, '')] }
49
+
50
+ pages.find { stems[it] == name } || pages.find { stems[it].end_with?("/#{name}") }
51
+ end
52
+
53
+ def markdown_link(link, page)
54
+ name, heading, label = link.captures.map { it&.strip }
55
+ label ||= [name, heading].compact.join(' > ')
56
+ anchor = "##{anchor(heading)}" if heading
57
+
58
+ "[#{label.gsub(/[\[\]]/) { "\\#{it}" }}](<#{page.path}#{anchor}>)"
59
+ end
60
+
61
+ # A heading's id, the way Commonmarker makes it, like to-read for "To read".
62
+ def anchor(heading)
63
+ heading.downcase.gsub(/[^\p{Word}\- ]/, '').tr(' ', '-')
64
+ end
65
+ end
66
+ end
data/lib/raindown.rb CHANGED
@@ -17,6 +17,7 @@ require_relative 'raindown/pages'
17
17
  require_relative 'raindown/pre_renderer'
18
18
  require_relative 'raindown/utils'
19
19
  require_relative 'raindown/variables'
20
+ require_relative 'raindown/wiki_links'
20
21
 
21
22
  module Raindown
22
23
  # Antlers evaluates any braces in the HTML, so braces in code are swapped for placeholders
@@ -83,6 +84,7 @@ module Raindown
83
84
  # The document's HTML from Commonmarker, after nodes and the template pass, with components as fragments.
84
85
  def html(markdown:, variables:, metadata: NoMetadata, bare: false)
85
86
  markdown = NodeBlocks.to_rendered(markdown.dup.force_encoding('UTF-8'), metadata:, bare:)
87
+ markdown = WikiLinks.to_markdown(markdown, metadata:)
86
88
  commonmark(PreRenderer.render(markdown, variables:, metadata:, bare:))
87
89
  end
88
90
 
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Raindown
4
- VERSION = '0.9.1'
4
+ VERSION = '0.11.0'
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raindown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-11-03 00:00:00.000000000 Z
10
+ date: 2026-11-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: antlers
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 1.2.2
18
+ version: 1.4.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 1.2.2
25
+ version: 1.4.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: commonmarker
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +121,20 @@ dependencies:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: lowtype
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '1.7'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '1.7'
124
138
  - !ruby/object:Gem::Dependency
125
139
  name: plugs
126
140
  requirement: !ruby/object:Gem::Requirement
@@ -211,8 +225,13 @@ files:
211
225
  - lib/raindown/config.rb
212
226
  - lib/raindown/directives.rb
213
227
  - lib/raindown/document.rb
228
+ - lib/raindown/editing.rb
214
229
  - lib/raindown/endpoint/curl.rb
230
+ - lib/raindown/endpoint/curl_response.rb
231
+ - lib/raindown/endpoint/endpoint_args.rb
215
232
  - lib/raindown/endpoint/endpoint_runner.rb
233
+ - lib/raindown/endpoint/endpoint_variables.rb
234
+ - lib/raindown/endpoint/schema_block.rb
216
235
  - lib/raindown/files.rb
217
236
  - lib/raindown/fragments.rb
218
237
  - lib/raindown/front_matter.rb
@@ -220,6 +239,7 @@ files:
220
239
  - lib/raindown/heading_ids.rb
221
240
  - lib/raindown/js/host.mjs
222
241
  - lib/raindown/js/js_nodes.rb
242
+ - lib/raindown/markdown_nodes.rb
223
243
  - lib/raindown/node.rb
224
244
  - lib/raindown/node_blocks.rb
225
245
  - lib/raindown/node_injector.rb
@@ -229,6 +249,7 @@ files:
229
249
  - lib/raindown/pre_renderer.rb
230
250
  - lib/raindown/projects.rb
231
251
  - lib/raindown/rewriter.rb
252
+ - lib/raindown/server.rb
232
253
  - lib/raindown/stat.rb
233
254
  - lib/raindown/table_formatter.rb
234
255
  - lib/raindown/toc.rb
@@ -304,14 +325,15 @@ files:
304
325
  - lib/raindown/watcher/file_watcher.rb
305
326
  - lib/raindown/watcher/project.rb
306
327
  - lib/raindown/watcher/supervisor.rb
328
+ - lib/raindown/wiki_links.rb
307
329
  - lib/raindown/workers/worker.rb
308
330
  - lib/raindown/workers/workers.rb
309
331
  - lib/version.rb
310
- homepage: https://github.com/raindeer-rb/raindown
332
+ homepage: https://github.com/rainpour/raindown
311
333
  licenses: []
312
334
  metadata:
313
- homepage_uri: https://github.com/raindeer-rb/raindown
314
- source_code_uri: https://github.com/raindeer-rb/raindown/src/branch/main
335
+ homepage_uri: https://github.com/rainpour/raindown
336
+ source_code_uri: https://github.com/rainpour/raindown/tree/main
315
337
  rdoc_options: []
316
338
  require_paths:
317
339
  - lib