rakit 0.1.5 → 0.1.6
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 +22 -1
- data/lib/generated/rakit.azure_pb.rb +26 -0
- data/lib/generated/{example_pb.rb → rakit.example_pb.rb} +2 -2
- data/lib/generated/rakit.shell_pb.rb +22 -0
- data/lib/generated/{static_web_server_pb.rb → rakit.static_web_server_pb.rb} +2 -2
- data/lib/rakit/azure/dev_ops.rb +1 -1
- data/lib/rakit/cli/file.rb +163 -0
- data/lib/rakit/file.rb +181 -0
- data/lib/rakit/gem.rb +79 -6
- data/lib/rakit/hugo.rb +43 -0
- data/lib/rakit/shell.rb +1 -1
- data/lib/rakit/static_web_server.rb +56 -12
- data/lib/rakit.rb +1 -0
- metadata +16 -7
- data/lib/generated/azure.devops_pb.rb +0 -26
- data/lib/generated/shell_pb.rb +0 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7270ecd6c8ca0f035dde641cc2c8713a9907f93fa837099b8b4bb5e96be528ac
|
|
4
|
+
data.tar.gz: eb0c02cd74eaa13bf8b9a3f4a6ac22506267c123138a0f718c9450c7f457432d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b83885ba425b9bef93ddc077a442da2ae567dd5986a7d7fc9260e3e09d5f05ff576d628c51ecdb8a70bd96766d6e9d6ec51c8fd7868014424aa537b45c41cec6
|
|
7
|
+
data.tar.gz: c520af4a3530456192f317e13f1b8f4d842d71be2eee8570a5d50047c389507288fff144536c089f10dcbd6d62ee58c5b90e506f715fbe6430dcb4502c2fac10
|
data/exe/rakit
CHANGED
|
@@ -14,17 +14,28 @@ def usage_stderr(msg = nil)
|
|
|
14
14
|
$stderr.puts " file list <directory> [--recursive] [--include-hidden] [--format console|json|proto-json]"
|
|
15
15
|
$stderr.puts " file copy <source> <destination> [--overwrite] [--create-directories] [--format ...]"
|
|
16
16
|
$stderr.puts " word-count <file>|--stdin --json-keys [--format console|json|proto-json] [options]"
|
|
17
|
-
$stderr.puts " static-web-server <start|stop|running|publish> [options] [args]"
|
|
17
|
+
$stderr.puts " static-web-server <start|stop|running|publish|view> [options] [args]"
|
|
18
18
|
$stderr.puts " start [--port PORT] Start server (idempotent)"
|
|
19
19
|
$stderr.puts " stop Stop server"
|
|
20
20
|
$stderr.puts " running Exit 0 if running, non-zero otherwise"
|
|
21
21
|
$stderr.puts " publish <site_name> <source_dir> Publish static site"
|
|
22
|
+
$stderr.puts " view [path] Open path in browser (default /)"
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def main(argv = ARGV)
|
|
25
26
|
return usage_stderr("Expected subcommand.") if argv.empty?
|
|
26
27
|
|
|
27
28
|
sub = argv[0]
|
|
29
|
+
if sub == "--version" || sub == "-v"
|
|
30
|
+
spec = Gem.loaded_specs["rakit"]
|
|
31
|
+
puts spec ? "#{spec.name} #{spec.version}" : "rakit (version unknown)"
|
|
32
|
+
return 0
|
|
33
|
+
end
|
|
34
|
+
if sub == "--help" || sub == "-h"
|
|
35
|
+
usage_stderr
|
|
36
|
+
return 0
|
|
37
|
+
end
|
|
38
|
+
|
|
28
39
|
if sub == "file"
|
|
29
40
|
argv.shift
|
|
30
41
|
require "rakit/cli/file"
|
|
@@ -53,6 +64,7 @@ def main(argv = ARGV)
|
|
|
53
64
|
end
|
|
54
65
|
end
|
|
55
66
|
Rakit::StaticWebServer.start(port: port)
|
|
67
|
+
puts "http://localhost:#{port}"
|
|
56
68
|
return 0
|
|
57
69
|
when "stop"
|
|
58
70
|
Rakit::StaticWebServer.stop
|
|
@@ -68,6 +80,15 @@ def main(argv = ARGV)
|
|
|
68
80
|
end
|
|
69
81
|
Rakit::StaticWebServer.publish(site_name, source_dir)
|
|
70
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
|
|
71
92
|
else
|
|
72
93
|
usage_stderr(cmd ? "Unknown command: #{cmd}" : "Expected command after static-web-server.")
|
|
73
94
|
return 1
|
|
@@ -0,0 +1,26 @@
|
|
|
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,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: example.proto
|
|
3
|
+
# source: rakit.example.proto
|
|
4
4
|
|
|
5
5
|
require 'google/protobuf'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
descriptor_data = "\n\
|
|
8
|
+
descriptor_data = "\n\x13rakit.example.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: 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
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: static_web_server.proto
|
|
3
|
+
# source: rakit.static_web_server.proto
|
|
4
4
|
|
|
5
5
|
require 'google/protobuf'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
descriptor_data = "\n\
|
|
8
|
+
descriptor_data = "\n\x1drakit.static_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)
|
data/lib/rakit/azure/dev_ops.rb
CHANGED
|
@@ -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
|
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
|
data/lib/rakit/hugo.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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
|
data/lib/rakit/shell.rb
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
-
require "generated/static_web_server_pb"
|
|
4
|
+
require "generated/rakit.static_web_server_pb"
|
|
5
5
|
|
|
6
6
|
module Rakit
|
|
7
|
-
# Publish static sites into a configurable root, regenerate a root index, and control a local HTTP server (start/stop/running).
|
|
8
|
-
#
|
|
7
|
+
# Publish static sites into a configurable root (~/.rakit/wwwroot), regenerate a root index, and control a local HTTP server (start/stop/running).
|
|
8
|
+
# Host and port are configurable (host default 127.0.0.1, port 5099) and returned regardless of server state for URL building.
|
|
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.
|
|
9
10
|
module StaticWebServer
|
|
10
11
|
SITE_NAME_REGEX = /\A[a-z0-9\-]+\z/.freeze
|
|
11
12
|
|
|
12
13
|
class << self
|
|
13
|
-
# @return [String] Static root directory (default ~/.rakit/
|
|
14
|
+
# @return [String] Static root directory (default ~/.rakit/wwwroot). Used by publish and server lifecycle.
|
|
14
15
|
attr_accessor :root
|
|
15
|
-
# @return [
|
|
16
|
+
# @return [String] Host for building URLs (default 127.0.0.1). Returned regardless of server running state.
|
|
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.
|
|
16
19
|
attr_accessor :port
|
|
17
20
|
end
|
|
18
21
|
|
|
19
|
-
self.root = ::File.expand_path("~/.rakit/
|
|
22
|
+
self.root = ::File.expand_path("~/.rakit/wwwroot")
|
|
23
|
+
self.host = "127.0.0.1"
|
|
20
24
|
self.port = 5099
|
|
21
25
|
|
|
22
26
|
# 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.
|
|
23
28
|
def self.validate_site_name!(site_name)
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
validate_site_path!(site_name)
|
|
30
|
+
end
|
|
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
|
|
26
40
|
end
|
|
27
41
|
|
|
28
42
|
# T006: PID file path for start/stop/running (research: ~/.rakit/static_web_server.pid).
|
|
@@ -49,26 +63,27 @@ module Rakit
|
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
# Publish static content from source_directory to root/site_name (atomic copy), then regenerate root index.
|
|
52
|
-
# @param site_name [String]
|
|
66
|
+
# @param site_name [String] Single segment (e.g. "mysite") or path (e.g. "louparslow/rakit"); each segment must match \\A[a-z0-9\-]+\\z.
|
|
53
67
|
# @param source_directory [String] Existing directory path; contents are copied (no traversal outside allowed paths).
|
|
54
68
|
# @return [true] on success.
|
|
55
69
|
# @raise [ArgumentError] for invalid site_name, missing/invalid source_directory, or root not writable.
|
|
56
70
|
def self.publish(site_name, source_directory)
|
|
57
|
-
|
|
71
|
+
validate_site_path!(site_name)
|
|
58
72
|
src = ::File.expand_path(source_directory)
|
|
59
73
|
raise ArgumentError, "source_directory does not exist: #{source_directory}" unless ::File.exist?(src)
|
|
60
74
|
raise ArgumentError, "source_directory is not a directory: #{source_directory}" unless ::File.directory?(src)
|
|
61
75
|
|
|
62
76
|
ensure_root
|
|
63
|
-
# Check root is writable before we do any copy (T015 / edge case).
|
|
64
77
|
unless ::File.writable?(root)
|
|
65
78
|
raise ArgumentError, "root directory is not writable: #{root}"
|
|
66
79
|
end
|
|
67
80
|
|
|
68
81
|
target = ::File.join(root, site_name)
|
|
69
|
-
|
|
82
|
+
temp_name = ".tmp_#{site_name.to_s.gsub(::File::SEPARATOR, '_')}_#{Process.pid}_#{rand(1_000_000)}"
|
|
83
|
+
temp = ::File.join(root, temp_name)
|
|
70
84
|
FileUtils.mkdir_p(temp)
|
|
71
85
|
FileUtils.cp_r(::File.join(src, "."), temp)
|
|
86
|
+
FileUtils.mkdir_p(::File.dirname(target))
|
|
72
87
|
FileUtils.rm_rf(target)
|
|
73
88
|
FileUtils.mv(temp, target)
|
|
74
89
|
regenerate_root_index
|
|
@@ -102,6 +117,7 @@ module Rakit
|
|
|
102
117
|
root_path = ::File.expand_path(root)
|
|
103
118
|
pid = spawn(bin, root_path, "--port", p.to_s, out: ::File::NULL, err: ::File::NULL)
|
|
104
119
|
Process.detach(pid)
|
|
120
|
+
FileUtils.mkdir_p(::File.dirname(pid_file_path))
|
|
105
121
|
::File.write(pid_file_path, pid.to_s)
|
|
106
122
|
true
|
|
107
123
|
end
|
|
@@ -144,6 +160,20 @@ module Rakit
|
|
|
144
160
|
true
|
|
145
161
|
end
|
|
146
162
|
|
|
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
|
+
|
|
147
177
|
# T017: Write root/index.html listing all site subdirectories alphabetically with links.
|
|
148
178
|
def self.regenerate_root_index
|
|
149
179
|
entries = Dir.entries(root).sort.select do |e|
|
|
@@ -177,6 +207,20 @@ module Rakit
|
|
|
177
207
|
rescue Errno::EADDRINUSE
|
|
178
208
|
true
|
|
179
209
|
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
|
|
180
224
|
end
|
|
181
225
|
end
|
|
182
226
|
end
|
data/lib/rakit.rb
CHANGED
|
@@ -25,6 +25,7 @@ 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"
|
|
28
29
|
require_relative "rakit/word_count"
|
|
29
30
|
require_relative "rakit/file"
|
|
30
31
|
require_relative "rakit/azure/dev_ops"
|
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.6
|
|
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
|
|
@@ -99,18 +105,21 @@ extensions: []
|
|
|
99
105
|
extra_rdoc_files: []
|
|
100
106
|
files:
|
|
101
107
|
- exe/rakit
|
|
102
|
-
- lib/generated/azure.devops_pb.rb
|
|
103
108
|
- lib/generated/data_pb.rb
|
|
104
|
-
- lib/generated/
|
|
109
|
+
- lib/generated/rakit.azure_pb.rb
|
|
110
|
+
- lib/generated/rakit.example_pb.rb
|
|
105
111
|
- lib/generated/rakit.file_pb.rb
|
|
112
|
+
- lib/generated/rakit.shell_pb.rb
|
|
113
|
+
- lib/generated/rakit.static_web_server_pb.rb
|
|
106
114
|
- lib/generated/rakit.word_count_pb.rb
|
|
107
|
-
- lib/generated/shell_pb.rb
|
|
108
|
-
- lib/generated/static_web_server_pb.rb
|
|
109
115
|
- lib/rakit.rb
|
|
110
116
|
- lib/rakit/azure/dev_ops.rb
|
|
117
|
+
- lib/rakit/cli/file.rb
|
|
111
118
|
- lib/rakit/cli/word_count.rb
|
|
119
|
+
- lib/rakit/file.rb
|
|
112
120
|
- lib/rakit/gem.rb
|
|
113
121
|
- lib/rakit/git.rb
|
|
122
|
+
- lib/rakit/hugo.rb
|
|
114
123
|
- lib/rakit/protobuf.rb
|
|
115
124
|
- lib/rakit/shell.rb
|
|
116
125
|
- lib/rakit/static_web_server.rb
|
|
@@ -1,26 +0,0 @@
|
|
|
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/shell_pb.rb
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: shell.proto
|
|
4
|
-
|
|
5
|
-
require 'google/protobuf'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
descriptor_data = "\n\x0bshell.proto\x12\x0brakit.shell\"\x9a\x02\n\x07\x43ommand\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x02 \x03(\t\x12\x19\n\x11working_directory\x18\x03 \x01(\t\x12\x17\n\x0ftimeout_seconds\x18\x04 \x01(\x05\x12\x1a\n\x12\x65xpected_exit_code\x18\x05 \x01(\x05\x12\x17\n\x0f\x65xpected_stdout\x18\x06 \x01(\t\x12\x17\n\x0f\x65xpected_stderr\x18\x07 \x01(\t\x12<\n\x13\x61\x63\x63\x65ptance_criteria\x18\x08 \x03(\x0b\x32\x1f.rakit.shell.AcceptanceCriteria\x12\x13\n\x0b\x65xit_status\x18\t \x01(\x05\x12\x0e\n\x06stdout\x18\n \x01(\t\x12\x0e\n\x06stderr\x18\x0b \x01(\t\"1\n\x12\x41\x63\x63\x65ptanceCriteria\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"-\n\nTestResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06\x65rrors\x18\x02 \x03(\t\"b\n\rFormatRequest\x12%\n\x07\x63ommand\x18\x01 \x01(\x0b\x32\x14.rakit.shell.Command\x12*\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x1a.rakit.shell.CommandFormat\" \n\x0e\x46ormatResponse\x12\x0e\n\x06output\x18\x01 \x01(\t*Z\n\rCommandFormat\x12\x1e\n\x1a\x43OMMAND_FORMAT_UNSPECIFIED\x10\x00\x12\x0c\n\x08ONE_LINE\x10\x01\x12\x0e\n\nMULTI_LINE\x10\x02\x12\x0b\n\x07\x43OMPACT\x10\x03\x32\xc1\x01\n\x0e\x43ommandService\x12\x35\n\x07\x45xecute\x12\x14.rakit.shell.Command\x1a\x14.rakit.shell.Command\x12\x35\n\x04Test\x12\x14.rakit.shell.Command\x1a\x17.rakit.shell.TestResult\x12\x41\n\x06\x46ormat\x12\x1a.rakit.shell.FormatRequest\x1a\x1b.rakit.shell.FormatResponseB\x0f\xea\x02\x0cRakit::Shellb\x06proto3"
|
|
9
|
-
|
|
10
|
-
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
-
pool.add_serialized_file(descriptor_data)
|
|
12
|
-
|
|
13
|
-
module Rakit
|
|
14
|
-
module Shell
|
|
15
|
-
Command = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.Command").msgclass
|
|
16
|
-
AcceptanceCriteria = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.AcceptanceCriteria").msgclass
|
|
17
|
-
TestResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.TestResult").msgclass
|
|
18
|
-
FormatRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.FormatRequest").msgclass
|
|
19
|
-
FormatResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.FormatResponse").msgclass
|
|
20
|
-
CommandFormat = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("rakit.shell.CommandFormat").enummodule
|
|
21
|
-
end
|
|
22
|
-
end
|