rakit 0.1.11 → 0.1.12
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/exe/rakit +51 -1
- data/lib/generated/data_pb.rb +1 -2
- data/lib/rakit/markdown.rb +317 -0
- metadata +1 -3
- data/lib/generated/shell_pb.rb +0 -22
- data/lib/generated/static_web_server_pb.rb +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5e60dd0463ec8a3e0c80259b617fce0badd83b6fb964fce65d31644436afb34
|
|
4
|
+
data.tar.gz: 760a389c98a574a529483d365fa83dbd3c520f814e7789d4d3c6399ec89f862b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a3dce2c248c972ff5fd20a2f5d80384f093ecb40d7eaaeb3dc996150c879c6c318f95b760510650d3693060fcb39b756cb5e142e859d2b44a0aced579456ba2
|
|
7
|
+
data.tar.gz: 1832edc6a06d03c95e862301985c718d5bad7bfe295caf87c50eb612ee116295370f706927ede3f04ed4dc6d462dad112c9b9a3297c54865a604dc90cbacddb3
|
data/exe/rakit
CHANGED
|
@@ -19,6 +19,7 @@ def usage_stderr(msg = nil)
|
|
|
19
19
|
$stderr.puts " markdown amalgamate <root_dir> --out <output_path>"
|
|
20
20
|
$stderr.puts " markdown disaggregate <markdown_path> --out <output_root_dir>"
|
|
21
21
|
$stderr.puts " markdown validate <root_dir>"
|
|
22
|
+
$stderr.puts " markdown toc|tree|outline [--root <dir>|--document-json <path>|--outline-json <path>] [--max-depth N] [options]"
|
|
22
23
|
$stderr.puts " static-web-server <start|stop|running|publish|view> [options] [args]"
|
|
23
24
|
$stderr.puts " word-cloud <status|install|generate> [options] [args]"
|
|
24
25
|
$stderr.puts " start [--port PORT] Start server (idempotent)"
|
|
@@ -120,8 +121,17 @@ def main(argv = ARGV)
|
|
|
120
121
|
return 1
|
|
121
122
|
end
|
|
122
123
|
return 0
|
|
124
|
+
when "toc"
|
|
125
|
+
code = run_markdown_renderer(argv, :toc)
|
|
126
|
+
return code
|
|
127
|
+
when "tree"
|
|
128
|
+
code = run_markdown_renderer(argv, :tree)
|
|
129
|
+
return code
|
|
130
|
+
when "outline"
|
|
131
|
+
code = run_markdown_renderer(argv, :outline)
|
|
132
|
+
return code
|
|
123
133
|
else
|
|
124
|
-
usage_stderr(cmd ? "markdown: use amalgamate, disaggregate, validate, merge, or
|
|
134
|
+
usage_stderr(cmd ? "markdown: use amalgamate, disaggregate, validate, merge, pdf, toc, tree, or outline" : "markdown requires a command")
|
|
125
135
|
return 1
|
|
126
136
|
end
|
|
127
137
|
rescue ArgumentError, Errno::ENOENT, Errno::EACCES => e
|
|
@@ -190,4 +200,44 @@ rescue => e
|
|
|
190
200
|
return 1
|
|
191
201
|
end
|
|
192
202
|
|
|
203
|
+
def run_markdown_renderer(argv, command)
|
|
204
|
+
opts = Rakit::Markdown.parse_renderer_args(argv)
|
|
205
|
+
type, path = Rakit::Markdown.resolve_renderer_input(opts)
|
|
206
|
+
unless type
|
|
207
|
+
usage_stderr("markdown #{command} requires one of: --root <dir>, --document-json <path>, --outline-json <path>")
|
|
208
|
+
return Rakit::Markdown::EXIT_INVALID_INPUT
|
|
209
|
+
end
|
|
210
|
+
outline, code = Rakit::Markdown.load_outline_from_options(opts)
|
|
211
|
+
if code
|
|
212
|
+
return code
|
|
213
|
+
end
|
|
214
|
+
max_depth = opts[:max_depth]
|
|
215
|
+
case command
|
|
216
|
+
when :toc
|
|
217
|
+
no_links = argv.include?("--no-links")
|
|
218
|
+
href_mode = :file_anchor
|
|
219
|
+
href_mode = :anchor if argv.include?("--href-mode") && argv[argv.index("--href-mode") + 1].to_s == "anchor"
|
|
220
|
+
href_mode = :none if argv.include?("--href-mode") && argv[argv.index("--href-mode") + 1].to_s == "none"
|
|
221
|
+
site_base = (argv.include?("--site-base") && argv[argv.index("--site-base") + 1]) ? argv[argv.index("--site-base") + 1] : nil
|
|
222
|
+
show_ids = argv.include?("--show-ids")
|
|
223
|
+
out = Rakit::Markdown.render_toc(outline, max_depth: max_depth, no_links: no_links, href_mode: href_mode, site_base: site_base, show_ids: show_ids)
|
|
224
|
+
$stdout.write(out)
|
|
225
|
+
when :tree
|
|
226
|
+
show_ids = argv.include?("--show-ids")
|
|
227
|
+
show_hrefs = argv.include?("--show-hrefs")
|
|
228
|
+
ascii = argv.include?("--ascii")
|
|
229
|
+
out = Rakit::Markdown.render_tree(outline, max_depth: max_depth, show_ids: show_ids, show_hrefs: show_hrefs, ascii: ascii)
|
|
230
|
+
$stdout.write(out)
|
|
231
|
+
when :outline
|
|
232
|
+
show_ids = argv.include?("--show-ids")
|
|
233
|
+
no_numbers = argv.include?("--no-numbers")
|
|
234
|
+
out = Rakit::Markdown.render_outline(outline, max_depth: max_depth, show_ids: show_ids, no_numbers: no_numbers)
|
|
235
|
+
$stdout.write(out)
|
|
236
|
+
end
|
|
237
|
+
Rakit::Markdown::EXIT_SUCCESS
|
|
238
|
+
rescue => e
|
|
239
|
+
$stderr.puts e.message
|
|
240
|
+
Rakit::Markdown::EXIT_INTERNAL
|
|
241
|
+
end
|
|
242
|
+
|
|
193
243
|
exit main(ARGV.dup)
|
data/lib/generated/data_pb.rb
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# source: data.proto
|
|
4
4
|
|
|
5
|
-
require
|
|
6
|
-
|
|
5
|
+
require "google/protobuf"
|
|
7
6
|
|
|
8
7
|
descriptor_data = "\n\ndata.proto\x12\nrakit.data\"h\n\x05Index\x12/\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x1e.rakit.data.Index.EntriesEntry\x1a.\n\x0c\x45ntriesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01*^\n\x0c\x45xportFormat\x12\x19\n\x15PROTOBUF_BINARY_FILES\x10\x00\x12\x17\n\x13PROTOBUF_JSON_FILES\x10\x01\x12\x1a\n\x16PROTOBUF_BINARY_ZIPPED\x10\x02\x42\x0e\xea\x02\x0bRakit::Datab\x06proto3"
|
|
9
8
|
|
data/lib/rakit/markdown.rb
CHANGED
|
@@ -7,9 +7,30 @@ require "kramdown"
|
|
|
7
7
|
|
|
8
8
|
module Rakit
|
|
9
9
|
# Modular document amalgamate, disaggregate, validate (009); merge (legacy); generate_pdf.
|
|
10
|
+
# Outline/toc/tree/outline renderers (010): see specs/010-markdown-outline.
|
|
10
11
|
# Document and Section are defined in proto/rakit.markdown.proto (generated in lib/generated/rakit.markdown_pb.rb).
|
|
11
12
|
# See specs/009-markdown-modular-docs/contracts/ruby-api.md
|
|
12
13
|
module Markdown
|
|
14
|
+
# Exit codes for renderer CLI (toc, tree, outline). 0=success, 2=invalid input, 3=validation, 4=internal.
|
|
15
|
+
EXIT_SUCCESS = 0
|
|
16
|
+
EXIT_INVALID_INPUT = 2
|
|
17
|
+
EXIT_VALIDATION = 3
|
|
18
|
+
EXIT_INTERNAL = 4
|
|
19
|
+
|
|
20
|
+
# In-memory outline for rendering (010). title: optional root title; nodes: top-level OutlineNode list.
|
|
21
|
+
Outline = Struct.new(:title, :nodes, keyword_init: true) do
|
|
22
|
+
def initialize(title: nil, nodes: nil)
|
|
23
|
+
super(title: title.to_s.strip, nodes: nodes || [])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Single node in the outline tree. id/href may be set by normalization.
|
|
28
|
+
OutlineNode = Struct.new(:title, :id, :href, :source_path, :children, keyword_init: true) do
|
|
29
|
+
def initialize(title: "", id: nil, href: nil, source_path: nil, children: nil)
|
|
30
|
+
super(title: title.to_s, id: id, href: href, source_path: source_path, children: children || [])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
13
34
|
# Derive slug from section title: lowercase, non-alphanumerics → '-', collapse '-', trim.
|
|
14
35
|
def self.slug_from_title(title)
|
|
15
36
|
return "" if title.nil? || title.to_s.strip.empty?
|
|
@@ -148,6 +169,302 @@ module Rakit
|
|
|
148
169
|
false
|
|
149
170
|
end
|
|
150
171
|
|
|
172
|
+
# ---- Outline (010): derivation, normalization, validation ----
|
|
173
|
+
|
|
174
|
+
# Deterministic slug for outline node id: trim, lower-case, NFKD (when available), strip diacritics, non-[a-z0-9]→-, trim -, empty→"section".
|
|
175
|
+
def self.slug_for_outline(title)
|
|
176
|
+
return "section" if title.nil? || title.to_s.strip.empty?
|
|
177
|
+
s = title.to_s.strip.downcase
|
|
178
|
+
s = s.unicode_normalize(:nfkd).gsub(/\p{M}+/, "") if s.respond_to?(:unicode_normalize)
|
|
179
|
+
s = s.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
|
|
180
|
+
s.empty? ? "section" : s
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Build Outline from Document (map Section → OutlineNode recursively). Does not normalize.
|
|
184
|
+
def self.document_to_outline(doc)
|
|
185
|
+
return Outline.new(title: doc.title, nodes: []) if doc.nil?
|
|
186
|
+
nodes = (doc.sections || []).map { |sec| section_to_outline_node(sec) }
|
|
187
|
+
Outline.new(title: doc.title.to_s.strip, nodes: nodes)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def self.section_to_outline_node(sec)
|
|
191
|
+
children = (sec.sections || []).map { |s| section_to_outline_node(s) }
|
|
192
|
+
OutlineNode.new(
|
|
193
|
+
title: sec.title.to_s,
|
|
194
|
+
id: sec.id.to_s.strip.empty? ? nil : sec.id.to_s,
|
|
195
|
+
source_path: sec.source_path.to_s.strip.empty? ? nil : sec.source_path.to_s,
|
|
196
|
+
children: children
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Apply slug (when id blank), sibling dedupe, and href. Modifies outline in place. dedupe: true = suffix -2,-3 and warn instead of validation error.
|
|
201
|
+
def self.normalize_outline!(outline, href_mode: :file_anchor, site_base: nil, dedupe: false)
|
|
202
|
+
outline.nodes.each { |n| normalize_node!(n, href_mode: href_mode, site_base: site_base, dedupe: dedupe) }
|
|
203
|
+
dedupe_sibling_ids!(outline.nodes, dedupe: dedupe)
|
|
204
|
+
outline.nodes.each { |n| normalize_node_href!(n, href_mode: href_mode, site_base: site_base) }
|
|
205
|
+
outline
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def self.normalize_node!(node, href_mode: :file_anchor, site_base: nil, dedupe: false)
|
|
209
|
+
node.children.each { |c| normalize_node!(c, href_mode: href_mode, site_base: site_base, dedupe: dedupe) }
|
|
210
|
+
node.id = node.id.to_s.strip if node.id
|
|
211
|
+
node.id = nil if node.id.to_s.empty?
|
|
212
|
+
node.id = slug_for_outline(node.title) if node.id.to_s.empty?
|
|
213
|
+
dedupe_sibling_ids!(node.children, dedupe: dedupe)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def self.normalize_node_href!(node, href_mode: :file_anchor, site_base: nil)
|
|
217
|
+
node.href = compute_href(node, href_mode: href_mode, site_base: site_base) if node.href.to_s.strip.empty?
|
|
218
|
+
node.children.each { |c| normalize_node_href!(c, href_mode: href_mode, site_base: site_base) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def self.dedupe_sibling_ids!(siblings, dedupe: false)
|
|
222
|
+
seen = {}
|
|
223
|
+
siblings.each do |n|
|
|
224
|
+
id = n.id.to_s
|
|
225
|
+
if seen.key?(id)
|
|
226
|
+
if dedupe
|
|
227
|
+
idx = 2
|
|
228
|
+
idx += 1 while seen.key?("#{id}-#{idx}")
|
|
229
|
+
n.id = "#{id}-#{idx}"
|
|
230
|
+
seen[n.id] = true
|
|
231
|
+
end
|
|
232
|
+
else
|
|
233
|
+
seen[id] = true
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def self.compute_href(node, href_mode: :file_anchor, site_base: nil)
|
|
239
|
+
id = node.id.to_s
|
|
240
|
+
case href_mode
|
|
241
|
+
when :anchor then "##{id}"
|
|
242
|
+
when :none then ""
|
|
243
|
+
else
|
|
244
|
+
path = node.source_path.to_s.strip
|
|
245
|
+
if site_base.to_s.strip.empty?
|
|
246
|
+
path.empty? ? "##{id}" : "#{path}##{id}"
|
|
247
|
+
else
|
|
248
|
+
base = site_base.to_s.chomp("/")
|
|
249
|
+
path_no_ext = path.sub(/\.md\z/i, "").tr("\\", "/")
|
|
250
|
+
path_no_ext.empty? ? "##{id}" : "#{base}/#{path_no_ext}/##{id}"
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Validate outline: empty title, duplicate explicit id among siblings, max_depth≤0. Returns [errors, warnings]. Zero nodes is valid.
|
|
256
|
+
def self.validate_outline(outline, max_depth: nil, dedupe: false, strict_paths: false)
|
|
257
|
+
errors = []
|
|
258
|
+
warnings = []
|
|
259
|
+
if max_depth.is_a?(Integer) && max_depth <= 0
|
|
260
|
+
errors << "max_depth must be >= 1"
|
|
261
|
+
end
|
|
262
|
+
validate_outline_nodes!(outline.nodes, 1, errors, warnings, dedupe: dedupe, strict_paths: strict_paths)
|
|
263
|
+
[errors, warnings]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def self.validate_outline_nodes!(nodes, depth, errors, warnings, dedupe: false, strict_paths: false)
|
|
267
|
+
nodes.each do |n|
|
|
268
|
+
if n.title.to_s.strip.empty?
|
|
269
|
+
errors << "Node has empty title"
|
|
270
|
+
end
|
|
271
|
+
if strict_paths && n.source_path.to_s.strip != "" && (n.source_path.include?("..") || ::File.absolute_path?(n.source_path))
|
|
272
|
+
errors << "source_path must be relative: #{n.source_path}"
|
|
273
|
+
end
|
|
274
|
+
validate_outline_nodes!(n.children, depth + 1, errors, warnings, dedupe: dedupe, strict_paths: strict_paths)
|
|
275
|
+
end
|
|
276
|
+
return if dedupe
|
|
277
|
+
ids = nodes.map { |n| n.id.to_s }.reject(&:empty?)
|
|
278
|
+
seen = {}
|
|
279
|
+
ids.each do |id|
|
|
280
|
+
if seen[id]
|
|
281
|
+
errors << "Duplicate id among siblings: #{id}"
|
|
282
|
+
else
|
|
283
|
+
seen[id] = true
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Load outline from root dir. If no .md files, returns Outline with empty nodes (valid for renderers).
|
|
289
|
+
def self.outline_from_root(root_dir)
|
|
290
|
+
path = ::File.expand_path(root_dir.to_s)
|
|
291
|
+
raise ArgumentError, "root_dir does not exist: #{root_dir}" unless ::File.exist?(path)
|
|
292
|
+
raise ArgumentError, "root_dir is not a directory: #{root_dir}" unless ::File.directory?(path)
|
|
293
|
+
paths = ::Dir.glob(::File.join(path, "**", "*.md")).reject { |p| ::File.basename(p).start_with?(".") }.sort
|
|
294
|
+
title = ::File.basename(path)
|
|
295
|
+
if paths.empty?
|
|
296
|
+
return Outline.new(title: title, nodes: [])
|
|
297
|
+
end
|
|
298
|
+
doc = load_document(root_dir: root_dir)
|
|
299
|
+
document_to_outline(doc)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Parse argv for renderer commands; returns { root:, document_json:, outline_json:, max_depth:, dedupe:, strict_paths:, ... }. Precedence for input: outline_json > document_json > root.
|
|
303
|
+
def self.parse_renderer_args(argv)
|
|
304
|
+
args = argv.dup
|
|
305
|
+
opts = { root: nil, document_json: nil, outline_json: nil, max_depth: nil, dedupe: false, strict_paths: false }
|
|
306
|
+
while (arg = args.shift)
|
|
307
|
+
case arg
|
|
308
|
+
when "--root" then opts[:root] = args.shift
|
|
309
|
+
when "--document-json" then opts[:document_json] = args.shift
|
|
310
|
+
when "--outline-json" then opts[:outline_json] = args.shift
|
|
311
|
+
when "--max-depth" then opts[:max_depth] = args.shift
|
|
312
|
+
when "--dedupe" then opts[:dedupe] = true
|
|
313
|
+
when "--strict-paths" then opts[:strict_paths] = true
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
opts[:max_depth] = opts[:max_depth].to_s =~ /\A\d+\z/ ? opts[:max_depth].to_i : nil
|
|
317
|
+
opts
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Resolve which input source from parsed opts (precedence: outline_json > document_json > root). Returns :outline_json, :document_json, or :root and the path value, or [nil, nil] if none.
|
|
321
|
+
def self.resolve_renderer_input(opts)
|
|
322
|
+
return [:outline_json, opts[:outline_json]] if opts[:outline_json].to_s.strip != ""
|
|
323
|
+
return [:document_json, opts[:document_json]] if opts[:document_json].to_s.strip != ""
|
|
324
|
+
return [:root, opts[:root]] if opts[:root].to_s.strip != ""
|
|
325
|
+
[nil, nil]
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Load and normalize outline from options. Returns [outline, nil] or [nil, exit_code]. Errors written to stderr via yield or callers responsibility.
|
|
329
|
+
def self.load_outline_from_options(opts, href_mode: :file_anchor, site_base: nil)
|
|
330
|
+
input_type, path = resolve_renderer_input(opts)
|
|
331
|
+
if input_type.nil?
|
|
332
|
+
return [nil, EXIT_INVALID_INPUT]
|
|
333
|
+
end
|
|
334
|
+
path = ::File.expand_path(path.to_s.strip)
|
|
335
|
+
outline = case input_type
|
|
336
|
+
when :root
|
|
337
|
+
outline_from_root(path)
|
|
338
|
+
when :document_json
|
|
339
|
+
json = ::File.read(path, encoding: "UTF-8")
|
|
340
|
+
doc = decode_document_json(json)
|
|
341
|
+
document_to_outline(doc)
|
|
342
|
+
when :outline_json
|
|
343
|
+
json = ::File.read(path, encoding: "UTF-8")
|
|
344
|
+
decode_outline_json(json)
|
|
345
|
+
end
|
|
346
|
+
normalize_outline!(outline, href_mode: href_mode, site_base: site_base, dedupe: opts[:dedupe])
|
|
347
|
+
errs, _warn = validate_outline(outline, max_depth: opts[:max_depth], dedupe: opts[:dedupe], strict_paths: opts[:strict_paths])
|
|
348
|
+
return [nil, EXIT_VALIDATION] if errs.any?
|
|
349
|
+
[outline, nil]
|
|
350
|
+
rescue ArgumentError, Errno::ENOENT, Errno::EACCES => e
|
|
351
|
+
$stderr.puts e.message if $stderr
|
|
352
|
+
[nil, EXIT_INVALID_INPUT]
|
|
353
|
+
rescue ::JSON::ParserError => e
|
|
354
|
+
$stderr.puts e.message if $stderr
|
|
355
|
+
[nil, EXIT_INVALID_INPUT]
|
|
356
|
+
rescue => e
|
|
357
|
+
$stderr.puts e.message if $stderr
|
|
358
|
+
[nil, EXIT_INTERNAL]
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Decode Document from JSON (protobuf JSON). Minimal implementation: parse and build Document from hash.
|
|
362
|
+
def self.decode_document_json(json)
|
|
363
|
+
require "json"
|
|
364
|
+
h = ::JSON.parse(json)
|
|
365
|
+
title = h["title"] || h["rootDir"] && ::File.basename(h["rootDir"]) || ""
|
|
366
|
+
root_dir = h["rootDir"].to_s
|
|
367
|
+
sections = (h["sections"] || []).map { |s| decode_section_hash(s) }
|
|
368
|
+
Document.new(title: title, root_dir: root_dir, sections: sections)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def self.decode_section_hash(h)
|
|
372
|
+
id = (h["id"] || "").to_s
|
|
373
|
+
title = (h["title"] || "").to_s
|
|
374
|
+
source_path = (h["sourcePath"] || h["source_path"] || "").to_s
|
|
375
|
+
sections = (h["sections"] || []).map { |s| decode_section_hash(s) }
|
|
376
|
+
Section.new(id: id, title: title, body: (h["body"] || "").to_s, source_path: source_path, sections: sections)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Render outline as Markdown TOC (nested bullets, optional links). max_depth: nil = no limit; depth 1 = top-level.
|
|
380
|
+
def self.render_toc(outline, max_depth: nil, no_links: false, href_mode: :file_anchor, site_base: nil, show_ids: false)
|
|
381
|
+
buf = +""
|
|
382
|
+
render_toc_nodes(buf, outline.nodes, 1, max_depth: max_depth, no_links: no_links, show_ids: show_ids, indent: "")
|
|
383
|
+
buf
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def self.render_toc_nodes(buf, nodes, depth, max_depth: nil, no_links: false, show_ids: false, indent: "")
|
|
387
|
+
return if max_depth && depth > max_depth
|
|
388
|
+
nodes.each do |n|
|
|
389
|
+
line = indent + "- "
|
|
390
|
+
if !no_links && n.href.to_s.strip != ""
|
|
391
|
+
line << "[#{n.title}](#{n.href})"
|
|
392
|
+
else
|
|
393
|
+
line << n.title.to_s
|
|
394
|
+
end
|
|
395
|
+
line << " (id: #{n.id})" if show_ids && n.id.to_s != ""
|
|
396
|
+
line << "\n"
|
|
397
|
+
buf << line
|
|
398
|
+
render_toc_nodes(buf, n.children, depth + 1, max_depth: max_depth, no_links: no_links, show_ids: show_ids, indent: indent + " ")
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Render outline as tree (box-drawing or ASCII). Root title printed above when non-empty.
|
|
403
|
+
def self.render_tree(outline, max_depth: nil, show_ids: false, show_hrefs: false, ascii: false)
|
|
404
|
+
buf = +""
|
|
405
|
+
buf << outline.title.to_s << "\n" if outline.title.to_s.strip != ""
|
|
406
|
+
render_tree_nodes(buf, outline.nodes, 1, max_depth: max_depth, show_ids: show_ids, show_hrefs: show_hrefs, ascii: ascii, prefix: "")
|
|
407
|
+
buf
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def self.render_tree_nodes(buf, nodes, depth, max_depth: nil, show_ids: false, show_hrefs: false, ascii: false, prefix: "")
|
|
411
|
+
return if max_depth && depth > max_depth
|
|
412
|
+
return if nodes.empty?
|
|
413
|
+
n_last = nodes.last
|
|
414
|
+
nodes.each do |n|
|
|
415
|
+
is_last = (n == n_last)
|
|
416
|
+
branch = ascii ? (is_last ? "-- " : "|-- ") : (is_last ? "└─ " : "├─ ")
|
|
417
|
+
line = prefix + branch + n.title.to_s
|
|
418
|
+
line << " (id: #{n.id})" if show_ids && n.id.to_s != ""
|
|
419
|
+
line << " → #{n.href}" if show_hrefs && n.href.to_s != ""
|
|
420
|
+
line << "\n"
|
|
421
|
+
buf << line
|
|
422
|
+
child_prefix = prefix + (ascii ? (is_last ? " " : "| ") : (is_last ? " " : "│ "))
|
|
423
|
+
render_tree_nodes(buf, n.children, depth + 1, max_depth: max_depth, show_ids: show_ids, show_hrefs: show_hrefs, ascii: ascii, prefix: child_prefix)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Render outline as numbered list (1., 1.1., …) or indented text. Indentation 2 spaces per depth.
|
|
428
|
+
def self.render_outline(outline, max_depth: nil, show_ids: false, no_numbers: false)
|
|
429
|
+
buf = +""
|
|
430
|
+
render_outline_nodes(buf, outline.nodes, 1, max_depth: max_depth, show_ids: show_ids, no_numbers: no_numbers, indent: "", numbers: [])
|
|
431
|
+
buf
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def self.render_outline_nodes(buf, nodes, depth, max_depth: nil, show_ids: false, no_numbers: false, indent: "", numbers: [])
|
|
435
|
+
return if max_depth && depth > max_depth
|
|
436
|
+
nodes.each_with_index do |n, i|
|
|
437
|
+
nums = numbers + [i + 1]
|
|
438
|
+
num_str = no_numbers ? "" : nums.join(".") + ". "
|
|
439
|
+
line = indent + num_str + n.title.to_s
|
|
440
|
+
line << " (id: #{n.id})" if show_ids && n.id.to_s != ""
|
|
441
|
+
line << "\n"
|
|
442
|
+
buf << line
|
|
443
|
+
child_indent = indent + " "
|
|
444
|
+
render_outline_nodes(buf, n.children, depth + 1, max_depth: max_depth, show_ids: show_ids, no_numbers: no_numbers, indent: child_indent, numbers: nums)
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Decode Outline from JSON. Format: { "title": "", "nodes": [ { "title", "id", "href", "source_path", "children": [] } ] }.
|
|
449
|
+
def self.decode_outline_json(json)
|
|
450
|
+
require "json"
|
|
451
|
+
h = ::JSON.parse(json)
|
|
452
|
+
title = (h["title"] || "").to_s.strip
|
|
453
|
+
nodes = (h["nodes"] || []).map { |n| decode_outline_node_hash(n) }
|
|
454
|
+
Outline.new(title: title, nodes: nodes)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def self.decode_outline_node_hash(h)
|
|
458
|
+
children = (h["children"] || []).map { |c| decode_outline_node_hash(c) }
|
|
459
|
+
OutlineNode.new(
|
|
460
|
+
title: (h["title"] || "").to_s,
|
|
461
|
+
id: (h["id"] || "").to_s.strip.empty? ? nil : (h["id"] || "").to_s,
|
|
462
|
+
href: (h["href"] || "").to_s.strip.empty? ? nil : (h["href"] || "").to_s,
|
|
463
|
+
source_path: (h["sourcePath"] || h["source_path"] || "").to_s.strip.empty? ? nil : (h["sourcePath"] || h["source_path"]).to_s,
|
|
464
|
+
children: children
|
|
465
|
+
)
|
|
466
|
+
end
|
|
467
|
+
|
|
151
468
|
# ---- Legacy API: merge (concatenate .md files) and generate_pdf ----
|
|
152
469
|
|
|
153
470
|
# Merge: collect all .md under source_dir (sorted by path), write concatenated content to target_path.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rakit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rakit
|
|
@@ -130,8 +130,6 @@ files:
|
|
|
130
130
|
- lib/generated/rakit.shell_pb.rb
|
|
131
131
|
- lib/generated/rakit.static_web_server_pb.rb
|
|
132
132
|
- lib/generated/rakit.word_count_pb.rb
|
|
133
|
-
- lib/generated/shell_pb.rb
|
|
134
|
-
- lib/generated/static_web_server_pb.rb
|
|
135
133
|
- lib/generated/word_cloud_pb.rb
|
|
136
134
|
- lib/rakit.rb
|
|
137
135
|
- lib/rakit/azure/dev_ops.rb
|
data/lib/generated/shell_pb.rb
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: shell.proto
|
|
4
|
-
|
|
5
|
-
require 'google/protobuf'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
descriptor_data = "\n\x0bshell.proto\x12\x0brakit.shell\"\x9a\x02\n\x07\x43ommand\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x02 \x03(\t\x12\x19\n\x11working_directory\x18\x03 \x01(\t\x12\x17\n\x0ftimeout_seconds\x18\x04 \x01(\x05\x12\x1a\n\x12\x65xpected_exit_code\x18\x05 \x01(\x05\x12\x17\n\x0f\x65xpected_stdout\x18\x06 \x01(\t\x12\x17\n\x0f\x65xpected_stderr\x18\x07 \x01(\t\x12<\n\x13\x61\x63\x63\x65ptance_criteria\x18\x08 \x03(\x0b\x32\x1f.rakit.shell.AcceptanceCriteria\x12\x13\n\x0b\x65xit_status\x18\t \x01(\x05\x12\x0e\n\x06stdout\x18\n \x01(\t\x12\x0e\n\x06stderr\x18\x0b \x01(\t\"1\n\x12\x41\x63\x63\x65ptanceCriteria\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"-\n\nTestResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06\x65rrors\x18\x02 \x03(\t\"b\n\rFormatRequest\x12%\n\x07\x63ommand\x18\x01 \x01(\x0b\x32\x14.rakit.shell.Command\x12*\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x1a.rakit.shell.CommandFormat\" \n\x0e\x46ormatResponse\x12\x0e\n\x06output\x18\x01 \x01(\t*Z\n\rCommandFormat\x12\x1e\n\x1a\x43OMMAND_FORMAT_UNSPECIFIED\x10\x00\x12\x0c\n\x08ONE_LINE\x10\x01\x12\x0e\n\nMULTI_LINE\x10\x02\x12\x0b\n\x07\x43OMPACT\x10\x03\x32\xc1\x01\n\x0e\x43ommandService\x12\x35\n\x07\x45xecute\x12\x14.rakit.shell.Command\x1a\x14.rakit.shell.Command\x12\x35\n\x04Test\x12\x14.rakit.shell.Command\x1a\x17.rakit.shell.TestResult\x12\x41\n\x06\x46ormat\x12\x1a.rakit.shell.FormatRequest\x1a\x1b.rakit.shell.FormatResponseB\x0f\xea\x02\x0cRakit::Shellb\x06proto3"
|
|
9
|
-
|
|
10
|
-
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
-
pool.add_serialized_file(descriptor_data)
|
|
12
|
-
|
|
13
|
-
module Rakit
|
|
14
|
-
module Shell
|
|
15
|
-
Command = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.Command").msgclass
|
|
16
|
-
AcceptanceCriteria = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.AcceptanceCriteria").msgclass
|
|
17
|
-
TestResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.TestResult").msgclass
|
|
18
|
-
FormatRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.FormatRequest").msgclass
|
|
19
|
-
FormatResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.FormatResponse").msgclass
|
|
20
|
-
CommandFormat = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.CommandFormat").enummodule
|
|
21
|
-
end
|
|
22
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: static_web_server.proto
|
|
4
|
-
|
|
5
|
-
require 'google/protobuf'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
descriptor_data = "\n\x17static_web_server.proto\x12\x17rakit.static_web_server\"T\n\x15StaticWebServerConfig\x12\x16\n\x0eroot_directory\x18\x01 \x01(\t\x12\x0c\n\x04port\x18\x02 \x01(\r\x12\x15\n\rhttps_enabled\x18\x03 \x01(\x08\"=\n\x0ePublishRequest\x12\x11\n\tsite_name\x18\x01 \x01(\t\x12\x18\n\x10source_directory\x18\x02 \x01(\t\"1\n\rPublishResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"-\n\x0cServerStatus\x12\x0f\n\x07running\x18\x01 \x01(\x08\x12\x0c\n\x04port\x18\x02 \x01(\rB\x13\xea\x02\x10Rakit::Generatedb\x06proto3"
|
|
9
|
-
|
|
10
|
-
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
-
pool.add_serialized_file(descriptor_data)
|
|
12
|
-
|
|
13
|
-
module Rakit
|
|
14
|
-
module Generated
|
|
15
|
-
StaticWebServerConfig = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.static_web_server.StaticWebServerConfig").msgclass
|
|
16
|
-
PublishRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.static_web_server.PublishRequest").msgclass
|
|
17
|
-
PublishResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.static_web_server.PublishResult").msgclass
|
|
18
|
-
ServerStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.static_web_server.ServerStatus").msgclass
|
|
19
|
-
end
|
|
20
|
-
end
|