diff --git a/README.md b/README.md index 4d8b90a..fa7869f 100644 --- a/README.md +++ b/README.md @@ -55,19 +55,13 @@ chmod +x runner.sh ### Linux and macOS ```bash -# Run a specific file -./runner.sh HelloWorld.java - -# If there is only one .java file in the current directory, the argument is optional -./runner.sh - -# If no .java file is found in the current directory, runner looks inside examples/ automatically -./runner.sh - -# Enable watch mode: recompiles and reruns every time the file is saved -./runner.sh HelloWorld.java --watch - -# Show help +./runner.sh HelloWorld.java # run a specific file +./runner.sh # auto-detect +./runner.sh HelloWorld.java --watch # watch mode +./runner.sh *.java --all # compile all .java in the directory together +./runner.sh Main.java --input in.txt # pipe file into stdin +./runner.sh Main.java --output out.txt +./runner.sh Main.java --classpath lib/junit.jar ./runner.sh --help ``` @@ -75,8 +69,10 @@ chmod +x runner.sh ```cmd runner.bat HelloWorld.java -runner.bat runner.bat HelloWorld.java --watch +runner.bat Main.java --all +runner.bat Main.java --input in.txt --output out.txt +runner.bat Main.java --classpath lib\junit.jar ``` ### Windows (PowerShell) @@ -85,12 +81,26 @@ runner.bat HelloWorld.java --watch ```powershell .\runner.ps1 HelloWorld.java -.\runner.ps1 .\runner.ps1 HelloWorld.java -Watch +.\runner.ps1 Main.java -All +.\runner.ps1 Main.java -InputFile in.txt -OutputFile out.txt +.\runner.ps1 Main.java -Classpath lib\junit.jar ``` --- +## Flags + +| Flag | Short form (PS1) | Description | +|------|-----------------|-------------| +| `--watch` | `-Watch` | Recompile and rerun every time the file is saved | +| `--all` | `-All` | Compile all `.java` files in the same directory together | +| `--input ` | `-InputFile ` | Pipe a file into stdin when running | +| `--classpath ` | `-Classpath ` | Append to the compile and run classpath | +| `--output ` | `-OutputFile ` | Save program output to a file (also prints to terminal) | + +--- + ## File detection order When no file is passed as an argument, runner searches in this order: @@ -248,10 +258,10 @@ The `examples/` folder contains three files you can use to verify the setup or a ### Phase 4 - Features -- [ ] Multi-file support: compile all `.java` files in the current directory together -- [ ] `--input file.txt` flag to pipe a file into stdin -- [ ] `--classpath path` flag to append jars to the compile and run commands -- [ ] `--output file.txt` flag to save program output to a file +- [x] `--all` flag: compile all `.java` files in the same directory together +- [x] `--input ` flag to pipe a file into stdin +- [x] `--classpath ` flag to append jars to the compile and run commands +- [x] `--output ` flag to save program output to a file (also prints to terminal) ### Phase 5 - Community diff --git a/runner.bat b/runner.bat index 6a7c5c6..58fff17 100644 --- a/runner.bat +++ b/runner.bat @@ -1,7 +1,7 @@ @echo off REM ───────────────────────────────────────────────────────────── REM java-runner - run Java exercises without an IDE (Windows) -REM Usage: runner.bat [File.java] [--watch] +REM Usage: runner.bat [File.java] [--watch] [--all] [--input f] [--classpath p] [--output f] REM ───────────────────────────────────────────────────────────── setlocal EnableDelayedExpansion @@ -17,14 +17,25 @@ set "RESET=[0m" set "FILE=" set "WATCH=false" +set "ALL=false" +set "INPUT_FILE=" +set "EXTRA_CP=" +set "OUTPUT_FILE=" REM ── Parse arguments ─────────────────────────────────────────── -for %%A in (%*) do ( - if "%%A"=="--watch" set "WATCH=true" - if "%%~xA"==".java" set "FILE=%%A" - if "%%A"=="--help" goto :help - if "%%A"=="-h" goto :help -) +:parse_args +if "%~1"=="" goto :end_parse +if /i "%~1"=="--watch" ( set "WATCH=true" & shift & goto :parse_args ) +if /i "%~1"=="--all" ( set "ALL=true" & shift & goto :parse_args ) +if /i "%~1"=="--input" ( shift & set "INPUT_FILE=%~1" & shift & goto :parse_args ) +if /i "%~1"=="--classpath" ( shift & set "EXTRA_CP=%~1" & shift & goto :parse_args ) +if /i "%~1"=="--output" ( shift & set "OUTPUT_FILE=%~1" & shift & goto :parse_args ) +if /i "%~1"=="--help" goto :help +if /i "%~1"=="-h" goto :help +if "%~x1"==".java" ( set "FILE=%~1" & shift & goto :parse_args ) +shift +goto :parse_args +:end_parse REM ── Auto-detect .java file ──────────────────────────────────── if "%FILE%"=="" ( @@ -86,26 +97,49 @@ exit /b REM ── Help ────────────────────────────────────────────────────── :help -echo Usage: runner.bat [File.java] [--watch] -echo File.java Java file to compile and run -echo --watch Recompile and rerun on every file change +echo Usage: runner.bat [File.java] [options] +echo. +echo Options: +echo --watch Recompile and rerun on every file change +echo --all Compile all .java files in the same directory together +echo --input ^ Pipe file contents into stdin when running +echo --classpath ^ Append to the compile and run classpath +echo --output ^ Save program output to file (also prints to terminal) exit /b REM ── Main run function ───────────────────────────────────────── :run for %%F in ("%FILE%") do set "CLASSNAME=%%~nF" + for %%F in ("%FILE%") do set "FILEDIR=%%~dpF" set "TMPDIR=%TEMP%\java-runner-%RANDOM%" mkdir "%TMPDIR%" 2>nul + REM Build classpath + if defined EXTRA_CP ( + set "CP=!TMPDIR!;!EXTRA_CP!" + set "CP_FLAG=-cp "!EXTRA_CP!"" + ) else ( + set "CP=!TMPDIR!" + set "CP_FLAG=" + ) + echo. - echo !CYAN![....] Compiling %CLASSNAME%.java...!RESET! + echo !CYAN![....] Compiling !CLASSNAME!.java...!RESET! + + REM Choose sources: --all compiles every .java in the same directory + if "!ALL!"=="true" ( + set "JAVA_FILES=" + for %%F in ("!FILEDIR!*.java") do set "JAVA_FILES=!JAVA_FILES! "%%~fF"" + javac !CP_FLAG! -d "!TMPDIR!" !JAVA_FILES! 2>"!TMPDIR!\errors.txt" + ) else ( + javac !CP_FLAG! -d "!TMPDIR!" "%FILE%" 2>"!TMPDIR!\errors.txt" + ) - javac -d "%TMPDIR%" "%FILE%" 2>"%TMPDIR%\errors.txt" if errorlevel 1 ( echo !RED![FAIL] Compilation failed:!RESET! echo. - type "%TMPDIR%\errors.txt" - rmdir /s /q "%TMPDIR%" + type "!TMPDIR!\errors.txt" + rmdir /s /q "!TMPDIR!" exit /b 1 ) @@ -113,9 +147,28 @@ REM ── Main run function ───────────────── echo !CYAN!───────────────────────── output ─────────────────────────!RESET! set "START_TIME=%TIME%" - java -cp "%TMPDIR%" "%CLASSNAME%" - set "JAVA_EXIT=%ERRORLEVEL%" - set "END_TIME=%TIME%" + + REM Run java with optional input and output redirection + if defined OUTPUT_FILE ( + set "TMPOUT=!TMPDIR!\output.txt" + if defined INPUT_FILE ( + java -cp "!CP!" "!CLASSNAME!" < "!INPUT_FILE!" > "!TMPOUT!" + ) else ( + java -cp "!CP!" "!CLASSNAME!" > "!TMPOUT!" + ) + set "JAVA_EXIT=!ERRORLEVEL!" + set "END_TIME=!TIME!" + type "!TMPOUT!" + copy "!TMPOUT!" "!OUTPUT_FILE!" >nul + ) else ( + if defined INPUT_FILE ( + java -cp "!CP!" "!CLASSNAME!" < "!INPUT_FILE!" + ) else ( + java -cp "!CP!" "!CLASSNAME!" + ) + set "JAVA_EXIT=!ERRORLEVEL!" + set "END_TIME=!TIME!" + ) REM Parse start time (HH:MM:SS.cc) for /f "tokens=1-4 delims=:,. " %%a in ("!START_TIME!") do ( @@ -133,8 +186,9 @@ REM ── Main run function ───────────────── echo !RED![FAIL] Finished in !ELAPSED_MS!ms ^(exit code !JAVA_EXIT!^)!RESET! ) else ( echo !GREEN![ OK ] Finished in !ELAPSED_MS!ms!RESET! + if defined OUTPUT_FILE echo !CYAN! Output saved to: !OUTPUT_FILE!!RESET! ) echo. - rmdir /s /q "%TMPDIR%" + rmdir /s /q "!TMPDIR!" exit /b !JAVA_EXIT! diff --git a/runner.ps1 b/runner.ps1 index 4c65009..da1e541 100644 --- a/runner.ps1 +++ b/runner.ps1 @@ -1,16 +1,25 @@ # java-runner - run Java exercises without an IDE (PowerShell) -# Usage: .\runner.ps1 [File.java] [-Watch] +# Usage: .\runner.ps1 [File.java] [-Watch] [-All] [-InputFile f] [-Classpath p] [-OutputFile f] param( [string]$JavaFile = "", [switch]$Watch, + [switch]$All, + [string]$InputFile = "", + [string]$Classpath = "", + [string]$OutputFile = "", [switch]$Help ) if ($Help) { - Write-Host "Usage: .\runner.ps1 [File.java] [-Watch]" - Write-Host " File.java Java file to compile and run (optional if only one .java exists)" - Write-Host " -Watch Recompile and rerun on every file save (event-based)" + Write-Host "Usage: .\runner.ps1 [File.java] [options]" + Write-Host "" + Write-Host "Options:" + Write-Host " -Watch Recompile and rerun on every file save (event-based)" + Write-Host " -All Compile all .java files in the same directory together" + Write-Host " -InputFile Pipe file contents into stdin when running" + Write-Host " -Classpath Append to the compile and run classpath" + Write-Host " -OutputFile Save program output to file (also prints to terminal)" exit 0 } @@ -46,14 +55,27 @@ $JavaFile = (Resolve-Path $JavaFile).Path function Invoke-Run { $className = [System.IO.Path]::GetFileNameWithoutExtension($JavaFile) - $tmpDir = Join-Path $env:TEMP ("java-runner-" + [System.IO.Path]::GetRandomFileName()) + $fileDir = [System.IO.Path]::GetDirectoryName($JavaFile) + $tmpDir = Join-Path $env:TEMP ("java-runner-" + [System.IO.Path]::GetRandomFileName()) New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null + # Build classpath + $cp = $tmpDir + if ($Classpath) { $cp = "$tmpDir;$Classpath" } + Write-Host "" Write-Host "[....] Compiling $className.java..." -ForegroundColor Cyan + # Choose sources: -All compiles every .java in the same directory $errFile = Join-Path $tmpDir "errors.txt" - & javac -d $tmpDir $JavaFile 2>$errFile + if ($All) { + $sources = (Get-ChildItem -Path $fileDir -Filter "*.java" -File | ForEach-Object { $_.FullName }) + $cpArgs = if ($Classpath) { @("-cp", $Classpath) } else { @() } + & javac @cpArgs -d $tmpDir @sources 2>$errFile + } else { + $cpArgs = if ($Classpath) { @("-cp", $Classpath) } else { @() } + & javac @cpArgs -d $tmpDir $JavaFile 2>$errFile + } $compileExit = $LASTEXITCODE if ($compileExit -ne 0) { @@ -68,8 +90,22 @@ function Invoke-Run { Write-Host "───────────────────────── output ─────────────────────────" -ForegroundColor Cyan $sw = [System.Diagnostics.Stopwatch]::StartNew() - & java -cp $tmpDir $className - $javaExit = $LASTEXITCODE + + # Run with optional input/output redirection + if ($InputFile -and $OutputFile) { + & java -cp $cp $className < $InputFile | Tee-Object -FilePath $OutputFile + $javaExit = $LASTEXITCODE + } elseif ($InputFile) { + & java -cp $cp $className < $InputFile + $javaExit = $LASTEXITCODE + } elseif ($OutputFile) { + & java -cp $cp $className | Tee-Object -FilePath $OutputFile + $javaExit = $LASTEXITCODE + } else { + & java -cp $cp $className + $javaExit = $LASTEXITCODE + } + $sw.Stop() $elapsed = $sw.ElapsedMilliseconds @@ -78,6 +114,7 @@ function Invoke-Run { Write-Host "[FAIL] Finished in ${elapsed}ms (exit code $javaExit)" -ForegroundColor Red } else { Write-Host "[ OK ] Finished in ${elapsed}ms" -ForegroundColor Green + if ($OutputFile) { Write-Host " Output saved to: $OutputFile" -ForegroundColor Cyan } } Write-Host "" @@ -88,7 +125,7 @@ function Invoke-Run { if ($Watch) { Write-Host "[WATCH] Watch mode enabled - save the file to rerun" -ForegroundColor Yellow - $dir = Split-Path $JavaFile -Parent + $dir = Split-Path $JavaFile -Parent $filename = Split-Path $JavaFile -Leaf Clear-Host diff --git a/runner.sh b/runner.sh index 6d835a5..d0c9ee1 100644 --- a/runner.sh +++ b/runner.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # ───────────────────────────────────────────────────────────── # java-runner - run Java exercises without an IDE -# Usage: ./runner.sh [File.java] [--watch] +# Usage: ./runner.sh [File.java] [--watch] [--all] [--input f] [--classpath p] [--output f] # ───────────────────────────────────────────────────────────── set -euo pipefail @@ -24,18 +24,33 @@ esac # ── Arguments ───────────────────────────────────────────────── FILE="" WATCH=false - -for arg in "$@"; do - case $arg in - --watch) WATCH=true ;; - *.java) FILE="$arg" ;; +ALL=false +INPUT_FILE="" +EXTRA_CP="" +OUTPUT_FILE="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --watch) WATCH=true ;; + --all) ALL=true ;; + --input) INPUT_FILE="$2"; shift ;; + --classpath) EXTRA_CP="$2"; shift ;; + --output) OUTPUT_FILE="$2"; shift ;; --help|-h) - echo -e "Usage: ./runner.sh [File.java] [--watch]" - echo -e " File.java Java file to compile and run (optional if only one .java exists)" - echo -e " --watch Recompile and rerun on every file save" + echo "Usage: ./runner.sh [File.java] [options]" + echo "" + echo "Options:" + echo " --watch Recompile and rerun on every file save" + echo " --all Compile all .java files in the same directory together" + echo " --input Pipe file contents into stdin when running" + echo " --classpath Append to the compile and run classpath" + echo " --output Save program output to file (also prints to terminal)" exit 0 ;; + *.java) FILE="$1" ;; + *) echo -e "${RED}✗ Unknown argument: $1${RESET}"; exit 1 ;; esac + shift done # ── Auto-detect .java file ──────────────────────────────────── @@ -71,16 +86,28 @@ fi run() { local classname classname=$(basename "$FILE" .java) + local filedir + filedir=$(dirname "$FILE") local tmpdir tmpdir=$(mktemp -d) + # Build classpath + local cp="$tmpdir" + [[ -n "$EXTRA_CP" ]] && cp="$cp:$EXTRA_CP" + echo -e "" echo -e "${CYAN}▶ Compiling ${classname}.java...${RESET}" - # Compile - capture errors - if ! javac -d "$tmpdir" "$FILE" 2>"$tmpdir/errors.txt"; then + # Choose sources: --all compiles every .java in the same directory + local sources + if $ALL; then + sources=("$filedir"/*.java) + else + sources=("$FILE") + fi + + if ! javac ${EXTRA_CP:+-cp "$EXTRA_CP"} -d "$tmpdir" "${sources[@]}" 2>"$tmpdir/errors.txt"; then echo -e "${RED}✗ Compilation failed:${RESET}\n" - # Strip absolute path noise sed "s|$PWD/||g" "$tmpdir/errors.txt" rm -rf "$tmpdir" return 1 @@ -92,14 +119,28 @@ run() { # Run and measure time local start start=$(date +%s%N 2>/dev/null || date +%s) + + local java_exit set +e - java -cp "$tmpdir" "$classname" - local java_exit=$? + if [[ -n "$INPUT_FILE" && -n "$OUTPUT_FILE" ]]; then + java -cp "$cp" "$classname" < "$INPUT_FILE" | tee "$OUTPUT_FILE" + java_exit=${PIPESTATUS[0]} + elif [[ -n "$INPUT_FILE" ]]; then + java -cp "$cp" "$classname" < "$INPUT_FILE" + java_exit=$? + elif [[ -n "$OUTPUT_FILE" ]]; then + java -cp "$cp" "$classname" | tee "$OUTPUT_FILE" + java_exit=${PIPESTATUS[0]} + else + java -cp "$cp" "$classname" + java_exit=$? + fi set -e + local end end=$(date +%s%N 2>/dev/null || date +%s) - # Calculate elapsed (nanoseconds if available, else seconds) + # Calculate elapsed local elapsed if [[ ${#start} -gt 10 ]]; then elapsed=$(( (end - start) / 1000000 )) @@ -108,6 +149,7 @@ run() { echo -e "${RED}✗ Finished in ${elapsed}ms (exit code ${java_exit})${RESET}\n" else echo -e "${GREEN}✓ Finished in ${elapsed}ms${RESET}\n" + [[ -n "$OUTPUT_FILE" ]] && echo -e "${CYAN} Output saved to: ${OUTPUT_FILE}${RESET}" fi else elapsed=$(( end - start )) @@ -116,6 +158,7 @@ run() { echo -e "${RED}✗ Finished in ${elapsed}s (exit code ${java_exit})${RESET}\n" else echo -e "${GREEN}✓ Finished in ${elapsed}s${RESET}\n" + [[ -n "$OUTPUT_FILE" ]] && echo -e "${CYAN} Output saved to: ${OUTPUT_FILE}${RESET}" fi fi