diff --git a/examples/one.toml b/examples/one.toml index 935340d..9467cca 100644 --- a/examples/one.toml +++ b/examples/one.toml @@ -22,6 +22,14 @@ trim_output = true max_memory = { compile = 128, run = 64 } max_file_size = 8192 +[workspaces.gleam_env] +# from = "./some_dir" +setup = ''' +yes | gleam new . +gleam add dateformat@1 +gleam add birl +''' + [languages] python3 = "latest" java = "21" @@ -29,6 +37,13 @@ javascript = "deno:latest" ocaml = { build = "ocamlc -o out solution.ml", run = "./out", source_file = "solution.ml" } haskell = { build = "ghc solution.hs", run = "./solution", source_file = "solution.hs", syntax = "haskell" } +[languages.gleam] +build = "gleam build" +run = "gleam run" +workspace = "gleam_env" +source_file = "src/main.gleam" +syntax = "elixir" + [[accounts.hosts]] name = "Teacher" password = "abc123" diff --git a/src/language/language_set.rs b/src/language/language_set.rs index 7efcc83..30fa9a5 100644 --- a/src/language/language_set.rs +++ b/src/language/language_set.rs @@ -99,6 +99,7 @@ impl<'de> Visitor<'de> for LanguageMapVisitor { display_name, build, run, + workspace, source_file, syntax, } => Language::Custom { @@ -106,6 +107,7 @@ impl<'de> Visitor<'de> for LanguageMapVisitor { display_name: display_name.unwrap_or_else(|| key.clone()).into_owned(), build: build.map(Cow::into_owned), run: run.into_owned(), + workspace: workspace.map(Cow::into_owned), syntax: syntax .or_else(|| Syntax::from_string::(key).ok()) .unwrap_or_default(), @@ -147,6 +149,7 @@ impl Serialize for LanguageSet { name, display_name, build, + workspace, run, source_file, syntax, @@ -157,6 +160,7 @@ impl Serialize for LanguageSet { display_name: Some(display_name.into()), build: build.as_ref().map(Into::into), run: run.into(), + workspace: workspace.as_ref().map(Cow::from), source_file: source_file.into(), syntax: Some(*syntax), }, @@ -182,6 +186,7 @@ enum TomlLanguage<'a> { display_name: Option>, build: Option>, run: Cow<'a, str>, + workspace: Option>, source_file: Cow<'a, str>, syntax: Option, }, diff --git a/src/language/mod.rs b/src/language/mod.rs index 50314df..35dc0a4 100644 --- a/src/language/mod.rs +++ b/src/language/mod.rs @@ -309,6 +309,7 @@ pub enum Language { run: String, source_file: String, syntax: Syntax, + workspace: Option, }, } @@ -368,4 +369,15 @@ impl Language { Language::Custom { syntax, .. } => *syntax, } } + + pub fn workspace(&self) -> Option<&str> { + match self { + Language::BuiltIn { .. } => None, + Language::Custom { + workspace: Some(workspace), + .. + } => Some(workspace), + _ => None, + } + } } diff --git a/src/lib.rs b/src/lib.rs index d010f74..473619c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use std::{ + collections::BTreeMap, hash::{DefaultHasher, Hasher}, io::Read, path::PathBuf, @@ -13,6 +14,8 @@ use roi::RawOrImport; use serde::{Deserialize, Serialize}; use typst::foundations::{Array, Dict, IntoValue, Str, Value}; +use crate::workspaces::Workspace; + mod custom_serde; pub mod integrations; pub mod language; @@ -20,6 +23,7 @@ pub mod packet; pub mod render; pub mod roi; pub mod scoring; +pub mod workspaces; mod util; @@ -350,6 +354,9 @@ pub struct Config { pub integrations: Integrations, /// Maximum number of attempts that a user is allowed to make for a given problem pub max_submissions: Option>, + /// Map of workspaces that function as reusable execution environments + #[serde(default)] + pub workspaces: RawOrImport>, /// List of languages available for the server pub languages: RawOrImport, /// Accounts that will be granted access to the server @@ -635,6 +642,7 @@ impl Default for Config { game: Default::default(), max_submissions: None, languages: Default::default(), + workspaces: Default::default(), accounts: Default::default(), packet: Default::default(), test_runner: Default::default(), diff --git a/src/tests.rs b/src/tests.rs index 106ffd6..b6b8e84 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -38,6 +38,7 @@ fn packet_files_parse_correctly() -> Result<()> { Some(&Language::Custom { name: "ocaml".into(), display_name: "ocaml".into(), + workspace: None, build: Some("ocamlc -o out solution.ml".into()), run: "./out".into(), source_file: "solution.ml".into(), @@ -50,6 +51,7 @@ fn packet_files_parse_correctly() -> Result<()> { Some(&Language::Custom { name: "haskell".into(), display_name: "haskell".into(), + workspace: None, build: Some("ghc solution.hs".into()), run: "./solution".into(), source_file: "solution.hs".into(), @@ -65,6 +67,19 @@ fn packet_files_parse_correctly() -> Result<()> { config.languages.get_by_str("javascript") ); + assert_eq!( + Some(&Language::Custom { + name: "gleam".into(), + display_name: "gleam".into(), + workspace: Some("gleam_env".into()), + build: Some("gleam build".into()), + run: "gleam run".into(), + source_file: "src/main.gleam".into(), + syntax: Syntax::Elixir, + }), + config.languages.get_by_str("gleam") + ); + dbg!(config.hash()); Ok(()) diff --git a/src/workspaces.rs b/src/workspaces.rs new file mode 100644 index 0000000..de7ef04 --- /dev/null +++ b/src/workspaces.rs @@ -0,0 +1,15 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +use crate::roi::RawOrImport; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Workspace { + /// Optional directory on which to base the generation of this workspace + from: Option, + /// Script executed to generate the workspace + setup: Option>, + /// Script executed to install any required dependencies if any are required + install: Option>, +} diff --git a/tests/events.toml b/tests/events.toml index 06ac73b..cce6db0 100644 --- a/tests/events.toml +++ b/tests/events.toml @@ -12,6 +12,18 @@ max_file_size = 8192 event_handlers = ["./tests/on-score.js", "./tests/on-complete.ts"] webhooks = ["http://localhost:9090", "http://localhost:8090"] +[workspaces] +gleam-env = { setup = "yes | gleam new ." } + +# A more complex workspace with a base, gleam project, and dependencies +[workspaces.gleam-env-two] +from = "./some_dir" # some directory to act as base (perhaps you want some files pre-included) +setup = ''' +yes | gleam new . +gleam add dateformat@1 +gleam add birl +''' + [game] score = "points - 2*max(completed, attempts)" diff --git a/tests/hashing.rs b/tests/hashing.rs index 0a3bd8a..0b78dbd 100644 --- a/tests/hashing.rs +++ b/tests/hashing.rs @@ -11,6 +11,7 @@ fn hash_consistent() { game: Default::default(), max_submissions: None, languages: Default::default(), + workspaces: Default::default(), accounts: Default::default(), packet: Default::default(), test_runner: Default::default(), @@ -29,6 +30,7 @@ fn port_diff() { game: Default::default(), max_submissions: None, languages: Default::default(), + workspaces: Default::default(), accounts: Default::default(), packet: Default::default(), test_runner: Default::default(), @@ -42,6 +44,7 @@ fn port_diff() { integrations: Default::default(), max_submissions: None, languages: Default::default(), + workspaces: Default::default(), accounts: Default::default(), packet: Default::default(), test_runner: Default::default(),