mirror of
https://github.com/fanzeyi/cargo-play
synced 2024-11-24 19:33:09 +00:00
parent
2b88ad9f02
commit
09a1cf6b19
7 changed files with 117 additions and 43 deletions
3
fixtures/mod_hello.rs
Normal file
3
fixtures/mod_hello.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub(super) fn hello() {
|
||||
println!("Hello World!");
|
||||
}
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
|||
edition = "2018"
|
21
src/infer.rs
21
src/infer.rs
|
@ -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>>()?;
|
||||
|
||||
|
|
25
src/main.rs
25
src/main.rs
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
60
src/steps.rs
60
src/steps.rs
|
@ -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<_>, _>>()?;
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue