pray-cli 1.6.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 +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE.md +21 -0
- data/README.md +125 -0
- data/SECURITY.md +28 -0
- data/bin/polyrun +108 -0
- data/bin/pray +26 -0
- data/lib/pray/archive.rb +68 -0
- data/lib/pray/auth_client.rb +97 -0
- data/lib/pray/cli/commands/auth.rb +42 -0
- data/lib/pray/cli/commands/distribution.rb +17 -0
- data/lib/pray/cli/commands/init.rb +62 -0
- data/lib/pray/cli/commands/meta.rb +15 -0
- data/lib/pray/cli/commands/packages.rb +107 -0
- data/lib/pray/cli/commands/trust.rb +73 -0
- data/lib/pray/cli/commands/workflow.rb +126 -0
- data/lib/pray/cli/help.rb +168 -0
- data/lib/pray/cli/helpers.rb +166 -0
- data/lib/pray/cli/parse.rb +136 -0
- data/lib/pray/cli/parse_auth.rb +91 -0
- data/lib/pray/cli/parse_trust.rb +152 -0
- data/lib/pray/cli/suggest.rb +57 -0
- data/lib/pray/cli.rb +117 -0
- data/lib/pray/confess.rb +113 -0
- data/lib/pray/config.rb +52 -0
- data/lib/pray/constraint.rb +80 -0
- data/lib/pray/destination.rb +158 -0
- data/lib/pray/dotenv.rb +46 -0
- data/lib/pray/environment.rb +41 -0
- data/lib/pray/error.rb +55 -0
- data/lib/pray/format_manifest.rb +255 -0
- data/lib/pray/format_serialize.rb +135 -0
- data/lib/pray/git_sources.rb +228 -0
- data/lib/pray/hashing.rb +59 -0
- data/lib/pray/invocation.rb +110 -0
- data/lib/pray/literal.rb +382 -0
- data/lib/pray/lockfile.rb +259 -0
- data/lib/pray/lockfile_serialize.rb +125 -0
- data/lib/pray/manifest.rb +126 -0
- data/lib/pray/manifest_formatter.rb +56 -0
- data/lib/pray/manifest_json.rb +128 -0
- data/lib/pray/manifest_parser.rb +152 -0
- data/lib/pray/manifest_parser_blocks.rb +216 -0
- data/lib/pray/manifest_parser_helpers.rb +208 -0
- data/lib/pray/materialize.rb +120 -0
- data/lib/pray/package_spec.rb +245 -0
- data/lib/pray/path_safety.rb +41 -0
- data/lib/pray/plan.rb +83 -0
- data/lib/pray/project_context.rb +67 -0
- data/lib/pray/publish.rb +162 -0
- data/lib/pray/registry.rb +355 -0
- data/lib/pray/render.rb +360 -0
- data/lib/pray/resolve.rb +396 -0
- data/lib/pray/resolve_context.rb +19 -0
- data/lib/pray/serve.rb +130 -0
- data/lib/pray/serve_federation.rb +109 -0
- data/lib/pray/session.rb +90 -0
- data/lib/pray/ssh_agent.rb +115 -0
- data/lib/pray/statement_surface.rb +262 -0
- data/lib/pray/substitute.rb +50 -0
- data/lib/pray/sync.rb +219 -0
- data/lib/pray/terminal.rb +30 -0
- data/lib/pray/trust.rb +201 -0
- data/lib/pray/trust_feed.rb +110 -0
- data/lib/pray/trust_ops.rb +202 -0
- data/lib/pray/verify.rb +257 -0
- data/lib/pray/version.rb +6 -0
- data/lib/pray-cli.rb +4 -0
- data/lib/pray.rb +46 -0
- data/pray-cli.gemspec +48 -0
- metadata +187 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module LockfileSerialize
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def lockfile_to_toml(lockfile)
|
|
8
|
+
serialize_lockfile_text(lockfile.canonicalized)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def serialize_lockfile_text(lockfile)
|
|
12
|
+
lines = [
|
|
13
|
+
"prayfile_lock = #{format_string(lockfile.prayfile_lock)}",
|
|
14
|
+
"spec = #{format_string(lockfile.spec)}",
|
|
15
|
+
"generated_by = #{format_string(lockfile.generated_by)}",
|
|
16
|
+
"manifest_hash = #{format_string(lockfile.manifest_hash)}"
|
|
17
|
+
]
|
|
18
|
+
lines << "environment = #{format_string(lockfile.environment)}" if lockfile.environment
|
|
19
|
+
lines << ""
|
|
20
|
+
|
|
21
|
+
lockfile.source.each { |entry| append_section(lines, format_source(entry)) }
|
|
22
|
+
lockfile.package.each { |entry| append_section(lines, format_package(entry)) }
|
|
23
|
+
lockfile.target.each { |entry| append_section(lines, format_target(entry)) }
|
|
24
|
+
lockfile.managed_span.each { |entry| append_section(lines, format_managed_span(entry)) }
|
|
25
|
+
|
|
26
|
+
lines.pop while lines.last == ""
|
|
27
|
+
"#{lines.join("\n")}\n"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def append_section(lines, section_lines)
|
|
31
|
+
return if section_lines.empty?
|
|
32
|
+
|
|
33
|
+
lines.concat(section_lines, [""])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def format_source(entry)
|
|
37
|
+
lines = ["[[source]]"]
|
|
38
|
+
scalars = [
|
|
39
|
+
["name", entry.name],
|
|
40
|
+
["kind", entry.kind],
|
|
41
|
+
["url", entry.url]
|
|
42
|
+
]
|
|
43
|
+
scalars << ["revision", entry.revision] if entry.revision
|
|
44
|
+
scalars << ["host_key_fingerprint", entry.host_key_fingerprint] if entry.host_key_fingerprint
|
|
45
|
+
append_scalars(lines, scalars)
|
|
46
|
+
lines
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def format_package(entry)
|
|
50
|
+
lines = ["[[package]]"]
|
|
51
|
+
scalars = [
|
|
52
|
+
["name", entry.name],
|
|
53
|
+
["version", entry.version]
|
|
54
|
+
]
|
|
55
|
+
scalars << ["source", entry.source] unless entry.source.nil?
|
|
56
|
+
scalars.concat(
|
|
57
|
+
[
|
|
58
|
+
["path", entry.path],
|
|
59
|
+
["tree_hash", entry.tree_hash],
|
|
60
|
+
["artifact_hash", entry.artifact_hash],
|
|
61
|
+
["artifact", entry.artifact]
|
|
62
|
+
]
|
|
63
|
+
)
|
|
64
|
+
append_scalars(lines, scalars)
|
|
65
|
+
lines << "exports = #{format_string_array(entry.exports)}"
|
|
66
|
+
lines << "dependencies = #{format_string_array(entry.dependencies)}"
|
|
67
|
+
lines << "signer_fingerprint = #{format_string(entry.signer_fingerprint)}" if entry.signer_fingerprint
|
|
68
|
+
lines
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def format_target(entry)
|
|
72
|
+
lines = ["[[target]]"]
|
|
73
|
+
append_scalars(lines, [["name", entry.name]])
|
|
74
|
+
lines << "outputs = #{format_string_array(entry.outputs)}"
|
|
75
|
+
lines
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def format_managed_span(entry)
|
|
79
|
+
lines = ["[[managed_span]]"]
|
|
80
|
+
append_scalars(
|
|
81
|
+
lines,
|
|
82
|
+
[
|
|
83
|
+
["id", entry.id],
|
|
84
|
+
["target", entry.target],
|
|
85
|
+
["open_line", entry.open_line],
|
|
86
|
+
["close_line", entry.close_line],
|
|
87
|
+
["ideal_checksum", entry.ideal_checksum],
|
|
88
|
+
["package", entry.package],
|
|
89
|
+
["export", entry.export],
|
|
90
|
+
["source_checksum", entry.source_checksum],
|
|
91
|
+
["silenced", entry.silenced]
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
lines
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def append_scalars(lines, entries)
|
|
98
|
+
entries.each do |key, value|
|
|
99
|
+
lines << case value
|
|
100
|
+
when String
|
|
101
|
+
"#{key} = #{format_string(value)}"
|
|
102
|
+
when Integer
|
|
103
|
+
"#{key} = #{value}"
|
|
104
|
+
when TrueClass, FalseClass
|
|
105
|
+
"#{key} = #{value}"
|
|
106
|
+
else
|
|
107
|
+
raise ArgumentError, "unsupported lockfile scalar type for #{key}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def format_string(value)
|
|
113
|
+
escaped = value.gsub("\\", "\\\\").gsub("\"", "\\\"")
|
|
114
|
+
"\"#{escaped}\""
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def format_string_array(values)
|
|
118
|
+
return "[]" if values.empty?
|
|
119
|
+
return "[#{format_string(values.first)}]" if values.length == 1
|
|
120
|
+
|
|
121
|
+
items = values.map { |value| " #{format_string(value)}" }.join(",\n")
|
|
122
|
+
"[\n#{items},\n]"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "manifest_json"
|
|
4
|
+
require_relative "manifest_formatter"
|
|
5
|
+
require_relative "manifest_parser_helpers"
|
|
6
|
+
require_relative "manifest_parser_blocks"
|
|
7
|
+
require_relative "manifest_parser"
|
|
8
|
+
|
|
9
|
+
module Pray
|
|
10
|
+
RenderPolicy = Struct.new(
|
|
11
|
+
:mode, :conflict, :churn, :header, :section_markers, :line_endings
|
|
12
|
+
) do
|
|
13
|
+
def self.default
|
|
14
|
+
new(
|
|
15
|
+
mode: "managed",
|
|
16
|
+
conflict: "fail",
|
|
17
|
+
churn: "minimal",
|
|
18
|
+
header: true,
|
|
19
|
+
section_markers: true,
|
|
20
|
+
line_endings: "lf"
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ManifestSource = Struct.new(:name, :kind, :url, :subdir, :rev, :tag)
|
|
26
|
+
ManifestTarget = Struct.new(
|
|
27
|
+
:name, :outputs, :skills, :commands, :rules, :max_bytes, :mode, :scoped, :entries
|
|
28
|
+
) do
|
|
29
|
+
def initialize(
|
|
30
|
+
name:, outputs: [], skills: [], commands: [], rules: [], max_bytes: nil,
|
|
31
|
+
mode: "legacy", scoped: false, entries: []
|
|
32
|
+
)
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
ManifestPackage = Struct.new(
|
|
38
|
+
:name, :constraint, :source, :exports, :targets, :features, :groups, :optional,
|
|
39
|
+
:path, :git, :tag, :rev, :tarball, :oci, :file, :roles, :bound
|
|
40
|
+
) do
|
|
41
|
+
def initialize(
|
|
42
|
+
name:, constraint: "*", source: nil, exports: [], targets: [], features: [], groups: [],
|
|
43
|
+
optional: false, path: nil, git: nil, tag: nil, rev: nil, tarball: nil, oci: nil,
|
|
44
|
+
file: nil, roles: [], bound: false
|
|
45
|
+
)
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
ManifestLocal = Struct.new(:path, :position, :optional, :bound) do
|
|
51
|
+
def initialize(path:, position: "after", optional: false, bound: false)
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Manifest = Struct.new(
|
|
57
|
+
:prayfile_version, :sources, :targets, :packages, :local, :symbols, :render,
|
|
58
|
+
:deprecated_keywords,
|
|
59
|
+
keyword_init: true
|
|
60
|
+
) do
|
|
61
|
+
def initialize(
|
|
62
|
+
prayfile_version: "",
|
|
63
|
+
sources: [],
|
|
64
|
+
targets: [],
|
|
65
|
+
packages: [],
|
|
66
|
+
local: [],
|
|
67
|
+
symbols: {},
|
|
68
|
+
render: RenderPolicy.default,
|
|
69
|
+
deprecated_keywords: []
|
|
70
|
+
)
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def note_deprecated_keyword(keyword)
|
|
75
|
+
return unless %w[target output agent].include?(keyword)
|
|
76
|
+
self.deprecated_keywords ||= []
|
|
77
|
+
deprecated_keywords << keyword unless deprecated_keywords.include?(keyword)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def deprecation_warnings
|
|
81
|
+
replacements = {
|
|
82
|
+
"target" => "compose` / `tree",
|
|
83
|
+
"output" => "compose",
|
|
84
|
+
"agent" => "pray"
|
|
85
|
+
}
|
|
86
|
+
(deprecated_keywords || []).filter_map do |keyword|
|
|
87
|
+
replacement = replacements[keyword]
|
|
88
|
+
next unless replacement
|
|
89
|
+
|
|
90
|
+
"warning: `#{keyword}` is deprecated and will be removed in version 2; prefer `#{replacement}`"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def canonicalized
|
|
95
|
+
dup.tap do |copy|
|
|
96
|
+
copy.sources = sources.sort_by(&:name)
|
|
97
|
+
copy.targets = targets.sort_by(&:name)
|
|
98
|
+
copy.packages = packages.sort_by { |package| [package.name, package.source.to_s, package.constraint] }
|
|
99
|
+
copy.local = local.sort_by(&:path)
|
|
100
|
+
copy.deprecated_keywords = []
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def manifest_hash
|
|
105
|
+
bytes = ManifestJson.encode_compact(canonicalized)
|
|
106
|
+
Hashing.sha256_prefixed(bytes)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
module ManifestMethods
|
|
111
|
+
module_function
|
|
112
|
+
|
|
113
|
+
def read_manifest_text(manifest_path)
|
|
114
|
+
File.read(manifest_path)
|
|
115
|
+
rescue Errno::ENOENT
|
|
116
|
+
raise Error.manifest("missing #{manifest_path}; run pray init to create one")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def parse_manifest(text)
|
|
120
|
+
lines = Literal.prepare_parser_lines(text)
|
|
121
|
+
BlockParser.new(lines).parse_root
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
extend ManifestMethods
|
|
126
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ManifestMethods
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def format_package_declaration(package)
|
|
8
|
+
parts = ["pray \"#{package.name}\""]
|
|
9
|
+
parts << "\"#{package.constraint}\"" unless package.constraint == "*"
|
|
10
|
+
parts << "path: \"#{package.path}\"" if package.path
|
|
11
|
+
parts << "source: \"#{package.source}\"" if package.source
|
|
12
|
+
parts << "git: \"#{package.git}\"" if package.git
|
|
13
|
+
parts << "tag: \"#{package.tag}\"" if package.tag
|
|
14
|
+
parts << "rev: \"#{package.rev}\"" if package.rev
|
|
15
|
+
parts << "tarball: \"#{package.tarball}\"" if package.tarball
|
|
16
|
+
parts << "oci: \"#{package.oci}\"" if package.oci
|
|
17
|
+
parts << "file: \"#{package.file}\"" if package.file
|
|
18
|
+
unless package.exports.empty?
|
|
19
|
+
parts << if package.exports.length == 1
|
|
20
|
+
"export: \"#{package.exports.first}\""
|
|
21
|
+
else
|
|
22
|
+
"exports: [#{format_string_keyword_list(package.exports)}]"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
parts << "targets: [#{format_string_keyword_list(package.targets)}]" unless package.targets.empty?
|
|
26
|
+
parts << "features: [#{format_string_keyword_list(package.features)}]" unless package.features.empty?
|
|
27
|
+
parts << "optional: true" if package.optional
|
|
28
|
+
parts.join(", ")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def replace_package_declaration(text, package)
|
|
32
|
+
name = package.name
|
|
33
|
+
prefixes = [
|
|
34
|
+
"pray \"#{name}\"", "pray '#{name}'",
|
|
35
|
+
"use \"#{name}\"", "include \"#{name}\"",
|
|
36
|
+
"agent \"#{name}\"", "agent '#{name}'",
|
|
37
|
+
"package \"#{name}\"", "package '#{name}'"
|
|
38
|
+
]
|
|
39
|
+
lines = text.lines.map(&:chomp)
|
|
40
|
+
index = lines.index { |line|
|
|
41
|
+
trimmed = line.lstrip
|
|
42
|
+
prefixes.any? { |prefix| trimmed.start_with?(prefix) }
|
|
43
|
+
}
|
|
44
|
+
raise Error.manifest("package #{name} not found in manifest") unless index
|
|
45
|
+
|
|
46
|
+
lines[index] = format_package_declaration(package)
|
|
47
|
+
output = lines.join("\n")
|
|
48
|
+
output += "\n" if text.end_with?("\n") && !output.end_with?("\n")
|
|
49
|
+
output
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def format_string_keyword_list(values)
|
|
53
|
+
values.map { |value| "\"#{value}\"" }.join(", ")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ManifestJson
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def encode_compact(manifest)
|
|
8
|
+
encode_object(manifest_fields(manifest))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def manifest_fields(manifest)
|
|
12
|
+
fields = {
|
|
13
|
+
"prayfile_version" => manifest.prayfile_version,
|
|
14
|
+
"sources" => manifest.sources.map { |source| source_fields(source) },
|
|
15
|
+
"targets" => manifest.targets.map { |target| target_fields(target) },
|
|
16
|
+
"packages" => manifest.packages.map { |package| package_fields(package) },
|
|
17
|
+
"local" => manifest.local.map { |entry| local_fields(entry) },
|
|
18
|
+
"render" => render_fields(manifest.render)
|
|
19
|
+
}
|
|
20
|
+
fields["symbols"] = manifest.symbols.sort.to_h unless manifest.symbols.empty?
|
|
21
|
+
fields
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def source_fields(source)
|
|
25
|
+
fields = {
|
|
26
|
+
"name" => source.name,
|
|
27
|
+
"kind" => source.kind,
|
|
28
|
+
"url" => source.url
|
|
29
|
+
}
|
|
30
|
+
fields["subdir"] = source.subdir if source.subdir
|
|
31
|
+
fields["rev"] = source.rev if source.rev
|
|
32
|
+
fields["tag"] = source.tag if source.tag
|
|
33
|
+
fields
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def target_fields(target)
|
|
37
|
+
{
|
|
38
|
+
"name" => target.name,
|
|
39
|
+
"outputs" => target.outputs,
|
|
40
|
+
"skills" => target.skills,
|
|
41
|
+
"commands" => target.commands,
|
|
42
|
+
"rules" => target.rules,
|
|
43
|
+
"max_bytes" => target.max_bytes,
|
|
44
|
+
"mode" => target.mode || "legacy",
|
|
45
|
+
"scoped" => target.scoped || false,
|
|
46
|
+
"entries" => (target.entries || []).map { |entry| entry_fields(entry) }
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def entry_fields(entry)
|
|
51
|
+
if entry.kind == "package"
|
|
52
|
+
{"kind" => "package", "name" => entry.name}
|
|
53
|
+
else
|
|
54
|
+
{"kind" => "local", "path" => entry.path}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def package_fields(package)
|
|
59
|
+
fields = {
|
|
60
|
+
"name" => package.name,
|
|
61
|
+
"constraint" => package.constraint,
|
|
62
|
+
"source" => package.source,
|
|
63
|
+
"exports" => package.exports,
|
|
64
|
+
"targets" => package.targets,
|
|
65
|
+
"features" => package.features,
|
|
66
|
+
"groups" => package.groups,
|
|
67
|
+
"optional" => package.optional,
|
|
68
|
+
"path" => package.path,
|
|
69
|
+
"git" => package.git,
|
|
70
|
+
"tag" => package.tag,
|
|
71
|
+
"rev" => package.rev,
|
|
72
|
+
"tarball" => package.tarball,
|
|
73
|
+
"oci" => package.oci
|
|
74
|
+
}
|
|
75
|
+
fields["file"] = package.file if package.file
|
|
76
|
+
fields["roles"] = package.roles unless package.roles.nil? || package.roles.empty?
|
|
77
|
+
fields["bound"] = true if package.bound
|
|
78
|
+
fields
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def local_fields(entry)
|
|
82
|
+
fields = {
|
|
83
|
+
"path" => entry.path,
|
|
84
|
+
"position" => entry.position,
|
|
85
|
+
"optional" => entry.optional
|
|
86
|
+
}
|
|
87
|
+
fields["bound"] = true if entry.bound
|
|
88
|
+
fields
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_fields(render)
|
|
92
|
+
{
|
|
93
|
+
"mode" => render.mode,
|
|
94
|
+
"conflict" => render.conflict,
|
|
95
|
+
"churn" => render.churn,
|
|
96
|
+
"header" => render.header,
|
|
97
|
+
"section_markers" => render.section_markers,
|
|
98
|
+
"line_endings" => render.line_endings
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def encode_object(hash)
|
|
103
|
+
entries = hash.map { |key, value| "#{encode_string(key)}:#{encode_value(value)}" }
|
|
104
|
+
"{#{entries.join(",")}}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def encode_array(array)
|
|
108
|
+
"[#{array.map { |value| encode_value(value) }.join(",")}]"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def encode_value(value)
|
|
112
|
+
case value
|
|
113
|
+
when Hash then encode_object(value)
|
|
114
|
+
when Array then encode_array(value)
|
|
115
|
+
when String then encode_string(value)
|
|
116
|
+
when TrueClass, FalseClass then value.to_s
|
|
117
|
+
when NilClass then "null"
|
|
118
|
+
when Integer then value.to_s
|
|
119
|
+
else
|
|
120
|
+
raise Error.manifest("unsupported JSON value: #{value.inspect}")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def encode_string(text)
|
|
125
|
+
"\"#{text.gsub("\\", "\\\\").gsub("\"", "\\\"").gsub("\n", "\\n").gsub("\r", "\\r").gsub("\t", "\\t")}\""
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ManifestMethods
|
|
5
|
+
class BlockParser
|
|
6
|
+
include ParserHelpers
|
|
7
|
+
include ParserBlocks
|
|
8
|
+
|
|
9
|
+
def initialize(lines)
|
|
10
|
+
@lines = lines
|
|
11
|
+
@cursor = 0
|
|
12
|
+
@group_stack = []
|
|
13
|
+
@surface = StatementSurface::Reader.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parse_root
|
|
17
|
+
manifest = Manifest.new
|
|
18
|
+
while (statement = next_statement)
|
|
19
|
+
raise Error.parse("manifest", "unexpected 'end'") if statement == "end"
|
|
20
|
+
|
|
21
|
+
apply_statement(manifest, statement, false)
|
|
22
|
+
end
|
|
23
|
+
raise Error.manifest("missing prayfile version") if manifest.prayfile_version.empty?
|
|
24
|
+
|
|
25
|
+
manifest
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_nested(manifest, stop_on_end:)
|
|
29
|
+
while (statement = next_statement)
|
|
30
|
+
return if statement == "end" && stop_on_end
|
|
31
|
+
raise Error.parse("manifest", "unexpected 'end'") if statement == "end"
|
|
32
|
+
|
|
33
|
+
apply_statement(manifest, statement, true)
|
|
34
|
+
end
|
|
35
|
+
raise Error.parse("manifest", "missing 'end'") if stop_on_end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def apply_statement(manifest, statement, allow_target)
|
|
39
|
+
case statement
|
|
40
|
+
when /\Aprayfile (.+)\z/
|
|
41
|
+
manifest.prayfile_version = string_from_literal(Regexp.last_match(1))
|
|
42
|
+
when /\Asource (.+)\z/
|
|
43
|
+
manifest.sources << parse_source(Regexp.last_match(1))
|
|
44
|
+
when /\Atarget (.+)\z/
|
|
45
|
+
apply_legacy_target(manifest, statement, Regexp.last_match(1), allow_target)
|
|
46
|
+
when /\Agroup (.+)\z/
|
|
47
|
+
apply_group(manifest, Regexp.last_match(1))
|
|
48
|
+
when "pray do", "template do"
|
|
49
|
+
parse_symbols_block(manifest)
|
|
50
|
+
when /\A(?:compose|output) (.+)\z/
|
|
51
|
+
apply_compose_or_output(manifest, statement, Regexp.last_match(1))
|
|
52
|
+
when /\A(?:tree|folder|skills) (.+)\z/
|
|
53
|
+
apply_tree_or_folder(manifest, statement, Regexp.last_match(1))
|
|
54
|
+
when /\Afile (.+)\z/
|
|
55
|
+
parse_file_block(manifest, Regexp.last_match(1))
|
|
56
|
+
when /\Aagent (.+)\z/
|
|
57
|
+
apply_agent_package(manifest, Regexp.last_match(1))
|
|
58
|
+
when /\Apackage (.+)\z/
|
|
59
|
+
Destination.upsert_package(manifest, parse_package_with_groups(Regexp.last_match(1)))
|
|
60
|
+
when /\A(?:pray|use|include) (.+)\z/
|
|
61
|
+
apply_pray_statement(manifest, Regexp.last_match(1), nil)
|
|
62
|
+
when /\Alocal (.+)\z/
|
|
63
|
+
apply_unbound_local(manifest, Regexp.last_match(1))
|
|
64
|
+
when /\Arender (.+)\z/
|
|
65
|
+
manifest.render = parse_render_policy(Regexp.last_match(1))
|
|
66
|
+
else
|
|
67
|
+
raise Error.parse("manifest", "unrecognized statement: #{statement}")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def apply_legacy_target(manifest, statement, rest, allow_target)
|
|
72
|
+
raise Error.parse("manifest", "target must use a block") if !allow_target && !statement.end_with?(" do")
|
|
73
|
+
|
|
74
|
+
manifest.note_deprecated_keyword("target")
|
|
75
|
+
target, is_block = parse_target_header(rest)
|
|
76
|
+
manifest.targets << target
|
|
77
|
+
parse_target_block(manifest, manifest.targets.length - 1) if is_block
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def apply_group(manifest, rest)
|
|
81
|
+
group_names, is_block = parse_group_header(rest)
|
|
82
|
+
raise Error.parse("manifest", "group must use a block") unless is_block
|
|
83
|
+
raise Error.parse("manifest", "nested group blocks are not supported") if @group_stack.any?
|
|
84
|
+
|
|
85
|
+
@group_stack.push(group_names)
|
|
86
|
+
parse_group_block(manifest)
|
|
87
|
+
@group_stack.pop
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def apply_compose_or_output(manifest, statement, rest)
|
|
91
|
+
if statement.start_with?("output ")
|
|
92
|
+
unless statement.end_with?(" do")
|
|
93
|
+
raise Error.parse(
|
|
94
|
+
"manifest",
|
|
95
|
+
"top-level output must use a compose block (output \"path\" do ... end)"
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
manifest.note_deprecated_keyword("output")
|
|
99
|
+
end
|
|
100
|
+
parse_destination_block(manifest, rest, "compose")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def apply_tree_or_folder(manifest, statement, rest)
|
|
104
|
+
if statement.start_with?("folder ", "skills ") && !statement.end_with?(" do")
|
|
105
|
+
raise Error.parse("manifest", "top-level folder/skills must use a tree block")
|
|
106
|
+
end
|
|
107
|
+
parse_destination_block(manifest, rest, "tree")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def apply_agent_package(manifest, rest)
|
|
111
|
+
manifest.note_deprecated_keyword("agent")
|
|
112
|
+
Destination.upsert_package(manifest, parse_package_with_groups(rest))
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def apply_unbound_local(manifest, rest)
|
|
116
|
+
local = parse_local_decl(rest)
|
|
117
|
+
local.bound = false
|
|
118
|
+
Destination.upsert_local(manifest, local)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parse_package_with_groups(rest)
|
|
122
|
+
package = parse_package_decl(rest)
|
|
123
|
+
package.groups = @group_stack.last&.dup || []
|
|
124
|
+
package
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def next_statement
|
|
128
|
+
pending = @surface.next
|
|
129
|
+
return pending if pending
|
|
130
|
+
|
|
131
|
+
while @cursor < @lines.length
|
|
132
|
+
statement = @lines[@cursor].strip
|
|
133
|
+
@cursor += 1
|
|
134
|
+
next if statement.empty?
|
|
135
|
+
|
|
136
|
+
while !statement.end_with?(" do") && statement != "end" && @cursor < @lines.length &&
|
|
137
|
+
(statement.rstrip.end_with?(",") || !Literal.is_balanced?(statement))
|
|
138
|
+
next_line = @lines[@cursor].strip
|
|
139
|
+
@cursor += 1
|
|
140
|
+
next if next_line.empty?
|
|
141
|
+
|
|
142
|
+
statement = "#{statement} #{next_line}"
|
|
143
|
+
end
|
|
144
|
+
@surface.push_raw(statement)
|
|
145
|
+
pending = @surface.next
|
|
146
|
+
return pending if pending
|
|
147
|
+
end
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|