rakit 0.1.5 → 0.1.7
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 +17 -5
- data/lib/generated/data_pb.rb +2 -1
- data/lib/generated/word_cloud_pb.rb +21 -0
- data/lib/rakit/cli/file.rb +163 -0
- data/lib/rakit/cli/markdown.rb +90 -0
- data/lib/rakit/file.rb +181 -0
- data/lib/rakit/gem.rb +79 -6
- data/lib/rakit/markdown.rb +157 -0
- data/lib/rakit/static_web_server.rb +1 -0
- data/lib/rakit.rb +1 -0
- metadata +28 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f146d5f635716871e7960854ae13c5f64dcf353d50b7b57a808f81258580972
|
|
4
|
+
data.tar.gz: 80f4539d5cb9e02c506789a3ebb4862a280eb750aea7b88449dbcf54222cf6f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe9c41e4505e458900c66bfa5fed2041fed850f568fdcb44cf0843468278a5320317d84d6b97c1b76bc303f683f79ca1b9994a69224574599c8ec5c216b00925
|
|
7
|
+
data.tar.gz: fc4fd21d2dbefede571552edc32d3f8e62e323ed008e09d7831dd282ad216aca8ba7221af3515f56b2d76897898e40f295d075dbbfb058bb2749d32aeba54507
|
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,20 +12,25 @@ 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
20
|
$stderr.puts " static-web-server <start|stop|running|publish> [options] [args]"
|
|
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"
|
|
21
|
+
$stderr.puts " word-cloud <status|install|generate> [options] [args]"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def main(argv = ARGV)
|
|
25
25
|
return usage_stderr("Expected subcommand.") if argv.empty?
|
|
26
26
|
|
|
27
27
|
sub = argv[0]
|
|
28
|
+
if sub == "markdown"
|
|
29
|
+
argv.shift
|
|
30
|
+
require "rakit/cli/markdown"
|
|
31
|
+
return Rakit::CLI::Markdown.run(argv)
|
|
32
|
+
end
|
|
33
|
+
|
|
28
34
|
if sub == "file"
|
|
29
35
|
argv.shift
|
|
30
36
|
require "rakit/cli/file"
|
|
@@ -37,6 +43,12 @@ def main(argv = ARGV)
|
|
|
37
43
|
return Rakit::CLI::WordCount.run(argv)
|
|
38
44
|
end
|
|
39
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
|
+
|
|
40
52
|
if sub != "static-web-server"
|
|
41
53
|
usage_stderr("Unknown subcommand: #{sub}")
|
|
42
54
|
return 1
|
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
|
|
|
@@ -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
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "google/protobuf"
|
|
5
|
+
require "rakit/file"
|
|
6
|
+
|
|
7
|
+
module Rakit
|
|
8
|
+
module CLI
|
|
9
|
+
module File
|
|
10
|
+
class << self
|
|
11
|
+
# @param argv [Array<String>] arguments after "file"
|
|
12
|
+
# @return [Integer] exit code
|
|
13
|
+
def run(argv)
|
|
14
|
+
return 1 if argv.empty?
|
|
15
|
+
sub = argv.shift
|
|
16
|
+
case sub
|
|
17
|
+
when "list" then run_list(argv)
|
|
18
|
+
when "copy" then run_copy(argv)
|
|
19
|
+
else
|
|
20
|
+
$stderr.puts "Unknown command: #{sub}. Use 'list' or 'copy'."
|
|
21
|
+
$stderr.puts " rakit file list <directory> [--recursive] [--include-hidden] [--format console|json|proto-json]"
|
|
22
|
+
$stderr.puts " rakit file copy <source> <destination> [--overwrite] [--create-directories] [--follow-symlinks] [--format ...]"
|
|
23
|
+
1
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def run_list(argv)
|
|
30
|
+
opts = parse_list_argv(argv)
|
|
31
|
+
return 1 if opts[:error]
|
|
32
|
+
req = Rakit::File::ListRequest.new(
|
|
33
|
+
directory: opts[:directory],
|
|
34
|
+
recursive: opts[:recursive],
|
|
35
|
+
include_hidden: opts[:include_hidden],
|
|
36
|
+
config: Rakit::File::FileConfig.new(follow_symlinks: opts[:follow_symlinks])
|
|
37
|
+
)
|
|
38
|
+
result = Rakit::File.list(req)
|
|
39
|
+
render_list_result(result, opts[:format])
|
|
40
|
+
result.exit_code
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_list_argv(argv)
|
|
44
|
+
opts = { format: "console", recursive: false, include_hidden: false, follow_symlinks: false, directory: nil }
|
|
45
|
+
args = argv.dup
|
|
46
|
+
while (arg = args.shift)
|
|
47
|
+
case arg
|
|
48
|
+
when "--recursive" then opts[:recursive] = true
|
|
49
|
+
when "--include-hidden" then opts[:include_hidden] = true
|
|
50
|
+
when "--follow-symlinks" then opts[:follow_symlinks] = true
|
|
51
|
+
when "--format" then opts[:format] = args.shift || "console"
|
|
52
|
+
when /^--/
|
|
53
|
+
$stderr.puts "Unknown option: #{arg}"
|
|
54
|
+
opts[:error] = true
|
|
55
|
+
else
|
|
56
|
+
opts[:directory] = arg if opts[:directory].nil?
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
unless opts[:directory] && !opts[:directory].empty?
|
|
60
|
+
$stderr.puts "list requires <directory>"
|
|
61
|
+
opts[:error] = true
|
|
62
|
+
end
|
|
63
|
+
opts
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def run_copy(argv)
|
|
67
|
+
opts = parse_copy_argv(argv)
|
|
68
|
+
return 1 if opts[:error]
|
|
69
|
+
req = Rakit::File::CopyRequest.new(
|
|
70
|
+
source: opts[:source],
|
|
71
|
+
destination: opts[:destination],
|
|
72
|
+
config: Rakit::File::FileConfig.new(
|
|
73
|
+
overwrite: opts[:overwrite],
|
|
74
|
+
create_directories: opts[:create_directories],
|
|
75
|
+
follow_symlinks: opts[:follow_symlinks]
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
result = Rakit::File.copy(req)
|
|
79
|
+
render_copy_result(result, opts[:format])
|
|
80
|
+
result.exit_code
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def parse_copy_argv(argv)
|
|
84
|
+
opts = { format: "console", overwrite: false, create_directories: false, follow_symlinks: false, source: nil, destination: nil }
|
|
85
|
+
args = argv.dup
|
|
86
|
+
while (arg = args.shift)
|
|
87
|
+
case arg
|
|
88
|
+
when "--overwrite" then opts[:overwrite] = true
|
|
89
|
+
when "--create-directories" then opts[:create_directories] = true
|
|
90
|
+
when "--follow-symlinks" then opts[:follow_symlinks] = true
|
|
91
|
+
when "--format" then opts[:format] = args.shift || "console"
|
|
92
|
+
when /^--/
|
|
93
|
+
$stderr.puts "Unknown option: #{arg}"
|
|
94
|
+
opts[:error] = true
|
|
95
|
+
else
|
|
96
|
+
if opts[:source].nil?
|
|
97
|
+
opts[:source] = arg
|
|
98
|
+
elsif opts[:destination].nil?
|
|
99
|
+
opts[:destination] = arg
|
|
100
|
+
else
|
|
101
|
+
$stderr.puts "Unexpected argument: #{arg}"
|
|
102
|
+
opts[:error] = true
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
unless opts[:source] && opts[:destination]
|
|
107
|
+
$stderr.puts "copy requires <source> and <destination>"
|
|
108
|
+
opts[:error] = true
|
|
109
|
+
end
|
|
110
|
+
opts
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def render_list_result(result, format)
|
|
114
|
+
$stderr.puts result.stderr if result.stderr && !result.stderr.empty? && !result.success
|
|
115
|
+
case format
|
|
116
|
+
when "json"
|
|
117
|
+
puts list_result_to_json(result)
|
|
118
|
+
when "proto-json"
|
|
119
|
+
puts Google::Protobuf.encode_json(result)
|
|
120
|
+
else
|
|
121
|
+
result.entries.each do |e|
|
|
122
|
+
puts "#{e.path}\t#{e.name}\t#{e.is_directory ? 'dir' : 'file'}"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def list_result_to_json(result)
|
|
128
|
+
h = {
|
|
129
|
+
success: result.success,
|
|
130
|
+
message: result.message.to_s,
|
|
131
|
+
entries: result.entries.map { |e| { path: e.path, name: e.name, is_directory: e.is_directory, is_symlink: e.is_symlink, size_bytes: e.size_bytes, modified_unix_ms: e.modified_unix_ms } },
|
|
132
|
+
exit_code: result.exit_code
|
|
133
|
+
}
|
|
134
|
+
JSON.generate(h)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def render_copy_result(result, format)
|
|
138
|
+
$stderr.puts result.stderr if result.stderr && !result.stderr.empty? && !result.success
|
|
139
|
+
case format
|
|
140
|
+
when "json"
|
|
141
|
+
puts copy_result_to_json(result)
|
|
142
|
+
when "proto-json"
|
|
143
|
+
puts Google::Protobuf.encode_json(result)
|
|
144
|
+
else
|
|
145
|
+
puts "Copied #{result.source} → #{result.destination}" if result.success
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def copy_result_to_json(result)
|
|
150
|
+
h = {
|
|
151
|
+
success: result.success,
|
|
152
|
+
message: result.message.to_s,
|
|
153
|
+
source: result.source.to_s,
|
|
154
|
+
destination: result.destination.to_s,
|
|
155
|
+
exit_code: result.exit_code,
|
|
156
|
+
bytes_copied: result.bytes_copied
|
|
157
|
+
}
|
|
158
|
+
JSON.generate(h)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -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
|
data/lib/rakit/file.rb
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "generated/rakit.file_pb"
|
|
5
|
+
|
|
6
|
+
module Rakit
|
|
7
|
+
# Protobuf-first file operations (list directory, copy file). See specs/005-file-ops/contracts/ruby-api.md.
|
|
8
|
+
module File
|
|
9
|
+
class << self
|
|
10
|
+
# @param request [Rakit::File::ListRequest]
|
|
11
|
+
# @return [Rakit::File::ListResult]
|
|
12
|
+
def list(request)
|
|
13
|
+
dir_path = normalize_path(request.directory.to_s)
|
|
14
|
+
if dir_path.nil?
|
|
15
|
+
return error_list_result("Empty or invalid directory path", 1)
|
|
16
|
+
end
|
|
17
|
+
unless ::File.exist?(dir_path)
|
|
18
|
+
return error_list_result("Directory does not exist: #{dir_path}", 1)
|
|
19
|
+
end
|
|
20
|
+
unless ::File.directory?(dir_path)
|
|
21
|
+
return error_list_result("Not a directory: #{dir_path}", 1)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
include_hidden = request.include_hidden
|
|
25
|
+
follow_symlinks = request.config&.follow_symlinks == true
|
|
26
|
+
entries = request.recursive ? list_entries_recursive(dir_path, include_hidden, follow_symlinks) : list_entries_one_dir(dir_path, include_hidden, follow_symlinks)
|
|
27
|
+
|
|
28
|
+
ListResult.new(
|
|
29
|
+
success: true,
|
|
30
|
+
message: "",
|
|
31
|
+
entries: entries,
|
|
32
|
+
exit_code: 0,
|
|
33
|
+
stderr: ""
|
|
34
|
+
)
|
|
35
|
+
rescue => e
|
|
36
|
+
error_list_result("#{e.message}", 1)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param request [Rakit::File::CopyRequest]
|
|
40
|
+
# @return [Rakit::File::CopyResult]
|
|
41
|
+
def copy(request)
|
|
42
|
+
config = request.config || FileConfig.new
|
|
43
|
+
src = normalize_path(request.source.to_s)
|
|
44
|
+
dest_raw = normalize_path(request.destination.to_s)
|
|
45
|
+
|
|
46
|
+
if src.nil? || dest_raw.nil?
|
|
47
|
+
return error_copy_result(request.source.to_s, request.destination.to_s, "Empty or invalid path", 1)
|
|
48
|
+
end
|
|
49
|
+
unless ::File.exist?(src)
|
|
50
|
+
return error_copy_result(src, dest_raw, "Source does not exist: #{src}", 1)
|
|
51
|
+
end
|
|
52
|
+
if ::File.directory?(src)
|
|
53
|
+
return error_copy_result(src, dest_raw, "Source is a directory (MVP: file only)", 1)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
dest_path = resolve_copy_destination(src, dest_raw)
|
|
57
|
+
parent = ::File.dirname(dest_path)
|
|
58
|
+
|
|
59
|
+
unless ::File.directory?(parent)
|
|
60
|
+
if config.create_directories
|
|
61
|
+
FileUtils.mkdir_p(parent)
|
|
62
|
+
else
|
|
63
|
+
return error_copy_result(src, dest_path, "Parent directory does not exist: #{parent}", 1)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if ::File.file?(dest_path) && !config.overwrite
|
|
68
|
+
return error_copy_result(src, dest_path, "Destination file exists (use overwrite to replace)", 1)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
bytes = perform_copy(src, dest_path, config)
|
|
72
|
+
CopyResult.new(
|
|
73
|
+
success: true,
|
|
74
|
+
message: "",
|
|
75
|
+
source: src,
|
|
76
|
+
destination: dest_path,
|
|
77
|
+
exit_code: 0,
|
|
78
|
+
stderr: "",
|
|
79
|
+
bytes_copied: bytes || 0
|
|
80
|
+
)
|
|
81
|
+
rescue => e
|
|
82
|
+
error_copy_result(request.source.to_s, request.destination.to_s, e.message, 1)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# T004: Path normalization. Returns expanded absolute path or nil if nil/empty.
|
|
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 = base_dir || Dir.pwd
|
|
93
|
+
::File.expand_path(s, base)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def error_list_result(message, exit_code)
|
|
97
|
+
ListResult.new(success: false, message: message, entries: [], exit_code: exit_code, stderr: message)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def error_copy_result(source, destination, message, exit_code)
|
|
101
|
+
CopyResult.new(success: false, message: message, source: source.to_s, destination: destination.to_s, exit_code: exit_code, stderr: message)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# T005: List entries for one directory; sort by name; filter hidden; best-effort metadata.
|
|
105
|
+
def list_entries_one_dir(dir_path, include_hidden, follow_symlinks)
|
|
106
|
+
names = Dir.children(dir_path)
|
|
107
|
+
names.reject! { |n| n.start_with?(".") } unless include_hidden
|
|
108
|
+
names.sort!
|
|
109
|
+
names.map { |name| file_entry_for(::File.join(dir_path, name), name, follow_symlinks) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def file_entry_for(abs_path, name, follow_symlinks)
|
|
113
|
+
stat = follow_symlinks ? (::File.stat(abs_path) rescue nil) : (::File.lstat(abs_path) rescue nil)
|
|
114
|
+
is_symlink = ::File.symlink?(abs_path)
|
|
115
|
+
is_dir = stat ? stat.directory? : false
|
|
116
|
+
size_bytes = (stat && stat.file?) ? (stat.size rescue 0) : 0
|
|
117
|
+
modified_unix_ms = stat ? (stat.mtime.to_f * 1000).to_i : 0
|
|
118
|
+
FileEntry.new(
|
|
119
|
+
path: abs_path,
|
|
120
|
+
name: name,
|
|
121
|
+
is_directory: is_dir,
|
|
122
|
+
is_symlink: is_symlink,
|
|
123
|
+
size_bytes: size_bytes,
|
|
124
|
+
modified_unix_ms: modified_unix_ms
|
|
125
|
+
)
|
|
126
|
+
rescue
|
|
127
|
+
FileEntry.new(path: abs_path, name: name, is_directory: false, is_symlink: false, size_bytes: 0, modified_unix_ms: 0)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# T006: Depth-first recursive list; children at each level sorted by name.
|
|
131
|
+
def list_entries_recursive(dir_path, include_hidden, follow_symlinks)
|
|
132
|
+
entries = list_entries_one_dir(dir_path, include_hidden, follow_symlinks)
|
|
133
|
+
result = []
|
|
134
|
+
entries.each do |entry|
|
|
135
|
+
result << entry
|
|
136
|
+
if entry.is_directory
|
|
137
|
+
result.concat(list_entries_recursive(entry.path, include_hidden, follow_symlinks))
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
result
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Resolve destination: if existing directory, copy into it with basename(source); else file path.
|
|
144
|
+
def resolve_copy_destination(source, destination)
|
|
145
|
+
return ::File.join(destination, ::File.basename(source)) if ::File.exist?(destination) && ::File.directory?(destination)
|
|
146
|
+
destination
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# T007: Atomic copy where possible (temp then rename); respect follow_symlinks for source.
|
|
150
|
+
def perform_copy(source, dest_path, config)
|
|
151
|
+
if !config.follow_symlinks && ::File.symlink?(source)
|
|
152
|
+
# Copy symlink itself: create new symlink with same target
|
|
153
|
+
target = ::File.readlink(source)
|
|
154
|
+
::File.delete(dest_path) if ::File.exist?(dest_path)
|
|
155
|
+
::File.symlink(target, dest_path)
|
|
156
|
+
return 0 # best-effort bytes
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
content = ::File.binread(source)
|
|
160
|
+
dest_dir = ::File.dirname(dest_path)
|
|
161
|
+
temp_path = ::File.join(dest_dir, ".rakit_copy_#{Process.pid}_#{object_id}_#{::File.basename(dest_path)}")
|
|
162
|
+
bytes = nil
|
|
163
|
+
begin
|
|
164
|
+
::File.write(temp_path, content)
|
|
165
|
+
bytes = content.bytesize
|
|
166
|
+
::File.rename(temp_path, dest_path)
|
|
167
|
+
rescue Errno::EXDEV, Errno::EPERM
|
|
168
|
+
# Cross-filesystem or rename not allowed: fall back to overwrite
|
|
169
|
+
::File.write(dest_path, content)
|
|
170
|
+
bytes = content.bytesize
|
|
171
|
+
::File.delete(temp_path) if ::File.exist?(temp_path)
|
|
172
|
+
ensure
|
|
173
|
+
::File.delete(temp_path) if ::File.exist?(temp_path)
|
|
174
|
+
end
|
|
175
|
+
bytes
|
|
176
|
+
rescue => e
|
|
177
|
+
raise "Failed to copy: #{e.message}"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
data/lib/rakit/gem.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "open3"
|
|
4
6
|
require "rubygems/package"
|
|
5
7
|
|
|
6
8
|
module Rakit
|
|
@@ -44,9 +46,8 @@ module Rakit
|
|
|
44
46
|
raise "Gem not found: #{gem_path}. Run rake package first." unless ::File.file?(gem_path)
|
|
45
47
|
|
|
46
48
|
base = ::File.basename(gem_path, ".gem")
|
|
47
|
-
|
|
48
|
-
version
|
|
49
|
-
name = parts.join("-")
|
|
49
|
+
name, version = parse_gem_basename(base)
|
|
50
|
+
raise "Could not parse name/version from #{base}.gem" unless name && version
|
|
50
51
|
|
|
51
52
|
if version_published?(name, version)
|
|
52
53
|
warn "publish: Version #{version} of #{name} is already published on rubygems.org. Skipping push. Bump the version in the gemspec to publish again."
|
|
@@ -57,14 +58,86 @@ module Rakit
|
|
|
57
58
|
raise "gem push failed" unless success
|
|
58
59
|
end
|
|
59
60
|
|
|
61
|
+
# Parse "name-version" basename (no .gem) into [name, version].
|
|
62
|
+
# Version is the last hyphen-separated segment that looks like a version (e.g. 0.1.5).
|
|
63
|
+
def self.parse_gem_basename(base)
|
|
64
|
+
# Match name (may contain hyphens) and version (digits and dots, optional pre-release suffix).
|
|
65
|
+
m = base.match(/\A(.+)-(\d+(?:\.\d+)*(?:\.\w+)?)\z/)
|
|
66
|
+
m ? [m[1], m[2]] : nil
|
|
67
|
+
end
|
|
68
|
+
|
|
60
69
|
def self.version_published?(name, version)
|
|
70
|
+
begin
|
|
71
|
+
return true if version_published_gem_list?(name, version)
|
|
72
|
+
rescue StandardError
|
|
73
|
+
# try API fallbacks
|
|
74
|
+
end
|
|
75
|
+
begin
|
|
76
|
+
return true if version_published_v2?(name, version)
|
|
77
|
+
rescue StandardError
|
|
78
|
+
# try v1 fallback
|
|
79
|
+
end
|
|
80
|
+
begin
|
|
81
|
+
return true if version_published_v1?(name, version)
|
|
82
|
+
rescue StandardError
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Run `gem list NAME --remote` and check if version appears in the output.
|
|
89
|
+
def self.version_published_gem_list?(name, version)
|
|
90
|
+
out, err, status = Open3.capture3("gem", "list", name, "--remote")
|
|
91
|
+
return false unless status.success?
|
|
92
|
+
# Output format: "name (1.0.0, 0.9.0)" or "name (1.0.0)"
|
|
93
|
+
combined = "#{out}#{err}"
|
|
94
|
+
combined.each_line do |line|
|
|
95
|
+
next unless line.include?(name)
|
|
96
|
+
if line =~ /\s*#{Regexp.escape(name)}\s*\((.*)\)/
|
|
97
|
+
versions = Regexp.last_match(1).split(",").map(&:strip)
|
|
98
|
+
return true if versions.include?(version)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# GET /api/v2/rubygems/{name}/versions/{version}.json (follows redirects)
|
|
105
|
+
def self.version_published_v2?(name, version)
|
|
61
106
|
require "net/http"
|
|
62
107
|
require "uri"
|
|
63
108
|
uri = URI("https://rubygems.org/api/v2/rubygems/#{URI::DEFAULT_PARSER.escape(name)}/versions/#{URI::DEFAULT_PARSER.escape(version)}.json")
|
|
64
|
-
response =
|
|
109
|
+
response = http_get_following_redirects(uri)
|
|
65
110
|
response.is_a?(Net::HTTPSuccess)
|
|
66
|
-
|
|
67
|
-
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# GET /api/v1/versions/{name}.json and check if version is in the list
|
|
114
|
+
def self.version_published_v1?(name, version)
|
|
115
|
+
require "net/http"
|
|
116
|
+
require "json"
|
|
117
|
+
require "uri"
|
|
118
|
+
uri = URI("https://rubygems.org/api/v1/versions/#{URI::DEFAULT_PARSER.escape(name)}.json")
|
|
119
|
+
response = http_get_following_redirects(uri)
|
|
120
|
+
return false unless response.is_a?(Net::HTTPSuccess)
|
|
121
|
+
list = JSON.parse(response.body)
|
|
122
|
+
list.is_a?(Array) && list.any? { |h| h["number"] == version }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.http_get_following_redirects(uri, limit: 5)
|
|
126
|
+
raise ArgumentError, "redirect limit exceeded" if limit <= 0
|
|
127
|
+
require "net/http"
|
|
128
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 10) do |http|
|
|
129
|
+
request = Net::HTTP::Get.new(uri)
|
|
130
|
+
request["User-Agent"] = "rakit (https://rubygems.org/gems/rakit)"
|
|
131
|
+
http.request(request)
|
|
132
|
+
end
|
|
133
|
+
case response
|
|
134
|
+
when Net::HTTPRedirection
|
|
135
|
+
location = response["location"]
|
|
136
|
+
next_uri = location.match?(/\Ahttps?:\/\//) ? URI(location) : URI.join(uri, location)
|
|
137
|
+
http_get_following_redirects(next_uri, limit: limit - 1)
|
|
138
|
+
else
|
|
139
|
+
response
|
|
140
|
+
end
|
|
68
141
|
end
|
|
69
142
|
end
|
|
70
143
|
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
|
|
@@ -102,6 +102,7 @@ module Rakit
|
|
|
102
102
|
root_path = ::File.expand_path(root)
|
|
103
103
|
pid = spawn(bin, root_path, "--port", p.to_s, out: ::File::NULL, err: ::File::NULL)
|
|
104
104
|
Process.detach(pid)
|
|
105
|
+
FileUtils.mkdir_p(::File.dirname(pid_file_path))
|
|
105
106
|
::File.write(pid_file_path, pid.to_s)
|
|
106
107
|
true
|
|
107
108
|
end
|
data/lib/rakit.rb
CHANGED
|
@@ -26,6 +26,7 @@ require_relative "rakit/protobuf"
|
|
|
26
26
|
autoload :Shell, "rakit/shell"
|
|
27
27
|
require_relative "rakit/static_web_server"
|
|
28
28
|
require_relative "rakit/word_count"
|
|
29
|
+
require_relative "rakit/word_cloud"
|
|
29
30
|
require_relative "rakit/file"
|
|
30
31
|
require_relative "rakit/azure/dev_ops"
|
|
31
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.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rakit
|
|
@@ -41,16 +41,22 @@ dependencies:
|
|
|
41
41
|
name: google-protobuf
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
46
|
version: '3.25'
|
|
47
|
+
- - "<"
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '6'
|
|
47
50
|
type: :runtime
|
|
48
51
|
prerelease: false
|
|
49
52
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
53
|
requirements:
|
|
51
|
-
- - "
|
|
54
|
+
- - ">="
|
|
52
55
|
- !ruby/object:Gem::Version
|
|
53
56
|
version: '3.25'
|
|
57
|
+
- - "<"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '6'
|
|
54
60
|
- !ruby/object:Gem::Dependency
|
|
55
61
|
name: rubyzip
|
|
56
62
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,6 +71,20 @@ dependencies:
|
|
|
65
71
|
- - "~>"
|
|
66
72
|
- !ruby/object:Gem::Version
|
|
67
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'
|
|
68
88
|
- !ruby/object:Gem::Dependency
|
|
69
89
|
name: grpc-tools
|
|
70
90
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -106,11 +126,16 @@ files:
|
|
|
106
126
|
- lib/generated/rakit.word_count_pb.rb
|
|
107
127
|
- lib/generated/shell_pb.rb
|
|
108
128
|
- lib/generated/static_web_server_pb.rb
|
|
129
|
+
- lib/generated/word_cloud_pb.rb
|
|
109
130
|
- lib/rakit.rb
|
|
110
131
|
- lib/rakit/azure/dev_ops.rb
|
|
132
|
+
- lib/rakit/cli/file.rb
|
|
133
|
+
- lib/rakit/cli/markdown.rb
|
|
111
134
|
- lib/rakit/cli/word_count.rb
|
|
135
|
+
- lib/rakit/file.rb
|
|
112
136
|
- lib/rakit/gem.rb
|
|
113
137
|
- lib/rakit/git.rb
|
|
138
|
+
- lib/rakit/markdown.rb
|
|
114
139
|
- lib/rakit/protobuf.rb
|
|
115
140
|
- lib/rakit/shell.rb
|
|
116
141
|
- lib/rakit/static_web_server.rb
|