rakit 0.1.6 → 0.1.8
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 +16 -25
- data/lib/generated/azure.devops_pb.rb +26 -0
- data/lib/generated/data_pb.rb +2 -1
- data/lib/generated/{rakit.example_pb.rb → example_pb.rb} +2 -2
- data/lib/generated/shell_pb.rb +22 -0
- data/lib/generated/{rakit.static_web_server_pb.rb → static_web_server_pb.rb} +2 -2
- data/lib/generated/word_cloud_pb.rb +21 -0
- data/lib/rakit/azure/dev_ops.rb +1 -1
- data/lib/rakit/cli/markdown.rb +90 -0
- data/lib/rakit/cli/word_cloud.rb +225 -0
- data/lib/rakit/markdown.rb +157 -0
- data/lib/rakit/shell.rb +1 -1
- data/lib/rakit/static_web_server.rb +12 -55
- data/lib/rakit/word_cloud.rb +247 -0
- data/lib/rakit.rb +1 -1
- metadata +24 -6
- data/lib/generated/rakit.azure_pb.rb +0 -26
- data/lib/generated/rakit.shell_pb.rb +0 -22
- data/lib/rakit/hugo.rb +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 618281ffaa52461777b6cf921c5290e89dc503ef845adc9410d4934d917ed285
|
|
4
|
+
data.tar.gz: c3b90837302fb00dac1987e6c181307480ab7133fe113dc65805b307e0c2ed3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c9b06b9a2fce44ff8cb4b99993c2a6caff13a9b5155cb2202b23000faccdc82853201134bff2cef57bd17400e038d86d6ef3437239c26409587ed78ef3b3c30
|
|
7
|
+
data.tar.gz: 4b3624c2093bde3d3f0a42959a511793e231dc2fba0a6e2cf7baac4613812355764a11a195cd76ec8abfa7c082813e38243e7276e94de19505fd33499827d0ca
|
data/exe/rakit
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
# CLI for rakit gem. Subcommands: file (list|copy), word-count (--json-keys), static-web-server (start|stop|running|publish).
|
|
4
|
+
# CLI for rakit gem. Subcommands: markdown (pdf|amalgamate), file (list|copy), word-count (--json-keys), static-web-server (start|stop|running|publish).
|
|
5
|
+
# CLI for rakit gem. Subcommands: file (list|copy), word-count (--json-keys), static-web-server (start|stop|running|publish), word-cloud (status|install|generate).
|
|
5
6
|
|
|
6
7
|
require "rakit"
|
|
7
8
|
|
|
@@ -11,29 +12,23 @@ Rakit::StaticWebServer.root = ENV["RAKIT_WWW_ROOT"] if ENV["RAKIT_WWW_ROOT"]
|
|
|
11
12
|
def usage_stderr(msg = nil)
|
|
12
13
|
$stderr.puts msg if msg
|
|
13
14
|
$stderr.puts "Usage: rakit <subcommand> [options] [args]"
|
|
15
|
+
$stderr.puts " markdown pdf <input.md> [output.pdf]"
|
|
16
|
+
$stderr.puts " markdown amalgamate <source_dir> <target.md> [--create-directories]"
|
|
14
17
|
$stderr.puts " file list <directory> [--recursive] [--include-hidden] [--format console|json|proto-json]"
|
|
15
18
|
$stderr.puts " file copy <source> <destination> [--overwrite] [--create-directories] [--format ...]"
|
|
16
19
|
$stderr.puts " word-count <file>|--stdin --json-keys [--format console|json|proto-json] [options]"
|
|
17
|
-
$stderr.puts " static-web-server <start|stop|running|publish
|
|
18
|
-
$stderr.puts "
|
|
19
|
-
$stderr.puts " stop Stop server"
|
|
20
|
-
$stderr.puts " running Exit 0 if running, non-zero otherwise"
|
|
21
|
-
$stderr.puts " publish <site_name> <source_dir> Publish static site"
|
|
22
|
-
$stderr.puts " view [path] Open path in browser (default /)"
|
|
20
|
+
$stderr.puts " static-web-server <start|stop|running|publish> [options] [args]"
|
|
21
|
+
$stderr.puts " word-cloud <status|install|generate> [options] [args]"
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def main(argv = ARGV)
|
|
26
25
|
return usage_stderr("Expected subcommand.") if argv.empty?
|
|
27
26
|
|
|
28
27
|
sub = argv[0]
|
|
29
|
-
if sub == "
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return
|
|
33
|
-
end
|
|
34
|
-
if sub == "--help" || sub == "-h"
|
|
35
|
-
usage_stderr
|
|
36
|
-
return 0
|
|
28
|
+
if sub == "markdown"
|
|
29
|
+
argv.shift
|
|
30
|
+
require "rakit/cli/markdown"
|
|
31
|
+
return Rakit::CLI::Markdown.run(argv)
|
|
37
32
|
end
|
|
38
33
|
|
|
39
34
|
if sub == "file"
|
|
@@ -48,6 +43,12 @@ def main(argv = ARGV)
|
|
|
48
43
|
return Rakit::CLI::WordCount.run(argv)
|
|
49
44
|
end
|
|
50
45
|
|
|
46
|
+
if sub == "word-cloud"
|
|
47
|
+
argv.shift
|
|
48
|
+
require "rakit/cli/word_cloud"
|
|
49
|
+
return Rakit::CLI::WordCloud.run(argv)
|
|
50
|
+
end
|
|
51
|
+
|
|
51
52
|
if sub != "static-web-server"
|
|
52
53
|
usage_stderr("Unknown subcommand: #{sub}")
|
|
53
54
|
return 1
|
|
@@ -64,7 +65,6 @@ def main(argv = ARGV)
|
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
67
|
Rakit::StaticWebServer.start(port: port)
|
|
67
|
-
puts "http://localhost:#{port}"
|
|
68
68
|
return 0
|
|
69
69
|
when "stop"
|
|
70
70
|
Rakit::StaticWebServer.stop
|
|
@@ -80,15 +80,6 @@ def main(argv = ARGV)
|
|
|
80
80
|
end
|
|
81
81
|
Rakit::StaticWebServer.publish(site_name, source_dir)
|
|
82
82
|
return 0
|
|
83
|
-
when "view"
|
|
84
|
-
path = argv.shift || "/"
|
|
85
|
-
begin
|
|
86
|
-
Rakit::StaticWebServer.view(path)
|
|
87
|
-
return 0
|
|
88
|
-
rescue RuntimeError => e
|
|
89
|
-
$stderr.puts e.message
|
|
90
|
-
return 1
|
|
91
|
-
end
|
|
92
83
|
else
|
|
93
84
|
usage_stderr(cmd ? "Unknown command: #{cmd}" : "Expected command after static-web-server.")
|
|
94
85
|
return 1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: azure.devops.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
descriptor_data = "\n\x12\x61zure.devops.proto\x12\x0brakit.azure\"L\n\x08Pipeline\x12\x0b\n\x03org\x18\x01 \x01(\t\x12\x0f\n\x07project\x18\x02 \x01(\t\x12\x13\n\x0bpipeline_id\x18\x03 \x01(\x05\x12\r\n\x05token\x18\x04 \x01(\t\"R\n\x18GetPipelineResultRequest\x12\'\n\x08pipeline\x18\x01 \x01(\x0b\x32\x15.rakit.azure.Pipeline\x12\r\n\x05token\x18\x02 \x01(\t\"C\n\x0ePipelineStatus\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06\x65rrors\x18\x02 \x03(\t\x12\x10\n\x08warnings\x18\x03 \x03(\t\"8\n\x0ePipelineResult\x12&\n\x04runs\x18\x01 \x03(\x0b\x32\x18.rakit.azure.PipelineRun\"\x97\x01\n\x0bPipelineRun\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x0e\n\x06result\x18\x04 \x01(\t\x12\x14\n\x0c\x63reated_date\x18\x05 \x01(\t\x12\x15\n\rfinished_date\x18\x06 \x01(\t\x12\"\n\x06stages\x18\x07 \x03(\x0b\x32\x12.rakit.azure.Stage\"&\n\x05Issue\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\"G\n\x03Job\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\"\n\x06issues\x18\x03 \x03(\x0b\x32\x12.rakit.azure.Issue\"i\n\x05Stage\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\x1e\n\x04jobs\x18\x03 \x03(\x0b\x32\x10.rakit.azure.Job\x12\"\n\x06issues\x18\x04 \x03(\x0b\x32\x12.rakit.azure.Issue\"`\n\x0eTimelineRecord\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0e\n\x06result\x18\x03 \x01(\t\x12\"\n\x06issues\x18\x04 \x03(\x0b\x32\x12.rakit.azure.Issue\"\xa8\x01\n\x14PipelineResultDetail\x12\x12\n\nsuccessful\x18\x01 \x01(\x08\x12\x0e\n\x06\x65rrors\x18\x02 \x01(\t\x12\x10\n\x08warnings\x18\x03 \x01(\t\x12%\n\x03run\x18\x04 \x01(\x0b\x32\x18.rakit.azure.PipelineRun\x12\x33\n\x0e\x66\x61iled_records\x18\x05 \x03(\x0b\x32\x1b.rakit.azure.TimelineRecord2i\n\x0ePipelineServer\x12W\n\x11GetPipelineResult\x12%.rakit.azure.GetPipelineResultRequest\x1a\x1b.rakit.azure.PipelineResultB\x0f\xea\x02\x0cRakit::Azureb\x06proto3"
|
|
9
|
+
|
|
10
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
+
pool.add_serialized_file(descriptor_data)
|
|
12
|
+
|
|
13
|
+
module Rakit
|
|
14
|
+
module Azure
|
|
15
|
+
Pipeline = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Pipeline").msgclass
|
|
16
|
+
GetPipelineResultRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.GetPipelineResultRequest").msgclass
|
|
17
|
+
PipelineStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineStatus").msgclass
|
|
18
|
+
PipelineResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineResult").msgclass
|
|
19
|
+
PipelineRun = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineRun").msgclass
|
|
20
|
+
Issue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Issue").msgclass
|
|
21
|
+
Job = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Job").msgclass
|
|
22
|
+
Stage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Stage").msgclass
|
|
23
|
+
TimelineRecord = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.TimelineRecord").msgclass
|
|
24
|
+
PipelineResultDetail = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineResultDetail").msgclass
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/generated/data_pb.rb
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# source: data.proto
|
|
4
4
|
|
|
5
|
-
require
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
6
7
|
|
|
7
8
|
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"
|
|
8
9
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source:
|
|
3
|
+
# source: example.proto
|
|
4
4
|
|
|
5
5
|
require 'google/protobuf'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
descriptor_data = "\n\
|
|
8
|
+
descriptor_data = "\n\rexample.proto\x12\rrakit.example\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\" \n\rHelloResponse\x12\x0f\n\x07message\x18\x01 \x01(\tB\x13\xea\x02\x10Rakit::Generatedb\x06proto3"
|
|
9
9
|
|
|
10
10
|
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
11
|
pool.add_serialized_file(descriptor_data)
|
|
@@ -0,0 +1,22 @@
|
|
|
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,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source:
|
|
3
|
+
# source: static_web_server.proto
|
|
4
4
|
|
|
5
5
|
require 'google/protobuf'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
descriptor_data = "\n\
|
|
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
9
|
|
|
10
10
|
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
11
|
pool.add_serialized_file(descriptor_data)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: word_cloud.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
descriptor_data = "\n\x10word_cloud.proto\x12\x0frakit.wordcloud\"W\n\x0fWordCloudConfig\x12\x13\n\x0bwcloud_path\x18\x01 \x01(\t\x12\x14\n\x0c\x61uto_install\x18\x02 \x01(\x08\x12\x19\n\x11working_directory\x18\x03 \x01(\t\"S\n\nTextSource\x12\x13\n\ttext_file\x18\x01 \x01(\tH\x00\x12\x15\n\x0binline_text\x18\x02 \x01(\tH\x00\x12\x0f\n\x05stdin\x18\x03 \x01(\x08H\x00\x42\x08\n\x06source\"\xa6\x02\n\x0fGenerateRequest\x12\x30\n\x06\x63onfig\x18\x01 \x01(\x0b\x32 .rakit.wordcloud.WordCloudConfig\x12)\n\x04text\x18\x02 \x01(\x0b\x32\x1b.rakit.wordcloud.TextSource\x12\x14\n\x0coutput_image\x18\x03 \x01(\t\x12\r\n\x05width\x18\x04 \x01(\x05\x12\x0e\n\x06height\x18\x05 \x01(\x05\x12\x10\n\x08rng_seed\x18\x06 \x01(\x05\x12\x12\n\nmask_image\x18\x07 \x01(\t\x12\x11\n\tfont_file\x18\x08 \x01(\t\x12\x1a\n\x12\x65xclude_words_file\x18\t \x01(\t\x12\x11\n\tmax_words\x18\n \x01(\x05\x12\x19\n\x11publish_site_name\x18\x0b \x01(\t\"\xcf\x01\n\x0eGenerateResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x14\n\x0coutput_image\x18\x03 \x01(\t\x12\x11\n\texit_code\x18\x04 \x01(\x05\x12\x0e\n\x06stdout\x18\x05 \x01(\t\x12\x0e\n\x06stderr\x18\x06 \x01(\t\x12\x1c\n\x14wcloud_was_installed\x18\x07 \x01(\x08\x12\x1c\n\x14wcloud_resolved_path\x18\x08 \x01(\t\x12\x16\n\x0ewcloud_version\x18\t \x01(\t\"`\n\nToolStatus\x12\x14\n\x0cwcloud_found\x18\x01 \x01(\x08\x12\x13\n\x0bwcloud_path\x18\x02 \x01(\t\x12\x13\n\x0b\x63\x61rgo_found\x18\x03 \x01(\x08\x12\x12\n\ncargo_path\x18\x04 \x01(\tB\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
|
+
WordCloudConfig = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.wordcloud.WordCloudConfig").msgclass
|
|
16
|
+
TextSource = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.wordcloud.TextSource").msgclass
|
|
17
|
+
GenerateRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.wordcloud.GenerateRequest").msgclass
|
|
18
|
+
GenerateResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.wordcloud.GenerateResult").msgclass
|
|
19
|
+
ToolStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.wordcloud.ToolStatus").msgclass
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/rakit/azure/dev_ops.rb
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rakit/markdown"
|
|
4
|
+
|
|
5
|
+
module Rakit
|
|
6
|
+
module CLI
|
|
7
|
+
module Markdown
|
|
8
|
+
class << self
|
|
9
|
+
# @param argv [Array<String>] arguments after "markdown"
|
|
10
|
+
# @return [Integer] exit code
|
|
11
|
+
def run(argv)
|
|
12
|
+
return 1 if argv.empty?
|
|
13
|
+
sub = argv.shift
|
|
14
|
+
case sub
|
|
15
|
+
when "pdf" then run_pdf(argv)
|
|
16
|
+
when "amalgamate" then run_amalgamate(argv)
|
|
17
|
+
else
|
|
18
|
+
$stderr.puts "Unknown command: #{sub}. Use 'pdf' or 'amalgamate'."
|
|
19
|
+
$stderr.puts " rakit markdown pdf <input.md> [output.pdf]"
|
|
20
|
+
$stderr.puts " rakit markdown amalgamate <source_dir> <target.md> [--create-directories]"
|
|
21
|
+
1
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def run_pdf(argv)
|
|
28
|
+
opts = parse_pdf_argv(argv)
|
|
29
|
+
return 1 if opts[:error]
|
|
30
|
+
result = Rakit::Markdown.generate_pdf(opts[:input], opts[:output])
|
|
31
|
+
$stderr.puts result[:stderr] if result[:stderr] && !result[:stderr].empty? && !result[:success]
|
|
32
|
+
$stderr.puts result[:message] if result[:message] && !result[:message].empty? && !result[:success]
|
|
33
|
+
result[:exit_code] || (result[:success] ? 0 : 1)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse_pdf_argv(argv)
|
|
37
|
+
opts = { input: nil, output: nil, error: false }
|
|
38
|
+
args = argv.dup
|
|
39
|
+
pos = []
|
|
40
|
+
while (arg = args.shift)
|
|
41
|
+
if arg.start_with?("--")
|
|
42
|
+
$stderr.puts "Unknown option: #{arg}"
|
|
43
|
+
opts[:error] = true
|
|
44
|
+
else
|
|
45
|
+
pos << arg
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
opts[:input] = pos[0]
|
|
49
|
+
opts[:output] = pos[1] if pos.length > 1
|
|
50
|
+
unless opts[:input] && !opts[:input].empty?
|
|
51
|
+
$stderr.puts "pdf requires <input.md>"
|
|
52
|
+
opts[:error] = true
|
|
53
|
+
end
|
|
54
|
+
opts
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run_amalgamate(argv)
|
|
58
|
+
opts = parse_amalgamate_argv(argv)
|
|
59
|
+
return 1 if opts[:error]
|
|
60
|
+
result = Rakit::Markdown.amalgamate(opts[:source_dir], opts[:target], create_directories: opts[:create_directories])
|
|
61
|
+
$stderr.puts result[:message] if result[:message] && !result[:message].empty? && !result[:success]
|
|
62
|
+
result[:exit_code] || (result[:success] ? 0 : 1)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def parse_amalgamate_argv(argv)
|
|
66
|
+
opts = { source_dir: nil, target: nil, create_directories: false, error: false }
|
|
67
|
+
args = argv.dup
|
|
68
|
+
pos = []
|
|
69
|
+
while (arg = args.shift)
|
|
70
|
+
case arg
|
|
71
|
+
when "--create-directories" then opts[:create_directories] = true
|
|
72
|
+
when /^--/
|
|
73
|
+
$stderr.puts "Unknown option: #{arg}"
|
|
74
|
+
opts[:error] = true
|
|
75
|
+
else
|
|
76
|
+
pos << arg
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
opts[:source_dir] = pos[0]
|
|
80
|
+
opts[:target] = pos[1]
|
|
81
|
+
unless opts[:source_dir] && opts[:target]
|
|
82
|
+
$stderr.puts "amalgamate requires <source_dir> and <target.md>"
|
|
83
|
+
opts[:error] = true
|
|
84
|
+
end
|
|
85
|
+
opts
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "google/protobuf"
|
|
5
|
+
require "rakit/word_cloud"
|
|
6
|
+
|
|
7
|
+
module Rakit
|
|
8
|
+
module CLI
|
|
9
|
+
module WordCloud
|
|
10
|
+
class << self
|
|
11
|
+
# @param argv [Array<String>] arguments after "word-cloud"
|
|
12
|
+
# @return [Integer] exit code
|
|
13
|
+
def run(argv)
|
|
14
|
+
return usage_stderr("Expected command: status | install | generate") if argv.empty?
|
|
15
|
+
|
|
16
|
+
cmd = argv.shift
|
|
17
|
+
case cmd
|
|
18
|
+
when "status" then run_status(argv)
|
|
19
|
+
when "install" then run_install(argv)
|
|
20
|
+
when "generate" then run_generate(argv)
|
|
21
|
+
else
|
|
22
|
+
$stderr.puts "Unknown command: #{cmd}"
|
|
23
|
+
1
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def parse_format(argv)
|
|
30
|
+
format = "console"
|
|
31
|
+
args = argv.dup
|
|
32
|
+
i = args.index("--format")
|
|
33
|
+
if i && args[i + 1]
|
|
34
|
+
format = args[i + 1]
|
|
35
|
+
args.delete_at(i + 1)
|
|
36
|
+
args.delete_at(i)
|
|
37
|
+
end
|
|
38
|
+
[format, args]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def run_status(argv)
|
|
42
|
+
format, _ = parse_format(argv)
|
|
43
|
+
st = Rakit::WordCloud.status({})
|
|
44
|
+
render_tool_status(st, format)
|
|
45
|
+
0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def run_install(argv)
|
|
49
|
+
format, _ = parse_format(argv)
|
|
50
|
+
result = Rakit::WordCloud.install({})
|
|
51
|
+
render_install_result(result, format)
|
|
52
|
+
result.success ? 0 : 1
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def run_generate(argv)
|
|
56
|
+
format, rest = parse_format(argv)
|
|
57
|
+
opts = parse_generate_opts(rest)
|
|
58
|
+
return 1 if opts[:error]
|
|
59
|
+
|
|
60
|
+
request = build_generate_request(opts)
|
|
61
|
+
return 1 unless request
|
|
62
|
+
|
|
63
|
+
stdin_content = opts[:stdin] ? $stdin.read : nil
|
|
64
|
+
result = Rakit::WordCloud.generate(request, stdin_content: stdin_content)
|
|
65
|
+
|
|
66
|
+
if opts[:publish_site_name] && result.success && defined?(Rakit::StaticWebServer)
|
|
67
|
+
out_dir = ::File.dirname(result.output_image.to_s)
|
|
68
|
+
begin
|
|
69
|
+
Rakit::StaticWebServer.publish(opts[:publish_site_name], out_dir)
|
|
70
|
+
rescue => e
|
|
71
|
+
$stderr.puts "Publish failed: #{e.message}"
|
|
72
|
+
return 1
|
|
73
|
+
end
|
|
74
|
+
elsif opts[:publish_site_name] && result.success && !defined?(Rakit::StaticWebServer)
|
|
75
|
+
$stderr.puts "Publish requested but StaticWebServer not available. Require the static web server feature."
|
|
76
|
+
return 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
render_generate_result(result, format)
|
|
80
|
+
result.success ? 0 : 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def parse_generate_opts(argv)
|
|
84
|
+
opts = {
|
|
85
|
+
format: "console",
|
|
86
|
+
text_file: nil,
|
|
87
|
+
inline_text: nil,
|
|
88
|
+
stdin: false,
|
|
89
|
+
out: nil,
|
|
90
|
+
width: nil,
|
|
91
|
+
height: nil,
|
|
92
|
+
seed: nil,
|
|
93
|
+
mask: nil,
|
|
94
|
+
font: nil,
|
|
95
|
+
exclude_words: nil,
|
|
96
|
+
max_words: nil,
|
|
97
|
+
auto_install: false,
|
|
98
|
+
publish_site_name: nil,
|
|
99
|
+
working_directory: nil
|
|
100
|
+
}
|
|
101
|
+
args = argv.dup
|
|
102
|
+
while (arg = args.shift)
|
|
103
|
+
case arg
|
|
104
|
+
when "--text" then opts[:text_file] = args.shift
|
|
105
|
+
when "--inline" then opts[:inline_text] = args.shift
|
|
106
|
+
when "--stdin" then opts[:stdin] = true
|
|
107
|
+
when "--out" then opts[:out] = args.shift
|
|
108
|
+
when "--width" then opts[:width] = (args.shift || "0").to_i
|
|
109
|
+
when "--height" then opts[:height] = (args.shift || "0").to_i
|
|
110
|
+
when "--seed" then opts[:seed] = (args.shift || "-1").to_i
|
|
111
|
+
when "--mask" then opts[:mask] = args.shift
|
|
112
|
+
when "--font" then opts[:font] = args.shift
|
|
113
|
+
when "--exclude-words" then opts[:exclude_words] = args.shift
|
|
114
|
+
when "--max-words" then opts[:max_words] = (args.shift || "0").to_i
|
|
115
|
+
when "--auto-install" then opts[:auto_install] = true
|
|
116
|
+
when "--publish" then opts[:publish_site_name] = args.shift
|
|
117
|
+
when "--working-directory" then opts[:working_directory] = args.shift
|
|
118
|
+
when /^--/
|
|
119
|
+
$stderr.puts "Unknown option: #{arg}"
|
|
120
|
+
opts[:error] = true
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
opts
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_generate_request(opts)
|
|
127
|
+
unless opts[:out] && opts[:out].to_s.strip != ""
|
|
128
|
+
$stderr.puts "generate requires --out PATH"
|
|
129
|
+
return nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
has_file = opts[:text_file] && opts[:text_file].to_s.strip != ""
|
|
133
|
+
has_inline = opts[:inline_text] && opts[:inline_text].to_s.strip != ""
|
|
134
|
+
has_stdin = opts[:stdin]
|
|
135
|
+
count = [has_file, has_inline, has_stdin].count(true)
|
|
136
|
+
unless count == 1
|
|
137
|
+
$stderr.puts "Provide exactly one of: --text FILE, --inline \"string\", or --stdin"
|
|
138
|
+
return nil
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
gen = Rakit::Generated
|
|
142
|
+
config = gen::WordCloudConfig.new(
|
|
143
|
+
wcloud_path: "",
|
|
144
|
+
auto_install: opts[:auto_install],
|
|
145
|
+
working_directory: (opts[:working_directory] || "").to_s
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
text = if opts[:text_file] && opts[:text_file].to_s.strip != ""
|
|
149
|
+
gen::TextSource.new(text_file: opts[:text_file].to_s)
|
|
150
|
+
elsif opts[:inline_text] && opts[:inline_text].to_s.strip != ""
|
|
151
|
+
gen::TextSource.new(inline_text: opts[:inline_text].to_s)
|
|
152
|
+
else
|
|
153
|
+
gen::TextSource.new(stdin: true)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
req = gen::GenerateRequest.new(
|
|
157
|
+
config: config,
|
|
158
|
+
text: text,
|
|
159
|
+
output_image: opts[:out].to_s,
|
|
160
|
+
width: opts[:width] || 0,
|
|
161
|
+
height: opts[:height] || 0,
|
|
162
|
+
rng_seed: opts[:seed] || -1,
|
|
163
|
+
mask_image: (opts[:mask] || "").to_s,
|
|
164
|
+
font_file: (opts[:font] || "").to_s,
|
|
165
|
+
exclude_words_file: (opts[:exclude_words] || "").to_s,
|
|
166
|
+
max_words: opts[:max_words] || 0
|
|
167
|
+
)
|
|
168
|
+
req
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def render_tool_status(st, format)
|
|
172
|
+
case format
|
|
173
|
+
when "json"
|
|
174
|
+
puts ({ wcloud_found: st.wcloud_found, wcloud_path: st.wcloud_path, cargo_found: st.cargo_found, cargo_path: st.cargo_path }.to_json)
|
|
175
|
+
when "proto-json"
|
|
176
|
+
puts Google::Protobuf.encode_json(st)
|
|
177
|
+
else
|
|
178
|
+
puts "wcloud: #{st.wcloud_found ? st.wcloud_path : 'not found'}"
|
|
179
|
+
puts "cargo: #{st.cargo_found ? st.cargo_path : 'not found'}"
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def render_install_result(result, format)
|
|
184
|
+
case format
|
|
185
|
+
when "json"
|
|
186
|
+
puts ({ success: result.success, message: result.message, stderr: result.stderr }.to_json)
|
|
187
|
+
when "proto-json"
|
|
188
|
+
puts Google::Protobuf.encode_json(result)
|
|
189
|
+
else
|
|
190
|
+
unless result.success
|
|
191
|
+
$stderr.puts result.message
|
|
192
|
+
$stderr.puts result.stderr if result.stderr.to_s.strip != ""
|
|
193
|
+
else
|
|
194
|
+
puts result.message
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def render_generate_result(result, format)
|
|
200
|
+
unless result.success
|
|
201
|
+
$stderr.puts result.message
|
|
202
|
+
$stderr.puts result.stderr if result.stderr.to_s.strip != "" && result.stderr != result.message
|
|
203
|
+
end
|
|
204
|
+
case format
|
|
205
|
+
when "json"
|
|
206
|
+
h = { success: result.success, message: result.message, output_image: result.output_image, exit_code: result.exit_code }
|
|
207
|
+
puts h.to_json
|
|
208
|
+
when "proto-json"
|
|
209
|
+
puts Google::Protobuf.encode_json(result)
|
|
210
|
+
else
|
|
211
|
+
puts result.message if result.success
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def usage_stderr(msg)
|
|
216
|
+
$stderr.puts msg
|
|
217
|
+
$stderr.puts " word-cloud status [--format console|json|proto-json]"
|
|
218
|
+
$stderr.puts " word-cloud install [--format ...]"
|
|
219
|
+
$stderr.puts " word-cloud generate (--text FILE|--inline \"string\"|--stdin) --out PATH [options]"
|
|
220
|
+
1
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "kramdown"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Rakit
|
|
7
|
+
module Markdown
|
|
8
|
+
class << self
|
|
9
|
+
# @param source_path [String] path to markdown file
|
|
10
|
+
# @param output_path [String, nil] path for PDF; default same dir, .pdf extension
|
|
11
|
+
# @return [Hash] { success: Boolean, message: String, exit_code: Integer, stderr: String (optional) }
|
|
12
|
+
def generate_pdf(source_path, output_path = nil)
|
|
13
|
+
validated = validate_file_path(source_path)
|
|
14
|
+
unless validated
|
|
15
|
+
return { success: false, message: "Source path must exist and be a file", exit_code: 1, stderr: "" }
|
|
16
|
+
end
|
|
17
|
+
# Explicit output_path is relative to CWD (or absolute); default is same dir as source, .pdf extension
|
|
18
|
+
out_path = if output_path.to_s.strip.empty?
|
|
19
|
+
nil
|
|
20
|
+
else
|
|
21
|
+
::File.expand_path(output_path.to_s.strip)
|
|
22
|
+
end
|
|
23
|
+
out_path ||= ::File.join(::File.dirname(validated), ::File.basename(validated, ".*") + ".pdf")
|
|
24
|
+
out_path = ::File.expand_path(out_path)
|
|
25
|
+
|
|
26
|
+
unless wkhtmltopdf_available?
|
|
27
|
+
msg = wkhtmltopdf_install_message
|
|
28
|
+
return { success: false, message: msg, exit_code: 1, stderr: msg }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
temp_html = nil
|
|
32
|
+
begin
|
|
33
|
+
md_content = ::File.read(validated, encoding: "UTF-8")
|
|
34
|
+
html = markdown_to_html(md_content)
|
|
35
|
+
temp_html = ::File.join(::File.dirname(out_path), ".rakit_md_#{Process.pid}_#{object_id}.html")
|
|
36
|
+
::File.write(temp_html, html, encoding: "UTF-8")
|
|
37
|
+
ok = system("wkhtmltopdf", "-q", temp_html, out_path, out: ::File::NULL, err: ::File::NULL)
|
|
38
|
+
unless ok
|
|
39
|
+
::File.unlink(out_path) if ::File.file?(out_path)
|
|
40
|
+
return { success: false, message: "wkhtmltopdf failed", exit_code: 1, stderr: "" }
|
|
41
|
+
end
|
|
42
|
+
{ success: true, message: "", exit_code: 0, stderr: "" }
|
|
43
|
+
rescue => e
|
|
44
|
+
::File.unlink(out_path) if out_path && ::File.file?(out_path)
|
|
45
|
+
{ success: false, message: e.message, exit_code: 1, stderr: "" }
|
|
46
|
+
ensure
|
|
47
|
+
::File.unlink(temp_html) if temp_html && ::File.file?(temp_html)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param source_dir [String] directory to collect .md files from
|
|
52
|
+
# @param target_path [String] path to output markdown file
|
|
53
|
+
# @param create_directories [Boolean]
|
|
54
|
+
# @return [Hash] { success: Boolean, message: String, exit_code: Integer }
|
|
55
|
+
def amalgamate(source_dir, target_path, create_directories: false)
|
|
56
|
+
validated_dir = validate_dir_path(source_dir)
|
|
57
|
+
unless validated_dir
|
|
58
|
+
return { success: false, message: "Source directory must exist and be a directory", exit_code: 1 }
|
|
59
|
+
end
|
|
60
|
+
target_norm = normalize_path(target_path)
|
|
61
|
+
unless target_norm
|
|
62
|
+
return { success: false, message: "Target path is required", exit_code: 1 }
|
|
63
|
+
end
|
|
64
|
+
unless target_outside_source?(validated_dir, target_norm)
|
|
65
|
+
return { success: false, message: "Target path must be outside source directory", exit_code: 1 }
|
|
66
|
+
end
|
|
67
|
+
parent = ::File.dirname(target_norm)
|
|
68
|
+
unless ::File.directory?(parent)
|
|
69
|
+
if create_directories
|
|
70
|
+
::FileUtils.mkdir_p(parent)
|
|
71
|
+
else
|
|
72
|
+
return { success: false, message: "Parent directory does not exist; use create_directories: true", exit_code: 1 }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
begin
|
|
76
|
+
content = amalgamate_collect(validated_dir)
|
|
77
|
+
::File.write(target_norm, content, encoding: "UTF-8")
|
|
78
|
+
{ success: true, message: "", exit_code: 0 }
|
|
79
|
+
rescue => e
|
|
80
|
+
::File.unlink(target_norm) if ::File.file?(target_norm)
|
|
81
|
+
{ success: false, message: e.message, exit_code: 1 }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# T004: Normalize path; return nil if nil/empty after strip.
|
|
88
|
+
def normalize_path(path, base_dir = nil)
|
|
89
|
+
return nil if path.nil?
|
|
90
|
+
s = path.to_s.strip
|
|
91
|
+
return nil if s.empty?
|
|
92
|
+
base_dir ? ::File.expand_path(s, base_dir) : ::File.expand_path(s)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# T004: Validate path exists and is a file. Return expanded path or nil.
|
|
96
|
+
def validate_file_path(path)
|
|
97
|
+
norm = normalize_path(path)
|
|
98
|
+
return nil unless norm
|
|
99
|
+
return nil unless ::File.file?(norm)
|
|
100
|
+
norm
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# T004: Validate path exists and is a directory. Return expanded path or nil.
|
|
104
|
+
def validate_dir_path(path)
|
|
105
|
+
norm = normalize_path(path)
|
|
106
|
+
return nil unless norm
|
|
107
|
+
return nil unless ::File.directory?(norm)
|
|
108
|
+
norm
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# T005: Target is outside source_dir (not equal, not under). Both should be normalized.
|
|
112
|
+
def target_outside_source?(source_dir, target_path)
|
|
113
|
+
src = source_dir.to_s.gsub(::File::SEPARATOR, "/").chomp("/")
|
|
114
|
+
target = ::File.expand_path(target_path).to_s.gsub(::File::SEPARATOR, "/")
|
|
115
|
+
target_dir = ::File.dirname(target).gsub(::File::SEPARATOR, "/")
|
|
116
|
+
return false if target_dir == src
|
|
117
|
+
return false if target_dir.start_with?(src + "/")
|
|
118
|
+
return false if target == src
|
|
119
|
+
true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# T006: True if wkhtmltopdf is available.
|
|
123
|
+
def wkhtmltopdf_available?
|
|
124
|
+
out = `wkhtmltopdf --version 2>&1`
|
|
125
|
+
$?.success?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# T006: Platform-specific install message for wkhtmltopdf.
|
|
129
|
+
def wkhtmltopdf_install_message
|
|
130
|
+
if ::Gem.win_platform?
|
|
131
|
+
"Install wkhtmltopdf: choco install wkhtmltopdf"
|
|
132
|
+
elsif ::File.exist?("/usr/bin/sw_vers") || RUBY_PLATFORM.include?("darwin")
|
|
133
|
+
"Install wkhtmltopdf: brew install wkhtmltopdf"
|
|
134
|
+
else
|
|
135
|
+
"Install wkhtmltopdf: e.g. apt install wkhtmltopdf or yum install wkhtmltopdf"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# T007: Convert markdown string to HTML. Empty input -> minimal HTML.
|
|
140
|
+
def markdown_to_html(md_string)
|
|
141
|
+
return "" if md_string.nil? || md_string.to_s.strip.empty?
|
|
142
|
+
Kramdown::Document.new(md_string.to_s).to_html
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# T008: Collect all **/*.md under source_dir, sort by path, concatenate with separator. Return string.
|
|
146
|
+
AMALGAM_SEPARATOR = "\n\n"
|
|
147
|
+
|
|
148
|
+
def amalgamate_collect(source_dir)
|
|
149
|
+
norm_dir = ::File.expand_path(source_dir)
|
|
150
|
+
pattern = ::File.join(norm_dir, "**", "*.md")
|
|
151
|
+
paths = ::Dir.glob(pattern).select { |p| ::File.file?(p) }.sort
|
|
152
|
+
return "" if paths.empty?
|
|
153
|
+
paths.map { |p| ::File.read(p, encoding: "UTF-8") }.join(AMALGAM_SEPARATOR)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
data/lib/rakit/shell.rb
CHANGED
|
@@ -1,42 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
-
require "generated/
|
|
4
|
+
require "generated/static_web_server_pb"
|
|
5
5
|
|
|
6
6
|
module Rakit
|
|
7
|
-
# Publish static sites into a configurable root
|
|
8
|
-
#
|
|
9
|
-
# CLI: +rakit static-web-server+ (start|stop|running|publish|view). See specs/003-static-web-server/contracts/ruby-api.md and specs/007-wwwroot-docs-hugo-view/contracts/ruby-api.md.
|
|
7
|
+
# Publish static sites into a configurable root, regenerate a root index, and control a local HTTP server (start/stop/running).
|
|
8
|
+
# CLI: +rakit static-web-server+ (start|stop|running|publish). See contracts/ruby-api.md and quickstart in specs/003-static-web-server/quickstart.md.
|
|
10
9
|
module StaticWebServer
|
|
11
10
|
SITE_NAME_REGEX = /\A[a-z0-9\-]+\z/.freeze
|
|
12
11
|
|
|
13
12
|
class << self
|
|
14
|
-
# @return [String] Static root directory (default ~/.rakit/
|
|
13
|
+
# @return [String] Static root directory (default ~/.rakit/www_root). Used by publish and server lifecycle.
|
|
15
14
|
attr_accessor :root
|
|
16
|
-
# @return [
|
|
17
|
-
attr_accessor :host
|
|
18
|
-
# @return [Integer] Default port for start (default 5099). Used when no override passed to start; returned regardless of server running state.
|
|
15
|
+
# @return [Integer] Default port for start (default 5099). Used when no override passed to start.
|
|
19
16
|
attr_accessor :port
|
|
20
17
|
end
|
|
21
18
|
|
|
22
|
-
self.root = ::File.expand_path("~/.rakit/
|
|
23
|
-
self.host = "127.0.0.1"
|
|
19
|
+
self.root = ::File.expand_path("~/.rakit/www_root")
|
|
24
20
|
self.port = 5099
|
|
25
21
|
|
|
26
22
|
# T005: Validate site name; raise ArgumentError before any filesystem write.
|
|
27
|
-
# Single segment (e.g. "mysite") or multi-segment path (e.g. "louparslow/rakit"); each segment must match SITE_NAME_REGEX.
|
|
28
23
|
def self.validate_site_name!(site_name)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def self.validate_site_path!(site_name)
|
|
33
|
-
return if site_name.nil? || site_name.to_s.empty?
|
|
34
|
-
site_name.to_s.split(::File::SEPARATOR).each do |seg|
|
|
35
|
-
next if seg.empty?
|
|
36
|
-
unless seg.match?(SITE_NAME_REGEX)
|
|
37
|
-
raise ArgumentError, "site_name segment must be lowercase alphanumeric and dashes only (e.g. my-site); got: #{seg.inspect}"
|
|
38
|
-
end
|
|
39
|
-
end
|
|
24
|
+
return if site_name.is_a?(String) && site_name.match?(SITE_NAME_REGEX)
|
|
25
|
+
raise ArgumentError, "site_name must be lowercase alphanumeric and dashes only (e.g. my-site); got: #{site_name.inspect}"
|
|
40
26
|
end
|
|
41
27
|
|
|
42
28
|
# T006: PID file path for start/stop/running (research: ~/.rakit/static_web_server.pid).
|
|
@@ -63,27 +49,26 @@ module Rakit
|
|
|
63
49
|
end
|
|
64
50
|
|
|
65
51
|
# Publish static content from source_directory to root/site_name (atomic copy), then regenerate root index.
|
|
66
|
-
# @param site_name [String]
|
|
52
|
+
# @param site_name [String] Must match \\A[a-z0-9\-]+\\z (lowercase, alphanumeric, dashes only).
|
|
67
53
|
# @param source_directory [String] Existing directory path; contents are copied (no traversal outside allowed paths).
|
|
68
54
|
# @return [true] on success.
|
|
69
55
|
# @raise [ArgumentError] for invalid site_name, missing/invalid source_directory, or root not writable.
|
|
70
56
|
def self.publish(site_name, source_directory)
|
|
71
|
-
|
|
57
|
+
validate_site_name!(site_name)
|
|
72
58
|
src = ::File.expand_path(source_directory)
|
|
73
59
|
raise ArgumentError, "source_directory does not exist: #{source_directory}" unless ::File.exist?(src)
|
|
74
60
|
raise ArgumentError, "source_directory is not a directory: #{source_directory}" unless ::File.directory?(src)
|
|
75
61
|
|
|
76
62
|
ensure_root
|
|
63
|
+
# Check root is writable before we do any copy (T015 / edge case).
|
|
77
64
|
unless ::File.writable?(root)
|
|
78
65
|
raise ArgumentError, "root directory is not writable: #{root}"
|
|
79
66
|
end
|
|
80
67
|
|
|
81
68
|
target = ::File.join(root, site_name)
|
|
82
|
-
|
|
83
|
-
temp = ::File.join(root, temp_name)
|
|
69
|
+
temp = ::File.join(root, ".tmp_#{site_name}_#{Process.pid}_#{rand(1_000_000)}")
|
|
84
70
|
FileUtils.mkdir_p(temp)
|
|
85
71
|
FileUtils.cp_r(::File.join(src, "."), temp)
|
|
86
|
-
FileUtils.mkdir_p(::File.dirname(target))
|
|
87
72
|
FileUtils.rm_rf(target)
|
|
88
73
|
FileUtils.mv(temp, target)
|
|
89
74
|
regenerate_root_index
|
|
@@ -160,20 +145,6 @@ module Rakit
|
|
|
160
145
|
true
|
|
161
146
|
end
|
|
162
147
|
|
|
163
|
-
# Open a path on the static server in the default browser. Ensures server is running (starts if not), builds URL, launches browser.
|
|
164
|
-
# @param relative_path [String] URL path (e.g. "/louparslow/rakit/" or "louparslow/rakit"); normalized to one leading slash.
|
|
165
|
-
# @return [true] on success.
|
|
166
|
-
# @raise [RuntimeError] if browser cannot be launched (e.g. headless), with message e.g. "Could not launch browser; display required?"
|
|
167
|
-
def self.view(relative_path)
|
|
168
|
-
start unless running?
|
|
169
|
-
path = relative_path.to_s.strip
|
|
170
|
-
path = "/#{path}" unless path.empty? || path.start_with?("/")
|
|
171
|
-
path = "/" if path.empty?
|
|
172
|
-
url = "http://#{host}:#{port}#{path}"
|
|
173
|
-
launch_browser(url)
|
|
174
|
-
true
|
|
175
|
-
end
|
|
176
|
-
|
|
177
148
|
# T017: Write root/index.html listing all site subdirectories alphabetically with links.
|
|
178
149
|
def self.regenerate_root_index
|
|
179
150
|
entries = Dir.entries(root).sort.select do |e|
|
|
@@ -207,20 +178,6 @@ module Rakit
|
|
|
207
178
|
rescue Errno::EADDRINUSE
|
|
208
179
|
true
|
|
209
180
|
end
|
|
210
|
-
|
|
211
|
-
def launch_browser(url)
|
|
212
|
-
cmd = case ::RbConfig::CONFIG["host_os"]
|
|
213
|
-
when /darwin|mac os/i
|
|
214
|
-
["open", url]
|
|
215
|
-
when /mswin|mingw|windows/i
|
|
216
|
-
["cmd", "/c", "start", "", url]
|
|
217
|
-
else
|
|
218
|
-
["xdg-open", url]
|
|
219
|
-
end
|
|
220
|
-
system(*cmd, out: ::File::NULL, err: ::File::NULL)
|
|
221
|
-
return true if $?.success?
|
|
222
|
-
raise "Could not launch browser; display required?"
|
|
223
|
-
end
|
|
224
181
|
end
|
|
225
182
|
end
|
|
226
183
|
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "generated/word_cloud_pb"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "open3"
|
|
6
|
+
|
|
7
|
+
module Rakit
|
|
8
|
+
module WordCloud
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :wcloud_path, :auto_install, :working_directory
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
self.auto_install = false
|
|
14
|
+
|
|
15
|
+
# Validate path has no traversal. If relative, must be under base_dir; absolute paths allowed.
|
|
16
|
+
def self.path_safe?(path, base_dir)
|
|
17
|
+
return false if path.nil? || path.to_s.strip.empty?
|
|
18
|
+
p = path.to_s.strip
|
|
19
|
+
return false if p.include?("..")
|
|
20
|
+
base = ::File.expand_path(base_dir.to_s)
|
|
21
|
+
expanded = ::File.expand_path(p, base)
|
|
22
|
+
# Absolute path: allow (no traversal)
|
|
23
|
+
return true if p.start_with?("/") || (p.size >= 2 && p[1] == ":")
|
|
24
|
+
expanded == base || expanded.start_with?(base + ::File::SEPARATOR)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Validate text file path: exists, readable file, no traversal. base_dir = working_directory or Dir.pwd.
|
|
28
|
+
def self.validate_text_file_path(path, base_dir)
|
|
29
|
+
base = ::File.expand_path(base_dir.to_s)
|
|
30
|
+
expanded = ::File.expand_path(path.to_s, base)
|
|
31
|
+
return [false, "Path contains traversal (..) or is outside working directory"] unless path_safe?(path, base_dir)
|
|
32
|
+
return [false, "File does not exist: #{path}"] unless ::File.exist?(expanded)
|
|
33
|
+
return [false, "Not a file: #{path}"] unless ::File.file?(expanded)
|
|
34
|
+
return [false, "File not readable: #{path}"] unless ::File.readable?(expanded)
|
|
35
|
+
[true, expanded]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Validate output image path: parent creatable/writable, no traversal.
|
|
39
|
+
def self.validate_output_path(path, base_dir)
|
|
40
|
+
base = ::File.expand_path(base_dir.to_s)
|
|
41
|
+
expanded = ::File.expand_path(path.to_s, base)
|
|
42
|
+
return [false, "Path contains traversal (..) or is outside working directory"] unless path_safe?(path, base_dir)
|
|
43
|
+
parent = ::File.dirname(expanded)
|
|
44
|
+
begin
|
|
45
|
+
FileUtils.mkdir_p(parent) unless ::File.directory?(parent)
|
|
46
|
+
rescue SystemCallError => e
|
|
47
|
+
return [false, "Cannot create output directory: #{e.message}"]
|
|
48
|
+
end
|
|
49
|
+
return [false, "Output path not writable: #{path}"] unless ::File.writable?(parent)
|
|
50
|
+
[true, expanded]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Validate optional file path (mask, font, exclude-words): if present, must exist and be readable.
|
|
54
|
+
def self.validate_optional_file_path(path, base_dir, label)
|
|
55
|
+
return [true, nil] if path.nil? || path.to_s.strip.empty?
|
|
56
|
+
base = ::File.expand_path(base_dir.to_s)
|
|
57
|
+
expanded = ::File.expand_path(path.to_s, base)
|
|
58
|
+
return [false, "#{label}: path contains traversal or is outside working directory"] unless path_safe?(path, base_dir)
|
|
59
|
+
return [false, "#{label} file does not exist: #{path}"] unless ::File.exist?(expanded)
|
|
60
|
+
return [false, "#{label} not a file: #{path}"] unless ::File.file?(expanded)
|
|
61
|
+
return [false, "#{label} not readable: #{path}"] unless ::File.readable?(expanded)
|
|
62
|
+
[true, expanded]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Resolve wcloud: config path, then ENV RAKIT_WCLOUD_BINARY, then which wcloud.
|
|
66
|
+
def self.resolve_wcloud(config = {})
|
|
67
|
+
path = config[:wcloud_path] || config["wcloud_path"] || self.wcloud_path
|
|
68
|
+
return path if path && path.to_s.strip != "" && ::File.executable?(::File.expand_path(path))
|
|
69
|
+
env_path = ENV["RAKIT_WCLOUD_BINARY"]
|
|
70
|
+
return env_path if env_path && env_path.to_s.strip != "" && ::File.executable?(::File.expand_path(env_path))
|
|
71
|
+
which("wcloud")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Resolve cargo: which cargo.
|
|
75
|
+
def self.resolve_cargo(_config = {})
|
|
76
|
+
which("cargo")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Status: resolve wcloud and cargo; no side effects. Returns ToolStatus.
|
|
80
|
+
def self.status(config = {})
|
|
81
|
+
cfg = config.is_a?(Hash) ? config : {}
|
|
82
|
+
wcloud_path = cfg[:wcloud_path] || cfg["wcloud_path"] || self.wcloud_path
|
|
83
|
+
wcloud_bin = resolve_wcloud(cfg)
|
|
84
|
+
cargo_bin = resolve_cargo(cfg)
|
|
85
|
+
Rakit::Generated::ToolStatus.new(
|
|
86
|
+
wcloud_found: !wcloud_bin.nil?,
|
|
87
|
+
wcloud_path: wcloud_bin.to_s,
|
|
88
|
+
cargo_found: !cargo_bin.nil?,
|
|
89
|
+
cargo_path: cargo_bin.to_s
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Install: run cargo install wcloud. Returns GenerateResult with success, message, stderr.
|
|
94
|
+
def self.install(config = {})
|
|
95
|
+
cfg = config.is_a?(Hash) ? config : {}
|
|
96
|
+
cargo_bin = resolve_cargo(cfg)
|
|
97
|
+
unless cargo_bin
|
|
98
|
+
return fail_result("", "cargo not found. Install Rust from https://rustup.rs/ then run: cargo install wcloud", -1)
|
|
99
|
+
end
|
|
100
|
+
_stdout, stderr, status = Open3.capture3(cargo_bin, "install", "wcloud")
|
|
101
|
+
Rakit::Generated::GenerateResult.new(
|
|
102
|
+
success: status.success?,
|
|
103
|
+
message: status.success? ? "wcloud installed or already present" : stderr.to_s.strip,
|
|
104
|
+
output_image: "",
|
|
105
|
+
exit_code: status.exitstatus,
|
|
106
|
+
stdout: "",
|
|
107
|
+
stderr: stderr.to_s
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.which(cmd)
|
|
112
|
+
exe = nil
|
|
113
|
+
ENV["PATH"].to_s.split(::File::PATH_SEPARATOR).each do |dir|
|
|
114
|
+
full = ::File.join(dir.strip, cmd)
|
|
115
|
+
if ::File.executable?(full)
|
|
116
|
+
exe = full
|
|
117
|
+
break
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
exe
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Generate word cloud. request is GenerateRequest. Optional stdin_content when text source is stdin.
|
|
124
|
+
# Returns GenerateResult. Does not leave partial output on validation or tool failure.
|
|
125
|
+
def self.generate(request, stdin_content: nil)
|
|
126
|
+
cfg = request.config || Rakit::Generated::WordCloudConfig.new
|
|
127
|
+
base_dir = (cfg.working_directory.to_s.strip != "") ? cfg.working_directory : (self.working_directory || Dir.pwd)
|
|
128
|
+
|
|
129
|
+
# Resolve text content and validate non-empty
|
|
130
|
+
content, err = resolve_text_content(request.text, base_dir, stdin_content)
|
|
131
|
+
unless content
|
|
132
|
+
return fail_result(request.output_image.to_s, err, -1)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Validate output path (creates parent if missing)
|
|
136
|
+
ok, out_path = validate_output_path(request.output_image.to_s, base_dir)
|
|
137
|
+
unless ok
|
|
138
|
+
return fail_result(request.output_image.to_s, out_path, -1)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Optional file paths (mask, font, exclude-words)
|
|
142
|
+
mask_path = request.mask_image.to_s.strip
|
|
143
|
+
if mask_path != ""
|
|
144
|
+
ok, err = validate_optional_file_path(mask_path, base_dir, "Mask image")
|
|
145
|
+
return fail_result(out_path, err, -1) unless ok
|
|
146
|
+
end
|
|
147
|
+
font_path = request.font_file.to_s.strip
|
|
148
|
+
if font_path != ""
|
|
149
|
+
ok, err = validate_optional_file_path(font_path, base_dir, "Font file")
|
|
150
|
+
return fail_result(out_path, err, -1) unless ok
|
|
151
|
+
end
|
|
152
|
+
exclude_path = request.exclude_words_file.to_s.strip
|
|
153
|
+
if exclude_path != ""
|
|
154
|
+
ok, err = validate_optional_file_path(exclude_path, base_dir, "Exclude-words file")
|
|
155
|
+
return fail_result(out_path, err, -1) unless ok
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
config_hash = { wcloud_path: cfg.wcloud_path.to_s, auto_install: cfg.auto_install }
|
|
159
|
+
wcloud_bin = resolve_wcloud(config_hash)
|
|
160
|
+
if wcloud_bin.nil? && cfg.auto_install
|
|
161
|
+
install_result = install(config_hash)
|
|
162
|
+
unless install_result.success
|
|
163
|
+
return fail_result(out_path, "wcloud not found and auto-install failed: #{install_result.message}", -1)
|
|
164
|
+
end
|
|
165
|
+
wcloud_bin = resolve_wcloud(config_hash)
|
|
166
|
+
end
|
|
167
|
+
unless wcloud_bin
|
|
168
|
+
return fail_result(out_path, "wcloud not found. Install with: cargo install wcloud", -1)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
argv = build_wcloud_argv(request, out_path, mask_path, font_path, exclude_path)
|
|
172
|
+
stdout, stderr, exit_code = run_wcloud(wcloud_bin, argv, stdin: content)
|
|
173
|
+
|
|
174
|
+
result = Rakit::Generated::GenerateResult.new(
|
|
175
|
+
success: exit_code == 0,
|
|
176
|
+
message: exit_code == 0 ? "OK" : stderr.to_s.strip,
|
|
177
|
+
output_image: out_path,
|
|
178
|
+
exit_code: exit_code,
|
|
179
|
+
stdout: stdout.to_s,
|
|
180
|
+
stderr: stderr.to_s,
|
|
181
|
+
wcloud_resolved_path: wcloud_bin
|
|
182
|
+
)
|
|
183
|
+
unless result.success
|
|
184
|
+
::File.unlink(out_path) if ::File.exist?(out_path)
|
|
185
|
+
end
|
|
186
|
+
result
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def self.fail_result(output_image, message, exit_code)
|
|
190
|
+
Rakit::Generated::GenerateResult.new(
|
|
191
|
+
success: false,
|
|
192
|
+
message: message.to_s,
|
|
193
|
+
output_image: output_image.to_s,
|
|
194
|
+
exit_code: exit_code,
|
|
195
|
+
stdout: "",
|
|
196
|
+
stderr: message.to_s
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def self.resolve_text_content(text_source, base_dir, stdin_content)
|
|
201
|
+
return [nil, "Text source is empty; provide non-empty text."] if text_source.nil?
|
|
202
|
+
|
|
203
|
+
case text_source.source
|
|
204
|
+
when :text_file
|
|
205
|
+
path = text_source.text_file.to_s.strip
|
|
206
|
+
return [nil, "Text file path is empty."] if path.empty?
|
|
207
|
+
ok, expanded = validate_text_file_path(path, base_dir)
|
|
208
|
+
return [nil, expanded] unless ok
|
|
209
|
+
content = ::File.read(expanded)
|
|
210
|
+
return [nil, "Text source is empty; provide non-empty text."] if content.to_s.strip.empty?
|
|
211
|
+
return [content, nil]
|
|
212
|
+
when :inline_text
|
|
213
|
+
content = text_source.inline_text.to_s
|
|
214
|
+
return [nil, "Text source is empty; provide non-empty text."] if content.strip.empty?
|
|
215
|
+
return [content, nil]
|
|
216
|
+
when :stdin
|
|
217
|
+
content = stdin_content.to_s
|
|
218
|
+
return [nil, "Text source is empty; provide non-empty text."] if content.strip.empty?
|
|
219
|
+
return [content, nil]
|
|
220
|
+
else
|
|
221
|
+
[nil, "Invalid text source."]
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def self.build_wcloud_argv(request, out_path, mask_path, font_path, exclude_path)
|
|
226
|
+
argv = ["-o", out_path]
|
|
227
|
+
argv += ["--width", request.width.to_s] if request.width && request.width > 0
|
|
228
|
+
argv += ["--height", request.height.to_s] if request.height && request.height > 0
|
|
229
|
+
argv += ["--seed", request.rng_seed.to_s] if request.rng_seed && request.rng_seed >= 0
|
|
230
|
+
argv += ["--mask", mask_path] if mask_path != ""
|
|
231
|
+
argv += ["--font", font_path] if font_path != ""
|
|
232
|
+
argv += ["--exclude-words", exclude_path] if exclude_path != ""
|
|
233
|
+
argv += ["--max-words", request.max_words.to_s] if request.max_words && request.max_words > 0
|
|
234
|
+
argv
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Run wcloud with argv (no shell). Optional stdin: string. Returns [stdout, stderr, exit_status].
|
|
238
|
+
def self.run_wcloud(wcloud_bin, argv, stdin: nil)
|
|
239
|
+
if stdin
|
|
240
|
+
stdout, stderr, status = Open3.capture3(wcloud_bin, *argv, stdin_data: stdin)
|
|
241
|
+
else
|
|
242
|
+
stdout, stderr, status = Open3.capture3(wcloud_bin, *argv)
|
|
243
|
+
end
|
|
244
|
+
[stdout.to_s, stderr.to_s, status.exitstatus]
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
data/lib/rakit.rb
CHANGED
|
@@ -25,8 +25,8 @@ require_relative "rakit/protobuf"
|
|
|
25
25
|
# Defer loading so rake tasks that don't need Shell (e.g. clobber) work without google-protobuf.
|
|
26
26
|
autoload :Shell, "rakit/shell"
|
|
27
27
|
require_relative "rakit/static_web_server"
|
|
28
|
-
require_relative "rakit/hugo"
|
|
29
28
|
require_relative "rakit/word_count"
|
|
29
|
+
require_relative "rakit/word_cloud"
|
|
30
30
|
require_relative "rakit/file"
|
|
31
31
|
require_relative "rakit/azure/dev_ops"
|
|
32
32
|
|
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.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rakit
|
|
@@ -71,6 +71,20 @@ dependencies:
|
|
|
71
71
|
- - "~>"
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
73
|
version: '2.0'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: kramdown
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '2.4'
|
|
81
|
+
type: :runtime
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - "~>"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '2.4'
|
|
74
88
|
- !ruby/object:Gem::Dependency
|
|
75
89
|
name: grpc-tools
|
|
76
90
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -105,25 +119,29 @@ extensions: []
|
|
|
105
119
|
extra_rdoc_files: []
|
|
106
120
|
files:
|
|
107
121
|
- exe/rakit
|
|
122
|
+
- lib/generated/azure.devops_pb.rb
|
|
108
123
|
- lib/generated/data_pb.rb
|
|
109
|
-
- lib/generated/
|
|
110
|
-
- lib/generated/rakit.example_pb.rb
|
|
124
|
+
- lib/generated/example_pb.rb
|
|
111
125
|
- lib/generated/rakit.file_pb.rb
|
|
112
|
-
- lib/generated/rakit.shell_pb.rb
|
|
113
|
-
- lib/generated/rakit.static_web_server_pb.rb
|
|
114
126
|
- lib/generated/rakit.word_count_pb.rb
|
|
127
|
+
- lib/generated/shell_pb.rb
|
|
128
|
+
- lib/generated/static_web_server_pb.rb
|
|
129
|
+
- lib/generated/word_cloud_pb.rb
|
|
115
130
|
- lib/rakit.rb
|
|
116
131
|
- lib/rakit/azure/dev_ops.rb
|
|
117
132
|
- lib/rakit/cli/file.rb
|
|
133
|
+
- lib/rakit/cli/markdown.rb
|
|
134
|
+
- lib/rakit/cli/word_cloud.rb
|
|
118
135
|
- lib/rakit/cli/word_count.rb
|
|
119
136
|
- lib/rakit/file.rb
|
|
120
137
|
- lib/rakit/gem.rb
|
|
121
138
|
- lib/rakit/git.rb
|
|
122
|
-
- lib/rakit/
|
|
139
|
+
- lib/rakit/markdown.rb
|
|
123
140
|
- lib/rakit/protobuf.rb
|
|
124
141
|
- lib/rakit/shell.rb
|
|
125
142
|
- lib/rakit/static_web_server.rb
|
|
126
143
|
- lib/rakit/task.rb
|
|
144
|
+
- lib/rakit/word_cloud.rb
|
|
127
145
|
- lib/rakit/word_count.rb
|
|
128
146
|
homepage: https://gitlab.com/gems/rakit
|
|
129
147
|
licenses:
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: rakit.azure.proto
|
|
4
|
-
|
|
5
|
-
require 'google/protobuf'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
descriptor_data = "\n\x11rakit.azure.proto\x12\x0brakit.azure\"L\n\x08Pipeline\x12\x0b\n\x03org\x18\x01 \x01(\t\x12\x0f\n\x07project\x18\x02 \x01(\t\x12\x13\n\x0bpipeline_id\x18\x03 \x01(\x05\x12\r\n\x05token\x18\x04 \x01(\t\"R\n\x18GetPipelineResultRequest\x12\'\n\x08pipeline\x18\x01 \x01(\x0b\x32\x15.rakit.azure.Pipeline\x12\r\n\x05token\x18\x02 \x01(\t\"C\n\x0ePipelineStatus\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06\x65rrors\x18\x02 \x03(\t\x12\x10\n\x08warnings\x18\x03 \x03(\t\"8\n\x0ePipelineResult\x12&\n\x04runs\x18\x01 \x03(\x0b\x32\x18.rakit.azure.PipelineRun\"\x97\x01\n\x0bPipelineRun\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x0e\n\x06result\x18\x04 \x01(\t\x12\x14\n\x0c\x63reated_date\x18\x05 \x01(\t\x12\x15\n\rfinished_date\x18\x06 \x01(\t\x12\"\n\x06stages\x18\x07 \x03(\x0b\x32\x12.rakit.azure.Stage\"&\n\x05Issue\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\"G\n\x03Job\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\"\n\x06issues\x18\x03 \x03(\x0b\x32\x12.rakit.azure.Issue\"i\n\x05Stage\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\x1e\n\x04jobs\x18\x03 \x03(\x0b\x32\x10.rakit.azure.Job\x12\"\n\x06issues\x18\x04 \x03(\x0b\x32\x12.rakit.azure.Issue\"`\n\x0eTimelineRecord\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0e\n\x06result\x18\x03 \x01(\t\x12\"\n\x06issues\x18\x04 \x03(\x0b\x32\x12.rakit.azure.Issue\"\xa8\x01\n\x14PipelineResultDetail\x12\x12\n\nsuccessful\x18\x01 \x01(\x08\x12\x0e\n\x06\x65rrors\x18\x02 \x01(\t\x12\x10\n\x08warnings\x18\x03 \x01(\t\x12%\n\x03run\x18\x04 \x01(\x0b\x32\x18.rakit.azure.PipelineRun\x12\x33\n\x0e\x66\x61iled_records\x18\x05 \x03(\x0b\x32\x1b.rakit.azure.TimelineRecord2i\n\x0ePipelineServer\x12W\n\x11GetPipelineResult\x12%.rakit.azure.GetPipelineResultRequest\x1a\x1b.rakit.azure.PipelineResultB\x0f\xea\x02\x0cRakit::Azureb\x06proto3"
|
|
9
|
-
|
|
10
|
-
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
-
pool.add_serialized_file(descriptor_data)
|
|
12
|
-
|
|
13
|
-
module Rakit
|
|
14
|
-
module Azure
|
|
15
|
-
Pipeline = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Pipeline").msgclass
|
|
16
|
-
GetPipelineResultRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.GetPipelineResultRequest").msgclass
|
|
17
|
-
PipelineStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineStatus").msgclass
|
|
18
|
-
PipelineResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineResult").msgclass
|
|
19
|
-
PipelineRun = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineRun").msgclass
|
|
20
|
-
Issue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Issue").msgclass
|
|
21
|
-
Job = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Job").msgclass
|
|
22
|
-
Stage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.Stage").msgclass
|
|
23
|
-
TimelineRecord = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.TimelineRecord").msgclass
|
|
24
|
-
PipelineResultDetail = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.azure.PipelineResultDetail").msgclass
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: rakit.shell.proto
|
|
4
|
-
|
|
5
|
-
require 'google/protobuf'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
descriptor_data = "\n\x11rakit.shell.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
|
data/lib/rakit/hugo.rb
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
|
|
5
|
-
module Rakit
|
|
6
|
-
# Build Hugo static sites from source. Used by publish_docs and tests.
|
|
7
|
-
# Contract: specs/008-hugo-docs-site/contracts/ruby-api.md
|
|
8
|
-
module Hugo
|
|
9
|
-
class << self
|
|
10
|
-
attr_accessor :hugo_path
|
|
11
|
-
|
|
12
|
-
def hugo_path
|
|
13
|
-
@hugo_path ||= "hugo"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# @param site_dir [String] path to Hugo source (must exist and be a directory)
|
|
17
|
-
# @param out_dir [String] path for build output
|
|
18
|
-
# @return [true] on success
|
|
19
|
-
# @return [false] on failure (Hugo not found, build failed, or invalid site_dir)
|
|
20
|
-
def build(site_dir:, out_dir:)
|
|
21
|
-
site_dir = ::File.expand_path(site_dir)
|
|
22
|
-
out_dir = ::File.expand_path(out_dir)
|
|
23
|
-
return false unless ::File.directory?(site_dir)
|
|
24
|
-
return false if ::File.file?(site_dir)
|
|
25
|
-
|
|
26
|
-
FileUtils.mkdir_p(out_dir)
|
|
27
|
-
success = system(hugo_path, "-s", site_dir, "-d", out_dir, out: $stdout, err: $stderr)
|
|
28
|
-
return false unless success
|
|
29
|
-
return false unless ::File.directory?(out_dir) && (Dir.entries(out_dir) - %w[. ..]).any?
|
|
30
|
-
|
|
31
|
-
true
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# @param site_dir [String] path to check
|
|
35
|
-
# @return [Boolean] true if config.toml or config.yaml exists under site_dir
|
|
36
|
-
def valid_site?(site_dir)
|
|
37
|
-
dir = ::File.expand_path(site_dir)
|
|
38
|
-
return false unless ::File.directory?(dir)
|
|
39
|
-
::File.file?(::File.join(dir, "config.toml")) || ::File.file?(::File.join(dir, "config.yaml"))
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|