aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-02 10:33:38 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-02 10:49:47 +0800
commit37bbc0ccf6ca34c1163384742f172741a5322c75 (patch)
tree77c6b9b03d895be51590f2c410582618b894f320
feat: add cross-platform script runner with run.sh and run.ps1
-rw-r--r--.run/.gitignore3
-rw-r--r--.run/src/bin/csharp-script.cs8
-rw-r--r--.run/src/bin/powrshell-script.ps18
-rw-r--r--.run/src/bin/python-script.py11
-rw-r--r--.run/src/bin/rust-script.rs12
-rw-r--r--.run/src/bin/shell-script.sh14
-rw-r--r--LICENSE13
-rw-r--r--README.md30
-rw-r--r--install/install.ps132
-rw-r--r--install/install.sh26
-rw-r--r--run.ps1147
-rw-r--r--run.sh148
12 files changed, 452 insertions, 0 deletions
diff --git a/.run/.gitignore b/.run/.gitignore
new file mode 100644
index 0000000..78d04c9
--- /dev/null
+++ b/.run/.gitignore
@@ -0,0 +1,3 @@
+/.temp
+/target
+/Cargo.*
diff --git a/.run/src/bin/csharp-script.cs b/.run/src/bin/csharp-script.cs
new file mode 100644
index 0000000..1e7a3b7
--- /dev/null
+++ b/.run/src/bin/csharp-script.cs
@@ -0,0 +1,8 @@
+Console.WriteLine("");
+Console.WriteLine(" Welcome to use `run.sh` / `run.ps1`");
+Console.WriteLine("");
+Console.WriteLine(" > \"Hello World\"");
+Console.WriteLine(" > cwd : \"{0}\"", Environment.CurrentDirectory);
+var argStr = args.Length > 0 ? string.Join(", ", args.Select(a => $"\"{a}\"")) : "";
+Console.WriteLine(" > args : {0}", argStr);
+Console.WriteLine("");
diff --git a/.run/src/bin/powrshell-script.ps1 b/.run/src/bin/powrshell-script.ps1
new file mode 100644
index 0000000..70f3bae
--- /dev/null
+++ b/.run/src/bin/powrshell-script.ps1
@@ -0,0 +1,8 @@
+Write-Host ""
+Write-Host ' Welcome to use `run.sh` / `run.ps1`'
+Write-Host ""
+Write-Host ' > "Hello World"'
+Write-Host " > cwd : `"$(Get-Location)`""
+$items = ($args | ForEach-Object { "`"$_`"" }) -join ", "
+Write-Host " > args : $items"
+Write-Host ""
diff --git a/.run/src/bin/python-script.py b/.run/src/bin/python-script.py
new file mode 100644
index 0000000..9f4ff7b
--- /dev/null
+++ b/.run/src/bin/python-script.py
@@ -0,0 +1,11 @@
+import os
+import sys
+
+print()
+print(" Welcome to use `run.sh` / `run.ps1`")
+print()
+print(' > "Hello World"')
+print(f' > cwd : "{os.getcwd()}"')
+args = ", ".join(f'"{a}"' for a in sys.argv[1:])
+print(f" > args : {args}")
+print()
diff --git a/.run/src/bin/rust-script.rs b/.run/src/bin/rust-script.rs
new file mode 100644
index 0000000..19f4bcb
--- /dev/null
+++ b/.run/src/bin/rust-script.rs
@@ -0,0 +1,12 @@
+fn main() {
+ let args: Vec<String> = std::env::args().skip(1).collect();
+ let cwd = std::env::current_dir().unwrap();
+ println!();
+ println!(" Welcome to use `run.sh` / `run.ps1`");
+ println!();
+ println!(" > \"Hello World\"");
+ println!(" > cwd : \"{}\"", cwd.display());
+ let args_str: Vec<String> = args.iter().map(|a| format!("\"{}\"", a)).collect();
+ println!(" > args : {}", args_str.join(", "));
+ println!();
+}
diff --git a/.run/src/bin/shell-script.sh b/.run/src/bin/shell-script.sh
new file mode 100644
index 0000000..306afb5
--- /dev/null
+++ b/.run/src/bin/shell-script.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+echo ""
+echo ' Welcome to use `run.sh` / `run.ps1`'
+echo ""
+echo ' > "Hello World"'
+echo " > cwd : \"$(pwd)\""
+items=""
+sep=""
+for arg in "$@"; do
+ items="${items}${sep}\"${arg}\""
+ sep=", "
+done
+echo " > args : ${items}"
+echo ""
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b3e27d9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2026 Weicao-CatilGrass
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..291f3d8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+# Screw "make", I just want a script runner
+
+Sometimes when working on projects, I need cross-platform operation. I don't want to install something like `just` or `make`. I just want to write some scripts, drop them in, and run them. That's it.
+
+This is the solution:
+
+- Rust program? Drop it in `.run/src/bin/myscript.rs`
+- C#? Drop it in `.run/src/bin/myscript.cs`
+- Python? Drop it in `.run/src/bin/myscript.py`
+- Shell? PowerShell? Same!
+
+Any script can be dropped in. After that, just run `.\run.ps1 script-name` or `./run.sh script-name`.
+
+## Install
+
+For Linux:
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/catilgrass/run/refs/heads/master/install/install.sh | bash
+```
+
+For Windows:
+
+```bash
+iwr -useb https://raw.githubusercontent.com/catilgrass/run/refs/heads/master/install/install.ps1 | iex
+```
+
+## License
+
+WTFPL — drop it into your project and use it.
diff --git a/install/install.ps1 b/install/install.ps1
new file mode 100644
index 0000000..f9853ff
--- /dev/null
+++ b/install/install.ps1
@@ -0,0 +1,32 @@
+$tempDir = "./run_script_temp"
+$repoDir = "$tempDir/run"
+
+Write-Host "Creating temporary directory..."
+$null = New-Item -ItemType Directory -Path $tempDir -Force
+
+Write-Host "Cloning https://github.com/catilgrass/run ..."
+git clone "https://github.com/catilgrass/run" $repoDir
+
+Write-Host "Removing unwanted files..."
+Remove-Item -Path "$repoDir/README.md", "$repoDir/LICENSE" -Force -ErrorAction SilentlyContinue
+Remove-Item -Path "$repoDir/install" -Recurse -Force -ErrorAction SilentlyContinue
+
+Write-Host "Removing git data..."
+Remove-Item -Path "$repoDir/.git" -Recurse -Force -ErrorAction SilentlyContinue
+
+Write-Host "Installing files..."
+Get-ChildItem -Path $repoDir -Force | ForEach-Object {
+ $dest = Join-Path (Get-Location) $_.Name
+ if ($_.PSIsContainer) {
+ Copy-Item -Path $_.FullName -Destination $dest -Recurse -Force
+ } else {
+ Copy-Item -Path $_.FullName -Destination $dest -Force
+ }
+}
+
+Write-Host "Cleaning up temporary directory..."
+[System.GC]::Collect()
+Start-Sleep -Milliseconds 200
+Remove-Item -Path $tempDir -Recurse -Force
+
+Write-Host "Done!"
diff --git a/install/install.sh b/install/install.sh
new file mode 100644
index 0000000..3dece5f
--- /dev/null
+++ b/install/install.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+set -e
+
+TEMP_DIR="./run_script_temp"
+REPO_DIR="$TEMP_DIR/run"
+
+echo "Creating temporary directory..."
+mkdir -p "$TEMP_DIR"
+
+echo "Cloning https://github.com/catilgrass/run ..."
+git clone https://github.com/catilgrass/run "$REPO_DIR"
+
+echo "Removing unwanted files..."
+rm -f "$REPO_DIR/README.md" "$REPO_DIR/LICENSE"
+rm -rf "$REPO_DIR/install"
+
+echo "Removing git data..."
+rm -rf "$REPO_DIR/.git"
+
+echo "Installing files..."
+cp -a "$REPO_DIR/." .
+
+echo "Cleaning up temporary directory..."
+rm -rf "$TEMP_DIR"
+
+echo "Done!"
diff --git a/run.ps1 b/run.ps1
new file mode 100644
index 0000000..17b85e1
--- /dev/null
+++ b/run.ps1
@@ -0,0 +1,147 @@
+Set-Location -Path (Split-Path -Parent $MyInvocation.MyCommand.Path) -ErrorAction Stop
+
+$tools = @{}
+
+if (Test-Path ".run/src/bin/*.cs") {
+ Get-ChildItem -Path ".run/src/bin/*.cs" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "cs"; Path = $_.FullName }
+ }
+}
+
+if (Test-Path ".run/src/bin/*.rs") {
+ Get-ChildItem -Path ".run/src/bin/*.rs" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "rs"; Path = $_.FullName }
+ }
+}
+
+if (Test-Path ".run/src/bin/*.py") {
+ Get-ChildItem -Path ".run/src/bin/*.py" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "py"; Path = $_.FullName }
+ }
+}
+
+if (Test-Path ".run/src/bin/*.ps1") {
+ Get-ChildItem -Path ".run/src/bin/*.ps1" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "ps1"; Path = $_.FullName }
+ }
+}
+
+if ($args.Count -eq 0) {
+ $sorted = $tools.Keys | Sort-Object
+ $total = $sorted.Count
+ $num_w = $total.ToString().Length
+
+ $max_name = ($sorted | ForEach-Object { $_.Length } | Measure-Object -Maximum).Maximum
+
+ $inner_w = 2 + $num_w + 1 + 1 + $max_name + 2 + 10 + 1 + 2
+ if ($inner_w -lt 38) { $inner_w = 38 }
+
+ $title = '.\run.ps1 <NUMBER/NAME> [ARGS...]'
+ $title_len = $title.Length
+ $dash_total = $inner_w - 2 - $title_len
+ $dash_left = [Math]::Floor($dash_total / 2)
+ $dash_right = $dash_total - $dash_left
+
+ Write-Host ("┌" + ("─" * $dash_left) + " " + $title + " " + ("─" * $dash_right) + "┐")
+ Write-Host ("│" + (" " * $inner_w) + "│")
+
+ $i = 1
+ foreach ($name in $sorted) {
+ $type = $tools[$name].Type
+ $lang = switch ($type) {
+ "cs" { "C#" }
+ "py" { "Python" }
+ "rs" { "Rust" }
+ "ps1" { "PowerShell" }
+ }
+ $entry = " " + $i.ToString().PadRight($num_w) + ". " + $name.PadRight($max_name) + " [$lang]"
+ Write-Host ("│" + $entry.PadRight($inner_w) + "│")
+ $i++
+ }
+
+ Write-Host ("│" + (" " * $inner_w) + "│")
+ Write-Host ("└" + ("─" * $inner_w) + "┘")
+ exit 1
+}
+
+$target_name = $args[0]
+
+if ($target_name -match '^\d+$') {
+ $sorted = $tools.Keys | Sort-Object
+ $idx = [int]$target_name - 1
+ if ($idx -ge 0 -and $idx -lt $sorted.Count) {
+ $target_name = $sorted[$idx]
+ } else {
+ Write-Host "Error: invalid number '$target_name', valid range is 1-$($sorted.Count)"
+ exit 1
+ }
+}
+
+if (-not $tools.ContainsKey($target_name)) {
+ Write-Host "Error: target '$target_name' does not exist"
+ exit 1
+}
+
+$script_args = $args[1..$args.Count]
+$info = $tools[$target_name]
+
+switch ($info.Type) {
+ "ps1" {
+ & $info.Path @script_args
+ }
+ "py" {
+ python $info.Path $script_args
+ }
+ "rs" {
+ if (-not (Test-Path ".run/Cargo.toml")) {
+@"
+[package]
+name = "run_rust"
+version = "0.1.0"
+edition = "2024"
+
+[workspace]
+
+[dependencies]
+"@ | Set-Content -Path ".run/Cargo.toml"
+ }
+ cargo build --manifest-path ".run/Cargo.toml" --target-dir ".run/target" --bin $target_name --quiet
+ $binary = ".run/target/debug/$target_name.exe"
+ if (-not (Test-Path $binary)) {
+ $binary = ".run/target/debug/$target_name"
+ }
+ & $binary $script_args
+ }
+ "cs" {
+ $temp_dir = ".run/target/csproj/$target_name"
+ $null = New-Item -ItemType Directory -Path $temp_dir -Force
+
+ $props_content = @'
+<Project>
+ <PropertyGroup>
+ <BaseOutputPath>$(MSBuildThisFileDirectory)../../csharp/bin</BaseOutputPath>
+ <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)../../csharp/obj</BaseIntermediateOutputPath>
+ </PropertyGroup>
+</Project>
+'@
+ Set-Content -Path "$temp_dir/Directory.Build.props" -Value $props_content
+
+ $csproj_content = @"
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net8.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+</Project>
+"@
+ Set-Content -Path "$temp_dir/$target_name.csproj" -Value $csproj_content
+ Copy-Item -Path $info.Path -Destination "$temp_dir/Program.cs" -Force
+
+ dotnet run --project "$temp_dir/$target_name.csproj" -- $script_args
+ }
+}
+
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000..62e08ac
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,148 @@
+#!/bin/bash
+
+cd "$(dirname "$0")" || exit 1
+
+declare -A tools
+
+for file in .run/src/bin/*.cs; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .cs)
+ tools["$name"]="cs"
+ fi
+done
+
+for file in .run/src/bin/*.rs; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .rs)
+ tools["$name"]="rs"
+ fi
+done
+
+for file in .run/src/bin/*.py; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .py)
+ tools["$name"]="py"
+ fi
+done
+
+for file in .run/src/bin/*.sh; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .sh)
+ tools["$name"]="sh"
+ fi
+done
+
+if [ $# -eq 0 ]; then
+ sorted_names=($(echo "${!tools[@]}" | tr ' ' '\n' | sort))
+ total=${#sorted_names[@]}
+ num_w=${#total}
+
+ max_name=0
+ for name in "${sorted_names[@]}"; do
+ len=${#name}
+ ((len > max_name)) && max_name=$len
+ done
+
+ inner_w=$((2 + num_w + 1 + 1 + max_name + 2 + 6 + 1 + 2))
+ ((inner_w < 38)) && inner_w=38
+
+ title="./run.sh <NUMBER/NAME> [ARGS...]"
+ title_len=${#title}
+ dash_total=$((inner_w - 2 - title_len))
+ dash_left=$((dash_total / 2))
+ dash_right=$((dash_total - dash_left))
+
+ echo "┌$(printf '─%.0s' $(seq 1 $dash_left)) $title $(printf '─%.0s' $(seq 1 $dash_right))┐"
+ printf "│%*s│\n" $inner_w ""
+
+ i=1
+ for name in "${sorted_names[@]}"; do
+ type="${tools[$name]}"
+ case "$type" in
+ cs) lang="C#";;
+ py) lang="Python";;
+ rs) lang="Rust";;
+ sh) lang="Shell";;
+ esac
+ entry=$(printf " %-*d) %-*s [%s]" $num_w $i $max_name $name $lang)
+ printf "│%-*s│\n" $inner_w "$entry"
+ i=$((i + 1))
+ done
+
+ printf "│%*s│\n" $inner_w ""
+ echo "└$(printf '─%.0s' $(seq 1 $inner_w))┘"
+ exit 1
+fi
+
+target_name="$1"
+shift
+
+if [[ "$target_name" =~ ^[0-9]+$ ]]; then
+ sorted=($(echo "${!tools[@]}" | tr ' ' '\n' | sort))
+ idx=$((target_name - 1))
+ if [ "$idx" -ge 0 ] && [ "$idx" -lt "${#sorted[@]}" ]; then
+ target_name="${sorted[$idx]}"
+ else
+ echo "Error: invalid number '$target_name', valid range is 1-${#sorted[@]}"
+ exit 1
+ fi
+fi
+
+if [ -z "${tools[$target_name]}" ]; then
+ echo "Error: target '$target_name' does not exist"
+ exit 1
+fi
+
+type="${tools[$target_name]}"
+
+case "$type" in
+ sh)
+ chmod +x ".run/src/bin/$target_name.sh"
+ ".run/src/bin/$target_name.sh" "$@"
+ ;;
+ py)
+ python ".run/src/bin/$target_name.py" "$@"
+ ;;
+ rs)
+ if [ ! -f ".run/Cargo.toml" ]; then
+ cat > ".run/Cargo.toml" <<'EOF'
+[package]
+name = "run_rust"
+version = "0.1.0"
+edition = "2024"
+
+[workspace]
+
+[dependencies]
+EOF
+ fi
+ cargo build --manifest-path ".run/Cargo.toml" --target-dir ".run/target" --bin "$target_name" --quiet
+ ".run/target/debug/$target_name" "$@"
+ ;;
+ cs)
+ temp_dir=".run/target/csproj/$target_name"
+ mkdir -p "$temp_dir"
+ cat > "$temp_dir/Directory.Build.props" <<'PROPS'
+<Project>
+ <PropertyGroup>
+ <BaseOutputPath>$(MSBuildThisFileDirectory)../../csharp/bin</BaseOutputPath>
+ <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)../../csharp/obj</BaseIntermediateOutputPath>
+ </PropertyGroup>
+</Project>
+PROPS
+ cat > "$temp_dir/$target_name.csproj" <<'CSPROJ'
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net8.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+</Project>
+CSPROJ
+ cp ".run/src/bin/$target_name.cs" "$temp_dir/Program.cs"
+ dotnet run --project "$temp_dir/$target_name.csproj" -- "$@"
+ ;;
+esac