raindown 0.9.1 → 0.10.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 +1 -1
- data/lib/raindown/cli.rb +11 -4
- data/lib/raindown/endpoint/curl.rb +61 -7
- data/lib/raindown/endpoint/endpoint_runner.rb +23 -15
- data/lib/raindown/endpoint/endpoint_variables.rb +80 -0
- data/lib/raindown/tui/project.rb +2 -1
- data/lib/raindown/variables.rb +7 -3
- data/lib/version.rb +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d8b4a4baae078b6b14f7ae6fd66dde441f97776e7107a07864ca4c12beebb74
|
|
4
|
+
data.tar.gz: 582a5012c02638f89991497cc789ef9513e7c13262445784c40ddb026b246574
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e8fc0825e5462616ce435e35188f5d41b45b23fdbbb59ff840c140cf46279de4a0e696def346a92f168dc6e04e97d1bcac9e309b1db3d9219fe59eeaadad52a
|
|
7
|
+
data.tar.gz: 5025d3381c0e0e2049a6b5fe462102438226feaa2d1a563c89b34dde74b1679163071d4df43e61d3a261e489ed71a107ae925db7f4cf08f1b9e83e773b45b46c
|
data/lib/raindown/actions.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Raindown
|
|
|
32
32
|
raise NotAnEndpoint, "The endpoint on line #{directive.line} only renders, so put it in comments to run it" unless directive.writes
|
|
33
33
|
|
|
34
34
|
content = Utils.read_markdown(path)
|
|
35
|
-
rewritten = EndpointRunner.new(content:, bare: BareDirectives.enabled?(path:, content:), only: directive.line - 1).rewrite
|
|
35
|
+
rewritten = EndpointRunner.new(content:, path:, bare: BareDirectives.enabled?(path:, content:), only: directive.line - 1).rewrite
|
|
36
36
|
return false if rewritten == content
|
|
37
37
|
|
|
38
38
|
File.write(path, rewritten)
|
data/lib/raindown/cli.rb
CHANGED
|
@@ -37,6 +37,7 @@ module Raindown
|
|
|
37
37
|
its extension (to allow bare directives) and can be unsaved. Add --html OUTPUT to write a file
|
|
38
38
|
--bare Allow bare directives like :toc in Markdown files
|
|
39
39
|
-x, --execute Run endpoint curl snippets that have no output yet, as files are written back
|
|
40
|
+
--env NAME Fill endpoint snippets from the NAME environment in .raindown/environments.yml
|
|
40
41
|
-h, --help Show this message
|
|
41
42
|
-v, --version Show the version
|
|
42
43
|
|
|
@@ -52,12 +53,18 @@ module Raindown
|
|
|
52
53
|
|
|
53
54
|
# A path opens the terminal UI, or after "watch" it's watched, and with --html or --stdin it's rendered. The flags
|
|
54
55
|
# are keyword arguments, in any order, like -x for x:, and --html is the OUTPUT when it's given one.
|
|
55
|
-
line(':path') do |path, html: nil, stdin: false, bare: false, execute: false, x: false| # rubocop:disable Metrics/ParameterLists -- each keyword is a flag
|
|
56
|
-
execute
|
|
56
|
+
line(':path') do |path, html: nil, stdin: false, bare: false, execute: false, x: false, env: nil| # rubocop:disable Metrics/ParameterLists -- each keyword is a flag
|
|
57
|
+
execute do
|
|
58
|
+
ENV['RAINDOWN_ENV'] = env if env
|
|
59
|
+
open_path(path, html:, stdin:, bare:, execute: execute || x)
|
|
60
|
+
end
|
|
57
61
|
end
|
|
58
62
|
|
|
59
|
-
line('watch :path') do |path, bare: false, execute: false, x: false|
|
|
60
|
-
execute
|
|
63
|
+
line('watch :path') do |path, bare: false, execute: false, x: false, env: nil|
|
|
64
|
+
execute do
|
|
65
|
+
ENV['RAINDOWN_ENV'] = env if env
|
|
66
|
+
watch(path, bare:, execute: execute || x)
|
|
67
|
+
end
|
|
61
68
|
end
|
|
62
69
|
|
|
63
70
|
def self.run(argv)
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'ipaddr'
|
|
3
4
|
require 'json'
|
|
4
5
|
require 'open3'
|
|
5
6
|
require 'shellwords'
|
|
7
|
+
require 'socket'
|
|
8
|
+
require 'uri'
|
|
6
9
|
|
|
7
10
|
module Raindown
|
|
8
11
|
# Runs a curl snippet with the curl binary. There's no shell, so pipes, redirects and variables don't apply.
|
|
9
12
|
# Snippets come from files that anyone (or any agent) may have written, so only options that make an HTTP request
|
|
10
13
|
# are allowed: nothing that reads or writes local files, loads a config, or uses another protocol.
|
|
14
|
+
# Link-local addresses, like the cloud metadata service at 169.254.169.254, are refused, as they can give out
|
|
15
|
+
# credentials, and redirects aren't followed (no -L), so a URL can't redirect there either.
|
|
11
16
|
module Curl
|
|
12
17
|
class Error < StandardError; end
|
|
13
18
|
Request = Data.define(:method, :url)
|
|
14
19
|
|
|
15
20
|
TIMEOUT = '30'
|
|
21
|
+
# The variables curl reads a proxy from, which then looks the names up rather than curl.
|
|
22
|
+
PROXY_VARIABLES = %w[http_proxy https_proxy HTTPS_PROXY all_proxy ALL_PROXY].freeze
|
|
16
23
|
# --disable must come first and skips ~/.curlrc. The protocol options stop redirects to file:// and the like.
|
|
17
24
|
SAFE_OPTIONS = ['--disable', '--proto', '=http,https', '--proto-redir', '=http,https'].freeze
|
|
18
25
|
|
|
@@ -21,7 +28,7 @@ module Raindown
|
|
|
21
28
|
|
|
22
29
|
# Options without a value.
|
|
23
30
|
FLAGS = {
|
|
24
|
-
'-G' => '--get', '-I' => '--head', '-
|
|
31
|
+
'-G' => '--get', '-I' => '--head', '-i' => '--include', '-k' => '--insecure',
|
|
25
32
|
'-f' => '--fail', '-g' => '--globoff', '-s' => '--silent', '-S' => '--show-error'
|
|
26
33
|
}.freeze
|
|
27
34
|
LONG_FLAGS = (FLAGS.values + %w[--compressed --http1.1 --http2]).freeze
|
|
@@ -52,9 +59,11 @@ module Raindown
|
|
|
52
59
|
module_function
|
|
53
60
|
|
|
54
61
|
# Returns the response body and the code block language for its content type.
|
|
55
|
-
|
|
62
|
+
# Placeholders like {base_url} are replaced from the variables, word by word, so a value can't add options.
|
|
63
|
+
def run(snippet, variables = nil)
|
|
64
|
+
args = arguments(snippet, variables)
|
|
56
65
|
stdout, stderr, status = Open3.capture3('curl', *SAFE_OPTIONS, '--silent', '--show-error', '--max-time', TIMEOUT,
|
|
57
|
-
*
|
|
66
|
+
*resolve_options(args), *args, '--write-out', WRITE_OUT)
|
|
58
67
|
raise Error, stderr.strip.empty? ? "curl exited with #{status.exitstatus}" : stderr.strip unless status.success?
|
|
59
68
|
|
|
60
69
|
body, _, content_type = stdout.rpartition(TYPE_MARKER)
|
|
@@ -66,8 +75,8 @@ module Raindown
|
|
|
66
75
|
end
|
|
67
76
|
|
|
68
77
|
# The snippet's method and URL, like GET https://httpbin.org/uuid, or nil when it can't be read.
|
|
69
|
-
def request(snippet)
|
|
70
|
-
args = arguments(snippet)
|
|
78
|
+
def request(snippet, variables = nil)
|
|
79
|
+
args = arguments(snippet, variables)
|
|
71
80
|
Request.new(method: request_method(args), url: args.find { it.match?(URL) })
|
|
72
81
|
rescue Error
|
|
73
82
|
nil
|
|
@@ -83,8 +92,9 @@ module Raindown
|
|
|
83
92
|
'GET'
|
|
84
93
|
end
|
|
85
94
|
|
|
86
|
-
def arguments(snippet)
|
|
95
|
+
def arguments(snippet, variables = nil)
|
|
87
96
|
words = Shellwords.split(snippet.gsub(/\\\r?\n/, ' ').strip.delete_prefix('$ '))
|
|
97
|
+
words = words.map { variables.substitute(it) } if variables
|
|
88
98
|
raise Error, 'Endpoint snippets must start with curl' unless words.first == 'curl'
|
|
89
99
|
raise Error, 'Endpoint snippets must be a single curl command' if words.intersect?(OPERATORS)
|
|
90
100
|
|
|
@@ -120,7 +130,7 @@ module Raindown
|
|
|
120
130
|
[option, value(option, words.shift)]
|
|
121
131
|
end
|
|
122
132
|
|
|
123
|
-
# Short flags can be grouped like -
|
|
133
|
+
# Short flags can be grouped like -sS, and a short option's value can be attached like -XPOST.
|
|
124
134
|
def short_options(word, words)
|
|
125
135
|
letters = word.delete_prefix('-')
|
|
126
136
|
args = []
|
|
@@ -160,6 +170,50 @@ module Raindown
|
|
|
160
170
|
value
|
|
161
171
|
end
|
|
162
172
|
|
|
173
|
+
# Resolves each URL's host and pins curl to the address it was checked at, like --resolve api.io:443:203.0.113.5,
|
|
174
|
+
# so the name can't point somewhere else by the time curl looks it up. A host that's already an IP isn't pinned,
|
|
175
|
+
# and behind a proxy, which looks names up itself, only a host that's an IP is checked.
|
|
176
|
+
def resolve_options(args)
|
|
177
|
+
args.grep(URL).flat_map { pin_option(URI.parse(it)) }
|
|
178
|
+
rescue URI::InvalidURIError => e
|
|
179
|
+
raise Error, "Can't read the URL: #{e.message}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def pin_option(uri)
|
|
183
|
+
raise Error, "#{uri} has no host" if uri.hostname.to_s.empty?
|
|
184
|
+
return [] if proxy? && !ip?(uri.hostname)
|
|
185
|
+
|
|
186
|
+
address = allowed_address(uri.hostname)
|
|
187
|
+
return [] if ip?(uri.hostname)
|
|
188
|
+
|
|
189
|
+
pinned = address.to_s
|
|
190
|
+
pinned = "[#{pinned}]" if address.ipv6?
|
|
191
|
+
['--resolve', "#{uri.hostname}:#{uri.port}:#{pinned}"]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Any way of writing an IP, like 169.254.169.254 or 2852039166, found without a DNS lookup.
|
|
195
|
+
def ip?(host)
|
|
196
|
+
Addrinfo.getaddrinfo(host, nil, nil, :STREAM, nil, Socket::AI_NUMERICHOST)
|
|
197
|
+
true
|
|
198
|
+
rescue SocketError
|
|
199
|
+
false
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def proxy?
|
|
203
|
+
PROXY_VARIABLES.any? { !ENV[it].to_s.empty? }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# The host's first address, unless any of them is link-local.
|
|
207
|
+
def allowed_address(host)
|
|
208
|
+
addresses = Addrinfo.getaddrinfo(host, nil, nil, :STREAM).map { IPAddr.new(it.ip_address.sub(/%.*/, '')).native }
|
|
209
|
+
blocked = addresses.find(&:link_local?)
|
|
210
|
+
raise Error, "Endpoint snippets can't request link-local addresses, like cloud metadata at #{blocked}" if blocked
|
|
211
|
+
|
|
212
|
+
addresses.first
|
|
213
|
+
rescue SocketError => e
|
|
214
|
+
raise Error, "Can't resolve #{host}: #{e.message}"
|
|
215
|
+
end
|
|
216
|
+
|
|
163
217
|
def language(content_type)
|
|
164
218
|
LANGUAGES.find { |pattern, _| content_type.match?(pattern) }&.last || ''
|
|
165
219
|
end
|
|
@@ -4,14 +4,15 @@ require 'date'
|
|
|
4
4
|
require_relative '../bare_directives'
|
|
5
5
|
require_relative '../code_blocks'
|
|
6
6
|
require_relative 'curl'
|
|
7
|
+
require_relative 'endpoint_variables'
|
|
7
8
|
|
|
8
9
|
module Raindown
|
|
9
10
|
# Runs the curl block under an endpoint directive in a comment, or a bare one, and writes the response in the code
|
|
10
11
|
# block below it,
|
|
11
12
|
# followed by the date it was fetched.
|
|
12
|
-
# Only runs when that block is missing or empty, and
|
|
13
|
+
# Only runs when that block is missing or empty, and --execute is passed, so a file can't run requests by itself.
|
|
13
14
|
# Or with only: and a directive's line index, runs just that endpoint, replacing any response it has.
|
|
14
|
-
# <!-- endpoint:
|
|
15
|
+
# <!-- endpoint: -->
|
|
15
16
|
# ```sh
|
|
16
17
|
# curl https://api.example.com
|
|
17
18
|
# ```
|
|
@@ -20,6 +21,8 @@ module Raindown
|
|
|
20
21
|
# ```
|
|
21
22
|
# <!-- raindown:fetched 2026-10-14 -->
|
|
22
23
|
# <!-- :endpoint -->
|
|
24
|
+
# Its snippet's {placeholders} are filled from EndpointVariables, and with as: login, later snippets can use its
|
|
25
|
+
# response, like {login.token}.
|
|
23
26
|
# One in Antlers' delimiters, <{ endpoint: }>, only renders, so it's never run.
|
|
24
27
|
class EndpointRunner
|
|
25
28
|
# An endpoint's directive in Antlers' delimiters, which only renders, or in a comment, which writes its response back,
|
|
@@ -27,16 +30,16 @@ module Raindown
|
|
|
27
30
|
DIRECTIVE = /\A {0,3}<\{\s*endpoint:(.*?)\}>[ \t]*$/
|
|
28
31
|
COMMENT = /\A {0,3}<!--\s*endpoint:(.*?)-->[ \t]*$/
|
|
29
32
|
END_TAG = /\A {0,3}(?:<\{\s*:endpoint\s*\}>|<!--\s*:endpoint\s*-->)[ \t]*$/
|
|
30
|
-
EXECUTE_ARGS = %w[:x :execute].freeze
|
|
31
33
|
# The date a response was fetched, on the line after it.
|
|
32
34
|
FETCHED = /\A {0,3}<!-- raindown:fetched [^\n]*? -->[ \t]*$/
|
|
33
35
|
|
|
34
36
|
def self.rewrite(content:, path: nil, bare: false, execute: false, **)
|
|
35
|
-
new(content:, bare: BareDirectives.enabled?(path:, content:, bare:), execute:).rewrite
|
|
37
|
+
new(content:, path:, bare: BareDirectives.enabled?(path:, content:, bare:), execute:).rewrite
|
|
36
38
|
end
|
|
37
39
|
|
|
38
|
-
def initialize(content:, bare: false, execute: false, only: nil)
|
|
40
|
+
def initialize(content:, path: nil, bare: false, execute: false, only: nil)
|
|
39
41
|
@lines = content.lines
|
|
42
|
+
@variables = EndpointVariables.new(path:, content:)
|
|
40
43
|
@bare = bare
|
|
41
44
|
@execute = execute
|
|
42
45
|
@only = only
|
|
@@ -71,24 +74,29 @@ module Raindown
|
|
|
71
74
|
def fill(index, args, directive:)
|
|
72
75
|
curl, output = CodeBlocks.endpoint(@lines, index)
|
|
73
76
|
return index - 1 unless curl&.dig(:body)&.lstrip&.start_with?('curl')
|
|
74
|
-
return curl
|
|
77
|
+
return write_output(curl:, output:, name: response_name(args)) if run?(output, directive)
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
@variables.remember(response_name(args), output[:body]) if output
|
|
80
|
+
curl[:close]
|
|
77
81
|
end
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
# The name that as: gives the endpoint, for later snippets to use its response.
|
|
84
|
+
def response_name(args)
|
|
85
|
+
index = args.index('as:')
|
|
86
|
+
args[index + 1] if index
|
|
83
87
|
end
|
|
84
88
|
|
|
85
|
-
def
|
|
86
|
-
@
|
|
89
|
+
def run?(output, directive)
|
|
90
|
+
return directive == @only if @only
|
|
91
|
+
|
|
92
|
+
@execute && (output.nil? || output[:body].strip.empty?)
|
|
87
93
|
end
|
|
88
94
|
|
|
89
95
|
# Replaces the output block and the date it was fetched, or with no block, replaces nothing just after the curl block.
|
|
90
|
-
def write_output(curl:, output:)
|
|
91
|
-
|
|
96
|
+
def write_output(curl:, output:, name:)
|
|
97
|
+
body, language = Curl.run(curl[:body], @variables)
|
|
98
|
+
@variables.remember(name, body)
|
|
99
|
+
block = [*output_block(body, language), fetched]
|
|
92
100
|
block.unshift("\n") unless output
|
|
93
101
|
start = output ? output[:open] : curl[:close] + 1
|
|
94
102
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require_relative '../front_matter'
|
|
6
|
+
require_relative 'curl'
|
|
7
|
+
|
|
8
|
+
module Raindown
|
|
9
|
+
# The values that {placeholders} in an endpoint's curl snippet are replaced with, like {base_url} or {login.token}.
|
|
10
|
+
# From least to most specific: the file's front matter, the project's environment, and the responses of endpoints
|
|
11
|
+
# named with as:, read from the file as it goes. {env.NAME} is the environment variable NAME, for secrets, and an
|
|
12
|
+
# environment's values can use it too, like token: '{env.STAGING_TOKEN}'.
|
|
13
|
+
# The environment is one in .raindown/environments.yml, chosen with --env or RAINDOWN_ENV, or else the first:
|
|
14
|
+
# local:
|
|
15
|
+
# base_url: http://localhost:3000
|
|
16
|
+
# staging:
|
|
17
|
+
# base_url: https://staging.example.com
|
|
18
|
+
class EndpointVariables
|
|
19
|
+
class Error < Curl::Error; end
|
|
20
|
+
|
|
21
|
+
FILE = 'environments.yml'
|
|
22
|
+
PLACEHOLDER = /\{([A-Za-z_][\w.-]*)\}/
|
|
23
|
+
|
|
24
|
+
def initialize(path:, content:)
|
|
25
|
+
@path = path
|
|
26
|
+
@content = content
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Keeps a named endpoint's response, as its JSON's data when it's JSON, or else its text.
|
|
30
|
+
def remember(name, body)
|
|
31
|
+
return unless name
|
|
32
|
+
|
|
33
|
+
values[name] = begin
|
|
34
|
+
JSON.parse(body)
|
|
35
|
+
rescue JSON::ParserError
|
|
36
|
+
body.strip
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The text with each placeholder that has a value replaced by it, and the rest left as they are. A value's own
|
|
41
|
+
# placeholders are replaced once, so an environment can read secrets like {env.TOKEN}.
|
|
42
|
+
def substitute(text, depth: 1)
|
|
43
|
+
text.gsub(PLACEHOLDER) do |placeholder|
|
|
44
|
+
value = value(Regexp.last_match(1))
|
|
45
|
+
next placeholder if value.nil?
|
|
46
|
+
next value.to_json if value.is_a?(Hash) || value.is_a?(Array)
|
|
47
|
+
next substitute(value, depth: 0) if depth.positive? && value.is_a?(String)
|
|
48
|
+
|
|
49
|
+
value.to_s
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Read when they're first needed, so a file without endpoints doesn't need its environment.
|
|
56
|
+
def values
|
|
57
|
+
@values ||= FrontMatter.parse(@content).first.transform_keys(&:to_s).merge(environment, 'env' => ENV.to_h)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def environment
|
|
61
|
+
folder = @path && Nodes.find_folder(File.dirname(File.expand_path(@path)))
|
|
62
|
+
file = folder && File.join(folder, FILE)
|
|
63
|
+
return {} unless file && File.exist?(file)
|
|
64
|
+
|
|
65
|
+
environments = YAML.safe_load_file(file) || {}
|
|
66
|
+
name = ENV.fetch('RAINDOWN_ENV') { environments.keys.first }
|
|
67
|
+
environments.fetch(name) { raise Error, "There's no #{name} environment in #{file}" }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# A value by its path, like login.token or users.0.id.
|
|
71
|
+
def value(name)
|
|
72
|
+
name.split('.').reduce(values) do |value, key|
|
|
73
|
+
case value
|
|
74
|
+
when Hash then value.fetch(key) { value[key.to_sym] }
|
|
75
|
+
when Array then value[Integer(key, exception: false) || value.length]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/raindown/tui/project.rb
CHANGED
|
@@ -15,6 +15,7 @@ require_relative 'card'
|
|
|
15
15
|
require_relative 'endpoint'
|
|
16
16
|
require_relative '../code_blocks'
|
|
17
17
|
require_relative '../endpoint/curl'
|
|
18
|
+
require_relative '../endpoint/endpoint_variables'
|
|
18
19
|
|
|
19
20
|
module Raindown
|
|
20
21
|
module TUI
|
|
@@ -89,7 +90,7 @@ module Raindown
|
|
|
89
90
|
|
|
90
91
|
def endpoint(path, lines, directive)
|
|
91
92
|
snippet = CodeBlocks.endpoint(lines, directive.line).first&.dig(:body)
|
|
92
|
-
request = Curl.request(snippet.to_s)
|
|
93
|
+
request = Curl.request(snippet.to_s, EndpointVariables.new(path:, content: lines.join))
|
|
93
94
|
Endpoint.new(project: root, path:, line: directive.line, **request.to_h) if request
|
|
94
95
|
end
|
|
95
96
|
|
data/lib/raindown/variables.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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)
|
data/lib/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-11-
|
|
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.
|
|
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.
|
|
25
|
+
version: 1.4.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: commonmarker
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -213,6 +213,7 @@ files:
|
|
|
213
213
|
- lib/raindown/document.rb
|
|
214
214
|
- lib/raindown/endpoint/curl.rb
|
|
215
215
|
- lib/raindown/endpoint/endpoint_runner.rb
|
|
216
|
+
- lib/raindown/endpoint/endpoint_variables.rb
|
|
216
217
|
- lib/raindown/files.rb
|
|
217
218
|
- lib/raindown/fragments.rb
|
|
218
219
|
- lib/raindown/front_matter.rb
|
|
@@ -307,11 +308,11 @@ files:
|
|
|
307
308
|
- lib/raindown/workers/worker.rb
|
|
308
309
|
- lib/raindown/workers/workers.rb
|
|
309
310
|
- lib/version.rb
|
|
310
|
-
homepage: https://github.com/
|
|
311
|
+
homepage: https://github.com/rainpour/raindown
|
|
311
312
|
licenses: []
|
|
312
313
|
metadata:
|
|
313
|
-
homepage_uri: https://github.com/
|
|
314
|
-
source_code_uri: https://github.com/
|
|
314
|
+
homepage_uri: https://github.com/rainpour/raindown
|
|
315
|
+
source_code_uri: https://github.com/rainpour/raindown/tree/main
|
|
315
316
|
rdoc_options: []
|
|
316
317
|
require_paths:
|
|
317
318
|
- lib
|