diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7164234..547b3d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,27 +2,41 @@ name: CI on: push: - branches: [ main ] + branches: + - main pull_request: - workflow_dispatch: + branches: + - main + +permissions: + contents: read env: CARGO_TERM_COLOR: always jobs: - lint: + check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Install rust toolchain - uses: dtolnay/rust-toolchain@stable + - name: Checkout + uses: actions/checkout@v6 + # https://github.com/actions/cache/blob/main/examples.md#rust---cargo + # Depends on `Cargo.lock` --> Has to be after checkout. + - uses: actions/cache@v5 with: - components: rustfmt, clippy - + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Install Rust + run: | + rustup toolchain install nightly --no-self-update --profile minimal --component rust-src,rustfmt,clippy - name: Check formatting run: cargo fmt --all -- --check - + - name: Run tests + run: cargo test --all-features --all-targets - name: Run clippy - run: cargo clippy --all-targets --all-features -- -D warnings - \ No newline at end of file + run: cargo clippy --all-features --all-targets -- --no-deps --deny warnings -W clippy::pedantic diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 3ae24e0..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,291 +0,0 @@ -# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/ -# -# Copyright 2022-2024, axodotdev -# SPDX-License-Identifier: MIT or Apache-2.0 -# -# CI that: -# -# * checks for a Git Tag that looks like a release -# * builds artifacts with dist (archives, installers, hashes) -# * uploads those artifacts to temporary workflow zip -# * on success, uploads the artifacts to a GitHub Release -# -# Note that the GitHub Release will be created with a generated -# title/body based on your changelogs. - -name: Release -permissions: - "contents": "write" - -# This task will run whenever you push a git tag that looks like a version -# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. -# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where -# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION -# must be a Cargo-style SemVer Version (must have at least major.minor.patch). -# -# If PACKAGE_NAME is specified, then the announcement will be for that -# package (erroring out if it doesn't have the given version or isn't dist-able). -# -# If PACKAGE_NAME isn't specified, then the announcement will be for all -# (dist-able) packages in the workspace with that version (this mode is -# intended for workspaces with only one dist-able package, or with all dist-able -# packages versioned/released in lockstep). -# -# If you push multiple tags at once, separate instances of this workflow will -# spin up, creating an independent announcement for each one. However, GitHub -# will hard limit this to 3 tags per commit, as it will assume more tags is a -# mistake. -# -# If there's a prerelease-style suffix to the version, then the release(s) -# will be marked as a prerelease. -on: - pull_request: - push: - tags: - - '**[0-9]+.[0-9]+.[0-9]+*' - -jobs: - # Run 'dist plan' (or host) to determine what tasks we need to do - plan: - runs-on: "ubuntu-latest" - outputs: - val: ${{ steps.plan.outputs.manifest }} - tag: ${{ !github.event.pull_request && github.ref_name || '' }} - tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} - publishing: ${{ !github.event.pull_request }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install dist - # we specify bash to get pipefail; it guards against the `curl` command - # failing. otherwise `sh` won't catch that `curl` returned non-0 - shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.28.0/cargo-dist-installer.sh | sh" - - name: Cache dist - uses: actions/upload-artifact@v4 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/dist - # sure would be cool if github gave us proper conditionals... - # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible - # functionality based on whether this is a pull_request, and whether it's from a fork. - # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* - # but also really annoying to build CI around when it needs secrets to work right.) - - id: plan - run: | - dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json - echo "dist ran successfully" - cat plan-dist-manifest.json - echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v4 - with: - name: artifacts-plan-dist-manifest - path: plan-dist-manifest.json - - # Build and packages all the platform-specific things - build-local-artifacts: - name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) - # Let the initial task tell us to not run (currently very blunt) - needs: - - plan - if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} - strategy: - fail-fast: false - # Target platforms/runners are computed by dist in create-release. - # Each member of the matrix has the following arguments: - # - # - runner: the github runner - # - dist-args: cli flags to pass to dist - # - install-dist: expression to run to install dist on the runner - # - # Typically there will be: - # - 1 "global" task that builds universal installers - # - N "local" tasks that build each platform's binaries and platform-specific installers - matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} - runs-on: ${{ matrix.runner }} - container: ${{ matrix.container && matrix.container.image || null }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json - steps: - - name: enable windows longpaths - run: | - git config --global core.longpaths true - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install Rust non-interactively if not already installed - if: ${{ matrix.container }} - run: | - if ! command -v cargo > /dev/null 2>&1; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - fi - - name: Install dist - run: ${{ matrix.install_dist.run }} - # Get the dist-manifest - - name: Fetch local artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - name: Install dependencies - run: | - ${{ matrix.packages_install }} - - name: Build artifacts - run: | - # Actually do builds and make zips and whatnot - dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json - echo "dist ran successfully" - - id: cargo-dist - name: Post-build - # We force bash here just because github makes it really hard to get values up - # to "real" actions without writing to env-vars, and writing to env-vars has - # inconsistent syntax between shell and powershell. - shell: bash - run: | - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v4 - with: - name: artifacts-build-local-${{ join(matrix.targets, '_') }} - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - - # Build and package all the platform-agnostic(ish) things - build-global-artifacts: - needs: - - plan - - build-local-artifacts - runs-on: "ubuntu-latest" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v4 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Get all the local artifacts for the global tasks to use (for e.g. checksums) - - name: Fetch local artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: cargo-dist - shell: bash - run: | - dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json - echo "dist ran successfully" - - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v4 - with: - name: artifacts-build-global - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - # Determines if we should publish/announce - host: - needs: - - plan - - build-local-artifacts - - build-global-artifacts - # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) - if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - runs-on: "ubuntu-latest" - outputs: - val: ${{ steps.host.outputs.manifest }} - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v4 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Fetch artifacts from scratch-storage - - name: Fetch artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: host - shell: bash - run: | - dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json - echo "artifacts uploaded and released successfully" - cat dist-manifest.json - echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v4 - with: - # Overwrite the previous copy - name: artifacts-dist-manifest - path: dist-manifest.json - # Create a GitHub Release while uploading all files to it - - name: "Download GitHub Artifacts" - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: artifacts - merge-multiple: true - - name: Cleanup - run: | - # Remove the granular manifests - rm -f artifacts/*-dist-manifest.json - - name: Create GitHub Release - env: - PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" - ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" - ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" - RELEASE_COMMIT: "${{ github.sha }}" - run: | - # Write and read notes from a file to avoid quoting breaking things - echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt - - gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* - - announce: - needs: - - plan - - host - # use "always() && ..." to allow us to wait for all publish jobs while - # still allowing individual publish jobs to skip themselves (for prereleases). - # "host" however must run to completion, no skipping allowed! - if: ${{ always() && needs.host.result == 'success' }} - runs-on: "ubuntu-latest" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive diff --git a/Cargo.nix b/Cargo.nix index 2ce713e..cc22005 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -171,8 +171,9 @@ rec { } ]; src = lib.cleanSourceWith { filter = sourceFilter; src = ./.; }; + libName = "rce_runner"; authors = [ - "ToolKitHub" + "ToolKitHub " ]; dependencies = [ { diff --git a/Cargo.toml b/Cargo.toml index 630496f..74b03a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,25 @@ [package] name = "rce-runner" version = "1.2.4" -authors = ["ToolKitHub"] -description = "A command line tool that runs your code snippets in many different programming languages" edition = "2024" -license = "MIT" +authors = ["ToolKitHub "] +description = """ +A command line application that reads code as a json payload from stdin, compiles and runs the code, and writes +the result as json to stdout. +""" repository = "https://github.com/ToolKitHub/rce-runner" -homepage = "https://github.com/ToolKitHub/rce-runner?tab=readme-ov-file#readme" +license = "MIT" [dependencies] serde = { version = "1.0.116", features = ["derive"] } serde_json = "1.0.58" -# We use `opt-level = "s"` as it significantly reduces binary size. +# See https://doc.rust-lang.org/cargo/reference/profiles.html +# See default profiles: https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles [profile.release] -codegen-units = 1 # reduces binary size by ~2% -debug = "full" # No one needs an undebuggable release binary -lto = true # reduces binary size by ~14% -opt-level = "s" # reduces binary size by ~25% -panic = "abort" # reduces binary size by ~50% in combination with -Zbuild-std-features=panic_immediate_abort -split-debuginfo = "packed" # generates a separate *.dwp/*.dSYM so the binary can get stripped -strip = "symbols" # See split-debuginfo - allows us to drop the size by ~65% -incremental = true # Improves re-compile times - -# The profile that 'dist' will build with -[profile.dist] -inherits = "release" +codegen-units = 1 # reduces binary size by ~2% +lto = true # reduces binary size by ~14% +panic = "abort" # reduces binary size by ~50% in combination with -Zbuild-std-features=panic_immediate_abort +split-debuginfo = "packed" # generates a separate *.dwp/*.dSYM so the binary can get stripped +strip = "symbols" # See split-debuginfo - allows us to drop the size by ~65% +incremental = true # Improves re-compile times diff --git a/README.md b/README.md index 1c167c6..7f5a4ab 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,13 @@ # rce-runner -## What is this? +A command line application that reads code as a json payload from stdin, compiles and runs the code, and writes +the result as json to stdout. -**rce-runner** is a command line tool that runs your code snippets in many different programming languages. You give it code, it runs the code, and gives you back the results. +Used by [rce-images](https://github.com/ToolKitHub/rce-images) to run code. -## When would I use this? +## Payload Format -- You're building a website where users can write and run code -- You need to test code in many languages without installing all those languages -- You're creating a learning platform and need to execute student code -- You're building tools for technical interviews or coding challenges - -## Installation - -Run this command to install rce-runner: - -```sh -curl --proto '=https' --tlsv1.2 -LsSf https://github.com/ToolKitHub/rce-runner/releases/download/v1.2.4/rce-runner-installer.sh | sh -``` - -## Quick Start Guide - -### What you'll need - -- A system with the programming languages you want to run installed -- Basic familiarity with JSON format - -### Basic Example - -Send this JSON as input: +Input: ```json { @@ -42,7 +21,7 @@ Send this JSON as input: } ``` -You'll get this output: +Output: ```json { @@ -52,62 +31,59 @@ You'll get this output: } ``` -## Supported Languages - -rce-runner supports 40+ programming languages: - -| Language | File Extension | -|----------|---------------| -| Assembly | .asm | -| Ats | .dats | -| Bash | .sh | -| C | .c | -| Clisp | .lisp | -| Clojure | .clj | -| Cobol | .cob | -| CoffeeScript | .coffee | -| Cpp (C++) | .cpp | -| Crystal | .cr | -| Csharp (C#) | .cs | -| D | .d | -| Dart | .dart | -| Elixir | .ex | -| Elm | .elm | -| Erlang | .erl | -| Fsharp (F#) | .fs | -| Go | .go | -| Groovy | .groovy | -| Guile | .scm | -| Hare | .ha | -| Haskell | .hs | -| Idris | .idr | -| Java | .java | -| JavaScript | .js | -| Julia | .jl | -| Kotlin | .kt | -| Lua | .lua | -| Mercury | .m | -| Nim | .nim | -| Nix | .nix | -| Ocaml | .ml | -| Pascal | .pas | -| Perl | .pl | -| Php | .php | -| Python | .py | -| Raku | .raku | -| Ruby | .rb | -| Rust | .rs | -| SaC | .c | -| Scala | .scala | -| Swift | .swift | -| TypeScript | .ts | -| Zig | .zig | - - -> [!TIP] +## Supported Programming Languages + +| Language | File Extension | +| ------------ | -------------- | +| Assembly | .asm | +| Ats | .dats | +| Bash | .sh | +| C | .c | +| Clisp | .lisp | +| Clojure | .clj | +| Cobol | .cob | +| CoffeeScript | .coffee | +| Cpp (C++) | .cpp | +| Crystal | .cr | +| Csharp (C#) | .cs | +| D | .d | +| Dart | .dart | +| Elixir | .ex | +| Elm | .elm | +| Erlang | .erl | +| Fsharp (F#) | .fs | +| Go | .go | +| Groovy | .groovy | +| Guile | .scm | +| Hare | .ha | +| Haskell | .hs | +| Idris | .idr | +| Java | .java | +| JavaScript | .js | +| Julia | .jl | +| Kotlin | .kt | +| Lua | .lua | +| Mercury | .m | +| Nim | .nim | +| Nix | .nix | +| Ocaml | .ml | +| Pascal | .pas | +| Perl | .pl | +| Php | .php | +| Python | .py | +| Raku | .raku | +| Ruby | .rb | +| Rust | .rs | +| SaC | .c | +| Scala | .scala | +| Swift | .swift | +| TypeScript | .ts | +| Zig | .zig | + +> [!TIP] > -> Can't find your preferred programming language? [Open an issue](https://github.com/ToolKitHub/rce-runner/issues/new) or send a PR to request support. +> Can't find your preferred programming language? [Open an issue](https://github.com/ToolKitHub/rce-runner/issues/new) to request support. ## License -See [License](LICENSE) +This project is licensed under the [MIT License](LICENSE). diff --git a/dist-workspace.toml b/dist-workspace.toml deleted file mode 100644 index 11fc620..0000000 --- a/dist-workspace.toml +++ /dev/null @@ -1,21 +0,0 @@ -[workspace] -members = ["cargo:."] - -# Config for 'dist' -[dist] -# The preferred dist version to use in CI (Cargo.toml SemVer syntax) -cargo-dist-version = "0.28.0" -# CI backends to support -ci = "github" -# The installers to generate for each app -installers = ["shell"] -# Target platforms to build apps for (Rust target-triple syntax) -targets = ["x86_64-unknown-linux-gnu"] -# Path that installers should place binaries in -install-path = "CARGO_HOME" -# Whether to install an updater program -install-updater = true - -[dist.github-custom-runners] -global = "ubuntu-latest" -x86_64-unknown-linux-gnu = "ubuntu-latest" diff --git a/run.sh b/run.sh index 4550122..42b6ed1 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh JSON=$( cat <<-END diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 31578d3..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "stable" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..9e3b69a --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +style_edition = "2024" +use_small_heuristics = "Max" +newline_style = "Unix" +use_field_init_shorthand = true diff --git a/src/rce_runner/cmd.rs b/src/cmd.rs similarity index 81% rename from src/rce_runner/cmd.rs rename to src/cmd.rs index 1afdf4d..f8276a9 100644 --- a/src/rce_runner/cmd.rs +++ b/src/cmd.rs @@ -26,11 +26,11 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Execute(err) => { - write!(f, "Error while executing command. {}", err) + write!(f, "Error while executing command. {err}") } Error::Output(err) => { - write!(f, "Error in output from command. {}", err) + write!(f, "Error in output from command. {err}") } } } @@ -48,7 +48,7 @@ impl fmt::Display for ExecuteError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ExecuteError::Execute(err) => { - write!(f, "{}", err) + write!(f, "{err}") } ExecuteError::CaptureStdin() => { @@ -56,11 +56,11 @@ impl fmt::Display for ExecuteError { } ExecuteError::WriteStdin(err) => { - write!(f, "Failed to write to stdin. {}", err) + write!(f, "Failed to write to stdin. {err}") } ExecuteError::WaitForChild(err) => { - write!(f, "Failed while waiting for child. {}", err) + write!(f, "Failed while waiting for child. {err}") } } } @@ -107,15 +107,15 @@ impl fmt::Display for ErrorOutput { let mut messages = Vec::new(); if let Some(code) = self.exit_code { - messages.push(format!("code: {}", code)); + messages.push(format!("code: {code}")); } if !self.stdout.is_empty() { - messages.push(format!("stdout: {}", self.stdout)) + messages.push(format!("stdout: {}", self.stdout)); } if !self.stderr.is_empty() { - messages.push(format!("stderr: {}", self.stderr)) + messages.push(format!("stderr: {}", self.stderr)); } write!(f, "{}", messages.join(", ")) @@ -133,15 +133,15 @@ impl fmt::Display for OutputError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { OutputError::ExitFailure(err) => { - write!(f, "Exited with non-zero exit code. {}", err) + write!(f, "Exited with non-zero exit code. {err}") } OutputError::ReadStdout(err) => { - write!(f, "Failed to read stdout. {}", err) + write!(f, "Failed to read stdout. {err}") } OutputError::ReadStderr(err) => { - write!(f, "Failed to read stderr. {}", err) + write!(f, "Failed to read stderr. {err}") } } } @@ -161,10 +161,6 @@ pub fn get_output(output: process::Output) -> Result let exit_code = output.status.code(); - Err(OutputError::ExitFailure(ErrorOutput { - stdout, - stderr, - exit_code, - })) + Err(OutputError::ExitFailure(ErrorOutput { stdout, stderr, exit_code })) } } diff --git a/src/language.rs b/src/language.rs new file mode 100644 index 0000000..c13ffae --- /dev/null +++ b/src/language.rs @@ -0,0 +1,393 @@ +use crate::non_empty_vec; +use std::{borrow::Cow, path}; + +#[derive(serde::Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum Language { + Assembly, + Ats, + Bash, + C, + Clisp, + Clojure, + Cobol, + CoffeeScript, + Cpp, + Crystal, + Csharp, + D, + Dart, + Elixir, + Elm, + Erlang, + Fsharp, + Go, + Groovy, + Guile, + Hare, + Haskell, + Idris, + Java, + JavaScript, + Julia, + Kotlin, + Lua, + Mercury, + Nim, + Nix, + Ocaml, + Pascal, + Perl, + Php, + Python, + Raku, + Ruby, + Rust, + SaC, + Scala, + Swift, + TypeScript, + Zig, +} + +#[derive(Debug)] +pub struct RunInstructions { + pub build_commands: Vec>, + pub run_command: Cow<'static, str>, +} + +#[must_use] +pub fn run_instructions( + language: &Language, + files: non_empty_vec::NonEmptyVec, +) -> RunInstructions { + let (main_file, other_files) = files.parts(); + let main_file_str = main_file.to_string_lossy(); + + match language { + Language::Assembly => RunInstructions { + build_commands: vec![ + format!("nasm -f elf64 -o a.o {main_file_str}").into(), + "ld -o a.out a.o".into(), + ], + run_command: "./a.out".into(), + }, + + Language::Ats => RunInstructions { + build_commands: vec![ + format!("patscc -o a.out {} {}", main_file_str, source_files(other_files, "dats")) + .into(), + ], + run_command: "./a.out".into(), + }, + + Language::Bash => RunInstructions { + build_commands: vec![], + run_command: format!("bash {main_file_str}").into(), + }, + + Language::C => RunInstructions { + build_commands: vec![ + format!("clang -o a.out -lm {} {}", main_file_str, source_files(other_files, "c")) + .into(), + ], + run_command: "./a.out".into(), + }, + + Language::Clisp => RunInstructions { + build_commands: Vec::new(), + run_command: format!("sbcl --noinform --non-interactive --load {main_file_str}").into(), + }, + + Language::Clojure => RunInstructions { + build_commands: Vec::new(), + run_command: format!("clj -M {main_file_str}").into(), + }, + + Language::Cobol => RunInstructions { + build_commands: vec![ + format!("cobc -x -o a.out {} {}", main_file_str, source_files(other_files, "cob")) + .into(), + ], + run_command: "./a.out".into(), + }, + + Language::CoffeeScript => RunInstructions { + build_commands: Vec::new(), + run_command: format!("coffee {main_file_str}").into(), + }, + + Language::Cpp => RunInstructions { + build_commands: vec![ + format!( + "clang++ -std=c++11 -o a.out {} {}", + main_file_str, + source_files(other_files, "c") + ) + .into(), + ], + run_command: "./a.out".into(), + }, + + Language::Crystal => RunInstructions { + build_commands: Vec::new(), + run_command: format!("crystal run {main_file_str}").into(), + }, + + Language::Csharp => RunInstructions { + build_commands: vec![ + format!("mcs -out:a.exe {} {}", main_file_str, source_files(other_files, "cs")) + .into(), + ], + run_command: "mono a.exe".into(), + }, + + Language::D => RunInstructions { + build_commands: vec![ + format!("dmd -ofa.out {} {}", main_file_str, source_files(other_files, "d")).into(), + ], + run_command: "./a.out".into(), + }, + + Language::Dart => RunInstructions { + build_commands: Vec::new(), + run_command: format!("dart {main_file_str}").into(), + }, + + Language::Elixir => RunInstructions { + build_commands: Vec::new(), + run_command: format!("elixirc {} {}", main_file_str, source_files(other_files, "ex")) + .into(), + }, + + Language::Elm => RunInstructions { + build_commands: vec![format!("elm make --output a.js {main_file_str}").into()], + run_command: "elm-runner a.js".into(), + }, + + Language::Erlang => RunInstructions { + build_commands: filter_by_extension(other_files, "erl") + .iter() + .map(|file| format!("erlc {}", file.to_string_lossy()).into()) + .collect(), + run_command: format!("escript {main_file_str}").into(), + }, + + Language::Fsharp => { + let mut source_files = filter_by_extension(other_files, "fs"); + source_files.reverse(); + + RunInstructions { + build_commands: vec![ + format!( + "fsharpc --out:a.exe {} {}", + space_separated_files(&source_files), + main_file_str + ) + .into(), + ], + run_command: "mono a.exe".into(), + } + } + + Language::Go => RunInstructions { + build_commands: vec![format!("go build -o a.out {main_file_str}").into()], + run_command: "./a.out".into(), + }, + + Language::Groovy => RunInstructions { + build_commands: Vec::new(), + run_command: format!("groovy {main_file_str}").into(), + }, + + Language::Guile => RunInstructions { + build_commands: Vec::new(), + run_command: format!( + "guile --no-debug --fresh-auto-compile --no-auto-compile -s {main_file_str}" + ) + .into(), + }, + + Language::Hare => RunInstructions { + build_commands: vec![format!("hare build -o a.out {main_file_str}").into()], + run_command: "./a.out".into(), + }, + + Language::Haskell => RunInstructions { + build_commands: Vec::new(), + run_command: format!("runghc {main_file_str}").into(), + }, + + Language::Idris => RunInstructions { + build_commands: vec![format!("idris2 -o a.out --output-dir . {main_file_str}").into()], + run_command: "./a.out".into(), + }, + + Language::Java => { + let file_stem = main_file.file_stem().and_then(|s| s.to_str()).unwrap_or("Main"); + + RunInstructions { + build_commands: vec![format!("javac {main_file_str}").into()], + run_command: format!("java {}", titlecase_ascii(file_stem)).into(), + } + } + + Language::JavaScript => RunInstructions { + build_commands: Vec::new(), + run_command: format!("node {main_file_str}").into(), + }, + + Language::Julia => RunInstructions { + build_commands: Vec::new(), + run_command: format!("julia {main_file_str}").into(), + }, + + Language::Kotlin => { + let file_stem = main_file.file_stem().and_then(|s| s.to_str()).unwrap_or("Main"); + + RunInstructions { + build_commands: vec![format!("kotlinc {main_file_str}").into()], + run_command: format!("kotlin {}Kt", titlecase_ascii(file_stem)).into(), + } + } + + Language::Lua => RunInstructions { + build_commands: vec![], + run_command: format!("lua {main_file_str}").into(), + }, + + Language::Mercury => RunInstructions { + build_commands: vec![ + format!("mmc -o a.out {} {}", main_file_str, source_files(other_files, "m")).into(), + ], + run_command: "./a.out".into(), + }, + + Language::Nim => RunInstructions { + build_commands: Vec::new(), + run_command: format!("nim --hints:off --verbosity:0 compile --run {main_file_str}") + .into(), + }, + + Language::Nix => RunInstructions { + build_commands: Vec::new(), + run_command: format!("nix-instantiate --eval {main_file_str}").into(), + }, + + Language::Ocaml => { + let mut source_files = filter_by_extension(other_files, "ml"); + source_files.reverse(); + + RunInstructions { + build_commands: vec![ + format!( + "ocamlc -o a.out {} {}", + space_separated_files(&source_files), + main_file_str + ) + .into(), + ], + run_command: "./a.out".into(), + } + } + + Language::Pascal => RunInstructions { + build_commands: vec![format!("fpc -oa.out {main_file_str}").into()], + run_command: "./a.out".into(), + }, + + Language::Perl => RunInstructions { + build_commands: Vec::new(), + run_command: format!("perl {main_file_str}").into(), + }, + + Language::Php => RunInstructions { + build_commands: Vec::new(), + run_command: format!("php {main_file_str}").into(), + }, + + Language::Python => RunInstructions { + build_commands: Vec::new(), + run_command: format!("python {main_file_str}").into(), + }, + + Language::Raku => RunInstructions { + build_commands: Vec::new(), + run_command: format!("raku {main_file_str}").into(), + }, + + Language::Ruby => RunInstructions { + build_commands: Vec::new(), + run_command: format!("ruby {main_file_str}").into(), + }, + + Language::Rust => RunInstructions { + build_commands: vec![format!("rustc -o a.out {main_file_str}").into()], + run_command: "./a.out".into(), + }, + + Language::SaC => RunInstructions { + build_commands: vec![ + format!( + "sac2c -t seq -o a.out {} {}", + main_file_str, + source_files(other_files, "c") + ) + .into(), + ], + run_command: "./a.out".into(), + }, + + Language::Scala => RunInstructions { + build_commands: vec![ + format!("scalac {} {}", main_file_str, source_files(other_files, "scala")).into(), + ], + run_command: "scala Main".into(), + }, + + Language::Swift => RunInstructions { + build_commands: vec![ + format!("swiftc -o a.out {} {}", main_file_str, source_files(other_files, "swift")) + .into(), + ], + run_command: "./a.out".into(), + }, + + Language::TypeScript => RunInstructions { + build_commands: vec![ + format!("tsc -outFile a.js {} {}", main_file_str, source_files(other_files, "ts")) + .into(), + ], + run_command: "node a.js".into(), + }, + + Language::Zig => RunInstructions { + build_commands: Vec::new(), + run_command: format!("zig run {main_file_str}").into(), + }, + } +} + +fn source_files(files: Vec, extension: &str) -> String { + space_separated_files(&filter_by_extension(files, extension)) +} + +fn filter_by_extension(files: Vec, extension: &str) -> Vec { + files + .into_iter() + .filter(|file| file.extension().and_then(|s| s.to_str()) == Some(extension)) + .collect() +} + +fn space_separated_files(files: &[path::PathBuf]) -> String { + files.iter().map(|file| file.to_string_lossy()).collect::>>().join(" ") +} + +fn titlecase_ascii(s: &str) -> Cow<'_, str> { + if !s.is_ascii() || s.len() < 2 { + s.into() + } else { + let (head, tail) = s.split_at(1); + format!("{}{}", head.to_ascii_uppercase(), tail).into() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2e1d38e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +#![allow(clippy::missing_errors_doc, clippy::too_many_lines)] + +pub mod cmd; +pub mod language; +pub mod non_empty_vec; diff --git a/src/main.rs b/src/main.rs index 6efd1c7..5debace 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -mod rce_runner; - use std::env; use std::fmt; use std::fs; @@ -9,39 +7,35 @@ use std::path::Path; use std::process; use std::time; -use crate::rce_runner::cmd; -use crate::rce_runner::language; -use crate::rce_runner::non_empty_vec; +use rce_runner::cmd; +use rce_runner::language; +use rce_runner::non_empty_vec; fn main() { let _ = start().map_err(handle_error); } fn handle_error(error: Error) { - match error { - // Print RunResult if it's a compile error - Error::Compile(err) => { - let run_result = to_error_result(err); - let _ = serde_json::to_writer(io::stdout(), &run_result) - .map_err(Error::SerializeRunResult) - .map_err(handle_error); - } - - _ => { - eprintln!("{}", error); - process::exit(1); - } + // Print RunResult if it's a compile error + if let Error::Compile(err) = error { + let run_result = to_error_result(err); + let _ = serde_json::to_writer(io::stdout(), &run_result) + .map_err(Error::SerializeRunResult) + .map_err(handle_error); + } else { + eprintln!("{error}"); + process::exit(1); } } fn start() -> Result<(), Error> { let stdin = io::stdin(); let stdout = io::stdout(); - let args = env::args().collect(); + let args: Vec<_> = env::args().collect(); let run_request = parse_request(stdin)?; - let work_path = match work_path_from_args(args) { + let work_path = match work_path_from_args(&args) { Some(path) => path, None => default_work_path()?, @@ -67,7 +61,7 @@ fn start() -> Result<(), Error> { let run_result = match run_request.command { Some(command) if !command.is_empty() => run(&work_path, &command, run_request.stdin), - Some(_) | None => run_default(&work_path, run_request.language, files, run_request.stdin)?, + Some(_) | None => run_default(&work_path, &run_request.language, files, run_request.stdin)?, }; serde_json::to_writer(stdout, &run_result).map_err(Error::SerializeRunResult) @@ -81,11 +75,7 @@ struct RunResult { } fn to_success_result(output: cmd::SuccessOutput) -> RunResult { - RunResult { - stdout: output.stdout, - stderr: output.stderr, - error: "".to_string(), - } + RunResult { stdout: output.stdout, stderr: output.stderr, error: String::new() } } fn to_error_result(error: cmd::Error) -> RunResult { @@ -95,18 +85,14 @@ fn to_error_result(error: cmd::Error) -> RunResult { stderr: output.stderr, error: match output.exit_code { Some(exit_code) => { - format!("Exit code: {}", exit_code) + format!("Exit code: {exit_code}") } - None => "".to_string(), + None => String::new(), }, }, - _ => RunResult { - stdout: "".to_string(), - stderr: "".to_string(), - error: format!("{}", error), - }, + _ => RunResult { stdout: String::new(), stderr: String::new(), error: format!("{error}") }, } } @@ -134,18 +120,15 @@ fn file_from_request_file(base_path: &path::Path, file: RequestFile) -> Result(reader: R) -> Result { serde_json::from_reader(reader).map_err(Error::ParseRequest) } -fn work_path_from_args(arguments: Vec) -> Option { - let args = arguments.iter().map(|s| s.as_ref()).collect::>(); +fn work_path_from_args(arguments: &[String]) -> Option { + let args = arguments.iter().map(std::convert::AsRef::as_ref).collect::>(); match &args[1..] { ["--path", path] => Some(path::PathBuf::from(path)), @@ -155,9 +138,8 @@ fn work_path_from_args(arguments: Vec) -> Option { } fn default_work_path() -> Result { - let duration = time::SystemTime::now() - .duration_since(time::UNIX_EPOCH) - .map_err(Error::GetTimestamp)?; + let duration = + time::SystemTime::now().duration_since(time::UNIX_EPOCH).map_err(Error::GetTimestamp)?; let name = format!("rce-{}", duration.as_secs()); @@ -176,17 +158,13 @@ fn unpack_bootstrap_file(work_path: &path::Path, bootstrap_file: &path::Path) -> } fn write_file(file: &File) -> Result<(), Error> { - let parent_dir = file - .path - .parent() - .ok_or_else(|| Error::GetParentDir(file.path.to_path_buf()))?; + let parent_dir = file.path.parent().ok_or_else(|| Error::GetParentDir(file.path.clone()))?; // Create parent directories fs::create_dir_all(parent_dir) .map_err(|err| Error::CreateParentDir(parent_dir.to_path_buf(), err))?; - fs::write(&file.path, &file.content) - .map_err(|err| Error::WriteFile(file.path.to_path_buf(), err)) + fs::write(&file.path, &file.content).map_err(|err| Error::WriteFile(file.path.clone(), err)) } fn compile(work_path: &path::Path, command: &str) -> Result { @@ -200,12 +178,12 @@ fn compile(work_path: &path::Path, command: &str) -> Result, stdin: Option, ) -> Result { let file_paths = get_relative_file_paths(work_path, files)?; - let run_instructions = language::run_instructions(&language, file_paths); + let run_instructions = language::run_instructions(language, file_paths); for command in &run_instructions.build_commands { compile(work_path, command)?; @@ -236,10 +214,7 @@ fn get_relative_file_paths( let names = files .into_iter() .map(|file| { - let path = file - .path - .strip_prefix(work_path) - .map_err(Error::StripWorkPath)?; + let path = file.path.strip_prefix(work_path).map_err(Error::StripWorkPath)?; Ok(path.to_path_buf()) }) @@ -267,7 +242,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::ParseRequest(err) => { - write!(f, "Failed to parse request json, {}", err) + write!(f, "Failed to parse request json, {err}") } Error::NoFiles() => { @@ -275,7 +250,7 @@ impl fmt::Display for Error { } Error::StripWorkPath(err) => { - write!(f, "Failed to strip work path of file. {}", err) + write!(f, "Failed to strip work path of file. {err}") } Error::EmptyFileName() => { @@ -287,15 +262,11 @@ impl fmt::Display for Error { } Error::GetTimestamp(err) => { - write!(f, "Failed to get timestamp for work directory, {}", err) + write!(f, "Failed to get timestamp for work directory, {err}") } Error::GetParentDir(file_path) => { - write!( - f, - "Failed to get parent dir for file: '{}'", - file_path.to_string_lossy() - ) + write!(f, "Failed to get parent dir for file: '{}'", file_path.to_string_lossy()) } Error::CreateParentDir(file_path, err) => { @@ -308,24 +279,19 @@ impl fmt::Display for Error { } Error::WriteFile(file_path, err) => { - write!( - f, - "Failed to write file: '{}'. {}", - file_path.to_string_lossy(), - err - ) + write!(f, "Failed to write file: '{}'. {}", file_path.to_string_lossy(), err) } Error::Bootstrap(err) => { - write!(f, "Failed to unpack bootstrap file: {}", err) + write!(f, "Failed to unpack bootstrap file: {err}") } Error::Compile(err) => { - write!(f, "Failed to compile: {}", err) + write!(f, "Failed to compile: {err}") } Error::SerializeRunResult(err) => { - write!(f, "Failed to serialize run result: {}", err) + write!(f, "Failed to serialize run result: {err}") } } } diff --git a/src/rce_runner/non_empty_vec.rs b/src/non_empty_vec.rs similarity index 96% rename from src/rce_runner/non_empty_vec.rs rename to src/non_empty_vec.rs index 1f72697..90974ee 100644 --- a/src/rce_runner/non_empty_vec.rs +++ b/src/non_empty_vec.rs @@ -10,6 +10,7 @@ impl NonEmptyVec { } } +#[must_use] pub fn from_vec(mut vec: Vec) -> Option> { if vec.is_empty() { None diff --git a/src/rce_runner/language.rs b/src/rce_runner/language.rs deleted file mode 100644 index 83bb399..0000000 --- a/src/rce_runner/language.rs +++ /dev/null @@ -1,407 +0,0 @@ -use crate::rce_runner::non_empty_vec; -use std::path; - -#[derive(serde::Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -pub enum Language { - Assembly, - Ats, - Bash, - C, - Clisp, - Clojure, - Cobol, - CoffeeScript, - Cpp, - Crystal, - Csharp, - D, - Dart, - Elixir, - Elm, - Erlang, - Fsharp, - Go, - Groovy, - Guile, - Hare, - Haskell, - Idris, - Java, - JavaScript, - Julia, - Kotlin, - Lua, - Mercury, - Nim, - Nix, - Ocaml, - Pascal, - Perl, - Php, - Python, - Raku, - Ruby, - Rust, - SaC, - Scala, - Swift, - TypeScript, - Zig, -} - -#[derive(Debug)] -pub struct RunInstructions { - pub build_commands: Vec, - pub run_command: String, -} - -pub fn run_instructions( - language: &Language, - files: non_empty_vec::NonEmptyVec, -) -> RunInstructions { - let (main_file, other_files) = files.parts(); - let main_file_str = main_file.to_string_lossy(); - - match language { - Language::Assembly => RunInstructions { - build_commands: vec![ - format!("nasm -f elf64 -o a.o {}", main_file_str), - "ld -o a.out a.o".to_string(), - ], - run_command: "./a.out".to_string(), - }, - - Language::Ats => RunInstructions { - build_commands: vec![format!( - "patscc -o a.out {} {}", - main_file_str, - source_files(other_files, "dats") - )], - run_command: "./a.out".to_string(), - }, - - Language::Bash => RunInstructions { - build_commands: vec![], - run_command: format!("bash {}", main_file_str), - }, - - Language::C => RunInstructions { - build_commands: vec![format!( - "clang -o a.out -lm {} {}", - main_file_str, - source_files(other_files, "c") - )], - run_command: "./a.out".to_string(), - }, - - Language::Clisp => RunInstructions { - build_commands: vec![], - run_command: format!("sbcl --noinform --non-interactive --load {}", main_file_str), - }, - - Language::Clojure => RunInstructions { - build_commands: vec![], - run_command: format!("clj -M {}", main_file_str), - }, - - Language::Cobol => RunInstructions { - build_commands: vec![format!( - "cobc -x -o a.out {} {}", - main_file_str, - source_files(other_files, "cob") - )], - run_command: "./a.out".to_string(), - }, - - Language::CoffeeScript => RunInstructions { - build_commands: vec![], - run_command: format!("coffee {}", main_file_str), - }, - - Language::Cpp => RunInstructions { - build_commands: vec![format!( - "clang++ -std=c++11 -o a.out {} {}", - main_file_str, - source_files(other_files, "c") - )], - run_command: "./a.out".to_string(), - }, - - Language::Crystal => RunInstructions { - build_commands: vec![], - run_command: format!("crystal run {}", main_file_str), - }, - - Language::Csharp => RunInstructions { - build_commands: vec![format!( - "mcs -out:a.exe {} {}", - main_file_str, - source_files(other_files, "cs") - )], - run_command: "mono a.exe".to_string(), - }, - - Language::D => RunInstructions { - build_commands: vec![format!( - "dmd -ofa.out {} {}", - main_file_str, - source_files(other_files, "d") - )], - run_command: "./a.out".to_string(), - }, - - Language::Dart => RunInstructions { - build_commands: vec![], - run_command: format!("dart {}", main_file_str), - }, - - Language::Elixir => RunInstructions { - build_commands: vec![], - run_command: format!( - "elixirc {} {}", - main_file_str, - source_files(other_files, "ex") - ), - }, - - Language::Elm => RunInstructions { - build_commands: vec![format!("elm make --output a.js {}", main_file_str)], - run_command: "elm-runner a.js".to_string(), - }, - - Language::Erlang => RunInstructions { - build_commands: filter_by_extension(other_files, "erl") - .iter() - .map(|file| format!("erlc {}", file.to_string_lossy())) - .collect(), - run_command: format!("escript {}", main_file_str), - }, - - Language::Fsharp => { - let mut source_files = filter_by_extension(other_files, "fs"); - source_files.reverse(); - - RunInstructions { - build_commands: vec![format!( - "fsharpc --out:a.exe {} {}", - space_separated_files(source_files), - main_file_str - )], - run_command: "mono a.exe".to_string(), - } - } - - Language::Go => RunInstructions { - build_commands: vec![format!("go build -o a.out {}", main_file_str)], - run_command: "./a.out".to_string(), - }, - - Language::Groovy => RunInstructions { - build_commands: vec![], - run_command: format!("groovy {}", main_file_str), - }, - - Language::Guile => RunInstructions { - build_commands: vec![], - run_command: format!( - "guile --no-debug --fresh-auto-compile --no-auto-compile -s {}", - main_file_str - ), - }, - - Language::Hare => RunInstructions { - build_commands: vec![format!("hare build -o a.out {}", main_file_str)], - run_command: "./a.out".to_string(), - }, - - Language::Haskell => RunInstructions { - build_commands: vec![], - run_command: format!("runghc {}", main_file_str), - }, - - Language::Idris => RunInstructions { - build_commands: vec![format!("idris2 -o a.out --output-dir . {}", main_file_str)], - run_command: "./a.out".to_string(), - }, - - Language::Java => { - let file_stem = main_file - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("Main"); - - RunInstructions { - build_commands: vec![format!("javac {}", main_file_str)], - run_command: format!("java {}", titlecase_ascii(file_stem)), - } - } - - Language::JavaScript => RunInstructions { - build_commands: vec![], - run_command: format!("node {}", main_file_str), - }, - - Language::Julia => RunInstructions { - build_commands: vec![], - run_command: format!("julia {}", main_file_str), - }, - - Language::Kotlin => { - let file_stem = main_file - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("Main"); - - RunInstructions { - build_commands: vec![format!("kotlinc {}", main_file_str)], - run_command: format!("kotlin {}Kt", titlecase_ascii(file_stem)), - } - } - - Language::Lua => RunInstructions { - build_commands: vec![], - run_command: format!("lua {}", main_file_str), - }, - - Language::Mercury => RunInstructions { - build_commands: vec![format!( - "mmc -o a.out {} {}", - main_file_str, - source_files(other_files, "m") - )], - run_command: "./a.out".to_string(), - }, - - Language::Nim => RunInstructions { - build_commands: vec![], - run_command: format!( - "nim --hints:off --verbosity:0 compile --run {}", - main_file_str - ), - }, - - Language::Nix => RunInstructions { - build_commands: vec![], - run_command: format!("nix-instantiate --eval {}", main_file_str), - }, - - Language::Ocaml => { - let mut source_files = filter_by_extension(other_files, "ml"); - source_files.reverse(); - - RunInstructions { - build_commands: vec![format!( - "ocamlc -o a.out {} {}", - space_separated_files(source_files), - main_file_str - )], - run_command: "./a.out".to_string(), - } - } - - Language::Pascal => RunInstructions { - build_commands: vec![format!("fpc -oa.out {}", main_file_str)], - run_command: "./a.out".to_string(), - }, - - Language::Perl => RunInstructions { - build_commands: vec![], - run_command: format!("perl {}", main_file_str), - }, - - Language::Php => RunInstructions { - build_commands: vec![], - run_command: format!("php {}", main_file_str), - }, - - Language::Python => RunInstructions { - build_commands: vec![], - run_command: format!("python {}", main_file_str), - }, - - Language::Raku => RunInstructions { - build_commands: vec![], - run_command: format!("raku {}", main_file_str), - }, - - Language::Ruby => RunInstructions { - build_commands: vec![], - run_command: format!("ruby {}", main_file_str), - }, - - Language::Rust => RunInstructions { - build_commands: vec![format!("rustc -o a.out {}", main_file_str)], - run_command: "./a.out".to_string(), - }, - - Language::SaC => RunInstructions { - build_commands: vec![format!( - "sac2c -t seq -o a.out {} {}", - main_file_str, - source_files(other_files, "c") - )], - run_command: "./a.out".to_string(), - }, - - Language::Scala => RunInstructions { - build_commands: vec![format!( - "scalac {} {}", - main_file_str, - source_files(other_files, "scala") - )], - run_command: "scala Main".to_string(), - }, - - Language::Swift => RunInstructions { - build_commands: vec![format!( - "swiftc -o a.out {} {}", - main_file_str, - source_files(other_files, "swift") - )], - run_command: "./a.out".to_string(), - }, - - Language::TypeScript => RunInstructions { - build_commands: vec![format!( - "tsc -outFile a.js {} {}", - main_file_str, - source_files(other_files, "ts") - )], - run_command: "node a.js".to_string(), - }, - - Language::Zig => RunInstructions { - build_commands: vec![], - run_command: format!("zig run {}", main_file_str), - }, - } -} - -fn source_files(files: Vec, extension: &str) -> String { - space_separated_files(filter_by_extension(files, extension)) -} - -fn filter_by_extension(files: Vec, extension: &str) -> Vec { - files - .into_iter() - .filter(|file| file.extension().and_then(|s| s.to_str()) == Some(extension)) - .collect() -} - -fn space_separated_files(files: Vec) -> String { - files - .iter() - .map(|file| file.to_string_lossy().to_string()) - .collect::>() - .join(" ") -} - -fn titlecase_ascii(s: &str) -> String { - if !s.is_ascii() || s.len() < 2 { - s.to_string() - } else { - let (head, tail) = s.split_at(1); - format!("{}{}", head.to_ascii_uppercase(), tail) - } -} diff --git a/src/rce_runner/mod.rs b/src/rce_runner/mod.rs deleted file mode 100644 index 90ca16c..0000000 --- a/src/rce_runner/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod cmd; -pub mod language; -pub mod non_empty_vec;