include_repo_impl/
lib.rs

1#[macro_use]
2extern crate proc_macro_hack;
3extern crate libflate;
4#[macro_use]
5extern crate syn;
6
7use libflate::gzip::Encoder;
8use std::io::Write;
9use syn::punctuated::Punctuated;
10use syn::synom::Parser;
11use syn::{Expr, Lit};
12
13fn repo_tarball(filter: &Vec<String>) -> Vec<u8> {
14    // in an ideal world, this would probably be implemented with git2-rs or such.
15    // In my world, it's a lot easier to just do this, and this is a bit of a hack in the first
16    // place.
17    let toplevel = std::process::Command::new("git")
18        .arg("rev-parse")
19        .arg("--show-toplevel")
20        .output()
21        .expect("could not get top level directory");
22    let toplevel_dir = String::from_utf8(toplevel.stdout).unwrap();
23    let toplevel_dir = toplevel_dir.trim();
24    let mut archive = std::process::Command::new("git");
25    archive
26        .current_dir(toplevel_dir)
27        .arg("archive")
28        .arg("HEAD")
29        .arg("--");
30    for f in filter {
31        archive.arg(f);
32    }
33    let output = archive
34        .output()
35        .expect("could not run 'git archive HEAD' for repo_tarball");
36
37    if !output.status.success() {
38        panic!("[include-repo]: error running git-archive: Exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr));
39    }
40    output.stdout
41}
42
43fn parse_input(input: &str) -> (String, Vec<String>) {
44    let parts = input.splitn(2, ",").collect::<Vec<_>>();
45    let const_name = parts[0];
46    if parts.len() == 1 {
47        // perfectly fine to not have any git filters
48        return (const_name.to_string(), Vec::new());
49    }
50    let git_filter = parts[1];
51
52    let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
53    let args = parser.parse_str(git_filter).unwrap();
54    let filters: Vec<String> = args
55        .iter()
56        .map(|item| match item {
57            Expr::Lit(lit) => match lit.lit {
58                Lit::Str(ref s) => return s.value(),
59                _ => {
60                    panic!("[include-repo] git filters must be string literals");
61                }
62            },
63            _ => {
64                panic!("[include-repo] git filters must be string literals");
65            }
66        }).collect();
67
68    return (const_name.to_string(), filters);
69}
70
71proc_macro_item_impl! {
72    pub fn include_repo_tarball(input: &str) -> String {
73        let (const_name, filters) = parse_input(input);
74        let tar = repo_tarball(&filters);
75        format!("const {}: [u8; {}] = [{}];", const_name, tar.len(), tar.iter().map(|b| b.to_string()).collect::<Vec<_>>().join(", "))
76    }
77
78    pub fn include_repo_targz(input: &str) -> String {
79        let (const_name, filters) = parse_input(input);
80        let tar = repo_tarball(&filters);
81
82        let mut encoder = Encoder::new(Vec::new()).unwrap();
83        encoder.write_all(&tar).unwrap();
84        let targz = encoder.finish().into_result().unwrap();
85
86        format!("const {}: [u8; {}] = [{}];", const_name, targz.len(), targz.iter().map(|b| b.to_string()).collect::<Vec<_>>().join(", "))
87    }
88}