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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +34 -7
  4. data/CHANGELOG.md +58 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +21 -0
  7. data/README.adoc +177 -135
  8. data/Rakefile +8 -1
  9. data/bin/console +1 -1
  10. data/capsium.gemspec +11 -19
  11. data/exe/capsium +1 -1
  12. data/lib/capsium/cli/convert.rb +18 -0
  13. data/lib/capsium/cli/package.rb +86 -0
  14. data/lib/capsium/cli/reactor.rb +25 -0
  15. data/lib/capsium/cli.rb +3 -96
  16. data/lib/capsium/converters/jekyll.rb +5 -7
  17. data/lib/capsium/converters.rb +7 -0
  18. data/lib/capsium/package/dataset.rb +67 -34
  19. data/lib/capsium/package/manifest.rb +33 -22
  20. data/lib/capsium/package/manifest_config.rb +52 -7
  21. data/lib/capsium/package/metadata.rb +4 -8
  22. data/lib/capsium/package/metadata_config.rb +97 -9
  23. data/lib/capsium/package/routes.rb +39 -41
  24. data/lib/capsium/package/routes_config.rb +120 -44
  25. data/lib/capsium/package/security.rb +123 -0
  26. data/lib/capsium/package/security_config.rb +60 -0
  27. data/lib/capsium/package/storage.rb +20 -45
  28. data/lib/capsium/package/storage_config.rb +100 -3
  29. data/lib/capsium/package/validator.rb +149 -0
  30. data/lib/capsium/package.rb +68 -66
  31. data/lib/capsium/packager.rb +42 -6
  32. data/lib/capsium/reactor/introspection.rb +88 -0
  33. data/lib/capsium/reactor.rb +89 -41
  34. data/lib/capsium/version.rb +1 -1
  35. data/lib/capsium.rb +7 -9
  36. data/sig/capsium/package/dataset.rbs +28 -0
  37. data/sig/capsium/package/manifest.rbs +34 -0
  38. data/sig/capsium/package/manifest_config.rbs +38 -0
  39. data/sig/capsium/package/metadata.rbs +26 -0
  40. data/sig/capsium/package/metadata_config.rbs +49 -0
  41. data/sig/capsium/package/routes.rbs +37 -0
  42. data/sig/capsium/package/routes_config.rbs +74 -0
  43. data/sig/capsium/package/security.rbs +65 -0
  44. data/sig/capsium/package/security_config.rbs +57 -0
  45. data/sig/capsium/package/storage.rbs +28 -0
  46. data/sig/capsium/package/storage_config.rbs +61 -0
  47. data/sig/capsium/package/validator.rbs +29 -0
  48. data/sig/capsium/package.rbs +53 -0
  49. data/sig/capsium/packager.rbs +23 -0
  50. data/sig/capsium/reactor/introspection.rbs +31 -0
  51. data/sig/capsium/reactor.rbs +30 -0
  52. data/sig/capsium.rbs +3 -1
  53. metadata +52 -183
  54. data/lib/capsium/package/dataset_config.rb +0 -28
  55. data/lib/capsium/protector.rb +0 -103
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+ require "time"
6
+
7
+ module Capsium
8
+ class Reactor
9
+ # Monitoring HTTP API reports for the package this reactor serves
10
+ # (ARCHITECTURE.md section 7). Each report wraps its single entry in
11
+ # the list shape all reactors converge on.
12
+ class Introspection
13
+ METADATA_PATH = "/api/v1/introspect/metadata"
14
+ ROUTES_PATH = "/api/v1/introspect/routes"
15
+ CONTENT_HASHES_PATH = "/api/v1/introspect/content-hashes"
16
+ CONTENT_VALIDITY_PATH = "/api/v1/introspect/content-validity"
17
+ PATHS = [METADATA_PATH, ROUTES_PATH, CONTENT_HASHES_PATH,
18
+ CONTENT_VALIDITY_PATH].freeze
19
+
20
+ attr_reader :package
21
+
22
+ def initialize(package)
23
+ @package = package
24
+ end
25
+
26
+ def endpoint?(path)
27
+ PATHS.include?(path)
28
+ end
29
+
30
+ # The report body for an introspection endpoint, or nil when the
31
+ # path is not an introspection endpoint.
32
+ def report_for(path)
33
+ case path
34
+ when METADATA_PATH then metadata_report
35
+ when ROUTES_PATH then routes_report
36
+ when CONTENT_HASHES_PATH then content_hashes_report
37
+ when CONTENT_VALIDITY_PATH then content_validity_report
38
+ end
39
+ end
40
+
41
+ def metadata_report
42
+ metadata = package.metadata
43
+ { packages: [{
44
+ name: metadata.name,
45
+ version: metadata.version,
46
+ author: metadata.author,
47
+ description: metadata.description
48
+ }] }
49
+ end
50
+
51
+ def routes_report
52
+ entries = package.routes.config.routes.map do |route|
53
+ { method: route.http_method || "GET", path: route.path }
54
+ end
55
+ { routes: [{ package: package.name, routes: entries }] }
56
+ end
57
+
58
+ def content_hashes_report
59
+ { contentHashes: [{ package: package.name, hash: content_hash }] }
60
+ end
61
+
62
+ def content_validity_report
63
+ errors = package.verify_integrity
64
+ entry = {
65
+ package: package.name,
66
+ valid: errors.empty?,
67
+ lastChecked: Time.now.utc.iso8601
68
+ }
69
+ entry[:reason] = errors.map(&:message).join("; ") unless errors.empty?
70
+ { contentValidity: [entry] }
71
+ end
72
+
73
+ private
74
+
75
+ # SHA-256 of the .cap blob when the package was loaded from one.
76
+ # For directory sources there is no blob, so the hash covers a
77
+ # canonical (sorted-key) JSON serialization of the package content
78
+ # checksums — the same data security.json integrityChecks carry.
79
+ def content_hash
80
+ cap_file = package.cap_file_path
81
+ return Digest::SHA256.file(cap_file).hexdigest if cap_file
82
+
83
+ checksums = Package::Security.checksums_for(package.path)
84
+ Digest::SHA256.hexdigest(JSON.generate(checksums))
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,52 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "webrick"
4
3
  require "json"
5
4
  require "listen"
6
- require "capsium/package"
5
+ require "webrick"
7
6
 
8
7
  module Capsium
9
8
  class Reactor
9
+ autoload :Introspection, "capsium/reactor/introspection"
10
+
10
11
  DEFAULT_PORT = 8864
12
+ DEFAULT_CACHE_CONTROL = "public, max-age=31536000"
11
13
 
12
- attr_accessor :package, :package_path, :routes, :port, :server,
13
- :server_thread
14
+ attr_reader :package, :package_path, :routes, :port, :cache_control,
15
+ :server, :server_thread, :introspection
14
16
 
15
- def initialize(package:, port: DEFAULT_PORT, do_not_listen: false)
17
+ def initialize(package:, port: DEFAULT_PORT,
18
+ cache_control: DEFAULT_CACHE_CONTROL, do_not_listen: false)
16
19
  @package = package.is_a?(String) ? Package.new(package) : package
17
20
  @package_path = @package.path
18
21
  @port = port
22
+ @cache_control = cache_control
19
23
  setup_server(do_not_listen)
20
- @routes = @package.routes
24
+ load_state
21
25
  mount_routes
22
26
  end
23
27
 
24
28
  def serve
29
+ trap("INT") { shutdown_server }
25
30
  @server_thread = start_server
26
31
  start_listener
27
32
  end
28
33
 
29
34
  def handle_request(request, response)
35
+ return serve_introspection(request, response) if @introspection.endpoint?(request.path)
36
+
30
37
  route = @routes.resolve(request.path)
31
- if route
32
- target = route.target
33
- content_path = target.fs_path(@package.manifest)
34
- if File.exist?(content_path)
35
- response.status = 200
36
- response["Content-Type"] = target.mime(@package.manifest)
37
- response.body = File.read(content_path)
38
- else
39
- response.status = 404
40
- response["Content-Type"] = "text/plain"
41
- response.body = "Not Found"
42
- end
43
- else
44
- response.status = 404
45
- response["Content-Type"] = "text/plain"
46
- response.body = "Not Found"
38
+ route ? serve_route(route, response) : respond_not_found(response)
39
+ end
40
+
41
+ def mount_routes
42
+ paths = @routes.config.routes.map(&:path) + Introspection::PATHS
43
+ paths.each do |path|
44
+ @server.mount_proc(path.to_s) { |req, res| handle_request(req, res) }
47
45
  end
48
46
  end
49
47
 
48
+ def restart_server
49
+ @server.shutdown
50
+ @server_thread&.join
51
+ load_package
52
+ setup_server(false)
53
+ mount_routes
54
+ @server_thread = start_server
55
+ end
56
+
50
57
  private
51
58
 
52
59
  def setup_server(do_not_listen)
@@ -55,18 +62,8 @@ module Capsium
55
62
  @server = WEBrick::HTTPServer.new(server_options)
56
63
  end
57
64
 
58
- def mount_routes
59
- @routes.config.routes.each do |route|
60
- path = route.path
61
- @server.mount_proc(path.to_s) do |req, res|
62
- handle_request(req, res)
63
- end
64
- end
65
- end
66
-
67
65
  def start_server
68
66
  Thread.new do
69
- trap("INT") { shutdown_server }
70
67
  puts "Starting server on http://localhost:#{@port}"
71
68
  @server.start
72
69
  end
@@ -78,15 +75,6 @@ module Capsium
78
75
  exit
79
76
  end
80
77
 
81
- def restart_server
82
- @server.shutdown
83
- @server_thread&.join
84
- load_package
85
- setup_server(false)
86
- mount_routes
87
- @server_thread = start_server
88
- end
89
-
90
78
  def start_listener
91
79
  listener = Listen.to(@package_path) do |_modified, _added, _removed|
92
80
  puts "Changes detected, reloading..."
@@ -100,7 +88,67 @@ module Capsium
100
88
 
101
89
  def load_package
102
90
  @package = Package.new(@package_path)
91
+ load_state
92
+ end
93
+
94
+ def load_state
103
95
  @routes = @package.routes
96
+ @introspection = Introspection.new(@package)
97
+ end
98
+
99
+ def serve_introspection(request, response)
100
+ return respond_method_not_allowed(response) unless request.request_method == "GET"
101
+
102
+ response.status = 200
103
+ response["Content-Type"] = "application/json"
104
+ response.body = JSON.generate(@introspection.report_for(request.path))
105
+ end
106
+
107
+ def serve_route(route, response)
108
+ case route.kind
109
+ when :dataset then serve_dataset(route.dataset, response)
110
+ when :resource then serve_file(route, response)
111
+ else respond_not_implemented(response)
112
+ end
113
+ end
114
+
115
+ def serve_file(route, response)
116
+ content_path = route.fs_path(@package.path)
117
+ if content_path && File.exist?(content_path)
118
+ response.status = 200
119
+ response["Content-Type"] = route.mime(@package.manifest) || "application/octet-stream"
120
+ headers_for(route).each { |name, value| response[name] = value }
121
+ response.body = File.read(content_path)
122
+ else
123
+ respond_not_found(response)
124
+ end
125
+ end
126
+
127
+ def headers_for(route)
128
+ route.headers || (@cache_control ? { "Cache-Control" => @cache_control } : {})
129
+ end
130
+
131
+ def serve_dataset(dataset_name, response)
132
+ dataset = @package.storage.dataset(dataset_name)
133
+ if dataset
134
+ response.status = 200
135
+ response["Content-Type"] = "application/json"
136
+ response.body = JSON.generate(dataset.data)
137
+ else
138
+ respond_not_found(response)
139
+ end
140
+ end
141
+
142
+ def respond_not_found(response) = respond_text(response, 404, "Not Found")
143
+
144
+ def respond_not_implemented(response) = respond_text(response, 501, "Not Implemented")
145
+
146
+ def respond_method_not_allowed(response) = respond_text(response, 405, "Method Not Allowed")
147
+
148
+ def respond_text(response, status, body)
149
+ response.status = status
150
+ response["Content-Type"] = "text/plain"
151
+ response.body = body
104
152
  end
105
153
  end
106
154
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Capsium
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/capsium.rb CHANGED
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "capsium/version"
4
- require "shale"
5
- require "shale/adapter/nokogiri"
6
- Shale.xml_adapter = Shale::Adapter::Nokogiri
7
-
8
3
  module Capsium
9
4
  class Error < StandardError; end
10
5
 
11
- # Your code goes here...
6
+ autoload :VERSION, "capsium/version"
7
+ autoload :Cli, "capsium/cli"
8
+ autoload :Converters, "capsium/converters"
9
+ autoload :Package, "capsium/package"
10
+ autoload :Packager, "capsium/packager"
11
+ autoload :Reactor, "capsium/reactor"
12
+ autoload :ThorExt, "capsium/thor_ext"
12
13
  end
13
-
14
- require_relative "capsium/package"
15
- require_relative "capsium/packager"
@@ -0,0 +1,28 @@
1
+ module Capsium
2
+ class Package
3
+ # A loaded dataset. "source"/"schemaFile"/"databaseFile" are
4
+ # package-relative paths resolved against the package directory.
5
+ class Dataset
6
+ attr_reader name: String
7
+ attr_reader config: DatasetConfig
8
+ attr_reader data: untyped
9
+ attr_reader package_path: String
10
+
11
+ def initialize: (name: String name, config: DatasetConfig config,
12
+ package_path: String package_path) -> void
13
+
14
+ def source_path: () -> String
15
+
16
+ def schema_path: () -> String?
17
+
18
+ def load_data: () -> untyped
19
+
20
+ def validate: () -> bool
21
+
22
+ # File-existence and schema validations; empty when valid.
23
+ def validation_errors: () -> Array[String]
24
+
25
+ def to_json: (*untyped args) -> String
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module Capsium
2
+ class Package
3
+ # Loads, generates and writes manifest.json (ARCHITECTURE.md
4
+ # section 3).
5
+ class Manifest
6
+ attr_reader path: String
7
+ attr_reader content_path: String
8
+ attr_reader config: ManifestConfig
9
+
10
+ def initialize: (String path) -> void
11
+
12
+ # Auto-generation: scan content/ recursively, detect MIME types,
13
+ # default visibility "exported", deterministic (sorted) output.
14
+ def generate_manifest: () -> Hash[String, Resource]
15
+
16
+ def resources: () -> Hash[String, Resource]
17
+
18
+ def lookup: (String path) -> Resource?
19
+
20
+ def type_for: (String path) -> String?
21
+
22
+ def to_json: (*untyped args) -> String
23
+ def to_hash: () -> Hash[String, untyped]
24
+
25
+ def save_to_file: (?String output_path) -> void
26
+
27
+ def path_to_content_file: (String path) -> Pathname
28
+
29
+ def content_file_exists?: (String path) -> bool
30
+
31
+ def relative_path: (String path) -> String
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ module Capsium
2
+ class Package
3
+ # A single manifest resource entry (ARCHITECTURE.md section 3).
4
+ class Resource
5
+ VISIBILITIES: Array[String]
6
+
7
+ attr_accessor type: String?
8
+ attr_accessor visibility: String
9
+ attr_accessor version: String?
10
+
11
+ def initialize: (?type: String? type, ?visibility: String? visibility,
12
+ ?version: String? version) -> void
13
+
14
+ def to_json: (*untyped args) -> String
15
+ def to_hash: () -> Hash[String, untyped]
16
+ end
17
+
18
+ # Canonical manifest.json model: an object keyed by package-relative
19
+ # resource path. The legacy array form is normalized on read;
20
+ # writers emit only the object form.
21
+ class ManifestConfig
22
+ attr_accessor resources: Hash[String, Resource]
23
+
24
+ def initialize: (?resources: Hash[String, Resource] resources) -> void
25
+
26
+ def self.from_json: (String json) -> instance
27
+
28
+ def resources_from_json: (ManifestConfig model, untyped value) -> void
29
+
30
+ def resources_to_json: (ManifestConfig model, Hash[String, untyped] doc) -> void
31
+
32
+ def sort!: () -> ManifestConfig
33
+
34
+ def to_json: (*untyped args) -> String
35
+ def to_hash: () -> Hash[String, untyped]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ module Capsium
2
+ class Package
3
+ # Loads and writes metadata.json; field access is delegated to the
4
+ # wrapped MetadataData config (ARCHITECTURE.md section 2).
5
+ class Metadata
6
+ attr_reader path: String
7
+ attr_reader config: MetadataData
8
+
9
+ def initialize: (String path) -> void
10
+
11
+ def save_to_file: (?String output_path) -> void
12
+
13
+ def to_json: (*untyped args) -> String
14
+ def to_hash: () -> Hash[String, untyped]
15
+ def name: () -> String?
16
+ def version: () -> String?
17
+ def description: () -> String?
18
+ def guid: () -> String?
19
+ def uuid: () -> String?
20
+ def author: () -> String?
21
+ def license: () -> String?
22
+ def repository: () -> Repository?
23
+ def dependencies: () -> Hash[String, String]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ module Capsium
2
+ class Package
3
+ # The repository object of metadata.json.
4
+ class Repository
5
+ attr_accessor type: String?
6
+ attr_accessor url: String?
7
+
8
+ def initialize: (?type: String? type, ?url: String? url) -> void
9
+
10
+ def to_json: (*untyped args) -> String
11
+ def to_hash: () -> Hash[String, untyped]
12
+ end
13
+
14
+ # Canonical metadata.json model (ARCHITECTURE.md section 2). The
15
+ # legacy array form of "dependencies" is normalized on read; writers
16
+ # emit only the object form.
17
+ class MetadataData
18
+ KEBAB_CASE_PATTERN: Regexp
19
+ SEMVER_PATTERN: Regexp
20
+ UUID_PATTERN: Regexp
21
+
22
+ attr_accessor name: String?
23
+ attr_accessor version: String?
24
+ attr_accessor description: String?
25
+ attr_accessor guid: String?
26
+ attr_accessor uuid: String?
27
+ attr_accessor author: String?
28
+ attr_accessor license: String?
29
+ attr_accessor repository: Repository?
30
+ attr_accessor dependencies: Hash[String, String]
31
+ attr_accessor read_only: bool?
32
+
33
+ def initialize: (?name: String? name, ?version: String? version,
34
+ ?description: String? description, ?guid: String? guid,
35
+ ?uuid: String? uuid, ?author: String? author,
36
+ ?license: String? license, ?repository: Repository? repository,
37
+ ?dependencies: Hash[String, String] dependencies,
38
+ ?read_only: bool? read_only) -> void
39
+
40
+ def self.from_json: (String json) -> instance
41
+
42
+ # Field-level format validations; empty when valid.
43
+ def format_errors: () -> Array[String]
44
+
45
+ def to_json: (*untyped args) -> String
46
+ def to_hash: () -> Hash[String, untyped]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ module Capsium
2
+ class Package
3
+ # Loads, generates and writes routes.json (ARCHITECTURE.md
4
+ # section 4).
5
+ class Routes
6
+ INDEX_ROUTE: String
7
+ INDEX_RESOURCE: String
8
+ DATASET_ROUTE_PREFIX: String
9
+
10
+ attr_reader path: String
11
+ attr_reader config: RoutesConfig
12
+ attr_reader manifest: Manifest
13
+ attr_reader storage: Storage
14
+
15
+ def initialize: (String path, Manifest manifest, Storage storage) -> void
16
+
17
+ def resolve: (String url_path) -> Route?
18
+
19
+ def add_route: (String route, String target) -> Route
20
+
21
+ def update_route: (String route, String updated_route, String updated_target) -> Route
22
+
23
+ def remove_route: (String route) -> Array[Route]
24
+
25
+ def to_json: (*untyped args) -> String
26
+ def to_hash: () -> Hash[String, untyped]
27
+
28
+ def save_to_file: (?String output_path) -> void
29
+
30
+ # Auto-generation (ARCHITECTURE.md section 4): every manifest
31
+ # resource gets a route at its path relative to content/; HTML
32
+ # files get two routes; the index HTML additionally gets "/";
33
+ # every dataset gets /api/v1/data/<id>. Deterministic output.
34
+ def generate_routes: () -> RoutesConfig
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,74 @@
1
+ module Capsium
2
+ class Package
3
+ # A single route entry (ARCHITECTURE.md section 4). Kinds are
4
+ # discriminated by key, MECE: static file, dataset route, or dynamic
5
+ # handler (accepted-and-ignored; reactors respond 501).
6
+ class Route
7
+ DATASET_PATH_PREFIX: String
8
+
9
+ attr_accessor path: String?
10
+ attr_accessor resource: String?
11
+ attr_accessor headers: Hash[String, String]?
12
+ attr_accessor headers_file: String?
13
+ attr_accessor visibility: String
14
+ attr_accessor dataset: String?
15
+ attr_accessor access_control: Hash[String, untyped]?
16
+ attr_accessor http_method: String?
17
+ attr_accessor handler: String?
18
+
19
+ def initialize: (?path: String? path, ?resource: String? resource,
20
+ ?headers: Hash[String, String]? headers,
21
+ ?headers_file: String? headers_file,
22
+ ?visibility: String? visibility, ?dataset: String? dataset,
23
+ ?access_control: Hash[String, untyped]? access_control,
24
+ ?http_method: String? http_method,
25
+ ?handler: String? handler) -> void
26
+
27
+ def self.from_json: (String json) -> instance
28
+
29
+ def kind: () -> (:resource | :dataset | :handler)
30
+
31
+ def dataset_route?: () -> bool
32
+
33
+ def handler_route?: () -> bool
34
+
35
+ def fs_path: (String package_path) -> String?
36
+
37
+ def mime: (Manifest manifest) -> String?
38
+
39
+ def validate_target: (String package_path, Storage storage) -> void
40
+
41
+ def to_json: (*untyped args) -> String
42
+ def to_hash: () -> Hash[String, untyped]
43
+ end
44
+
45
+ # Canonical routes.json model: optional top-level "index" plus a
46
+ # "routes" array. The legacy object/target form is normalized on
47
+ # read; writers emit only the canonical form.
48
+ class RoutesConfig
49
+ attr_accessor index: String?
50
+ attr_accessor routes: Array[Route]
51
+
52
+ def initialize: (?index: String? index, ?routes: Array[Route] routes) -> void
53
+
54
+ def self.from_json: (String json) -> instance
55
+
56
+ def routes_from_json: (RoutesConfig model, untyped value) -> void
57
+
58
+ def routes_to_json: (RoutesConfig model, Hash[String, untyped] doc) -> void
59
+
60
+ def resolve: (String path) -> Route?
61
+
62
+ def add: (String path, String target) -> Route
63
+
64
+ def update: (String path, String updated_path, String updated_target) -> Route
65
+
66
+ def remove: (String path) -> Array[Route]
67
+
68
+ def sort!: () -> RoutesConfig
69
+
70
+ def to_json: (*untyped args) -> String
71
+ def to_hash: () -> Hash[String, untyped]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,65 @@
1
+ module Capsium
2
+ class Package
3
+ # Loads, generates and verifies security.json (ARCHITECTURE.md
4
+ # section 6). Checksums cover every file in the package except
5
+ # security.json itself.
6
+ class Security
7
+ class IntegrityError < Capsium::Error
8
+ end
9
+
10
+ # A file whose SHA-256 does not match the recorded checksum.
11
+ class ChecksumMismatch
12
+ attr_reader path: String
13
+ attr_reader expected: String
14
+ attr_reader actual: String
15
+
16
+ def initialize: (path: String path, expected: String expected, actual: String actual) -> void
17
+
18
+ def message: () -> String
19
+ end
20
+
21
+ # A checksum listed for a file that is missing on disk.
22
+ class MissingFile
23
+ attr_reader path: String
24
+ attr_reader expected: String
25
+
26
+ def initialize: (path: String path, expected: String expected) -> void
27
+
28
+ def message: () -> String
29
+ end
30
+
31
+ # A file on disk not covered by any checksum.
32
+ class UncheckedFile
33
+ attr_reader path: String
34
+
35
+ def initialize: (path: String path) -> void
36
+
37
+ def message: () -> String
38
+ end
39
+
40
+ attr_reader path: String
41
+ attr_reader config: SecurityConfig?
42
+
43
+ def initialize: (String path, ?SecurityConfig? config) -> void
44
+
45
+ def self.generate: (String package_path) -> Security
46
+
47
+ def self.checksums_for: (String package_path) -> Hash[String, String]
48
+
49
+ def self.package_files: (String package_path) -> Array[String]
50
+
51
+ def present?: () -> bool
52
+
53
+ def checksums: () -> Hash[String, String]
54
+
55
+ def verify: (String package_path) -> Array[ChecksumMismatch | MissingFile | UncheckedFile]
56
+
57
+ def verify!: (String package_path) -> void
58
+
59
+ def save_to_file: (?String output_path) -> void
60
+
61
+ def to_json: (*untyped args) -> String
62
+ def to_hash: () -> Hash[String, untyped]
63
+ end
64
+ end
65
+ end