diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-08 18:54:50 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-08 18:54:50 +0800 |
| commit | 1e498d1ed1645d93a49230d0647f1fa15aecb113 (patch) | |
| tree | aeadab9cbe48d52728c5d77b9cf5a389b5a16ba5 | |
| parent | 4839653fdfaab5bd2a2c311924d45475a2b809db (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/.gitignore | 5 | ||||
| -rw-r--r-- | .run/src/bin/install-tscn2bsn.ps1 | 1 | ||||
| -rw-r--r-- | .run/src/bin/install-tscn2bsn.sh | 3 | ||||
| -rw-r--r-- | run.ps1 | 276 | ||||
| -rw-r--r-- | run.sh | 311 |
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/ @@ -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 @@ -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 |
