Use stdin as main.rs (#30)

close #14 

Thanks to @octaltree octaltree
This commit is contained in:
octaltree 2021-02-18 15:30:06 +09:00 committed by GitHub
parent 2b88ad9f02
commit 09a1cf6b19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 43 deletions

3
fixtures/mod_hello.rs Normal file
View file

@ -0,0 +1,3 @@
pub(super) fn hello() {
println!("Hello World!");
}

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
edition = "2018"

View file

@ -1,7 +1,5 @@
use std::collections::HashSet;
use std::fs;
use std::iter;
use std::path::PathBuf;
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::ToTokens;
@ -36,16 +34,15 @@ fn extra_use<'a, T: 'a + IntoIterator<Item = TokenTree> + Clone>(
)
}
pub fn analyze_sources(sources: &Vec<PathBuf>) -> Result<HashSet<String>, CargoPlayError> {
let contents: Vec<_> = sources
.into_iter()
.map(fs::read_to_string)
.collect::<Result<_, _>>()?;
let streams: Vec<TokenStream> = contents
.into_iter()
.map(|file| -> Result<_, CargoPlayError> {
Ok(syn::parse_file(&file)?.into_token_stream())
pub fn analyze_sources(
stdin: Option<&str>,
sources: &[&str],
) -> Result<HashSet<String>, CargoPlayError> {
let streams: Vec<TokenStream> = stdin
.iter()
.chain(sources.iter())
.map(|source| -> Result<_, CargoPlayError> {
Ok(syn::parse_file(source)?.into_token_stream())
})
.collect::<Result<_, CargoPlayError>>()?;

View file

@ -46,11 +46,13 @@ fn main() -> Result<(), CargoPlayError> {
}
}
let files = parse_inputs(&opt.src)?;
let dependencies = extract_headers(&files);
let stdin = if opt.stdin { Some(read_stdin()?) } else { None };
let files = read_files(&opt.src)?;
let sources: Vec<&str> = files.iter().map(|(source, _)| -> &str { source }).collect();
let dependencies = extract_headers(stdin.as_deref(), &sources);
let infers = if opt.infer {
infer::analyze_sources(&opt.src)?
infer::analyze_sources(stdin.as_deref(), &sources)?
} else {
HashSet::new()
};
@ -66,7 +68,7 @@ fn main() -> Result<(), CargoPlayError> {
opt.edition.clone(),
infers,
)?;
copy_sources(&temp, &opt.src)?;
copy_sources(&temp, stdin.as_deref(), &files)?;
let end = if let Some(save) = opt.save {
copy_project(&temp, &save)?
@ -86,7 +88,14 @@ mod tests {
#[test]
fn test_extract_headers() {
let inputs: Vec<String> = vec![
let stdin = Some(
r#"//# line 1
//# line 2
// line 3
//# line 4"#
.to_string(),
);
let inputs: Vec<&str> = vec![
r#"//# line 1
//# line 2
// line 3
@ -95,10 +104,12 @@ mod tests {
.into_iter()
.map(Into::into)
.collect();
let result = extract_headers(&inputs);
let result = extract_headers(stdin.as_deref(), &inputs);
assert_eq!(result.len(), 2);
assert_eq!(result.len(), 4);
assert_eq!(result[0], String::from("line 1"));
assert_eq!(result[1], String::from("line 2"));
assert_eq!(result[2], String::from("line 1"));
assert_eq!(result[3], String::from("line 2"));
}
}

View file

@ -77,7 +77,7 @@ pub struct Options {
#[structopt(
parse(try_from_os_str = osstr_to_abspath),
required = true,
required_unless = "stdin",
validator = file_exist
)]
/// Paths to your source code files
@ -107,6 +107,10 @@ pub struct Options {
/// Set Cargo verbose level
pub verbose: u16,
#[structopt(long = "stdin")]
/// Use stdin as main.rs
pub stdin: bool,
#[structopt(long = "cargo-option")]
/// Custom flags passing to cargo
pub cargo_option: Option<String>,

View file

@ -14,26 +14,32 @@ use crate::cargo::CargoManifest;
use crate::errors::CargoPlayError;
use crate::options::{Options, RustEdition};
pub fn parse_inputs(inputs: &[PathBuf]) -> Result<Vec<String>, CargoPlayError> {
pub fn read_stdin() -> Result<String, CargoPlayError> {
let mut buffer = String::new();
std::io::stdin().read_to_string(&mut buffer)?;
Ok(buffer)
}
pub fn read_files(inputs: &[PathBuf]) -> Result<Vec<(String, &Path)>, CargoPlayError> {
inputs
.iter()
.map(File::open)
.map(|res| match res {
Ok(mut fp) => {
let mut buf = String::new();
fp.read_to_string(&mut buf)?;
Ok(buf)
}
Err(e) => Err(CargoPlayError::from(e)),
.map(AsRef::as_ref)
.map(|p: &Path| {
let mut fp = File::open(p)?;
let mut buf = String::new();
fp.read_to_string(&mut buf)?;
Ok((buf, p))
})
.collect()
}
pub fn extract_headers(files: &[String]) -> Vec<String> {
files
pub fn extract_headers(stdin: Option<&str>, sources: &[&str]) -> Vec<String> {
stdin
.iter()
.map(|file: &String| -> Vec<String> {
file.lines()
.chain(sources.iter())
.map(|source| -> Vec<String> {
source
.lines()
.skip_while(|line| line.starts_with("#!") || line.is_empty())
.take_while(|line| line.starts_with("//#"))
.map(|line| line[3..].trim_start().into())
@ -83,25 +89,34 @@ pub fn write_cargo_toml(
/// Copy all the passed in sources to the temporary directory. The first in the list will be
/// treated as main.rs.
pub fn copy_sources(temp: &PathBuf, sources: &[PathBuf]) -> Result<(), CargoPlayError> {
pub fn copy_sources(
temp: &PathBuf,
stdin: Option<&str>,
files: &[(String, &Path)],
) -> Result<(), CargoPlayError> {
let destination = temp.join("src");
std::fs::create_dir_all(&destination)?;
let mut files = sources.iter();
let base = if let Some(first) = files.next() {
let mut files = files.iter();
let base: Option<PathBuf> = if let Some(main) = stdin {
let dst = destination.join("main.rs");
debug!("Copying stdin => {:?}", dst);
std::fs::write(dst, main)?;
Some(std::env::current_dir()?)
} else if let Some((main, first)) = files.next() {
let dst = destination.join("main.rs");
debug!("Copying {:?} => {:?}", first, dst);
std::fs::copy(first, dst)?;
first.parent()
std::fs::write(dst, main)?;
first.parent().map(|p| p.to_path_buf())
} else {
None
};
if let Some(base) = base {
if let Some(base) = &base {
files
.map(|file| -> Result<(), CargoPlayError> {
.map(|(source, file)| -> Result<(), CargoPlayError> {
let part = diff_paths(file, base)
.ok_or_else(|| CargoPlayError::DiffPathError(file.to_owned()))?;
.ok_or_else(|| CargoPlayError::DiffPathError(file.to_path_buf()))?;
let dst = destination.join(part);
// ensure the parent folder all exists
@ -110,7 +125,8 @@ pub fn copy_sources(temp: &PathBuf, sources: &[PathBuf]) -> Result<(), CargoPlay
}
debug!("Copying {:?} => {:?}", file, dst);
std::fs::copy(file, dst).map(|_| ()).map_err(From::from)
std::fs::write(dst, source)?;
Ok(())
})
.collect::<Result<Vec<_>, _>>()?;
}

View file

@ -3,9 +3,10 @@ use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::env;
use std::ffi::OsStr;
use std::io::prelude::*;
use std::io::Result;
use std::path::{Path, PathBuf};
use std::process::{ExitStatus, Output, Stdio};
use std::process::{Command, ExitStatus, Output, Stdio};
struct TestRuntime {
scratch: PathBuf,
@ -45,6 +46,23 @@ impl TestRuntime {
self.scratch.join(path)
}
fn run_with_stdin<
I: IntoIterator<Item = S> + std::fmt::Debug,
S: AsRef<OsStr> + std::fmt::Debug,
>(
&self,
args: I,
) -> Command {
let mut play = std::process::Command::new(cargo_play_binary_path());
play.env("TMP", &self.scratch)
.env("TMPDIR", &self.scratch)
.args(args)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped());
play
}
fn run<I: IntoIterator<Item = S> + std::fmt::Debug, S: AsRef<OsStr> + std::fmt::Debug>(
&self,
args: I,
@ -272,3 +290,27 @@ fn test_mode_test() -> Result<()> {
Ok(())
}
#[test]
fn stdin_with_hello() -> Result<()> {
let rt = TestRuntime::new()?;
let mut ps = {
let mut p = rt.run_with_stdin(&["--stdin", "mod_hello.rs"]);
p.current_dir(std::fs::canonicalize("fixtures")?);
p.spawn()?
};
{
let mut stdin = ps.stdin.take().unwrap();
stdin.write_all("mod mod_hello; fn main() { mod_hello::hello(); }".as_bytes())?;
} // close stdin
let status = ps.wait()?;
assert_eq!(status.code().unwrap(), 0);
let out = {
let mut buff = String::new();
ps.stdout.unwrap().read_to_string(&mut buff)?;
buff
};
assert_eq!(out, "Hello World!\n");
Ok(())
}