]> git.lizzy.rs Git - rust.git/blob - crates/tools/src/lib.rs
Merge #271
[rust.git] / crates / tools / src / lib.rs
1 use std::{
2     path::{Path, PathBuf},
3     process::{Command, Stdio},
4     fs::copy,
5     io::{Error, ErrorKind}
6 };
7
8 use failure::bail;
9 use itertools::Itertools;
10
11 pub use teraron::{Mode, Overwrite, Verify};
12
13 pub type Result<T> = std::result::Result<T, failure::Error>;
14
15 pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron";
16 pub const SYNTAX_KINDS: &str = "crates/ra_syntax/src/syntax_kinds/generated.rs.tera";
17 pub const AST: &str = "crates/ra_syntax/src/ast/generated.rs.tera";
18 const TOOLCHAIN: &str = "1.31.0";
19
20 #[derive(Debug)]
21 pub struct Test {
22     pub name: String,
23     pub text: String,
24 }
25
26 pub fn collect_tests(s: &str) -> Vec<(usize, Test)> {
27     let mut res = vec![];
28     let prefix = "// ";
29     let comment_blocks = s
30         .lines()
31         .map(str::trim_left)
32         .enumerate()
33         .group_by(|(_idx, line)| line.starts_with(prefix));
34
35     'outer: for (is_comment, block) in comment_blocks.into_iter() {
36         if !is_comment {
37             continue;
38         }
39         let mut block = block.map(|(idx, line)| (idx, &line[prefix.len()..]));
40
41         let (start_line, name) = loop {
42             match block.next() {
43                 Some((idx, line)) if line.starts_with("test ") => {
44                     break (idx, line["test ".len()..].to_string());
45                 }
46                 Some(_) => (),
47                 None => continue 'outer,
48             }
49         };
50         let text: String = itertools::join(
51             block.map(|(_, line)| line).chain(::std::iter::once("")),
52             "\n",
53         );
54         assert!(!text.trim().is_empty() && text.ends_with('\n'));
55         res.push((start_line, Test { name, text }))
56     }
57     res
58 }
59
60 pub fn generate(mode: Mode) -> Result<()> {
61     let grammar = project_root().join(GRAMMAR);
62     let syntax_kinds = project_root().join(SYNTAX_KINDS);
63     let ast = project_root().join(AST);
64     teraron::generate(&syntax_kinds, &grammar, mode)?;
65     teraron::generate(&ast, &grammar, mode)?;
66     Ok(())
67 }
68
69 pub fn project_root() -> PathBuf {
70     Path::new(&env!("CARGO_MANIFEST_DIR"))
71         .ancestors()
72         .nth(2)
73         .unwrap()
74         .to_path_buf()
75 }
76
77 pub fn run(cmdline: &str, dir: &str) -> Result<()> {
78     eprintln!("\nwill run: {}", cmdline);
79     let project_dir = project_root().join(dir);
80     let mut args = cmdline.split_whitespace();
81     let exec = args.next().unwrap();
82     let status = Command::new(exec)
83         .args(args)
84         .current_dir(project_dir)
85         .status()?;
86     if !status.success() {
87         bail!("`{}` exited with {}", cmdline, status);
88     }
89     Ok(())
90 }
91
92 pub fn run_rustfmt(mode: Mode) -> Result<()> {
93     match Command::new("rustup")
94         .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
95         .stderr(Stdio::null())
96         .stdout(Stdio::null())
97         .status()
98     {
99         Ok(status) if status.success() => (),
100         _ => install_rustfmt()?,
101     };
102
103     if mode == Verify {
104         run(
105             &format!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN),
106             ".",
107         )?;
108     } else {
109         run(&format!("rustup run {} -- cargo fmt", TOOLCHAIN), ".")?;
110     }
111     Ok(())
112 }
113
114 fn install_rustfmt() -> Result<()> {
115     run(&format!("rustup install {}", TOOLCHAIN), ".")?;
116     run(
117         &format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN),
118         ".",
119     )
120 }
121
122 pub fn install_format_hook() -> Result<()> {
123     let result_path = Path::new("./.git/hooks/pre-commit");
124     if !result_path.exists() {
125         run("cargo build --package tools --bin pre-commit", ".")?;
126         if cfg!(windows) {
127             copy("./target/debug/pre-commit.exe", result_path)?;
128         } else {
129             copy("./target/debug/pre-commit", result_path)?;
130         }
131     } else {
132         return Err(Error::new(ErrorKind::AlreadyExists, "Git hook already created").into());
133     }
134     Ok(())
135 }