aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-08 18:54:50 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-08 18:54:50 +0800
commit1e498d1ed1645d93a49230d0647f1fa15aecb113 (patch)
treeaeadab9cbe48d52728c5d77b9cf5a389b5a16ba5
parent4839653fdfaab5bd2a2c311924d45475a2b809db (diff)
feat: add run scripts for cross-platform tool execution
Add run.ps1 and run.sh launcher scripts that detect and execute scripts from .run/src/bin across multiple languages, with numbered selection, fuzzy matching, and case-insensitive fallback.
-rw-r--r--.run/.gitignore5
-rw-r--r--.run/src/bin/install-tscn2bsn.ps11
-rw-r--r--.run/src/bin/install-tscn2bsn.sh3
-rw-r--r--run.ps1276
-rw-r--r--run.sh311
5 files changed, 596 insertions, 0 deletions
diff --git a/.run/.gitignore b/.run/.gitignore
new file mode 100644
index 0000000..017bfab
--- /dev/null
+++ b/.run/.gitignore
@@ -0,0 +1,5 @@
+# All temp build artifacts will be stored in this dir
+/target
+
+# If you want to add some Rust crates? Remove this line
+/Cargo.*
diff --git a/.run/src/bin/install-tscn2bsn.ps1 b/.run/src/bin/install-tscn2bsn.ps1
new file mode 100644
index 0000000..46811d9
--- /dev/null
+++ b/.run/src/bin/install-tscn2bsn.ps1
@@ -0,0 +1 @@
+cargo install --path .\tools\tscn-to-bsn\
diff --git a/.run/src/bin/install-tscn2bsn.sh b/.run/src/bin/install-tscn2bsn.sh
new file mode 100644
index 0000000..34423ed
--- /dev/null
+++ b/.run/src/bin/install-tscn2bsn.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+cargo install --path ./tools/tscn-to-bsn/
diff --git a/run.ps1 b/run.ps1
new file mode 100644
index 0000000..65ba0b9
--- /dev/null
+++ b/run.ps1
@@ -0,0 +1,276 @@
+#
+# ██ ██████ ██ ██ ███ ██ ██████ ███████ ██
+# ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ███
+# ██ ██████ ██ ██ ██ ██ ██ ██████ ███████ ██
+# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+# ██ ██ ██ ██ ██████ ██ ████ ██ ██ ███████ ██
+#
+# You can go to [https://catilgrass.github.io/run] to install it
+# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+# Version: 0.1.2
+
+Set-Location -Path (Split-Path -Parent $MyInvocation.MyCommand.Path) -ErrorAction Stop
+
+function Get-SortedNames {
+ return @($tools.Keys | Sort-Object @{Expression={if ($_[0] -cmatch '[A-Z]') {0} else {1}}}, @{Expression={if ($tools[$_].Type -eq 'ps1') {0} else {1}}}, {$_})
+}
+
+function Show-List {
+ param([string]$Highlight = "")
+
+ $sorted = Get-SortedNames
+ $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) + "│")
+
+ $esc = [char]27
+ $boldBlue = "$esc[1;34m"
+ $reset = "$esc[0m"
+
+ for ($idx = 0; $idx -lt $sorted.Count; $idx++) {
+ $name = $sorted[$idx]
+ $plain = $name -replace '[_\-]', '-'
+ if ($Highlight) {
+ if (-not $plain.StartsWith($Highlight, [System.StringComparison]::OrdinalIgnoreCase)) { continue }
+ }
+ $type = $tools[$name].Type
+ $lang = switch ($type) {
+ "ps1" { "PowerShell" }
+ "cs" { "C#" }
+ "exe" { "Binary" }
+ "go" { "Go" }
+ "nim" { "Nim" }
+ "pl" { "Perl" }
+ "py" { "Python" }
+ "rb" { "Ruby" }
+ "rs" { "Rust" }
+ "zig" { "Zig" }
+ }
+ if ($Highlight) {
+ $preLen = $Highlight.Length
+ $prefix = $plain.Substring(0, $preLen)
+ $rest = $plain.Substring($preLen)
+ $displayName = $boldBlue + $prefix + $reset + $rest
+ } else {
+ $displayName = $plain
+ }
+ $i = $idx + 1
+ $numPart = " " + $i.ToString().PadRight($num_w) + ") "
+ $pad = $max_name - $plain.Length
+ $langPart = " [$lang]"
+ $row = $numPart + $displayName + (" " * $pad) + $langPart
+ $outerPad = $inner_w - ($numPart.Length + $plain.Length + $pad + $langPart.Length)
+ Write-Host ("│" + $row + (" " * $outerPad) + "│")
+ }
+
+ Write-Host ("│" + (" " * $inner_w) + "│")
+ Write-Host ("└" + ("─" * $inner_w) + "┘")
+}
+
+$tools = @{}
+
+if (Test-Path ".run/src/bin/*.ps1") {
+ Get-ChildItem -Path ".run/src/bin/*.ps1" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "ps1"; Path = $_.FullName }
+ }
+}
+
+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/*.exe") {
+ Get-ChildItem -Path ".run/src/bin/*.exe" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "exe"; Path = $_.FullName }
+ }
+}
+
+if (Test-Path ".run/src/bin/*.go") {
+ Get-ChildItem -Path ".run/src/bin/*.go" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "go"; Path = $_.FullName }
+ }
+}
+
+if (Test-Path ".run/src/bin/*.nim") {
+ Get-ChildItem -Path ".run/src/bin/*.nim" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "nim"; Path = $_.FullName }
+ }
+}
+
+if (Test-Path ".run/src/bin/*.pl") {
+ Get-ChildItem -Path ".run/src/bin/*.pl" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "pl"; 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/*.rb") {
+ Get-ChildItem -Path ".run/src/bin/*.rb" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "rb"; 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/*.zig") {
+ Get-ChildItem -Path ".run/src/bin/*.zig" | ForEach-Object {
+ $tools[$_.BaseName] = @{ Type = "zig"; Path = $_.FullName }
+ }
+}
+
+if ($args.Count -eq 0) {
+ Show-List
+ exit 1
+}
+
+$target_name = $args[0]
+
+if ($target_name -match '^\d+$') {
+ $sorted = Get-SortedNames
+ $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)) {
+ $normalized = $target_name.ToLower() -replace '[ _\-\.]', ''
+ $found = $null
+ foreach ($key in $tools.Keys) {
+ $keyNormalized = $key.ToLower() -replace '[ _\-\.]', ''
+ if ($normalized -eq $keyNormalized) {
+ $found = $key
+ break
+ }
+ }
+ if ($found) {
+ $target_name = $found
+ } else {
+ $sorted = Get-SortedNames
+ $hasHit = $false
+ foreach ($name in $sorted) {
+ $plain = $name -replace '[_\-]', '-'
+ if ($plain.StartsWith($target_name, [System.StringComparison]::OrdinalIgnoreCase)) {
+ $hasHit = $true
+ break
+ }
+ }
+ if ($hasHit) {
+ Show-List -Highlight $target_name
+ exit 1
+ }
+ 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
+ }
+ "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
+ }
+ "exe" {
+ & $info.Path $script_args
+ }
+ "go" {
+ go run $info.Path $script_args
+ }
+ "nim" {
+ nim r --hints:off $info.Path $script_args
+ }
+ "pl" {
+ perl $info.Path $script_args
+ }
+ "py" {
+ python $info.Path $script_args
+ }
+ "rb" {
+ ruby $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
+ }
+ "zig" {
+ zig run $info.Path $script_args
+ }
+}
+
+exit $LASTEXITCODE
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000..947aefc
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,311 @@
+#!/bin/bash
+
+#
+# ██ ██████ ██ ██ ███ ██ ███████ ██ ██
+# ██ ██ ██ ██ ██ ████ ██ ██ ██ ██
+# ██ ██████ ██ ██ ██ ██ ██ ███████ ███████
+# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+# ██ ██ ██ ██ ██████ ██ ████ ██ ███████ ██ ██
+#
+# You can go to [https://catilgrass.github.io/run] to install it
+# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+# Version: 0.1.2
+
+cd "$(dirname "$0")" || exit 1
+
+declare -A tools
+
+for file in .run/src/bin/*.sh; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .sh)
+ tools["$name"]="sh"
+ fi
+done
+
+for file in .run/src/bin/*; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file")
+ if [[ ! "$name" == *.* ]]; then
+ tools["$name"]="binary"
+ fi
+ fi
+done
+
+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/*.go; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .go)
+ tools["$name"]="go"
+ fi
+done
+
+for file in .run/src/bin/*.nim; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .nim)
+ tools["$name"]="nim"
+ fi
+done
+
+for file in .run/src/bin/*.pl; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .pl)
+ tools["$name"]="pl"
+ 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/*.rb; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .rb)
+ tools["$name"]="rb"
+ 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/*.zig; do
+ if [ -f "$file" ]; then
+ name=$(basename "$file" .zig)
+ tools["$name"]="zig"
+ fi
+done
+
+get_sorted_names() {
+ for name in "${!tools[@]}"; do
+ first_char="${name:0:1}"
+ if [[ "$first_char" =~ [A-Z] ]]; then
+ case_pri="0"
+ else
+ case_pri="1"
+ fi
+ if [ "${tools[$name]}" = "sh" ]; then
+ lang_pri="0"
+ else
+ lang_pri="1"
+ fi
+ echo "$case_pri$lang_pri $name"
+ done | sort | while read -r _ n; do echo "$n"; done
+}
+
+show_list() {
+ local highlight="$1"
+
+ local total=${#sorted_names[@]}
+ local num_w=${#total}
+
+ local max_name=0
+ for name in "${sorted_names[@]}"; do
+ len=${#name}
+ ((len > max_name)) && max_name=$len
+ done
+
+ local inner_w=$((2 + num_w + 1 + 1 + max_name + 2 + 6 + 1 + 2))
+ ((inner_w < 38)) && inner_w=38
+
+ local title="./run.sh <NUMBER/NAME> [ARGS...]"
+ local title_len=${#title}
+ local dash_total=$((inner_w - 2 - title_len))
+ local dash_left=$((dash_total / 2))
+ local 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 ""
+
+ local bold_blue=$'\033[1;34m'
+ local reset=$'\033[0m'
+
+ local lc_h=""
+ if [ -n "$highlight" ]; then
+ lc_h=$(echo "$highlight" | tr '[:upper:]' '[:lower:]')
+ fi
+
+ local i=1
+ for idx in "${!sorted_names[@]}"; do
+ local name="${sorted_names[$idx]}"
+ local type="${tools[$name]}"
+ local lang
+ case "$type" in
+ sh) lang="Shell";;
+ binary) lang="Binary";;
+ cs) lang="C#";;
+ go) lang="Go";;
+ nim) lang="Nim";;
+ pl) lang="Perl";;
+ py) lang="Python";;
+ rb) lang="Ruby";;
+ rs) lang="Rust";;
+ zig) lang="Zig";;
+ esac
+
+ local display_name
+ display_name=$(echo "$name" | tr '_-' '--')
+
+ if [ -n "$highlight" ]; then
+ local lc_dn
+ lc_dn=$(echo "$display_name" | tr '[:upper:]' '[:lower:]')
+ [[ "$lc_dn" == "$lc_h"* ]] || continue
+
+ local hl_len=${#highlight}
+ local prefix="${display_name:0:hl_len}"
+ local rest="${display_name:hl_len}"
+ display_name="${bold_blue}${prefix}${reset}${rest}"
+ fi
+
+ i=$((idx + 1))
+ local num_part
+ num_part=$(printf " %-*d) " $num_w $i)
+ local pad=$((max_name - ${#name}))
+ local pad_str
+ pad_str=$(printf '%*s' $pad '')
+ local lang_part=" [$lang]"
+ local visible_len=$(( ${#num_part} + ${#name} + pad + ${#lang_part} ))
+ local outer_pad=$((inner_w - visible_len))
+ printf "│%s%s%s%s%s│\n" "$num_part" "$display_name" "$pad_str" "$lang_part" "$(printf '%*s' $outer_pad '')"
+ done
+
+ printf "│%*s│\n" $inner_w ""
+ echo "└$(printf '─%.0s' $(seq 1 $inner_w))┘"
+}
+
+if [ $# -eq 0 ]; then
+ sorted_names=($(get_sorted_names))
+ show_list ""
+ exit 1
+fi
+
+target_name="$1"
+shift
+
+if [[ "$target_name" =~ ^[0-9]+$ ]]; then
+ sorted=($(get_sorted_names))
+ 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
+ normalized_user=$(echo "$target_name" | tr '[:upper:]' '[:lower:]' | sed 's/[_. -]//g')
+ found=""
+ for existing_name in "${!tools[@]}"; do
+ normalized_existing=$(echo "$existing_name" | tr '[:upper:]' '[:lower:]' | sed 's/[_. -]//g')
+ if [ "$normalized_user" = "$normalized_existing" ]; then
+ found="$existing_name"
+ break
+ fi
+ done
+ if [ -n "$found" ]; then
+ target_name="$found"
+ else
+ sorted_names=($(get_sorted_names))
+ lc_user=$(echo "$target_name" | tr '[:upper:]' '[:lower:]')
+ hit=0
+ for name in "${sorted_names[@]}"; do
+ lc_name=$(echo "$name" | tr '_-' '--' | tr '[:upper:]' '[:lower:]')
+ if [[ "$lc_name" == "$lc_user"* ]]; then
+ hit=1
+ break
+ fi
+ done
+ if [ $hit -eq 1 ]; then
+ show_list "$target_name"
+ exit 1
+ fi
+ echo "Error: target '$target_name' does not exist"
+ exit 1
+ fi
+fi
+
+type="${tools[$target_name]}"
+
+case "$type" in
+ sh)
+ chmod +x ".run/src/bin/$target_name.sh"
+ ".run/src/bin/$target_name.sh" "$@"
+ ;;
+ binary)
+ chmod +x ".run/src/bin/$target_name"
+ ".run/src/bin/$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" -- "$@"
+ ;;
+ go)
+ go run ".run/src/bin/$target_name.go" "$@"
+ ;;
+ nim)
+ nim r --hints:off ".run/src/bin/$target_name.nim" "$@"
+ ;;
+ pl)
+ perl ".run/src/bin/$target_name.pl" "$@"
+ ;;
+ py)
+ python ".run/src/bin/$target_name.py" "$@"
+ ;;
+ rb)
+ ruby ".run/src/bin/$target_name.rb" "$@"
+ ;;
+ 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" "$@"
+ ;;
+ zig)
+ zig run ".run/src/bin/$target_name.zig" "$@"
+ ;;
+esac