Initial commit

This commit is contained in:
Peter Niederwieser
2016-01-19 14:51:19 +01:00
committed by Dan Chao
commit ecad035dca
2972 changed files with 211653 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
/**
* 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.
*/
package org.pkl.commons.cli
import java.net.URI
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import java.util.regex.Pattern
import org.pkl.core.module.ProjectDependenciesManager
import org.pkl.core.util.IoUtils
/** Base options shared between CLI commands. */
data class CliBaseOptions(
/** The source modules to evaluate. Relative URIs are resolved against [workingDir]. */
private val sourceModules: List<URI> = listOf(),
/**
* The URI patterns that determine which modules can be loaded and evaluated. Patterns are matched
* against the beginning of module URIs. At least one pattern needs to match for a module to be
* loadable. Both [sourceModules] and module imports are subject to this check.
*/
val allowedModules: List<Pattern>? = null,
/**
* The URI patterns that determine which external resources can be read. Patterns are matched
* against the beginning of resource URIs. At least one pattern needs to match for a resource to
* be readable.
*/
val allowedResources: List<Pattern>? = null,
/**
* The environment variables to set. Pkl code can read environment variables with
* `read("env:<NAME>")`.
*/
val environmentVariables: Map<String, String>? = null,
/**
* The external properties to set. Pkl code can read external properties with
* `read("prop:<name>")`.
*/
val externalProperties: Map<String, String>? = null,
/**
* The directories, ZIP archives, or JAR archives to search when resolving `modulepath:` URIs.
* Relative paths are resolved against [workingDir].
*/
private val modulePath: List<Path>? = null,
/**
* The base path that relative module paths passed as command-line arguments are resolved against.
*/
private val workingDir: Path = IoUtils.getCurrentWorkingDir(),
/**
* The root directory for `file:` modules and resources. If non-null, access to file-based modules
* and resources is restricted to those located under [rootDir]. Any symlinks are resolved before
* this check is performed.
*/
private val rootDir: Path? = null,
/**
* The Pkl settings file to use. A settings file is a Pkl module amending the `pkl.settings`
* standard library module. If `null`, `~/.pkl/settings.pkl` (if present) or the defaults
* specified in the `pkl:settings` standard library module are used.
*/
private val settings: URI? = null,
/**
* The root directory of the project. The directory must contain a `PklProject` that amends the
* `pkl.Project` standard library module.
*
* If `null`, looks for a `PklProject` file in [workingDir], traversing up to [rootDir], or `/` if
* [rootDir] is `null`.
*
* This can be disabled with [noProject].
*/
private val projectDir: Path? = null,
/**
* The duration after which evaluation of a source module will be timed out. Note that a timeout
* is treated the same as a program error in that any subsequent source modules will not be
* evaluated.
*/
val timeout: Duration? = null,
/** The cache directory for storing packages. */
private val moduleCacheDir: Path? = null,
/** Whether to disable the module cache. */
val noCache: Boolean = false,
/** Ignores any evaluator settings set in the PklProject file. */
val omitProjectSettings: Boolean = false,
/** Disables all behavior related to projects. */
val noProject: Boolean = false,
/** Tells whether to run the CLI in test mode. This is an internal option. */
val testMode: Boolean = false,
/**
* [X.509 certificates](https://en.wikipedia.org/wiki/X.509) in PEM format.
*
* Elements can either be a [Path] or a [java.io.InputStream]. Input streams are closed when
* [CliCommand] is initialized.
*
* If not empty, this determines the CA root certs used for all HTTPS requests. Warning: this
* affects the whole Java runtime, not just the Pkl API!
*/
val caCertificates: List<Any> = emptyList(),
) {
companion object {
tailrec fun Path.getProjectFile(rootDir: Path?): Path? {
val candidate = resolve(ProjectDependenciesManager.PKL_PROJECT_FILENAME)
return when {
Files.exists(candidate) -> candidate
parent == null -> null
rootDir != null && !parent.startsWith(rootDir) -> null
else -> parent.getProjectFile(rootDir)
}
}
}
/** [workingDir] after normalization. */
val normalizedWorkingDir: Path = IoUtils.getCurrentWorkingDir().resolve(workingDir)
/** [rootDir] after normalization. */
val normalizedRootDir: Path? = rootDir?.let(normalizedWorkingDir::resolve)
/** [sourceModules] after normalization. */
val normalizedSourceModules: List<URI> =
sourceModules
.map { uri ->
if (uri.isAbsolute) uri else IoUtils.resolve(normalizedWorkingDir.toUri(), uri)
}
// sort modules to make cli output independent of source module order
.sorted()
val normalizedSettingsModule: URI? =
settings?.let { uri ->
if (uri.isAbsolute) uri else IoUtils.resolve(normalizedWorkingDir.toUri(), uri)
}
/** [modulePath] after normalization. */
val normalizedModulePath: List<Path>? = modulePath?.map(normalizedWorkingDir::resolve)
/** [moduleCacheDir] after normalization. */
val normalizedModuleCacheDir: Path? = moduleCacheDir?.let(normalizedWorkingDir::resolve)
/** The effective project directory, if exists. */
val normalizedProjectFile: Path? by lazy {
projectDir?.resolve(ProjectDependenciesManager.PKL_PROJECT_FILENAME)
?: normalizedWorkingDir.getProjectFile(rootDir)
}
}

View File

@@ -0,0 +1,208 @@
/**
* 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.
*/
package org.pkl.commons.cli
import java.nio.file.Path
import java.util.regex.Pattern
import org.pkl.core.*
import org.pkl.core.module.ModuleKeyFactories
import org.pkl.core.module.ModuleKeyFactory
import org.pkl.core.module.ModulePathResolver
import org.pkl.core.project.Project
import org.pkl.core.resource.ResourceReader
import org.pkl.core.resource.ResourceReaders
import org.pkl.core.runtime.CertificateUtils
import org.pkl.core.settings.PklSettings
import org.pkl.core.util.IoUtils
/** Building block for CLI commands. Configured programmatically to allow for embedding. */
abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
init {
if (cliOptions.caCertificates.isNotEmpty()) {
CertificateUtils.setupAllX509CertificatesGlobally(cliOptions.caCertificates)
}
}
/** Runs this command. */
fun run() {
if (cliOptions.testMode) {
IoUtils.setTestMode()
}
try {
doRun()
} catch (e: PklException) {
throw CliException(e.message!!)
} catch (e: CliException) {
throw e
} catch (e: Exception) {
throw CliBugException(e)
}
}
/**
* Implements this command. May throw [PklException] or [CliException]. Any other thrown exception
* is treated as a bug.
*/
protected abstract fun doRun()
/** The Pkl settings used by this command. */
@Suppress("MemberVisibilityCanBePrivate")
protected val settings: PklSettings by lazy {
try {
if (cliOptions.normalizedSettingsModule != null) {
PklSettings.load(ModuleSource.uri(cliOptions.normalizedSettingsModule))
} else {
PklSettings.loadFromPklHomeDir()
}
} catch (e: PklException) {
// do not use `errorRenderer` because it depends on `settings`
throw CliException(e.toString())
}
}
/** The Project used by this command. */
protected val project: Project? by lazy {
if (cliOptions.noProject) {
return@lazy null
}
cliOptions.normalizedProjectFile?.let { loadProject(it) }
}
protected fun loadProject(projectFile: Path): Project {
val securityManager =
SecurityManagers.standard(
cliOptions.allowedModules ?: SecurityManagers.defaultAllowedModules,
cliOptions.allowedResources ?: SecurityManagers.defaultAllowedResources,
SecurityManagers.defaultTrustLevels,
cliOptions.normalizedRootDir
)
val envVars = cliOptions.environmentVariables ?: System.getenv()
val stackFrameTransformer =
if (IoUtils.isTestMode()) StackFrameTransformers.empty
else StackFrameTransformers.defaultTransformer
return Project.loadFromPath(
projectFile,
securityManager,
cliOptions.timeout,
stackFrameTransformer,
envVars
)
}
private val projectSettings: Project.EvaluatorSettings? by lazy {
if (cliOptions.omitProjectSettings) {
return@lazy null
}
project?.settings
}
protected val allowedModules: List<Pattern> by lazy {
cliOptions.allowedModules
?: projectSettings?.allowedModules ?: SecurityManagers.defaultAllowedModules
}
protected val allowedResources: List<Pattern> by lazy {
cliOptions.allowedResources
?: projectSettings?.allowedResources ?: SecurityManagers.defaultAllowedResources
}
protected val rootDir: Path? by lazy { cliOptions.normalizedRootDir ?: projectSettings?.rootDir }
protected val environmentVariables: Map<String, String> by lazy {
cliOptions.environmentVariables ?: projectSettings?.env ?: System.getenv()
}
protected val externalProperties: Map<String, String> by lazy {
cliOptions.externalProperties ?: projectSettings?.externalProperties ?: emptyMap()
}
protected val moduleCacheDir: Path? by lazy {
if (cliOptions.noCache) null
else
cliOptions.normalizedModuleCacheDir
?: projectSettings?.let { settings ->
if (settings.isNoCache == true) null else settings.moduleCacheDir
}
?: IoUtils.getDefaultModuleCacheDir()
}
protected val modulePath: List<Path> by lazy {
cliOptions.normalizedModulePath ?: projectSettings?.modulePath ?: emptyList()
}
protected val stackFrameTransformer: StackFrameTransformer by lazy {
if (cliOptions.testMode) {
StackFrameTransformers.empty
} else {
StackFrameTransformers.createDefault(settings)
}
}
protected val securityManager: SecurityManager by lazy {
SecurityManagers.standard(
allowedModules,
allowedResources,
SecurityManagers.defaultTrustLevels,
rootDir
)
}
protected fun moduleKeyFactories(modulePathResolver: ModulePathResolver): List<ModuleKeyFactory> {
return buildList {
add(ModuleKeyFactories.standardLibrary)
add(ModuleKeyFactories.modulePath(modulePathResolver))
add(ModuleKeyFactories.pkg)
add(ModuleKeyFactories.projectpackage)
addAll(ModuleKeyFactories.fromServiceProviders())
add(ModuleKeyFactories.file)
add(ModuleKeyFactories.genericUrl)
}
}
private fun resourceReaders(modulePathResolver: ModulePathResolver): List<ResourceReader> {
return buildList {
add(ResourceReaders.environmentVariable())
add(ResourceReaders.externalProperty())
add(ResourceReaders.modulePath(modulePathResolver))
add(ResourceReaders.pkg())
add(ResourceReaders.projectpackage())
add(ResourceReaders.file())
add(ResourceReaders.http())
add(ResourceReaders.https())
}
}
/**
* Creates an [EvaluatorBuilder] preconfigured according to [cliOptions]. To avoid resource leaks,
* `ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)` must be called once the returned
* builder and evaluators built by it are no longer in use.
*/
protected fun evaluatorBuilder(): EvaluatorBuilder {
// indirectly closed by `ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)`
val modulePathResolver = ModulePathResolver(modulePath)
return EvaluatorBuilder.unconfigured()
.setStackFrameTransformer(stackFrameTransformer)
.apply { project?.let { setProjectDependencies(it.dependencies) } }
.setSecurityManager(securityManager)
.setExternalProperties(externalProperties)
.setEnvironmentVariables(environmentVariables)
.addModuleKeyFactories(moduleKeyFactories(modulePathResolver))
.addResourceReaders(resourceReaders(modulePathResolver))
.setLogger(Loggers.stdErr())
.setTimeout(cliOptions.timeout)
.setModuleCacheDir(moduleCacheDir)
}
}

View File

@@ -0,0 +1,47 @@
/**
* 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.
*/
package org.pkl.commons.cli
import org.pkl.commons.printStackTraceToString
/** A CLI error to report back to users. */
open class CliException(
/**
* The error message to report back to CLI users. The message is expected to be displayed as-is
* without any further enrichment. As such the message should be comprehensive and designed with
* the CLI user in mind.
*/
message: String,
/** The process exit code to use. */
val exitCode: Int = 1
) : RuntimeException(message) {
override fun toString(): String = message!!
}
/** An unexpected CLI error classified as bug. */
class CliBugException(
/** The cause for the bug. */
private val theCause: Exception,
/** The process exit code to use. */
exitCode: Int = 1
) :
CliException("An unexpected error has occurred. Would you mind filing a bug report?", exitCode) {
override fun toString(): String = "$message\n\n${theCause.printStackTraceToString()}"
}

View File

@@ -0,0 +1,46 @@
/**
* 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.
*/
package org.pkl.commons.cli
import java.io.PrintStream
import kotlin.system.exitProcess
/** Building block for CLIs. Intended to be called from a `main` method. */
fun cliMain(block: () -> Unit) {
fun printError(error: Throwable, stream: PrintStream) {
val message = error.toString()
stream.print(message)
// ensure CLI output always ends with newline
if (!message.endsWith('\n')) stream.println()
}
try {
block()
} catch (e: CliTestException) {
// no need to print the error, the test results will already do it
exitProcess(e.exitCode)
} catch (e: CliException) {
printError(e, if (e.exitCode == 0) System.out else System.err)
exitProcess(e.exitCode)
} catch (e: Exception) {
printError(CliBugException(e), System.err)
exitProcess(1)
}
}
object CliMain {
val compat: String? = System.getProperty("org.pkl.compat")
}

View File

@@ -0,0 +1,18 @@
/**
* 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.
*/
package org.pkl.commons.cli
class CliTestException(msg: String) : CliException(msg, 1)

View File

@@ -0,0 +1,20 @@
/**
* 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.
*/
package org.pkl.commons.cli
import java.nio.file.Path
class CliTestOptions(val junitDir: Path? = null, val overwrite: Boolean = false)

View File

@@ -0,0 +1,59 @@
/**
* 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.
*/
package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import java.net.URI
import java.net.URISyntaxException
import org.pkl.commons.cli.CliException
import org.pkl.core.runtime.VmUtils
import org.pkl.core.util.IoUtils
abstract class BaseCommand(name: String, helpLink: String, help: String = "") :
CliktCommand(
name = name,
help = help,
epilog = "For more information, visit $helpLink",
) {
val baseOptions by BaseOptions()
/**
* Parses [moduleName] into a URI. If scheme is not present, we expect that this is a file path
* and encode any possibly invalid characters. If a scheme is present, we expect that this is a
* valid URI.
*/
protected fun parseModuleName(moduleName: String): URI =
when (moduleName) {
"-" -> VmUtils.REPL_TEXT_URI
else ->
try {
IoUtils.toUri(moduleName)
} catch (e: URISyntaxException) {
val message = buildString {
append("Module URI `$moduleName` has invalid syntax (${e.reason}).")
if (e.index > -1) {
append("\n\n")
append(moduleName)
append("\n")
append(" ".repeat(e.index))
append("^")
}
}
throw CliException(message)
}
}
}

View File

@@ -0,0 +1,192 @@
/**
* 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.
*/
package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.parameters.groups.OptionGroup
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.long
import com.github.ajalt.clikt.parameters.types.path
import java.io.File
import java.io.InputStream
import java.net.URI
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import java.util.regex.Pattern
import java.util.stream.Collectors
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.isRegularFile
import org.pkl.commons.cli.CliBaseOptions
import org.pkl.core.util.IoUtils
@Suppress("MemberVisibilityCanBePrivate")
class BaseOptions : OptionGroup() {
companion object {
fun includedCARootCerts(): InputStream {
return BaseOptions::class.java.getResourceAsStream("IncludedCARoots.pem")!!
}
}
private val defaults = CliBaseOptions()
private val output =
arrayOf("json", "jsonnet", "pcf", "properties", "plist", "textproto", "xml", "yaml")
val allowedModules: List<Pattern> by
option(
names = arrayOf("--allowed-modules"),
help = "URI patterns that determine which modules can be loaded and evaluated."
)
.convert("<pattern1,pattern2>") { Pattern.compile(it) }
.splitAll(",")
val allowedResources: List<Pattern> by
option(
names = arrayOf("--allowed-resources"),
help = "URI patterns that determine which external resources can be read."
)
.convert("<pattern1,pattern2>") { Pattern.compile(it) }
.splitAll(",")
val rootDir: Path? by
option(
names = arrayOf("--root-dir"),
help =
"Restricts access to file-based modules and resources to those located under the root directory."
)
.single()
.path()
val cacheDir: Path? by
option(names = arrayOf("--cache-dir"), help = "The cache directory for storing packages.")
.single()
.path()
val workingDir: Path by
option(
names = arrayOf("-w", "--working-dir"),
help = "Base path that relative module paths are resolved against."
)
.single()
.path()
.default(defaults.normalizedWorkingDir)
val properties: Map<String, String> by
option(
names = arrayOf("-p", "--property"),
metavar = "<name=value>",
help = "External property to set (repeatable)."
)
.associate()
val noCache: Boolean by
option(names = arrayOf("--no-cache"), help = "Disable cacheing of packages")
.single()
.flag(default = false)
val format: String? by
option(
names = arrayOf("-f", "--format"),
help = "Output format to generate. <${output.joinToString()}>"
)
.single()
val envVars: Map<String, String> by
option(
names = arrayOf("-e", "--env-var"),
metavar = "<name=value>",
help = "Environment variable to set (repeatable)."
)
.associate()
val modulePath: List<Path> by
option(
names = arrayOf("--module-path"),
metavar = "<path1${File.pathSeparator}path2>",
help =
"Directories, ZIP archives, or JAR archives to search when resolving `modulepath:` URIs."
)
.path()
.splitAll(File.pathSeparator)
val settings: URI? by
option(names = arrayOf("--settings"), help = "Pkl settings module to use.").single().convert {
IoUtils.toUri(it)
}
val timeout: Duration? by
option(
names = arrayOf("-t", "--timeout"),
metavar = "<number>",
help = "Duration, in seconds, after which evaluation of a source module will be timed out."
)
.single()
.long()
.convert { Duration.ofSeconds(it) }
val caCertificates: List<Path> by
option(
names = arrayOf("--ca-certificates"),
metavar = "<path>",
help = "Replaces the built-in CA certificates with the provided certificate file."
)
.path()
.multiple()
/**
* 1. If `--ca-certificates` option is not empty, use that.
* 2. If directory `~/.pkl/cacerts` is not empty, use that.
* 3. Use the bundled CA certificates.
*/
private fun getEffectiveCaCertificates(): List<Any> {
return caCertificates
.ifEmpty {
val home = System.getProperty("user.home")
val cacerts = Path.of(home, ".pkl", "cacerts")
if (cacerts.exists() && cacerts.isDirectory())
Files.list(cacerts).filter(Path::isRegularFile).collect(Collectors.toList())
else emptyList()
}
.ifEmpty { listOf(includedCARootCerts()) }
}
fun baseOptions(
modules: List<URI>,
projectOptions: ProjectOptions? = null,
testMode: Boolean = false
): CliBaseOptions {
return CliBaseOptions(
sourceModules = modules,
allowedModules = allowedModules.ifEmpty { null },
allowedResources = allowedResources.ifEmpty { null },
environmentVariables = envVars.ifEmpty { null },
externalProperties = properties.mapValues { it.value.ifBlank { "true" } }.ifEmpty { null },
modulePath = modulePath.ifEmpty { null },
workingDir = workingDir,
settings = settings,
rootDir = rootDir,
projectDir = projectOptions?.projectDir,
timeout = timeout,
moduleCacheDir = cacheDir ?: defaults.normalizedModuleCacheDir,
noCache = noCache,
testMode = testMode,
omitProjectSettings = projectOptions?.omitProjectSettings ?: false,
noProject = projectOptions?.noProject ?: false,
caCertificates = getEffectiveCaCertificates()
)
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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.
*/
package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.convert
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import java.net.URI
abstract class ModulesCommand(name: String, helpLink: String, help: String = "") :
BaseCommand(
name = name,
help = help,
helpLink = helpLink,
) {
open val modules: List<URI> by
argument(name = "<modules>", help = "Module paths or URIs to evaluate.")
.convert { parseModuleName(it) }
.multiple(required = true)
protected val projectOptions by ProjectOptions()
}

View File

@@ -0,0 +1,49 @@
/**
* 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.
*/
package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.parameters.options.NullableOption
import com.github.ajalt.clikt.parameters.options.OptionWithValues
import com.github.ajalt.clikt.parameters.options.transformAll
/** Forbid this option from being repeated. */
fun <EachT : Any, ValueT> NullableOption<EachT, ValueT>.single(): NullableOption<EachT, ValueT> {
return transformAll {
if (it.size > 1) {
fail("Option cannot be repeated")
}
it.lastOrNull()
}
}
/**
* Allow this option to be repeated and to receive multiple values separated by [separator]. This is
* a mix of [split][com.github.ajalt.clikt.parameters.options.split] and
* [multiple][com.github.ajalt.clikt.parameters.options.multiple] joined together.
*/
fun <EachT : Any, ValueT> NullableOption<EachT, ValueT>.splitAll(
separator: String = ",",
default: List<ValueT> = emptyList()
): OptionWithValues<List<ValueT>, List<ValueT>, ValueT> {
return copy(
transformValue = transformValue,
transformEach = { it },
transformAll = { it.flatten().ifEmpty { default } },
validator = {},
nvalues = 1,
valueSplit = Regex.fromLiteral(separator)
)
}

View File

@@ -0,0 +1,54 @@
/**
* 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.
*/
package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.parameters.groups.OptionGroup
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.path
import java.nio.file.Path
/**
* Options related to projects for CLI commands that are related to normal evaluation (`pkl eval`,
* `pkl test`).
*/
class ProjectOptions : OptionGroup() {
val projectDir: Path? by
option(
names = arrayOf("--project-dir"),
metavar = "<path>",
help =
"The project directory to use for this command. By default, searches up from the working directory for a PklProject file."
)
.single()
.path()
val omitProjectSettings: Boolean by
option(
names = arrayOf("--omit-project-settings"),
help = "Ignores evaluator settings set in the PklProject file."
)
.single()
.flag(default = false)
val noProject: Boolean by
option(
names = arrayOf("--no-project"),
help = "Disables loading settings and dependencies from the PklProject file."
)
.single()
.flag(default = false)
}

View File

@@ -0,0 +1,38 @@
/**
* 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.
*/
package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.parameters.groups.OptionGroup
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.path
import java.nio.file.Path
import org.pkl.commons.cli.CliTestOptions
class TestOptions : OptionGroup() {
private val junitReportDir: Path? by
option(
names = arrayOf("--junit-reports"),
metavar = "<dir>",
help = "Directory where to store JUnit reports."
)
.path()
private val overwrite: Boolean by
option(names = arrayOf("--overwrite"), help = "Force generation of expected examples.").flag()
val cliTestOptions: CliTestOptions by lazy { CliTestOptions(junitReportDir, overwrite) }
}