raindown 0.10.0 → 0.12.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/lib/raindown/actions.rb +15 -9
- data/lib/raindown/bare_directives.rb +14 -0
- data/lib/raindown/blueprints.rb +7 -6
- data/lib/raindown/cli.rb +62 -20
- data/lib/raindown/code_blocks.rb +3 -1
- data/lib/raindown/directives.rb +7 -3
- data/lib/raindown/editing.rb +24 -0
- data/lib/raindown/endpoint/curl.rb +4 -25
- data/lib/raindown/endpoint/curl_response.rb +35 -0
- data/lib/raindown/endpoint/endpoint_args.rb +66 -0
- data/lib/raindown/endpoint/endpoint_runner.rb +52 -23
- data/lib/raindown/endpoint/endpoint_variables.rb +45 -14
- data/lib/raindown/endpoint/schema_block.rb +82 -0
- data/lib/raindown/markdown_nodes.rb +67 -0
- data/lib/raindown/node_blocks.rb +25 -12
- data/lib/raindown/nodes.rb +6 -3
- data/lib/raindown/pages.rb +58 -11
- data/lib/raindown/projects.rb +2 -1
- data/lib/raindown/rewriter.rb +13 -12
- data/lib/raindown/server.rb +73 -0
- data/lib/raindown/tui/blueprints/blueprints_view.rb +1 -1
- data/lib/raindown/tui/feelings/feelings_log.rb +4 -4
- data/lib/raindown/tui/project.rb +3 -2
- data/lib/raindown/watcher/file_watcher.rb +3 -1
- data/lib/raindown/watcher/project.rb +3 -2
- data/lib/raindown/wiki_links.rb +66 -0
- data/lib/raindown.rb +2 -0
- data/lib/version.rb +1 -1
- metadata +22 -1
data/lib/raindown/rewriter.rb
CHANGED
|
@@ -8,26 +8,27 @@ require_relative 'utils'
|
|
|
8
8
|
module Raindown
|
|
9
9
|
# Rewrites the generated parts of a Markdown file in place before it's rendered:
|
|
10
10
|
# node output, like the TOC, endpoint responses and table alignment.
|
|
11
|
-
# Each rewriter responds to rewrite(content:, path:, bare:, execute:, metadata:) and returns the new content.
|
|
12
11
|
module Rewriter
|
|
13
|
-
|
|
12
|
+
# The content written back, and the endpoint checks it failed, like "api.md:3: expected status 200, received 404".
|
|
13
|
+
Rewrite = Data.define(:original, :content, :failures) do
|
|
14
|
+
def changed?
|
|
15
|
+
content != original
|
|
16
|
+
end
|
|
17
|
+
end
|
|
14
18
|
|
|
15
19
|
module_function
|
|
16
20
|
|
|
17
21
|
def rewrite(content:, path:, bare: false, execute: false, metadata: NoMetadata)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
injected = NodeInjector.rewrite(content:, path:, bare:, metadata:)
|
|
23
|
+
endpoints = EndpointRunner.new(content: injected, path:, bare: BareDirectives.enabled?(path:, content: injected, bare:), execute:)
|
|
24
|
+
Rewrite.new(original: content, content: TableFormatter.rewrite(content: endpoints.rewrite), failures: endpoints.failures)
|
|
21
25
|
end
|
|
22
26
|
|
|
23
|
-
#
|
|
27
|
+
# Writes the file only when it changed.
|
|
24
28
|
def update(path:, bare: false, execute: false, metadata: NoMetadata)
|
|
25
|
-
content
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
File.write(path, rewritten)
|
|
30
|
-
true
|
|
29
|
+
rewrite(content: Utils.read_markdown(path), path:, bare:, execute:, metadata:).tap do |rewrite|
|
|
30
|
+
File.write(path, rewrite.content) if rewrite.changed?
|
|
31
|
+
end
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative '../version'
|
|
5
|
+
require_relative 'editing'
|
|
6
|
+
require_relative 'projects'
|
|
7
|
+
require_relative 'workers/workers'
|
|
8
|
+
|
|
9
|
+
module Raindown
|
|
10
|
+
# "raindown server" keeps Raindown loaded for editors, like the Obsidian plugin and the VS Code extension, so a render
|
|
11
|
+
# doesn't start Ruby again. Once it's ready, it says so, then answers a JSON request a line, in order:
|
|
12
|
+
# {"ready": true, "version": "0.10.0"}
|
|
13
|
+
# {"id": 1, "path": "/notes/a.md", "markdown": "<!-- toc: -->", "rewrite": true}
|
|
14
|
+
# {"id": 1, "output": "<!-- toc: -->\n- ...", "warnings": []}
|
|
15
|
+
# Rewriting answers like --stdin --rewrite, with "execute" like -x, and otherwise it's HTML, like --stdin. An error's
|
|
16
|
+
# answered as {"id": 1, "error": "raindown: /notes/a.md: ..."}, like the CLI writes it.
|
|
17
|
+
# Each project renders in a worker of its own, which starts again when its .raindown folder changes, so its nodes,
|
|
18
|
+
# config and schemas are never stale. Without fork, as on Windows, node changes need the server started again.
|
|
19
|
+
class Server
|
|
20
|
+
def initialize(input: $stdin, output: $stdout)
|
|
21
|
+
@input = input
|
|
22
|
+
@output = output
|
|
23
|
+
@workers = Workers.new(root: Dir.pwd) { Editing }
|
|
24
|
+
@stamps = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start
|
|
28
|
+
write(ready: true, version: VERSION)
|
|
29
|
+
@input.each_line { |line| write(answer(line)) }
|
|
30
|
+
ensure
|
|
31
|
+
@workers.stop
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def answer(line)
|
|
37
|
+
request = JSON.parse(line)
|
|
38
|
+
{ id: request['id'], **edit(request) }
|
|
39
|
+
rescue JSON::ParserError, KeyError => e
|
|
40
|
+
{ id: request&.dig('id'), error: "raindown: #{e.message}" }
|
|
41
|
+
rescue Workers::Worker::Error => e
|
|
42
|
+
{ id: request['id'], error: "raindown: #{request['path']}: #{e.message}" }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def edit(request)
|
|
46
|
+
path = File.expand_path(request.fetch('path'))
|
|
47
|
+
options = { path:, bare: request['bare'] == true }
|
|
48
|
+
worker = worker_for(path)
|
|
49
|
+
return worker.call(:rewrite_markdown, request.fetch('markdown'), **options, execute: request['execute'] == true) if request['rewrite']
|
|
50
|
+
|
|
51
|
+
{ output: worker.call(:render_markdown, request.fetch('markdown'), **options) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The project's worker, started again first when a file in its .raindown folder was added, changed or removed.
|
|
55
|
+
def worker_for(path)
|
|
56
|
+
project = Projects.root(path) || @workers.root
|
|
57
|
+
@workers.restart(project) if nodes_changed?(project)
|
|
58
|
+
@workers[project]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def nodes_changed?(project)
|
|
62
|
+
stamp = Dir.glob(File.join(project, Nodes::FOLDER, '**', '*')).to_h { [it, File.mtime(it)] }
|
|
63
|
+
changed = @stamps.key?(project) && @stamps[project] != stamp
|
|
64
|
+
@stamps[project] = stamp
|
|
65
|
+
changed
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def write(message)
|
|
69
|
+
@output.puts(JSON.generate(message))
|
|
70
|
+
@output.flush
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -11,7 +11,7 @@ require_relative 'filter'
|
|
|
11
11
|
|
|
12
12
|
module Raindown
|
|
13
13
|
module TUI
|
|
14
|
-
# The blueprints from Claude Code's replies, in the project's .raindown/blueprints.md, newest first, then those in its
|
|
14
|
+
# The blueprints from Claude Code's replies, in the project's .raindown/data/blueprints.md, newest first, then those in its
|
|
15
15
|
# code's comments, one at a time, of
|
|
16
16
|
# those its Filter's keywords match, each traced to its code: the arrows move between the boxes drawn in one, or a
|
|
17
17
|
# click on one, the code each box names shows below, Space or another click moving on to
|
|
@@ -22,16 +22,16 @@ module Raindown
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
# The feelings everyone's sorted about the code as it is now, kept in .raindown/feelings.csv with the commit they were
|
|
25
|
+
# The feelings everyone's sorted about the code as it is now, kept in .raindown/data/feelings.csv with the commit they were
|
|
26
26
|
# felt at, which a node can count, like the README's code debt. A feeling about code that's since changed, or gone, moves to
|
|
27
|
-
# .raindown/memories.csv, so the feelings stay few, and the history's kept.
|
|
27
|
+
# .raindown/data/memories.csv, so the feelings stay few, and the history's kept.
|
|
28
28
|
class FeelingsLog
|
|
29
29
|
TEMPERS = %w[frolic numb woe dread malice].freeze
|
|
30
30
|
HEADER = %i[timestamp commit checksum location method temper note].freeze
|
|
31
31
|
def initialize(root:)
|
|
32
32
|
@root = root
|
|
33
|
-
@csv = File.join(root, '.raindown', 'feelings.csv')
|
|
34
|
-
@memories = File.join(root, '.raindown', 'memories.csv')
|
|
33
|
+
@csv = File.join(root, '.raindown', 'data', 'feelings.csv')
|
|
34
|
+
@memories = File.join(root, '.raindown', 'data', 'memories.csv')
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Adds the feeling, as the newest, in place of one about the method before, which moves to the memories if it was
|
data/lib/raindown/tui/project.rb
CHANGED
|
@@ -90,8 +90,9 @@ module Raindown
|
|
|
90
90
|
|
|
91
91
|
def endpoint(path, lines, directive)
|
|
92
92
|
snippet = CodeBlocks.endpoint(lines, directive.line).first&.dig(:body)
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
variables = EndpointVariables.new(path:, content: lines.join)
|
|
94
|
+
request = Curl.request(snippet.to_s, variables)
|
|
95
|
+
Endpoint.new(project: root, path:, line: directive.line, method: request.method, url: variables.redact(request.url)) if request
|
|
95
96
|
end
|
|
96
97
|
|
|
97
98
|
def charts_in(path)
|
|
@@ -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
|
+
rewrite = Rewriter.update(path:, bare:, execute:, metadata: Pages.for(path))
|
|
55
|
+
rewrite.failures.each { warn "Endpoint check failed: #{it}" }
|
|
56
|
+
rewrite.changed?
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
end
|
|
@@ -28,8 +28,9 @@ module Raindown
|
|
|
28
28
|
|
|
29
29
|
# Writing back saves the file again, and that save changes nothing, so it ends there.
|
|
30
30
|
def update(path)
|
|
31
|
-
|
|
32
|
-
puts "[#{name}] Updated #{relative(path)}" if changed
|
|
31
|
+
rewrite = Rewriter.update(path:, bare: @bare, execute: @execute, metadata: Pages.for(path))
|
|
32
|
+
puts "[#{name}] Updated #{relative(path)}" if rewrite.changed?
|
|
33
|
+
rewrite.failures.each { warn "[#{name}] Endpoint check failed: #{it}" }
|
|
33
34
|
rescue StandardError => e
|
|
34
35
|
raise if ENV['RAINDOWN_DEBUG']
|
|
35
36
|
|
|
@@ -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
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: raindown
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -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,9 +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
|
|
216
233
|
- lib/raindown/endpoint/endpoint_variables.rb
|
|
234
|
+
- lib/raindown/endpoint/schema_block.rb
|
|
217
235
|
- lib/raindown/files.rb
|
|
218
236
|
- lib/raindown/fragments.rb
|
|
219
237
|
- lib/raindown/front_matter.rb
|
|
@@ -221,6 +239,7 @@ files:
|
|
|
221
239
|
- lib/raindown/heading_ids.rb
|
|
222
240
|
- lib/raindown/js/host.mjs
|
|
223
241
|
- lib/raindown/js/js_nodes.rb
|
|
242
|
+
- lib/raindown/markdown_nodes.rb
|
|
224
243
|
- lib/raindown/node.rb
|
|
225
244
|
- lib/raindown/node_blocks.rb
|
|
226
245
|
- lib/raindown/node_injector.rb
|
|
@@ -230,6 +249,7 @@ files:
|
|
|
230
249
|
- lib/raindown/pre_renderer.rb
|
|
231
250
|
- lib/raindown/projects.rb
|
|
232
251
|
- lib/raindown/rewriter.rb
|
|
252
|
+
- lib/raindown/server.rb
|
|
233
253
|
- lib/raindown/stat.rb
|
|
234
254
|
- lib/raindown/table_formatter.rb
|
|
235
255
|
- lib/raindown/toc.rb
|
|
@@ -305,6 +325,7 @@ files:
|
|
|
305
325
|
- lib/raindown/watcher/file_watcher.rb
|
|
306
326
|
- lib/raindown/watcher/project.rb
|
|
307
327
|
- lib/raindown/watcher/supervisor.rb
|
|
328
|
+
- lib/raindown/wiki_links.rb
|
|
308
329
|
- lib/raindown/workers/worker.rb
|
|
309
330
|
- lib/raindown/workers/workers.rb
|
|
310
331
|
- lib/version.rb
|