diff --git a/CHANGELOG.md b/CHANGELOG.md index 7003a85..4c7be35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Splitted the `download` command into `new`, `open`, and `download`. ([#3](https://github.com/qryxip/cargo-compete/pull/3)) - Changed the format of `workspace-metadata.toml`. - Now `cargo-compete.test-suite`s are [Liquid](https://shopify.github.io/liquid/) templates. ([#4](https://github.com/qryxip/cargo-compete/pull/4)) + - Now `cargo-compete.open`s are [jq](https://github.com/stedolan/jq) commands. ([#5](https://github.com/qryxip/cargo-compete/pull/5)) ### Fixed diff --git a/README-ja.md b/README-ja.md index dab5f56..cebfba9 100644 --- a/README-ja.md +++ b/README-ja.md @@ -100,7 +100,7 @@ AtCoderを選択に入れる場合、 ## 設定 -**v0.2.0で`workspace-metadata.toml`のフォーマットを変更する予定です。** ([#4](https://github.com/qryxip/cargo-compete/pull/4)) +**v0.2.0で`workspace-metadata.toml`のフォーマットを変更する予定です。** ([#4](https://github.com/qryxip/cargo-compete/pull/4), [#5](https://github.com/qryxip/cargo-compete/pull/5)) 設定は各ワークスペース下にある`workspace-metadata.toml`にあります。 バイナリ提出関連の設定もこちらです。 diff --git a/src/commands/init.rs b/src/commands/init.rs index c08e6b3..1c6ee31 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -219,9 +219,16 @@ fn write_workspace_metadata_toml( ) -> anyhow::Result<()> { let content = format!( r#"[cargo-compete] -new-workspace-member = "include" # "include", "focus" +# How to manage new workspace members ("include", "focus") +new-workspace-member = "include" +# Path to the test file (Liquid template) test-suite = "./testcases/{{{{ contest }}}}/{{{{ problem | kebabcase }}}}.yml" -#open = "vscode" # "vscode", "emacsclient" +# Open files with the command (`jq` command) +# +# VSCode: +#open = '["code"] + (.paths | map([.src, .test_suite]) | flatten) + ["-a", .manifest_dir]' +# Emacs: +#open = '["emacsclient", "-n"] + (.paths | map([.src, .test_suite]) | flatten)' [cargo-compete.template] code = "./cargo-compete-template/src/main.rs" diff --git a/src/open.rs b/src/open.rs index ee0a86b..0dbf913 100644 --- a/src/open.rs +++ b/src/open.rs @@ -1,10 +1,12 @@ -use crate::{project::Open, shell::Shell}; +use crate::shell::Shell; +use anyhow::{ensure, Context as _}; +use serde_json::json; use std::{borrow::Borrow, path::Path}; use url::Url; pub(crate) fn open( urls: &[impl Borrow], - open: Option, + open: Option>, paths: &[(impl AsRef, impl AsRef)], pkg_manifest_dir: &Path, cwd: &Path, @@ -17,26 +19,44 @@ pub(crate) fn open( } if let Some(open) = open { - let mut cmd = match open { - Open::Vscode => crate::process::with_which("code", cwd)?, - Open::Emacsclient => { - let mut cmd = crate::process::with_which("emacsclient", cwd)?; - cmd.arg("-n"); - cmd - } - }; - - for (src_path, test_suite_path) in paths { - cmd.arg(src_path.as_ref()); - cmd.arg(test_suite_path.as_ref()); + fn ensure_utf8(path: &Path) -> anyhow::Result<&str> { + path.to_str() + .with_context(|| format!("must be UTF-8: {:?}", path.display())) } - if open == Open::Vscode { - cmd.arg("-a"); - cmd.arg(pkg_manifest_dir); - } + let input = json!({ + "manifest_dir": ensure_utf8(pkg_manifest_dir)?, + "paths": paths + .iter() + .map(|(src_path, test_suite_path)| { + let src_path = ensure_utf8(src_path.as_ref())?; + let test_suite_path = ensure_utf8(test_suite_path.as_ref())?; + Ok(json!({ + "src": src_path, + "test_suite": test_suite_path + })) + }) + .collect::>>()? + }) + .to_string(); + + let jq = crate::process::which("jq", cwd).with_context(|| { + "`jq` not found. install `jq` from https://github.com/stedolan/jq/releases" + })?; + + let output = crate::process::process(jq, &cwd) + .args(&["-c", open.as_ref()]) + .pipe_input(Some(input)) + .read_with_shell_status(shell)?; + + let args = serde_json::from_str::>(&output) + .with_context(|| "expected string array")?; + + ensure!(!args.is_empty(), "empty command"); - cmd.exec_with_shell_status(shell)?; + crate::process::with_which(&args[0], cwd)? + .args(&args[1..]) + .exec_with_shell_status(shell)?; } Ok(()) } diff --git a/src/process.rs b/src/process.rs index 87dab85..d609b03 100644 --- a/src/process.rs +++ b/src/process.rs @@ -5,7 +5,9 @@ use std::{ env, ffi::{OsStr, OsString}, fmt, + io::Write as _, path::{Path, PathBuf}, + process::Stdio, }; #[derive(Debug)] @@ -13,6 +15,7 @@ pub(crate) struct ProcessBuilder { program: OsString, args: Vec, cwd: PathBuf, + pipe_input: Option>, } impl ProcessBuilder { @@ -26,12 +29,13 @@ impl ProcessBuilder { self } - pub(crate) fn exec(&self) -> anyhow::Result<()> { - let status = std::process::Command::new(&self.program) - .args(&self.args) - .current_dir(&self.cwd) - .status()?; + pub(crate) fn pipe_input(&mut self, pipe_input: Option>>) -> &mut Self { + self.pipe_input = pipe_input.map(Into::into); + self + } + pub(crate) fn exec(&self) -> anyhow::Result<()> { + let status = self.spawn(Stdio::inherit())?.wait()?; if !status.success() { bail!("{} didn't exit successfully: {}", self, status); } @@ -42,6 +46,41 @@ impl ProcessBuilder { shell.status("Running", self)?; self.exec() } + + fn read(&self) -> anyhow::Result { + let std::process::Output { status, stdout, .. } = + self.spawn(Stdio::piped())?.wait_with_output()?; + if !status.success() { + bail!("{} didn't exit successfully: {}", self, status); + } + String::from_utf8(stdout).with_context(|| "non UTF-8 output") + } + + pub(crate) fn read_with_shell_status(&self, shell: &mut Shell) -> anyhow::Result { + shell.status("Running", self)?; + self.read() + } + + fn spawn(&self, stdout: Stdio) -> anyhow::Result { + let mut child = std::process::Command::new(&self.program) + .args(&self.args) + .current_dir(&self.cwd) + .stdin(if self.pipe_input.is_some() { + Stdio::piped() + } else { + Stdio::inherit() + }) + .stdout(stdout) + .spawn()?; + + if let (Some(mut stdin), Some(pipe_input)) = (child.stdin.take(), self.pipe_input.as_ref()) + { + stdin.write_all(pipe_input)?; + stdin.flush()?; + } + + Ok(child) + } } impl fmt::Display for ProcessBuilder { @@ -64,6 +103,7 @@ pub(crate) fn process(program: impl AsRef, cwd: impl AsRef) -> Proce program: program.as_ref().into(), args: vec![], cwd: cwd.as_ref().into(), + pipe_input: None, } } diff --git a/src/project.rs b/src/project.rs index 34b05be..b1affc0 100644 --- a/src/project.rs +++ b/src/project.rs @@ -29,7 +29,7 @@ pub(crate) struct WorkspaceMetadataCargoCompete { #[derivative(Debug = "ignore")] #[serde(deserialize_with = "deserialize_liquid_template_with_custom_filter")] pub(crate) test_suite: liquid::Template, - pub(crate) open: Option, + pub(crate) open: Option, pub(crate) template: WorkspaceMetadataCargoCompeteTemplate, pub(crate) platform: WorkspaceMetadataCargoCompetePlatform, } @@ -75,13 +75,6 @@ fn liquid_template_with_custom_filter(text: &str) -> Result