capsium 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +34 -7
- data/CHANGELOG.md +58 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.adoc +177 -135
- data/Rakefile +8 -1
- data/bin/console +1 -1
- data/capsium.gemspec +11 -19
- data/exe/capsium +1 -1
- data/lib/capsium/cli/convert.rb +18 -0
- data/lib/capsium/cli/package.rb +86 -0
- data/lib/capsium/cli/reactor.rb +25 -0
- data/lib/capsium/cli.rb +3 -96
- data/lib/capsium/converters/jekyll.rb +5 -7
- data/lib/capsium/converters.rb +7 -0
- data/lib/capsium/package/dataset.rb +67 -34
- data/lib/capsium/package/manifest.rb +33 -22
- data/lib/capsium/package/manifest_config.rb +52 -7
- data/lib/capsium/package/metadata.rb +4 -8
- data/lib/capsium/package/metadata_config.rb +97 -9
- data/lib/capsium/package/routes.rb +39 -41
- data/lib/capsium/package/routes_config.rb +120 -44
- data/lib/capsium/package/security.rb +123 -0
- data/lib/capsium/package/security_config.rb +60 -0
- data/lib/capsium/package/storage.rb +20 -45
- data/lib/capsium/package/storage_config.rb +100 -3
- data/lib/capsium/package/validator.rb +149 -0
- data/lib/capsium/package.rb +68 -66
- data/lib/capsium/packager.rb +42 -6
- data/lib/capsium/reactor/introspection.rb +88 -0
- data/lib/capsium/reactor.rb +89 -41
- data/lib/capsium/version.rb +1 -1
- data/lib/capsium.rb +7 -9
- data/sig/capsium/package/dataset.rbs +28 -0
- data/sig/capsium/package/manifest.rbs +34 -0
- data/sig/capsium/package/manifest_config.rbs +38 -0
- data/sig/capsium/package/metadata.rbs +26 -0
- data/sig/capsium/package/metadata_config.rbs +49 -0
- data/sig/capsium/package/routes.rbs +37 -0
- data/sig/capsium/package/routes_config.rbs +74 -0
- data/sig/capsium/package/security.rbs +65 -0
- data/sig/capsium/package/security_config.rbs +57 -0
- data/sig/capsium/package/storage.rbs +28 -0
- data/sig/capsium/package/storage_config.rbs +61 -0
- data/sig/capsium/package/validator.rbs +29 -0
- data/sig/capsium/package.rbs +53 -0
- data/sig/capsium/packager.rbs +23 -0
- data/sig/capsium/reactor/introspection.rbs +31 -0
- data/sig/capsium/reactor.rbs +30 -0
- data/sig/capsium.rbs +3 -1
- metadata +52 -183
- data/lib/capsium/package/dataset_config.rb +0 -28
- data/lib/capsium/protector.rb +0 -103
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capsium
|
|
4
|
+
class Cli
|
|
5
|
+
class Convert < Thor
|
|
6
|
+
extend ThorExt::Start
|
|
7
|
+
|
|
8
|
+
desc "jekyll PACKAGE_FILE OUTPUT_DIRECTORY",
|
|
9
|
+
"Convert a Capsium package to a Jekyll site"
|
|
10
|
+
|
|
11
|
+
def jekyll(package_file, output_directory)
|
|
12
|
+
converter = Capsium::Converters::Jekyll.new(package_file,
|
|
13
|
+
output_directory)
|
|
14
|
+
converter.convert
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Capsium
|
|
6
|
+
class Cli
|
|
7
|
+
class Package < Thor
|
|
8
|
+
extend ThorExt::Start
|
|
9
|
+
|
|
10
|
+
desc "info PACKAGE_PATH", "Display information about the package"
|
|
11
|
+
|
|
12
|
+
def info(path_to_package)
|
|
13
|
+
package = Capsium::Package.new(path_to_package)
|
|
14
|
+
puts "Package Path: #{package.path}"
|
|
15
|
+
puts "Routes: #{package.routes.to_hash}"
|
|
16
|
+
puts "Manifest: #{package.manifest.to_hash}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "manifest PATH_TO_PACKAGE",
|
|
20
|
+
"Show the manifest content of the package"
|
|
21
|
+
|
|
22
|
+
def manifest(path_to_package)
|
|
23
|
+
package = Capsium::Package.new(path_to_package)
|
|
24
|
+
puts JSON.pretty_generate(package.manifest.to_hash)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc "storage PATH_TO_PACKAGE", "Show the storage datasets of the package"
|
|
28
|
+
|
|
29
|
+
def storage(path_to_package)
|
|
30
|
+
package = Capsium::Package.new(path_to_package)
|
|
31
|
+
puts JSON.pretty_generate(package.storage.to_hash)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "routes PATH_TO_PACKAGE", "Show the routes of the package"
|
|
35
|
+
|
|
36
|
+
def routes(path_to_package)
|
|
37
|
+
package = Capsium::Package.new(path_to_package)
|
|
38
|
+
puts JSON.pretty_generate(package.routes.to_hash)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
desc "metadata PATH_TO_PACKAGE", "Show the metadata of the package"
|
|
42
|
+
|
|
43
|
+
def metadata(path_to_package)
|
|
44
|
+
package = Capsium::Package.new(path_to_package)
|
|
45
|
+
puts JSON.pretty_generate(package.metadata.to_hash)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc "pack PATH_TO_PACKAGE_FOLDER", "Package the files into the package"
|
|
49
|
+
option :force, type: :boolean, default: false, aliases: "-f"
|
|
50
|
+
|
|
51
|
+
def pack(path_to_package)
|
|
52
|
+
package = Capsium::Package.new(path_to_package)
|
|
53
|
+
packager = Capsium::Packager.new
|
|
54
|
+
packager.pack(package, options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc "unpack PACKAGE_FILE", "Unpack a .cap package into a directory"
|
|
58
|
+
option :output, type: :string, aliases: "-o", desc: "Output directory"
|
|
59
|
+
|
|
60
|
+
def unpack(path_to_package)
|
|
61
|
+
destination = options[:output] || File.basename(path_to_package, ".cap")
|
|
62
|
+
Capsium::Packager.new.unpack(path_to_package, destination)
|
|
63
|
+
puts "Package unpacked to: #{destination}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
desc "validate PACKAGE_PATH", "Validate a package directory or .cap file"
|
|
67
|
+
|
|
68
|
+
def validate(path_to_package)
|
|
69
|
+
results = Capsium::Package::Validator.new(path_to_package).run
|
|
70
|
+
results.each { |result| puts format_result(result) }
|
|
71
|
+
return if results.all?(&:ok?)
|
|
72
|
+
|
|
73
|
+
raise Thor::Error, "Package validation failed"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def format_result(result)
|
|
79
|
+
line = "#{result.ok? ? 'PASS' : 'FAIL'} #{result.name}"
|
|
80
|
+
return line if result.messages.empty?
|
|
81
|
+
|
|
82
|
+
"#{line}: #{result.messages.join('; ')}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capsium
|
|
4
|
+
class Cli
|
|
5
|
+
class Reactor < Thor
|
|
6
|
+
extend ThorExt::Start
|
|
7
|
+
|
|
8
|
+
desc "serve PACKAGE_PATH",
|
|
9
|
+
"Start the Capsium reactor to serve the package"
|
|
10
|
+
option :port, type: :numeric, default: Capsium::Reactor::DEFAULT_PORT
|
|
11
|
+
option :do_not_listen, type: :boolean, default: false
|
|
12
|
+
|
|
13
|
+
def serve(path_to_package)
|
|
14
|
+
reactor = Capsium::Reactor.new(
|
|
15
|
+
package: path_to_package,
|
|
16
|
+
port: options[:port],
|
|
17
|
+
do_not_listen: options[:do_not_listen]
|
|
18
|
+
)
|
|
19
|
+
reactor.serve
|
|
20
|
+
ensure
|
|
21
|
+
reactor&.package&.cleanup
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/capsium/cli.rb
CHANGED
|
@@ -1,107 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# lib/capsium/cli.rb
|
|
4
3
|
require "thor"
|
|
5
|
-
require_relative "package"
|
|
6
|
-
require_relative "reactor"
|
|
7
|
-
require_relative "thor_ext"
|
|
8
|
-
require_relative "converters/jekyll"
|
|
9
4
|
|
|
10
5
|
module Capsium
|
|
11
6
|
class Cli < Thor
|
|
12
7
|
extend ThorExt::Start
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
desc "info PACKAGE_PATH", "Display information about the package"
|
|
18
|
-
|
|
19
|
-
def info(path_to_package)
|
|
20
|
-
package = Capsium::Package.new(path_to_package)
|
|
21
|
-
puts "Package Path: #{package.path}"
|
|
22
|
-
puts "Routes: #{package.routes.as_json}"
|
|
23
|
-
puts "Manifest: #{package.manifest.as_json}"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
desc "manifest PATH_TO_PACKAGE",
|
|
27
|
-
"Show the manifest content of the package"
|
|
28
|
-
|
|
29
|
-
def manifest(path_to_package)
|
|
30
|
-
package = Capsium::Package.new(path_to_package)
|
|
31
|
-
puts JSON.pretty_generate(package.manifest.as_json)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
desc "storage PATH_TO_PACKAGE", "Show the storage datasets of the package"
|
|
35
|
-
|
|
36
|
-
def storage(path_to_package)
|
|
37
|
-
package = Capsium::Package.new(path_to_package)
|
|
38
|
-
puts JSON.pretty_generate(package.storage.as_json)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
desc "routes PATH_TO_PACKAGE", "Show the routes of the package"
|
|
42
|
-
|
|
43
|
-
def routes(path_to_package)
|
|
44
|
-
package = Capsium::Package.new(path_to_package)
|
|
45
|
-
puts JSON.pretty_generate(package.routes.as_json)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
desc "metadata PATH_TO_PACKAGE", "Show the metadata of the package"
|
|
49
|
-
|
|
50
|
-
def metadata(path_to_package)
|
|
51
|
-
package = Capsium::Package.new(path_to_package)
|
|
52
|
-
puts JSON.pretty_generate(package.metadata.as_json)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
desc "pack PATH_TO_PACKAGE_FOLDER", "Package the files into the package"
|
|
56
|
-
option :force, type: :boolean, default: false, aliases: "-f"
|
|
57
|
-
|
|
58
|
-
def pack(path_to_package)
|
|
59
|
-
package = Capsium::Package.new(path_to_package)
|
|
60
|
-
packager = Capsium::Packager.new
|
|
61
|
-
packager.pack(package, options)
|
|
62
|
-
rescue StandardError => e
|
|
63
|
-
puts e
|
|
64
|
-
puts e.inspect
|
|
65
|
-
puts e.backtrace
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
class Reactor < Thor
|
|
70
|
-
extend ThorExt::Start
|
|
71
|
-
|
|
72
|
-
desc "serve PACKAGE_PATH",
|
|
73
|
-
"Start the Capsium reactor to serve the package"
|
|
74
|
-
option :port, type: :numeric, default: Capsium::Reactor::DEFAULT_PORT
|
|
75
|
-
option :do_not_listen, type: :boolean, default: false
|
|
76
|
-
|
|
77
|
-
def serve(path_to_package)
|
|
78
|
-
reactor = Capsium::Reactor.new(
|
|
79
|
-
package: path_to_package,
|
|
80
|
-
port: options[:port],
|
|
81
|
-
do_not_listen: options[:do_not_listen],
|
|
82
|
-
)
|
|
83
|
-
reactor.serve
|
|
84
|
-
rescue StandardError => e
|
|
85
|
-
puts e
|
|
86
|
-
puts e.inspect
|
|
87
|
-
puts e.backtrace
|
|
88
|
-
ensure
|
|
89
|
-
reactor.package.cleanup
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
class Convert < Thor
|
|
94
|
-
extend ThorExt::Start
|
|
95
|
-
|
|
96
|
-
desc "jekyll PACKAGE_FILE OUTPUT_DIRECTORY",
|
|
97
|
-
"Convert a Capsium package to a Jekyll site"
|
|
98
|
-
|
|
99
|
-
def jekyll(package_file, output_directory)
|
|
100
|
-
converter = Capsium::Converters::Jekyll.new(package_file,
|
|
101
|
-
output_directory)
|
|
102
|
-
converter.convert
|
|
103
|
-
end
|
|
104
|
-
end
|
|
9
|
+
autoload :Convert, "capsium/cli/convert"
|
|
10
|
+
autoload :Package, "capsium/cli/package"
|
|
11
|
+
autoload :Reactor, "capsium/cli/reactor"
|
|
105
12
|
|
|
106
13
|
desc "package SUBCOMMAND ...ARGS", "Manage packages"
|
|
107
14
|
subcommand "package", Capsium::Cli::Package
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# lib/capsium/converters/jekyll.rb
|
|
4
3
|
require "fileutils"
|
|
5
|
-
require "
|
|
6
|
-
require "
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "yaml"
|
|
7
6
|
|
|
8
7
|
module Capsium
|
|
9
8
|
module Converters
|
|
@@ -38,7 +37,7 @@ module Capsium
|
|
|
38
37
|
"baseurl" => "",
|
|
39
38
|
"url" => "",
|
|
40
39
|
"markdown" => "kramdown",
|
|
41
|
-
"theme" => "minima"
|
|
40
|
+
"theme" => "minima"
|
|
42
41
|
}
|
|
43
42
|
write_file("_config.yml", config.to_yaml)
|
|
44
43
|
end
|
|
@@ -51,7 +50,7 @@ module Capsium
|
|
|
51
50
|
|
|
52
51
|
def copy_content_files(package)
|
|
53
52
|
package.content_files.each do |file_path|
|
|
54
|
-
relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(package.
|
|
53
|
+
relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(package.path))
|
|
55
54
|
output_path = File.join(@output_directory, relative_path)
|
|
56
55
|
FileUtils.mkdir_p(File.dirname(output_path))
|
|
57
56
|
FileUtils.cp(file_path, output_path)
|
|
@@ -63,8 +62,7 @@ module Capsium
|
|
|
63
62
|
route.path == "/"
|
|
64
63
|
end
|
|
65
64
|
if root_route
|
|
66
|
-
index_path = File.join(package.
|
|
67
|
-
root_route.target.file)
|
|
65
|
+
index_path = File.join(package.path, root_route.resource)
|
|
68
66
|
index_content = File.read(index_path)
|
|
69
67
|
write_file("index.html", index_content)
|
|
70
68
|
else
|
|
@@ -1,72 +1,105 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
|
-
require "yaml"
|
|
5
3
|
require "csv"
|
|
6
|
-
require "
|
|
7
|
-
require "
|
|
4
|
+
require "forwardable"
|
|
5
|
+
require "json"
|
|
8
6
|
require "json-schema"
|
|
9
|
-
|
|
7
|
+
require "sqlite3"
|
|
8
|
+
require "yaml"
|
|
10
9
|
|
|
11
10
|
module Capsium
|
|
12
11
|
class Package
|
|
12
|
+
# A loaded dataset. "source"/"schemaFile"/"databaseFile" are
|
|
13
|
+
# package-relative paths (ARCHITECTURE.md section 5), resolved against
|
|
14
|
+
# the package directory.
|
|
13
15
|
class Dataset
|
|
14
|
-
attr_reader :config, :data, :
|
|
16
|
+
attr_reader :name, :config, :data, :package_path
|
|
15
17
|
|
|
16
18
|
extend Forwardable
|
|
17
19
|
|
|
18
20
|
def_delegators :@config, :to_json
|
|
19
21
|
|
|
20
|
-
def initialize(config:,
|
|
22
|
+
def initialize(name:, config:, package_path:)
|
|
23
|
+
@name = name
|
|
21
24
|
@config = config
|
|
22
|
-
@
|
|
25
|
+
@package_path = package_path
|
|
23
26
|
@data = load_data
|
|
24
27
|
end
|
|
25
28
|
|
|
29
|
+
def source_path
|
|
30
|
+
File.join(@package_path, @config.backing_file)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def schema_path
|
|
34
|
+
return unless @config.schema_file
|
|
35
|
+
|
|
36
|
+
File.join(@package_path, @config.schema_file)
|
|
37
|
+
end
|
|
38
|
+
|
|
26
39
|
def load_data
|
|
40
|
+
return nil unless File.file?(source_path)
|
|
41
|
+
|
|
27
42
|
case @config.format
|
|
28
|
-
when "yaml" then YAML.load_file(
|
|
29
|
-
when "json" then JSON.parse(File.read(
|
|
30
|
-
when "csv" then CSV.read(
|
|
31
|
-
when "tsv" then CSV.read(
|
|
43
|
+
when "yaml" then YAML.load_file(source_path)
|
|
44
|
+
when "json" then JSON.parse(File.read(source_path))
|
|
45
|
+
when "csv" then CSV.read(source_path, headers: true)
|
|
46
|
+
when "tsv" then CSV.read(source_path, col_sep: "\t", headers: true)
|
|
32
47
|
when "sqlite" then load_sqlite_data
|
|
33
48
|
else
|
|
34
|
-
raise "Unsupported data file type: #{@config.format}"
|
|
49
|
+
raise Error, "Unsupported data file type: #{@config.format}"
|
|
35
50
|
end
|
|
36
51
|
end
|
|
37
52
|
|
|
38
53
|
def validate
|
|
39
|
-
return unless @config.schema
|
|
54
|
+
return true unless @config.schema_file && @config.schema_type == "json-schema"
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
schema = JSON.parse(File.read(schema_path)) if @config.format == "json"
|
|
56
|
+
JSON::Validator.validate!(load_schema, @data.to_json)
|
|
57
|
+
end
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
# File-existence and schema validations for this dataset. Returns a
|
|
60
|
+
# list of human-readable problems; empty when valid.
|
|
61
|
+
def validation_errors
|
|
62
|
+
problems = []
|
|
63
|
+
unless File.file?(source_path)
|
|
64
|
+
problems << "dataset source missing on disk: #{@config.backing_file}"
|
|
50
65
|
end
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
problems.concat(schema_validation_errors)
|
|
67
|
+
problems
|
|
53
68
|
end
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def schema_validation_errors
|
|
73
|
+
return [] unless @config.schema_file
|
|
74
|
+
unless File.file?(schema_path)
|
|
75
|
+
return ["dataset schema missing on disk: #{@config.schema_file}"]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
validate
|
|
79
|
+
[]
|
|
80
|
+
rescue JSON::Schema::ValidationError => e
|
|
81
|
+
["dataset #{@name} fails schema validation: #{e.message}"]
|
|
57
82
|
end
|
|
58
83
|
|
|
59
|
-
|
|
84
|
+
def load_schema
|
|
85
|
+
case File.extname(schema_path).downcase
|
|
86
|
+
when ".yaml", ".yml" then YAML.load_file(schema_path)
|
|
87
|
+
else JSON.parse(File.read(schema_path))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
60
90
|
|
|
61
91
|
def load_sqlite_data
|
|
62
|
-
db = SQLite3::Database.new(
|
|
63
|
-
tables =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
table_name = table.first
|
|
67
|
-
data[table_name] = db.execute("SELECT * FROM #{table_name};")
|
|
92
|
+
db = SQLite3::Database.new(source_path)
|
|
93
|
+
tables = @config.table ? [@config.table] : sqlite_tables(db)
|
|
94
|
+
tables.to_h do |table_name|
|
|
95
|
+
[table_name, db.execute("SELECT * FROM #{table_name};")]
|
|
68
96
|
end
|
|
69
|
-
|
|
97
|
+
ensure
|
|
98
|
+
db&.close
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def sqlite_tables(db)
|
|
102
|
+
db.execute("SELECT name FROM sqlite_master WHERE type='table';").map(&:first)
|
|
70
103
|
end
|
|
71
104
|
end
|
|
72
105
|
end
|
|
@@ -1,51 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "json"
|
|
3
|
+
require "forwardable"
|
|
5
4
|
require "marcel"
|
|
6
|
-
require "
|
|
7
|
-
require_relative "manifest_config"
|
|
5
|
+
require "pathname"
|
|
8
6
|
|
|
9
7
|
module Capsium
|
|
10
8
|
class Package
|
|
11
9
|
class Manifest
|
|
12
10
|
extend Forwardable
|
|
13
|
-
attr_accessor :path, :content_path, :config
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
attr_reader :path, :content_path, :config
|
|
13
|
+
|
|
14
|
+
def_delegators :@config, :to_hash
|
|
16
15
|
|
|
17
16
|
def initialize(path)
|
|
18
17
|
@path = path
|
|
19
18
|
@content_path = File.join(File.dirname(@path), Package::CONTENT_DIR)
|
|
20
|
-
|
|
21
19
|
@config = if File.exist?(path)
|
|
22
20
|
ManifestConfig.from_json(File.read(path))
|
|
23
21
|
else
|
|
24
|
-
ManifestConfig.new(
|
|
22
|
+
ManifestConfig.new(resources: generate_manifest)
|
|
25
23
|
end
|
|
26
24
|
end
|
|
27
25
|
|
|
26
|
+
# Auto-generation (ARCHITECTURE.md section 3): scan content/
|
|
27
|
+
# recursively, detect MIME types, default visibility "exported",
|
|
28
|
+
# deterministic (sorted) output.
|
|
28
29
|
def generate_manifest
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
content_files.sort.to_h do |file_path|
|
|
31
|
+
[relative_path(file_path),
|
|
32
|
+
Resource.new(type: mime_from_path(file_path), visibility: "exported")]
|
|
31
33
|
end
|
|
34
|
+
end
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
file: relative_path(file_path),
|
|
36
|
-
mime: mime_from_path(file_path),
|
|
37
|
-
)
|
|
38
|
-
end
|
|
36
|
+
def resources
|
|
37
|
+
@config.resources
|
|
39
38
|
end
|
|
40
39
|
|
|
41
|
-
def lookup(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
def lookup(path)
|
|
41
|
+
resources[path]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def type_for(path)
|
|
45
|
+
resource = lookup(path)
|
|
46
|
+
resource&.type
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_json(*_args)
|
|
50
|
+
@config.to_json
|
|
45
51
|
end
|
|
46
52
|
|
|
47
53
|
def save_to_file(output_path = @path)
|
|
48
|
-
@config.content.sort_by!(&:file)
|
|
49
54
|
File.write(output_path, to_json)
|
|
50
55
|
end
|
|
51
56
|
|
|
@@ -65,11 +70,17 @@ module Capsium
|
|
|
65
70
|
|
|
66
71
|
private
|
|
67
72
|
|
|
73
|
+
def content_files
|
|
74
|
+
Dir.glob(File.join(@content_path, "**", "*")).select do |file|
|
|
75
|
+
File.file?(file)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
68
79
|
def mime_from_path(path)
|
|
69
80
|
Marcel::MimeType.for(
|
|
70
81
|
Pathname.new(path),
|
|
71
82
|
name: File.basename(path),
|
|
72
|
-
extension: File.extname(path)
|
|
83
|
+
extension: File.extname(path)
|
|
73
84
|
)
|
|
74
85
|
end
|
|
75
86
|
end
|
|
@@ -1,17 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "lutaml/model"
|
|
2
5
|
|
|
3
6
|
module Capsium
|
|
4
7
|
class Package
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
# A single manifest resource entry (ARCHITECTURE.md section 3).
|
|
9
|
+
class Resource < Lutaml::Model::Serializable
|
|
10
|
+
VISIBILITIES = %w[exported private].freeze
|
|
11
|
+
|
|
12
|
+
attribute :type, :string
|
|
13
|
+
attribute :visibility, :string, values: VISIBILITIES, default: "exported"
|
|
14
|
+
attribute :version, :string
|
|
15
|
+
|
|
16
|
+
json do
|
|
17
|
+
map :type, to: :type
|
|
18
|
+
map :visibility, to: :visibility
|
|
19
|
+
map :version, to: :version
|
|
20
|
+
end
|
|
8
21
|
end
|
|
9
22
|
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
# Canonical manifest.json model: an object keyed by package-relative
|
|
24
|
+
# resource path. The legacy gem form ({"content": [{file, mime}]}) is
|
|
25
|
+
# accepted on read and normalized; writers emit only the object form.
|
|
26
|
+
class ManifestConfig < Lutaml::Model::Serializable
|
|
27
|
+
attribute :resources, :hash, default: {}
|
|
28
|
+
|
|
29
|
+
json do
|
|
30
|
+
map :resources, with: { from: :resources_from_json, to: :resources_to_json }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.from_json(json)
|
|
34
|
+
doc = JSON.parse(json)
|
|
35
|
+
doc["resources"] ||= legacy_resources(doc.delete("content")) if doc.key?("content")
|
|
36
|
+
super(JSON.generate(doc))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.legacy_resources(content)
|
|
40
|
+
(content || []).to_h do |item|
|
|
41
|
+
[item["file"], { "type" => item["mime"], "visibility" => "exported" }]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
private_class_method :legacy_resources
|
|
45
|
+
|
|
46
|
+
def resources_from_json(model, value)
|
|
47
|
+
model.resources = (value || {}).to_h do |path, attributes|
|
|
48
|
+
[path, Resource.from_json(JSON.generate(attributes))]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def resources_to_json(model, doc)
|
|
53
|
+
doc["resources"] = model.resources.sort.to_h do |path, resource|
|
|
54
|
+
[path, JSON.parse(resource.to_json)]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
12
57
|
|
|
13
58
|
def sort!
|
|
14
|
-
|
|
59
|
+
self.resources = resources.sort.to_h
|
|
15
60
|
self
|
|
16
61
|
end
|
|
17
62
|
end
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# lib/capsium/package/metadata.rb
|
|
4
|
-
require "shale"
|
|
5
3
|
require "forwardable"
|
|
6
|
-
require_relative "metadata_config"
|
|
7
4
|
|
|
8
5
|
module Capsium
|
|
9
6
|
class Package
|
|
@@ -11,11 +8,10 @@ module Capsium
|
|
|
11
8
|
attr_reader :path, :config
|
|
12
9
|
|
|
13
10
|
extend Forwardable
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def_delegator :@config, :dependencies
|
|
11
|
+
|
|
12
|
+
def_delegators :@config, :to_json, :to_hash, :name, :version,
|
|
13
|
+
:description, :guid, :uuid, :author, :license,
|
|
14
|
+
:repository, :dependencies
|
|
19
15
|
|
|
20
16
|
def initialize(path)
|
|
21
17
|
@path = path
|