├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── installer.yml ├── .gitignore ├── LICENSE.txt ├── NEWS.adoc ├── README.adoc ├── TODO.adoc ├── build.gradle.kts ├── docs ├── .notes_images │ └── 7eef33f0.png ├── notes.md └── user_guide.md ├── env_posix.sh ├── env_windows.bat ├── examples ├── basic_template_example.kts ├── classpath_example.kts ├── clikt-example.kts ├── java_module_example.kts ├── kotlinc_example.kts ├── more_examples.sh └── url_example.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── misc ├── CustomResolverExample.kts ├── Dockerfile ├── benchmarking │ ├── benchmark_kscript.R │ ├── benchmark_kscript.kts │ └── benchmarking.md ├── boostrap_header.sh ├── bootstrap_tester │ ├── Dockerfile │ └── README.md ├── docker_tests.sh ├── experimental │ ├── jar_embedding.sh │ ├── kscriptD │ └── kscriptJarStub ├── gradle2deps │ └── gradle2deps.kts ├── kscript_release.sh ├── kshell_launcher │ ├── README.md │ ├── docker_example.sh │ ├── krangl_example.kts │ └── kshell_kts.sh └── readme_images │ └── minus_idea.png ├── settings.gradle.kts ├── src ├── integration │ └── kotlin │ │ └── io │ │ └── github │ │ └── kscripting │ │ └── kscript │ │ └── integration │ │ ├── AnnotationTest.kt │ │ ├── BootstrapHeaderTest.kt │ │ ├── CliReplTest.kt │ │ ├── CustomInterpretersTest.kt │ │ ├── DeprecatedReportTest.kt │ │ ├── EnvironmentTest.kt │ │ ├── IdeaTest.kt │ │ ├── KtSupportTest.kt │ │ ├── MiscTest.kt │ │ ├── PackagingTest.kt │ │ ├── ResolverTest.kt │ │ ├── ScriptInputModesTest.kt │ │ ├── SimpleTest.kt │ │ ├── SupportApiTest.kt │ │ ├── TestBase.kt │ │ └── tools │ │ ├── TestAssertion.kt │ │ ├── TestContext.kt │ │ └── TestMatcher.kt ├── kscript ├── kscript.bat ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── kscripting │ │ └── kscript │ │ ├── Kscript.kt │ │ ├── KscriptHandler.kt │ │ ├── cache │ │ └── Cache.kt │ │ ├── code │ │ ├── GradleTemplates.kt │ │ └── Templates.kt │ │ ├── creator │ │ ├── BootstrapCreator.kt │ │ ├── DebugInfoCreator.kt │ │ ├── DeprecatedInfoCreator.kt │ │ ├── IdeaProjectCreator.kt │ │ ├── JarArtifactCreator.kt │ │ └── PackageCreator.kt │ │ ├── model │ │ ├── Config.kt │ │ ├── ConfigBuilder.kt │ │ ├── Content.kt │ │ ├── Script.kt │ │ ├── ScriptAnnotation.kt │ │ ├── ScriptNode.kt │ │ └── Section.kt │ │ ├── parser │ │ ├── LineParser.kt │ │ ├── ParseException.kt │ │ └── Parser.kt │ │ ├── resolver │ │ ├── CommandResolver.kt │ │ ├── DependencyResolver.kt │ │ ├── InputOutputResolver.kt │ │ ├── ResolutionContext.kt │ │ ├── ScriptResolver.kt │ │ └── SectionResolver.kt │ │ └── util │ │ ├── Executor.kt │ │ ├── FileUtils.kt │ │ ├── Logger.kt │ │ ├── OptionsUtils.kt │ │ ├── ScriptUtils.kt │ │ ├── ShellUtils.kt │ │ ├── UriUtils.kt │ │ └── VersionChecker.kt └── test │ └── kotlin │ └── io │ └── github │ └── kscripting │ └── kscript │ ├── code │ └── GradleTemplatesTest.kt │ ├── model │ └── ConfigBuilderTest.kt │ ├── parser │ └── LineParserTest.kt │ ├── resolver │ ├── CommandResolverTest.kt │ ├── DependencyResolverTest.kt │ ├── RegressionResolverTest.kt │ └── ScriptResolverTest.kt │ └── util │ ├── FileUtilsTest.kt │ ├── LoggerTest.kt │ └── VersionCheckerTest.kt └── test ├── TestsReadme.md ├── help-dev.txt └── resources ├── artifactory_config ├── artifactory.config.xml └── security_descriptor.xml ├── cmd_subst_test.sh ├── compiler_opts_with_includes.sh ├── config ├── jars │ ├── jar_file_1.jar │ ├── jar_file_1.kt │ └── subdir │ │ ├── jar_file_2.jar │ │ └── jar_file_2.kt ├── kscript.properties └── script_with_local_jars.kts ├── consolidate_includes ├── expected.kts ├── file1.kts ├── file2.kts ├── file3.kts └── template.kts ├── custom_dsl ├── mydsl ├── mydsl_test_with_deps.kts └── test_dsl_include.kt ├── custom_mvn_repo_annot.kts ├── dash-test.kts ├── depends_on_annot.kts ├── depends_on_dynamic.kts ├── depends_on_klaxon.kts ├── depends_on_maven_annot.kts ├── depends_on_with_type.kts ├── deprecated_report.kt ├── deprecated_report_include.kts ├── direct_script_arg.sh ├── dot.Test.kts ├── echo_stdin_args.kts ├── here_doc_test.sh ├── home_dir_include.sh ├── idea ├── illegal_depends_on_arg.kts ├── includes ├── diamond.kts ├── dup_include │ ├── dup_include.kts │ ├── dup_include_1.kt │ ├── dup_include_2.kt │ └── expected_dup_include.kts ├── expected_variations.kts ├── include_3.kt ├── include_4.kt ├── include_7.kt ├── include_by_url.kt ├── include_context.kts ├── include_variations.kts ├── rel_includes │ ├── include_1.kt │ ├── include_2.kt │ ├── include_5.kt │ ├── include_6.kt │ └── include_by_url.kt └── shebang_mode_includes ├── invalid_script.kts ├── jar_dependency ├── build.gradle ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── holgerbrandl │ │ │ └── kscript │ │ │ └── test │ │ │ └── SomethingCool.kt │ └── test │ │ └── java │ │ └── Tester.kt ├── test_jar_readme.md └── use_local_jar.kts ├── kt_tests ├── custom_entry_nopckg.kt ├── custom_entry_withpckg.kt ├── default_entry_nopckg.kt ├── default_entry_withpckg.kt ├── main_with_deps.kt └── simple_app.kt ├── local_script_file.sh ├── memory_test.kts ├── multi_line_deps.kts ├── package_example.kts ├── script_in_pckg.kts ├── script_with_compile_flags.kts ├── url_test.kts └── uses_self_file_name.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.bat text eol=crlf 4 | 5 | *.png binary 6 | *.jpg binary 7 | *.jar binary 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - ubuntu-20.04 17 | - macos-12 18 | - windows-2022 19 | variant: 20 | - posix 21 | include: 22 | - os: windows-2022 23 | variant: cygwin 24 | - os: windows-2022 25 | variant: cmd 26 | runs-on: ${{ matrix.os }} 27 | timeout-minutes: 30 28 | 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v3 32 | 33 | - name: Setup Java 34 | uses: actions/setup-java@v3 35 | with: 36 | distribution: 'zulu' 37 | java-version: '11' 38 | 39 | - name: Setup Kotlin 40 | uses: fwilhe2/setup-kotlin@main 41 | with: 42 | version: 1.7.21 43 | 44 | - name: Setup Gradle 45 | uses: gradle/gradle-build-action@v2.4.2 46 | with: 47 | gradle-version: 8.0.2 48 | 49 | - name: Install dependencies for ${{ runner.os }} 50 | shell: bash 51 | run: | 52 | if [ "$RUNNER_OS" == "Windows" ]; then 53 | choco install zip 54 | # Overwrite the WSL bash.exe 55 | # cp /c/msys64/usr/bin/bash.exe /c/Windows/System32/bash.exe 56 | mv /c/Windows/System32/bash.exe /c/Windows/System32/wsl-bash.exe 57 | fi 58 | 59 | - name: Run tests for Posix (and MSYS on Windows) 60 | if: matrix.variant == 'posix' 61 | shell: bash 62 | run: | 63 | # For Windows this action is running MSYS Os type 64 | 65 | echo "OsType: $OSTYPE" 66 | 67 | gradle clean assemble test || { echo 'Compilation or Unit tests failed' ; exit 1; } 68 | 69 | if [[ "$OSTYPE" == "linux"* ]]; then 70 | echo "Linux test..." 71 | gradle -DosType=$OSTYPE -DincludeTags='posix | linux' integrationTest 72 | elif [[ "$OSTYPE" == "darwin"* ]]; then 73 | echo "MacOs test..." 74 | gradle -DosType=$OSTYPE -DincludeTags='posix | macos' integrationTest 75 | elif [[ "$OSTYPE" == "cygwin" ]]; then 76 | echo "Cygwin test..." 77 | gradle -DosType=$OSTYPE -DincludeTags='posix | cygwin' integrationTest 78 | elif [[ "$OSTYPE" == "msys" ]]; then 79 | echo "MSys test..." 80 | gradle -DosType=$OSTYPE -DincludeTags='posix | msys' integrationTest 81 | elif [[ "$OSTYPE" == "freebsd"* ]]; then 82 | echo "FreeBsd test..." 83 | gradle -DosType=$OSTYPE -DincludeTags='posix' integrationTest 84 | else 85 | echo "Unknown OS" 86 | exit 1 87 | fi 88 | 89 | - name: Run tests specific for Windows (cmd shell) 90 | if: matrix.variant == 'cmd' 91 | shell: cmd 92 | run: | 93 | echo "Windows test..." 94 | gradle -DosType=windows -DincludeTags="windows" clean assemble test integrationTest 95 | 96 | - name: Install Cygwin (only Windows) 97 | if: matrix.variant == 'cygwin' 98 | uses: egor-tensin/setup-cygwin@v3 99 | 100 | - name: Run tests specific for Cygwin 101 | if: matrix.variant == 'cygwin' 102 | shell: C:\tools\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' 103 | run: | 104 | echo $OSTYPE 105 | echo "Cygwin test..." 106 | echo "Changing directory to $GITHUB_WORKSPACE ..." 107 | cd $GITHUB_WORKSPACE 108 | gradle clean assemble test || { echo 'Compilation or Unit tests failed' ; exit 1; } 109 | gradle -DosType=$OSTYPE -DincludeTags='posix | cygwin' integrationTest 110 | -------------------------------------------------------------------------------- /.github/workflows/installer.yml: -------------------------------------------------------------------------------- 1 | name: installer 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | kscript_version: 7 | description: 'KScript version to test' 8 | required: true 9 | default: '4.1.1' 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-20.04 18 | - macos-12 19 | - windows-2022 20 | variant: 21 | - sdkman 22 | include: 23 | - os: macos-12 24 | variant: brew 25 | # - os: windows-2022 26 | # variant: sdkman-cygwin, scoop-windows 27 | 28 | 29 | runs-on: ${{ matrix.os }} 30 | timeout-minutes: 60 31 | 32 | env: 33 | KSCRIPT_VERSION: ${{ github.event.inputs.kscript_version }} 34 | 35 | steps: 36 | - uses: actions/setup-java@v3 37 | with: 38 | distribution: 'zulu' 39 | java-version: '11' 40 | - uses: fwilhe2/setup-kotlin@main 41 | with: 42 | version: 1.7.21 43 | 44 | - name: Prerequisites for ${{ runner.os }} 45 | shell: bash 46 | run: | 47 | if [ "$RUNNER_OS" == "Windows" ]; then 48 | choco install zip 49 | # Overwrite the WSL bash.exe 50 | cp /c/msys64/usr/bin/bash.exe /c/Windows/System32/bash.exe 51 | fi 52 | 53 | # for Windows by default it runs MSYS 54 | - name: Check SdkMan for ${{ runner.os }} 55 | if: matrix.variant == 'sdkman' 56 | shell: bash 57 | run: | 58 | bash -c "curl -s "https://get.sdkman.io" | bash" 59 | source "$HOME/.sdkman/bin/sdkman-init.sh" 60 | 61 | sdk install kscript ${{ env.KSCRIPT_VERSION }} 62 | kscript --help 63 | kscript -d "println(1+1)" 64 | sdk uninstall kscript ${{ env.KSCRIPT_VERSION }} 65 | 66 | - name: Check brew for ${{ runner.os }} 67 | if: matrix.variant == 'brew' 68 | shell: bash 69 | run: | 70 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 71 | export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 72 | brew install kscripting/tap/kscript 73 | kscript --help 74 | kscript -d "println(1+1)" 75 | brew uninstall kscripting/tap/kscript 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | target/ 4 | out/ 5 | 6 | *.iml 7 | .idea 8 | 9 | kscript.jar 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Holger Brandl, Marcin Kuszczak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TODO.adoc: -------------------------------------------------------------------------------- 1 | = kscript 4.3 features: 2 | 3 | * Change a way of executing .kts files (investigate if we can get rid of reflections) 4 | * Passing additional information to scripts (args, script name, script path, kscript path etc.) 5 | * Onboard on brew 6 | * Deprecate KotlinOptions 7 | * New package for Windows e.g. scoop 8 | * Release scripts in Kotlin 9 | * Windows console support requires @argfiles as kotlin/kotlinc command line might be too long to execute it from console (especially for big classpaths). 10 | * Improve Unit tests coverage 11 | * Use compilation option -include-runtime: https://kotlinlang.org/docs/command-line.html#create-and-run-an-application 12 | * Integration tests - more tests should be enabled; 13 | * kscript - some features might be disabled on specific OSes - handle that on code level e.g. throw exception if for some OS feature is not available. 14 | * Deprecate referencing script by $HOME and by '/' (it is handled now safely, but does it make sense to keep it?) 15 | * Compatibility with Kotlin Scripting 16 | * Onboard on docker etc. and other release channels 17 | * Abstraction for shell command (Command class containing e.g. environment variables) 18 | * Change description of kscript in SDKMan page 19 | * Deployments for Docker 20 | * Deployments for ArchLinux 21 | -------------------------------------------------------------------------------- /docs/.notes_images/7eef33f0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscripting/kscript/6acd4e1907478c49cc4e3db640200f926933b15d/docs/.notes_images/7eef33f0.png -------------------------------------------------------------------------------- /env_posix.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 4 | PROJECT_DIR=$(realpath "$SCRIPT_DIR") 5 | KSCRIPT_EXEC_DIR="$PROJECT_DIR/build/kscript/bin" 6 | KSCRIPT_TEST_DIR="$PROJECT_DIR/build/tmp/test" 7 | 8 | mkdir -p $KSCRIPT_EXEC_DIR 9 | mkdir -p $KSCRIPT_TEST_DIR 10 | 11 | echo "Setting up environment..." 12 | echo "SCRIPT_DIR : $SCRIPT_DIR" 13 | echo "PROJECT_DIR: $PROJECT_DIR" 14 | echo "KSCRIPT_EXEC_DIR: $KSCRIPT_EXEC_DIR" 15 | echo "KSCRIPT_TEST_DIR: $KSCRIPT_TEST_DIR" 16 | echo 17 | 18 | if [[ "$PATH" != *"$KSCRIPT_EXEC_DIR"* ]]; then 19 | export PATH=$KSCRIPT_EXEC_DIR:$PATH 20 | fi 21 | 22 | echo "KScript path for testing: $(which kscript)" 23 | 24 | alias cdk="cd $PROJECT_DIR" 25 | 26 | alias switchPath=' 27 | if [[ "$PATH" != *"$KSCRIPT_EXEC_DIR"* ]]; then 28 | export PATH="$KSCRIPT_EXEC_DIR:$PATH" 29 | echo "Project path set." 30 | else 31 | export PATH=$(echo $PATH | tr ":" "\n" | grep -v "$KSCRIPT_EXEC_DIR" | grep -v "^$" | tr "\n" ":") 32 | echo "Generic path set." 33 | fi 34 | ' 35 | 36 | alias help-dev="cat $SCRIPT_DIR/test/help-dev.txt" 37 | -------------------------------------------------------------------------------- /env_windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set SCRIPT_DIR=%~dp0 3 | set PATH=%SCRIPT_DIR%build\kscript\bin;%PATH% 4 | echo %PATH% 5 | set SCRIPT_DIR= 6 | -------------------------------------------------------------------------------- /examples/basic_template_example.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kscript 2 | // TODO fill here what your script does 3 | 4 | import kotlin.system.exitProcess 5 | 6 | class AnsiColors { companion object { const val ANSI_RESET = "\u001B[0m"; const val ANSI_RED = "\u001B[31m"; const val ANSI_GREEN = "\u001B[32m"; const val ANSI_YELLOW = "\u001B[33m"; const val ANSI_BLUE = "\u001B[34m"; const val ANSI_PURPLE = "\u001B[35m"; const val ANSI_CYAN = "\u001B[36m"; const val ANSI_WHITE = "\u001B[37m"; } } 7 | 8 | fun logInfo(message: String) = println("${AnsiColors.ANSI_BLUE}$message${AnsiColors.ANSI_RESET}") 9 | fun logWarn(message: String) = println("${AnsiColors.ANSI_YELLOW}$message${AnsiColors.ANSI_RESET}") 10 | fun logError(message: String) = println("${AnsiColors.ANSI_RED}$message${AnsiColors.ANSI_RESET}") 11 | 12 | val usage = """ 13 | Use this tool to... 14 | required params info 15 | """ 16 | 17 | if (args.size < 2) { 18 | logWarn(usage) 19 | exitProcess(-1) 20 | } 21 | 22 | val arg1 = args.get(0) 23 | val arg2 = args.get(1) 24 | 25 | // 28 | -------------------------------------------------------------------------------- /examples/classpath_example.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kscript 2 | 3 | //DEPS com.offbytwo:docopt:0.6.0.20150202,log4j:log4j:1.2.14 4 | 5 | //#!/usr/bin/env kotlinc -script -classpath /Users/brandl/.m2/repository/org/docopt/docopt/0.6.0-SNAPSHOT/docopt-0.6.0-SNAPSHOT.jar 6 | 7 | import org.docopt.Docopt 8 | import java.io.File 9 | import java.util.* 10 | 11 | 12 | // woraround for https://youtrack.jetbrains.com/issue/KT-13347 13 | //val args = listOf("foo", "bar") 14 | 15 | val usage =""" 16 | kscript - Enhanced scripting support for Kotlin on *nix-based systems. 17 | 18 | Usage: 19 | kscript ( -t | --text ) 20 | kscript [ --interactive | --idea | --package ] [--] ( - | ) []... 21 | kscript (-h | --help) 22 | 23 | Options: 24 | -t, --text Enable stdin support API for more streamlined text processing [default: latest] 25 | --package Package script and dependencies into self-dependent binary 26 | --idea boostrap IDEA from a kscript 27 | -i, --interactive Create interactive shell with dependencies as declared in script 28 | - Read script from the STDIN 29 | -h, --help Print this text 30 | --clear-cache Wipe cached script jars and urls 31 | """ 32 | 33 | val doArgs = Docopt(usage).parse(args.toList()) 34 | 35 | println("parsed args are: \n$doArgs (${doArgs.javaClass.simpleName})\n") 36 | 37 | doArgs.forEach { (key: Any, value: Any) -> 38 | println("$key:\t$value\t(${value?.javaClass?.canonicalName})") 39 | } 40 | 41 | println("\nHello from Kotlin!") 42 | for (arg in args) { 43 | println("arg: $arg") 44 | } 45 | -------------------------------------------------------------------------------- /examples/clikt-example.kts: -------------------------------------------------------------------------------- 1 | //@file:DependsOn("com.github.ajalt:clikt:2.7.1") 2 | @file:DependsOn("com.github.ajalt.clikt:clikt-jvm:3.0.1") 3 | 4 | 5 | import com.github.ajalt.clikt.core.CliktCommand 6 | import com.github.ajalt.clikt.parameters.options.default 7 | import com.github.ajalt.clikt.parameters.options.option 8 | import com.github.ajalt.clikt.parameters.options.prompt 9 | import com.github.ajalt.clikt.parameters.types.int 10 | 11 | class Hello : CliktCommand() { 12 | val count: Int by option(help="Number of greetings").int().default(1) 13 | val name: String by option(help="The person to greet").prompt("Your name") 14 | 15 | override fun run() { 16 | repeat(count) { 17 | echo("Hello $name!") 18 | } 19 | } 20 | } 21 | //@file:KotlinOpts("-J-Xmx5g") 22 | //@file:KotlinOpts("-J-server") 23 | //@file:CompilerOpts("-jvm-target 1.8") 24 | 25 | Hello().main(args) 26 | //Startup.main(args) -------------------------------------------------------------------------------- /examples/java_module_example.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kscript 2 | 3 | 4 | // See here for kscript installation instructions 5 | // https://github.com/holgerbrandl/kscript 6 | 7 | //@file:DependsOn("com.amazon.redshift:redshift-jdbc4:1.1.17.1017") 8 | //@file:MavenRepository("redshift", "http://redshift-maven-repository.s3-website-us-east-1.amazonaws.com/release") 9 | //@file:CompilerOpts("-jvm-target 1.8") 10 | 11 | import java.sql.* 12 | 13 | 14 | // See https://github.com/holgerbrandl/kscript/issues/220#issuecomment-503002241 15 | // todo which one works 16 | @file:KotlinOpts("-J'--add-modules java.sql'") 17 | @file:CompilerOpts("-J'--add-modules java.sql'") 18 | 19 | 20 | print("sql example is") 21 | print(Connection.TRANSACTION_SERIALIZABLE) -------------------------------------------------------------------------------- /examples/kotlinc_example.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlinc -script 2 | 3 | // This is NOT a kscript example but demonstrates how kotlin scripting would work without kscript. 4 | // In the shebang line we point to kotlinc in scripting mode. Note, that this will just work in some flavors of bash. 5 | 6 | // If `args` are showing up as red in Intellij, you've stumbled over an intellij bug. 7 | // See https://youtrack.jetbrains.com/issue/KT-15019 8 | 9 | println("Hello from Kotlin!") 10 | for (arg in args) { 11 | println("arg: $arg") 12 | } -------------------------------------------------------------------------------- /examples/more_examples.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | kscript - test2.fastq <<"EOF" 4 | java.io.File(args[0]).useLines { 5 | it.map { 6 | if (!it.startsWith(">")) it else "huhu" + it 7 | }.forEach { println(it) } 8 | } 9 | EOF 10 | 11 | 12 | 13 | 14 | kscript - <<"EOF" 15 | println("getting started") 16 | generateSequence() { readLine() }.map { 17 | if (!it.startsWith(">")) it else 18 | "huhu" + it 19 | }.forEach { println(it) } 20 | EOF 21 | 22 | 23 | head -n 1000 test.fastq > test2.fastq 24 | 25 | 26 | ## how use stdin but still use heredoc for cod 27 | kscript - test2.fastq <(echo <<"EOF" 28 | println("getting started") 29 | //generateSequence() { readLine() }.map { 30 | File(args[0]).readLines().map { 31 | if (!it.startsWith(">")) it else 32 | "huhu" + it 33 | }.forEach { println(it) } 34 | EOF 35 | ) 36 | 37 | 38 | 39 | ## todo provide picard tools examples 40 | http://mvnrepository.com/artifact/com.github.broadinstitute/picard/2.5.0 41 | potentially like 42 | https://www.biostars.org/p/52698/ 43 | 44 | 45 | ## simplified line streaming 46 | 47 | 48 | 49 | kscript -s ' 50 | //DEPS de.mpicbg.scicomp:kutils:0.2-SNAPSHOT 51 | kutils.KscriptHelpers.processStdin { "huhu" + it } 52 | 53 | ' 54 | 55 | ## todo streamline further by using simple code wrapper for (see https://github.com/holgerbrandl/kscript/issues/9) 56 | kscript -st '"huhu" + it' 57 | 58 | 59 | ## REPL test: Filter-Join fasta files by ID 60 | kotlinc -classpath $(resdeps.kts de.mpicbg.scicomp:kutils:0.2) 61 | <<"EOF" 62 | import de.mpicbg.scicomp.kscript.* 63 | 64 | "house\nasdf".processLines { "huhu" + it } 65 | 66 | EOF -------------------------------------------------------------------------------- /examples/url_example.kts: -------------------------------------------------------------------------------- 1 | println("Hello kscript-user") 2 | println("The arguments were " + args.joinToString(", ")) -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscripting/kscript/6acd4e1907478c49cc4e3db640200f926933b15d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /misc/CustomResolverExample.kts: -------------------------------------------------------------------------------- 1 | package kscript.app 2 | 3 | import kotlin.script.experimental.dependencies.CompoundDependenciesResolver 4 | import kotlin.script.experimental.dependencies.FileSystemDependenciesResolver 5 | import kotlin.script.experimental.dependencies.RepositoryCoordinates 6 | import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver 7 | 8 | suspend fun main() { 9 | val jcenterResolver = MavenDependenciesResolver().apply { 10 | addRepository(RepositoryCoordinates("https://jcenter.bintray.com") ) 11 | } 12 | 13 | val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver(), jcenterResolver) 14 | // resolver.addRepository() 15 | 16 | val example = "de.mpicbg.scicomp:kutils:0.7" 17 | // val example = "log4j:log4j:1.2+" 18 | 19 | val results = resolver.resolve(example) 20 | 21 | print(results) 22 | } 23 | -------------------------------------------------------------------------------- /misc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | MAINTAINER go.kscripting@gmail.com 4 | 5 | ARG KSCRIPT_VERSION=4.1.1 6 | ARG KOTLIN_VERSION=1.7.21 7 | 8 | RUN \ 9 | # Install bash & Java \ 10 | apk add bash openjdk11 && \ 11 | \ 12 | # Create temp dir 13 | cd $(mktemp -d) && \ 14 | \ 15 | # Install kscript \ 16 | wget https://github.com/holgerbrandl/kscript/releases/download/v${KSCRIPT_VERSION}/kscript-${KSCRIPT_VERSION}-bin.zip -q -O - | \ 17 | unzip - && \ 18 | mv kscript-${KSCRIPT_VERSION}/bin/* /usr/local/bin && \ 19 | chmod a+x /usr/local/bin/kscript && \ 20 | \ 21 | # Install Kotlin 22 | wget https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip -q -O - | \ 23 | unzip - && \ 24 | chmod a+x kotlinc/bin/kotlin kotlinc/bin/kotlinc && \ 25 | mv kotlinc /opt && \ 26 | \ 27 | # Done 28 | rm -rf $PWD 29 | 30 | WORKDIR /w 31 | 32 | ENTRYPOINT KOTLIN_HOME=/opt/kotlinc \ 33 | PATH=/opt/kotlinc/bin:$PATH \ 34 | kscript "$0" "$@" 35 | 36 | CMD [ "--help" ] 37 | -------------------------------------------------------------------------------- /misc/benchmarking/benchmark_kscript.R: -------------------------------------------------------------------------------- 1 | # devtools::source_url("https://raw.githubusercontent.com/holgerbrandl/datautils/v1.46/R/core_commons.R") 2 | 3 | devtools::source_url("https://raw.githubusercontent.com/holgerbrandl/datautils/v1.46/R/stats/ci_commons.R") 4 | 5 | 6 | benchData = list.files(pattern = "scriptlet_runtimes*") %>% map_df(~ { 7 | # .x= "scriptlet_runtimes_pr93.txt" 8 | read_tsv(.x) %>% 9 | mutate(version = str_match(.x, "scriptlet_runtimes_(.*).txt") %>% get_col(2)) %>% 10 | mutate_at(vars(cached), as.logical) 11 | }) 12 | 13 | benchData %>% 14 | filter(cached == FALSE) %>% 15 | group_by(version, script_name) %>% 16 | plot_ci(runtime_ms) + 17 | ggtitle("without cached jar") + 18 | scale_y_continuous(limits = c(0, NA)) 19 | 20 | benchData %>% 21 | filter(cached == TRUE) %>% 22 | group_by(version, script_name) %>% 23 | plot_ci(runtime_ms) + 24 | ggtitle("with cached jar") + 25 | scale_y_continuous(limits = c(0, NA)) 26 | 27 | 28 | benchData %>% ggplot(aes(version, runtime_ms, color = cached)) + 29 | geom_jitter(height = 0, width = 0.1) + 30 | scale_y_continuous(limits = c(0, NA)) + 31 | facet_grid(~ script_name) 32 | 33 | -------------------------------------------------------------------------------- /misc/benchmarking/benchmark_kscript.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kscript 2 | 3 | 4 | @file:DependsOn("de.mpicbg.scicomp:kutils:0.9.0") 5 | @file:DependsOn("de.mpicbg.scicomp:krangl:0.6") 6 | 7 | import de.mpicbg.scicomp.kutils.evalBash 8 | import krangl.* 9 | import org.apache.commons.csv.CSVFormat 10 | import kotlin.system.measureTimeMillis 11 | 12 | require(args.size == 1) { "Usage: benchmark_kscript.kts " } 13 | 14 | //val resultFile = "scriptlet_runtimes_pr93.txt" 15 | //val resultFile = "scriptlet_runtimes_master.txt" 16 | val resultFile = args[0] 17 | 18 | val examples = mapOf("printSum" to """println("1+2")""", "simpleSum" to """1+1""") 19 | 20 | 21 | // first run with empty cache 22 | fun clearCache() = evalBash("kscript --clear-cache") 23 | 24 | 25 | fun evalKscript(kscriptlet: String, cached: Boolean = false): Long { 26 | if (cached) { 27 | require(evalBash("""kscript '$kscriptlet'""").exitCode == 0) 28 | } else { 29 | clearCache() 30 | } 31 | 32 | return measureTimeMillis { 33 | require(evalBash("""kscript '$kscriptlet'""").exitCode == 0) { "scriptlet eval failed for $kscriptlet" } 34 | } 35 | } 36 | 37 | //data class ScripletRuntime(val name:String, val cached:Boolean, val runtimeMs:Int) 38 | 39 | 40 | //evalKscript(examples.values.first(), cached = true) 41 | //val cachedR = (1..5).map {evalKscript(examples.values.first())}.map{ScripletRuntime} 42 | 43 | 44 | //var runtimes = examples.toList().asDataFrame { mapOf("script_name" to it.first, "scriptlet" to it.second) } 45 | //runtimes.outerJoin(DataFrame.builder("with_cached")(true, false)) 46 | //runtimes = runtimes.addColumn("runtime") { it["scriptlet"].asStrings().map { evalKscript(it!!) } } 47 | 48 | System.err.println("running benchmark suite") 49 | 50 | var runtimes = examples.toList().flatMap { ex -> 51 | listOf(true, false).flatMap { cached -> 52 | (1..10).toList().map { rep -> 53 | System.err.print(".") 54 | listOf(evalKscript(ex.second, cached)).asDataFrame { runtime -> 55 | mapOf( 56 | "script_name" to ex.first, 57 | // "scriptlet" to ex.second, 58 | "cached" to cached, 59 | "runtime_ms" to runtime 60 | ) 61 | } 62 | } 63 | } 64 | }.bindRows() 65 | 66 | runtimes.writeCSV(resultFile, CSVFormat.TDF) 67 | -------------------------------------------------------------------------------- /misc/benchmarking/benchmarking.md: -------------------------------------------------------------------------------- 1 | 2 | ```bash 3 | 4 | cd ${KSCRIPT_HOME} 5 | 6 | ## make sure to use devel-version 7 | export PATH=${KSCRIPT_HOME}:${PATH} 8 | which kscript 9 | 10 | #https://stackoverflow.com/questions/9257533/what-is-the-difference-between-origin-and-upstream-on-github 11 | 12 | prNumber=93 13 | git fetch upstream pull/${prNumber}/head:pr${prNumber} 14 | git checkout pr${prNumber} 15 | ./gradlew shadowJar 16 | 17 | ## see https://stackoverflow.com/questions/6245570/how-to-get-the-current-branch-name-in-git 18 | misc/benchmarking/benchmark_kscript.kts scriptlet_runtimes_$(git rev-parse --abbrev-ref HEAD).txt 19 | 20 | 21 | ## roll back to master and redo the benchmarking 22 | git checkout master 23 | ./gradlew clean assemble 24 | 25 | misc/benchmarking/benchmark_kscript.kts scriptlet_runtimes_$(git rev-parse --abbrev-ref HEAD)-$(git rev-parse --short HEAD).txt 26 | 27 | ## render the report (see https://github.com/holgerbrandl/datautils/tree/master/tools/rendr) 28 | rend.R misc/benchmarking/benchmark_kscript.R 29 | 30 | 31 | ## export commit history for date references 32 | # https://stackoverflow.com/questions/14243380/how-to-configure-git-log-to-show-commit-date 33 | #https://stackoverflow.com/questions/3631005/git-log-tabular-formatting 34 | 35 | git log --pretty=format:'%h %<(20)%an %s %cd' > kscript_commit_history.txt 36 | 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /misc/boostrap_header.sh: -------------------------------------------------------------------------------- 1 | function _in_path() { command -v "$1" >/dev/null 2>&1; } 2 | _in_path kscript && echo "kscript is already installed at $(which kscript)" 1>&2 || { 3 | function echo_and_eval() { echo "$ $@" 1>&2; eval "$@" 1>&2; } 4 | _in_path sdk || { 5 | export SDKMAN_DIR="$HOME/.sdkman" && curl "https://get.sdkman.io" | bash 1>&2 && \ 6 | echo_and_eval source "$SDKMAN_DIR/bin/sdkman-init.sh" 7 | } 8 | 9 | sdkman_auto_answer=true 10 | 11 | _in_path java || echo_and_eval sdk install java 12 | _in_path kotlin || echo_and_eval sdk install kotlin 13 | _in_path gradle || echo_and_eval sdk install gradle 14 | _in_path kscript || echo_and_eval sdk install kscript 15 | echo_and_eval source "$SDKMAN_DIR/bin/sdkman-init.sh" 16 | } 17 | -------------------------------------------------------------------------------- /misc/bootstrap_tester/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | MAINTAINER holgerbrandl@gmail.com 4 | 5 | RUN apt-get update 6 | RUN apt-get install -y zip 7 | RUN apt-get install -y curl 8 | -------------------------------------------------------------------------------- /misc/bootstrap_tester/README.md: -------------------------------------------------------------------------------- 1 | # Test Protocol for Bootstrap Header 2 | 3 | ```bash 4 | cd $KSCRIPT_HOME/misc/bootstrap_tester 5 | docker build -t bstester . 6 | 7 | echo "println(42)" >> bs_test.kts 8 | 9 | # Enrich the script with a bootstrap header 10 | export PATH=${KSCRIPT_HOME}/build/libs:${PATH} 11 | which kscript 12 | #kscript --help 13 | 14 | kscript --add-bootstrap-header bs_test.kts 15 | cat bs_test.kts 16 | 17 | # spin up the container to test the 18 | #docker run --rm -v ${PWD}:/apps -it ubuntu 19 | docker run --rm -v ${PWD}:/apps -it bstester 20 | ``` 21 | 22 | Within the container run 23 | ```bash 24 | cd /apps 25 | bash bs_test.kts 26 | ``` 27 | -------------------------------------------------------------------------------- /misc/docker_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | 5 | ######################################################################################################################## 6 | ## Build and upload the image to the registry 7 | 8 | cd ${KSCRIPT_HOME}/misc 9 | #docker run --name kscript_interactive -it ubuntu:18.04 10 | 11 | #docker build --no-cache -t kscript_tester . 12 | docker build -t kscript_tester . 13 | 14 | # quick test -> does it work (see section below) 15 | 16 | #docker login 17 | 18 | ## create the versioned tag 19 | docker tag kscript_tester holgerbrandl/kscript_tester:jdk11_kotlin1.3.31_gradle4.10 20 | docker push holgerbrandl/kscript_tester:jdk11_kotlin1.3.31_gradle4.10 21 | 22 | ## create the latest tag 23 | docker tag kscript_tester holgerbrandl/kscript_tester 24 | docker push holgerbrandl/kscript_tester 25 | 26 | 27 | 28 | ######################################################################################################################## 29 | ## Use the image for testing and debugging 30 | 31 | cd ${KSCRIPT_HOME} 32 | 33 | 34 | docker run -it --rm -v $(pwd)/kscript_docker:/kscript kscript_tester 35 | docker run -it --rm -v $(pwd)/kscript_docker:/kscript a9945a6a860d 36 | 37 | ## with slash escaped for windows 38 | #docker run -it --rm -v d:/projects/misc/kscript://kscript kscript_tester 39 | #docker run -it -v ${pwd}:/kscript kscript_tester 40 | 41 | ## path path tp allow env usage within container 42 | export PATH=/kscript/build/libs:$PATH 43 | export KSCRIPT_HOME=/kscript 44 | 45 | 46 | # https://stackoverflow.com/questions/28302178/how-can-i-add-a-volume-to-an-existing-docker-container 47 | #docker commit a9945a6a860d kscript_tmp 48 | #docker rm `docker ps -q -l` # restart it in the background 49 | #docker start `docker ps -q -l` # restart it in the background 50 | #docker attach `docker ps -q -l` # reattach the terminal & stdin 51 | 52 | ## rebuild 53 | cd $KSCRIPT_HOME 54 | ./gradlew assemble 55 | 56 | kscript --clear-cache 57 | 58 | ## run test suite 59 | # git clone https://github.com/holgerbrandl/kscript kscript_docker 60 | #cd kscript_docker 61 | #test/test_suite.sh 62 | ${KSCRIPT_HOME}/test/test_suite.sh 63 | 64 | 65 | ## manual test dependency lookup 66 | ./kscript --clear-cache 67 | rm -rf ~/.m2/; kscript --clear-cache 68 | resolve_deps() { kotlin -classpath kscript.jar kscript.app.DependencyUtil "$@";} 69 | 70 | resolve_deps log4j:log4j:1.2.14 71 | 72 | mvn -f /tmp/__resdeps__temp__6026124609748923742_pom.xml dependency:build-classpath 73 | 74 | ## copy newer jar into container 75 | #scp brandl@scicomp-mac-12-usb:/Users/brandl/projects/kotlin/kscript/kscript.jar . 76 | 77 | 78 | ##https://github.com/moby/moby/issues/26872 79 | ##https://stackoverflow.com/questions/38532483/where-is-var-lib-docker-on-mac-os-x 80 | 81 | 82 | ## network address docker ps && docker inspect -> IPAdress 83 | #https://stackoverflow.com/questions/17157721/how-to-get-a-docker-containers-ip-address-from-the-host 84 | #sshfs -o root@172.17.0.3:/ /Users/brandl/Desktop/docker_container/ #-o idmap=user -o uid=1001 -o gid=1001 85 | #sshfs -o 172.17.0.3:/ /Users/brandl/Desktop/docker_container/ #-o idmap=user -o uid=1001 -o gid=1001 86 | -------------------------------------------------------------------------------- /misc/experimental/jar_embedding.sh: -------------------------------------------------------------------------------- 1 | ####################################################################################################################### 2 | ### jar script embedding https://stackoverflow.com/questions/17339631/creating-shell-script-from-executable-java-jar-file 3 | 4 | cd /Users/brandl/projects/kotlin/kscript/misc/experimental 5 | 6 | #cp kscript2 kscriptJarStub 7 | (cd ../../ && ./gradlew shadowJar) 8 | 9 | cat kscriptJarStub /Users/brandl/projects/kotlin/kscript/build/libs/kscript-0.1-SNAPSHOT-all.jar > kscriptJar && chmod +x kscriptJar 10 | 11 | ./kscriptJar --help 12 | ./kscriptJar "println(1+1)" 13 | ./kscriptJar "1-" 14 | 15 | ## References 16 | #https://stackoverflow.com/questions/17583578/what-command-means-do-nothing-in-a-conditional-in-bash 17 | #https://github.com/megastep/makeself 18 | #https://stackoverflow.com/questions/10491704/embed-a-executable-binary-in-a-shell-script 19 | -------------------------------------------------------------------------------- /misc/experimental/kscriptD: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | exec $(kotlin -J-agentlib:jdwp=transport=dt_socket,server=n,address=scicomp-mac-12.local:8765,suspend=y -classpath build/libs/kscript-0.1-SNAPSHOT-all.jar kscript.app.KscriptKt "$@") 4 | -------------------------------------------------------------------------------- /misc/experimental/kscriptJarStub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | # resolve own location 5 | MYSELF=$(which "$0" 2>/dev/null) 6 | [ $? -gt 0 -a -f "$0" ] && MYSELF="./$0" 7 | 8 | exec $(kotlin -classpath $MYSELF kscript.app.KscriptKt "$@") 9 | #echo "survived exec!" 10 | -------------------------------------------------------------------------------- /misc/gradle2deps/gradle2deps.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kscript 2 | 3 | package gradle2deps 4 | 5 | import java.io.File 6 | import kotlin.system.exitProcess 7 | 8 | if (args.size == 0) { 9 | System.err.println("Usage: gradle2deps.kts ") 10 | exitProcess(-1) 11 | } 12 | 13 | val gradleFileName = args[0] 14 | //val gradleFile = File("/Users/brandl/projects/kotlin/krangl/examples/smile/build.gradle") 15 | val gradleFile = File(gradleFileName) 16 | 17 | val gradle = gradleFile.readLines() 18 | 19 | //val urlRegex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]" 20 | //val mavenRepoRegex = """maven [{]{1} url ['"]{1}($urlRegex)['"]{1} [}]""".toRegex() 21 | 22 | 23 | //mavenRepoRegex.matches("""maven { url 'https://jitpack.io' } """)?.groupValues 24 | //val df = mavenRepoRegex.toRegex().find("""https://jitpack.io""") 25 | //df?.groupValues 26 | 27 | // find repos 28 | val reposAnnotatoins = gradle 29 | .dropWhile { !it.startsWith("repositories") } 30 | .takeWhile { it != "}" }.toList() 31 | .map(String::trim) 32 | .flatMap { it.replace("'", "").split(" ").filter { it.startsWith("http") } } 33 | .mapIndexed { index, repoUrl -> """@file:MavenRepository("repo${index + 1}","$repoUrl")""" } 34 | 35 | // Examples 36 | // mavenLocal() 37 | // mavenCentral() 38 | // maven { url 'https://jitpack.io' } 39 | 40 | 41 | // compile "org.jetbrains.kotlin:kotlin-stdlib" 42 | // compile "org.jetbrains.kotlin:kotlin-reflect" 43 | //// compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 44 | // 45 | // 46 | // compile "org.apache.commons:commons-csv:1.3" 47 | //// compile "com.univocity:univocity-parsers:2.7.5" 48 | // compile 'com.beust:klaxon:0.30' 49 | //// compile 'me.tongfei:progressbar:0.5.5' 50 | // 51 | // testCompile group: 'junit', name: 'junit', version: '4.12' 52 | //// testCompile "io.kotlintest:kotlintest-runner-junit5:3.1.9" 53 | // testCompile 'io.kotlintest:kotlintest-assertions:3.1.6' 54 | listOf(1, 2, 3).map(Int::dec) 55 | val depAnnotations = gradle 56 | .dropWhile { !it.startsWith("dependencies") } 57 | .takeWhile { it != "}" } 58 | .map(String::trim) 59 | .filter { it.startsWith("compile") } 60 | .map { 61 | it.removePrefix("compile ") 62 | .replace("group: '", "\"") 63 | .replace(", name: '", ":") 64 | .replace(", version: '", ":") 65 | .replace("'", "").replace("\"", "") 66 | } 67 | .filterNot { it.contains("jetrains.kotlin:kotlin") } 68 | .map { """@file:DependsOn("$it")""" } 69 | 70 | 71 | println(reposAnnotatoins.joinToString("\n")) 72 | println() 73 | println() 74 | println(depAnnotations.joinToString("\n")) 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /misc/kshell_launcher/README.md: -------------------------------------------------------------------------------- 1 | # KShell launcher for kscriptlets 2 | 3 | 4 | Based on https://github.com/khud/sparklin which is a proof-of-concept software that includes experimental new Kotlin REPL called `KShell`. 5 | 6 | ## Installation 7 | 8 | Since not all dependencies of `sparklin` are hosted on maven-central yet, we need to install some of its dependencies manually into our local maven repo 9 | 10 | ```bash 11 | git clone https://github.com/khud/sparklin 12 | cd sparklin 13 | git checkout f200d1 14 | mvn clean install 15 | 16 | cd .. 17 | git clone https://github.com/khud/kshell-repl-api 18 | cd kshell-repl-api 19 | git checkout c32e4e 20 | mvn install 21 | ``` 22 | 23 | Now since all dependencies are met we can simply fetch the launcher script 24 | ```bash 25 | cd ~/bin 26 | wget https://raw.githubusercontent.com/holgerbrandl/kscript/master/misc/kshell_launcher/kshell_kts.sh 27 | chmod +x kshell_kts.sh 28 | ``` 29 | 30 | You may want to add it to your `PATH` as well. 31 | 32 | ## Usage 33 | 34 | Simply provide any kscript as argument. E.g [`krangl_example.kts`](https://github.com/holgerbrandl/kscript/blob/master/misc/kshell_launcher/krangl_example.kts) 35 | 36 | ```bash 37 | kshell_kts.sh krangl_example.kts 38 | ``` 39 | This will launch a `kshell`-session with all dependencies from the kscriptlet in the class path 40 | 41 | ``` 42 | Preparing interactive session by resolving script dependencies... 43 | [2] import krangl.schema 44 | [3] import krangl.irisData 45 | [4] irisData.schema() 46 | DataFrame with 150 observations 47 | Sepal.Length [Dbl] 5.1, 4.9, 4.7, 4.6, 5, 5.4, 4.6, 5, 4.4, 4.9, 5.4, 4.8, 4.8, 4.3, 5.8, 5.7, 5.4,... 48 | Sepal.Width [Dbl] 3.5, 3, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.4, 3, 3, 4, 4.4, 3.9, 3.5... 49 | Petal.Length [Dbl] 1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.6, 1.4, 1.1, 1.2, 1.5, ... 50 | Petal.Width [Dbl] 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.2, 0.1, 0.1, 0.2, 0.4, ... 51 | Species [Str] setosa, setosa, setosa, setosa, setosa, setosa, setosa, setosa, setosa, setosa, ... 52 | [6] 53 | ``` 54 | 55 | ## Todo 56 | 57 | * bundle this with kscript itself -------------------------------------------------------------------------------- /misc/kshell_launcher/docker_example.sh: -------------------------------------------------------------------------------- 1 | # start container with 2 | #docker pull maven:3.5.4-jdk-8 3 | #docker run --rm --name sparklin -it maven:3.5.4-jdk-8 /bin/bash --login 4 | 5 | 6 | ## install kotlin, maven and gradle 7 | apt-get update 8 | apt-get install zip 9 | curl -s 'https://get.sdkman.io' | bash 10 | source ~/.sdkman/bin/sdkman-init.sh && sdkman_auto_answer=true && sdk install kotlin && sdk install maven && sdk install gradle && sdk install kscript 11 | 12 | cd / 13 | git clone https://github.com/khud/sparklin 14 | cd sparklin 15 | git checkout f200d1461d7f5f6ca625c39fb7915d4fa71eb56e 16 | mvn clean install 17 | 18 | cd / 19 | git clone https://github.com/khud/kshell-repl-api 20 | cd kshell-repl-api 21 | git checkout c32e4e 22 | mvn install 23 | 24 | ## test the orignal launcher 25 | #../sparklin/bin/kshell.sh 26 | 27 | cd / 28 | wget https://raw.githubusercontent.com/holgerbrandl/kscript/master/misc/kshell_launcher/kshell_kts.sh 29 | chmod +x kshell_kts.sh 30 | 31 | echo '@file:DependsOn("de.mpicbg.scicomp:krangl:0.9.1")' >> example.kts 32 | 33 | ./kshell_kts.sh example.kts 34 | -------------------------------------------------------------------------------- /misc/kshell_launcher/krangl_example.kts: -------------------------------------------------------------------------------- 1 | @file:DependsOn("com.offbytwo:docopt:0.6.0.20150202", "log4j:log4j:1.2.14") 2 | @file:DependsOn("de.mpicbg.scicomp:krangl:0.9.1") 3 | 4 | import krangl.irisData 5 | import krangl.schema 6 | 7 | 8 | irisData.schema() -------------------------------------------------------------------------------- /misc/kshell_launcher/kshell_kts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | if [ $# -ne 1 ]; then 5 | echo "Usage: kshell_kts.sh " 6 | exit 0 7 | fi 8 | 9 | tmpfile=$(mktemp).kts 10 | 11 | #echo '@file:Import("https://git.io/fAJ5h")' >> $tmpfile 12 | echo ' 13 | @file:DependsOn("org.apache.hadoop:hadoop-common:2.7.0") 14 | 15 | // should be now on maven central 16 | @file:DependsOn("com.github.khud:kshell-repl-api:0.2.4-1.2.60") 17 | 18 | @file:DependsOn("sparklin:jline3-shaded:0.2") 19 | 20 | //@file:DependsOn("sparklin:kshell:0.2-SNAPSHOT") 21 | @file:DependsOn("sparklin:kshell:0.2.5") 22 | 23 | ' > $tmpfile 24 | echo '' >> $tmpfile 25 | 26 | argScript=$1 27 | #argScript=krangl_example.kts 28 | 29 | cat $argScript | grep '@file' >> $tmpfile 30 | 31 | #cat $tmpfile 32 | 33 | 34 | echo "Preparing interactive session by resolving script dependencies..." 35 | 36 | ## resolve dependencies without running the kscript 37 | KSCRIPT_DIR=$(dirname $(which kscript)) 38 | kscript_nocall() { kotlin -classpath ${KSCRIPT_DIR}/kscript.jar kscript.app.KscriptKt "$@";} 39 | 40 | kshellCP=$(kscript_nocall $tmpfile | cut -d' ' -f4) 41 | 42 | ## create new 43 | java -classpath "${kshellCP}" com.github.khud.sparklin.kshell.KotlinShell $@ 44 | -------------------------------------------------------------------------------- /misc/readme_images/minus_idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kscripting/kscript/6acd4e1907478c49cc4e3db640200f926933b15d/misc/readme_images/minus_idea.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "kscript" 2 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/AnnotationTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 7 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 8 | import org.apache.commons.io.FileUtils 9 | import org.junit.jupiter.api.Tag 10 | import org.junit.jupiter.api.Test 11 | import java.io.File 12 | 13 | class AnnotationTest : TestBase { 14 | @Test 15 | @Tag("posix") 16 | @Tag("windows") 17 | fun `There are some dependencies which are not jar, but maybe pom, aar and others - make sure they work, too`() { 18 | verify("kscript ${resolvePath("$projectDir/test/resources/depends_on_with_type.kts")}", 0, "getBigDecimal(1L): 1\n", any()) 19 | } 20 | 21 | @Test 22 | @Tag("posix") 23 | @Tag("windows") 24 | fun `Make sure that DependsOn is parsed correctly`() { 25 | verify("kscript ${resolvePath("$projectDir/test/resources/depends_on_annot.kts")}", 0, "kscript with annotations rocks!\n", any()) 26 | } 27 | 28 | @Test 29 | @Tag("posix") 30 | @Tag("windows") 31 | fun `Make sure that DependsOnMaven is parsed correctly`() { 32 | verify("kscript ${resolvePath("$projectDir/test/resources/depends_on_maven_annot.kts")}", 0, "kscript with annotations rocks!\n", any()) 33 | } 34 | 35 | @Test 36 | @Tag("posix") 37 | @Tag("windows") 38 | fun `Make sure that dynamic versions are matched properly`() { 39 | verify("kscript ${resolvePath("$projectDir/test/resources/depends_on_dynamic.kts")}", 0, "dynamic kscript rocks!\n", any()) 40 | } 41 | 42 | @Test 43 | @Tag("posix") 44 | @Tag("windows") 45 | fun `Make sure that MavenRepository is parsed correctly`() { 46 | verify( 47 | "kscript ${resolvePath("$projectDir/test/resources/custom_mvn_repo_annot.kts")}", 48 | 0, 49 | "kscript with annotations rocks!\n", 50 | startsWith("[kscript] Adding repository: Repository(id=, url=http://maven.imagej.net/content/repositories/releases, user=, password=)\n") 51 | ) 52 | verify( 53 | "kscript ${resolvePath("$projectDir/test/resources/illegal_depends_on_arg.kts")}", 54 | 1, 55 | "", 56 | "[kscript] [ERROR] Artifact locators must be provided as separate annotation arguments and not as comma-separated list: [com.squareup.moshi:moshi:1.5.0,com.squareup.moshi:moshi-adapters:1.5.0]\n\n" 57 | ) 58 | verify("kscript $projectDir/test/resources/script_with_compile_flags.kts", 0, "hoo_ray\n", any()) 59 | } 60 | 61 | @Test 62 | @Tag("posix") 63 | @Tag("windows") 64 | fun `Ensure dependencies are solved correctly #345`() { 65 | val dependencyDirectory = File(System.getProperty("user.home") + "/.m2/repository/com/beust") 66 | if (dependencyDirectory.exists()) { 67 | FileUtils.cleanDirectory(dependencyDirectory) 68 | } 69 | 70 | verify( 71 | "kscript ${resolvePath("$projectDir/test/resources/depends_on_klaxon.kts")}", 72 | 0, 73 | "Successfully resolved klaxon\n", 74 | startsWith("[kscript] Resolving com.beust:klaxon:5.5...\n") 75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/BootstrapHeaderTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.contains 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import io.github.kscripting.kscript.integration.tools.TestContext.copyToTestPath 7 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 8 | import io.github.kscripting.kscript.integration.tools.TestContext.testDir 9 | import org.junit.jupiter.api.Tag 10 | import org.junit.jupiter.api.Test 11 | 12 | class BootstrapHeaderTest : TestBase { 13 | @Test 14 | @Tag("linux") 15 | @Tag("macos") 16 | //TODO: Doesn't work on msys and cygwin for some reason 17 | fun `Test adding bootstrap header`() { 18 | // ensure script works as is 19 | val testFile = resolvePath("$testDir/echo_stdin_args.kts") 20 | verify("echo stdin | '$testFile' --foo bar", 0, "stdin | script --foo bar\n") 21 | 22 | // add bootstrap header 23 | verify("kscript --add-bootstrap-header '$testFile'", 0, "", contains("echo_stdin_args.kts updated")) 24 | 25 | // ensure adding it again raises an error 26 | verify("kscript --add-bootstrap-header '$testFile'", 1, "", startsWith("[kscript] [ERROR] Bootstrap header already detected:")) 27 | 28 | // ensure scripts works with header, including stdin 29 | verify("echo stdin | '$testFile' --foo bar", 0, "stdin | script --foo bar\n") 30 | 31 | // ensure scripts works with header invoked with explicit `kscript` 32 | verify("echo stdin | kscript '$testFile' --foo bar", 0, "stdin | script --foo bar\n") 33 | } 34 | 35 | companion object { 36 | init { 37 | copyToTestPath("test/resources/echo_stdin_args.kts") 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/CliReplTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 5 | import org.junit.jupiter.api.Tag 6 | import org.junit.jupiter.api.Test 7 | 8 | class CliReplTest : TestBase { 9 | @Test 10 | @Tag("posix") 11 | @Tag("windows") 12 | fun `Do not run interactive mode prep without script argument`() { 13 | verify("kscript -i", 1, "", startsWith("kscript - Enhanced scripting support for Kotlin")) 14 | } 15 | 16 | // fun `CLI REPL tests`() { 17 | // ## interactive mode without dependencies 18 | // #assert "kscript -i 'exitProcess(0)'" "To create a shell with script dependencies run:\nkotlinc -classpath ''" 19 | // #assert "echo '' | kscript -i -" "To create a shell with script dependencies run:\nkotlinc -classpath ''" 20 | // 21 | // 22 | // ## first version is disabled because support-auto-prefixing kicks in 23 | // #assert "kscript -i '//DEPS log4j:log4j:1.2.14'" "To create a shell with script dependencies run:\nkotlinc -classpath '${HOME}/.m2/repository/log4j/log4j/1.2.14/log4j-1.2.14.jar'" 24 | // #assert "kscript -i <(echo '//DEPS log4j:log4j:1.2.14')" "To create a shell with script dependencies run:\nkotlinc -classpath '${HOME}/.m2/repository/log4j/log4j/1.2.14/log4j-1.2.14.jar'" 25 | // } 26 | } 27 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/CustomInterpretersTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestContext.copyToExecutablePath 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 7 | import org.junit.jupiter.api.Tag 8 | import org.junit.jupiter.api.Test 9 | 10 | class CustomInterpretersTest : TestBase { 11 | @Test 12 | @Tag("posix") 13 | fun `Execute mydsl as interpreter`() { 14 | verify("mydsl \"println(foo)\"", 0, "bar\n", any()) 15 | } 16 | 17 | @Test 18 | @Tag("posix") 19 | fun `Execute mydsl test with deps`() { 20 | verify("$projectDir/test/resources/custom_dsl/mydsl_test_with_deps.kts", 0, "foobar\n", any()) 21 | } 22 | 23 | companion object { 24 | init { 25 | copyToExecutablePath("test/resources/custom_dsl/mydsl") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/DeprecatedReportTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.contains 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 7 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 8 | import org.junit.jupiter.api.Tag 9 | import org.junit.jupiter.api.Test 10 | 11 | class DeprecatedReportTest : TestBase { 12 | @Test 13 | @Tag("posix") 14 | @Tag("windows") 15 | fun `Make sure that for deprecated features warn is generated`() { 16 | verify( 17 | "kscript ${resolvePath("$projectDir/test/resources/deprecated_report.kt")}", 18 | 0, 19 | "made it!\n", 20 | startsWith("[kscript] [WARN] There are deprecated features in scripts. Use --report option to print full report.") 21 | ) 22 | } 23 | 24 | @Test 25 | @Tag("posix") 26 | @Tag("windows") 27 | fun `Assert that report with deprecated features is generated`() { 28 | verify( 29 | "kscript --report ${resolvePath("$projectDir/test/resources/deprecated_report.kt")}", 30 | 0, 31 | "", 32 | contains("@file:DependsOn(\"org.apache.commons:commons-lang3:3.12.0\")") 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/EnvironmentTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 4 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 5 | import org.junit.jupiter.api.Tag 6 | import org.junit.jupiter.api.Test 7 | 8 | class EnvironmentTest : TestBase { 9 | @Test 10 | @Tag("posix") 11 | @Tag("windows") 12 | fun `Make sure that KOTLIN_HOME can be guessed from kotlinc correctly`() { 13 | verify("kscript \"println(99)\"", 0, "99\n") { env -> env.remove("KOTLIN_HOME") } 14 | } 15 | 16 | //TODO: test what happens if kotlin/kotlinc/java/gradle/idea is not in PATH 17 | 18 | @Test 19 | @Tag("posix") 20 | fun `Run script that tries to find out its own filename via environment variable`() { 21 | val path = "$projectDir/test/resources/uses_self_file_name.kts" 22 | verify(path, 0, "Usage: uses_self_file_name.kts [-ae] [--foo] file+\n") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/IdeaTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 5 | import io.github.kscripting.kscript.integration.tools.TestContext.copyToExecutablePath 6 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 7 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 8 | import org.junit.jupiter.api.Tag 9 | import org.junit.jupiter.api.Test 10 | 11 | class IdeaTest : TestBase { 12 | @Test 13 | @Tag("linux") 14 | @Tag("macos") 15 | //TODO: On MSys and Cygwin test doesn't work, and is accomplished with timeout 16 | fun `Temp projects with include symlinks`() { 17 | val result = verify("kscript --idea ${resolvePath("$projectDir/test/resources/includes/include_variations.kts")}", 0, any(), any()) 18 | val ideaDir = result.stderr.trim().lines().last().removePrefix("[kscript] ") 19 | verify("cd $ideaDir && gradle build", 0, any(), any()) 20 | } 21 | 22 | @Test 23 | @Tag("linux") 24 | @Tag("macos") 25 | //TODO: On MSys and Cygwin test doesn't work, and is accomplished with timeout 26 | fun `Support diamond-shaped include schemes (see #133)`() { 27 | val result = verify("kscript --idea ${resolvePath("$projectDir/test/resources/includes/diamond.kts")}", 0, any(), any()) 28 | val ideaDir = result.stderr.trim().lines().last().removePrefix("[kscript] ") 29 | verify("cd $ideaDir && gradle build", 0, any(), any()) 30 | } 31 | 32 | companion object { 33 | init { 34 | copyToExecutablePath("test/resources/idea") 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/KtSupportTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 5 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 6 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 7 | import org.junit.jupiter.api.Tag 8 | import org.junit.jupiter.api.Test 9 | 10 | class KtSupportTest : TestBase { 11 | @Test 12 | @Tag("posix") 13 | fun `Run kt via interpreter mode`() { 14 | verify(resolvePath("$projectDir/test/resources/kt_tests/simple_app.kt").stringPath(), 0, "main was called\n", any()) 15 | } 16 | 17 | @Test 18 | @Tag("posix") 19 | @Tag("windows") 20 | fun `Run kt via interpreter mode with dependencies`() { 21 | verify("kscript ${resolvePath("$projectDir/test/resources/kt_tests/main_with_deps.kt")}", 0, "made it!\n", "[kscript] Resolving log4j:log4j:1.2.14...\n") 22 | } 23 | 24 | @Test 25 | @Tag("linux") 26 | @Tag("macos") 27 | @Tag("msys") 28 | @Tag("windows") 29 | //TODO: Additional new lines are in stdout for cygwin 30 | fun `Test misc entry point with or without package configurations (no cygwin)`() { 31 | verify("kscript ${resolvePath("$projectDir/test/resources/kt_tests/default_entry_nopckg.kt")}", 0, "main was called\n", any()) 32 | verify("kscript ${resolvePath("$projectDir/test/resources/kt_tests/default_entry_withpckg.kt")}", 0, "main was called\n", any()) 33 | } 34 | 35 | @Test 36 | @Tag("posix") 37 | @Tag("windows") 38 | fun `Test misc entry point with or without package configurations`() { 39 | verify("kscript ${resolvePath("$projectDir/test/resources/kt_tests/custom_entry_nopckg.kt")}", 0, "foo companion was called\n") 40 | verify("kscript ${resolvePath("$projectDir/test/resources/kt_tests/custom_entry_withpckg.kt")}", 0, "foo companion was called\n") 41 | } 42 | 43 | @Test 44 | @Tag("posix") 45 | fun `Also make sure that kts in package can be run via kscript`() { 46 | verify(resolvePath("$projectDir/test/resources/script_in_pckg.kts").stringPath(), 0, "I live in a package!\n", any()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/MiscTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.contains 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 7 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 8 | import io.github.kscripting.kscript.integration.tools.TestContext.testDir 9 | import org.junit.jupiter.api.Tag 10 | import org.junit.jupiter.api.Test 11 | 12 | class MiscTest : TestBase { 13 | @Test 14 | @Tag("posix") 15 | @Tag("windows") 16 | fun `Clearing cache test`() { 17 | verify("kscript --clear-cache", 0, "", "Cleaning up cache...\n") 18 | } 19 | 20 | @Test 21 | @Tag("linux") 22 | @Tag("macos") 23 | @Tag("msys") 24 | @Tag("windows") 25 | //TODO: Additional new lines are in stdout for cygwin 26 | fun `Prevent regressions of #98 (no cygwin)`() { 27 | verify("""kscript "print(args[0])" "foo bar"""", 0, "foo bar") //make sure quotes are not propagated into args 28 | } 29 | 30 | @Test 31 | @Tag("posix") 32 | @Tag("windows") 33 | fun `Prevent regressions of #98 (it fails to process empty or space-containing arguments)`() { 34 | verify("""kscript "print(args.size)" foo bar""", 0, "2") //regular args 35 | verify( 36 | """kscript "print(args.size)" "--params foo"""", 37 | 0, 38 | "1" 39 | ) //make sure dash args are not confused with options 40 | verify("""kscript "print(args.size)" "foo bar"""", 0, "1") //allow for spaces 41 | } 42 | 43 | @Test 44 | @Tag("posix") 45 | fun `Prevent regressions of #98 (only posix)`() { 46 | verify("""kscript "print(args.size)" "" foo bar""", 0, "3") //accept empty args 47 | } 48 | 49 | @Test 50 | @Tag("posix") 51 | fun `Prevent regression of #181`() { 52 | verify("""echo "println(123)" > $testDir/123foo.kts; kscript $testDir/123foo.kts""", 0, "123\n") 53 | } 54 | 55 | @Test 56 | @Tag("linux") 57 | @Tag("macos") 58 | @Tag("msys") 59 | //TODO: @Tag("cygwin") - doesn't work on cygwin 60 | fun `Prevent regression of #185`() { 61 | verify("source $projectDir/test/resources/home_dir_include.sh $testDir", 0, "42\n") 62 | } 63 | 64 | @Test 65 | @Tag("posix") 66 | fun `Prevent regression of #173`() { 67 | verify("source $projectDir/test/resources/compiler_opts_with_includes.sh $testDir", 0, "hello42\n", any()) 68 | } 69 | 70 | @Test 71 | @Tag("posix") 72 | fun `Ensure relative includes with in shebang mode`() { 73 | verify("$projectDir/test/resources/includes/shebang_mode_includes", 0, "include_1\n") 74 | } 75 | 76 | @Test 77 | @Tag("posix") 78 | @Tag("windows") 79 | fun `Ensure that compilation errors are not cached #349`() { 80 | //first run (not yet cached) 81 | verify("kscript $projectDir/test/resources/invalid_script.kts", 1, "", contains("error: expecting ')'")) 82 | //real test 83 | verify("kscript $projectDir/test/resources/invalid_script.kts", 1, "", contains("error: expecting ')'")) 84 | } 85 | 86 | @Test 87 | @Tag("posix") 88 | @Tag("windows") 89 | fun `Test local jar dir referenced in ENV variable`() { 90 | val shellPath = resolvePath("$projectDir/test/resources/config/") 91 | 92 | verify( 93 | "kscript ${shellPath.resolve("script_with_local_jars.kts")}", 94 | 0, 95 | "I am living in Test1 class...\nAnd I come from Test2 class...\n", 96 | "" 97 | ) { env -> 98 | env["KSCRIPT_DIRECTORY_ARTIFACTS"] = shellPath.resolve("jars").stringPath() 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/PackagingTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 7 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 8 | import org.junit.jupiter.api.Tag 9 | import org.junit.jupiter.api.Test 10 | 11 | class PackagingTest : TestBase { 12 | @Test 13 | @Tag("linux") 14 | @Tag("macos") 15 | //TODO: doesn't work on msys, cygwin, windows 16 | fun `Packaged script is cached`() { 17 | //@formatter:off 18 | verify("kscript --package \"println(1+1)\"", 0, "", startsWith("[kscript] Packaging script 'scriplet' into standalone executable...")) 19 | verify("kscript --package \"println(1+1)\"", 0, "", startsWith("[kscript] Packaged script 'scriplet' available at path:")) 20 | //@formatter:on 21 | } 22 | 23 | @Test 24 | @Tag("linux") 25 | @Tag("macos") 26 | //TODO: doesn't work on msys, cygwin, windows 27 | fun `Packaging of simple script`() { 28 | val result = 29 | verify("kscript --package ${resolvePath("$projectDir/test/resources/package_example.kts")}", 0, "", any()) 30 | val command = result.stderr.trim().lines().last().removePrefix("[kscript] ") 31 | verify("$command argument", 0, "package_me_args_1_mem_536870912\n") 32 | } 33 | 34 | @Test 35 | @Tag("linux") 36 | @Tag("macos") 37 | //TODO: doesn't work on msys, cygwin, windows 38 | fun `Packaging provided source code and execution with arguments`() { 39 | val result = verify("""kscript --package "println(args.size)"""", 0, "", any()) 40 | val command = result.stderr.trim().lines().last().removePrefix("[kscript] ") 41 | verify("$command three arg uments", 0, "3\n") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/ResolverTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 5 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 6 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 7 | import org.junit.jupiter.api.Tag 8 | import org.junit.jupiter.api.Test 9 | import java.io.File 10 | 11 | class ResolverTest : TestBase { 12 | @Test 13 | @Tag("posix") 14 | @Tag("windows") 15 | fun `It should run kscript and resolve dependencies`() { 16 | // The first time artifact resolution is started because the cache is cleaned... 17 | verify( 18 | "kscript ${resolvePath("$projectDir/test/resources/depends_on_annot.kts")}", 19 | 0, 20 | "kscript with annotations rocks!\n", 21 | startsWith("[kscript] Resolving log4j:log4j:1.2.14") 22 | ) 23 | 24 | 25 | // clear .m2 cache 26 | val log4jCached = File(System.getProperty("user.home"), ".m2/repository/log4j/log4j/1.2.14/") 27 | 28 | if (log4jCached.isDirectory) { 29 | System.err.println("Cleaning up cached copy of log4j: ${log4jCached.absolutePath}") 30 | log4jCached.deleteRecursively() 31 | } 32 | 33 | // The second time it is because of removing artifact from cache... 34 | verify( 35 | "kscript ${resolvePath("$projectDir/test/resources/depends_on_annot.kts")}", 36 | 0, 37 | "kscript with annotations rocks!\n", 38 | startsWith("[kscript] Resolving log4j:log4j:1.2.14") 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/ScriptInputModesTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 5 | import io.github.kscripting.kscript.integration.tools.TestContext.projectDir 6 | import io.github.kscripting.kscript.integration.tools.TestContext.resolvePath 7 | import io.github.kscripting.kscript.integration.tools.TestContext.testDir 8 | import org.junit.jupiter.api.Tag 9 | import org.junit.jupiter.api.Test 10 | 11 | class ScriptInputModesTest : TestBase { 12 | @Test 13 | @Tag("posix") 14 | fun `Make sure that scripts can be piped into kscript`() { 15 | verify("source $projectDir/test/resources/direct_script_arg.sh", 0, "kotlin rocks\n", "") 16 | } 17 | 18 | @Test 19 | @Tag("posix") 20 | //it doesn't work on Windows 21 | fun `Also allow for empty programs`() { 22 | verify("kscript ''", 0, "", "") 23 | } 24 | 25 | @Test 26 | @Tag("posix") 27 | @Tag("windows") 28 | fun `Provide script as direct argument`() { 29 | verify("""kscript "println(1+1)"""", 0, "2\n", "") 30 | } 31 | 32 | @Test 33 | @Tag("linux") 34 | @Tag("macos") 35 | @Tag("windows") 36 | //TODO: Doesn't work on msys, cygwin as during test execution " is replaced with '. It causes syntax error in Kotlin. 37 | fun `Use dashed arguments`() { 38 | verify("""kscript "println(args.joinToString(\"\"))" --arg u ments""", 0, "--arguments\n", "") 39 | verify("""kscript -s "print(args.joinToString(\"\"))" --arg u ments""", 0, "--arguments", "") 40 | } 41 | 42 | @Test 43 | @Tag("posix") 44 | fun `Provide script via stidin`() { 45 | verify("echo 'println(1+1)' | kscript -", 0, "2\n") 46 | //stdin and further switch (to avoid regressions of #94) 47 | verify("echo 'println(1+3)' | kscript - --foo", 0, "4\n") 48 | } 49 | 50 | @Test 51 | @Tag("windows") 52 | fun `Provide script via stidin (windows version without quotes)`() { 53 | verify("echo println(1+1) | kscript -", 0, "2\n") 54 | //stdin and further switch (to avoid regressions of #94) 55 | verify("echo println(1+3) | kscript - --foo", 0, "4\n") 56 | } 57 | 58 | @Test 59 | @Tag("posix") 60 | fun `Make sure that heredoc is accepted as argument`() { 61 | verify("source ${projectDir}/test/resources/here_doc_test.sh", 0, "hello kotlin\n") 62 | } 63 | 64 | @Test 65 | @Tag("linux") 66 | @Tag("macos") 67 | //Command substitution doesn't work on msys and cygwin 68 | fun `Make sure that command substitution works as expected`() { 69 | verify("source ${projectDir}/test/resources/cmd_subst_test.sh", 0, "command substitution works as well\n") 70 | } 71 | 72 | @Test 73 | @Tag("posix") 74 | fun `Make sure that it runs with local bash script files`() { 75 | verify("source ${projectDir}/test/resources/local_script_file.sh $testDir", 0, "kscript rocks!\n") 76 | } 77 | 78 | @Test 79 | @Tag("posix") 80 | @Tag("windows") 81 | fun `Make sure that it runs with local script files`() { 82 | verify( 83 | "kscript ${resolvePath("${projectDir}/test/resources/multi_line_deps.kts")}", 84 | 0, 85 | "kscript is cool!\n", 86 | "[kscript] Resolving com.offbytwo:docopt:0.6.0.20150202...\n[kscript] Resolving log4j:log4j:1.2.14...\n" 87 | ) 88 | } 89 | 90 | @Test 91 | @Tag("posix") 92 | @Tag("windows") 93 | fun `Scripts with dashes in the file name should work as well`() { 94 | verify("kscript ${resolvePath("$projectDir/test/resources/dash-test.kts")}", 0, "dash alarm!\n") 95 | } 96 | 97 | @Test 98 | @Tag("posix") 99 | @Tag("windows") 100 | fun `Scripts with additional dots in the file name should work as well`() { 101 | //We also test inner uppercase letters in file name here by using .*T*est 102 | verify("kscript ${resolvePath("$projectDir/test/resources/dot.Test.kts")}", 0, "dot alarm!\n") 103 | } 104 | 105 | @Test 106 | @Tag("posix") 107 | @Tag("windows") 108 | fun `Make sure that it runs with remote URLs`() { 109 | verify( 110 | "kscript https://raw.githubusercontent.com/kscripting/kscript/master/test/resources/url_test.kts", 111 | 0, "I came from the internet\n", any() 112 | ) 113 | verify("kscript https://git.io/fxHBv", 0, "main was called\n", any()) 114 | } 115 | 116 | @Test 117 | @Tag("posix") 118 | @Tag("windows") 119 | fun `Missing script gives always error on execution`() { 120 | verify( 121 | "kscript i_do_not_exist.kts", 1, "", "[kscript] [ERROR] Could not read script from 'i_do_not_exist.kts'\n\n" 122 | ) 123 | verify( 124 | "kscript i_do_not_exist.kts", 1, "", "[kscript] [ERROR] Could not read script from 'i_do_not_exist.kts'\n\n" 125 | ) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/SimpleTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.contains 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import org.junit.jupiter.api.Tag 7 | import org.junit.jupiter.api.Test 8 | 9 | class SimpleTest : TestBase { 10 | @Test 11 | @Tag("posix") 12 | @Tag("windows") 13 | fun `Providing source code works`() { 14 | verify("kscript \"println(1+1)\"", 0, "2\n") 15 | } 16 | 17 | @Test 18 | @Tag("posix") 19 | @Tag("windows") 20 | fun `Debugging information is printed`() { 21 | verify("kscript -d \"println(1+1)\"", 0, "2\n", contains("Debugging information for KScript")) 22 | } 23 | 24 | @Test 25 | @Tag("posix") 26 | @Tag("windows") 27 | fun `Help is printed`() { 28 | verify("kscript --help", 0, "", startsWith("kscript - Enhanced scripting support for Kotlin")) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/SupportApiTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestAssertion.any 4 | import io.github.kscripting.kscript.integration.tools.TestAssertion.startsWith 5 | import io.github.kscripting.kscript.integration.tools.TestAssertion.verify 6 | import io.github.kscripting.kscript.integration.tools.TestContext.nl 7 | import org.junit.jupiter.api.Tag 8 | import org.junit.jupiter.api.Test 9 | 10 | class SupportApiTest : TestBase { 11 | @Test 12 | @Tag("posix") 13 | fun `Make sure that one-liners include support-api`() { 14 | verify("""echo "foo${nl}bar" | kscript -t "stdin.print()"""", 0, "foo\nbar\n", any()) 15 | verify("""echo "foo${nl}bar" | kscript -t "lines.print()"""", 0, "foo\nbar\n", any()) 16 | verify("""echo 'foo${nl}bar' | kscript -t 'lines.print()'""", 0, "foo\nbar\n", any()) 17 | verify( 18 | """echo 'foo${nl}bar' | kscript -s --text 'lines.split().select(1,2,-3)'""", 1, "", 19 | startsWith("[ERROR] Can not mix positive and negative selections\n") 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/TestBase.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestContext 4 | import org.junit.jupiter.api.BeforeAll 5 | 6 | interface TestBase { 7 | companion object { 8 | @BeforeAll 9 | @JvmStatic 10 | fun setUp() { 11 | TestContext.clearCache() 12 | TestContext.printPaths() 13 | println("[nl] - new line; [bs] - backspace") 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/tools/TestAssertion.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration.tools 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestContext.runProcess 4 | import io.github.kscripting.shell.model.GobbledProcessResult 5 | import io.github.kscripting.shell.process.EnvAdjuster 6 | 7 | object TestAssertion { 8 | fun geq(value: T) = GenericEquals(value) 9 | 10 | fun any() = AnyMatch() 11 | fun eq(string: String, ignoreCase: Boolean = false) = Equals(string, ignoreCase) 12 | fun startsWith(string: String, ignoreCase: Boolean = false) = StartsWith(string, ignoreCase) 13 | fun contains(string: String, ignoreCase: Boolean = false) = Contains(string, ignoreCase) 14 | 15 | fun verify( 16 | command: String, 17 | exitCode: Int = 0, 18 | stdOut: TestMatcher, 19 | stdErr: String = "", 20 | envAdjuster: EnvAdjuster = {} 21 | ): GobbledProcessResult = verify(command, exitCode, stdOut, eq(stdErr), envAdjuster) 22 | 23 | fun verify( 24 | command: String, 25 | exitCode: Int = 0, 26 | stdOut: String, 27 | stdErr: TestMatcher, 28 | envAdjuster: EnvAdjuster = {} 29 | ): GobbledProcessResult = verify(command, exitCode, eq(stdOut), stdErr, envAdjuster) 30 | 31 | fun verify( 32 | command: String, 33 | exitCode: Int = 0, 34 | stdOut: String = "", 35 | stdErr: String = "", 36 | envAdjuster: EnvAdjuster = {} 37 | ): GobbledProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), envAdjuster) 38 | 39 | fun verify( 40 | command: String, 41 | exitCode: Int = 0, 42 | stdOut: TestMatcher, 43 | stdErr: TestMatcher, 44 | envAdjuster: EnvAdjuster = {} 45 | ): GobbledProcessResult { 46 | val processResult = runProcess(command, envAdjuster) 47 | val extCde = geq(exitCode) 48 | 49 | extCde.checkAssertion("ExitCode", processResult.exitCode) 50 | stdOut.checkAssertion("StdOut", processResult.stdout) 51 | stdErr.checkAssertion("StdErr", processResult.stderr) 52 | println() 53 | 54 | return processResult 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/tools/TestContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration.tools 2 | 3 | import io.github.kscripting.kscript.util.ShellUtils 4 | import io.github.kscripting.kscript.util.ShellUtils.which 5 | import io.github.kscripting.shell.ShellExecutor 6 | import io.github.kscripting.shell.model.* 7 | import io.github.kscripting.shell.process.EnvAdjuster 8 | 9 | object TestContext { 10 | private val osType: OsType = OsType.findOrThrow(System.getProperty("osType")) 11 | private val nativeType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType 12 | 13 | private val projectPath: OsPath = OsPath.createOrThrow(nativeType, System.getProperty("projectPath")) 14 | private val execPath: OsPath = projectPath.resolve("build/kscript/bin") 15 | private val testPath: OsPath = projectPath.resolve("build/tmp/test") 16 | private val pathEnvName = if (osType.isWindowsLike()) "Path" else "PATH" 17 | private val systemPath: String = System.getenv()[pathEnvName]!! 18 | 19 | private val pathSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" 20 | private val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" 21 | 22 | val nl: String = System.getProperty("line.separator") 23 | val projectDir: String = projectPath.convert(osType).stringPath() 24 | val testDir: String = testPath.convert(osType).stringPath() 25 | 26 | init { 27 | println("osType : $osType") 28 | println("nativeType : $nativeType") 29 | println("projectDir : $projectDir") 30 | println("testDir : $testDir") 31 | println("execDir : ${execPath.convert(osType)}") 32 | println("Kotlin version : ${ShellExecutor.evalAndGobble(osType, "kotlin -version", null, ::adjustEnv)}") 33 | 34 | testPath.createDirectories() 35 | } 36 | 37 | fun resolvePath(path: String): OsPath { 38 | return OsPath.createOrThrow(osType, path) 39 | } 40 | 41 | fun runProcess(command: String, envAdjuster: EnvAdjuster): GobbledProcessResult { 42 | //In MSYS all quotes should be single quotes, otherwise content is interpreted e.g. backslashes. 43 | //(MSYS bash interpreter is also replacing double quotes into the single quotes: see: bash -xc 'kscript "println(1+1)"') 44 | val newCommand = when { 45 | osType.isPosixHostedOnWindows() -> command.replace('"', '\'') 46 | else -> command 47 | } 48 | 49 | fun internalEnvAdjuster(map: MutableMap) { 50 | ShellUtils.environmentAdjuster(map) 51 | map[pathEnvName] = envPath 52 | envAdjuster(map) 53 | } 54 | 55 | val result = ShellExecutor.evalAndGobble(osType, newCommand, null, ::internalEnvAdjuster) 56 | println(result) 57 | 58 | return result 59 | } 60 | 61 | fun copyToExecutablePath(source: String) { 62 | val sourceFile = projectPath.resolve(source).toNativeFile() 63 | val targetFile = execPath.resolve(sourceFile.name).toNativeFile() 64 | 65 | sourceFile.copyTo(targetFile, overwrite = true) 66 | targetFile.setExecutable(true) 67 | } 68 | 69 | fun copyToTestPath(source: String) { 70 | val sourceFile = projectPath.resolve(source).toNativeFile() 71 | val targetFile = testPath.resolve(sourceFile.name).toNativeFile() 72 | 73 | sourceFile.copyTo(targetFile, overwrite = true) 74 | targetFile.setExecutable(true) //Needed if the file is kotlin script 75 | } 76 | 77 | fun printPaths() { 78 | val kscriptPath = which(osType, "kscript", ::adjustEnv) 79 | println("kscript path: $kscriptPath") 80 | val kotlincPath = which(osType, "kotlinc", ::adjustEnv) 81 | println("kotlinc path: $kotlincPath") 82 | } 83 | 84 | fun clearCache() { 85 | print("Clearing kscript cache... ") 86 | ShellExecutor.eval(osType, "kscript --clear-cache", null, ::adjustEnv) 87 | println("done.") 88 | } 89 | 90 | private fun adjustEnv(map: MutableMap) { 91 | map[pathEnvName] = envPath 92 | ShellUtils.environmentAdjuster(map) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/integration/kotlin/io/github/kscripting/kscript/integration/tools/TestMatcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.integration.tools 2 | 3 | import io.github.kscripting.kscript.integration.tools.TestContext.nl 4 | import org.opentest4j.AssertionFailedError 5 | 6 | abstract class TestMatcher(protected val expectedValue: T, private val expressionName: String) { 7 | abstract fun matches(value: T): Boolean 8 | 9 | fun checkAssertion(assertionName: String, value: T) { 10 | if (matches(value)) { 11 | return 12 | } 13 | 14 | throw AssertionFailedError( 15 | "$nl$nl$assertionName: expected that value '${ 16 | whitespaceCharsToSymbols(value.toString()) 17 | }' $expressionName '${ 18 | whitespaceCharsToSymbols(expectedValue.toString()) 19 | }'$nl$nl" 20 | ) 21 | } 22 | } 23 | 24 | class GenericEquals(expectedValue: T) : TestMatcher(expectedValue, "is equal to") { 25 | override fun matches(value: T): Boolean = (value == expectedValue) 26 | } 27 | 28 | class AnyMatch : TestMatcher("", "has any value") { 29 | override fun matches(value: String): Boolean = true 30 | } 31 | 32 | class Equals(private val expectedString: String, private val ignoreCase: Boolean) : 33 | TestMatcher(expectedString, "is equal to") { 34 | override fun matches(value: String): Boolean = value.equals(normalize(expectedString), ignoreCase) 35 | } 36 | 37 | class StartsWith(private val expectedString: String, private val ignoreCase: Boolean) : 38 | TestMatcher(expectedString, "starts with") { 39 | override fun matches(value: String): Boolean = value.startsWith(normalize(expectedString), ignoreCase) 40 | } 41 | 42 | class Contains(private val expectedString: String, private val ignoreCase: Boolean) : 43 | TestMatcher(expectedString, "contains") { 44 | override fun matches(value: String): Boolean = value.contains(normalize(expectedString), ignoreCase) 45 | } 46 | 47 | private fun normalize(string: String) = string.replace("\n", nl) 48 | 49 | private fun whitespaceCharsToSymbols(string: String): String = string.replace("\\", "[bs]").lines().joinToString("[nl]") 50 | -------------------------------------------------------------------------------- /src/kscript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Based on kotlinc.bat from the Kotlin distribution 4 | 5 | resolveAbsolutePath() { 6 | # if the given path to the kscript launcher is absolute (i.e. it is either starting with / or a 7 | # 'letter:/' when using gitbash on windows) it is returned unchanged, otherwise we construct an absolute path 8 | [[ $1 = /* ]] || [[ $1 =~ ^[A-z]:/ ]] && echo "$1" || echo "$PWD/${1#./}" 9 | } 10 | 11 | resolveSymlinks() ( 12 | if [[ $OSTYPE != darwin* ]]; then minusFarg="-f"; fi 13 | sym_resolved=$(readlink ${minusFarg} $1) 14 | 15 | if [[ -n $sym_resolved ]]; then 16 | echo $sym_resolved 17 | else 18 | echo $1 19 | fi 20 | ) 21 | 22 | findJavaVersion() { 23 | # Note that this only loads the first component of the version, so "1.8.0_265" -> "1". 24 | # But it's fine because major version is 9 for JDK 9, and so on. 25 | regex='^.*version "([[:digit:]]+).*$' 26 | while read -r line; do 27 | if [[ "$line" =~ $regex ]]; then 28 | echo ${BASH_REMATCH[1]} 29 | fi 30 | done <<< $("${JAVACMD:=java}" -version 2>&1) 31 | } 32 | 33 | # Resolve KSCRIPT_HOME 34 | KSCRIPT_HOME=$(dirname $(dirname $(resolveSymlinks $(resolveAbsolutePath $0)))) 35 | KSCRIPT_LIB="$KSCRIPT_HOME/bin" 36 | 37 | # Resolve JAVA_CMD 38 | if [ -z "$JAVACMD" -a -n "$JAVA_HOME" -a -x "$JAVA_HOME/bin/java" ]; then 39 | JAVACMD="$JAVA_HOME/bin/java" 40 | fi 41 | 42 | # OSTYPE can be: linux*, freebsd, darwin*, cygwin, msys 43 | if [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then 44 | KSCRIPT_LIB=$(cygpath -w "${KSCRIPT_LIB}") 45 | fi 46 | 47 | ## expose the name of the script being run to the script itself 48 | export KSCRIPT_FILE="$1" 49 | 50 | [ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx256M -Xms32M" 51 | 52 | declare -a JAVA_ARGS 53 | declare -a KOTLIN_ARGS 54 | 55 | while [ $# -gt 0 ]; do 56 | case "$1" in 57 | -D*) 58 | JAVA_ARGS=("${JAVA_ARGS[@]}" "$1") 59 | shift 60 | ;; 61 | -J*) 62 | JAVA_ARGS=("${JAVA_ARGS[@]}" "${1:2}") 63 | if [ "x${1:2}" = "x" ]; then 64 | echo "error: empty -J argument" 65 | exit 1 66 | fi 67 | shift 68 | ;; 69 | *) 70 | KOTLIN_ARGS=("${KOTLIN_ARGS[@]}" "$1") 71 | shift 72 | ;; 73 | esac 74 | done 75 | 76 | # Additional Java args 77 | JAVA_VERSION="$(findJavaVersion)" 78 | if [[ $JAVA_VERSION -ge 9 ]]; then 79 | # Workaround the illegal reflective access warning from ReflectionUtil to ResourceBundle.setParent, see IDEA-248785. 80 | JAVA_ARGS=("${JAVA_ARGS[@]}" "--add-opens" "java.base/java.util=ALL-UNNAMED") 81 | fi 82 | 83 | if [[ $JAVA_VERSION < 13 ]]; then 84 | JAVA_ARGS=("${JAVA_ARGS[@]}" "-noverify") 85 | fi 86 | 87 | # KScript dependencies itself 88 | CLASS_PATH="$KSCRIPT_LIB/*" 89 | 90 | ## execute process 91 | "${JAVACMD:=java}" $JAVA_OPTS "${JAVA_ARGS[@]}" -classpath "$CLASS_PATH" io.github.kscripting.kscript.KscriptKt "$OSTYPE" "${KOTLIN_ARGS[@]}" 92 | -------------------------------------------------------------------------------- /src/kscript.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Based on kotlinc.bat from the Kotlin distribution 4 | 5 | setlocal 6 | call :set_home 7 | 8 | if "%_KOTLIN_COMPILER%"=="" set _KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler 9 | 10 | if not "%JAVA_HOME%"=="" ( 11 | rem Prepend JAVA_HOME to local PATH to be able to simply execute "java" later in the script. 12 | set "PATH=%JAVA_HOME%\bin;%PATH%" 13 | ) 14 | 15 | rem We use the value of the JAVA_OPTS environment variable if defined 16 | if "%JAVA_OPTS%"=="" set JAVA_OPTS=-Xmx256M -Xms32M 17 | 18 | rem Iterate through arguments and split them into java and kotlin ones 19 | :loop 20 | set _arg=%~1 21 | if "%_arg%" == "" goto loopend 22 | 23 | if "%_arg:~0,2%"=="-J" ( 24 | if "%_arg:~2%"=="" ( 25 | echo error: empty -J argument 26 | goto error 27 | ) 28 | set JAVA_OPTS=%JAVA_OPTS% "%_arg:~2%" 29 | ) else ( 30 | if "%_arg:~0,2%"=="-D" ( 31 | set JAVA_OPTS=%JAVA_OPTS% "%_arg%" 32 | ) else ( 33 | set KOTLIN_OPTS=%KOTLIN_OPTS% "%_arg%" 34 | ) 35 | ) 36 | shift 37 | goto loop 38 | :loopend 39 | 40 | setlocal EnableDelayedExpansion 41 | 42 | call :set_java_version 43 | if !_java_major_version! geq 9 ( 44 | rem Workaround the illegal reflective access warning from ReflectionUtil to ResourceBundle.setParent, see IDEA-248785. 45 | set JAVA_OPTS=!JAVA_OPTS! "--add-opens" "java.base/java.util=ALL-UNNAMED" 46 | ) 47 | 48 | set CLASS_PATH=%_BIN_DIR%\* 49 | 50 | java !JAVA_OPTS! -classpath %CLASS_PATH% io.github.kscripting.kscript.KscriptKt windows %KOTLIN_OPTS% 51 | 52 | goto end 53 | 54 | rem ########################################################################## 55 | rem # subroutines 56 | 57 | :set_home 58 | set _BIN_DIR=%~dps0 59 | goto :eof 60 | 61 | rem Parses "java -version" output and stores the major version to _java_major_version. 62 | rem Note that this only loads the first component of the version, so "1.8.0_265" -> "1". 63 | rem But it's fine because major version is 9 for JDK 9, and so on. 64 | rem Needs to be executed in the EnableDelayedExpansion mode. 65 | :set_java_version 66 | set _version= 67 | rem Parse output and take the third token from the string containing " version ". 68 | rem It should be something like "1.8.0_275" or "15.0.1". 69 | for /f "tokens=3" %%i in ('java -version 2^>^&1 ^| findstr /i " version "') do ( 70 | rem Split the string by "-" or "." and take the first token. 71 | for /f "delims=-. tokens=1" %%j in ("%%i") do ( 72 | rem At this point, _version should be something like "1 or "15. Note the leading quote. 73 | set _version=%%j 74 | ) 75 | ) 76 | if "!_version!"=="" ( 77 | rem If failed to parse the output, set the version to 1. 78 | set _java_major_version=1 79 | ) else ( 80 | rem Strip the leading quote. 81 | set _java_major_version=!_version:~1! 82 | ) 83 | goto :eof 84 | 85 | :error 86 | set ERRORLEVEL=1 87 | 88 | :end 89 | exit /b %ERRORLEVEL% 90 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/kscripting/kscript/Kscript.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript 2 | 3 | import io.github.kscripting.kscript.code.Templates 4 | import io.github.kscripting.kscript.creator.DebugInfoCreator 5 | import io.github.kscripting.kscript.model.ConfigBuilder 6 | import io.github.kscripting.kscript.resolver.CommandResolver 7 | import io.github.kscripting.kscript.util.Executor 8 | import io.github.kscripting.kscript.util.Logger 9 | import io.github.kscripting.kscript.util.Logger.errorMsg 10 | import io.github.kscripting.kscript.util.Logger.info 11 | import io.github.kscripting.kscript.util.OptionsUtils 12 | import io.github.kscripting.kscript.util.VersionChecker 13 | import io.github.kscripting.shell.model.OsType 14 | import org.apache.commons.cli.CommandLineParser 15 | import org.apache.commons.cli.DefaultParser 16 | import org.apache.commons.cli.ParseException 17 | import kotlin.system.exitProcess 18 | 19 | 20 | /** 21 | * A kscript - Scripting enhancements for Kotlin 22 | * 23 | * For details and license see https://github.com/kscripting/kscript 24 | * 25 | * @author Holger Brandl 26 | * @author Marcin Kuszczak 27 | */ 28 | 29 | fun main(args: Array) { 30 | try { 31 | val config = ConfigBuilder( 32 | OsType.findOrThrow(args[0]), System.getProperties(), System.getenv() 33 | ).build() 34 | 35 | // note: first argument has to be OSTYPE 36 | val remainingArgs = args.drop(1) 37 | 38 | // note: with current implementation we still don't support `kscript -1` where "-1" is a valid kotlin expression 39 | val userArgs = remainingArgs.dropWhile { it.startsWith("-") && it != "-" }.drop(1) 40 | val kscriptArgs = remainingArgs.take(remainingArgs.size - userArgs.size) 41 | 42 | val parser: CommandLineParser = DefaultParser() 43 | val options = OptionsUtils.createOptions() 44 | 45 | val parsedLine = try { 46 | parser.parse(options, kscriptArgs.toTypedArray()) 47 | } catch (e: ParseException) { 48 | info(OptionsUtils.createHelpText(config.osConfig.selfName, options)) 49 | throw IllegalArgumentException(e.message) 50 | } 51 | 52 | val parsedOptions = parsedLine.options.associate { it.longOpt to it.value }.toMutableMap() 53 | 54 | if (parsedLine.argList.isNotEmpty()) { 55 | parsedOptions["script"] = parsedLine.argList[0] 56 | } 57 | 58 | // Constraints 59 | if (parsedOptions.isEmpty() || (parsedOptions.containsKey("interactive") && !parsedOptions.containsKey("script"))) { 60 | info(OptionsUtils.createHelpText(config.osConfig.selfName, options)) 61 | throw IllegalArgumentException("KScript requires 'script' as an argument") 62 | } 63 | 64 | Logger.silentMode = parsedOptions.containsKey("silent") 65 | Logger.devMode = parsedOptions.containsKey("development") 66 | 67 | if (Logger.devMode) { 68 | info(DebugInfoCreator().create(config, kscriptArgs, userArgs)) 69 | } 70 | 71 | val executor = Executor(CommandResolver(config.osConfig)) 72 | 73 | if (parsedOptions.containsKey("help") || parsedOptions.containsKey("version")) { 74 | val versionChecker = VersionChecker(executor) 75 | 76 | val newVersion = if (versionChecker.isThereANewKscriptVersion()) versionChecker.remoteKscriptVersion else "" 77 | 78 | if (parsedOptions.containsKey("help")) { 79 | info(OptionsUtils.createHelpText(config.osConfig.selfName, options).trimEnd()) 80 | } 81 | 82 | info( 83 | Templates.createVersionInfo( 84 | BuildConfig.APP_BUILD_TIME, 85 | BuildConfig.APP_VERSION, 86 | newVersion, 87 | versionChecker.localKotlinVersion, 88 | versionChecker.localJreVersion 89 | ) 90 | ) 91 | info() 92 | 93 | return 94 | } 95 | 96 | KscriptHandler(executor, config, parsedOptions).handle(userArgs) 97 | } catch (e: Exception) { 98 | errorMsg(e) 99 | info() 100 | exitProcess(1) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/kscripting/kscript/KscriptHandler.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript 2 | 3 | import io.github.kscripting.kscript.cache.Cache 4 | import io.github.kscripting.kscript.code.Templates 5 | import io.github.kscripting.kscript.creator.* 6 | import io.github.kscripting.kscript.model.Config 7 | import io.github.kscripting.kscript.parser.Parser 8 | import io.github.kscripting.kscript.resolver.DependencyResolver 9 | import io.github.kscripting.kscript.resolver.InputOutputResolver 10 | import io.github.kscripting.kscript.resolver.ScriptResolver 11 | import io.github.kscripting.kscript.resolver.SectionResolver 12 | import io.github.kscripting.kscript.util.Executor 13 | import io.github.kscripting.kscript.util.FileUtils.getArtifactsRecursively 14 | import io.github.kscripting.kscript.util.Logger.info 15 | import io.github.kscripting.kscript.util.Logger.infoMsg 16 | import io.github.kscripting.kscript.util.Logger.warnMsg 17 | import io.github.kscripting.shell.model.ScriptType 18 | import java.net.URI 19 | 20 | class KscriptHandler( 21 | private val executor: Executor, private val config: Config, private val options: Map 22 | ) { 23 | fun handle(userArgs: List) { 24 | val cache = Cache(config.osConfig.cacheDir) 25 | 26 | // optionally clear up the jar cache 27 | if (options.containsKey("clear-cache")) { 28 | info("Cleaning up cache...") 29 | cache.clear() 30 | 31 | if (!options.containsKey("script")) { 32 | return 33 | } 34 | } 35 | 36 | val scriptSource = options["script"] ?: throw IllegalArgumentException("Script argument is required") 37 | 38 | if (scriptSource.isBlank()) { 39 | return 40 | } 41 | 42 | val enableSupportApi = options.containsKey("text") 43 | 44 | val preambles = buildList { 45 | if (enableSupportApi) { 46 | add(Templates.textProcessingPreamble) 47 | } 48 | 49 | add(config.scriptingConfig.customPreamble) 50 | } 51 | 52 | val inputOutputResolver = InputOutputResolver(config.osConfig, cache) 53 | val sectionResolver = SectionResolver(inputOutputResolver, Parser(), config.scriptingConfig) 54 | val scriptResolver = ScriptResolver(inputOutputResolver, sectionResolver, config.scriptingConfig) 55 | 56 | if (options.containsKey("add-bootstrap-header")) { 57 | val script = scriptResolver.resolve(scriptSource, maxResolutionLevel = 0) 58 | BootstrapCreator().create(script) 59 | return 60 | } 61 | 62 | val script = scriptResolver.resolve(scriptSource, preambles) 63 | 64 | if (script.deprecatedItems.isNotEmpty()) { 65 | if (options.containsKey("report")) { 66 | info(DeprecatedInfoCreator().create(script.deprecatedItems)) 67 | return 68 | } 69 | 70 | warnMsg("There are deprecated features in scripts. Use --report option to print full report.") 71 | } 72 | 73 | val resolvedDependencies = cache.getOrCreateDependencies(script.digest) { 74 | val localArtifacts = if (config.scriptingConfig.artifactsDir != null) { 75 | getArtifactsRecursively(config.scriptingConfig.artifactsDir, DependencyResolver.supportedExtensions) 76 | } else emptyList() 77 | 78 | DependencyResolver(script.repositories).resolve(script.dependencies) + localArtifacts 79 | } 80 | 81 | // Create temporary dev environment 82 | if (options.containsKey("idea")) { 83 | val path = cache.getOrCreateIdeaProject(script.digest) { basePath -> 84 | val uriLocalPathProvider = { uri: URI -> inputOutputResolver.resolveContent(uri).localPath } 85 | IdeaProjectCreator(executor).create(basePath, script, userArgs, uriLocalPathProvider) 86 | } 87 | 88 | infoMsg("Idea project available at:") 89 | infoMsg(path.convert(config.osConfig.osType).stringPath()) 90 | return 91 | } 92 | 93 | // Even if we just need and support the @file:EntryPoint directive in case of kt-class 94 | // files, we extract it here to fail if it was used in kts files. 95 | if (script.entryPoint != null && script.scriptLocation.scriptType == ScriptType.KTS) { 96 | throw IllegalStateException("@file:EntryPoint directive is just supported for kt class files") 97 | } 98 | 99 | val jar = cache.getOrCreateJar(script.digest) { basePath -> 100 | JarArtifactCreator(executor).create(basePath, script, resolvedDependencies) 101 | } 102 | 103 | // Optionally enter interactive mode 104 | if (options.containsKey("interactive")) { 105 | executor.runInteractiveRepl(jar, resolvedDependencies, script.compilerOpts, script.kotlinOpts) 106 | return 107 | } 108 | 109 | //if requested try to package the into a standalone binary 110 | if (options.containsKey("package")) { 111 | val path = 112 | cache.getOrCreatePackage(script.digest, script.scriptLocation.scriptName) { basePath, packagePath -> 113 | PackageCreator(executor).packageKscript(basePath, packagePath, script, jar) 114 | } 115 | 116 | infoMsg("Packaged script '${script.scriptLocation.scriptName}' available at path:") 117 | infoMsg(path.convert(config.osConfig.osType).stringPath()) 118 | return 119 | } 120 | 121 | executor.executeKotlin(jar, resolvedDependencies, userArgs, script.kotlinOpts) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/kscripting/kscript/cache/Cache.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.cache 2 | 3 | import io.github.kscripting.kscript.creator.JarArtifact 4 | import io.github.kscripting.kscript.model.Content 5 | import io.github.kscripting.shell.model.* 6 | import org.apache.commons.codec.digest.DigestUtils 7 | import org.apache.commons.io.FileUtils 8 | import java.net.URI 9 | 10 | class Cache(private val cacheBasePath: OsPath) { 11 | init { 12 | cacheBasePath.createDirectories() 13 | } 14 | 15 | fun getOrCreateIdeaProject(digest: String, creator: (OsPath) -> OsPath): OsPath { 16 | val path = cacheBasePath.resolve("idea_$digest") 17 | 18 | return if (path.exists()) { 19 | path 20 | } else { 21 | path.createDirectories() 22 | creator(path) 23 | } 24 | } 25 | 26 | fun getOrCreatePackage(digest: String, scriptName: String, creator: (OsPath, OsPath) -> OsPath): OsPath { 27 | val path = cacheBasePath.resolve("package_$digest") 28 | val cachedPackageFile = path.resolve("build/libs/$scriptName") 29 | 30 | return if (cachedPackageFile.exists()) { 31 | cachedPackageFile 32 | } else { 33 | path.createDirectories() 34 | creator(path, cachedPackageFile) 35 | } 36 | } 37 | 38 | fun getOrCreateJar(digest: String, creator: (OsPath) -> JarArtifact): JarArtifact { 39 | val directory = cacheBasePath.resolve("jar_$digest") 40 | val cachedJarArtifact = directory.resolve("jarArtifact.descriptor") 41 | 42 | return if (cachedJarArtifact.exists()) { 43 | val jarArtifactLines = cachedJarArtifact.readText().lines() 44 | JarArtifact(OsPath.createOrThrow(cacheBasePath.nativeType, jarArtifactLines[0]), jarArtifactLines[1]) 45 | } else { 46 | directory.createDirectories() 47 | val jarArtifact = creator(directory) 48 | cachedJarArtifact.writeText("${jarArtifact.path}\n${jarArtifact.execClassName}") 49 | jarArtifact 50 | } 51 | } 52 | 53 | fun getOrCreateUriItem(uri: URI, creator: (URI, OsPath) -> Content): Content { 54 | val digest = DigestUtils.md5Hex(uri.toString()) 55 | 56 | val directory = cacheBasePath.resolve("uri_$digest") 57 | val descriptorFile = directory.resolve("uri.descriptor") 58 | val contentFile = directory.resolve("uri.content") 59 | 60 | if (descriptorFile.exists() && contentFile.exists()) { 61 | //Cache hit 62 | val descriptor = descriptorFile.readText().lines() 63 | val scriptType = ScriptType.valueOf(descriptor[0]) 64 | val fileName = descriptor[1] 65 | val cachedUri = URI.create(descriptor[2]) 66 | val contextUri = URI.create(descriptor[3]) 67 | val content = contentFile.readText() 68 | 69 | return Content(content, scriptType, fileName, cachedUri, contextUri, contentFile) 70 | } 71 | 72 | //Cache miss 73 | val content = creator(uri, contentFile) 74 | 75 | directory.createDirectories() 76 | descriptorFile.writeText("${content.scriptType}\n${content.fileName}\n${content.uri}\n${content.contextUri}") 77 | contentFile.writeText(content.text) 78 | 79 | return content 80 | } 81 | 82 | fun getOrCreateDependencies(digest: String, creator: () -> Set): Set { 83 | val directory = cacheBasePath.resolve("dependencies_$digest") 84 | val contentFile = directory.resolve("dependencies.content") 85 | 86 | if (contentFile.exists()) { 87 | val dependencies = 88 | contentFile.readText() 89 | .lines() 90 | .filter { it.isNotEmpty() } 91 | .map { OsPath.createOrThrow(cacheBasePath.nativeType, it) } 92 | .toSet() 93 | 94 | //Recheck cached paths - if there are missing artifacts skip the cached values 95 | if (dependencies.all { it.exists() }) { 96 | return dependencies 97 | } 98 | } 99 | 100 | val dependencies = creator() 101 | directory.createDirectories() 102 | contentFile.writeText(dependencies.joinToString("\n") { it.toString() }) 103 | 104 | return dependencies 105 | } 106 | 107 | fun clear() { 108 | FileUtils.cleanDirectory(cacheBasePath.toNativeFile()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/kscripting/kscript/code/GradleTemplates.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.code 2 | 3 | import io.github.kscripting.kscript.creator.JarArtifact 4 | import io.github.kscripting.kscript.model.CompilerOpt 5 | import io.github.kscripting.kscript.model.Dependency 6 | import io.github.kscripting.kscript.model.Repository 7 | import io.github.kscripting.kscript.model.Script 8 | 9 | object GradleTemplates { 10 | fun createGradleIdeaScript(script: Script): String { 11 | val kotlinVersion = KotlinVersion.CURRENT 12 | val extendedDependencies = setOf( 13 | Dependency("org.jetbrains.kotlin:kotlin-stdlib"), 14 | Dependency("org.jetbrains.kotlin:kotlin-script-runtime:$kotlinVersion"), 15 | Dependency("io.github.kscripting:kscript-annotations:1.5.0"), 16 | ) + script.dependencies 17 | 18 | return """ 19 | |plugins { 20 | | id("org.jetbrains.kotlin.jvm") version "$kotlinVersion" 21 | |} 22 | | 23 | |repositories { 24 | | mavenLocal() 25 | | mavenCentral() 26 | |${createGradleRepositoriesSection(script.repositories).prependIndent()} 27 | |} 28 | | 29 | |dependencies { 30 | |${createGradleDependenciesSection(extendedDependencies).prependIndent()} 31 | |} 32 | | 33 | |sourceSets.getByName("main").java.srcDirs("src") 34 | |sourceSets.getByName("test").java.srcDirs("src") 35 | | 36 | |${createCompilerOptionsSection(script.compilerOpts)} 37 | |""".trimMargin() 38 | } 39 | 40 | fun createGradlePackageScript(script: Script, jarArtifact: JarArtifact): String { 41 | val kotlinOptions = createCompilerOptionsSection(script.compilerOpts) 42 | 43 | val kotlinVersion = KotlinVersion.CURRENT 44 | val extendedDependencies = setOf( 45 | Dependency("org.jetbrains.kotlin:kotlin-stdlib"), 46 | Dependency("org.jetbrains.kotlin:kotlin-script-runtime:$kotlinVersion") 47 | ) + script.dependencies 48 | 49 | val capsuleApp = jarArtifact.execClassName 50 | val baseName = script.scriptLocation.scriptName 51 | 52 | return """ 53 | |import java.io.* 54 | |import java.lang.System 55 | |import java.nio.file.Files 56 | |import java.nio.file.Paths 57 | | 58 | |plugins { 59 | | id("org.jetbrains.kotlin.jvm") version "$kotlinVersion" 60 | | application 61 | |} 62 | | 63 | |repositories { 64 | | mavenLocal() 65 | | mavenCentral() 66 | |${createGradleRepositoriesSection(script.repositories).prependIndent()} 67 | |} 68 | | 69 | |tasks.jar { 70 | | manifest { 71 | | attributes["Main-Class"] = "$capsuleApp" 72 | | } 73 | | archiveBaseName.set("$baseName") 74 | | configurations["compileClasspath"].forEach { file: File -> 75 | | from(zipTree(file.absoluteFile)) 76 | | } 77 | | duplicatesStrategy = DuplicatesStrategy.INCLUDE 78 | |} 79 | | 80 | |tasks.register("makeScript") { 81 | | dependsOn(":jar") 82 | | doLast { 83 | | val headerDir = layout.projectDirectory.toString() 84 | | val jarFileName = layout.buildDirectory.file("libs/$baseName.jar").get().toString() 85 | | val outFileName = layout.buildDirectory.file("libs/$baseName").get().toString() 86 | | val lineSeparator = System.getProperty("line.separator").encodeToByteArray() 87 | | val headerPath = Paths.get(headerDir).resolve("exec_header.sh") 88 | | val headerBytes = Files.readAllBytes(headerPath) 89 | | val jarBytes = Files.readAllBytes(Paths.get(jarFileName)) 90 | | val outFile = Paths.get(outFileName).toFile() 91 | | val fileStream = FileOutputStream(outFile) 92 | | 93 | | fileStream.write(headerBytes) 94 | | fileStream.write(lineSeparator) 95 | | fileStream.write(jarBytes) 96 | | fileStream.close() 97 | | } 98 | |} 99 | | 100 | |dependencies { 101 | | implementation(files("${jarArtifact.path.stringPath().replace("\\", "\\\\")}")) 102 | |${createGradleDependenciesSection(extendedDependencies).prependIndent()} 103 | |} 104 | | 105 | |$kotlinOptions 106 | """.trimStart().trimMargin() 107 | } 108 | 109 | private fun createGradleRepositoryCredentials(repository: Repository): String { 110 | if (repository.user.isNotBlank() && repository.password.isNotBlank()) { 111 | return """|credentials { 112 | | username = "${repository.user}" 113 | | password = "${repository.password}" 114 | |}""".trimMargin() 115 | } 116 | 117 | return "" 118 | } 119 | 120 | private fun createGradleDependenciesSection(dependencies: Set) = dependencies.joinToString("\n") { 121 | "implementation(\"${it.value}\")" 122 | } 123 | 124 | private fun createGradleRepositoriesSection(repositories: Set) = repositories.joinToString("\n") { 125 | """|maven { 126 | | url = uri("${it.url}") 127 | |${createGradleRepositoryCredentials(it).prependIndent()} 128 | |} 129 | """.trimMargin() 130 | } 131 | 132 | private fun createCompilerOptionsSection(compilerOpts: Set): String { 133 | if (compilerOpts.isEmpty()) { 134 | return "" 135 | } 136 | 137 | var jvmTarget = "" 138 | val freeCompilerArgs = mutableListOf() 139 | 140 | for (opt in compilerOpts) { 141 | when { 142 | opt.value.startsWith("-jvm-target") -> { 143 | jvmTarget = "jvmTarget = \"" + opt.value.drop(11).trim() + "\"" 144 | } 145 | 146 | else -> { 147 | freeCompilerArgs.add(opt.value) 148 | } 149 | } 150 | } 151 | 152 | return """|tasks.withType { 153 | | kotlinOptions { 154 | | $jvmTarget 155 | | freeCompilerArgs = listOf(${freeCompilerArgs.joinToString(", ") { "\"$it\"" }}) 156 | | } 157 | |}""".trimMargin() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/kscripting/kscript/code/Templates.kt: -------------------------------------------------------------------------------- 1 | package io.github.kscripting.kscript.code 2 | 3 | import io.github.kscripting.kscript.model.KotlinOpt 4 | import io.github.kscripting.kscript.model.PackageName 5 | import io.github.kscripting.shell.model.ScriptType 6 | import org.intellij.lang.annotations.Language 7 | import java.time.ZonedDateTime 8 | 9 | object Templates { 10 | @Language("sh") 11 | val bootstrapHeader = """ 12 | |#!/bin/bash 13 | | 14 | |//usr/bin/env echo ' 15 | |/**** BOOTSTRAP kscript ****\'>/dev/null 16 | |command -v kscript >/dev/null 2>&1 || source /dev/stdin <<< "${'$'}(curl -L https://git.io/fpF1K)" 17 | |exec kscript $0 "$@" 18 | |\*** IMPORTANT: Any code including imports and annotations must come after this line ***/ 19 | | 20 | |""".trimStart().trimMargin() 21 | 22 | val textProcessingPreamble = """ 23 | |@file:DependsOn("com.github.holgerbrandl:kscript-support-api:1.2.5") 24 | | 25 | |import kscript.text.* 26 | |val lines = resolveArgFile(args) 27 | | 28 | |""".trimStart().trimMargin() 29 | 30 | fun createExecuteHeader(kotlinOpts: Set): String { 31 | val options = mutableListOf("") 32 | 33 | for (opt in kotlinOpts) { 34 | val s = opt.value 35 | if (s.startsWith("-J")) { 36 | options.add(s.substring(2)) 37 | } 38 | } 39 | 40 | val opts = options.joinToString(" ").trim() 41 | 42 | return """ 43 | |#!/usr/bin/env bash 44 | |exec java $opts -jar ${'$'}0 "${'$'}@" 45 | """.trimStart().trimMargin() 46 | } 47 | 48 | fun createWrapperForScript(packageName: PackageName, className: String): String { 49 | val classReference = packageName.value + "." + className 50 | 51 | return """ 52 | |class Main_${className}{ 53 | | companion object { 54 | | @JvmStatic 55 | | fun main(args: Array) { 56 | | val script = Main_${className}::class.java.classLoader.loadClass("$classReference") 57 | | script.getDeclaredConstructor(Array::class.java).newInstance(args); 58 | | } 59 | | } 60 | |}""".trimStart().trimMargin() 61 | } 62 | 63 | fun createRunConfig(rootScriptName: String, rootScriptType: ScriptType, userArgs: List): String { 64 | val rootFileName = rootScriptName + rootScriptType.extension 65 | val userArgsString = userArgs.joinToString(" ") { it } 66 | 67 | if (rootScriptType == ScriptType.KT) { 68 | return """ 69 | | 70 | | 71 | | 72 | | 79 | | 80 | |""".trimStart().trimMargin() 81 | } 82 | 83 | // This is Kotlin scripting configuration (other possible options: ShConfigurationType (linux), BatchConfigurationType (windows)) 84 | return """ 85 | | 86 | | 87 | | 88 | | 95 | | 96 | |""".trimStart().trimMargin() 97 | } 98 | 99 | fun createTitleInfo(selfName: String) = 100 | "$selfName - Enhanced scripting support for Kotlin on *nix and Windows based systems.\n\n" 101 | 102 | fun createHeaderInfo() = 103 | "\nThe