include_repo/
lib.rs

1use std::io::Write;
2
3use libflate::gzip::Encoder;
4use proc_macro::TokenStream;
5use syn::parse_macro_input;
6
7fn repo_tarball(filter: &[String]) -> Vec<u8> {
8    // in an ideal world, this would probably be implemented with git2-rs or such.
9    // In my world, it's a lot easier to just do this, and this is a bit of a hack in the first
10    // place.
11    let toplevel = std::process::Command::new("git")
12        .arg("rev-parse")
13        .arg("--show-toplevel")
14        .output()
15        .expect("could not get top level directory");
16    let toplevel_dir = String::from_utf8(toplevel.stdout).unwrap();
17    let toplevel_dir = toplevel_dir.trim();
18    let mut archive = std::process::Command::new("git");
19    archive
20        .current_dir(toplevel_dir)
21        .arg("archive")
22        .arg("HEAD")
23        .arg("--");
24    for f in filter {
25        archive.arg(f);
26    }
27    let output = archive
28        .output()
29        .expect("could not run 'git archive HEAD' for repo_tarball");
30
31    if !output.status.success() {
32        panic!("[include-repo]: error running git-archive: Exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr));
33    }
34    output.stdout
35}
36
37struct Filters {
38    filters: Vec<String>
39}
40
41impl syn::parse::Parse for Filters {
42    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
43        let mut filters = Vec::new();
44
45        while input.peek(syn::Lit) {
46            let lit: syn::Lit = input.parse()?;
47            let s = match lit {
48                syn::Lit::Str(s) => s.value(),
49                syn::Lit::ByteStr(s) => String::from_utf8(s.value()).unwrap(),
50                _ => panic!("[include-repo]: error parsing filter argument. Must be a string, got {:?}", lit),
51            };
52            filters.push(s);
53        }
54        Ok(Filters{filters})
55    }
56}
57
58#[proc_macro]
59/// Create a tarball of the git repo for this project.
60///
61/// That is to say, `const FOO = include_repo!();` will become `const FOO: [u8; ...] = [...];`
62pub fn include_repo(args: TokenStream) -> TokenStream {
63    let f: Filters = parse_macro_input!(args as Filters);
64    let tar = repo_tarball(&f.filters);
65    format!("&[{}]", tar.iter().map(|b| b.to_string()).collect::<Vec<_>>().join(", ")).parse().unwrap()
66}
67
68#[proc_macro]
69/// Create a gzipped tarball of the git repo for this project
70///
71/// That is to say, `const FOO: &[u8] = include_repo_gz!();` will become `const FOO: &[u8] = [<gzipped bytes>];`
72pub fn include_repo_gz(args: TokenStream) -> TokenStream {
73    let f: Filters = parse_macro_input!(args as Filters);
74    let tar = repo_tarball(&f.filters);
75
76    let mut encoder = Encoder::new(Vec::new()).unwrap();
77    encoder.write_all(&tar).unwrap();
78    let targz = encoder.finish().into_result().unwrap();
79
80    format!("&[{}];", targz.iter().map(|b| b.to_string()).collect::<Vec<_>>().join(", ")).parse().unwrap()
81}