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,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ManifestMethods
|
|
5
|
+
module ParserBlocks
|
|
6
|
+
def parse_symbols_block(manifest)
|
|
7
|
+
while (statement = next_statement)
|
|
8
|
+
return if statement == "end"
|
|
9
|
+
|
|
10
|
+
assignment = StatementSurface.split_symbol_assignment(statement)
|
|
11
|
+
unless assignment
|
|
12
|
+
raise Error.parse(
|
|
13
|
+
"manifest",
|
|
14
|
+
"unsupported statement inside pray/template block: #{statement}"
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
key, value_literal = assignment
|
|
19
|
+
unless Substitute.pray_symbol_key?(key)
|
|
20
|
+
raise Error.parse("manifest", "invalid pray symbol key `#{key}`")
|
|
21
|
+
end
|
|
22
|
+
if manifest.symbols.key?(key)
|
|
23
|
+
raise Error.parse("manifest", "duplicate pray symbol `#{key}`")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
manifest.symbols[key] = string_from_literal(value_literal)
|
|
27
|
+
end
|
|
28
|
+
raise Error.parse("manifest", "missing 'end' for pray/template block")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_group_block(manifest)
|
|
32
|
+
while (statement = next_statement)
|
|
33
|
+
return if statement == "end"
|
|
34
|
+
if (match = statement.match(/\Aagent (.+)\z/))
|
|
35
|
+
manifest.note_deprecated_keyword("agent")
|
|
36
|
+
Destination.upsert_package(manifest, parse_package_with_groups(match[1]))
|
|
37
|
+
elsif (match = statement.match(/\A(?:package|pray|use|include) (.+)\z/))
|
|
38
|
+
Destination.upsert_package(manifest, parse_package_with_groups(match[1]))
|
|
39
|
+
elsif /\Agroup (.+)\z/.match?(statement)
|
|
40
|
+
raise Error.parse("manifest", "nested group blocks are not supported")
|
|
41
|
+
else
|
|
42
|
+
raise Error.parse(
|
|
43
|
+
"manifest",
|
|
44
|
+
"group blocks only support agent, package, or pray declarations: #{statement}"
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
raise Error.parse("manifest", "missing 'end' for group block")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_destination_block(manifest, rest, mode)
|
|
52
|
+
unless rest.rstrip.end_with?("do")
|
|
53
|
+
label = (mode == "compose") ? "compose" : "tree"
|
|
54
|
+
raise Error.parse("manifest", "#{label} must use a block")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
header = rest.sub(/\s*do\z/, "").strip
|
|
58
|
+
values, = parse_call(header)
|
|
59
|
+
raise Error.parse("manifest", "destination missing path") if values.empty?
|
|
60
|
+
|
|
61
|
+
path = string_from_value(values.first)
|
|
62
|
+
manifest.targets << Destination.new_destination_target(mode, path)
|
|
63
|
+
index = manifest.targets.length - 1
|
|
64
|
+
parse_destination_body(manifest, index, mode)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parse_destination_body(manifest, index, mode)
|
|
68
|
+
while (statement = next_statement)
|
|
69
|
+
return if statement == "end"
|
|
70
|
+
|
|
71
|
+
apply_destination_statement(manifest, statement, index, mode)
|
|
72
|
+
end
|
|
73
|
+
raise Error.parse("manifest", "missing 'end' for destination block")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def apply_destination_statement(manifest, statement, index, mode)
|
|
77
|
+
if (match = statement.match(/\Aagent (.+)\z/))
|
|
78
|
+
manifest.note_deprecated_keyword("agent")
|
|
79
|
+
apply_pray_statement(manifest, match[1], index)
|
|
80
|
+
return
|
|
81
|
+
end
|
|
82
|
+
if (match = statement.match(/\A(?:pray|use|include|package) (.+)\z/))
|
|
83
|
+
apply_pray_statement(manifest, match[1], index)
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
if mode == "compose" && (match = statement.match(/\Alocal (.+)\z/))
|
|
87
|
+
bind_compose_local(manifest, index, match[1])
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
raise Error.parse("manifest", "unsupported statement inside destination block: #{statement}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def bind_compose_local(manifest, index, rest)
|
|
94
|
+
local = parse_local_decl(rest)
|
|
95
|
+
local.bound = true
|
|
96
|
+
Destination.bind_local_entry(manifest.targets[index], local.path)
|
|
97
|
+
Destination.upsert_local(manifest, local)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parse_file_block(manifest, rest)
|
|
101
|
+
unless rest.rstrip.end_with?("do")
|
|
102
|
+
raise Error.parse("manifest", "file must use a block (or use pray ..., file: \"path\")")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
header = rest.sub(/\s*do\z/, "").strip
|
|
106
|
+
values, = parse_call(header)
|
|
107
|
+
raise Error.parse("manifest", "file block missing path") if values.empty?
|
|
108
|
+
|
|
109
|
+
parse_file_body(manifest, string_from_value(values.first))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parse_file_body(manifest, file_path)
|
|
113
|
+
saw_package = false
|
|
114
|
+
while (statement = next_statement)
|
|
115
|
+
if statement == "end"
|
|
116
|
+
unless saw_package
|
|
117
|
+
raise Error.parse("manifest", "file block requires a pray package declaration")
|
|
118
|
+
end
|
|
119
|
+
return
|
|
120
|
+
end
|
|
121
|
+
saw_package = true if apply_file_body_statement(manifest, statement, file_path)
|
|
122
|
+
end
|
|
123
|
+
raise Error.parse("manifest", "missing 'end' for file block")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def apply_file_body_statement(manifest, statement, file_path)
|
|
127
|
+
if (match = statement.match(/\Aagent (.+)\z/))
|
|
128
|
+
manifest.note_deprecated_keyword("agent")
|
|
129
|
+
bind_file_package(manifest, match[1], file_path)
|
|
130
|
+
return true
|
|
131
|
+
end
|
|
132
|
+
if (match = statement.match(/\A(?:pray|use|include|package) (.+)\z/))
|
|
133
|
+
bind_file_package(manifest, match[1], file_path)
|
|
134
|
+
return true
|
|
135
|
+
end
|
|
136
|
+
raise Error.parse("manifest", "unsupported statement inside file block: #{statement}")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def bind_file_package(manifest, rest, file_path)
|
|
140
|
+
package = parse_package_with_groups(rest)
|
|
141
|
+
if package.file
|
|
142
|
+
raise Error.parse("manifest", "file: keyword is invalid inside a file block")
|
|
143
|
+
end
|
|
144
|
+
package.file = file_path
|
|
145
|
+
package.bound = true
|
|
146
|
+
package.roles << "file" unless package.roles.include?("file")
|
|
147
|
+
Destination.upsert_package(manifest, package)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def apply_pray_statement(manifest, rest, destination_index)
|
|
151
|
+
values, keywords = parse_call(rest)
|
|
152
|
+
raise Error.parse("manifest", "pray missing package or path") if values.empty?
|
|
153
|
+
|
|
154
|
+
first = string_from_value(values.first)
|
|
155
|
+
return if apply_local_pray_path(manifest, first, keywords, values, destination_index)
|
|
156
|
+
|
|
157
|
+
package = parse_package_with_groups(rest)
|
|
158
|
+
bind_pray_package(manifest, package, destination_index)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def apply_local_pray_path(manifest, first, keywords, values, destination_index)
|
|
162
|
+
return false if package_signal?(values, keywords)
|
|
163
|
+
return false unless Destination.local_path_form?(first)
|
|
164
|
+
|
|
165
|
+
in_compose = destination_index &&
|
|
166
|
+
manifest.targets[destination_index]&.mode == "compose"
|
|
167
|
+
unless in_compose
|
|
168
|
+
raise Error.parse("manifest", "local pray paths are only valid inside compose blocks")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
local = ManifestLocal.new(path: first, position: "after", optional: false, bound: true)
|
|
172
|
+
Destination.bind_local_entry(manifest.targets[destination_index], local.path)
|
|
173
|
+
Destination.upsert_local(manifest, local)
|
|
174
|
+
true
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def package_signal?(values, keywords)
|
|
178
|
+
return true if values.length > 1
|
|
179
|
+
|
|
180
|
+
%w[source export exports file optional path git tag rev tarball oci targets features]
|
|
181
|
+
.any? { |key| keywords.key?(key) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def bind_pray_package(manifest, package, destination_index)
|
|
185
|
+
if package.file
|
|
186
|
+
if destination_index
|
|
187
|
+
raise Error.parse("manifest", "file: is mutually exclusive with compose/tree nesting")
|
|
188
|
+
end
|
|
189
|
+
package.bound = true
|
|
190
|
+
package.roles << "file" unless package.roles.include?("file")
|
|
191
|
+
end
|
|
192
|
+
if destination_index
|
|
193
|
+
mode = manifest.targets[destination_index].mode
|
|
194
|
+
package.bound = true
|
|
195
|
+
role = Destination.role_for_destination(mode)
|
|
196
|
+
package.roles << role if role && !package.roles.include?(role)
|
|
197
|
+
Destination.bind_package_entry(manifest.targets[destination_index], package.name)
|
|
198
|
+
end
|
|
199
|
+
Destination.upsert_package(manifest, package)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def parse_target_block(manifest, target_index)
|
|
203
|
+
while (statement = next_statement)
|
|
204
|
+
return if statement == "end"
|
|
205
|
+
|
|
206
|
+
target = manifest.targets[target_index]
|
|
207
|
+
raise Error.manifest("target index out of range") unless target
|
|
208
|
+
|
|
209
|
+
manifest.note_deprecated_keyword("output") if statement.start_with?("output ")
|
|
210
|
+
apply_target_statement(target, statement)
|
|
211
|
+
end
|
|
212
|
+
raise Error.parse("manifest", "missing 'end' for target block")
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ManifestMethods
|
|
5
|
+
module ParserHelpers
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def parse_call(rest)
|
|
9
|
+
positional = []
|
|
10
|
+
keywords = {}
|
|
11
|
+
Literal.split_top_level(rest.strip.sub(/,\z/, ""), ",").each do |segment|
|
|
12
|
+
if (keyword = parse_keyword_segment(segment))
|
|
13
|
+
keywords[keyword[0]] = keyword[1]
|
|
14
|
+
elsif !segment.empty?
|
|
15
|
+
positional << Literal.parse_literal(segment)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
[positional, keywords]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parse_keyword_segment(segment)
|
|
22
|
+
if (index = Literal.find_top_level(segment, "=>"))
|
|
23
|
+
key = string_from_literal(segment[0...index].strip)
|
|
24
|
+
return [key, Literal.parse_literal(segment[(index + 2)..].strip)]
|
|
25
|
+
end
|
|
26
|
+
if (index = Literal.find_top_level(segment, ":"))
|
|
27
|
+
left = segment[0...index].strip
|
|
28
|
+
right = segment[(index + 1)..].strip
|
|
29
|
+
return nil if left.empty?
|
|
30
|
+
|
|
31
|
+
return [left.delete_prefix(":"), Literal.parse_literal(right)]
|
|
32
|
+
end
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def keyword_array(keywords, key)
|
|
37
|
+
keywords[key]&.as_array&.filter_map(&:as_string) || []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def string_from_value(value)
|
|
41
|
+
text = value.as_string
|
|
42
|
+
raise Error.parse("manifest", "expected string-like literal, found #{value.inspect}") unless text
|
|
43
|
+
|
|
44
|
+
text
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def string_from_literal(input)
|
|
48
|
+
string_from_value(Literal.parse_literal(input))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_source(rest)
|
|
52
|
+
values, keywords = parse_call(rest)
|
|
53
|
+
raise Error.parse("manifest", "source requires a name") if values.empty?
|
|
54
|
+
if values.length < 2 && !keywords.key?("path") && !keywords.key?("git")
|
|
55
|
+
raise Error.parse("manifest", "source requires a name and url, path:, or git:")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
kind, url = source_kind_and_url(values, keywords)
|
|
59
|
+
ManifestSource.new(
|
|
60
|
+
name: string_from_value(values.first),
|
|
61
|
+
kind: kind,
|
|
62
|
+
url: url,
|
|
63
|
+
subdir: keyword_string(keywords, "subdir") || keyword_string(keywords, "distribution"),
|
|
64
|
+
rev: keyword_string(keywords, "rev"),
|
|
65
|
+
tag: keyword_string(keywords, "tag")
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def source_kind_and_url(values, keywords)
|
|
70
|
+
if keywords["path"]
|
|
71
|
+
return ["path", string_from_value(keywords["path"])]
|
|
72
|
+
end
|
|
73
|
+
if keywords["git"]
|
|
74
|
+
url = string_from_value(keywords["git"])
|
|
75
|
+
url = "git+#{url}" unless url.start_with?("git+")
|
|
76
|
+
return ["git", url]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
url = string_from_value(values[1])
|
|
80
|
+
[infer_source_kind(url), url]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def infer_source_kind(url)
|
|
84
|
+
return "git" if url.start_with?("git+")
|
|
85
|
+
return "pray_ssh" if url.start_with?("pray+ssh://", "ssh+pray://")
|
|
86
|
+
|
|
87
|
+
"registry"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def keyword_string(keywords, key)
|
|
91
|
+
keywords[key]&.then { |value| string_from_value(value) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parse_target_header(rest)
|
|
95
|
+
is_block = rest.rstrip.end_with?("do")
|
|
96
|
+
header = rest.sub(/\s*do\z/, "").strip
|
|
97
|
+
values, keywords = parse_call(header)
|
|
98
|
+
name = string_from_value(values.first)
|
|
99
|
+
outputs = keyword_array(keywords, "output")
|
|
100
|
+
folders = keyword_array(keywords, "folder") + keyword_array(keywords, "skills")
|
|
101
|
+
target = ManifestTarget.new(
|
|
102
|
+
name: name,
|
|
103
|
+
outputs: outputs,
|
|
104
|
+
skills: folders,
|
|
105
|
+
commands: keyword_array(keywords, "commands"),
|
|
106
|
+
rules: keyword_array(keywords, "rules"),
|
|
107
|
+
max_bytes: keywords["max_bytes"]&.as_integer
|
|
108
|
+
)
|
|
109
|
+
[target, is_block]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parse_group_header(rest)
|
|
113
|
+
is_block = rest.rstrip.end_with?("do")
|
|
114
|
+
header = rest.sub(/\s*do\z/, "").strip
|
|
115
|
+
values, = parse_call(header)
|
|
116
|
+
raise Error.parse("manifest", "group missing name") if values.empty?
|
|
117
|
+
|
|
118
|
+
names = values.map { |value| string_from_value(value) }
|
|
119
|
+
[names, is_block]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def parse_package_decl(rest)
|
|
123
|
+
values, keywords = parse_call(rest)
|
|
124
|
+
raise Error.parse("manifest", "agent missing name") if values.empty?
|
|
125
|
+
|
|
126
|
+
file = keywords["file"]&.as_string
|
|
127
|
+
ManifestPackage.new(
|
|
128
|
+
name: string_from_value(values[0]),
|
|
129
|
+
constraint: package_constraint(values),
|
|
130
|
+
source: keywords["source"]&.as_string,
|
|
131
|
+
exports: package_exports(keywords),
|
|
132
|
+
targets: keyword_array(keywords, "targets"),
|
|
133
|
+
features: keyword_array(keywords, "features"),
|
|
134
|
+
optional: keywords["optional"]&.as_bool || false,
|
|
135
|
+
path: keywords["path"]&.as_string,
|
|
136
|
+
git: keywords["git"]&.as_string,
|
|
137
|
+
tag: keywords["tag"]&.as_string,
|
|
138
|
+
rev: keywords["rev"]&.as_string,
|
|
139
|
+
tarball: keywords["tarball"]&.as_string,
|
|
140
|
+
oci: keywords["oci"]&.as_string,
|
|
141
|
+
file: file,
|
|
142
|
+
roles: file ? ["file"] : [],
|
|
143
|
+
bound: false
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def package_constraint(values)
|
|
148
|
+
return "*" unless values[1]
|
|
149
|
+
|
|
150
|
+
Constraint.normalize_version_constraint(string_from_value(values[1]))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def package_exports(keywords)
|
|
154
|
+
exports = keyword_array(keywords, "exports")
|
|
155
|
+
if (export = keywords["export"]&.as_string)
|
|
156
|
+
exports << export unless exports.include?(export)
|
|
157
|
+
end
|
|
158
|
+
exports
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def parse_local_decl(rest)
|
|
162
|
+
values, keywords = parse_call(rest)
|
|
163
|
+
ManifestLocal.new(
|
|
164
|
+
path: string_from_value(values.first),
|
|
165
|
+
position: keywords["position"]&.as_string || keywords["at"]&.as_string || "after",
|
|
166
|
+
optional: keywords["optional"]&.as_bool || false,
|
|
167
|
+
bound: false
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def parse_render_policy(rest)
|
|
172
|
+
_, keywords = parse_call(rest)
|
|
173
|
+
RenderPolicy.new(
|
|
174
|
+
mode: keywords["mode"]&.as_string || "managed",
|
|
175
|
+
conflict: keywords["conflict"]&.as_string || "fail",
|
|
176
|
+
churn: keywords["churn"]&.as_string || "minimal",
|
|
177
|
+
header: keyword_bool(keywords, "header", true),
|
|
178
|
+
section_markers: keyword_bool(keywords, "section_markers", true),
|
|
179
|
+
line_endings: keywords["line_endings"]&.as_string || "lf"
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def keyword_bool(keywords, key, default)
|
|
184
|
+
value = keywords[key]
|
|
185
|
+
return default if value.nil?
|
|
186
|
+
|
|
187
|
+
value.as_bool
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def apply_target_statement(target, statement)
|
|
191
|
+
case statement
|
|
192
|
+
when /\Aoutput (.+)\z/
|
|
193
|
+
target.outputs << string_from_literal(Regexp.last_match(1))
|
|
194
|
+
when /\Afolder (.+)\z/, /\Askills (.+)\z/
|
|
195
|
+
target.skills << string_from_literal(Regexp.last_match(1))
|
|
196
|
+
when /\Acommands (.+)\z/
|
|
197
|
+
target.commands << string_from_literal(Regexp.last_match(1))
|
|
198
|
+
when /\Arules (.+)\z/
|
|
199
|
+
target.rules << string_from_literal(Regexp.last_match(1))
|
|
200
|
+
when /\Amax_bytes (.+)\z/
|
|
201
|
+
target.max_bytes = Literal.parse_literal(Regexp.last_match(1).strip).as_integer
|
|
202
|
+
else
|
|
203
|
+
raise Error.parse("manifest", "unrecognized target statement: #{statement}")
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module Materialize
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def materialize_project(
|
|
8
|
+
manifest_path: nil,
|
|
9
|
+
frozen: false,
|
|
10
|
+
locked: false,
|
|
11
|
+
offline: false,
|
|
12
|
+
refresh: false
|
|
13
|
+
)
|
|
14
|
+
context = Invocation.invocation_context
|
|
15
|
+
manifest_path = File.expand_path(manifest_path || context.manifest_path)
|
|
16
|
+
unless File.exist?(manifest_path)
|
|
17
|
+
raise Error.manifest("missing #{manifest_path}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
project_root = context.project_root
|
|
21
|
+
options = ResolveOptions.new(
|
|
22
|
+
offline: offline,
|
|
23
|
+
refresh: refresh,
|
|
24
|
+
environment: context.environment
|
|
25
|
+
)
|
|
26
|
+
allow_git_refresh_fallback = !locked && !frozen
|
|
27
|
+
project = begin
|
|
28
|
+
Resolve.resolve_project_in_context(manifest_path, project_root, options)
|
|
29
|
+
rescue Error => error
|
|
30
|
+
if allow_git_refresh_fallback &&
|
|
31
|
+
!offline &&
|
|
32
|
+
!refresh &&
|
|
33
|
+
Resolve.resolution_may_benefit_from_git_source_refresh?(error)
|
|
34
|
+
refreshed = options.dup
|
|
35
|
+
refreshed.refresh = true
|
|
36
|
+
Resolve.resolve_project_in_context(manifest_path, project_root, refreshed)
|
|
37
|
+
else
|
|
38
|
+
raise
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
rendered = Render.render_project(project)
|
|
42
|
+
lockfile_path = default_lockfile_path(project.project_root)
|
|
43
|
+
next_lockfile = LockfileIO.build_lockfile(
|
|
44
|
+
project.manifest_hash,
|
|
45
|
+
project.environment,
|
|
46
|
+
project.project_root,
|
|
47
|
+
project.manifest.sources,
|
|
48
|
+
project.manifest.targets,
|
|
49
|
+
rendered,
|
|
50
|
+
project.packages,
|
|
51
|
+
project.source_revisions,
|
|
52
|
+
project.source_host_keys
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if locked
|
|
56
|
+
unless File.exist?(lockfile_path)
|
|
57
|
+
raise Error.verify("missing Prayfile.lock; run install first")
|
|
58
|
+
end
|
|
59
|
+
existing = Pray.read_lockfile(lockfile_path)
|
|
60
|
+
unless Pray.lockfiles_equivalent?(existing, next_lockfile)
|
|
61
|
+
raise Error.verify("lockfile needs update; rerun install to refresh Prayfile.lock")
|
|
62
|
+
end
|
|
63
|
+
unless frozen
|
|
64
|
+
Render.write_rendered_targets(project, rendered)
|
|
65
|
+
end
|
|
66
|
+
if frozen
|
|
67
|
+
rendered.each do |target|
|
|
68
|
+
path = File.join(project.project_root, target.path)
|
|
69
|
+
on_disk = File.read(path)
|
|
70
|
+
if on_disk != target.content
|
|
71
|
+
raise Error.render(
|
|
72
|
+
"#{path} is stale; rerun install to regenerate it or plan to inspect the diff"
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if frozen
|
|
81
|
+
existing = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
|
|
82
|
+
if existing
|
|
83
|
+
rendered.each do |target|
|
|
84
|
+
output_path = File.join(project.project_root, target.path)
|
|
85
|
+
unless File.exist?(output_path)
|
|
86
|
+
raise Error.verify("Rendered file #{target.path} is missing under frozen mode")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Pray.write_lockfile_if_changed(lockfile_path, next_lockfile)
|
|
93
|
+
Render.write_rendered_targets(project, rendered)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def default_manifest_path(working_directory = Dir.pwd)
|
|
97
|
+
File.join(working_directory, "Prayfile")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def default_lockfile_path(project_root)
|
|
101
|
+
File.join(project_root, "Prayfile.lock")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def project_root_from_manifest(manifest_path)
|
|
105
|
+
Resolve.project_root_from_manifest(manifest_path)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
module_function
|
|
110
|
+
|
|
111
|
+
def materialize_project(...) = Materialize.materialize_project(...)
|
|
112
|
+
def default_manifest_path(...) = Materialize.default_manifest_path(...)
|
|
113
|
+
def default_lockfile_path(...) = Materialize.default_lockfile_path(...)
|
|
114
|
+
def project_root_from_manifest(...) = Materialize.project_root_from_manifest(...)
|
|
115
|
+
def resolve_project(...) = Resolve.resolve_project(...)
|
|
116
|
+
def render_project(...) = Render.render_project(...)
|
|
117
|
+
def inspect_project(...) = Verify.inspect_project(...)
|
|
118
|
+
def verify_project(...) = Verify.verify_project(...)
|
|
119
|
+
def drift_project(...) = Verify.drift_project(...)
|
|
120
|
+
end
|