pkl-parser 0.1.1

A rust Pkl Parser!
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
//===----------------------------------------------------------------------===//
// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

/// A manifest that defines the presence of a project.
///
/// A project is useful for defining [dependencies], and also for defining common evaluator
/// [settings].
///
/// A project is a directory that contains a project file at its root.
/// The project file is a file named `PklProject` exactly, that amends or embeds `"pkl:Project"`.
///
/// When evaluating from the CLI, the project directory can be specified explicitly using
/// `--project-dir` flag, or is determined implicitly by walking up the working directory, looking
/// for a directory containing a `PklProject` file.
///
/// When using the API libraries (e.g. _pkl-swift_, _pkl-go_ or _pkl-config-java_), the project must be
/// explicitly stated via the evaluator builder.
///
/// ## Embedding [Project]
///
/// In a project file, instead of amending `pkl:Project`, it is possible simply set the
/// `output.value` property of a module to an instance of [Project].
/// This allows defining higher levels of abstraction that encapsulate an underlying [Project]
/// definition.
///
/// When defining an abstraction, it is important to:
///   1. Set the module's `output.value` to the underlying project output.
///   2. Set [projectFileUri] to the enclosing module's URI.
///      This is necessary to determine the correct project directory, as well as for resolving
///      local project dependencies correctly.
///
///      The [newInstance()] helper method exists as a convenient way to set this when embedding
///      project definitions.
///
/// Example:
/// ```
/// module MyTeamProject
///
/// import "pkl:Project"
///
/// /// The package name, to be prefixed with `myteam`.
/// packageName: String
///
/// project: Project = (Project.newInstance(module)) {
///   package {
///     name = "myteam.\(packageName)"
///   }
/// }
///
/// output {
///   value = project
/// }
/// ```
@ModuleInfo { minPklVersion = "0.27.0" }
module pkl.Project

import "pkl:EvaluatorSettings" as EvaluatorSettingsModule
import "pkl:Project"
import "pkl:reflect"
import "pkl:semver"

/// The details for the package represented by this project.
///
/// This section is used if publishing this project as a package.
package: Package?

/// The tests of the project.
///
/// If set, allows running `pkl test` without specifying the paths to source modules.
///
/// Relative paths are resolved against PklProject's enclosing directory.
///
/// Glob imports can be useful for defining this property.
///
/// Example:
///
/// ```
/// tests = import*("**.test.pkl").keys.toListing()
/// ```
tests: Listing<String>(isDistinct)

/// Tells if the project is a local module named `PklProject`, is not self, and has a [package] section
local isValidLoadDependency = (it: Project) ->
  isUriLocal(projectFileUri, it.projectFileUri)
  && it.projectFileUri.endsWith("/PklProject")
  && it != module
  && it.package != null

const local function isUriLocal(uri1: Uri, uri2: Uri): Boolean =
  // This is an imperfect check; should also check that the URIs have the same authority.
  // We should improve this if/when there is a URI library in the stdlib.
  uri1.substring(0, uri1.indexOf(":")) == uri2.substring(0, uri2.indexOf(":"))

/// The dependencies of this project.
///
/// A dependency is a group of Pkl modules and file resources that can be imported within the
/// project.
/// Within the project, a dependency can be referenced via _dependency notation_, where the name
/// is prefixed with the `@` character.
///
/// For example, given the following descriptor:
///
/// ```
/// dependencies {
///   ["birds"] {
///     uri = "package://example.com/birds@1.0.0"
///   }
/// }
/// ```
///
/// This enables the following snippet:
///
/// ```
/// import "@birds/canary.pkl" // Import a module from the `birds` dependency
///
/// birdIndex = read("@birds/index.txt") // Read a file from the `birds` dependency
/// ```
///
/// A dependency's coordinates can either be specified in the form of a [RemoteDependency]
/// descriptor, which gets fetched over the network, or an import of another PklProject file, which
/// represents a package that exists locally.
///
/// Remote dependencies are fetched over HTTPS.
/// When fetching a remote dependency, two HTTPS requests are made.
/// Given dependency URI `package://example.com/foo@0.5.0`, the following requests are made:
///
///   1. `GET https://example.com/foo@0.5.0` to retrieve the metadata JSON file.
///   2. Given the metadata JSON file, make a GET request to the package URI ZIP archive.
///
/// If this project is published as a package, these dependencies are included within the published
/// package metadata.
///
/// ## Local project dependencies
///
/// A local project can alternatively be used as a dependency.
/// This is useful when structuring a single repository that publishes multiple packages.
///
/// To specify a local project dependency, import the relative `PklProject` file.
///
/// The local project dependency must define its own [package].
///
/// Example:
/// ```
/// dependencies {
///   ["penguins"] = import("../penguins/PklProject")
/// }
/// ```
///
/// ## Resolving dependencies
///
/// Dependencies must be _resolved_ before they can be used.
/// To resolve dependencies, run the CLI command `pkl project resolve`.
/// This will generate a `PklProject.deps.json` file next to the `PklProject` file.
///
/// ### Minimum version selection
///
/// Pkl uses the
/// [minimum version selection algorithm 1](https://research.swtch.com/vgo-mvs#algorithm_1)
/// to resolve dependencies.
/// A dependency is identified by its package URI, as well as its major semver number.
///
/// To determine the resolved dependencies of a project, the following algorithm is applied:
///   1. Gather all dependencies, both direct and transitive.
///   2. For each package's major version, determine the highest declared minor version.
///   3. Write each resolved dependency to sibling file `PklProject.deps.json`.
dependencies: Mapping<String(!contains("/")), *RemoteDependency|Project(isValidLoadDependency)>

local isFileBasedProject = projectFileUri.startsWith("file:")

/// If set, controls the base evaluator settings when running the evaluator.
///
/// These settings influence the behavior of the evaluator when running the `pkl eval`, `pkl test`,
/// and `pkl repl` CLI commands.
/// Command line flags passed to the CLI will override any settings defined here.
///
/// Other integrations can possibly ignore these evaluator settings.
///
/// Evaluator settings do not get published as part of a package.
/// It is not possible for a package dependency to influence the evaluator settings of a project.
///
/// The following values can only be set if this is a file-based project.
///
///  - [modulePath][EvaluatorSettings.modulePath]
///  - [rootDir][EvaluatorSettings.rootDir]
///  - [moduleCacheDir][EvaluatorSettings.moduleCacheDir]
///
/// For each of these, relative paths are resolved against the project's enclosing directory.
evaluatorSettings: EvaluatorSettingsModule(
  (modulePath != null).implies(isFileBasedProject),
  (rootDir != null).implies(isFileBasedProject),
  (moduleCacheDir != null).implies(isFileBasedProject)
)

/// The URI of the PklProject file.
///
/// This value is used to resolve relative paths when importing another local project as a
/// dependency.
projectFileUri: String = reflect.Module(module).uri

/// Instantiates a project definition within the enclosing module.
///
/// This is a convenience method for setting [projectFileUri] to the enclosing module's URI.
///
/// Example:
/// ```
/// myProject: Project = (Project.newInstance(module)) {
///   dependencies { /* etc */ }
/// }
/// ```
function newInstance(enclosingModule: Module): Project = new {
  projectFileUri = reflect.Module(enclosingModule).uri
}

const local hasVersion = (it: Uri) ->
  let (versionSep = it.lastIndexOf("@"))
    if (versionSep == -1) false
    else let (version = it.drop(versionSep + 1))
      semver.parseOrNull(version) != null

typealias PackageUri = Uri(startsWith("package:"), hasVersion)

class RemoteDependency {
  /// The URI that this dependency is published to.
  uri: PackageUri

  /// The checksums of this package.
  ///
  /// If omitted, this is taken from the derived package coordinates.
  checksums: Checksums?
}

class Checksums {
  /// The [SHA-256](https://en.wikipedia.org/wiki/SHA-2) checksum value of the dependency, in hexadecimal representation.
  sha256: String
}

/// An email address, conformant to the
/// [RFC5322 mailbox](https://www.rfc-editor.org/rfc/rfc5322#section-3.4) specification.
///
/// Can be in the form of an address spec, or a named address.
///
/// Examples:
///   * `"johnny.appleseed@example.com"`
///   * `"Johnny Appleseed <johnny.appleseed@example.com>"`
typealias EmailAddress = String(matches(Regex(#".+@\S+|.+<\S+@\S+>"#)))

class Package {
  /// The name of this package.
  ///
  /// The package name is only used for display purposes.
  ///
  /// Example:
  /// ```
  /// name = "myproject"
  /// ```
  name: String

  /// The URI that the package is published to, without the version part.
  ///
  /// This, along with the version, determines the import path for modules and resources published
  /// by this package.
  ///
  /// Example:
  /// ```
  /// baseUri = "package://example.com/myproject"
  /// ```
  baseUri: Uri(startsWith("package:"))

  /// The version of this package.
  ///
  /// Must adhere to semantic versioning.
  ///
  /// Example:
  /// ```
  /// version = "1.5.0"
  /// ```
  version: String(semver.isValid(this))

  /// The HTTPS location for the zip archive for this package.
  ///
  /// Example:
  /// ```
  /// packageZipUrl = "https://example.com/artifacts/myproject/\(version).zip"
  /// ```
  packageZipUrl: Uri(startsWith("https:"))

  /// The description of this package.
  description: String?

  /// The maintainers' emails for this package.
  ///
  /// Email addresses must adhere to
  /// [RFC5322 mailbox](https://www.rfc-editor.org/rfc/rfc5322#section-3.4) specification.
  ///
  /// Example:
  /// ```
  /// email { "Johnny Appleseed <johnny.appleseed@example.com>" }
  /// ```
  authors: Listing<EmailAddress>

  /// The website for this package.
  ///
  /// Example:
  /// ```
  /// website = "https://example.com/myproject"
  /// ```
  website: String?

  /// The web URL of the Pkldoc documentation for this package.
  documentation: Uri(!endsWith("/"))?

  /// The source code repository for this package.
  ///
  /// Example:
  /// ```
  /// sourceCode = "https://github.com/myorg/myproject"
  /// ```
  sourceCode: String?

  /// The source code scheme for this package.
  ///
  /// This is used to transform stack frames for errors arising for this package.
  ///
  /// The following placeholders are available:
  ///
  /// - `%{path}`
  ///   absolute file path of the file to open
  /// - `%{line}`
  ///   start line number to navigate to
  /// - `%{endLine}`
  ///   end line number to navigate to
  /// - `%{column}`
  ///   column number to navigate to
  /// - `%{endColumn}`
  ///   end column number to navigate to
  ///
  /// For example, if publishing to GitHub, assuming that the version gets published as a tag:
  ///
  /// ```
  /// sourceCodeUrlScheme = "\(sourceCode)/blob/\(version)%{path}#L%{line}-L%{endLine}"
  /// ```
  sourceCodeUrlScheme: String?

  /// The license associated with this package.
  ///
  /// If using a common license, use its [SPDX license identifier](https://spdx.org/licenses/).
  ///
  /// If using multiple common licenses, use a
  /// [SPDX license expression syntax version 2.0 string](https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/).
  /// For example: `"Apache-2.0 or MIT"`.
  ///
  /// If using an uncommon license, also provide its full text in the [licenseText] property.
  ///
  /// Example:
  /// ```
  /// license = "Apache-2.0"
  /// ```
  license: (CommonSpdxLicenseIdentifier|String)?

  /// The full text of the license associated with this package.
  licenseText: String?

  /// The web URL of the issue tracker for this package.
  issueTracker: String?

  /// Paths to the tests that define the API of the package.
  ///
  /// These tests are run as part of the `pkl project package` command.
  ///
  /// Relative paths are resolved against PklProject's enclosing directory.
  ///
  /// Glob imports can be useful for defining this property.
  ///
  /// Example:
  ///
  /// ```
  /// tests = import*("**.test.pkl").keys.toListing()
  /// ```
  apiTests: Listing<String>(isDistinct)

  /// Glob patterns describing the set of files to exclude from packaging.
  ///
  /// By default, the project manifest files are excluded, and any paths that start with a dot.
  ///
  /// Glob patterns follows the same glob rules as glob imports and reads.
  exclude: Listing<String> = new {
    "PklProject"
    "PklProject.deps.json"
    ".**"
  }

  /// The effective package URI for the package represented by this project.
  fixed uri: PackageUri = "\(baseUri)@\(version)"
}

@Deprecated { since = "0.26.0"; replaceWith = "EvaluatorSettingsModule" }
typealias EvaluatorSettings = EvaluatorSettingsModule

/// Common software licenses in the [SPDX License List](https://spdx.org/licenses/).
typealias CommonSpdxLicenseIdentifier =
  "Apache-2.0"
  |"MIT"
  |"BSD-2-Clause"
  |"BSD-3-Clause"
  |"ISC"
  |"GPL-3.0"
  |"GPL-2.0"
  |"MPL-2.0"
  |"MPL-1.1"
  |"MPL-1.0"
  |"AGPL-1.0-only"
  |"AGPL-1.0-or-later"
  |"AGPL-3.0-only"
  |"AGPL-3.0-or-later"
  |"LGPL-2.0-only"
  |"LGPL-2.0-or-later"
  |"LGPL-2.1-only"
  |"LGPL-2.1-or-later"
  |"LGPL-3.0-only"
  |"LGPL-3.0-or-later"
  |"EPL-1.0"
  |"EPL-2.0"
  |"UPL-1.0"
  |"BSL-1.0"
  |"Unlicense"