Skip to content

Commit 2af3bab

Browse files
authored
Merge pull request #7 from jnorthrup/feat/python-npm-packaging
Feat/python npm packaging
2 parents ad84dc2 + 5d78351 commit 2af3bab

14 files changed

Lines changed: 500 additions & 16 deletions

README.adoc

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,78 @@ scripts.
574574
On the other hand this doesn't embed dependencies within the script("fat jar"), so internet connection may be required
575575
on its first run.
576576

577+
== Python and NPM Packaging
578+
579+
Starting with version 4.2.3, the main `kscript` binary distribution ZIP file (e.g., `kscript-4.2.3-bin.zip`) now includes helper files to allow users to easily build and install `kscript` as a Python package or an NPM package. This provides a convenient way to integrate `kscript` into Python or Node.js project environments and makes `kscript` available as a command-line tool through `pip` or `npm`.
580+
581+
The necessary files (`setup.py` for Python, `package.json` for Node.js, and various wrapper scripts) are located in the extracted distribution archive. When you extract the main kscript zip, these files will be in the root directory, and the wrappers along with `kscript.jar` will be in the `wrappers/` subdirectory.
582+
583+
=== Python (pip)
584+
585+
To build and install `kscript` as a Python package:
586+
587+
1. Download and extract the `kscript-4.2.3-bin.zip` (or the appropriate version) distribution.
588+
2. Navigate to the root of the extracted directory in your terminal.
589+
3. The `setup.py` script expects `kscript.jar` to be in the `wrappers/` subdirectory, where it should be placed automatically by the build process.
590+
4. Build the wheel package:
591+
+
592+
[source,bash]
593+
----
594+
python setup.py bdist_wheel
595+
----
596+
+
597+
Alternatively, you can create a source distribution:
598+
+
599+
[source,bash]
600+
----
601+
python setup.py sdist
602+
----
603+
5. Install the generated package (the exact filename will depend on the version and build tags):
604+
+
605+
[source,bash]
606+
----
607+
pip install dist/kscript-*.whl
608+
----
609+
6. After installation, `kscript` should be available as a command-line tool, using the Python wrapper to execute `kscript.jar`.
610+
611+
=== Node.js (npm)
612+
613+
To build and install `kscript` as an NPM package:
614+
615+
1. Download and extract the `kscript-4.2.3-bin.zip` (or the appropriate version) distribution.
616+
2. Navigate to the root of the extracted directory in your terminal.
617+
3. The `package.json` file expects `kscript.jar` to be in the `wrappers/` subdirectory, where it should be by default.
618+
4. Create the NPM package:
619+
+
620+
[source,bash]
621+
----
622+
npm pack
623+
----
624+
+
625+
This will create a `kscript-4.2.3.tgz` file (the version comes from `package.json`).
626+
5. Install the package. For global installation:
627+
+
628+
[source,bash]
629+
----
630+
npm install -g kscript-4.2.3.tgz
631+
----
632+
+
633+
Or, to install it as a project dependency, navigate to your project directory and run (adjust path as necessary):
634+
+
635+
[source,bash]
636+
----
637+
npm install /path/to/extracted_kscript_dist/kscript-4.2.3.tgz
638+
----
639+
6. After installation (globally, or locally if `node_modules/.bin` is in your PATH), `kscript` should be available as a command-line tool, using the Node.js wrapper.
640+
641+
=== Direct Wrapper Usage
642+
643+
Advanced users can also utilize the wrapper scripts directly if they prefer to manage their environment and `kscript.jar` location manually:
644+
* Python wrapper: `wrappers/kscript_py_wrapper.py`
645+
* Node.js wrapper: `wrappers/kscript_js_wrapper.js` (make it executable or run with `node`)
646+
647+
These wrappers expect `kscript.jar` to be in the same directory (`wrappers/`) by default. This approach requires `java` to be available in the system PATH.
648+
577649
== kscript configuration file
578650

579651
To keep some options stored permanently in configuration you can create kscript configuration file.

build.gradle.kts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
22
import com.github.jengelman.gradle.plugins.shadow.transformers.ComponentsXmlResourceTransformer
3-
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
3+
import java.util.Locale // Added for toLowerCase
44
import java.time.ZoneOffset
55
import java.time.ZonedDateTime
6+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget // Moved import to top
67

7-
val kotlinVersion: String = "2.1.21-embedded"
8+
val kotlinVersion: String = "2.2.0-RC2"
89

910
plugins {
10-
kotlin("jvm") version "2.1.21-embedded"
11+
kotlin("jvm") version "2.2.0-RC2"
1112
application
1213
id("com.adarshr.test-logger") version "3.2.0"
1314
id("com.github.gmazzo.buildconfig") version "3.1.0"
@@ -72,16 +73,16 @@ idea {
7273

7374
java {
7475
toolchain {
75-
languageVersion.set(JavaLanguageVersion.of(11))
76+
languageVersion.set(JavaLanguageVersion.of(21))
7677
}
7778

7879
withJavadocJar()
7980
withSourcesJar()
8081
}
8182

82-
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().all {
83-
kotlinOptions {
84-
jvmTarget = "11"
83+
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { // Changed .all to .configureEach as per modern practice
84+
compilerOptions {
85+
jvmTarget.set(JvmTarget.JVM_21)
8586
}
8687
}
8788

@@ -132,22 +133,35 @@ tasks.test {
132133
useJUnitPlatform()
133134
}
134135

136+
val copyJarToWrappers by tasks.register<Copy>("copyJarToWrappers") {
137+
dependsOn(tasks.shadowJar)
138+
from(tasks.shadowJar.get().archiveFile)
139+
into(project.projectDir.resolve("wrappers"))
140+
}
141+
135142
val createKscriptLayout by tasks.register<Copy>("createKscriptLayout") {
136-
dependsOn(shadowJar)
143+
dependsOn(copyJarToWrappers)
137144

138145
into(layout.buildDirectory.dir("kscript"))
139146

140-
from(shadowJar) {
147+
from(tasks.shadowJar.get().archiveFile) { // kscript.jar from shadowJar output
141148
into("bin")
142149
}
143150

144-
from("src/kscript") {
151+
from("src/kscript") { // kscript shell script
145152
into("bin")
146153
}
147154

148-
from("src/kscript.bat") {
155+
from("src/kscript.bat") { // kscript batch script
149156
into("bin")
150157
}
158+
159+
from("wrappers") { // Python and Nodejs wrappers + kscript.jar
160+
into("wrappers")
161+
}
162+
163+
from("setup.py") // Python packaging script
164+
from("package.json") // Nodejs packaging manifest
151165
}
152166

153167
val packageKscriptDistribution by tasks.register<Zip>("packageKscriptDistribution") {
@@ -174,7 +188,7 @@ application {
174188
}
175189

176190
fun adjustVersion(archiveVersion: String): String {
177-
var newVersion = archiveVersion.toLowerCaseAsciiOnly()
191+
var newVersion = archiveVersion.lowercase(Locale.ROOT) // Changed to lowercase(Locale.ROOT)
178192

179193
val temporaryVersion = newVersion.substringBeforeLast(".")
180194

@@ -291,11 +305,12 @@ dependencies {
291305

292306
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
293307
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
294-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
308+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") // Updated for Kotlin 2.0.0
295309

296310
implementation("org.jetbrains.kotlin:kotlin-scripting-common:$kotlinVersion")
297311
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm:$kotlinVersion")
298312
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven-all:$kotlinVersion")
313+
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.0-RC2") // Added as requested
299314

300315
implementation("org.apache.commons:commons-lang3:3.12.0")
301316
implementation("commons-io:commons-io:2.11.0")

examples/find_large_files.kts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env kscript
2+
@file:DependsOn("com.github.ajalt:clikt:3.2.0") // For command line parsing
3+
4+
import com.github.ajalt.clikt.core.CliktCommand
5+
import com.github.ajalt.clikt.parameters.options.*
6+
import com.github.ajalt.clikt.parameters.types.long
7+
import com.github.ajalt.clikt.parameters.types.path
8+
import java.io.File
9+
import java.nio.file.Files
10+
import java.nio.file.Path
11+
import java.util.zip.ZipEntry
12+
import java.util.zip.ZipOutputStream
13+
14+
class FindLargeFiles : CliktCommand(help = "Finds files larger than a specified size and optionally zips them.") {
15+
val directory: Path by option("-d", "--directory", help = "Directory to search (default: current directory)")
16+
.path(mustExist = true, canBeFile = false, mustBeReadable = true)
17+
.default(Path.of("."))
18+
19+
val minSize: Long by option("-s", "--size", help = "Minimum file size in MB")
20+
.long()
21+
.default(100L)
22+
23+
val archivePath: Path? by option("-a", "--archive", help = "Optional ZIP file path to archive found files")
24+
.path(canBeDir = false) // Removed mustBeWritable as it causes issues if file doesn't exist for parent dir check
25+
26+
val quiet: Boolean by option("-q", "--quiet", help = "Suppress listing of found files").flag(default = false)
27+
28+
val force: Boolean by option("-f", "--force", help="Force overwrite of existing archive").flag(default = false)
29+
30+
31+
override fun run() {
32+
val minSizeBytes = minSize * 1024 * 1024
33+
if (!quiet) {
34+
echo("Searching for files larger than $minSize MB ($minSizeBytes bytes) in directory: $directory")
35+
}
36+
37+
val largeFiles = mutableListOf<File>()
38+
39+
directory.toFile().walkTopDown().forEach { file ->
40+
if (file.isFile && file.length() >= minSizeBytes) {
41+
largeFiles.add(file)
42+
if (!quiet) {
43+
echo("Found: ${file.absolutePath} (${file.length() / (1024 * 1024)} MB)")
44+
}
45+
}
46+
}
47+
48+
if (largeFiles.isEmpty()) {
49+
if (!quiet) {
50+
echo("No files found larger than $minSize MB.")
51+
}
52+
return
53+
}
54+
55+
if (!quiet) {
56+
echo("\nFound ${largeFiles.size} file(s) larger than $minSize MB.")
57+
}
58+
59+
archivePath?.let { zipPath ->
60+
if (Files.exists(zipPath) && !force) {
61+
// Simplified prompt for non-interactive, or use a different Clikt mechanism for actual prompting if needed
62+
val shouldOverwrite = System.getenv("KSCRIPT_OVERWRITE_ARCHIVE") == "true" // Example: control via env var
63+
if (!shouldOverwrite) {
64+
echo("Archive '$zipPath' exists. Overwrite not forced and not confirmed. Archiving cancelled.", err = true)
65+
return
66+
}
67+
}
68+
69+
if (!quiet) {
70+
echo("Archiving ${largeFiles.size} file(s) to $zipPath ...")
71+
}
72+
try {
73+
ZipOutputStream(Files.newOutputStream(zipPath)).use { zos ->
74+
largeFiles.forEach { file ->
75+
// Ensure relative paths for zip entries if directory is not current
76+
val entryPath = if (directory.toFile().absolutePath == Path.of(".").toFile().absolutePath) {
77+
file.toPath() // Use relative path if searching "."
78+
} else {
79+
directory.relativize(file.toPath())
80+
}
81+
val zipEntry = ZipEntry(entryPath.toString())
82+
zos.putNextEntry(zipEntry)
83+
Files.copy(file.toPath(), zos)
84+
zos.closeEntry()
85+
if (!quiet) {
86+
echo(" Added: ${file.name}")
87+
}
88+
}
89+
}
90+
if (!quiet) {
91+
echo("Archiving complete: $zipPath")
92+
}
93+
} catch (e: Exception) {
94+
echo("Error during archiving: ${e.message}", err = true)
95+
// e.printStackTrace() // for more detailed debug if necessary
96+
}
97+
}
98+
}
99+
}
100+
101+
fun main(args: Array<String>) = FindLargeFiles().main(args)

examples/test_kotlin2_feature.kts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env kscript
2+
3+
// This script uses a 'value class', a feature refined in modern Kotlin.
4+
@JvmInline
5+
value class UserId(val id: String) {
6+
fun greet(): String = "Hello, User '${id}' from a value class!"
7+
}
8+
9+
fun main() {
10+
val userId = UserId("KTS_User_123")
11+
val greeting = userId.greet()
12+
println(greeting)
13+
println("Kotlin 2.x feature (value class) test successful!")
14+
}
15+
16+
main()

examples/test_wrapper.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env kscript
2+
println("kscript wrapper test successful!")

gradle/wrapper/gradle-wrapper.jar

34 Bytes
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
44
networkTimeout=10000
55
zipStoreBase=GRADLE_USER_HOME
66
zipStorePath=wrapper/dists

gradlew

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "kscript",
3+
"version": "4.2.3",
4+
"description": "KScript - easy scripting with Kotlin",
5+
"author": "Holger Brandl, Marcin Kuszczak",
6+
"license": "MIT",
7+
"homepage": "https://github.com/kscripting/kscript",
8+
"repository": {
9+
"type": "git",
10+
"url": "git+https://github.com/kscripting/kscript.git"
11+
},
12+
"keywords": [
13+
"kotlin",
14+
"scripting",
15+
"kscript"
16+
],
17+
"bin": {
18+
"kscript": "./wrappers/kscript_js_wrapper.js"
19+
},
20+
"files": [
21+
"wrappers/kscript_js_wrapper.js",
22+
"wrappers/kscript.jar"
23+
],
24+
"engines": {
25+
"node": ">=12"
26+
}
27+
}

setup.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name='kscript',
5+
version='4.2.3',
6+
author='Holger Brandl, Marcin Kuszczak',
7+
author_email='holgerbrandl@gmail.com, aarti@interia.pl',
8+
description='KScript - easy scripting with Kotlin',
9+
url='https://github.com/kscripting/kscript',
10+
license='MIT',
11+
packages=['wrappers'],
12+
entry_points={
13+
'console_scripts': [
14+
'kscript=wrappers.kscript_py_wrapper:main'
15+
]
16+
},
17+
package_data={
18+
'wrappers': ['kscript.jar'] # Assume kscript.jar is copied to wrappers directory
19+
},
20+
classifiers=[
21+
'Programming Language :: Python :: 3',
22+
'License :: OSI Approved :: MIT License',
23+
'Operating System :: OS Independent',
24+
],
25+
python_requires='>=3.6',
26+
)

0 commit comments

Comments
 (0)