diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml
new file mode 100644
index 0000000..8d46b78
--- /dev/null
+++ b/.github/workflows/gradle-publish.yml
@@ -0,0 +1,32 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
+
+name: Kommand Publish
+
+on:
+ push:
+ tags:
+ - "*"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ publish:
+ runs-on: [self-hosted, macOS, ARM64, aarch64-apple-darwin]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up just
+ uses: extractions/setup-just@v1
+
+ - name: Test Gradle availability
+ run: ./gradlew build --dry-run
+
+ - name: Test with Docker
+ run: just autoPublish
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
new file mode 100644
index 0000000..7505805
--- /dev/null
+++ b/.github/workflows/gradle.yml
@@ -0,0 +1,98 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
+
+name: Kommand Test
+
+on:
+ push:
+ branches: [ "main", "dev" ]
+ pull_request:
+ branches: [ "main" ]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - macos-12
+ - [self-hosted, macOS, ARM64, aarch64-apple-darwin]
+ - ubuntu-22.04
+ - [self-hosted, macOS, ARM64, aarch64-unknown-linux-gnu]
+ - windows-latest
+ include:
+ - os: macos-12
+ target: x86_64-apple-darwin
+ task: macosX64Test
+ - os: [self-hosted, macOS, ARM64, aarch64-apple-darwin]
+ target: aarch64-apple-darwin
+ task: macosArm64Test
+ - os: ubuntu-22.04
+ target: x86_64-unknown-linux-gnu
+ task: linuxX64Test
+ gcc: true
+ - os: [self-hosted, macOS, ARM64, aarch64-unknown-linux-gnu]
+ target: aarch64-unknown-linux-gnu
+ task: linuxArm64Test
+ cross: true
+ - os: windows-latest
+ target: x86_64-pc-windows-gnu
+ task: mingwX64Test
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Cargo
+ uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ toolchain: 1.69.0
+ target: ${{ matrix.target }}
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4.0.0
+ with:
+ java-version: '17'
+ distribution: 'zulu'
+ architecture: ${{ matrix.arch }}
+ cache: 'gradle'
+
+ - if: ${{ matrix.gcc }}
+ name: Set up GCC
+ uses: Dup4/actions-setup-gcc@v1
+
+ - name: Set up just
+ uses: extractions/setup-just@v1
+
+ - name: Set up Gradle
+ uses: gradle/gradle-build-action@v2
+ with:
+ gradle-version: '8.2'
+
+ - name: Test Gradle availability
+ run: gradle build --dry-run
+
+ - if: ${{ !matrix.gcc }}
+ name: Build kommand-core
+ run: just ${{ matrix.target }}
+ working-directory: ./kommand-core
+
+ - if: ${{ matrix.gcc }}
+ name: Build kommand-core with GCC
+ run: just workflow ${{ matrix.target }}
+ working-directory: ./kommand-core
+
+ - if: ${{ !matrix.cross }}
+ name: Test with Gradle
+ run: gradle ${{ matrix.task }} jvmTest
+
+ - if: ${{ matrix.cross }}
+ name: Test with Docker
+ run: just ${{ matrix.task }}
diff --git a/.gitignore b/.gitignore
index 46ec24b..de17a73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,120 +1,6 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,intellij+iml,vim,visualstudiocode,gradle,kotlin
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,intellij+iml,vim,visualstudiocode,gradle,kotlin
-### Intellij+iml ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-.idea/artifacts
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-### Intellij+iml Patch ###
-# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
-
-*.iml
-modules.xml
-.idea/misc.xml
-*.ipr
-
-### Kotlin ###
-# Compiled class file
-*.class
-
-# Log file
-*.log
-
-# BlueJ files
-*.ctxt
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.nar
-*.ear
-*.zip
-*.tar.gz
-*.rar
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
-replay_pid*
-
### macOS ###
# General
.DS_Store
@@ -216,3 +102,5 @@ gradle-app.setting
*.hprof
# End of https://www.toptal.com/developers/gitignore/api/macos,intellij+iml,vim,visualstudiocode,gradle,kotlin
+
+build-cache
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..2b43a41
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+/artifacts
+/uiDesigner.xml
diff --git a/.idea/artifacts/kommand_jvm_1_2_0.xml b/.idea/artifacts/kommand_jvm_1_2_0.xml
new file mode 100644
index 0000000..d54754b
--- /dev/null
+++ b/.idea/artifacts/kommand_jvm_1_2_0.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..be4bb8e
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 5c593f5..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml
deleted file mode 100644
index 2a0b4e8..0000000
--- a/.idea/jsonSchemas.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kommand.iml b/.idea/kommand.iml
new file mode 100644
index 0000000..1dfe6e7
--- /dev/null
+++ b/.idea/kommand.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index c8378aa..0000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..2304d43
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..e3257ea
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/kommand.iml b/.idea/modules/kommand.iml
new file mode 100644
index 0000000..2ed6250
--- /dev/null
+++ b/.idea/modules/kommand.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vagrant/machines/default/virtualbox/action_set_name b/.vagrant/machines/default/virtualbox/action_set_name
deleted file mode 100644
index b5dcc97..0000000
--- a/.vagrant/machines/default/virtualbox/action_set_name
+++ /dev/null
@@ -1 +0,0 @@
-1672061732
\ No newline at end of file
diff --git a/.vagrant/machines/default/virtualbox/creator_uid b/.vagrant/machines/default/virtualbox/creator_uid
deleted file mode 100644
index ec52cb8..0000000
--- a/.vagrant/machines/default/virtualbox/creator_uid
+++ /dev/null
@@ -1 +0,0 @@
-501
\ No newline at end of file
diff --git a/.vagrant/machines/default/virtualbox/id b/.vagrant/machines/default/virtualbox/id
deleted file mode 100644
index 4155507..0000000
--- a/.vagrant/machines/default/virtualbox/id
+++ /dev/null
@@ -1 +0,0 @@
-9e83fa97-934e-4dd2-beb5-eed3a84736e0
\ No newline at end of file
diff --git a/.vagrant/machines/default/virtualbox/index_uuid b/.vagrant/machines/default/virtualbox/index_uuid
deleted file mode 100644
index 13f8831..0000000
--- a/.vagrant/machines/default/virtualbox/index_uuid
+++ /dev/null
@@ -1 +0,0 @@
-4025459ae1a544fe887d25baabcfa405
\ No newline at end of file
diff --git a/.vagrant/machines/default/virtualbox/vagrant_cwd b/.vagrant/machines/default/virtualbox/vagrant_cwd
deleted file mode 100644
index f3cb59b..0000000
--- a/.vagrant/machines/default/virtualbox/vagrant_cwd
+++ /dev/null
@@ -1 +0,0 @@
-/Users/bppleman/kgit2/kommand
\ No newline at end of file
diff --git a/.vagrant/rgloader/loader.rb b/.vagrant/rgloader/loader.rb
deleted file mode 100644
index c3c05b0..0000000
--- a/.vagrant/rgloader/loader.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file loads the proper rgloader/loader.rb file that comes packaged
-# with Vagrant so that encoded files can properly run with Vagrant.
-
-if ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]
- require File.expand_path(
- "rgloader/loader", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"])
-else
- raise "Encoded files can't be read outside of the Vagrant installer."
-end
diff --git a/README-CN.md b/README-CN.md
index de8b544..6937b46 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -1,32 +1,51 @@
+[![Kommand Test](https://github.com/kgit2/kommand/actions/workflows/gradle.yml/badge.svg)](https://github.com/kgit2/kommand/actions/workflows/gradle.yml)
+
![logo](https://raw.githubusercontent.com/floater-git/Artist/main/kommand/logo.png)
# Kommand
+
一个可以将外部命令跑在子进程的库,用于Kotlin Native/JVM
-# 架构示意
+# v2.0.0
+
+Rust 是一门兼顾性能和工程性的优秀语言。
+
+在 1.x 版本中,我们使用以下 API 来提供创建子进程的功能
-![architecture](https://raw.githubusercontent.com/floater-git/Artist/main/kommand/architecture_2.0.png)
+- `fork` of [POSIX api]
+- `CreateChildProcess` of [win32 api]
+- `java.lang.ProcessBuilder` of JVM
-# 源泉
-- 深受rust-std `Command`启发。
-- 基于ktor-io,可以使用管道处理进程间通信(IPC)。
-- kotlin多平台1.7.20,使用新的内存管理器。
+在 2.0 版本中,我们使用 Rust 标准库来提供创建子进程的功能。
-- ### Native for macOS/Linux
+- `std::process::Command` of Rust
+- `java.lang.ProcessBuilder` of JVM
- 使用POSIX api的系统调用
+它将带来
-- ### Native for Mingw
+- 更统一的 API
+- 更易用的 API
+- 性能依旧优秀
+- 更易于维护
+- 代码结构更清晰
- 使用Win32 api的系统调用
+# 支持的平台
-- ### JVM
+- x86_64-apple-darwin
+- aarch64-apple-darwin
+- x86_64-unknown-linux-gnu
+- aarch64-unknown-linux-gnu
+- x86_64-pc-windows-gnu (mingw-w64)
+- jvm
- 基于 `java.lang.ProcessBuilder`
+# 依赖于
-# Usage
+- Rust Standard Library 1.69.0
+- Kotlin Multiplatform 1.9.21
-## Dependency
+# 用法
+
+## 依赖
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.kgit2/kommand/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.kgit2/kommand)
@@ -40,57 +59,103 @@ repositories {
// ……
dependencies {
- implementation("com.kgit2:kommand:$lastVersion")
+ // should replace with the latest version
+ implementation("com.kgit2:kommand:2.x")
}
```
-## Quick Start
+## 快速上手
-### Inherit Standard I/O
+### 继承标准 I/O
-```kotlin
-Command("ping")
- .arg("-c")
- .args("5", "localhost")
- .spawn()
- .wait()
+https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12
+
+
+### 管道 I/O
+
+https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L15
+
+
+### 屏蔽 I/O
+
+https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12
+
+
+## 自行编译
+
+### 1. 依赖
+
+- rust toolchain - <= 1.69.0 (https://rustup.rs) (建议)
+ - cross (install with `cargo install cross`)
+ - just (install with `cargo install just`)
+- 交叉编译工具链
+ - x86_64-apple-darwin
+ - aarch64-apple-darwin
+ - x86_64-unknown-linux-gnu
+ - aarch64-unknown-linux-gnu
+ - x86_64-pc-windows-gnu (mingw-w64)
+- docker (可选)
+
+强烈推荐在 macOS 编译所有平台。
+
+Kotlin Multiplatform 在 macOS 有更好的支持
+
+> 如果你使用 macOS , 你可以用下述命令安装工具链
+> ```bash
+> just prepare
+> ```
+> 否则, 你需要自行安装工具链
+
+### 2. 克隆仓库
+
+```bash
+git clone https://github.com/kgit2/kommand.git
```
+### 3. 编译 kommand-core
-### Piped I/O
+```bash
+cd kommand-core
+just all
+```
-```kotlin
-val child = Command("ping")
- .args("-c", "5", "localhost")
- .stdout(Stdio.Pipe)
- .spawn()
-val stdoutReader: com.kgit2.io.Reader? = child.getChildStdout()
-val lines: Sequence = stdoutReader?.lines()
-lines.forEach {
- println(it)
-}
+### 4. 编译 kommand
+
+```bash
+./gradlew build
```
-### Null I/O
+### 5. 跨平台测试
-```kotlin
-Command("gradle")
- .arg("build")
- .stdout(Stdio.Null)
- .spawn()
- .wait()
+> 仅 linux 平台支持跨平台测试.
+
+* install docker
+
+[Install Docker Engine](https://docs.docker.com/engine/install/)
+
+* 运行测试
+
+```bash
+# for x86_64
+just linuxX64Test
+# for aarch64
+just linuxArm64Test
```
-## 主要贡献者
+## Maintainers
[@BppleMan](https://github.com/BppleMan).
-[@XJMiada](https://github.com/XJMiada).(图片原创)
+[@XJMiada](https://github.com/XJMiada).(Original Picture)
-## 许可证
+## License
[Apache2.0](LICENSE) © BppleMan
-## 感谢
+## Credits
- [![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://jb.gg/OpenSourceSupport)
+
+## Star History
+
+[![Star History Chart](https://api.star-history.com/svg?repos=kgit2/kommand&type=Date&theme=dark)](https://star-history.com/#kgit2/kommand&Date)
diff --git a/README.md b/README.md
index b37a442..e95272d 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,48 @@
+[![Kommand Test](https://github.com/kgit2/kommand/actions/workflows/gradle.yml/badge.svg)](https://github.com/kgit2/kommand/actions/workflows/gradle.yml)
+[![Kommand Publish](https://github.com/kgit2/kommand/actions/workflows/gradle-publish.yml/badge.svg)](https://github.com/kgit2/kommand/actions/workflows/gradle-publish.yml)
+
![logo](https://raw.githubusercontent.com/floater-git/Artist/main/kommand/logo.png)
# Kommand
-Kotlin Native library for create subprocesses and handle their I/O.
-
-# Supported Platforms
+Kotlin Native library for create sub-process and redirect their I/O.
-- macOS-X64
-- macOS-Arm64
-- linux-X64
-- linux-Arm64
-- mingw-X64
-- JVM
+# v2.0.0
-# Architecture
+Rust is an excellent language that takes into account both performance and engineering.
-![architecture](assets/architecture_3.0.png)
+In version 1.x, we use the following API to provide the function of creating child processes
-# Dependent
+- `fork` of [POSIX api]
+- `CreateChildProcess` of [win32 api]
+- `java.lang.ProcessBuilder` of JVM
-- Heavily inspired by the rust-std `Command`.
-- Based on the `ktor-io`, Inter-Process Communication(IPC) can be handled using pipes
-- Kotlin Multiplatform 1.7.20 with new memory manager
+In version 2.0, we use the Rust standard library to provide the function of creating child processes.
-- ### Native for macOS/Linux
+- `std::process::Command` of Rust
+- `java.lang.ProcessBuilder` of JVM
- System calls using POSIX api
+It will bring
-- ### Native for Mingw
+- More unified API
+- Easier to use API
+- Performance is still excellent
+- Easier to maintain
+- Code structure is clearer
- System calls using Win32 api
+# Supported Platforms
-- ### JVM
+- x86_64-apple-darwin
+- aarch64-apple-darwin
+- x86_64-unknown-linux-gnu
+- aarch64-unknown-linux-gnu
+- x86_64-pc-windows-gnu (mingw-w64)
+- jvm
- Based `java.lang.ProcessBuilder`
+# Dependent
+- Rust Standard Library 1.69.0
+- Kotlin Multiplatform 1.9.21
# Usage
@@ -52,7 +60,8 @@ repositories {
// ……
dependencies {
- implementation("com.kgit2:kommand:$lastVersion")
+ // should replace with the latest version
+ implementation("com.kgit2:kommand:2.x")
}
```
@@ -61,92 +70,77 @@ dependencies {
### Inherit Standard I/O
-```kotlin
-Command("ping")
- .arg("-c")
- .args("5", "localhost")
- .spawn()
- .wait()
-```
+https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12
+
### Piped I/O
-```kotlin
-val child = Command("ping")
- .args("-c", "5", "localhost")
- .stdout(Stdio.Pipe)
- .spawn()
-val stdoutReader: com.kgit2.io.Reader? = child.getChildStdout()
-val lines: Sequence = stdoutReader?.lines()
-lines.forEach {
- println(it)
-}
-child.wait()
-```
+https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L15
+
### Null I/O
-```kotlin
-Command("gradle")
- .arg("build")
- .stdout(Stdio.Null)
- .spawn()
- .wait()
-```
+https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12
-## Build
-### 1. clone this repo
+## Build by yourself
-```bash
-git clone https://github.com/kgit2/kommand.gi
-```
-Then you can build it with gradle
+### 1. Dependencies
-```bash
-./gradlew build
-```
+- rust toolchain - <= 1.69.0 (https://rustup.rs) (recommend)
+ - cross (install with `cargo install cross`)
+ - just (install with `cargo install just`)
+- cross-compile toolchain
+ - x86_64-apple-darwin
+ - aarch64-apple-darwin
+ - x86_64-unknown-linux-gnu
+ - aarch64-unknown-linux-gnu
+ - x86_64-pc-windows-gnu (mingw-w64)
+- docker (optional)
+
+Recommend build all platforms in macOS.
-> If you want to unit test it, go on.
+Kotlin Multiplatform gets the most complete support on macOS.
-### 2. install dependencies
+> If you are using macOS, you can install the cross-compile toolchain with
+> ```bash
+> just prepare
+> ```
+> Otherwise, you need to install the cross-compile toolchain yourself.
-* install rust toolchain
+### 2. Clone this repo
```bash
-curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+git clone https://github.com/kgit2/kommand.git
```
+### 3. Build kommand-core
-* install cross & justfile
+```bash
+cd kommand-core
+just all
+```
+
+### 4. Build kommand
```bash
-cargo install cross justfile
+./gradlew build
```
+### 5. cross-platform test
+
+> Only linux support cross-platform test.
+
* install docker
[Install Docker Engine](https://docs.docker.com/engine/install/)
-* build `eko`
-
-```bash
-cd eko
-just prepare # install some toolchains
-just all # for all platforms
-# also can build for specific platform
-just macosX64
-just linuxX64
-#or else
-just --list
-```
* test it
```bash
+# for x86_64
just linuxX64Test
-# or
-just macosX64Test
-# or else
-just --list
+# for aarch64
+just linuxArm64Test
```
## Maintainers
diff --git a/build.gradle.kts b/build.gradle.kts
index 3d02587..da33507 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,28 +1,31 @@
+import org.apache.tools.ant.taskdefs.AbstractJarSignerTask
import org.jetbrains.dokka.gradle.DokkaTask
plugins {
kotlin("multiplatform")
id("org.jetbrains.dokka")
- id("io.github.gradle-nexus.publish-plugin")
`maven-publish`
- application
signing
+ id("io.github.gradle-nexus.publish-plugin")
}
group = "com.kgit2"
-version = "1.2.0"
-
-val ktorIO = "2.3.4"
+version = "2.0.0"
repositories {
mavenCentral()
gradlePluginPortal()
}
+subprojects {
+ group = "com.kgit2"
+ version = "1.2.0"
+}
+
kotlin {
jvm {
compilations.all {
- kotlinOptions.jvmTarget = "11"
+ kotlinOptions.jvmTarget = "17"
}
withJava()
testRuns["test"].executionTask.configure {
@@ -31,100 +34,67 @@ kotlin {
}
val nativeTargets = listOf(
- macosArm64(),
- macosX64(),
- linuxX64(),
- linuxArm64(),
- mingwX64(),
+ macosX64() to Platform.MACOS_X64,
+ macosArm64() to Platform.MACOS_ARM64,
+ linuxX64() to Platform.LINUX_X64,
+ linuxArm64() to Platform.LINUX_ARM64,
+ mingwX64() to Platform.MINGW_X64,
)
+ nativeTargets.forEach { (nativeTarget, targetPlatform) ->
+ nativeTarget.apply {
+ compilations.getByName("main") {
+ cinterops {
+ create("kommandCore") {
+ defFile(project.file("src/nativeInterop/cinterop/${targetPlatform.archName}.def"))
+ packageName("kommand_core")
+ }
+ }
+ }
+ }
+ }
+
+ applyDefaultHierarchyTemplate()
+
sourceSets {
// add opt-in
all {
languageSettings.optIn("kotlinx.cinterop.UnsafeNumber")
languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi")
- // languageSettings.optIn("kotlin.ExperimentalStdlibApi")
- }
+ languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
+ languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi")
+ languageSettings.optIn("kotlin.ExperimentalStdlibApi")
- val commonMain by getting {
- dependencies {
- implementation("io.ktor:ktor-io:2.3.4")
+ languageSettings {
+ compilerOptions {
+ freeCompilerArgs.add("-Xexpect-actual-classes")
+ }
}
}
- val commonTest by getting {
+
+ commonMain {
dependencies {
- implementation(kotlin("test"))
+ implementation("org.jetbrains.kotlinx:atomicfu:0.23.1")
}
}
-
- val jvmMain by getting
- val jvmTest by getting
-
- val posixMain by creating {
- dependsOn(commonMain)
- }
- val posixTest by creating {
- dependsOn(commonTest)
+ commonTest {
dependencies {
- implementation("io.ktor:ktor-server-core:2.3.4")
- implementation("io.ktor:ktor-server-cio:2.3.4")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+ implementation(kotlin("test"))
}
}
-
- val unixLikeMain by creating {
- dependsOn(posixMain)
- }
- val unixLikeTest by creating {
- dependsOn(posixTest)
- }
- val macosArm64Main by getting {
- dependsOn(unixLikeMain)
- }
- val macosArm64Test by getting {
- dependsOn(unixLikeTest)
- }
- val macosX64Main by getting {
- dependsOn(unixLikeMain)
- }
- val macosX64Test by getting {
- dependsOn(unixLikeTest)
- }
- val linuxX64Main by getting {
- dependsOn(unixLikeMain)
- }
- val linuxX64Test by getting {
- dependsOn(unixLikeTest)
- }
- val linuxArm64Main by getting {
- dependsOn(unixLikeMain)
- }
- val linuxArm64Test by getting {
- dependsOn(unixLikeTest)
- }
- val mingwX64Main by getting {
- dependsOn(posixMain)
- }
- val mingwX64Test by getting
}
}
-val subCommandInstallDist = tasks.findByPath(":sub_command:installDist")
-
-val buildEko = tasks.create("buildEko") {
- group = "build"
- doLast {
- ProcessBuilder("bash", "-c", "cargo build --release")
- .directory(file("eko"))
- .inheritIO()
- .start()
- .waitFor()
+tasks {
+ withType(Wrapper::class) {
+ distributionType = Wrapper.DistributionType.ALL
+ gradleVersion = "8.2"
}
-}
-tasks.forEach {
- if (it.group == "verification" || it.path.contains("Test")) {
- it.dependsOn(buildEko)
+ withType(Test::class) {
+ testLogging {
+ showStandardStreams = true
+ }
}
}
@@ -142,26 +112,6 @@ val ossrhPassword = runCatching {
}.getOrNull()
if (ossrhUsername != null && ossrhPassword != null) {
- val keyId = project.findProperty("signing.keyId") as String?
- val keyPass = project.findProperty("signing.password") as String?
- val keyRingFile = project.findProperty("signing.secretKeyRingFile") as String?
-
- val dokkaOutputDir = "$buildDir/dokka"
-
- tasks.getByName("dokkaHtml") {
- outputDirectory.set(file(dokkaOutputDir))
- }
-
- val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") {
- delete(dokkaOutputDir)
- }
-
- val javadocJar = tasks.register("javadocJar") {
- dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml)
- archiveClassifier.set("javadoc")
- from(dokkaOutputDir)
- }
-
nexusPublishing {
repositories {
sonatype {
@@ -186,7 +136,18 @@ if (ossrhUsername != null && ossrhPassword != null) {
}
publications {
withType {
- artifact(javadocJar.get())
+ val dokkaJar = project.tasks.register("${name}DokkaJar", Jar::class) {
+ group = JavaBasePlugin.DOCUMENTATION_GROUP
+ description = "Assembles Kotlin docs with Dokka into a Javadoc jar"
+ archiveClassifier.set("javadoc")
+ from(tasks.dokkaHtml)
+
+ // Each archive name should be distinct, to avoid implicit dependency issues.
+ // We use the same format as the sources Jar tasks.
+ // https://youtrack.jetbrains.com/issue/KT-46466
+ archiveBaseName.set("${archiveBaseName.get()}-${name}")
+ }
+ artifact(dokkaJar)
pom {
name.set("kommand")
description.set("A simple process library for Kotlin Multiplatform")
@@ -194,7 +155,7 @@ if (ossrhUsername != null && ossrhPassword != null) {
licenses {
license {
name.set("The Apache License, Version 2.0")
- url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
+ url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
scm {
@@ -214,6 +175,29 @@ if (ossrhUsername != null && ossrhPassword != null) {
}
signing {
+ // will default find the
+ // - signing.keyId
+ // - signing.password
+ // - signing.secretKeyRingFile
sign(publishing.publications)
}
}
+
+enum class Platform(
+ val archName: String
+) {
+ MACOS_X64("x86_64-apple-darwin"),
+ MACOS_ARM64("aarch64-apple-darwin"),
+ LINUX_X64("x86_64-unknown-linux-gnu"),
+ LINUX_ARM64("aarch64-unknown-linux-gnu"),
+ MINGW_X64("x86_64-pc-windows-gnu"),
+ ;
+}
+
+val platforms: List = listOf(
+ Platform.MACOS_X64,
+ Platform.MACOS_ARM64,
+ Platform.LINUX_X64,
+ Platform.LINUX_ARM64,
+ Platform.MINGW_X64,
+)
diff --git a/eko/.gitignore b/eko/.gitignore
deleted file mode 100644
index 615b90c..0000000
--- a/eko/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-target
-.DS_Store
-.idea
diff --git a/eko/Cargo.lock b/eko/Cargo.lock
deleted file mode 100644
index fa9863e..0000000
--- a/eko/Cargo.lock
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "eko"
-version = "0.1.0"
diff --git a/eko/Cargo.toml b/eko/Cargo.toml
deleted file mode 100644
index ddc1591..0000000
--- a/eko/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "eko"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
diff --git a/eko/justfile b/eko/justfile
deleted file mode 100755
index bfb372b..0000000
--- a/eko/justfile
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env just --justfile
-
-release:
- cargo build --release
-
-macosX64:
- cargo build --release --target x86_64-apple-darwin
-
-macosArm64:
- cargo build --release --target aarch64-apple-darwin
-
-linuxX64:
- CROSS_ROOTLESS_CONTAINER_ENGINE=1 cross build --release --target x86_64-unknown-linux-gnu
-
-linuxArm64:
- CROSS_ROOTLESS_CONTAINER_ENGINE=1 cross build --release --target aarch64-unknown-linux-gnu
-
-windowsX64:
- CROSS_ROOTLESS_CONTAINER_ENGINE=1 cross build --release --target x86_64-pc-windows-gnu
-
-all: macosX64 macosArm64 linuxX64 linuxArm64 windowsX64
-
-prepare:
- cargo install cross
- rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-apple-darwin aarch64-apple-darwin x86_64-pc-windows-gnu
diff --git a/gradle.properties b/gradle.properties
index 1da62bf..7e59c15 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,6 +1,5 @@
kotlin.code.style=official
-kotlin.mpp.enableGranularSourceSetsMetadata=true
-kotlin.native.enableDependencyPropagation=false
-kotlin.js.generate.executable.default=false
org.gradle.jvmargs=-Xmx4096m
+#org.gradle.caching=true
+kotlin.mpp.enableCInteropCommonization=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 41d9927..d64cd49 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index aa991fc..bf01c4d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1b6c787..1aa94a4 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,11 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index ac1b06f..6689b85 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/justfile b/justfile
index 23a510a..548922d 100755
--- a/justfile
+++ b/justfile
@@ -1,68 +1,68 @@
#!/usr/bin/env just --justfile
-macosX64:
- ./gradlew macosX64TestBinaries
-
-macosArm64:
- ./gradlew macosArm64TestBinaries
-
-linuxX64:
- ./gradlew linuxX64TestBinaries
-
-linuxArm64:
- ./gradlew linuxArm64TestBinaries
-
-windowsX64:
- ./gradlew mingwX64TestBinaries
-
-all: macosX64 macosArm64 linuxX64 linuxArm64 windowsX64
+prepare:
+ brew install mingw-w64
+ brew tap messense/macos-cross-toolchains
+ brew install x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
clean:
./gradlew clean
-linuxX64Test: linuxX64
+link-test target:
+ ./gradlew {{target}}TestBinaries
+
+linuxX64Test:
+ just link-test linuxX64
+ # ignore the error exit code
-docker run -itd --name linuxX64Test \
- -v ./build/bin/linuxX64:/kommand \
- -v ./eko/target/x86_64-unknown-linux-gnu/release:/kommand/eko/target/release \
+ -v ./build/bin/linuxX64:/kommand/build/bin/linuxX64 \
+ -v ./kommand-core/target/x86_64-unknown-linux-gnu/release/kommand-echo:/kommand/kommand-core/target/x86_64-unknown-linux-gnu/release/kommand-echo \
-w /kommand \
-e HTTP_PROXY=host.docker.internal:6152 \
-e HTTPS_PROXY=host.docker.internal:6152 \
-e ALL_PROXY=host.docker.internal:6153 \
--platform linux/amd64 \
- -m 900m \
+ -m 256m \
--cpus=1 \
- azul/zulu-openjdk:11-latest \
+ ubuntu \
bash
sleep 1
- -docker exec linuxX64Test ./debugTest/test.kexe
+ -docker exec linuxX64Test build/bin/linuxX64/debugTest/test.kexe
docker rm -f linuxX64Test
-linuxArm64Test: linuxArm64
+linuxArm64Test:
+ just link-test linuxArm64
+ # ignore the error exit code
-docker run -itd --name linuxArm64Test \
- -v ./build/bin/linuxArm64:/kommand \
- -v ./eko/target/aarch64-unknown-linux-gnu/release:/kommand/eko/target/release \
+ -v ./build/bin/linuxArm64:/kommand/build/bin/linuxArm64 \
+ -v ./kommand-core/target/aarch64-unknown-linux-gnu/release/kommand-echo:/kommand/kommand-core/target/aarch64-unknown-linux-gnu/release/kommand-echo \
-w /kommand \
-e HTTP_PROXY=host.docker.internal:6152 \
-e HTTPS_PROXY=host.docker.internal:6152 \
-e ALL_PROXY=host.docker.internal:6153 \
--platform linux/arm64 \
- -m 900m \
+ -m 256m \
--cpus=1 \
- azul/zulu-openjdk:11-latest \
+ ubuntu \
bash
sleep 1
- -docker exec linuxArm64Test ./debugTest/test.kexe
+ -docker exec linuxArm64Test build/bin/linuxArm64/debugTest/test.kexe
docker rm -f linuxArm64Test
-macosX64Test: macosX64
- ./gradlew macosX64Test
+macosX64Test:
+ ./gradlew :cleanMacosX64Test :macosX64Test
+ leaks -atExit -- build/bin/macosX64/debugTest/test.kexe
-macosArm64Test: macosArm64
- ./gradlew macosArm64Test
+macosArm64Test:
+ ./gradlew :cleanMacosArm64Test :macosArm64Test
+ leaks -atExit -- build/bin/macosArm64/debugTest/test.kexe
-windowsX64Test: windowsX64
+windowsX64Test:
./gradlew mingwX64Test
+build:
+ cd kommand-core && just all
+
publishToSonatype:
./gradlew publishToSonatype
@@ -72,4 +72,15 @@ closeSonatype:
releaseSonatype:
./gradlew findSonatypeStagingRepository releaseSonatypeStagingRepository
-autoPublish: publishToSonatype closeSonatype releaseSonatype
+autoPublish: build publishToSonatype closeSonatype releaseSonatype
+
+leaks:
+ ./gradlew :cleanMacosX64Test :macosX64Test
+ leaks -atExit -- build/bin/macosX64/debugTest/test.kexe
+
+teamcity:
+ #-v :/opt/teamcity/logs
+ docker run --name teamcity-server-instance \
+ -v ./:/data/teamcity_server/kommand \
+ -p 8111:8111 \
+ jetbrains/teamcity-server
diff --git a/kommand-core/.gitignore b/kommand-core/.gitignore
new file mode 100644
index 0000000..54ba423
--- /dev/null
+++ b/kommand-core/.gitignore
@@ -0,0 +1,2 @@
+/target
+!src/bin
diff --git a/src/mingwX64Test/resources/sub_command/.idea/.gitignore b/kommand-core/.idea/.gitignore
similarity index 100%
rename from src/mingwX64Test/resources/sub_command/.idea/.gitignore
rename to kommand-core/.idea/.gitignore
diff --git a/kommand-core/.idea/kommand-core.iml b/kommand-core/.idea/kommand-core.iml
new file mode 100644
index 0000000..6970db0
--- /dev/null
+++ b/kommand-core/.idea/kommand-core.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/kommand-core/.idea/modules.xml b/kommand-core/.idea/modules.xml
new file mode 100644
index 0000000..2455ebc
--- /dev/null
+++ b/kommand-core/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/mingwX64Test/resources/sub_command/.idea/vcs.xml b/kommand-core/.idea/vcs.xml
similarity index 61%
rename from src/mingwX64Test/resources/sub_command/.idea/vcs.xml
rename to kommand-core/.idea/vcs.xml
index 4fce1d8..6c0b863 100644
--- a/src/mingwX64Test/resources/sub_command/.idea/vcs.xml
+++ b/kommand-core/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/kommand-core/Cargo.lock b/kommand-core/Cargo.lock
new file mode 100644
index 0000000..df10d34
--- /dev/null
+++ b/kommand-core/Cargo.lock
@@ -0,0 +1,1024 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "cbindgen"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faeaa693e5a727975a79211b8f35c0cb09b031fdb6eaa4a788bc6713d01488ca"
+dependencies = [
+ "clap",
+ "heck",
+ "indexmap",
+ "log",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "syn 1.0.109",
+ "tempfile",
+ "toml",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "atty",
+ "bitflags 1.3.2",
+ "clap_lex",
+ "indexmap",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "color-eyre"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
+dependencies = [
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
+]
+
+[[package]]
+name = "color-spantrace"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
+dependencies = [
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
+]
+
+[[package]]
+name = "colored"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6"
+dependencies = [
+ "is-terminal",
+ "lazy_static",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "console"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "eyre"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "filetime"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags 1.3.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "kommand-core"
+version = "0.1.0"
+dependencies = [
+ "cbindgen",
+]
+
+[[package]]
+name = "kommand-echo"
+version = "0.1.0"
+dependencies = [
+ "ansi_term",
+ "colored",
+ "console",
+]
+
+[[package]]
+name = "kommand-watch"
+version = "0.1.0"
+dependencies = [
+ "ansi_term",
+ "color-eyre",
+ "colored",
+ "console",
+ "fs_extra",
+ "lazy_static",
+ "notify-debouncer-mini",
+ "tokio",
+ "walkdir",
+]
+
+[[package]]
+name = "kqueue"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "notify"
+version = "6.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
+dependencies = [
+ "bitflags 2.4.1",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "log",
+ "mio",
+ "walkdir",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "notify-debouncer-mini"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
+dependencies = [
+ "crossbeam-channel",
+ "log",
+ "notify",
+]
+
+[[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tokio"
+version = "1.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+dependencies = [
+ "backtrace",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-error"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
diff --git a/kommand-core/Cargo.toml b/kommand-core/Cargo.toml
new file mode 100644
index 0000000..38522b1
--- /dev/null
+++ b/kommand-core/Cargo.toml
@@ -0,0 +1,30 @@
+[workspace]
+members = [
+ "kommand-watch",
+ "kommand-echo",
+]
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+authors = ["BppleMan"]
+
+[package]
+name = "kommand-core"
+version.workspace = true
+edition.workspace = true
+authors.workspace = true
+
+[lib]
+name = "kommand_core"
+crate-type = ["rlib", "staticlib"]
+
+[dependencies]
+
+[build-dependencies]
+cbindgen = "0.25.0"
+
+[package.metadata.cargo-xbuild]
+memcpy = true
+sysroot_path = "/Users/bppleman/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-pc-windows-gnu:/Users/bppleman/.konan/dependencies/msys2-mingw-w64-x86_64-2/x86_64-w64-mingw32"
+panic_immediate_abort = false
diff --git a/kommand-core/build.rs b/kommand-core/build.rs
new file mode 100644
index 0000000..8d9f93f
--- /dev/null
+++ b/kommand-core/build.rs
@@ -0,0 +1,17 @@
+fn main() {
+ use std::env;
+ println!("PROFILE {:?}", env::var("PROFILE"));
+ println!(
+ "CARGO_CFG_TARGET_ARCH {:?}",
+ env::var("CARGO_CFG_TARGET_ARCH")
+ );
+ let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+
+ cbindgen::Builder::new()
+ .with_crate(crate_dir)
+ .with_no_includes()
+ .with_language(cbindgen::Language::C)
+ .generate()
+ .expect("Unable to generate bindings")
+ .write_to_file("kommand_core.h");
+}
diff --git a/kommand-core/justfile b/kommand-core/justfile
new file mode 100755
index 0000000..de1bcb1
--- /dev/null
+++ b/kommand-core/justfile
@@ -0,0 +1,51 @@
+#!/usr/bin/env just --justfile
+
+prepare:
+ brew install mingw-w64
+ brew tap messense/macos-cross-toolchains
+ brew install x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
+
+leaks:
+ cargo build
+ leaks -atExit -- target/debug/leaks_test
+
+bench:
+ cargo build
+ time target/debug/leaks_test
+
+x86_64-apple-darwin:
+ cargo build --release --package kommand-core --package kommand-echo --target x86_64-apple-darwin
+
+aarch64-apple-darwin:
+ cargo build --release --package kommand-core --package kommand-echo --target aarch64-apple-darwin
+
+macos: x86_64-apple-darwin aarch64-apple-darwin
+
+x86_64-unknown-linux-gnu:
+ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-unknown-linux-gnu-gcc \
+ cargo build --release --package kommand-core --package kommand-echo --target x86_64-unknown-linux-gnu
+
+aarch64-unknown-linux-gnu:
+ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc \
+ cargo build --release --package kommand-core --package kommand-echo --target aarch64-unknown-linux-gnu
+
+linux: x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
+
+x86_64-pc-windows-gnu:
+ cargo build --release --package kommand-core --package kommand-echo --target x86_64-pc-windows-gnu
+
+win: x86_64-pc-windows-gnu
+
+all: macos linux win
+
+watch:
+ cargo build --release --package kommand-watch
+ target/release/kommand-watch
+
+build-kommand-echo:
+ cargo build --package kommand-echo
+ cargo build --release --package kommand-echo
+
+workflow dir:
+ cargo build --release --package kommand-core --package kommand-echo
+ cd target && mkdir {{dir}} && mv ./release ./{{dir}}/release/
diff --git a/kommand-core/kommand-echo/Cargo.toml b/kommand-core/kommand-echo/Cargo.toml
new file mode 100644
index 0000000..41a284b
--- /dev/null
+++ b/kommand-core/kommand-echo/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "kommand-echo"
+version.workspace = true
+edition.workspace = true
+authors.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+colored = "2.0.4"
+console = "0.15.7"
+ansi_term = "0.12.1"
diff --git a/eko/src/main.rs b/kommand-core/kommand-echo/src/main.rs
similarity index 64%
rename from eko/src/main.rs
rename to kommand-core/kommand-echo/src/main.rs
index 223d6c1..f1f4036 100644
--- a/eko/src/main.rs
+++ b/kommand-core/kommand-echo/src/main.rs
@@ -1,3 +1,4 @@
+use colored::Colorize;
use std::env;
use std::io::stdin;
use std::str::FromStr;
@@ -5,13 +6,15 @@ use std::str::FromStr;
fn main() {
let args = env::args().collect::>();
if args.len() == 1 {
- println!("Hello, Kommand!");
+ print!("Hello, Kommand!");
return;
}
let cmd = &args[1];
match cmd {
+ cmd if cmd == "echo" => echo(),
cmd if cmd == "stdout" => stdout(),
cmd if cmd == "stderr" => stderr(),
+ cmd if cmd == "color" => color(),
cmd if cmd == "interval" => interval(
args.get(2)
.map(|s| u32::from_str(s).unwrap_or_else(|_| panic!("{} cannot convert to u32", s)))
@@ -23,12 +26,19 @@ fn main() {
};
}
+fn echo() {
+ let mut line = String::new();
+ if stdin().read_line(&mut line).is_ok() {
+ print!("{}", line);
+ }
+}
+
fn stdout() {
for line in stdin().lines() {
match line {
Ok(line) => println!("{}", line),
Err(_) => {
- eprintln!("Error reading stdin");
+ eprint!("Error reading stdin");
break;
}
}
@@ -38,7 +48,7 @@ fn stdout() {
fn stderr() {
for line in stdin().lines() {
match line {
- Ok(line) => eprintln!("{}", line),
+ Ok(line) => eprint!("{}", line),
Err(_) => break,
}
}
@@ -51,3 +61,11 @@ fn interval(count: u32) {
std::thread::sleep(duration);
}
}
+
+fn color() {
+ colored::control::set_override(true);
+ println!("{}", "Hello, Kommand!".red());
+ println!("{}", "Hello, Kommand!".green());
+ println!("{}", "Hello, Kommand!".blue());
+ colored::control::unset_override();
+}
diff --git a/kommand-core/kommand-watch/Cargo.toml b/kommand-core/kommand-watch/Cargo.toml
new file mode 100644
index 0000000..3711cb1
--- /dev/null
+++ b/kommand-core/kommand-watch/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "kommand-watch"
+version.workspace = true
+edition.workspace = true
+authors.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+notify-debouncer-mini = "0.4.1"
+color-eyre = "0.6.2"
+tokio = { version = "1.35.1", features = ["rt", "signal", "macros"] }
+lazy_static = "1.4.0"
+fs_extra = "1.3.0"
+walkdir = "2.4.0"
+colored = "2.0.4"
+console = "0.15.7"
+ansi_term = "0.12.1"
diff --git a/kommand-core/kommand-watch/src/main.rs b/kommand-core/kommand-watch/src/main.rs
new file mode 100644
index 0000000..26987ba
--- /dev/null
+++ b/kommand-core/kommand-watch/src/main.rs
@@ -0,0 +1,178 @@
+use std::collections::{HashMap, HashSet};
+use std::ffi::OsStr;
+use std::fmt::Display;
+use std::path::Path;
+use std::path::PathBuf;
+use std::time::Duration;
+
+use color_eyre::eyre::Context;
+use color_eyre::owo_colors::OwoColorize;
+use color_eyre::{install, Result};
+use lazy_static::lazy_static;
+use notify_debouncer_mini::notify::RecursiveMode;
+use notify_debouncer_mini::{new_debouncer, DebounceEventResult};
+use walkdir::WalkDir;
+
+lazy_static! {
+ static ref ROOT_DIR: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .canonicalize()
+ .unwrap();
+ static ref MAIN_DIR: PathBuf = ROOT_DIR.join("src/macosX64Main/kotlin/com/kgit2/kommand/wrapper");
+ #[rustfmt::skip]
+ static ref CLUSTER_DIRS: HashMap = HashMap::from([
+ ("macosArm64".to_string(), ROOT_DIR.join("src/macosArm64Main/kotlin/com/kgit2/kommand/wrapper")),
+ ("linuxX64".to_string(), ROOT_DIR.join("src/linuxX64Main/kotlin/com/kgit2/kommand/wrapper")),
+ ("linuxArm64".to_string(), ROOT_DIR.join("src/linuxArm64Main/kotlin/com/kgit2/kommand/wrapper")),
+ ("mingwX64".to_string(), ROOT_DIR.join("src/mingwX64Main/kotlin/com/kgit2/kommand/wrapper")),
+ ]);
+}
+
+#[tokio::main(flavor = "current_thread")]
+async fn main() -> Result<()> {
+ install()?;
+ #[cfg(windows)]
+ ansi_term::enable_ansi_support().unwrap();
+
+ sync_dir(MAIN_DIR.as_path());
+ let mut debouncer = new_debouncer(Duration::from_millis(500), process_file_change)?;
+ format!("Watching {}", MAIN_DIR.display())
+ .blue()
+ .bold()
+ .print();
+ debouncer
+ .watcher()
+ .watch(&MAIN_DIR, RecursiveMode::Recursive)?;
+
+ tokio::signal::ctrl_c().await?;
+ Ok(())
+}
+
+fn process_file_change(result: DebounceEventResult) {
+ match result {
+ Ok(result) => {
+ result
+ .into_iter()
+ .filter(|e| e.path.extension() == Some(OsStr::new("kt")))
+ .for_each(|e| {
+ let path = e.path;
+ if path.exists() {
+ if path.is_file() {
+ sync_file(path);
+ } else {
+ sync_dir(path);
+ }
+ } else {
+ sync_remove(path);
+ }
+ });
+ }
+ Err(error) => {
+ format!("{}", error).red().eprint();
+ }
+ }
+}
+
+fn sync_file(path: impl AsRef) {
+ format!("File changed {:?}", path.as_ref()).green().print();
+ let content = fs_extra::file::read_to_string(path.as_ref()).unwrap();
+ let striped = path
+ .as_ref()
+ .strip_prefix(MAIN_DIR.as_path())
+ .with_context(|| "Strip Error")
+ .unwrap();
+ CLUSTER_DIRS.iter().for_each(|(name, cluster_dir)| {
+ let new_path = cluster_dir.join(striped.to_string_lossy().replace("macosX64", name));
+ format!(" Will write to {:?}", new_path).yellow().print();
+ if !new_path.exists() {
+ fs_extra::dir::create_all(new_path.parent().unwrap(), false).unwrap();
+ }
+ fs_extra::file::write_all(&new_path, &content).unwrap();
+ });
+}
+
+fn sync_dir(path: impl AsRef) {
+ format!("Dir changed {:?}", path.as_ref()).green().print();
+ let main_files = collect_dir(MAIN_DIR.as_path());
+ let main_set = main_files
+ .iter()
+ .map(|file| {
+ file.strip_prefix(MAIN_DIR.as_path())
+ .unwrap()
+ .to_string_lossy()
+ .to_string()
+ })
+ .collect::>();
+ CLUSTER_DIRS.iter().for_each(|(name, cluster_dir)| {
+ let cluster_files = collect_dir(cluster_dir.as_path());
+ let cluster_set = cluster_files
+ .iter()
+ .map(|file| {
+ let file = file.strip_prefix(cluster_dir.as_path()).unwrap();
+ if let Some(file_name) = file.file_name() {
+ file_name.to_string_lossy().replace(name, "macosX64")
+ } else {
+ file.to_string_lossy().to_string()
+ }
+ })
+ .collect::>();
+ let diff = cluster_set.difference(&main_set);
+ diff.for_each(|path| sync_remove(MAIN_DIR.join(path)));
+ #[rustfmt::skip]
+ main_set.iter().for_each(|path| sync_file(MAIN_DIR.join(path)));
+ });
+}
+
+fn sync_remove(path: impl AsRef) {
+ format!("Item removed {:?}", path.as_ref()).green().print();
+ let striped = path
+ .as_ref()
+ .strip_prefix(MAIN_DIR.as_path())
+ .with_context(|| "Strip Error")
+ .unwrap();
+ CLUSTER_DIRS.iter().for_each(|(name, cluster_dir)| {
+ let new_path = cluster_dir.join(striped.to_string_lossy().replace("macosX64", name));
+ if new_path.exists() {
+ format!(" Will remove {:?}", new_path).red().print();
+ if new_path.is_file() {
+ fs_extra::file::remove(&new_path).unwrap();
+ } else if new_path.is_dir() {
+ fs_extra::dir::remove(&new_path).unwrap();
+ } else if new_path.is_symlink() {
+ fs_extra::file::remove(&new_path).unwrap();
+ }
+ } else {
+ format!(" Will not remove (not exist) {:?}", new_path)
+ .magenta()
+ .eprint();
+ }
+ });
+}
+
+fn collect_dir(path: impl AsRef) -> HashSet {
+ let walk_dir = WalkDir::new(path.as_ref()).min_depth(1);
+ walk_dir
+ .into_iter()
+ .flatten()
+ .map(|entry| entry.into_path())
+ .collect::>()
+}
+
+pub trait Print {
+ fn print(&self);
+
+ fn eprint(&self);
+}
+
+impl Print for T {
+ fn print(&self) {
+ println!("{}", self);
+ }
+
+ fn eprint(&self) {
+ eprintln!("{}", self);
+ }
+}
diff --git a/kommand-core/kommand_core.h b/kommand-core/kommand_core.h
new file mode 100644
index 0000000..125d1bf
--- /dev/null
+++ b/kommand-core/kommand_core.h
@@ -0,0 +1,239 @@
+typedef enum ErrorType {
+ None,
+ Io,
+ Utf8,
+ Unknown,
+} ErrorType;
+
+typedef enum Stdio {
+ Inherit,
+ Null,
+ Pipe,
+} Stdio;
+
+typedef struct EnvVars {
+ char **names;
+ char **values;
+ unsigned long long len;
+} EnvVars;
+
+typedef struct VoidResult {
+ void *ok;
+ char *err;
+ enum ErrorType error_type;
+} VoidResult;
+
+typedef struct UnitResult {
+ int ok;
+ char *err;
+ enum ErrorType error_type;
+} UnitResult;
+
+typedef struct IntResult {
+ int ok;
+ char *err;
+ enum ErrorType error_type;
+} IntResult;
+
+typedef struct Output {
+ int exit_code;
+ char *stdout_content;
+ char *stderr_content;
+} Output;
+
+/**
+ * # Safety
+ */
+char *env_var(const char *name);
+
+struct EnvVars env_vars(void);
+
+void drop_env_vars(struct EnvVars env_vars);
+
+/**
+ * # Safety
+ * Will drop the string
+ */
+void drop_string(char *string);
+
+char *void_to_string(void *ptr);
+
+/**
+ * # Safety
+ */
+struct VoidResult read_line_stdout(const void *reader, unsigned long long *size);
+
+/**
+ * # Safety
+ */
+struct VoidResult read_all_stdout(const void *reader, unsigned long long *size);
+
+/**
+ * # Safety
+ */
+struct VoidResult read_line_stderr(const void *reader, unsigned long long *size);
+
+/**
+ * # Safety
+ */
+struct VoidResult read_all_stderr(const void *reader, unsigned long long *size);
+
+/**
+ * # Safety
+ */
+struct UnitResult write_line_stdin(const void *writer, const char *line);
+
+struct UnitResult flush_stdin(const void *writer);
+
+void drop_stderr(void *reader);
+
+void drop_stdin(void *reader);
+
+void drop_stdout(void *reader);
+
+void *buffered_stdin_child(const void *child);
+
+void *buffered_stdout_child(const void *child);
+
+void *buffered_stderr_child(const void *child);
+
+struct UnitResult kill_child(const void *child);
+
+unsigned int id_child(const void *child);
+
+struct IntResult wait_child(const void *child);
+
+/**
+ * The returned [Output] will empty
+ * if convert the [Child.stdout] and [Child.stderr] to buffered
+ * with [buffered_stdout_child] and [buffered_stderr_child]
+ */
+struct VoidResult wait_with_output_child(void *child);
+
+void drop_child(void *child);
+
+/**
+ * # Safety
+ * Will not move the [name]'s ownership
+ * You must drop the command with [drop_command]
+ *
+ * ```rust
+ * use kommand_core::ffi_util::as_cstring;
+ * use kommand_core::process::{drop_command, new_command};
+ * unsafe {
+ * let command = new_command(as_cstring("pwd").as_ptr());
+ * drop_command(command);
+ * }
+ * ```
+ */
+void *new_command(const char *name);
+
+/**
+ * ```rust
+ * use kommand_core::ffi_util::{as_cstring, drop_string};
+ * use kommand_core::process::{display_command, drop_command, new_command};
+ * unsafe {
+ * let command = new_command(as_cstring("pwd").as_ptr());
+ * let display = display_command(command);
+ * drop_string(display);
+ * drop_command(command);
+ * }
+ * ```
+ */
+char *display_command(const void *command);
+
+/**
+ * ```rust
+ * use kommand_core::ffi_util::{as_cstring, drop_string};
+ * use kommand_core::process::{debug_command, drop_command, new_command};
+ * unsafe {
+ * let command = new_command(as_cstring("pwd").as_ptr());
+ * let debug = debug_command(command);
+ * drop_string(debug);
+ * drop_command(command);
+ * }
+ * ```
+ */
+char *debug_command(const void *command);
+
+/**
+ * ```rust
+ * use kommand_core::ffi_util::as_cstring;
+ * use kommand_core::process::{drop_command, new_command};
+ * unsafe {
+ * let command = new_command(as_cstring("pwd").as_ptr());
+ * drop_command(command);
+ * }
+ * ```
+ */
+void drop_command(void *command);
+
+/**
+ * # Safety
+ * Will not take over ownership of arg
+ *
+ * ```rust
+ * use kommand_core::ffi_util::as_cstring;
+ * use kommand_core::process::{arg_command, drop_command, new_command};
+ * unsafe {
+ * let command = new_command(as_cstring("ls").as_ptr());
+ * arg_command(command, as_cstring("-l").as_ptr());
+ * drop_command(command);
+ * }
+ * ```
+ */
+void arg_command(const void *command, const char *arg);
+
+/**
+ * # Safety
+ * Will not take over ownership of key & value
+ *
+ * ```rust
+ * use kommand_core::ffi_util::as_cstring;
+ * use kommand_core::process::{arg_command, drop_command, env_command, new_command};
+ * unsafe {
+ * let command = new_command(as_cstring("echo").as_ptr());
+ * arg_command(command, as_cstring("$KOMMAND").as_ptr());
+ * env_command(command, as_cstring("KOMMAND").as_ptr(), as_cstring("kommand").as_ptr());
+ * drop_command(command);
+ * }
+ * ```
+ */
+void env_command(const void *command, const char *key, const char *value);
+
+/**
+ * # Safety
+ * Will not drop the key
+ */
+void remove_env_command(const void *command, const char *key);
+
+void env_clear_command(const void *command);
+
+/**
+ * # Safety
+ * Will not drop the path
+ */
+void current_dir_command(const void *command, const char *path);
+
+void stdin_command(const void *command, enum Stdio stdio);
+
+void stdout_command(const void *command, enum Stdio stdio);
+
+void stderr_command(const void *command, enum Stdio stdio);
+
+struct VoidResult spawn_command(const void *command);
+
+struct VoidResult output_command(const void *command);
+
+struct IntResult status_command(const void *command);
+
+/**
+ * [Command::get_program] returns a [OsString] which is not compatible with C.
+ * unix-like: OsString is vec
+ * windows: OsString is vec
+ */
+char *get_program_command(const void *command);
+
+struct Output into_output(void *ptr);
+
+void drop_output(struct Output output);
diff --git a/kommand-core/rust-toolchain.toml b/kommand-core/rust-toolchain.toml
new file mode 100644
index 0000000..0ff3080
--- /dev/null
+++ b/kommand-core/rust-toolchain.toml
@@ -0,0 +1,3 @@
+[toolchain]
+channel = "1.69.0"
+targets = ["x86_64-apple-darwin", "aarch64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "x86_64-pc-windows-gnu"]
diff --git a/kommand-core/src/bin/leaks_test.rs b/kommand-core/src/bin/leaks_test.rs
new file mode 100644
index 0000000..645a19f
--- /dev/null
+++ b/kommand-core/src/bin/leaks_test.rs
@@ -0,0 +1,48 @@
+use std::ffi::{c_char, c_ulonglong};
+
+use kommand_core::ffi_util::{as_cstring, into_string};
+use kommand_core::io::{drop_stdout, read_line_stdout};
+use kommand_core::process::{buffered_stdout_child, Stdio};
+use kommand_core::process::{drop_child, wait_child};
+use kommand_core::process::{drop_command, new_command, spawn_command, stdout_command};
+
+fn main() {
+ unsafe {
+ (0..1).for_each(|i| {
+ let command = new_command(as_cstring("target/debug/kommand-echo").as_ptr());
+ // arg_command(command, as_cstring("echo").as_ptr());
+ stdout_command(command, Stdio::Pipe);
+ let result = spawn_command(command);
+ let child = if !result.ok.is_null() {
+ result.ok
+ } else {
+ drop_command(command);
+ panic!("{}", into_string(result.err))
+ };
+
+ let stdout = buffered_stdout_child(child);
+ if stdout.is_null() {
+ panic!("stdout is null");
+ }
+ loop {
+ let mut size = 0u64;
+ let result = read_line_stdout(stdout, (&mut size) as *mut c_ulonglong);
+ println!("size: {}", size);
+ let line = if !result.ok.is_null() {
+ into_string(result.ok as *mut c_char)
+ } else {
+ panic!("{}", into_string(result.err))
+ };
+ println!("[{i}] line {}", line);
+ if line.is_empty() {
+ break;
+ }
+ }
+
+ wait_child(child);
+ drop_stdout(stdout);
+ drop_child(child);
+ drop_command(command);
+ });
+ }
+}
diff --git a/kommand-core/src/bin/normal_test.rs b/kommand-core/src/bin/normal_test.rs
new file mode 100644
index 0000000..bb68c8d
--- /dev/null
+++ b/kommand-core/src/bin/normal_test.rs
@@ -0,0 +1,14 @@
+use std::io::{stdout, Write};
+use std::process::{Command, Stdio};
+
+fn main() {
+ println!("{:?}", std::env::current_dir());
+ (0..1).for_each(|_| {
+ let mut command = Command::new("target/debug/kommand-echo");
+ command.arg("interval").stdout(Stdio::inherit());
+ let child = command.spawn().unwrap();
+ let output = child.wait_with_output().unwrap();
+ println!("output");
+ stdout().write_all(&output.stdout).unwrap();
+ });
+}
diff --git a/kommand-core/src/bin/temp.rs b/kommand-core/src/bin/temp.rs
new file mode 100644
index 0000000..fb11f8c
--- /dev/null
+++ b/kommand-core/src/bin/temp.rs
@@ -0,0 +1,10 @@
+use std::process::Command;
+
+fn main() {
+ println!("{:?}", std::env::current_dir());
+ let output = Command::new("target/debug/kommand-echo")
+ .arg("color")
+ .output()
+ .unwrap();
+ println!("{:?}", output);
+}
diff --git a/kommand-core/src/env.rs b/kommand-core/src/env.rs
new file mode 100644
index 0000000..fb96a36
--- /dev/null
+++ b/kommand-core/src/env.rs
@@ -0,0 +1,53 @@
+use crate::ffi_util::{as_string, into_cstring, into_string};
+use std::ffi::{c_char, c_ulonglong};
+
+#[repr(C)]
+pub struct EnvVars {
+ pub names: *mut *mut c_char,
+ pub values: *mut *mut c_char,
+ pub len: c_ulonglong,
+}
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn env_var(name: *const c_char) -> *mut c_char {
+ let name = as_string(name);
+ into_cstring(std::env::var(name).unwrap_or_default())
+}
+
+#[no_mangle]
+pub extern "C" fn env_vars() -> EnvVars {
+ let (mut names, mut values) = std::env::vars().fold(
+ (Vec::new(), Vec::new()),
+ |(mut names, mut values), (name, value)| {
+ names.push(into_cstring(name));
+ values.push(into_cstring(value));
+ (names, values)
+ },
+ );
+ let env_vars = EnvVars {
+ names: names.as_mut_ptr(),
+ values: values.as_mut_ptr(),
+ len: names.len() as u64,
+ };
+ std::mem::forget(names);
+ std::mem::forget(values);
+ env_vars
+}
+
+#[no_mangle]
+pub extern "C" fn drop_env_vars(env_vars: EnvVars) {
+ unsafe {
+ let len = env_vars.len as usize;
+ Vec::from_raw_parts(env_vars.names, len, len)
+ .into_iter()
+ .for_each(|name| {
+ into_string(name);
+ });
+ Vec::from_raw_parts(env_vars.values, len, len)
+ .into_iter()
+ .for_each(|value| {
+ into_string(value);
+ });
+ }
+}
diff --git a/kommand-core/src/ffi_util.rs b/kommand-core/src/ffi_util.rs
new file mode 100644
index 0000000..23b5517
--- /dev/null
+++ b/kommand-core/src/ffi_util.rs
@@ -0,0 +1,50 @@
+use std::ffi::{c_char, c_void, CStr, CString};
+
+/// # Safety
+pub unsafe fn as_string(ptr: *const c_char) -> String {
+ assert!(!ptr.is_null(), "char ptr is null");
+ CStr::from_ptr(ptr)
+ .to_str()
+ .expect("convert char ptr to CStr failed")
+ .to_string()
+}
+
+/// # Safety
+pub unsafe fn into_string(ptr: *mut c_char) -> String {
+ assert!(!ptr.is_null(), "char ptr is null");
+ CString::from_raw(ptr)
+ .into_string()
+ .expect("convert char ptr to CString failed")
+}
+
+pub fn as_cstring(str: impl AsRef) -> CString {
+ CString::new(str.as_ref()).expect("convert to CString failed")
+}
+
+pub fn into_cstring(str: impl AsRef) -> *mut c_char {
+ CString::new(str.as_ref())
+ .expect("convert to CString failed")
+ .into_raw()
+}
+
+/// # Safety
+/// Will drop the string
+#[no_mangle]
+pub unsafe extern "C" fn drop_string(string: *mut c_char) {
+ if string.is_null() {
+ return;
+ }
+ into_string(string);
+}
+
+pub fn into_void(object: T) -> *mut c_void {
+ Box::into_raw(Box::new(object)) as *mut c_void
+}
+
+#[no_mangle]
+pub extern "C" fn void_to_string(ptr: *mut c_void) -> *mut c_char {
+ if ptr.is_null() {
+ return std::ptr::null_mut();
+ }
+ ptr as *mut c_char
+}
diff --git a/kommand-core/src/io.rs b/kommand-core/src/io.rs
new file mode 100644
index 0000000..522be3b
--- /dev/null
+++ b/kommand-core/src/io.rs
@@ -0,0 +1,103 @@
+use crate::ffi_util::as_string;
+use std::ffi::{c_char, c_ulonglong, c_void};
+use std::io::{BufRead, Read, Write};
+
+use crate::io::stdout::as_stdout_mut;
+use crate::result::{UnitResult, VoidResult};
+
+mod stderr;
+mod stdin;
+mod stdout;
+
+pub use stderr::drop_stderr;
+pub use stdin::drop_stdin;
+pub use stdout::drop_stdout;
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn read_line_stdout(
+ mut reader: *const c_void,
+ size: *mut c_ulonglong,
+) -> VoidResult {
+ let reader = as_stdout_mut(&mut reader);
+ let mut line = String::new();
+ reader
+ .read_line(&mut line)
+ .map(|len| {
+ unsafe { *size = len as c_ulonglong };
+ line.trim_end_matches('\n').to_string()
+ })
+ .into()
+}
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn read_all_stdout(
+ mut reader: *const c_void,
+ size: *mut c_ulonglong,
+) -> VoidResult {
+ let reader = as_stdout_mut(&mut reader);
+ let mut line = String::new();
+ reader
+ .read_to_string(&mut line)
+ .map(|len| {
+ unsafe { *size = len as c_ulonglong };
+ line
+ })
+ .into()
+}
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn read_line_stderr(
+ mut reader: *const c_void,
+ size: *mut c_ulonglong,
+) -> VoidResult {
+ let reader = stderr::as_stderr_mut(&mut reader);
+ let mut line = String::new();
+ reader
+ .read_line(&mut line)
+ .map(|len| {
+ unsafe { *size = len as c_ulonglong };
+ line.trim_end_matches('\n').to_string()
+ })
+ .into()
+}
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn read_all_stderr(
+ mut reader: *const c_void,
+ size: *mut c_ulonglong,
+) -> VoidResult {
+ let reader = stderr::as_stderr_mut(&mut reader);
+ let mut line = String::new();
+ reader
+ .read_to_string(&mut line)
+ .map(|len| {
+ unsafe { *size = len as c_ulonglong };
+ line
+ })
+ .into()
+}
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn write_line_stdin(
+ mut writer: *const c_void,
+ line: *const c_char,
+) -> UnitResult {
+ let writer = stdin::as_stdin_mut(&mut writer);
+ let line = as_string(line);
+ let result = writer
+ .write_all(line.as_bytes())
+ .and_then(|_| writer.write(b"\n"))
+ .map(|_| ());
+ result.into()
+}
+
+#[no_mangle]
+pub extern "C" fn flush_stdin(mut writer: *const c_void) -> UnitResult {
+ let writer = stdin::as_stdin_mut(&mut writer);
+ writer.flush().into()
+}
diff --git a/kommand-core/src/io/stderr.rs b/kommand-core/src/io/stderr.rs
new file mode 100644
index 0000000..6c77279
--- /dev/null
+++ b/kommand-core/src/io/stderr.rs
@@ -0,0 +1,19 @@
+use std::ffi::c_void;
+use std::io::BufReader;
+use std::process::ChildStderr;
+
+pub fn as_stderr_mut(reader: &mut *const c_void) -> &mut BufReader {
+ unsafe { &mut *(*reader as *mut BufReader) }
+}
+
+pub fn into_stderr(reader: *mut c_void) -> BufReader {
+ unsafe { *Box::from_raw(reader as *mut BufReader) }
+}
+
+#[no_mangle]
+pub extern "C" fn drop_stderr(reader: *mut c_void) {
+ if reader.is_null() {
+ return;
+ }
+ into_stderr(reader);
+}
diff --git a/kommand-core/src/io/stdin.rs b/kommand-core/src/io/stdin.rs
new file mode 100644
index 0000000..13b5288
--- /dev/null
+++ b/kommand-core/src/io/stdin.rs
@@ -0,0 +1,19 @@
+use std::ffi::c_void;
+use std::io::BufWriter;
+use std::process::ChildStdin;
+
+pub fn as_stdin_mut(reader: &mut *const c_void) -> &mut BufWriter {
+ unsafe { &mut *(*reader as *mut BufWriter) }
+}
+
+pub fn into_stdin(reader: *mut c_void) -> BufWriter {
+ unsafe { *Box::from_raw(reader as *mut BufWriter) }
+}
+
+#[no_mangle]
+pub extern "C" fn drop_stdin(reader: *mut c_void) {
+ if reader.is_null() {
+ return;
+ }
+ into_stdin(reader);
+}
diff --git a/kommand-core/src/io/stdout.rs b/kommand-core/src/io/stdout.rs
new file mode 100644
index 0000000..100b7d1
--- /dev/null
+++ b/kommand-core/src/io/stdout.rs
@@ -0,0 +1,19 @@
+use std::ffi::c_void;
+use std::io::BufReader;
+use std::process::ChildStdout;
+
+pub fn as_stdout_mut(reader: &mut *const c_void) -> &mut BufReader {
+ unsafe { &mut *(*reader as *mut BufReader) }
+}
+
+pub fn into_stdout(reader: *mut c_void) -> BufReader {
+ unsafe { *Box::from_raw(reader as *mut BufReader) }
+}
+
+#[no_mangle]
+pub extern "C" fn drop_stdout(reader: *mut c_void) {
+ if reader.is_null() {
+ return;
+ }
+ into_stdout(reader);
+}
diff --git a/kommand-core/src/lib.rs b/kommand-core/src/lib.rs
new file mode 100644
index 0000000..6cb3373
--- /dev/null
+++ b/kommand-core/src/lib.rs
@@ -0,0 +1,9 @@
+//! This is a [std::process::Command] wrapper for ffi
+//!
+//! Export the ffi interface through [cbindgen] and use it in kotlin/native
+
+pub mod env;
+pub mod ffi_util;
+pub mod io;
+pub mod process;
+pub mod result;
diff --git a/kommand-core/src/process.rs b/kommand-core/src/process.rs
new file mode 100644
index 0000000..87e9a30
--- /dev/null
+++ b/kommand-core/src/process.rs
@@ -0,0 +1,9 @@
+mod child;
+mod kommand;
+mod output;
+mod stdio;
+
+pub use child::*;
+pub use kommand::*;
+pub use output::*;
+pub use stdio::*;
diff --git a/kommand-core/src/process/child.rs b/kommand-core/src/process/child.rs
new file mode 100644
index 0000000..a7939f3
--- /dev/null
+++ b/kommand-core/src/process/child.rs
@@ -0,0 +1,89 @@
+use crate::ffi_util::into_void;
+use crate::process::Output;
+use crate::result::{ErrorType, IntResult, UnitResult, VoidResult};
+use std::ffi::{c_uint, c_void};
+use std::io::{BufReader, BufWriter};
+use std::process::Child;
+
+pub fn as_child(command_ptr: &*const c_void) -> &Child {
+ unsafe { &*(*command_ptr as *const Child) }
+}
+
+pub fn as_child_mut(command_ptr: &mut *const c_void) -> &mut Child {
+ unsafe { &mut *(*command_ptr as *mut Child) }
+}
+
+pub fn into_child(command_ptr: *mut c_void) -> Child {
+ unsafe { *Box::from_raw(command_ptr as *mut Child) }
+}
+
+#[no_mangle]
+pub extern "C" fn buffered_stdin_child(mut child: *const c_void) -> *mut c_void {
+ let child = as_child_mut(&mut child);
+ child
+ .stdin
+ .take()
+ .map(BufWriter::new)
+ .map(into_void)
+ .unwrap_or(std::ptr::null_mut())
+}
+
+#[no_mangle]
+pub extern "C" fn buffered_stdout_child(mut child: *const c_void) -> *mut c_void {
+ let child = as_child_mut(&mut child);
+ child
+ .stdout
+ .take()
+ .map(BufReader::new)
+ .map(into_void)
+ .unwrap_or(std::ptr::null_mut())
+}
+
+#[no_mangle]
+pub extern "C" fn buffered_stderr_child(mut child: *const c_void) -> *mut c_void {
+ let child = as_child_mut(&mut child);
+ child
+ .stderr
+ .take()
+ .map(BufReader::new)
+ .map(into_void)
+ .unwrap_or(std::ptr::null_mut())
+}
+
+#[no_mangle]
+pub extern "C" fn kill_child(mut child: *const c_void) -> UnitResult {
+ let child = as_child_mut(&mut child);
+ child.kill().into()
+}
+
+#[no_mangle]
+pub extern "C" fn id_child(child: *const c_void) -> c_uint {
+ let child = as_child(&child);
+ child.id()
+}
+
+#[no_mangle]
+pub extern "C" fn wait_child(mut child: *const c_void) -> IntResult {
+ let child = as_child_mut(&mut child);
+ child.wait().into()
+}
+
+/// The returned [Output] will empty
+/// if convert the [Child.stdout] and [Child.stderr] to buffered
+/// with [buffered_stdout_child] and [buffered_stderr_child]
+#[no_mangle]
+pub extern "C" fn wait_with_output_child(child: *mut c_void) -> VoidResult {
+ if child.is_null() {
+ return VoidResult::error("Child has been consumed", ErrorType::None);
+ }
+ let child = into_child(child);
+ child.wait_with_output().map(Output::from).into()
+}
+
+#[no_mangle]
+pub extern "C" fn drop_child(child: *mut c_void) {
+ if child.is_null() {
+ return;
+ }
+ into_child(child);
+}
diff --git a/kommand-core/src/process/kommand.rs b/kommand-core/src/process/kommand.rs
new file mode 100644
index 0000000..6da2c44
--- /dev/null
+++ b/kommand-core/src/process/kommand.rs
@@ -0,0 +1,211 @@
+use std::ffi::{c_char, c_void};
+use std::process::Command;
+
+use crate::ffi_util::{as_string, into_cstring, into_void};
+use crate::process::Output;
+use crate::process::Stdio;
+use crate::result::{IntResult, VoidResult};
+
+pub fn as_command(command_ptr: &*const c_void) -> &Command {
+ unsafe { &*(*command_ptr as *const Command) }
+}
+
+pub fn as_command_mut(command_ptr: &mut *const c_void) -> &mut Command {
+ unsafe { &mut *(*command_ptr as *mut Command) }
+}
+
+pub fn into_command(command_ptr: *mut c_void) -> Command {
+ unsafe { *Box::from_raw(command_ptr as *mut Command) }
+}
+
+/// # Safety
+/// Will not move the [name]'s ownership
+/// You must drop the command with [drop_command]
+///
+/// ```rust
+/// use kommand_core::ffi_util::as_cstring;
+/// use kommand_core::process::{drop_command, new_command};
+/// unsafe {
+/// let command = new_command(as_cstring("pwd").as_ptr());
+/// drop_command(command);
+/// }
+/// ```
+#[no_mangle]
+pub unsafe extern "C" fn new_command(name: *const c_char) -> *mut c_void {
+ let name = as_string(name);
+ let command = Command::new(name);
+ into_void(command)
+}
+
+/// ```rust
+/// use kommand_core::ffi_util::{as_cstring, drop_string};
+/// use kommand_core::process::{display_command, drop_command, new_command};
+/// unsafe {
+/// let command = new_command(as_cstring("pwd").as_ptr());
+/// let display = display_command(command);
+/// drop_string(display);
+/// drop_command(command);
+/// }
+/// ```
+#[no_mangle]
+pub extern "C" fn display_command(command: *const c_void) -> *mut c_char {
+ let command = as_command(&command);
+ into_cstring(format!("{:?}", command))
+}
+
+/// ```rust
+/// use kommand_core::ffi_util::{as_cstring, drop_string};
+/// use kommand_core::process::{debug_command, drop_command, new_command};
+/// unsafe {
+/// let command = new_command(as_cstring("pwd").as_ptr());
+/// let debug = debug_command(command);
+/// drop_string(debug);
+/// drop_command(command);
+/// }
+/// ```
+#[no_mangle]
+pub extern "C" fn debug_command(command: *const c_void) -> *mut c_char {
+ let command = as_command(&command);
+ into_cstring(format!("{:#?}", command))
+}
+
+/// ```rust
+/// use kommand_core::ffi_util::as_cstring;
+/// use kommand_core::process::{drop_command, new_command};
+/// unsafe {
+/// let command = new_command(as_cstring("pwd").as_ptr());
+/// drop_command(command);
+/// }
+/// ```
+#[no_mangle]
+pub extern "C" fn drop_command(command: *mut c_void) {
+ if command.is_null() {
+ return;
+ }
+ into_command(command);
+}
+
+/// # Safety
+/// Will not take over ownership of arg
+///
+/// ```rust
+/// use kommand_core::ffi_util::as_cstring;
+/// use kommand_core::process::{arg_command, drop_command, new_command};
+/// unsafe {
+/// let command = new_command(as_cstring("ls").as_ptr());
+/// arg_command(command, as_cstring("-l").as_ptr());
+/// drop_command(command);
+/// }
+/// ```
+#[no_mangle]
+pub unsafe extern "C" fn arg_command(mut command: *const c_void, arg: *const c_char) {
+ let command = as_command_mut(&mut command);
+ let arg = as_string(arg);
+ command.arg(arg);
+}
+
+/// # Safety
+/// Will not take over ownership of key & value
+///
+/// ```rust
+/// use kommand_core::ffi_util::as_cstring;
+/// use kommand_core::process::{arg_command, drop_command, env_command, new_command};
+/// unsafe {
+/// let command = new_command(as_cstring("echo").as_ptr());
+/// arg_command(command, as_cstring("$KOMMAND").as_ptr());
+/// env_command(command, as_cstring("KOMMAND").as_ptr(), as_cstring("kommand").as_ptr());
+/// drop_command(command);
+/// }
+/// ```
+#[no_mangle]
+pub unsafe extern "C" fn env_command(
+ mut command: *const c_void,
+ key: *const c_char,
+ value: *const c_char,
+) {
+ let command = as_command_mut(&mut command);
+ let key = as_string(key);
+ let value = as_string(value);
+ command.env(key, value);
+}
+
+/// # Safety
+/// Will not drop the key
+#[no_mangle]
+pub unsafe extern "C" fn remove_env_command(mut command: *const c_void, key: *const c_char) {
+ let command = as_command_mut(&mut command);
+ let key = as_string(key);
+ command.env_remove(key);
+}
+
+#[no_mangle]
+pub extern "C" fn env_clear_command(mut command: *const c_void) {
+ let command = as_command_mut(&mut command);
+ command.env_clear();
+}
+
+/// # Safety
+/// Will not drop the path
+#[no_mangle]
+pub unsafe extern "C" fn current_dir_command(mut command: *const c_void, path: *const c_char) {
+ let command = as_command_mut(&mut command);
+ let path = as_string(path);
+ command.current_dir(path);
+}
+
+#[no_mangle]
+pub extern "C" fn stdin_command(mut command: *const c_void, stdio: Stdio) {
+ let command = as_command_mut(&mut command);
+ match stdio {
+ Stdio::Inherit => command.stdin(std::process::Stdio::inherit()),
+ Stdio::Null => command.stdin(std::process::Stdio::null()),
+ Stdio::Pipe => command.stdin(std::process::Stdio::piped()),
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn stdout_command(mut command: *const c_void, stdio: Stdio) {
+ let command = as_command_mut(&mut command);
+ match stdio {
+ Stdio::Inherit => command.stdout(std::process::Stdio::inherit()),
+ Stdio::Null => command.stdout(std::process::Stdio::null()),
+ Stdio::Pipe => command.stdout(std::process::Stdio::piped()),
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn stderr_command(mut command: *const c_void, stdio: Stdio) {
+ let command = as_command_mut(&mut command);
+ match stdio {
+ Stdio::Inherit => command.stderr(std::process::Stdio::inherit()),
+ Stdio::Null => command.stderr(std::process::Stdio::null()),
+ Stdio::Pipe => command.stderr(std::process::Stdio::piped()),
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn spawn_command(mut command: *const c_void) -> VoidResult {
+ let command = as_command_mut(&mut command);
+ command.spawn().into()
+}
+
+#[no_mangle]
+pub extern "C" fn output_command(mut command: *const c_void) -> VoidResult {
+ let command = as_command_mut(&mut command);
+ command.output().map(Output::from).into()
+}
+
+#[no_mangle]
+pub extern "C" fn status_command(mut command: *const c_void) -> IntResult {
+ let command = as_command_mut(&mut command);
+ command.status().into()
+}
+
+/// [Command::get_program] returns a [OsString] which is not compatible with C.
+/// unix-like: OsString is vec
+/// windows: OsString is vec
+#[no_mangle]
+pub extern "C" fn get_program_command(command: *const c_void) -> *mut c_char {
+ let command = as_command(&command);
+ into_cstring(command.get_program().to_string_lossy())
+}
diff --git a/kommand-core/src/process/output.rs b/kommand-core/src/process/output.rs
new file mode 100644
index 0000000..730ba4f
--- /dev/null
+++ b/kommand-core/src/process/output.rs
@@ -0,0 +1,42 @@
+use std::ffi::{c_char, c_void};
+use std::os::raw::c_int;
+
+use crate::ffi_util::{into_cstring, into_string};
+
+#[repr(C)]
+pub struct Output {
+ pub exit_code: c_int,
+ pub stdout_content: *mut c_char,
+ pub stderr_content: *mut c_char,
+}
+
+#[no_mangle]
+pub extern "C" fn into_output(ptr: *mut c_void) -> Output {
+ unsafe { *Box::from_raw(ptr as *mut Output) }
+}
+
+#[no_mangle]
+pub extern "C" fn drop_output(output: Output) {
+ let stdout = output.stdout_content;
+ if !stdout.is_null() {
+ let _ = unsafe { into_string(stdout) };
+ }
+ let stderr = output.stderr_content;
+ if !stderr.is_null() {
+ let _ = unsafe { into_string(stderr) };
+ }
+}
+
+impl From for Output {
+ fn from(value: std::process::Output) -> Self {
+ let exit_code = value.status.code().unwrap_or(-1);
+ let stdout = String::from_utf8_lossy(&value.stdout).to_string();
+ let stderr = String::from_utf8_lossy(&value.stderr).to_string();
+
+ Output {
+ exit_code,
+ stdout_content: into_cstring(stdout),
+ stderr_content: into_cstring(stderr),
+ }
+ }
+}
diff --git a/kommand-core/src/process/stdio.rs b/kommand-core/src/process/stdio.rs
new file mode 100644
index 0000000..fe3c6de
--- /dev/null
+++ b/kommand-core/src/process/stdio.rs
@@ -0,0 +1,7 @@
+#[repr(C)]
+#[derive(Debug)]
+pub enum Stdio {
+ Inherit,
+ Null,
+ Pipe,
+}
diff --git a/kommand-core/src/result.rs b/kommand-core/src/result.rs
new file mode 100644
index 0000000..7744a62
--- /dev/null
+++ b/kommand-core/src/result.rs
@@ -0,0 +1,137 @@
+use crate::ffi_util::{into_cstring, into_void};
+use std::ffi::{c_char, c_int, c_void};
+use std::fmt::Display;
+use std::io;
+
+#[repr(C)]
+pub struct VoidResult {
+ pub ok: *mut c_void,
+ pub err: *mut c_char,
+ pub error_type: ErrorType,
+}
+
+#[repr(C)]
+pub struct IntResult {
+ pub ok: c_int,
+ pub err: *mut c_char,
+ pub error_type: ErrorType,
+}
+
+#[repr(C)]
+pub struct UnitResult {
+ pub ok: c_int,
+ pub err: *mut c_char,
+ pub error_type: ErrorType,
+}
+
+#[repr(C)]
+pub enum ErrorType {
+ None,
+ Io,
+ Utf8,
+ Unknown,
+}
+
+impl VoidResult {
+ pub fn error(err: impl AsRef, error_type: ErrorType) -> Self {
+ VoidResult {
+ ok: std::ptr::null_mut(),
+ err: into_cstring(err.as_ref()),
+ error_type,
+ }
+ }
+}
+
+impl From> for VoidResult {
+ //noinspection DuplicatedCode
+ fn from(result: io::Result) -> Self {
+ match result {
+ Ok(child) => VoidResult {
+ ok: into_void(child),
+ err: std::ptr::null_mut() as *mut c_char,
+ error_type: ErrorType::None,
+ },
+ Err(e) => VoidResult {
+ ok: std::ptr::null_mut(),
+ err: into_cstring(e.to_string()),
+ error_type: ErrorType::Io,
+ },
+ }
+ }
+}
+
+impl From> for VoidResult {
+ //noinspection DuplicatedCode
+ fn from(value: io::Result) -> Self {
+ match value {
+ Ok(output) => VoidResult {
+ ok: into_void(output),
+ err: std::ptr::null_mut() as *mut c_char,
+ error_type: ErrorType::None,
+ },
+ Err(e) => VoidResult {
+ ok: std::ptr::null_mut(),
+ err: into_cstring(e.to_string()),
+ error_type: ErrorType::Io,
+ },
+ }
+ }
+}
+
+impl From> for VoidResult {
+ fn from(value: Result) -> Self {
+ match value {
+ Ok(string) => VoidResult {
+ ok: into_cstring(string) as *mut c_void,
+ err: std::ptr::null_mut() as *mut c_char,
+ error_type: ErrorType::None,
+ },
+ Err(e) => VoidResult {
+ ok: std::ptr::null_mut(),
+ err: into_cstring(e.to_string()),
+ error_type: ErrorType::Io,
+ },
+ }
+ }
+}
+
+impl From> for IntResult {
+ fn from(value: io::Result) -> Self {
+ match value {
+ Ok(status) => match status.code() {
+ None => IntResult {
+ ok: -1,
+ err: into_cstring("No exit code"),
+ error_type: ErrorType::None,
+ },
+ Some(code) => IntResult {
+ ok: code,
+ err: std::ptr::null_mut() as *mut c_char,
+ error_type: ErrorType::None,
+ },
+ },
+ Err(e) => IntResult {
+ ok: -1,
+ err: into_cstring(e.to_string()),
+ error_type: ErrorType::Io,
+ },
+ }
+ }
+}
+
+impl From> for UnitResult {
+ fn from(value: io::Result<()>) -> Self {
+ match value {
+ Ok(_) => UnitResult {
+ ok: 0,
+ err: std::ptr::null_mut() as *mut c_char,
+ error_type: ErrorType::None,
+ },
+ Err(e) => UnitResult {
+ ok: -1,
+ err: into_cstring(e.to_string()),
+ error_type: ErrorType::Io,
+ },
+ }
+ }
+}
diff --git a/kommand-examples/build.gradle.kts b/kommand-examples/build.gradle.kts
new file mode 100644
index 0000000..e69de29
diff --git a/kommand-examples/example1/build.gradle.kts b/kommand-examples/example1/build.gradle.kts
new file mode 100644
index 0000000..cebe189
--- /dev/null
+++ b/kommand-examples/example1/build.gradle.kts
@@ -0,0 +1,77 @@
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+
+val currentPlatform: Platform = when {
+ DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MACOS_X64
+ DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.MACOS_ARM64
+ DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.LINUX_X64
+ DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.LINUX_ARM64
+ DefaultNativePlatform.getCurrentOperatingSystem().isWindows && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MINGW_X64
+ else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
+}
+
+plugins {
+ kotlin("multiplatform")
+}
+
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+}
+
+kotlin {
+ jvm {
+ compilations.all {
+ kotlinOptions.jvmTarget = "17"
+ }
+ withJava()
+ testRuns["test"].executionTask.configure {
+ useJUnitPlatform()
+ }
+ }
+
+ val nativeTarget = when (currentPlatform) {
+ Platform.MACOS_X64 -> macosX64()
+ Platform.MACOS_ARM64 -> macosArm64()
+ Platform.LINUX_X64 -> linuxX64()
+ Platform.LINUX_ARM64 -> linuxArm64()
+ Platform.MINGW_X64 -> mingwX64()
+ }
+
+ nativeTarget.apply {
+ binaries {
+ executable {
+ entryPoint = "com.kgit2.kommand.main"
+ }
+ }
+ }
+
+ applyDefaultHierarchyTemplate()
+
+ sourceSets {
+ // add opt-in
+ all {
+ languageSettings.optIn("kotlinx.cinterop.UnsafeNumber")
+ languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi")
+ languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
+ languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi")
+ languageSettings.optIn("kotlin.ExperimentalStdlibApi")
+ }
+
+ commonMain {
+ dependencies {
+ implementation(rootProject)
+ }
+ }
+ }
+}
+
+enum class Platform(
+ val archName: String
+) {
+ MACOS_X64("x86_64-apple-darwin"),
+ MACOS_ARM64("aarch64-apple-darwin"),
+ LINUX_X64("x86_64-unknown-linux-gnu"),
+ LINUX_ARM64("aarch64-unknown-linux-gnu"),
+ MINGW_X64("x86_64-pc-windows-gnu"),
+ ;
+}
diff --git a/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt b/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt
new file mode 100644
index 0000000..facc180
--- /dev/null
+++ b/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt
@@ -0,0 +1,12 @@
+package com.kgit2.kommand
+
+import com.kgit2.kommand.process.Command
+import com.kgit2.kommand.process.Stdio
+
+fun main() {
+ Command("ping")
+ .args(listOf("-c", "5", "localhost"))
+ .stdout(Stdio.Inherit)
+ .spawn()
+ .wait()
+}
diff --git a/kommand-examples/example2/build.gradle.kts b/kommand-examples/example2/build.gradle.kts
new file mode 100644
index 0000000..cebe189
--- /dev/null
+++ b/kommand-examples/example2/build.gradle.kts
@@ -0,0 +1,77 @@
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+
+val currentPlatform: Platform = when {
+ DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MACOS_X64
+ DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.MACOS_ARM64
+ DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.LINUX_X64
+ DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.LINUX_ARM64
+ DefaultNativePlatform.getCurrentOperatingSystem().isWindows && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MINGW_X64
+ else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
+}
+
+plugins {
+ kotlin("multiplatform")
+}
+
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+}
+
+kotlin {
+ jvm {
+ compilations.all {
+ kotlinOptions.jvmTarget = "17"
+ }
+ withJava()
+ testRuns["test"].executionTask.configure {
+ useJUnitPlatform()
+ }
+ }
+
+ val nativeTarget = when (currentPlatform) {
+ Platform.MACOS_X64 -> macosX64()
+ Platform.MACOS_ARM64 -> macosArm64()
+ Platform.LINUX_X64 -> linuxX64()
+ Platform.LINUX_ARM64 -> linuxArm64()
+ Platform.MINGW_X64 -> mingwX64()
+ }
+
+ nativeTarget.apply {
+ binaries {
+ executable {
+ entryPoint = "com.kgit2.kommand.main"
+ }
+ }
+ }
+
+ applyDefaultHierarchyTemplate()
+
+ sourceSets {
+ // add opt-in
+ all {
+ languageSettings.optIn("kotlinx.cinterop.UnsafeNumber")
+ languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi")
+ languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
+ languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi")
+ languageSettings.optIn("kotlin.ExperimentalStdlibApi")
+ }
+
+ commonMain {
+ dependencies {
+ implementation(rootProject)
+ }
+ }
+ }
+}
+
+enum class Platform(
+ val archName: String
+) {
+ MACOS_X64("x86_64-apple-darwin"),
+ MACOS_ARM64("aarch64-apple-darwin"),
+ LINUX_X64("x86_64-unknown-linux-gnu"),
+ LINUX_ARM64("aarch64-unknown-linux-gnu"),
+ MINGW_X64("x86_64-pc-windows-gnu"),
+ ;
+}
diff --git a/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt b/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt
new file mode 100644
index 0000000..95f911c
--- /dev/null
+++ b/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt
@@ -0,0 +1,15 @@
+package com.kgit2.kommand
+
+import com.kgit2.kommand.process.Command
+import com.kgit2.kommand.process.Stdio
+
+fun main() {
+ val child = Command("ping")
+ .args(listOf("-c", "5", "localhost"))
+ .stdout(Stdio.Pipe)
+ .spawn()
+ child.bufferedStdout()?.lines()?.forEach { line ->
+ println(line)
+ }
+ child.wait()
+}
diff --git a/kommand-examples/example3/build.gradle.kts b/kommand-examples/example3/build.gradle.kts
new file mode 100644
index 0000000..cebe189
--- /dev/null
+++ b/kommand-examples/example3/build.gradle.kts
@@ -0,0 +1,77 @@
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+
+val currentPlatform: Platform = when {
+ DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MACOS_X64
+ DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.MACOS_ARM64
+ DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.LINUX_X64
+ DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.LINUX_ARM64
+ DefaultNativePlatform.getCurrentOperatingSystem().isWindows && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MINGW_X64
+ else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
+}
+
+plugins {
+ kotlin("multiplatform")
+}
+
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+}
+
+kotlin {
+ jvm {
+ compilations.all {
+ kotlinOptions.jvmTarget = "17"
+ }
+ withJava()
+ testRuns["test"].executionTask.configure {
+ useJUnitPlatform()
+ }
+ }
+
+ val nativeTarget = when (currentPlatform) {
+ Platform.MACOS_X64 -> macosX64()
+ Platform.MACOS_ARM64 -> macosArm64()
+ Platform.LINUX_X64 -> linuxX64()
+ Platform.LINUX_ARM64 -> linuxArm64()
+ Platform.MINGW_X64 -> mingwX64()
+ }
+
+ nativeTarget.apply {
+ binaries {
+ executable {
+ entryPoint = "com.kgit2.kommand.main"
+ }
+ }
+ }
+
+ applyDefaultHierarchyTemplate()
+
+ sourceSets {
+ // add opt-in
+ all {
+ languageSettings.optIn("kotlinx.cinterop.UnsafeNumber")
+ languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi")
+ languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
+ languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi")
+ languageSettings.optIn("kotlin.ExperimentalStdlibApi")
+ }
+
+ commonMain {
+ dependencies {
+ implementation(rootProject)
+ }
+ }
+ }
+}
+
+enum class Platform(
+ val archName: String
+) {
+ MACOS_X64("x86_64-apple-darwin"),
+ MACOS_ARM64("aarch64-apple-darwin"),
+ LINUX_X64("x86_64-unknown-linux-gnu"),
+ LINUX_ARM64("aarch64-unknown-linux-gnu"),
+ MINGW_X64("x86_64-pc-windows-gnu"),
+ ;
+}
diff --git a/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt b/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt
new file mode 100644
index 0000000..207e606
--- /dev/null
+++ b/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt
@@ -0,0 +1,12 @@
+package com.kgit2.kommand
+
+import com.kgit2.kommand.process.Command
+import com.kgit2.kommand.process.Stdio
+
+fun main() {
+ Command("echo")
+ .arg("nothing")
+ .stdout(Stdio.Null)
+ .spawn()
+ .wait()
+}
diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock
deleted file mode 100644
index ff32adc..0000000
--- a/kotlin-js-store/yarn.lock
+++ /dev/null
@@ -1,555 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@ungap/promise-all-settled@1.1.2":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
- integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
-
-ansi-colors@4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
- integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
-
-ansi-regex@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
- integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-
-ansi-styles@^4.0.0, ansi-styles@^4.1.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
- integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
- dependencies:
- color-convert "^2.0.1"
-
-anymatch@~3.1.2:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
- integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
- dependencies:
- normalize-path "^3.0.0"
- picomatch "^2.0.4"
-
-argparse@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
- integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
-balanced-match@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
- integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-binary-extensions@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
- integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
-
-brace-expansion@^1.1.7:
- version "1.1.11"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
- integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
- dependencies:
- balanced-match "^1.0.0"
- concat-map "0.0.1"
-
-brace-expansion@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
- integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
- dependencies:
- balanced-match "^1.0.0"
-
-braces@~3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
- integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
- dependencies:
- fill-range "^7.0.1"
-
-browser-stdout@1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
- integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
-
-buffer-from@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
- integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
-
-camelcase@^6.0.0:
- version "6.3.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
- integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
-
-chalk@^4.1.0:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
- integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
- dependencies:
- ansi-styles "^4.1.0"
- supports-color "^7.1.0"
-
-chokidar@3.5.3:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
- integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
- dependencies:
- anymatch "~3.1.2"
- braces "~3.0.2"
- glob-parent "~5.1.2"
- is-binary-path "~2.1.0"
- is-glob "~4.0.1"
- normalize-path "~3.0.0"
- readdirp "~3.6.0"
- optionalDependencies:
- fsevents "~2.3.2"
-
-cliui@^7.0.2:
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
- integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
- dependencies:
- string-width "^4.2.0"
- strip-ansi "^6.0.0"
- wrap-ansi "^7.0.0"
-
-color-convert@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
- integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
- dependencies:
- color-name "~1.1.4"
-
-color-name@~1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
- integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-concat-map@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
- integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
-
-debug@4.3.4:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
- integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
- dependencies:
- ms "2.1.2"
-
-decamelize@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
- integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
-
-diff@5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
- integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
-
-emoji-regex@^8.0.0:
- version "8.0.0"
- resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
- integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
-
-escalade@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
- integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
-escape-string-regexp@4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
- integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-
-fill-range@^7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
- integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
- dependencies:
- to-regex-range "^5.0.1"
-
-find-up@5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
- integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
- dependencies:
- locate-path "^6.0.0"
- path-exists "^4.0.0"
-
-flat@^5.0.2:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
- integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
-
-format-util@1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271"
- integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==
-
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-
-fsevents@~2.3.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
- integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
-
-get-caller-file@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
- integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-
-glob-parent@~5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
- integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
- dependencies:
- is-glob "^4.0.1"
-
-glob@7.2.0:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
- integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.4"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-has-flag@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
- integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-
-he@1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
- integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-
-inflight@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
- dependencies:
- once "^1.3.0"
- wrappy "1"
-
-inherits@2:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
- integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-is-binary-path@~2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
- integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
- dependencies:
- binary-extensions "^2.0.0"
-
-is-extglob@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
- integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
-
-is-fullwidth-code-point@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
- integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-
-is-glob@^4.0.1, is-glob@~4.0.1:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
- integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
- dependencies:
- is-extglob "^2.1.1"
-
-is-number@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
- integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
-
-is-plain-obj@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
- integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
-
-is-unicode-supported@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
- integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
-
-js-yaml@4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
- integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
- dependencies:
- argparse "^2.0.1"
-
-locate-path@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
- integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
- dependencies:
- p-locate "^5.0.0"
-
-log-symbols@4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
- integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
- dependencies:
- chalk "^4.1.0"
- is-unicode-supported "^0.1.0"
-
-minimatch@5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
- integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
- dependencies:
- brace-expansion "^2.0.1"
-
-minimatch@^3.0.4:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
- integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
- dependencies:
- brace-expansion "^1.1.7"
-
-mocha@10.0.0:
- version "10.0.0"
- resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9"
- integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==
- dependencies:
- "@ungap/promise-all-settled" "1.1.2"
- ansi-colors "4.1.1"
- browser-stdout "1.3.1"
- chokidar "3.5.3"
- debug "4.3.4"
- diff "5.0.0"
- escape-string-regexp "4.0.0"
- find-up "5.0.0"
- glob "7.2.0"
- he "1.2.0"
- js-yaml "4.1.0"
- log-symbols "4.1.0"
- minimatch "5.0.1"
- ms "2.1.3"
- nanoid "3.3.3"
- serialize-javascript "6.0.0"
- strip-json-comments "3.1.1"
- supports-color "8.1.1"
- workerpool "6.2.1"
- yargs "16.2.0"
- yargs-parser "20.2.4"
- yargs-unparser "2.0.0"
-
-ms@2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
- integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-
-ms@2.1.3:
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
- integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
-nanoid@3.3.3:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
- integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
-
-normalize-path@^3.0.0, normalize-path@~3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
- integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-
-once@^1.3.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
- dependencies:
- wrappy "1"
-
-p-limit@^3.0.2:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
- integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
- dependencies:
- yocto-queue "^0.1.0"
-
-p-locate@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
- integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
- dependencies:
- p-limit "^3.0.2"
-
-path-exists@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
- integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-
-path-is-absolute@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
- integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
-
-picomatch@^2.0.4, picomatch@^2.2.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
- integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
-
-randombytes@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
- integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
- dependencies:
- safe-buffer "^5.1.0"
-
-readdirp@~3.6.0:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
- integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
- dependencies:
- picomatch "^2.2.1"
-
-require-directory@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
- integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
-
-safe-buffer@^5.1.0:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
- integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-
-serialize-javascript@6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
- integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
- dependencies:
- randombytes "^2.1.0"
-
-source-map-support@0.5.21:
- version "0.5.21"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
- integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
- dependencies:
- buffer-from "^1.0.0"
- source-map "^0.6.0"
-
-source-map@^0.6.0:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
- integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-
-string-width@^4.1.0, string-width@^4.2.0:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-json-comments@3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
- integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-
-supports-color@8.1.1:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
- integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
- dependencies:
- has-flag "^4.0.0"
-
-supports-color@^7.1.0:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
- integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
- dependencies:
- has-flag "^4.0.0"
-
-to-regex-range@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
- integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
- dependencies:
- is-number "^7.0.0"
-
-workerpool@6.2.1:
- version "6.2.1"
- resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
- integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
-
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
-wrappy@1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
- integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
-
-y18n@^5.0.5:
- version "5.0.8"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
- integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
-yargs-parser@20.2.4:
- version "20.2.4"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
- integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
-
-yargs-parser@^20.2.2:
- version "20.2.9"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
- integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
-yargs-unparser@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
- integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
- dependencies:
- camelcase "^6.0.0"
- decamelize "^4.0.0"
- flat "^5.0.2"
- is-plain-obj "^2.1.0"
-
-yargs@16.2.0:
- version "16.2.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
- integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
- dependencies:
- cliui "^7.0.2"
- escalade "^3.1.1"
- get-caller-file "^2.0.5"
- require-directory "^2.1.1"
- string-width "^4.2.0"
- y18n "^5.0.5"
- yargs-parser "^20.2.2"
-
-yocto-queue@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
- integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 5f77412..d5c3a30 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,8 +1,8 @@
pluginManagement {
plugins {
- kotlin("jvm") version "1.9.0" apply false
- kotlin("multiplatform") version "1.9.0" apply false
- id("org.jetbrains.dokka") version "1.9.0" apply false
+ kotlin("jvm") version "1.9.21" apply false
+ kotlin("multiplatform") version "1.9.21" apply false
+ id("org.jetbrains.dokka") version "1.9.10" apply false
id("io.github.gradle-nexus.publish-plugin") version "1.3.0" apply false
}
@@ -12,6 +12,16 @@ pluginManagement {
}
}
+buildCache {
+ local {
+ directory = File(rootDir, "build-cache")
+ removeUnusedEntriesAfterDays = 30
+ }
+}
+
rootProject.name = "kommand"
-include(":sub_command")
+include(":kommand-examples")
+include(":kommand-examples:example1")
+include(":kommand-examples:example2")
+include(":kommand-examples:example3")
diff --git a/src/commonMain/kotlin/com/kgit2/io/Reader.kt b/src/commonMain/kotlin/com/kgit2/io/Reader.kt
deleted file mode 100644
index 9e53ed3..0000000
--- a/src/commonMain/kotlin/com/kgit2/io/Reader.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import io.ktor.utils.io.core.*
-
-expect class PlatformReader {
- fun closeSource()
-
- fun fill(destination: Memory, offset: Int, length: Int): Int
-}
-
-open class Reader (
- private val platformReader: PlatformReader
-) : Input(
- // pool = DefaultBufferPool(Buffer.ReservedSize + 1, 1)
-) {
-
- override fun closeSource() {
- platformReader.closeSource()
- }
-
- override fun fill(destination: Memory, offset: Int, length: Int): Int {
- return platformReader.fill(destination, offset, length)
- }
-
- fun readLine(): String? {
- return readUTF8Line()
- }
-
- fun lines(): Sequence {
- return sequence {
- while (endOfInput.not()) {
- readUTF8Line()?.let { yield(it) }
- }
- }
- }
-}
diff --git a/src/commonMain/kotlin/com/kgit2/io/Writer.kt b/src/commonMain/kotlin/com/kgit2/io/Writer.kt
deleted file mode 100644
index a7f489a..0000000
--- a/src/commonMain/kotlin/com/kgit2/io/Writer.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import io.ktor.utils.io.core.*
-
-expect class PlatformWriter {
- fun flush(source: Memory, offset: Int, length: Int)
- fun close()
-}
-
-open class Writer (
- private val platformWriter: PlatformWriter
-) : Output() {
- override fun closeDestination() {
- platformWriter.close()
- }
-
- override fun flush(source: Memory, offset: Int, length: Int) {
- platformWriter.flush(source, offset, length)
- }
-}
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/Platform.kt b/src/commonMain/kotlin/com/kgit2/kommand/Platform.kt
new file mode 100644
index 0000000..beb41a7
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/Platform.kt
@@ -0,0 +1,11 @@
+package com.kgit2.kommand
+
+enum class Platform {
+ MACOS_X64,
+ MACOS_ARM64,
+ LINUX_X64,
+ LINUX_ARM64,
+ MINGW_X64,
+}
+
+expect val platform: Platform
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/env/EnvVars.kt b/src/commonMain/kotlin/com/kgit2/kommand/env/EnvVars.kt
new file mode 100644
index 0000000..8e2fd29
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/env/EnvVars.kt
@@ -0,0 +1,5 @@
+package com.kgit2.kommand.env
+
+expect fun envVar(key: String): String?
+
+expect fun envVars(): Map?
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/exception/KommandException.kt b/src/commonMain/kotlin/com/kgit2/kommand/exception/KommandException.kt
new file mode 100644
index 0000000..8d62e62
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/exception/KommandException.kt
@@ -0,0 +1,15 @@
+package com.kgit2.kommand.exception
+
+class KommandException(
+ message: String?,
+ val errorType: ErrorType?,
+) : Exception(message)
+
+enum class ErrorType {
+ None,
+ IO,
+ Utf8,
+ Unknown,
+ ;
+ companion object;
+}
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedReader.kt b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedReader.kt
new file mode 100644
index 0000000..be396d0
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedReader.kt
@@ -0,0 +1,14 @@
+package com.kgit2.kommand.io
+
+import com.kgit2.kommand.exception.KommandException
+
+expect class BufferedReader {
+ @Throws(KommandException::class)
+ fun readLine(): String?
+
+ @Throws(KommandException::class)
+ fun readAll(): String?
+
+ @Throws(KommandException::class)
+ fun lines(): Sequence
+}
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedWriter.kt b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedWriter.kt
new file mode 100644
index 0000000..ea68ac9
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedWriter.kt
@@ -0,0 +1,14 @@
+package com.kgit2.kommand.io
+
+import com.kgit2.kommand.exception.KommandException
+
+expect class BufferedWriter {
+ @Throws(KommandException::class)
+ fun writeLine(line: String)
+
+ @Throws(KommandException::class)
+ fun flush()
+
+ @Throws(KommandException::class)
+ fun close()
+}
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/io/Output.kt b/src/commonMain/kotlin/com/kgit2/kommand/io/Output.kt
new file mode 100644
index 0000000..a409ca7
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/io/Output.kt
@@ -0,0 +1,9 @@
+package com.kgit2.kommand.io
+
+data class Output(
+ val status: Int?,
+ val stdout: String?,
+ val stderr: String?,
+) {
+ companion object;
+}
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt b/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt
new file mode 100644
index 0000000..2496bba
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt
@@ -0,0 +1,25 @@
+package com.kgit2.kommand.process
+
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.io.BufferedReader
+import com.kgit2.kommand.io.BufferedWriter
+import com.kgit2.kommand.io.Output
+
+expect class Child {
+ fun id(): UInt
+
+ fun bufferedStdin(): BufferedWriter?
+
+ fun bufferedStdout(): BufferedReader?
+
+ fun bufferedStderr(): BufferedReader?
+
+ @Throws(KommandException::class)
+ fun kill()
+
+ @Throws(KommandException::class)
+ fun wait(): Int
+
+ @Throws(KommandException::class)
+ fun waitWithOutput(): Output
+}
diff --git a/src/commonMain/kotlin/com/kgit2/kommand/process/Command.kt b/src/commonMain/kotlin/com/kgit2/kommand/process/Command.kt
new file mode 100644
index 0000000..9b7b09e
--- /dev/null
+++ b/src/commonMain/kotlin/com/kgit2/kommand/process/Command.kt
@@ -0,0 +1,47 @@
+package com.kgit2.kommand.process
+
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.io.Output
+
+expect class Command(command: String) {
+ val command: String
+
+ fun debugString(): String
+
+ fun arg(arg: String): Command
+
+ fun args(args: List): Command
+
+ fun env(key: String, value: String): Command
+
+ fun envs(envs: Map): Command
+
+ fun removeEnv(key: String): Command
+
+ fun envClear(): Command
+
+ fun cwd(dir: String): Command
+
+ fun stdin(stdio: Stdio): Command
+
+ fun stdout(stdio: Stdio): Command
+
+ fun stderr(stdio: Stdio): Command
+
+ @Throws(KommandException::class)
+ fun spawn(): Child
+
+ @Throws(KommandException::class)
+ fun output(): Output
+
+ @Throws(KommandException::class)
+ fun status(): Int
+}
+
+enum class Stdio {
+ Inherit,
+ Pipe,
+ Null,
+ ;
+ companion object;
+}
diff --git a/src/commonMain/kotlin/com/kgit2/process/Child.kt b/src/commonMain/kotlin/com/kgit2/process/Child.kt
deleted file mode 100644
index 9e8c9cb..0000000
--- a/src/commonMain/kotlin/com/kgit2/process/Child.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.kgit2.process
-
-import com.kgit2.io.Reader
-import com.kgit2.io.Writer
-import io.ktor.utils.io.errors.*
-
-expect class Child(
- command: String,
- args: List,
- envs: Map,
- cwd: String? = null,
- stdin: Stdio,
- stdout: Stdio,
- stderr: Stdio,
-) {
- val command: String
- val args: List
- val envs: Map
- val cwd: String?
- val stdin: Stdio
- val stdout: Stdio
- val stderr: Stdio
-
- var id: Int?
- fun getChildStdin(): Writer?
- fun getChildStdout(): Reader?
- fun getChildStderr(): Reader?
-
- @Throws(IOException::class)
- fun start(options: ChildOptions = ChildOptions.W_NOHANG)
-
- @Throws(IOException::class)
- fun wait(): ChildExitStatus
-
- @Throws(IOException::class)
- fun waitWithOutput(): String?
- fun kill()
- fun prompt(): String
-}
-
-class ChildOptions private constructor(
- val value: Int
-) {
- companion object {
- val W_UNTRACED = ChildOptions(0x00000001)
- val W_NOHANG = ChildOptions(0x00000002)
- }
-
- infix fun or(other: ChildOptions): ChildOptions {
- return ChildOptions(this.value or other.value)
- }
-
- operator fun contains(other: ChildOptions): Boolean {
- return this.value and other.value != 0
- }
-}
-
-data class ChildExitStatus(
- val code: Int,
-) {
- inline fun w_stopped(): Int {
- return 0x7F
- }
-
- inline fun w_coreflag(): Int {
- return 0x80
- }
-
- inline fun w_status(x: Int): Int {
- return x and w_stopped()
- }
-
- inline fun w_stopsig(x: Int): Int {
- return x shr 8
- }
-
- /**
- * returns true if the child terminated normally,
- * that is, by calling exit(3) or _exit(2),
- * or by returning from main().
- */
- inline fun exited(): Boolean {
- return w_status(code) == 0
- }
-
- /**
- * returns the exit status of the child.
- * This consists of the least significant 8 bits of the status argument
- * that the child specified in a call to exit(3) or _exit(2)
- * or as the argument for a return statement in main().
- * This macro should only be employed if WIFEXITED returned true.
- */
- inline fun exitStatus(): Int {
- return w_stopsig(code) and 0x000000FF
- }
-
- /**
- * returns true if the child process was terminated by a signal.
- */
- inline fun signaled(): Boolean {
- return (w_status(code) != w_stopped()) && (w_status(code) != 0)
- }
-
- /**
- * returns the number of the signal that caused the child process to terminate.
- * This macro should only be employed if WIFSIGNALED returned true.
- */
- inline fun signal(): Int {
- return w_status(code)
- }
-
- /**
- * returns true if the child produced a core dump.
- * This macro should only be employed if WIFSIGNALED returned true.
- * This macro is not specified in POSIX.1-2001
- * and is not available on some UNIX implementations (e.g., AIX, SunOS).
- * Only use this enclosed in #ifdef WCOREDUMP ... #endif.
- */
- inline fun coreDump(): Boolean {
- return code == w_coreflag()
- }
-
- /**
- * returns true if the child process was stopped by delivery of a signal;
- * this is only possible if the call was done using WUNTRACED or when the child is being traced (see ptrace(2)).
- */
- inline fun stopped(): Boolean {
- return (w_status(code) == w_stopped()) && (w_stopsig(code) != 0x13)
- }
-
- /**
- * returns the number of the signal which caused the child to stop.
- * This macro should only be employed if WIFSTOPPED returned true.
- */
- inline fun stopSignal(): Int {
- return w_stopsig(code)
- }
-}
diff --git a/src/commonMain/kotlin/com/kgit2/process/Command.kt b/src/commonMain/kotlin/com/kgit2/process/Command.kt
deleted file mode 100644
index 66e5428..0000000
--- a/src/commonMain/kotlin/com/kgit2/process/Command.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.kgit2.process
-
-public class Command(
- val command: String
-) {
- private val args = mutableListOf()
- private val envs = mutableMapOf()
- private var cwd: String? = null
-
- private var stdin: Stdio = Stdio.Inherit
- private var stdout: Stdio = Stdio.Inherit
- private var stderr: Stdio = Stdio.Inherit
-
- public fun arg(arg: String): Command {
- this.args.addAll(arg.split(" "))
- return this
- }
-
- public fun args(vararg args: String): Command {
- this.args.addAll(args)
- return this
- }
-
- public fun env(key: String, value: String): Command {
- this.envs[key] = value
- return this
- }
-
- public fun envs(vararg envs: Pair): Command {
- this.envs.putAll(envs)
- return this
- }
-
- public fun cwd(cwd: String?): Command {
- this.cwd = cwd
- return this
- }
-
-
- public fun stdin(stdin: Stdio): Command {
- this.stdin = stdin
- return this
- }
-
- public fun stdout(io: Stdio): Command {
- this.stdout = io
- return this
- }
-
- public fun stderr(io: Stdio): Command {
- this.stderr = io
- return this
- }
-
- public fun spawn(): Child {
- val child = Child(
- command = command,
- args = args,
- envs = envs,
- cwd = cwd,
- stdin = stdin,
- stdout = stdout,
- stderr = stderr,
- )
- child.start()
- return child
- }
-
- public fun output(): String? {
- return spawn().waitWithOutput()
- }
-
- public fun status(): ChildExitStatus {
- return spawn().wait()
- }
-
- public fun getArgs(): List {
- return args
- }
-
- public fun getEnvs(): Map {
- return envs
- }
-
- public fun getCwd(): String? {
- return cwd
- }
-
- override fun toString(): String {
- return "Command(command='$command', args=$args, envs=$envs, cwd=$cwd, stdin=$stdin, stdout=$stdout, stderr=$stderr)"
- }
-
- public fun prompt(): String {
- return "$command ${args.joinToString(" ")}"
- }
-}
-
-
-enum class Stdio {
- Inherit,
- Pipe,
- Null,
-}
diff --git a/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt
new file mode 100644
index 0000000..a0dd607
--- /dev/null
+++ b/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt
@@ -0,0 +1,77 @@
+package com.kgit2.kommand
+
+import com.kgit2.kommand.process.Command
+import com.kgit2.kommand.process.Stdio
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ChildTest {
+ @Test
+ fun spawnIntervalOutput() {
+ val command = Command(platformEchoPath())
+ .arg("interval")
+ .stdout(Stdio.Pipe)
+ val child = command.spawn()
+ val output = child.waitWithOutput()
+ val expect = listOf("0", "1", "2", "3", "4").joinToString("\n") + "\n"
+ assertEquals(expect, output.stdout)
+ val expectFailure = runCatching {
+ child.waitWithOutput()
+ }.onFailure {
+ assertEquals("Child has been consumed", it.message)
+ }.isFailure
+ assertEquals(true, expectFailure)
+ }
+
+ @Test
+ fun spawnIntervalStdout() {
+ val command = Command(platformEchoPath())
+ .arg("interval")
+ .stdout(Stdio.Pipe)
+ val child = command.spawn()
+ var line = child.bufferedStdout()?.readLine()
+ var expect = 0
+ while (!line.isNullOrEmpty()) {
+ assertEquals(expect.toString(), line)
+ line = child.bufferedStdout()?.readLine()
+ expect += 1
+ }
+ child.wait()
+ }
+
+ @Test
+ fun spawnPipedEcho() {
+ for (i in 0 .. 1000) {
+ val command = Command(platformEchoPath())
+ .arg("echo")
+ .stdin(Stdio.Pipe)
+ .stdout(Stdio.Pipe)
+ val child = command.spawn()
+ val expect = "Hello World!"
+ child.bufferedStdin()?.writeLine(expect)
+ child.bufferedStdin()?.flush()
+ val line = child.bufferedStdout()?.readLine()
+ assertEquals(expect, line)
+ child.wait()
+ }
+ }
+
+ @Test
+ fun manyStdout() {
+ for (i in 0 .. 100) {
+ val command = Command(platformEchoPath())
+ .arg("stdout")
+ .stdin(Stdio.Pipe)
+ .stdout(Stdio.Pipe)
+ val child = command.spawn()
+ val expect = "Hello World!"
+ for (j in 0 .. 100) {
+ child.bufferedStdin()?.writeLine("[$j]$expect")
+ child.bufferedStdin()?.flush()
+ val line = child.bufferedStdout()?.readLine()
+ assertEquals("[$j]$expect", line)
+ }
+ child.wait()
+ }
+ }
+}
diff --git a/src/commonTest/kotlin/com/kgit2/kommand/CommandTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/CommandTest.kt
new file mode 100644
index 0000000..04e6f9e
--- /dev/null
+++ b/src/commonTest/kotlin/com/kgit2/kommand/CommandTest.kt
@@ -0,0 +1,60 @@
+package com.kgit2.kommand
+
+import com.kgit2.kommand.process.Command
+import com.kgit2.kommand.process.Stdio
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class CommandTest {
+ @Test
+ fun autoFree() {
+ Command("echo").debugString()
+ }
+
+ @Test
+ fun simpleEcho() {
+ val command = Command(platformEchoPath())
+ val output = command.output()
+ assertEquals("Hello, Kommand!", output.stdout)
+ assertEquals("Hello, Kommand!", command.output().stdout)
+ assertEquals("Hello, Kommand!", command.output().stdout)
+ assertEquals("Hello, Kommand!", command.output().stdout)
+ val status = command.status()
+ assertEquals(0, status)
+ assertEquals(0, command.status())
+ assertEquals(0, command.status())
+ assertEquals(0, command.status())
+ }
+
+ @Test
+ fun discardIO() {
+ val command = Command(platformEchoPath()).stdout(Stdio.Null)
+ val output = command.output()
+ assertEquals("", output.stdout)
+ val status = command.status()
+ assertEquals(0, status)
+ }
+
+ @Test
+ fun colorEcho() {
+ val command = Command(platformEchoPath()).arg("color")
+ val output = command.output()
+ val expect = listOf(
+ "\u001B[31mHello, Kommand!\u001B[0m",
+ "\u001B[32mHello, Kommand!\u001B[0m",
+ "\u001B[34mHello, Kommand!\u001B[0m",
+ ).joinToString("\n") + "\n"
+ assertEquals(expect, output.stdout)
+ val status = command.status()
+ assertEquals(0, status)
+ }
+
+ @Test
+ fun currentWorkingDirectory() {
+ val command = platformCwd().cwd(homeDir())
+ val output = command.output()
+ assertEquals(homeDir(), output.stdout?.trim())
+ val status = command.status()
+ assertEquals(0, status)
+ }
+}
diff --git a/src/commonTest/kotlin/com/kgit2/kommand/EnvTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/EnvTest.kt
new file mode 100644
index 0000000..cf984a8
--- /dev/null
+++ b/src/commonTest/kotlin/com/kgit2/kommand/EnvTest.kt
@@ -0,0 +1,15 @@
+package com.kgit2.kommand
+
+import kotlin.test.Test
+
+class EnvTest {
+ @Test
+ fun envVar() {
+ com.kgit2.kommand.env.envVar("HOME")
+ }
+
+ @Test
+ fun envVars() {
+ com.kgit2.kommand.env.envVars()
+ }
+}
diff --git a/src/commonTest/kotlin/com/kgit2/kommand/PlatformTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/PlatformTest.kt
new file mode 100644
index 0000000..bdb8621
--- /dev/null
+++ b/src/commonTest/kotlin/com/kgit2/kommand/PlatformTest.kt
@@ -0,0 +1,42 @@
+package com.kgit2.kommand
+
+import com.kgit2.kommand.env.envVar
+import com.kgit2.kommand.process.Command
+import kotlin.test.Test
+
+class PlatformTest {
+ @Test
+ fun platform() {
+ platform
+ }
+}
+
+fun platformEchoPath(): String {
+ return when (platform) {
+ Platform.MACOS_X64 -> "kommand-core/target/x86_64-apple-darwin/release/kommand-echo"
+ Platform.MACOS_ARM64 -> "kommand-core/target/aarch64-apple-darwin/release/kommand-echo"
+ Platform.LINUX_X64 -> "kommand-core/target/x86_64-unknown-linux-gnu/release/kommand-echo"
+ Platform.LINUX_ARM64 -> "kommand-core/target/aarch64-unknown-linux-gnu/release/kommand-echo"
+ Platform.MINGW_X64 -> "kommand-core/target/x86_64-pc-windows-gnu/release/kommand-echo"
+ }
+}
+
+fun platformCwd(): Command {
+ return when (platform) {
+ Platform.MACOS_X64 -> Command("pwd")
+ Platform.MACOS_ARM64 -> Command("pwd")
+ Platform.LINUX_X64 -> Command("pwd")
+ Platform.LINUX_ARM64 -> Command("pwd")
+ Platform.MINGW_X64 -> Command("cmd").arg("/c").arg("chdir")
+ }
+}
+
+fun homeDir(): String {
+ return when (platform) {
+ Platform.MACOS_X64 -> envVar("HOME")!!
+ Platform.MACOS_ARM64 -> envVar("HOME")!!
+ Platform.LINUX_X64 -> envVar("HOME")!!
+ Platform.LINUX_ARM64 -> envVar("HOME")!!
+ Platform.MINGW_X64 -> envVar("userprofile")!!
+ }
+}
diff --git a/src/commonTest/kotlin/process/CommandTest.kt b/src/commonTest/kotlin/process/CommandTest.kt
deleted file mode 100644
index 235deaa..0000000
--- a/src/commonTest/kotlin/process/CommandTest.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-package process
-
-import com.kgit2.process.Command
-import com.kgit2.process.Stdio
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-expect val eko: String
-
-expect fun shellTest()
-
-expect fun envVar(key: String): String?
-
-expect fun homeDir(): String?
-
-expect fun pwd(): Command
-
-class CommandTest {
-
- @Test
- fun test() {
- println("begin")
- Command(eko)
- .stdout(Stdio.Pipe)
- .spawn()
- println("end")
- }
-
- @Test
- fun testOutput() {
- val expectString = "Hello, Kommand!\n"
- val output = Command(eko)
- .stdout(Stdio.Pipe)
- .spawn()
- .waitWithOutput()
- assertEquals(expectString, output)
- }
-
- @Test
- fun testEcho() {
- val expectString = "Hello, Kommand!"
- val child = Command(eko)
- .args("stdout")
- .stdin(Stdio.Pipe)
- .stdout(Stdio.Pipe)
- .spawn()
- val writer = child.getChildStdin()!!
- writer.appendLine(expectString)
- writer.close()
- val reader = child.getChildStdout()!!
- val output = reader.readLine()
- assertEquals(expectString, output)
- }
-
- @Test
- fun testEchoMultiLine() {
- val expectString = "Hello, Kommand!"
- val child = Command(eko)
- .args("stdout")
- .stdin(Stdio.Pipe)
- .stdout(Stdio.Pipe)
- .spawn()
- val writer = child.getChildStdin()!!
- writer.appendLine(expectString)
- writer.appendLine(expectString)
- writer.flush()
- writer.close()
- val reader = child.getChildStdout()!!
- val lines = reader.lines().toList()
- lines.forEach {
- assertEquals(expectString, it)
- }
- assertEquals(2, lines.count())
- }
-
- @Test
- fun testError() {
- val expectString = "Hello, Kommand!"
- val child = Command(eko)
- .args("stderr")
- .stdin(Stdio.Pipe)
- .stderr(Stdio.Pipe)
- .spawn()
- val writer = child.getChildStdin()!!
- writer.appendLine(expectString)
- writer.close()
- val reader = child.getChildStderr()!!
- val output = reader.readLine()
- assertEquals(expectString, output)
- }
-
- @Test
- fun testInterval() {
- val expectLineCount = 5
- var lineCount = 0
- Command(eko)
- .args("interval")
- .stdout(Stdio.Pipe)
- .spawn()
- .getChildStdout()
- ?.lines()?.forEach {
- println(it)
- lineCount += 1
- }
- assertEquals(expectLineCount, lineCount)
- }
-
- @Test
- fun testIntervalWithArgs() {
- val expectLineCount = 10
- var lineCount = 0
- Command(eko)
- .args("interval", "10")
- .stdout(Stdio.Pipe)
- .spawn()
- .getChildStdout()
- ?.lines()?.forEach {
- println(it)
- lineCount += 1
- }
- assertEquals(expectLineCount, lineCount)
- }
-
- @Test
- fun shTest() {
- shellTest()
- }
-
- @Test
- fun testCat() {
- val expectString = "PREV — DATA\nNEXT"
- val cmd = Command("echo").args(expectString).stdout(Stdio.Pipe).spawn()
- cmd.getChildStdout()?.lines()?.joinToString("\n")?.let {
- assertEquals(expectString, it)
- }
- }
-
- @Test
- fun testCwd() {
- val output = pwd()
- .cwd(homeDir())
- .stdout(Stdio.Pipe)
- .spawn()
- .waitWithOutput()
- ?.trim();
- assertEquals(homeDir()?.trimEnd('/'), output?.trimEnd('/'))
- }
-}
diff --git a/src/jsMain/kotlin/Main.kt b/src/jsMain/kotlin/Main.kt
deleted file mode 100644
index f8799c1..0000000
--- a/src/jsMain/kotlin/Main.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-@file:Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
-
-import child_process.ChildProcess
-import child_process.ChildProcessByStdio
-
-fun main() {
- val module = js("require('child_process')")
- val options: dynamic = js("{stdio: [0, 1, 2]}")
- val child: ChildProcess = module.spawn("ls", arrayOf("-l"), options) as ChildProcess
- console.log(child)
- // process.spawnargs()
- // console.log(process)
-}
diff --git a/src/jsMain/kotlin/com/kgit2/io/PlatformReader.kt b/src/jsMain/kotlin/com/kgit2/io/PlatformReader.kt
deleted file mode 100644
index c44be05..0000000
--- a/src/jsMain/kotlin/com/kgit2/io/PlatformReader.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import stream.internal
-
-actual class PlatformReader(readable: internal.Readable) {
- var readable: internal.Readable? = readable
-
- init {
- readable.setEncoding("utf8")
- }
-
- actual fun closeSource() {
- readable?.destroy()
- }
-
- actual fun fill(destination: Memory, offset: Int, length: Int): Int {
- val buffer = ByteArray(length)
- var readed = 0
- while (readable?.readable == true) {
- readable?.read()
- }
- return readed
- }
-
- actual fun fillLine(destination: Memory, offset: Int, length: Int): Int {
- TODO("Not yet implemented")
- }
-
-}
diff --git a/src/jsMain/kotlin/com/kgit2/io/PlatformWriter.kt b/src/jsMain/kotlin/com/kgit2/io/PlatformWriter.kt
deleted file mode 100644
index ed6c422..0000000
--- a/src/jsMain/kotlin/com/kgit2/io/PlatformWriter.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import stream.internal
-
-actual class PlatformWriter(writeable: internal.Writable) {
- var writeable: internal.Writable? = writeable
-
- @OptIn(ExperimentalUnsignedTypes::class)
- actual fun flush(source: Memory, offset: Int, length: Int) {
- val buffer = ByteArray(length)
- source.copyTo(buffer, offset, length, 0)
- writeable?.write(buffer.toUByteArray())
- }
-
- actual fun close() {
- writeable?.destroy()
- }
-}
diff --git a/src/jsMain/kotlin/com/kgit2/process/Child.kt b/src/jsMain/kotlin/com/kgit2/process/Child.kt
deleted file mode 100644
index b15d8c9..0000000
--- a/src/jsMain/kotlin/com/kgit2/process/Child.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.kgit2.process
-
-import NodeJS.Dict
-import NodeJS.ProcessEnv
-import child_process.ChildProcess
-import child_process.SpawnOptions
-import child_process.SpawnSyncOptions
-import child_process.spawn
-import child_process.spawnSync
-import com.kgit2.io.Reader
-import com.kgit2.io.Writer
-import io.ktor.utils.io.errors.*
-
-val module = js("require('child_process')")
-
-actual class Child actual constructor(
- actual val command: String,
- actual val args: List,
- actual val envs: Map,
- actual val cwd: String?,
- actual val stdin: Stdio,
- actual val stdout: Stdio,
- actual val stderr: Stdio
-) {
- actual var id: Int? = null
-
- actual fun getChildStdin(): Writer? {
- TODO("Not yet implemented")
- }
-
- actual fun getChildStdout(): Reader? {
- TODO("Not yet implemented")
- }
-
- actual fun getChildStderr(): Reader? {
- TODO("Not yet implemented")
- }
-
- @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
- actual fun start(options: ChildOptions) {
- val spawnOptions: SpawnOptions = js("{}") as SpawnOptions
- spawnOptions.cwd = cwd
- spawnOptions.env = envs.asDynamic() as ProcessEnv
- spawnOptions.stdio = arrayOf(stdin, stdout, stderr).map {
- when (it) {
- Stdio.Inherit -> "inherit"
- Stdio.Pipe -> "pipe"
- Stdio.Null -> "ignore"
- }
- }.toTypedArray()
- val process = spawn(command, spawnOptions)
- this.id = process.pid.toInt()
- process.stdin
- }
-
- actual fun wait(): ChildExitStatus {
- return ChildExitStatus(0)
- }
-
- actual fun waitWithOutput(): String? {
- TODO("Not yet implemented")
- }
-
- actual fun kill() {
- }
-
- actual fun prompt(): String {
- TODO("Not yet implemented")
- }
-
-}
diff --git a/src/jsMain/resources/main.js b/src/jsMain/resources/main.js
deleted file mode 100644
index 127d75c..0000000
--- a/src/jsMain/resources/main.js
+++ /dev/null
@@ -1,17 +0,0 @@
-(async () => {
- var child_process = require('child_process');
- console.log(child_process)
- let process = child_process.spawn('../../../sub_command/build/install/sub_command/bin/sub_command', ["echo"], {stdio: ['pipe', 'pipe', 'inherit']})
- console.log(process)
- process.stdin.write("Hello World1\n")
- process.stdin.write("Hello World2\n")
- process.stdin.write("Hello World3\n")
-
- // process.stdout.on('message', (data) => {
- // console.log(`stdout: ${data}`);
- // })
- process.on('spawn', (data) => {
- console.log(`stdout: ${data}`);
- })
- // console.log(process.status)
-})()
diff --git a/src/jsTest/kotlin/process/subCommand.kt b/src/jsTest/kotlin/process/subCommand.kt
deleted file mode 100644
index 245c081..0000000
--- a/src/jsTest/kotlin/process/subCommand.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package process
-
-actual val subCommand: String = "sub_command/build/install/sub_command/bin/sub_command"
diff --git a/src/jvmMain/kotlin/com/kgit2/io/PlatformReader.kt b/src/jvmMain/kotlin/com/kgit2/io/PlatformReader.kt
deleted file mode 100644
index c46d1fd..0000000
--- a/src/jvmMain/kotlin/com/kgit2/io/PlatformReader.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import java.io.BufferedReader
-import java.io.InputStream
-import java.util.*
-import kotlin.math.min
-
-actual class PlatformReader(inputStream: InputStream) {
- private var inputStream: BufferedReader? = null
-
- init {
- this.inputStream = inputStream.bufferedReader()
- }
-
- actual fun closeSource() {
- inputStream?.close()
- }
-
- private val buffer: Queue = LinkedList()
-
- actual fun fill(destination: Memory, offset: Int, length: Int): Int {
- var readed = 0
- val migrateBuffer = {
- for (i in 0 until min(length, buffer.size)) {
- destination.storeAt(offset + i, buffer.poll())
- readed += 1
- }
- }
- when {
- length < buffer.size -> {
- println("length < buffer.size")
- migrateBuffer()
- }
- else -> {
- inputStream?.readLine()?.apply {
- buffer.addAll("$this\n".toByteArray(Charsets.UTF_8).toList())
- }
- migrateBuffer()
- }
- }
- return readed
- }
-}
diff --git a/src/jvmMain/kotlin/com/kgit2/io/PlatformWriter.kt b/src/jvmMain/kotlin/com/kgit2/io/PlatformWriter.kt
deleted file mode 100644
index dd1e314..0000000
--- a/src/jvmMain/kotlin/com/kgit2/io/PlatformWriter.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import java.io.OutputStream
-
-actual class PlatformWriter(outputStream: OutputStream) {
- var outputStream: OutputStream? = outputStream
-
- actual fun flush(source: Memory, offset: Int, length: Int) {
- val buffer = ByteArray(length)
- source.copyTo(buffer, offset, length, 0)
- outputStream?.write(buffer)
- outputStream?.flush()
- }
-
- actual fun close() {
- outputStream?.close()
- }
-}
diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/Platform.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/Platform.jvm.kt
new file mode 100644
index 0000000..f84c5f3
--- /dev/null
+++ b/src/jvmMain/kotlin/com/kgit2/kommand/Platform.jvm.kt
@@ -0,0 +1,23 @@
+package com.kgit2.kommand
+
+actual val platform: Platform
+ get() {
+ val os = System.getProperty("os.name")
+ val arch = System.getProperty("os.arch")
+ return when {
+ os.contains("Mac OS X") -> {
+ when {
+ arch.contains("aarch64") -> Platform.MACOS_ARM64
+ else -> Platform.MACOS_X64
+ }
+ }
+ os.contains("Linux") -> {
+ when {
+ arch.contains("aarch64") -> Platform.LINUX_ARM64
+ else -> Platform.LINUX_X64
+ }
+ }
+ os.contains("Windows") -> Platform.MINGW_X64
+ else -> throw Exception("Unsupported platform: $os $arch")
+ }
+ }
diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/env/EnvVars.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/env/EnvVars.jvm.kt
new file mode 100644
index 0000000..7ca8403
--- /dev/null
+++ b/src/jvmMain/kotlin/com/kgit2/kommand/env/EnvVars.jvm.kt
@@ -0,0 +1,9 @@
+package com.kgit2.kommand.env
+
+actual fun envVar(key: String): String? {
+ return System.getenv(key)
+}
+
+actual fun envVars(): Map? {
+ return System.getenv()
+}
diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedReader.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedReader.jvm.kt
new file mode 100644
index 0000000..7fde319
--- /dev/null
+++ b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedReader.jvm.kt
@@ -0,0 +1,26 @@
+package com.kgit2.kommand.io
+
+import com.kgit2.kommand.exception.KommandException
+
+actual class BufferedReader(
+ private val reader: java.io.BufferedReader,
+) {
+ @Throws(KommandException::class)
+ actual fun readLine(): String? {
+ return reader.readLine()
+ }
+
+ @Throws(KommandException::class)
+ actual fun readAll(): String? {
+ return reader.readText()
+ }
+
+ @Throws(KommandException::class)
+ actual fun lines(): Sequence {
+ return reader.lineSequence()
+ }
+
+ fun close() {
+ reader.close()
+ }
+}
diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedWriter.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedWriter.jvm.kt
new file mode 100644
index 0000000..a60cdb1
--- /dev/null
+++ b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedWriter.jvm.kt
@@ -0,0 +1,23 @@
+package com.kgit2.kommand.io
+
+import com.kgit2.kommand.exception.KommandException
+
+actual class BufferedWriter(
+ private val writer: java.io.BufferedWriter,
+) {
+ @Throws(KommandException::class)
+ actual fun writeLine(line: String) {
+ writer.write(line)
+ writer.newLine()
+ }
+
+ @Throws(KommandException::class)
+ actual fun flush() {
+ writer.flush()
+ }
+
+ @Throws(KommandException::class)
+ actual fun close() {
+ writer.close()
+ }
+}
diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt
new file mode 100644
index 0000000..22c23cf
--- /dev/null
+++ b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt
@@ -0,0 +1,60 @@
+package com.kgit2.kommand.process
+
+import com.kgit2.kommand.exception.ErrorType
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.io.BufferedReader
+import com.kgit2.kommand.io.BufferedWriter
+import com.kgit2.kommand.io.Output
+import java.util.concurrent.atomic.AtomicReference
+
+actual class Child(
+ private val process: Process,
+) {
+ private var stdin: AtomicReference = AtomicReference(null)
+ private var stdout: AtomicReference = AtomicReference(null)
+ private var stderr: AtomicReference = AtomicReference(null)
+
+ actual fun id(): UInt {
+ return process.pid().toUInt()
+ }
+
+ actual fun bufferedStdin(): BufferedWriter? {
+ stdin.compareAndSet(null, BufferedWriter(process.outputWriter()))
+ return stdin.get()
+ }
+
+ actual fun bufferedStdout(): BufferedReader? {
+ stdout.compareAndSet(null, BufferedReader(process.inputReader()))
+ return stdout.get()
+ }
+
+ actual fun bufferedStderr(): BufferedReader? {
+ stderr.compareAndSet(null, BufferedReader(process.errorReader()))
+ return stderr.get()
+ }
+
+ @Throws(KommandException::class)
+ actual fun kill() {
+ stdin.get()?.close()
+ process.destroy()
+ }
+
+ @Throws(KommandException::class)
+ actual fun wait(): Int {
+ stdin.get()?.close()
+ return process.waitFor()
+ }
+
+ @Throws(KommandException::class)
+ actual fun waitWithOutput(): Output {
+ stdin.get()?.close()
+ val stdoutContent = runCatching { bufferedStdout()?.readAll() }
+ .getOrElse { throw KommandException("Child has been consumed", ErrorType.None) }
+ val stderrContent = runCatching { bufferedStderr()?.readAll() }
+ .getOrElse { throw KommandException("Child has been consumed", ErrorType.None) }
+ val status = process.waitFor()
+ stdout.get()?.close()
+ stderr.get()?.close()
+ return Output(status, stdoutContent, stderrContent)
+ }
+}
diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/process/Command.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/process/Command.jvm.kt
new file mode 100644
index 0000000..e101c34
--- /dev/null
+++ b/src/jvmMain/kotlin/com/kgit2/kommand/process/Command.jvm.kt
@@ -0,0 +1,100 @@
+package com.kgit2.kommand.process
+
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.io.Output
+import java.io.File
+
+actual class Command(
+ actual val command: String,
+ private val builder: ProcessBuilder,
+) {
+
+ actual constructor(command: String) : this(command, ProcessBuilder(command))
+
+ actual fun debugString(): String {
+ return "Command(command='$command', builder=${
+ builder.command()
+ .toMutableList()
+ .apply { this.removeFirst() }
+ .joinToString(",", "[", "]")
+ })"
+ }
+
+ actual fun arg(arg: String): Command {
+ builder.command().add(arg)
+ return this
+ }
+
+ actual fun args(args: List): Command {
+ builder.command().addAll(args)
+ return this
+ }
+
+ actual fun env(key: String, value: String): Command {
+ builder.environment()[key] = value
+ return this
+ }
+
+ actual fun envs(envs: Map): Command {
+ builder.environment().putAll(envs)
+ return this
+ }
+
+ actual fun removeEnv(key: String): Command {
+ builder.environment().remove(key)
+ return this
+ }
+
+ actual fun envClear(): Command {
+ builder.environment().clear()
+ return this
+ }
+
+ actual fun cwd(dir: String): Command {
+ builder.directory(File(dir))
+ return this
+ }
+
+ actual fun stdin(stdio: Stdio): Command {
+ builder.redirectInput(stdio.to())
+ return this
+ }
+
+ actual fun stdout(stdio: Stdio): Command {
+ builder.redirectOutput(stdio.to())
+ return this
+ }
+
+ actual fun stderr(stdio: Stdio): Command {
+ builder.redirectError(stdio.to())
+ return this
+ }
+
+ @Throws(KommandException::class)
+ actual fun spawn(): Child {
+ val process = builder.start()
+ return Child(process)
+ }
+
+ @Throws(KommandException::class)
+ actual fun output(): Output {
+ val process = builder.start()
+ val stdoutContent = process.inputReader().readText()
+ val stderrContent = process.errorReader().readText()
+ val status = process.waitFor()
+ return Output(status, stdoutContent, stderrContent)
+ }
+
+ @Throws(KommandException::class)
+ actual fun status(): Int {
+ return builder.start().waitFor()
+ }
+}
+
+fun Stdio.to(): ProcessBuilder.Redirect {
+ return when (this) {
+ Stdio.Inherit -> ProcessBuilder.Redirect.INHERIT
+ Stdio.Null -> ProcessBuilder.Redirect.DISCARD
+ Stdio.Pipe -> ProcessBuilder.Redirect.PIPE
+ }
+}
diff --git a/src/jvmMain/kotlin/com/kgit2/process/Child.kt b/src/jvmMain/kotlin/com/kgit2/process/Child.kt
deleted file mode 100644
index 34a3954..0000000
--- a/src/jvmMain/kotlin/com/kgit2/process/Child.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-package com.kgit2.process
-
-import com.kgit2.io.PlatformReader
-import com.kgit2.io.PlatformWriter
-import com.kgit2.io.Reader
-import com.kgit2.io.Writer
-import io.ktor.utils.io.errors.*
-import java.io.File
-
-actual class Child actual constructor(
- actual val command: String,
- actual val args: List,
- actual val envs: Map,
- actual val cwd: String?,
- actual val stdin: Stdio,
- actual val stdout: Stdio,
- actual val stderr: Stdio,
-) {
- actual var id: Int? = null
-
- private var process: Process? = null
-
- private var stdinWriter: Writer? = null
- private var stdoutReader: Reader? = null
- private var stderrReader: Reader? = null
-
- actual fun getChildStdin(): Writer? {
- return when (stdin) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> stdinWriter
- }
- }
-
- actual fun getChildStdout(): Reader? {
- return when (stdout) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> stdoutReader
- }
- }
-
- actual fun getChildStderr(): Reader? {
- return when (stderr) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> stderrReader
- }
- }
-
- @Throws(IOException::class)
- actual fun start(options: ChildOptions) {
- val processBuilder = ProcessBuilder(command, *args.toTypedArray())
- cwd?.let { processBuilder.directory(File(it)) }
- redirectStdio(processBuilder)
- runCatching {
- process = processBuilder.start()
- }.onFailure {
- throw IOException(it)
- }
- this.id = process?.pid()?.toInt()
- this.stdinWriter = createWriter(stdin, process!!)
- this.stdoutReader = createReader(stdout, process!!)
- this.stderrReader = createErrorReader(stderr, process!!)
- }
-
- @Throws(IOException::class)
- actual fun wait(): ChildExitStatus {
- val exitCode = try {
- stdinWriter?.close()
- val exitCode = process!!.waitFor()
- stdoutReader?.close()
- stderrReader?.close()
- exitCode
- } catch (e: InterruptedException) {
- 0x7F
- } catch (e: java.io.IOException) {
- throw IOException(e)
- }
- return ChildExitStatus(exitCode)
- }
-
- @Throws(IOException::class)
- actual fun waitWithOutput(): String? {
- return if (stdout != Stdio.Pipe) {
- stdinWriter?.close()
- process!!.waitFor()
- stderrReader?.close()
- null
- } else {
- stdinWriter?.close()
- process!!.waitFor()
- val output = StringBuilder()
- val reader = stdoutReader!!
- while (!reader.endOfInput) {
- output.append(reader.readText())
- }
- stdoutReader?.close()
- stderrReader?.close()
- output.toString()
- }
- }
-
- actual fun kill() {
- process?.destroy()
- }
-
- private fun redirectStdio(processBuilder: ProcessBuilder) {
- when (stdin) {
- Stdio.Inherit -> {
- processBuilder.redirectInput(ProcessBuilder.Redirect.INHERIT)
- }
-
- Stdio.Null -> {
- processBuilder.redirectInput(ProcessBuilder.Redirect.DISCARD)
- }
-
- Stdio.Pipe -> {
- processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE)
- }
- }
- when (stdout) {
- Stdio.Inherit -> {
- processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT)
- }
-
- Stdio.Null -> {
- processBuilder.redirectOutput(ProcessBuilder.Redirect.DISCARD)
- }
-
- Stdio.Pipe -> {
- processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE)
- }
- }
- when (stderr) {
- Stdio.Inherit -> {
- processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT)
- }
-
- Stdio.Null -> {
- processBuilder.redirectError(ProcessBuilder.Redirect.DISCARD)
- }
-
- Stdio.Pipe -> {
- processBuilder.redirectError(ProcessBuilder.Redirect.PIPE)
- }
- }
- }
-
- override fun toString(): String {
- return "Child(command='$command', args=$args, envs=$envs, cwd=$cwd, stdin=$stdin, stdout=$stdout, stderr=$stderr, id=$id, process=$process)"
- }
-
- actual fun prompt(): String {
- return "$command ${args.joinToString(" ")}"
- }
-
- companion object {
- private fun createWriter(stdio: Stdio, process: Process): Writer? {
- return when (stdio) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> {
- val outputStream = process.outputStream
- Writer(PlatformWriter(outputStream))
- }
- }
- }
-
- private fun createReader(stdio: Stdio, process: Process): Reader? {
- return when (stdio) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> {
- val inputStream = process.inputStream
- Reader(PlatformReader(inputStream))
- }
- }
- }
-
- private fun createErrorReader(stdio: Stdio, process: Process): Reader? {
- return when (stdio) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> {
- val errorStream = process.errorStream
- Reader(PlatformReader(errorStream))
- }
- }
- }
- }
-
-
-}
diff --git a/src/jvmTest/kotlin/process/CommandTest.jvm.kt b/src/jvmTest/kotlin/process/CommandTest.jvm.kt
deleted file mode 100644
index fae0fbe..0000000
--- a/src/jvmTest/kotlin/process/CommandTest.jvm.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package process
-
-import com.kgit2.process.Command
-import com.kgit2.process.Stdio
-import java.util.*
-import kotlin.test.assertEquals
-
-enum class OS {
- WIN,
- MAC,
- LINUX,
-}
-
-val osName = System.getProperty("os.name").lowercase(Locale.getDefault())
-val os: OS? = if (osName.contains("win")) {
- OS.WIN
-} else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) {
- OS.LINUX
-} else if (osName.contains("mac")) {
- OS.MAC
-} else null
-
-actual val eko: String =
- when (os) {
- OS.WIN -> "eko/target/release/eko.exe"
- else -> "eko/target/release/eko"
- }
-
-// actual val subCommand: String =
-// when (os) {
-// OS.WIN -> "sub_command/build/install/sub_command/bin/sub_command.bat"
-// else -> "sub_command/build/install/sub_command/bin/sub_command"
-// }
-
-actual fun shellTest() {
- when (os) {
- OS.WIN -> Unit
- else -> {
- val output = Command("sh")
- .args("-c", "f() { echo username=a; echo password=b; }; f get")
- .stdout(Stdio.Pipe)
- .spawn()
- .waitWithOutput()
- assertEquals("username=a\npassword=b\n", output)
- }
- }
-}
-
-actual fun envVar(key: String): String? {
- return System.getenv(key)
-}
-
-actual fun homeDir(): String? {
- return envVar("HOME")
-}
-
-actual fun pwd(): Command {
- return when (os) {
- OS.WIN -> Command("chdir")
- OS.MAC -> Command("pwd")
- OS.LINUX -> Command("pwd")
- null -> throw Exception("unknown os")
- }
-}
diff --git a/src/mingwX64Main/kotlin/com/kgit2/process/Child.kt b/src/mingwX64Main/kotlin/com/kgit2/process/Child.kt
deleted file mode 100644
index ffb6edc..0000000
--- a/src/mingwX64Main/kotlin/com/kgit2/process/Child.kt
+++ /dev/null
@@ -1,337 +0,0 @@
-package com.kgit2.process
-
-import com.kgit2.io.PlatformReader
-import com.kgit2.io.PlatformWriter
-import com.kgit2.io.Reader
-import com.kgit2.io.Writer
-import io.ktor.utils.io.errors.*
-import kotlinx.cinterop.Arena
-import kotlinx.cinterop.ByteVar
-import kotlinx.cinterop.CArrayPointer
-import kotlinx.cinterop.CPointer
-import kotlinx.cinterop.CValue
-import kotlinx.cinterop.MemScope
-import kotlinx.cinterop.UIntVar
-import kotlinx.cinterop.UShortVar
-import kotlinx.cinterop.alloc
-import kotlinx.cinterop.allocArray
-import kotlinx.cinterop.cValue
-import kotlinx.cinterop.convert
-import kotlinx.cinterop.invoke
-import kotlinx.cinterop.memScoped
-import kotlinx.cinterop.ptr
-import kotlinx.cinterop.reinterpret
-import kotlinx.cinterop.set
-import kotlinx.cinterop.sizeOf
-import kotlinx.cinterop.toKString
-import kotlinx.cinterop.value
-import platform.posix.FILE
-import platform.posix._open_osfhandle
-import platform.posix.fgets
-import platform.posix.intptr_tVar
-import platform.windows.CloseHandle
-import platform.windows.CreatePipe
-import platform.windows.CreateProcess
-import platform.windows.GetExitCodeProcess
-import platform.windows.GetLastError
-import platform.windows.HANDLEVar
-import platform.windows.HANDLE_FLAG_INHERIT
-import platform.windows.INFINITE
-import platform.windows.PROCESS_INFORMATION
-import platform.windows.SECURITY_ATTRIBUTES
-import platform.windows.STARTF_USESTDHANDLES
-import platform.windows.STARTUPINFO
-import platform.windows.SetHandleInformation
-import platform.windows.WaitForSingleObject
-
-actual class Child actual constructor(
- actual val command: String,
- actual val args: List,
- actual val envs: Map,
- actual val cwd: String?,
- actual val stdin: Stdio,
- actual val stdout: Stdio,
- actual val stderr: Stdio
-) {
- actual var id: Int? = null
-
- private var stdinWriter: Writer? = null
- private var stdoutReader: Reader? = null
- private var stderrReader: Reader? = null
-
- private val memory = Arena()
- private var processInformation = memory.alloc()
-
- actual fun getChildStdin(): Writer? {
- return when (stdin) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> stdinWriter
- }
- }
-
- actual fun getChildStdout(): Reader? {
- return when (stdout) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> stdoutReader
- }
- }
-
- actual fun getChildStderr(): Reader? {
- return when (stderr) {
- Stdio.Inherit, Stdio.Null -> null
- Stdio.Pipe -> stderrReader
- }
- }
-
- @Throws(IOException::class)
- actual fun start(options: ChildOptions): Unit = memScoped {
- val securityAttribute = createSecurityAttribute()
- val pipes = createPipe(securityAttribute.ptr)
- val startupInformation = createStartUpInformation(pipes)
- val cmdLine = createCMDLine(this)
- val cwd = createCurrentDirectory(this)
- // create child process
- val success = CreateProcess!!.invoke(
- null,
- cmdLine,
- null,
- null,
- 1,
- 0u,
- null,
- cwd,
- startupInformation.ptr,
- processInformation.ptr
- )
- if (success == 0) {
- val errorCode = GetLastError()
- throw IOException("CreateProcess failed: $errorCode")
- }
- redirectPipeHandle(pipes)
- id = processInformation.dwProcessId.toInt()
- openFileDescriptor(pipes)
- }
-
- @Throws(IOException::class)
- actual fun wait(): ChildExitStatus {
- stdinWriter?.close()
- WaitForSingleObject(processInformation.hProcess, INFINITE)
- val exitCode = memory.alloc()
- GetExitCodeProcess(processInformation.hProcess, exitCode.ptr)
- stdoutReader?.close()
- stderrReader?.close()
- val status = ChildExitStatus(exitCode.value.toInt())
- CloseHandle(processInformation.hProcess)
- CloseHandle(processInformation.hThread)
- memory.clear()
- return status
- }
-
- @Throws(IOException::class)
- actual fun waitWithOutput(): String? {
- return if (stdout != Stdio.Pipe) {
- stdinWriter?.close()
- val exitCode = memory.alloc()
- GetExitCodeProcess(processInformation.hProcess, exitCode.ptr)
- CloseHandle(processInformation.hProcess)
- CloseHandle(processInformation.hThread)
- memory.clear()
- null
- } else {
- stdinWriter?.close()
- val output = StringBuilder()
- val reader = stdoutReader!!
- while (!reader.endOfInput) {
- output.append(reader.readText())
- }
- WaitForSingleObject(processInformation.hProcess, INFINITE)
- stdoutReader?.close()
- stderrReader?.close()
- CloseHandle(processInformation.hProcess)
- CloseHandle(processInformation.hThread)
- memory.clear()
- output.toString()
- }
- }
-
- actual fun kill() {
- }
-
- actual fun prompt(): String {
- TODO("Not yet implemented")
- }
-
- private fun createSecurityAttribute(): CValue {
- return cValue() {
- nLength = sizeOf().convert()
- bInheritHandle = 1
- lpSecurityDescriptor = null
- }
- }
-
- private fun createPipe(saAttr: CPointer): CreatePipeResult {
- val pipes = CreatePipeResult()
- when (stdin) {
- Stdio.Pipe, Stdio.Null -> {
- pipes.stdinPipeReaderHandle = memory.alloc()
- pipes.stdinPipeWriterHandle = memory.alloc()
- // first param is read handle, second param is write handle
- CreatePipe(pipes.stdinPipeReaderHandle!!.ptr, pipes.stdinPipeWriterHandle!!.ptr, saAttr, 0u)
- // set handle information for parent process
- SetHandleInformation(pipes.stdinPipeWriterHandle!!.value, HANDLE_FLAG_INHERIT.convert(), 0u)
- }
- else -> Unit
- }
- when (stdout) {
- Stdio.Pipe, Stdio.Null -> {
- pipes.stdoutPipeReaderHandle = memory.alloc()
- pipes.stdoutPipeWriterHandle = memory.alloc()
- CreatePipe(pipes.stdoutPipeReaderHandle!!.ptr, pipes.stdoutPipeWriterHandle!!.ptr, saAttr, 0u)
- // set handle information for parent process
- SetHandleInformation(pipes.stdoutPipeReaderHandle!!.value, HANDLE_FLAG_INHERIT.convert(), 0u)
- }
- else -> Unit
- }
- when (stderr) {
- Stdio.Pipe, Stdio.Null -> {
- pipes.stderrPipeReaderHandle = memory.alloc()
- pipes.stderrPipeWriterHandle = memory.alloc()
- CreatePipe(pipes.stderrPipeReaderHandle!!.ptr, pipes.stderrPipeWriterHandle!!.ptr, saAttr, 0u)
- // set handle information for parent process
- SetHandleInformation(pipes.stderrPipeReaderHandle!!.value, HANDLE_FLAG_INHERIT.convert(), 0u)
- }
- else -> Unit
- }
- return pipes
- }
-
- private fun createStartUpInformation(pipes: CreatePipeResult): CValue {
- return cValue {
- cb = sizeOf().convert()
- pipes.stdinPipeReaderHandle?.let {
- hStdInput = it.value
- }
- pipes.stdoutPipeWriterHandle?.let {
- hStdOutput = it.value
- }
- pipes.stderrPipeWriterHandle?.let {
- hStdError = it.value
- }
- if (!(stdin == Stdio.Inherit && stdout == Stdio.Inherit && stderr == Stdio.Inherit)) {
- dwFlags = dwFlags or STARTF_USESTDHANDLES.convert()
- }
- }
- }
-
- private fun createCMDLine(memory: MemScope): CArrayPointer {
- val cmdLineString = listOf(command, *args.toTypedArray()).joinToString(" ")
- val cmdLine = memory.allocArray(cmdLineString.length.convert())
- cmdLineString.forEachIndexed { index, c ->
- cmdLine[index] = c.code.toUShort()
- }
- return cmdLine
- }
-
- private fun createCurrentDirectory(memory: MemScope): CPointer? {
- return cwd?.let {
- val currentDirectory = memory.allocArray(it.length.convert())
- it.forEachIndexed { index, c ->
- currentDirectory[index] = c.code.toUShort()
- }
- currentDirectory
- }
- }
-
- private fun redirectPipeHandle(pipes: CreatePipeResult) {
- when (stdin) {
- Stdio.Pipe, Stdio.Null -> CloseHandle(pipes.stdinPipeReaderHandle!!.value)
- Stdio.Inherit -> Unit
- }
-
- when (stdout) {
- Stdio.Pipe, Stdio.Null -> CloseHandle(pipes.stdoutPipeWriterHandle!!.value)
- Stdio.Inherit -> Unit
- }
-
- when (stderr) {
- Stdio.Pipe, Stdio.Null -> CloseHandle(pipes.stderrPipeWriterHandle!!.value)
- Stdio.Inherit -> Unit
- }
- }
-
- private fun openFileDescriptor(pipes: CreatePipeResult) {
- when (stdin) {
- Stdio.Pipe -> {
- if (pipes.stdinPipeWriterHandle != null) {
- val fd = _open_osfhandle(pipes.stdinPipeWriterHandle!!.reinterpret().value, 0x0001)
- val file = fdopen(fd, "w")
- stdinWriter = Writer(PlatformWriter(file))
- }
- }
- else -> Unit
- }
-
- when (stdout) {
- Stdio.Pipe -> {
- if (pipes.stdoutPipeReaderHandle != null) {
- val fd = _open_osfhandle(pipes.stdoutPipeReaderHandle!!.reinterpret().value, 0x0000)
- val file = fdopen(fd, "r")
- stdoutReader = Reader(PlatformReader(file))
- }
- }
- else -> Unit
- }
-
- when (stderr) {
- Stdio.Pipe -> {
- if (pipes.stderrPipeReaderHandle != null) {
- val fd = _open_osfhandle(pipes.stderrPipeReaderHandle!!.reinterpret().value, 0x0000)
- val file = fdopen(fd, "r")
- stderrReader = Reader(PlatformReader(file))
- }
- }
- else -> Unit
- }
- }
-
- @Throws(IOException::class)
- private fun fdopen(fileDescriptor: Int, mode: String): CPointer {
- return when (val file = platform.posix.fdopen(fileDescriptor, mode)) {
- null -> throw IOException("Invalid mode.")
- else -> file
- }
- }
-
- // just for debug step by step
- private fun readFromFD(stdoutReader: HANDLEVar) {
- val fd = _open_osfhandle(stdoutReader.reinterpret().value, 0)
- println("fd: $fd")
- val file = platform.posix.fdopen(fd, "r")
- memScoped {
- val buf = allocArray(4096)
- while (true) {
- val result = fgets(buf, 4096, file)
- if (result != null) {
- print(result.toKString())
- } else {
- break
- }
- }
- }
- }
-}
-
-data class CreatePipeResult(
- // child process read from here for stdin
- var stdinPipeReaderHandle: HANDLEVar? = null,
- // parent process write to here for stdin
- var stdinPipeWriterHandle: HANDLEVar? = null,
- // parent process read from here for stdout
- var stdoutPipeReaderHandle: HANDLEVar? = null,
- // parent process write to here for stdout
- var stdoutPipeWriterHandle: HANDLEVar? = null,
- // parent process read from here for stderr
- var stderrPipeReaderHandle: HANDLEVar? = null,
- // parent process write to here for stderr
- var stderrPipeWriterHandle: HANDLEVar? = null,
-)
diff --git a/src/mingwX64Test/kotlin/process/CommandTest.mingwX64.kt b/src/mingwX64Test/kotlin/process/CommandTest.mingwX64.kt
deleted file mode 100644
index 4af4d2c..0000000
--- a/src/mingwX64Test/kotlin/process/CommandTest.mingwX64.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package process
-
-import com.kgit2.process.Command
-import kotlinx.cinterop.UShortVar
-import kotlinx.cinterop.allocArray
-import kotlinx.cinterop.convert
-import kotlinx.cinterop.get
-import kotlinx.cinterop.invoke
-import kotlinx.cinterop.memScoped
-import kotlinx.cinterop.set
-import platform.windows.GetEnvironmentVariable
-
-actual val eko: String = "eko/target/release/eko"
-
-actual fun shellTest() {}
-
-actual fun envVar(key: String): String? = memScoped {
- val lpSize: UInt = 10240u
- val lpBuffer = allocArray(10240)
- val lpName = allocArray(key.length.convert())
- key.forEachIndexed { index, c ->
- lpName[index] = c.code.toUShort()
- }
- val size = GetEnvironmentVariable!!.invoke(lpName, lpBuffer, lpSize)
- if (size == 0u) {
- return null
- } else {
- val buffer = CharArray(size.toInt())
- for (i in 0 until size.toInt()) {
- buffer[i] = lpBuffer[i].toInt().toChar()
- }
- return buffer.joinToString("")
- }
-}
-
-actual fun homeDir(): String? {
- return envVar("userprofile")
-}
-
-actual fun pwd(): Command {
- return Command("chdir")
-}
diff --git a/src/nativeInterop/cinterop/aarch64-apple-darwin.def b/src/nativeInterop/cinterop/aarch64-apple-darwin.def
new file mode 100644
index 0000000..83f691d
--- /dev/null
+++ b/src/nativeInterop/cinterop/aarch64-apple-darwin.def
@@ -0,0 +1,4 @@
+headers = kommand_core.h
+staticLibraries = libkommand_core.a
+compilerOpts = -Ikommand-core
+libraryPaths = kommand-core/target/aarch64-apple-darwin/release
diff --git a/src/nativeInterop/cinterop/aarch64-unknown-linux-gnu.def b/src/nativeInterop/cinterop/aarch64-unknown-linux-gnu.def
new file mode 100644
index 0000000..c2ab1ee
--- /dev/null
+++ b/src/nativeInterop/cinterop/aarch64-unknown-linux-gnu.def
@@ -0,0 +1,4 @@
+headers = kommand_core.h
+staticLibraries = libkommand_core.a
+compilerOpts = -Ikommand-core
+libraryPaths = kommand-core/target/aarch64-unknown-linux-gnu/release
diff --git a/src/nativeInterop/cinterop/x86_64-apple-darwin.def b/src/nativeInterop/cinterop/x86_64-apple-darwin.def
new file mode 100644
index 0000000..c683f38
--- /dev/null
+++ b/src/nativeInterop/cinterop/x86_64-apple-darwin.def
@@ -0,0 +1,4 @@
+headers = kommand_core.h
+staticLibraries = libkommand_core.a
+compilerOpts = -Ikommand-core
+libraryPaths = kommand-core/target/x86_64-apple-darwin/release
diff --git a/src/nativeInterop/cinterop/x86_64-pc-windows-gnu.def b/src/nativeInterop/cinterop/x86_64-pc-windows-gnu.def
new file mode 100644
index 0000000..2ddc243
--- /dev/null
+++ b/src/nativeInterop/cinterop/x86_64-pc-windows-gnu.def
@@ -0,0 +1,5 @@
+headers = kommand_core.h
+staticLibraries = libkommand_core.a
+compilerOpts = -Ikommand-core
+linkerOpts = -static -lws2_32 -lbcrypt -luserenv -lntdll -v
+libraryPaths = kommand-core/target/x86_64-pc-windows-gnu/release
diff --git a/src/nativeInterop/cinterop/x86_64-unknown-linux-gnu.def b/src/nativeInterop/cinterop/x86_64-unknown-linux-gnu.def
new file mode 100644
index 0000000..5de6138
--- /dev/null
+++ b/src/nativeInterop/cinterop/x86_64-unknown-linux-gnu.def
@@ -0,0 +1,4 @@
+headers = kommand_core.h
+staticLibraries = libkommand_core.a
+compilerOpts = -Ikommand-core
+libraryPaths = kommand-core/target/x86_64-unknown-linux-gnu/release
diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt b/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt
new file mode 100644
index 0000000..29c2ac7
--- /dev/null
+++ b/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt
@@ -0,0 +1,100 @@
+package com.kgit2.kommand
+
+import com.kgit2.kommand.exception.ErrorType
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.io.Output
+import com.kgit2.kommand.process.Child
+import com.kgit2.kommand.process.Stdio
+import kommand_core.drop_output
+import kommand_core.drop_string
+import kommand_core.into_output
+import kommand_core.void_to_string
+import kotlinx.cinterop.ByteVar
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.CValue
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.pointed
+import kotlinx.cinterop.toKString
+
+inline fun CPointer.asString(): String {
+ val result = this.toKString()
+ drop_string(this)
+ return result
+}
+
+@Throws(KommandException::class)
+fun Child.Companion.from(result: CValue): Child = memScoped {
+ if (result.ptr.pointed.ok != null) {
+ com.kgit2.kommand.process.Child(result.ptr.pointed.ok)
+ } else if (result.ptr.pointed.err != null) {
+ throw KommandException(
+ result.ptr.pointed.err?.asString(),
+ result.ptr.pointed.error_type.to()
+ )
+ } else {
+ throw KommandException("[spawn_command] return [result]'s [ok] & [err] are both null", com.kgit2.kommand.exception.ErrorType.Unknown)
+ }
+}
+
+@Throws(KommandException::class)
+fun Output.Companion.from(result: CValue): Output = memScoped {
+ if (result.ptr.pointed.err != null) {
+ val errPtr = result.ptr.pointed.err!!
+ throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to())
+ } else {
+ val output = into_output(result.ptr.pointed.ok)
+ val newOutput = Output(
+ output.getPointer(memScope).pointed.exit_code,
+ output.getPointer(memScope).pointed.stdout_content?.toKString(),
+ output.getPointer(memScope).pointed.stderr_content?.toKString(),
+ )
+ drop_output(output)
+ newOutput
+ }
+}
+
+@Throws(KommandException::class)
+fun Int.Companion.from(result: CValue): Int = memScoped {
+ if (result.ptr.pointed.err != null) {
+ val errPtr = result.ptr.pointed.err!!
+ throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to())
+ } else {
+ return result.ptr.pointed.ok
+ }
+}
+
+@Throws(KommandException::class)
+fun String.Companion.from(result: CValue): String? = memScoped {
+ if (result.ptr.pointed.err != null) {
+ val errPtr = result.ptr.pointed.err!!
+ throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to())
+ } else {
+ return void_to_string(result.ptr.pointed.ok)?.asString()
+ }
+}
+
+@Throws(KommandException::class)
+fun CValue.unwrap() = memScoped {
+ if (this@unwrap.ptr.pointed.err != null) {
+ val errPtr = this@unwrap.ptr.pointed.err!!
+ throw KommandException(errPtr.asString(), this@unwrap.ptr.pointed.error_type.to())
+ }
+}
+
+fun Stdio.to(): kommand_core.Stdio {
+ return when (this) {
+ Stdio.Inherit -> kommand_core.Stdio.Inherit
+ Stdio.Pipe -> kommand_core.Stdio.Pipe
+ Stdio.Null -> kommand_core.Stdio.Null
+ }
+}
+
+fun kommand_core.ErrorType.to(): ErrorType {
+ return when (this) {
+ kommand_core.ErrorType.None -> ErrorType.None
+ kommand_core.ErrorType.Io -> ErrorType.IO
+ kommand_core.ErrorType.Utf8 -> ErrorType.Utf8
+ kommand_core.ErrorType.Unknown -> ErrorType.Unknown
+ else -> throw KommandException("Unknown error type: $this", ErrorType.Unknown)
+ }
+}
diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/Platform.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/Platform.native.kt
new file mode 100644
index 0000000..1843f1b
--- /dev/null
+++ b/src/nativeMain/kotlin/com/kgit2/kommand/Platform.native.kt
@@ -0,0 +1,24 @@
+package com.kgit2.kommand
+
+actual val platform: Platform
+ get() {
+ val os = kotlin.native.Platform.osFamily
+ val arch = kotlin.native.Platform.cpuArchitecture
+ return when (os) {
+ OsFamily.MACOSX -> when (arch) {
+ CpuArchitecture.ARM64 -> Platform.MACOS_ARM64
+ CpuArchitecture.X64 -> Platform.MACOS_X64
+ else -> throw Exception("Unsupported platform: $os $arch")
+ }
+ OsFamily.LINUX -> when (arch) {
+ CpuArchitecture.ARM64 -> Platform.LINUX_ARM64
+ CpuArchitecture.X64 -> Platform.LINUX_X64
+ else -> throw Exception("Unsupported platform: $os $arch")
+ }
+ OsFamily.WINDOWS -> when (arch) {
+ CpuArchitecture.X64 -> Platform.MINGW_X64
+ else -> throw Exception("Unsupported platform: $os $arch")
+ }
+ else -> throw Exception("Unsupported platform: $os $arch")
+ }
+ }
diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/env/EnvVars.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/env/EnvVars.native.kt
new file mode 100644
index 0000000..7a16597
--- /dev/null
+++ b/src/nativeMain/kotlin/com/kgit2/kommand/env/EnvVars.native.kt
@@ -0,0 +1,31 @@
+package com.kgit2.kommand.env
+
+import com.kgit2.kommand.asString
+import kommand_core.drop_env_vars
+import kommand_core.env_var
+import kommand_core.env_vars
+import kotlinx.cinterop.convert
+import kotlinx.cinterop.get
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.pointed
+import kotlinx.cinterop.toKString
+
+actual fun envVar(key: String): String? {
+ return env_var(key)?.asString()
+}
+
+actual fun envVars(): Map? = memScoped {
+ val envVars = env_vars()
+ val map = mutableMapOf()
+ val length = envVars.ptr.pointed.len
+ val envVarsValue = envVars.ptr.pointed
+ (0UL until length).forEach { i ->
+ val name = envVarsValue.names?.get(i.convert())?.toKString()
+ val value = envVarsValue.values?.get(i.convert())?.toKString()
+ if (name != null && value != null) {
+ map[name] = value
+ }
+ }
+ drop_env_vars(envVars)
+ map
+}
diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedReader.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedReader.native.kt
new file mode 100644
index 0000000..210a3f7
--- /dev/null
+++ b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedReader.native.kt
@@ -0,0 +1,89 @@
+package com.kgit2.kommand.io
+
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.from
+import kommand_core.drop_stderr
+import kommand_core.drop_stdout
+import kommand_core.read_all_stderr
+import kommand_core.read_all_stdout
+import kommand_core.read_line_stderr
+import kommand_core.read_line_stdout
+import kotlinx.cinterop.COpaquePointer
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import kotlinx.cinterop.value
+import kotlin.native.ref.createCleaner
+
+actual class BufferedReader(
+ private val inner: COpaquePointer?,
+ private val type: ReaderType,
+) {
+
+ val cleaner = createCleaner(inner) { reader ->
+ when (type) {
+ ReaderType.STDOUT -> {
+ drop_stdout(reader)
+ }
+ ReaderType.STDERR -> {
+ drop_stderr(reader)
+ }
+ }
+ }
+
+ @Throws(KommandException::class)
+ actual fun readLine(): String? = run {
+ memScoped {
+ val size = alloc(0uL)
+ val result = when (type) {
+ ReaderType.STDOUT -> {
+ read_line_stdout(inner, size.ptr)
+ }
+ ReaderType.STDERR -> {
+ read_line_stderr(inner, size.ptr)
+ }
+ }
+ if (size.value == 0uL) {
+ null
+ } else {
+ String.from(result)
+ }
+ }
+ }
+
+ @Throws(KommandException::class)
+ actual fun readAll(): String? = run {
+ memScoped {
+ val size = alloc(0uL)
+ val result = when (type) {
+ ReaderType.STDOUT -> {
+ read_all_stdout(inner, size.ptr)
+ }
+ ReaderType.STDERR -> {
+ read_all_stderr(inner, size.ptr)
+ }
+ }
+ if (size.value == 0uL) {
+ null
+ } else {
+ String.from(result)
+ }
+ }
+ }
+
+ @Throws(KommandException::class)
+ actual fun lines(): Sequence = sequence {
+ do {
+ val line = readLine()
+ if (line != null) {
+ yield(line)
+ }
+ } while (line != null)
+ }
+}
+
+enum class ReaderType {
+ STDOUT,
+ STDERR,
+ ;
+}
diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedWriter.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedWriter.native.kt
new file mode 100644
index 0000000..e4e5868
--- /dev/null
+++ b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedWriter.native.kt
@@ -0,0 +1,38 @@
+package com.kgit2.kommand.io
+
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.unwrap
+import kommand_core.drop_stdin
+import kommand_core.flush_stdin
+import kommand_core.write_line_stdin
+import kotlinx.atomicfu.atomic
+import kotlinx.cinterop.COpaquePointer
+import kotlin.native.ref.createCleaner
+
+actual class BufferedWriter(
+ private var inner: COpaquePointer?
+) {
+ private val isClosed = atomic(false)
+
+ private val cleaner = createCleaner(isClosed to inner) { (freed, writter) ->
+ if (freed.compareAndSet(expect = false, update = true)) {
+ drop_stdin(writter)
+ }
+ }
+
+ @Throws(KommandException::class)
+ actual fun writeLine(line: String) = run {
+ write_line_stdin(inner, line).unwrap()
+ }
+
+ @Throws(KommandException::class)
+ actual fun flush() = run {
+ flush_stdin(inner).unwrap()
+ }
+
+ @Throws(KommandException::class)
+ actual fun close() {
+ drop_stdin(inner)
+ isClosed.getAndSet(true)
+ }
+}
diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt
new file mode 100644
index 0000000..1ea8f59
--- /dev/null
+++ b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt
@@ -0,0 +1,79 @@
+package com.kgit2.kommand.process
+
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.from
+import com.kgit2.kommand.io.BufferedReader
+import com.kgit2.kommand.io.BufferedWriter
+import com.kgit2.kommand.io.Output
+import com.kgit2.kommand.io.ReaderType
+import com.kgit2.kommand.unwrap
+import kommand_core.buffered_stderr_child
+import kommand_core.buffered_stdin_child
+import kommand_core.buffered_stdout_child
+import kommand_core.drop_child
+import kommand_core.id_child
+import kommand_core.kill_child
+import kommand_core.wait_child
+import kommand_core.wait_with_output_child
+import kotlinx.atomicfu.atomic
+import kotlinx.atomicfu.locks.SynchronizedObject
+import kotlinx.cinterop.COpaquePointer
+import kotlin.concurrent.AtomicReference
+import kotlin.native.ref.createCleaner
+
+actual class Child(
+ private var inner: COpaquePointer?
+): SynchronizedObject() {
+ private var stdin: AtomicReference = AtomicReference(null)
+ private var stdout: AtomicReference = AtomicReference(null)
+ private var stderr: AtomicReference = AtomicReference(null)
+
+ private val isClosed = atomic(false)
+
+ private val cleaner = createCleaner(isClosed to inner) { (freed, child) ->
+ if (freed.compareAndSet(expect = false, update = true)) {
+ drop_child(child)
+ }
+ }
+
+ companion object;
+
+ actual fun id(): UInt {
+ return id_child(inner)
+ }
+
+ actual fun bufferedStdin(): BufferedWriter? {
+ stdin.compareAndSet(null, BufferedWriter(buffered_stdin_child(inner)))
+ return stdin.value
+ }
+
+ actual fun bufferedStdout(): BufferedReader? {
+ stdout.compareAndSet(null, BufferedReader(buffered_stdout_child(inner), ReaderType.STDOUT))
+ return stdout.value
+ }
+
+ actual fun bufferedStderr(): BufferedReader? {
+ stderr.compareAndSet(null, BufferedReader(buffered_stderr_child(inner), ReaderType.STDERR))
+ return stderr.value
+ }
+
+ @Throws(KommandException::class)
+ actual fun kill() = run {
+ kill_child(inner).unwrap()
+ }
+
+ @Throws(KommandException::class)
+ actual fun wait(): Int = run {
+ stdin.getAndSet(null)?.close()
+ Int.from(wait_child(inner))
+ }
+
+ @Throws(KommandException::class)
+ actual fun waitWithOutput(): Output = run {
+ stdin.getAndSet(null)?.close()
+ val inner = this.inner
+ this.inner = null
+ isClosed.getAndSet(true)
+ Output.from(wait_with_output_child(inner))
+ }
+}
diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/process/Command.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/process/Command.native.kt
new file mode 100644
index 0000000..67b0b03
--- /dev/null
+++ b/src/nativeMain/kotlin/com/kgit2/kommand/process/Command.native.kt
@@ -0,0 +1,111 @@
+package com.kgit2.kommand.process
+
+import com.kgit2.kommand.asString
+import com.kgit2.kommand.exception.KommandException
+import com.kgit2.kommand.from
+import com.kgit2.kommand.io.Output
+import com.kgit2.kommand.to
+import kommand_core.arg_command
+import kommand_core.current_dir_command
+import kommand_core.display_command
+import kommand_core.drop_command
+import kommand_core.env_clear_command
+import kommand_core.env_command
+import kommand_core.new_command
+import kommand_core.output_command
+import kommand_core.remove_env_command
+import kommand_core.spawn_command
+import kommand_core.status_command
+import kommand_core.stderr_command
+import kommand_core.stdin_command
+import kommand_core.stdout_command
+import kotlinx.cinterop.COpaquePointer
+import kotlin.native.ref.createCleaner
+
+actual class Command(
+ actual val command: String,
+ private val inner: COpaquePointer?,
+) {
+ actual constructor(command: String) : this(command, new_command(command))
+
+ private val cleaner = createCleaner(inner) { command ->
+ drop_command(command)
+ }
+
+ override fun toString(): String {
+ return display_command(inner)?.asString() ?: "null"
+ }
+
+ actual fun debugString(): String {
+ return display_command(inner)?.asString() ?: "null"
+ }
+
+ actual fun arg(arg: String): Command {
+ arg_command(inner, arg)
+ return this
+ }
+
+ actual fun args(args: List): Command {
+ for (arg in args) {
+ arg_command(inner, arg)
+ }
+ return this
+ }
+
+ actual fun env(key: String, value: String): Command {
+ env_command(inner, key, value)
+ return this
+ }
+
+ actual fun envs(envs: Map): Command {
+ for ((key, value) in envs) {
+ env_command(inner, key, value)
+ }
+ return this
+ }
+
+ actual fun removeEnv(key: String): Command {
+ remove_env_command(inner, key)
+ return this
+ }
+
+ actual fun envClear(): Command {
+ env_clear_command(inner)
+ return this
+ }
+
+ actual fun cwd(dir: String): Command {
+ current_dir_command(inner, dir)
+ return this
+ }
+
+ actual fun stdin(stdio: Stdio): Command {
+ stdin_command(inner, stdio.to())
+ return this
+ }
+
+ actual fun stdout(stdio: Stdio): Command {
+ stdout_command(inner, stdio.to())
+ return this
+ }
+
+ actual fun stderr(stdio: Stdio): Command {
+ stderr_command(inner, stdio.to())
+ return this
+ }
+
+ @Throws(KommandException::class)
+ actual fun spawn(): Child = run {
+ Child.from(spawn_command(inner))
+ }
+
+ @Throws(KommandException::class)
+ actual fun output(): Output = run {
+ Output.from(output_command(inner))
+ }
+
+ @Throws(KommandException::class)
+ actual fun status(): Int = run {
+ Int.from(status_command(inner))
+ }
+}
diff --git a/src/posixMain/kotlin/com/kgit2/io/PlatformReader.kt b/src/posixMain/kotlin/com/kgit2/io/PlatformReader.kt
deleted file mode 100644
index 9baa92f..0000000
--- a/src/posixMain/kotlin/com/kgit2/io/PlatformReader.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import io.ktor.utils.io.charsets.*
-import io.ktor.utils.io.core.*
-import kotlinx.cinterop.ByteVar
-import kotlinx.cinterop.CPointer
-import kotlinx.cinterop.allocArray
-import kotlinx.cinterop.memScoped
-import kotlinx.cinterop.toKString
-import platform.posix.FILE
-import platform.posix.fclose
-import platform.posix.fgets
-import kotlin.math.min
-
-actual class PlatformReader(file: CPointer) {
- private var file: CPointer? = file
- private val queue: ArrayDeque = ArrayDeque()
-
- actual fun closeSource() {
- fclose(file)
- }
-
- actual fun fill(destination: Memory, offset: Int, length: Int): Int {
- return memScoped {
- var readed = 0
- val migrateBuffer = {
- for (i in 0 until min(length, queue.size)) {
- destination.storeAt(offset + i, queue.removeFirst())
- readed += 1
- }
- }
- when {
- length < queue.size -> {
- migrateBuffer()
- }
- else -> {
- val buffer = allocArray(length)
- val line = fgets(buffer, length, file)
- line?.toKString()?.apply {
- queue.addAll(this.toByteArray(Charsets.UTF_8).toList())
- migrateBuffer()
- }
- }
- }
- readed
- }
- }
-}
diff --git a/src/posixMain/kotlin/com/kgit2/io/PlatformWriter.kt b/src/posixMain/kotlin/com/kgit2/io/PlatformWriter.kt
deleted file mode 100644
index 59af908..0000000
--- a/src/posixMain/kotlin/com/kgit2/io/PlatformWriter.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.kgit2.io
-
-import io.ktor.utils.io.bits.*
-import kotlinx.cinterop.CPointer
-import kotlinx.cinterop.addressOf
-import kotlinx.cinterop.convert
-import kotlinx.cinterop.usePinned
-import platform.posix.FILE
-import platform.posix.fclose
-import platform.posix.fflush
-import platform.posix.fwrite
-
-actual class PlatformWriter(file: CPointer) {
- var file: CPointer? = file
-
- actual fun flush(source: Memory, offset: Int, length: Int) {
- val buffer = ByteArray(length)
- source.copyTo(buffer, offset, length, 0)
- buffer.usePinned {
- fwrite(it.addressOf(0), 1u, length.convert(), file)
- }
- fflush(file)
- }
-
- actual fun close() {
- fclose(file)
- }
-}
diff --git a/src/posixTest/kotlin/process/CommandTest.posix.kt b/src/posixTest/kotlin/process/CommandTest.posix.kt
deleted file mode 100644
index 571c0a4..0000000
--- a/src/posixTest/kotlin/process/CommandTest.posix.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package process
-
-import com.kgit2.process.Command
-import com.kgit2.process.Stdio
-import kotlinx.cinterop.toKStringFromUtf8
-import platform.posix.getenv
-import kotlin.test.assertEquals
-
-actual val eko: String = "eko/target/release/eko"
-
-// actual val subCommand: String = "sub_command/build/install/sub_command/bin/sub_command"
-
-actual fun shellTest() {
- val output = Command("sh")
- .args("-c", "f() { echo username=a; echo password=b; }; f get")
- .stdout(Stdio.Pipe)
- .spawn()
- .waitWithOutput()
- assertEquals("username=a\npassword=b\n", output)
-}
-
-actual fun envVar(key: String): String? {
- return getenv(key)?.toKStringFromUtf8()
-}
-
-actual fun homeDir(): String? {
- return envVar("HOME")
-}
-
-actual fun pwd(): Command {
- return Command("pwd")
-}
diff --git a/src/unixLikeMain/kotlin/com/kgit2/process/Child.kt b/src/unixLikeMain/kotlin/com/kgit2/process/Child.kt
deleted file mode 100644
index b91513f..0000000
--- a/src/unixLikeMain/kotlin/com/kgit2/process/Child.kt
+++ /dev/null
@@ -1,230 +0,0 @@
-package com.kgit2.process
-
-import com.kgit2.io.Reader
-import com.kgit2.io.Writer
-import com.kgit2.process.Stdio.Inherit
-import com.kgit2.process.Stdio.Null
-import com.kgit2.process.Stdio.Pipe
-import io.ktor.utils.io.errors.*
-import kotlinx.cinterop.CPointer
-import kotlinx.cinterop.memScoped
-import platform.posix.FILE
-import platform.posix.SIGTERM
-import platform.posix.STDERR_FILENO
-import platform.posix.STDIN_FILENO
-import platform.posix.STDOUT_FILENO
-
-const val READ_END = 0
-const val WRITE_END = 1
-
-actual class Child actual constructor(
- actual val command: String,
- actual val args: List,
- actual val envs: Map,
- actual val cwd: String?,
- actual val stdin: Stdio,
- actual val stdout: Stdio,
- actual val stderr: Stdio,
-) {
- actual var id: Int? = null
-
- private var stdinWriter: Writer? = null
- private var stdoutReader: Reader? = null
- private var stderrReader: Reader? = null
-
- private val stdinPipe = IntArray(2)
- private val stdoutPipe = IntArray(2)
- private val stderrPipe = IntArray(2)
-
- private var options = ChildOptions.W_UNTRACED
-
- actual fun getChildStdin(): Writer? {
- return when (stdin) {
- Inherit, Null -> null
- Pipe -> stdinWriter
- }
- }
-
- actual fun getChildStdout(): Reader? {
- return when (stdout) {
- Inherit, Null -> null
- Pipe -> stdoutReader
- }
- }
-
- actual fun getChildStderr(): Reader? {
- return when (stderr) {
- Inherit, Null -> null
- Pipe -> stderrReader
- }
- }
-
- @Throws(IOException::class)
- actual fun start(options: ChildOptions) {
- this.options = options
- memScoped {
- createPipe()
- val childPid = Posix.fork()
- processChild(childPid)
- processLocal(childPid)
- }
- }
-
- @Throws(IOException::class)
- actual fun wait(): ChildExitStatus {
- stdinWriter?.close()
- val status = Posix.waitpid(id!!, options.value)
- stdoutReader?.close()
- stderrReader?.close()
- return status
- }
-
- @Throws(IOException::class)
- actual fun waitWithOutput(): String? {
- return if (stdout != Pipe) {
- stdinWriter?.close()
- Posix.waitpid(id!!, options.value)
- null
- } else {
- stdinWriter?.close()
- Posix.waitpid(id!!, options.value)
- val output = StringBuilder()
- val reader = stdoutReader!!
- while (!reader.endOfInput) {
- output.append(reader.readText())
- }
- stdoutReader?.close()
- stderrReader?.close()
- output.toString()
- }
- }
-
- @Throws(IOException::class)
- actual fun kill() {
- assert(id != null)
- Posix.kill(id!!, SIGTERM)
- }
-
- @Throws(IOException::class)
- private fun processChild(childPid: Int) {
- if (childPid == 0) {
- redirectFileDescriptor()
- if (cwd != null) {
- Posix.chdir(cwd)
- }
- val commands = listOf(command, *args.toTypedArray(), null)
- Posix.execvp(commands)
- }
- }
-
- private fun processLocal(childPid: Int) {
- if (childPid > 0) {
- this@Child.id = childPid
- val (stdinFile, stdoutFile, stderrFile) = openFileDescriptor()
- if (stdinFile != null) {
- this.stdinWriter = Posix.createWriter(stdinFile)
- }
- if (stdoutFile != null) {
- this.stdoutReader = Posix.createReader(stdoutFile)
- }
- if (stderrFile != null) {
- this.stderrReader = Posix.createReader(stderrFile)
- }
- }
- }
-
- private fun createPipe() {
- val pipes = mutableListOf>()
- when (stdin) {
- Pipe -> stdinPipe
- else -> null
- }?.also { pipes.add(it to "stdin") }
- when (stdout) {
- Pipe -> stdoutPipe
- else -> null
- }?.also { pipes.add(it to "stdout") }
- when (stderr) {
- Pipe -> stderrPipe
- else -> null
- }?.also { pipes.add(it to "stderr") }
- pipes.forEach {
- Posix.pipe(it.first)
- }
- }
-
- private fun redirectFileDescriptor() {
- when (stdin) {
- Null -> {
- Posix.close(STDIN_FILENO)
- }
-
- Pipe -> {
- Posix.dup2(stdinPipe[READ_END], STDIN_FILENO)
- Posix.close(stdinPipe[WRITE_END])
- }
-
- Inherit -> Unit
- }
- when (stdout) {
- Null -> {
- Posix.close(STDOUT_FILENO)
- }
-
- Pipe -> {
- Posix.dup2(stdoutPipe[WRITE_END], STDOUT_FILENO)
- Posix.close(stdoutPipe[READ_END])
- }
-
- Inherit -> Unit
- }
- when (stderr) {
- Null -> {
- Posix.close(STDERR_FILENO)
- }
-
- Pipe -> {
- Posix.dup2(stderrPipe[WRITE_END], STDERR_FILENO)
- Posix.close(stderrPipe[READ_END])
- }
-
- Inherit -> Unit
- }
- }
-
- @Throws(IOException::class)
- private fun openFileDescriptor(): Triple?, CPointer?, CPointer?> {
- val stdinFile = when (stdin) {
- Pipe -> {
- Posix.close(stdinPipe[READ_END])
- Posix.fdopen(stdinPipe[WRITE_END], "w")
- }
-
- else -> null
- }
- val stdoutFile = when (stdout) {
- Pipe -> {
- Posix.close(stdoutPipe[WRITE_END])
- Posix.fdopen(stdoutPipe[READ_END], "r")
- }
-
- else -> null
- }
- val stderrFile = when (stderr) {
- Pipe -> {
- Posix.close(stderrPipe[WRITE_END])
- Posix.fdopen(stderrPipe[READ_END], "r")
- }
-
- else -> null
- }
- return Triple(stdinFile, stdoutFile, stderrFile)
- }
-
- override fun toString(): String {
- return "Child(command='$command', args=$args, envs=$envs, cwd=$cwd, stdin=$stdin, stdout=$stdout, stderr=$stderr, id=$id, stdinPipe=${stdinPipe.contentToString()}, stdoutPipe=${stdoutPipe.contentToString()}, stderrPipe=${stderrPipe.contentToString()}, options=$options)"
- }
-
- actual fun prompt(): String {
- return "$command ${args.joinToString(" ")}"
- }
-}
diff --git a/src/unixLikeMain/kotlin/com/kgit2/process/Posix.kt b/src/unixLikeMain/kotlin/com/kgit2/process/Posix.kt
deleted file mode 100644
index bdb002d..0000000
--- a/src/unixLikeMain/kotlin/com/kgit2/process/Posix.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-package com.kgit2.process
-
-import com.kgit2.io.PlatformReader
-import com.kgit2.io.PlatformWriter
-import com.kgit2.io.Reader
-import com.kgit2.io.Writer
-import io.ktor.utils.io.errors.*
-import kotlinx.cinterop.CPointer
-import kotlinx.cinterop.IntVar
-import kotlinx.cinterop.addressOf
-import kotlinx.cinterop.alloc
-import kotlinx.cinterop.allocArrayOf
-import kotlinx.cinterop.cstr
-import kotlinx.cinterop.memScoped
-import kotlinx.cinterop.ptr
-import kotlinx.cinterop.usePinned
-import kotlinx.cinterop.value
-import platform.posix.E2BIG
-import platform.posix.EACCES
-import platform.posix.EAGAIN
-import platform.posix.EBADF
-import platform.posix.EBUSY
-import platform.posix.ECHILD
-import platform.posix.EFAULT
-import platform.posix.EINTR
-import platform.posix.EINVAL
-import platform.posix.EIO
-import platform.posix.EISDIR
-import platform.posix.ELOOP
-import platform.posix.EMFILE
-import platform.posix.ENAMETOOLONG
-import platform.posix.ENFILE
-import platform.posix.ENOENT
-import platform.posix.ENOEXEC
-import platform.posix.ENOMEM
-import platform.posix.ENOSYS
-import platform.posix.ENOTDIR
-import platform.posix.EPERM
-import platform.posix.ESRCH
-import platform.posix.ETXTBSY
-import platform.posix.FILE
-import platform.posix.errno
-
-object Posix {
- fun createWriter(file: CPointer): Writer {
- return Writer(PlatformWriter(file))
- }
-
- fun createReader(file: CPointer): Reader {
- return Reader(PlatformReader(file))
- }
-
- @Throws(IOException::class)
- fun pipe(fd: IntArray) {
- fd.usePinned { pin ->
- when (val result = platform.posix.pipe(pin.addressOf(0))) {
- EFAULT -> throw IOException("pipefd is not valid.")
- EINVAL -> throw IOException("Invalid flags.")
- EMFILE -> throw IOException("Too many open files")
- ENFILE -> throw IOException("The system limit on the total number of open files has been reached.")
- else -> Unit
- }
- }
- }
-
- @Throws(IOException::class)
- fun close(fileDescriptor: Int) {
- when (platform.posix.close(fileDescriptor)) {
- EBADF -> throw IOException("fd isn't a valid open file descriptor.")
- EINTR -> throw IOException("The close() call was interrupted by a signal.")
- EIO -> throw IOException("An I/O error occurred.")
- else -> Unit
- }
- }
-
- @Throws(IOException::class)
- fun dup2(oldFd: Int, newFd: Int) {
- when (platform.posix.dup2(oldFd, newFd)) {
- EBADF -> throw IOException("oldfd or newfd is not a valid file descriptor.")
- EBUSY -> throw IOException("(Linux only) This may be returned by dup2() or dup3() during a race condition with open(2) and dup().")
- EINTR -> throw IOException("The dup2() or dup3() call was interrupted by a signal; see signal(7).")
- EINVAL -> throw IOException("(dup3()) flags contain an invalid value. Or, oldfd was equal to newfd.")
- EMFILE -> throw IOException("The process already has the maximum number of file descriptors open and tried to open a new one.")
- else -> Unit
- }
- }
-
- @Throws(IOException::class)
- fun fork(): Int {
- return when (val pid = platform.posix.fork()) {
- EAGAIN -> throw IOException("fork() cannot allocate sufficient memory to copy the parent's page tables and allocate a task structure for the child.")
- ENOMEM -> throw IOException("fork() failed to allocate the necessary kernel structures because memory is tight.")
- ENOSYS -> throw IOException("fork() is not supported on this platform (for example, hardware without a Memory-Management Unit).")
- else -> pid
- }
- }
-
- @Throws(IOException::class)
- fun waitpid(pid: Int, options: Int): ChildExitStatus {
- return memScoped {
- val statusCode = alloc()
- when (val status = platform.posix.waitpid(pid, statusCode.ptr, options)) {
- ECHILD -> throw IOException("No child process with the specified pid exists.")
- EINTR -> throw IOException("The waitpid() call was interrupted by a signal.")
- EINVAL -> throw IOException("The options argument is not valid.")
- else -> status
- }
- ChildExitStatus(statusCode.value)
- }
- }
-
- @Throws(IOException::class)
- fun kill(pid: Int, signal: Int) {
- when (platform.posix.kill(pid, signal)) {
- EINVAL -> throw IOException("An invalid signal was specified.")
- EPERM -> throw IOException("The process does not have permission to send the signal to any of the target processes.")
- ESRCH -> throw IOException("The pid or process group does not exist. Note that an existing process might be a zombie, a process which already committed termination, but has not yet been wait(2)ed for.")
- else -> Unit
- }
- }
-
- @Throws(IOException::class)
- fun fdopen(fileDescriptor: Int, mode: String): CPointer {
- return when (val file = platform.posix.fdopen(fileDescriptor, mode)) {
- null -> throw IOException("Invalid mode.")
- else -> file
- }
- }
-
- @Throws(IOException::class)
- fun chdir(path: String) {
- if (platform.posix.chdir(path) != 0) {
- when (platform.posix.errno) {
- EACCES -> throw IOException("Search permission is denied for one of the components of path.")
- EFAULT -> throw IOException("path points outside your accessible address space.")
- EIO -> throw IOException("An I/O error occurred.")
- ELOOP -> throw IOException("Too many symbolic links were encountered in resolving path.")
- ENAMETOOLONG -> throw IOException("path is too long.")
- ENOENT -> throw IOException("The directory specified in path does not exist.")
- ENOMEM -> throw IOException("Insufficient kernel memory was available.")
- ENOTDIR -> throw IOException("A component of the path prefix is not a directory.")
- EBADF -> throw IOException("fd is not a valid file descriptor.")
- else -> Unit
- }
- }
- }
-
- @Throws(IOException::class)
- fun execvp(commands: List) {
- memScoped {
- platform.posix.execvp(commands[0], allocArrayOf(commands.map { it?.cstr?.getPointer(memScope) }))
- when (errno) {
- E2BIG -> IOException("The total number of bytes in the environment (envp) and argument list (argv) is too large.")
-
- EACCES -> IOException("The file or a script interpreter is not a regular file.\nOr Execute permission is denied for the file or a script or ELF interpreter.\nOr The file system is mounted noexec.")
-
- EFAULT -> IOException("filename points outside your accessible address space.")
-
- EINVAL -> IOException("An ELF executable had more than one PT_INTERP segment (i.e., tried to name more than one interpreter).")
-
- EIO -> IOException("An I/O error occurred.")
-
- EISDIR -> IOException("An ELF interpreter was a directory.")
-
- ELOOP -> IOException("Too many symbolic links were encountered in resolving filename or the name of a script or ELF interpreter.")
-
- EMFILE -> IOException("The process has the maximum number of files open.")
-
- ENAMETOOLONG -> IOException("filename is too long.")
-
- ENFILE -> IOException("The system limit on the total number of open files has been reached.")
-
- ENOENT -> IOException("The file filename or a script or ELF interpreter does not exist, or a shared library needed for file or interpreter cannot be found.")
-
- ENOEXEC -> IOException("An executable is not in a recognized format, is for the wrong architecture, or has some other format error that means it cannot be executed.")
-
- ENOMEM -> IOException("Insufficient kernel memory was available.")
-
- ENOTDIR -> IOException("A component of the path prefix of filename or a script or ELF interpreter is not a directory.")
-
- EPERM -> IOException("The file system is mounted nosuid, the user is not the superuser, and the file has the set-user-ID or set-group-ID bit set.")
-
- ETXTBSY -> IOException("Executable was open for writing by one or more processes.")
-
- else -> Unit
- }
- }
- }
-}
diff --git a/src/unixLikeTest/kotlin/process/ProcessTest.kt b/src/unixLikeTest/kotlin/process/ProcessTest.kt
deleted file mode 100644
index 1c2e159..0000000
--- a/src/unixLikeTest/kotlin/process/ProcessTest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package process
-
-import com.kgit2.process.Command
-import com.kgit2.process.Stdio
-import kotlin.test.Test
-
-class ProcessTest {
- // @Test
- // fun pingTest() {
- // val executor = Command("ping")
- // .arg("-c")
- // .args("5", "localhost")
- // .stdout(Stdio.Pipe)
- // .spawn()
- // val stdoutReader = executor.getChildStdout()!!
- // val lines = mutableListOf()
- // stdoutReader.lines().forEach {
- // // do something
- // println(it)
- // lines.add(it)
- // }
- // val exitStatus = runCatching {
- // executor.wait()
- // }
- // assertTrue(exitStatus.isSuccess)
- // }
-
- @Test
- fun pipeTest() {
- val child1 = Command("sh")
- .args("-c", "unset count; count=0; while ((count < 10)) do ((count += 1));echo from child1:${"$"}count;done")
- .stdout(Stdio.Pipe)
- .spawn()
- val child2 = Command("sh")
- .args("-c", "while read line; do echo from child2:${"$"}line; done")
- .stdin(Stdio.Pipe)
- .stdout(Stdio.Inherit)
- .spawn()
- val child1StdoutReader = child1.getChildStdout()!!
- val child2StdinWriter = child2.getChildStdin()!!
- child1StdoutReader.lines().forEach {
- child2StdinWriter.appendLine(it)
- }
- child2StdinWriter.close()
- }
-}
diff --git a/src/unixLikeTest/kotlin/server/FileService.kt b/src/unixLikeTest/kotlin/server/FileService.kt
deleted file mode 100644
index 0634980..0000000
--- a/src/unixLikeTest/kotlin/server/FileService.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package server
-
-import io.ktor.server.application.*
-import io.ktor.server.cio.*
-import io.ktor.server.engine.*
-import io.ktor.server.response.*
-import io.ktor.server.routing.*
-
-suspend fun startServer(port: Int, responseText: String): CIOApplicationEngine {
- val engine = embeddedServer(CIO, port = port) {
- routing {
- get("/") {
- call.respondText(responseText)
- }
- }
- }
- engine.start()
- return engine
-}
diff --git a/sub_command/.gitignore b/sub_command/.gitignore
deleted file mode 100644
index de0a616..0000000
--- a/sub_command/.gitignore
+++ /dev/null
@@ -1,629 +0,0 @@
-# Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all,clion+all,macos,windows,linux,visualstudiocode,visualstudio
-# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all,clion+all,macos,windows,linux,visualstudiocode,visualstudio
-
-### CLion+all ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-### CLion+all Patch ###
-# Ignore everything but code style settings and run configurations
-# that are supposed to be shared within teams.
-
-.idea/*
-
-!.idea/codeStyles
-!.idea/runConfigurations
-
-### JetBrains+all ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-
-# AWS User-specific
-
-# Generated files
-
-# Sensitive or high-churn files
-
-# Gradle
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-
-# Mongo Explorer plugin
-
-# File-based project format
-
-# IntelliJ
-
-# mpeltonen/sbt-idea plugin
-
-# JIRA plugin
-
-# Cursive Clojure plugin
-
-# SonarLint plugin
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-
-# Editor-based Rest Client
-
-# Android studio 3.1+ serialized cache file
-
-### JetBrains+all Patch ###
-# Ignore everything but code style settings and run configurations
-# that are supposed to be shared within teams.
-
-
-
-### Linux ###
-*~
-
-# temporary files which can be created if a process still has a handle open of a deleted file
-.fuse_hidden*
-
-# KDE directory preferences
-.directory
-
-# Linux trash folder which might appear on any partition or disk
-.Trash-*
-
-# .nfs files are created when an open file is removed but is still being accessed
-.nfs*
-
-### macOS ###
-# General
-.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-### macOS Patch ###
-# iCloud generated files
-*.icloud
-
-### VisualStudioCode ###
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-!.vscode/*.code-snippets
-
-# Local History for Visual Studio Code
-.history/
-
-# Built Visual Studio Code Extensions
-*.vsix
-
-### VisualStudioCode Patch ###
-# Ignore all local history of files
-.history
-.ionide
-
-### Windows ###
-# Windows thumbnail cache files
-Thumbs.db
-Thumbs.db:encryptable
-ehthumbs.db
-ehthumbs_vista.db
-
-# Dump file
-*.stackdump
-
-# Folder config file
-[Dd]esktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
-
-# Windows shortcuts
-*.lnk
-
-### VisualStudio ###
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
-
-# User-specific files
-*.rsuser
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Mono auto generated files
-mono_crash.*
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-[Ww][Ii][Nn]32/
-[Aa][Rr][Mm]/
-[Aa][Rr][Mm]64/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-[Ll]ogs/
-
-# Visual Studio 2015/2017 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUnit
-*.VisualState.xml
-TestResult.xml
-nunit-*.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-
-# ASP.NET Scaffolding
-ScaffoldingReadMe.txt
-
-# StyleCop
-StyleCopReport.xml
-
-# Files built by Visual Studio
-*_i.c
-*_p.c
-*_h.h
-*.ilk
-*.meta
-*.obj
-*.iobj
-*.pch
-*.pdb
-*.ipdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*_wpftmp.csproj
-*.log
-*.tlog
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Coverlet is a free, cross platform Code Coverage Tool
-coverage*.json
-coverage*.xml
-coverage*.info
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# NuGet Symbol Packages
-*.snupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
-*.appxbundle
-*.appxupload
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!?*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-*.rptproj.rsuser
-*- [Bb]ackup.rdl
-*- [Bb]ackup ([0-9]).rdl
-*- [Bb]ackup ([0-9][0-9]).rdl
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio 6 auto-generated project file (contains which files were open etc.)
-*.vbp
-
-# Visual Studio 6 workspace and project file (working project files containing files to include in project)
-*.dsw
-*.dsp
-
-# Visual Studio 6 technical files
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# CodeRush personal settings
-.cr/personal
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
-
-# Local History for Visual Studio
-.localhistory/
-
-# Visual Studio History (VSHistory) files
-.vshistory/
-
-# BeatPulse healthcheck temp database
-healthchecksdb
-
-# Backup folder for Package Reference Convert tool in Visual Studio 2017
-MigrationBackup/
-
-# Ionide (cross platform F# VS Code tools) working folder
-.ionide/
-
-# Fody - auto-generated XML schema
-FodyWeavers.xsd
-
-# VS Code files for those working on multiple tools
-*.code-workspace
-
-# Local History for Visual Studio Code
-
-# Windows Installer files from build outputs
-
-# JetBrains Rider
-*.sln.iml
-
-### VisualStudio Patch ###
-# Additional files built by Visual Studio
-
-# End of https://www.toptal.com/developers/gitignore/api/jetbrains+all,clion+all,macos,windows,linux,visualstudiocode,visualstudio
diff --git a/sub_command/build.gradle.kts b/sub_command/build.gradle.kts
deleted file mode 100644
index 3e54d38..0000000
--- a/sub_command/build.gradle.kts
+++ /dev/null
@@ -1,12 +0,0 @@
-plugins {
- kotlin("jvm")
- application
-}
-
-repositories {
- mavenCentral()
-}
-
-application {
- mainClass.set("MainKt")
-}
diff --git a/sub_command/src/main/kotlin/Main.kt b/sub_command/src/main/kotlin/Main.kt
deleted file mode 100644
index ebc27c7..0000000
--- a/sub_command/src/main/kotlin/Main.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-import java.util.Scanner
-
-fun main(args: Array) {
- if (args.isEmpty()) {
- print("Hello, Kommand!\n")
- return
- }
- when (args[0]) {
- "echo" -> echo()
- "error" -> error()
- "interval" -> interval(if (args.size > 1) args[1].toInt() else null)
- else -> System.err.println("Unknown command: ${args[0]}")
- }
-}
-
-fun echo() {
- val scanner = Scanner(System.`in`)
- while (scanner.hasNext()) {
- val line = scanner.nextLine()
- println(line)
- }
-}
-
-fun error() {
- val scanner = Scanner(System.`in`)
- while (scanner.hasNext()) {
- val line = scanner.nextLine()
- System.err.println(line)
- }
-}
-
-fun interval(count: Int?) {
- repeat(count ?: 5) {
- println(it)
- Thread.sleep(100)
- }
-}