mirror of
https://github.com/apple/pkl.git
synced 2026-03-24 01:51:19 +01:00
Initial commit
This commit is contained in:
37
pkl-commons-cli/gradle.lockfile
Normal file
37
pkl-commons-cli/gradle.lockfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.12.21=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
|
||||
org.assertj:assertj-core:3.24.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.graalvm.sdk:graal-sdk:22.3.1=default,runtimeClasspath,testRuntimeClasspath
|
||||
org.graalvm.truffle:truffle-api:22.3.1=default,runtimeClasspath,testRuntimeClasspath
|
||||
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.7.10=kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-script-runtime:1.7.10=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
|
||||
org.junit.jupiter:junit-jupiter-engine:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
|
||||
org.junit.jupiter:junit-jupiter-params:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
|
||||
org.junit.platform:junit-platform-engine:1.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
|
||||
org.junit:junit-bom:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
|
||||
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
|
||||
org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath
|
||||
org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath
|
||||
empty=annotationProcessor,archives,compile,compileOnly,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,runtime,runtimeOnlyDependenciesMetadata,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompile,testCompileOnly,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntime
|
||||
28
pkl-commons-cli/pkl-commons-cli.gradle.kts
Normal file
28
pkl-commons-cli/pkl-commons-cli.gradle.kts
Normal file
@@ -0,0 +1,28 @@
|
||||
plugins {
|
||||
pklAllProjects
|
||||
pklKotlinLibrary
|
||||
pklPublishLibrary
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":pkl-core"))
|
||||
api(libs.clikt) {
|
||||
// force clikt to use our version of the kotlin stdlib
|
||||
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
|
||||
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-common")
|
||||
}
|
||||
|
||||
implementation(project(":pkl-commons"))
|
||||
testImplementation(project(":pkl-commons-test"))
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
named<MavenPublication>("library") {
|
||||
pom {
|
||||
url.set("https://github.com/apple/pkl/tree/main/pkl-commons-cli")
|
||||
description.set("Internal CLI utilities. NOT A PUBLIC API.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()}"
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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 com.github.ajalt.clikt.core.BadParameterValue
|
||||
import com.github.ajalt.clikt.core.PrintHelpMessage
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.pkl.commons.cli.commands.BaseCommand
|
||||
|
||||
class BaseCommandTest {
|
||||
|
||||
private val cmd =
|
||||
object : BaseCommand("test", "") {
|
||||
override fun run() = Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invalid timeout`() {
|
||||
val e = assertThrows<BadParameterValue> { cmd.parse(arrayOf("--timeout", "abc")) }
|
||||
assertThat(e).hasMessageContaining("timeout")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `help queries do not present as errors`() {
|
||||
assertThrows<PrintHelpMessage> { cmd.parse(arrayOf("--help")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `external properties without value default to 'true'`() {
|
||||
cmd.parse(arrayOf("-p", "flag1", "-p", "flag2", "-p", "FOO=bar"))
|
||||
val props = cmd.baseOptions.baseOptions(emptyList()).externalProperties
|
||||
|
||||
assertThat(props).isEqualTo(mapOf("flag1" to "true", "flag2" to "true", "FOO" to "bar"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `--allowed-modules, --allowed-resources and --module-path can be repeated`() {
|
||||
cmd.parse(arrayOf("--allowed-modules", "m1,m2,m3", "--allowed-modules", "m4"))
|
||||
|
||||
assertThat(cmd.baseOptions.allowedModules.map(Pattern::toString))
|
||||
.isEqualTo(listOf("m1", "m2", "m3", "m4"))
|
||||
|
||||
cmd.parse(arrayOf("--allowed-resources", "r1,r2,r3", "--allowed-resources", "r4"))
|
||||
assertThat(cmd.baseOptions.allowedResources.map(Pattern::toString))
|
||||
.isEqualTo(listOf("r1", "r2", "r3", "r4"))
|
||||
|
||||
val sep = File.pathSeparator
|
||||
cmd.parse(arrayOf("--module-path", "p1${sep}p2${sep}p3", "--module-path", "p4"))
|
||||
assertThat(cmd.baseOptions.modulePath).isEqualTo(listOf("p1", "p2", "p3", "p4").map(Path::of))
|
||||
|
||||
cmd.parse(arrayOf())
|
||||
assertThat(cmd.baseOptions.allowedModules).isEmpty()
|
||||
|
||||
assertThat(cmd.baseOptions.allowedResources).isEmpty()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user