hugo_build/
lib.rs

1use std::{
2    fs::File,
3    io::{Read, Write},
4    path::{Path, PathBuf},
5    process::{Command, Output},
6};
7use flate2::read::GzDecoder;
8use tar::Archive;
9
10#[cfg(test)]
11mod test;
12
13#[derive(Debug, Default, Clone)]
14pub struct HugoBuilder {
15    /// path to the hugo binary
16    binary: PathBuf,
17    /// source directory
18    input_path: Option<PathBuf>,
19    /// target directory
20    output_path: Option<PathBuf>,
21}
22
23#[cfg(target_os = "macos")]
24static ARCH: &str = "darwin-universal";
25#[cfg(target_os = "linux")]
26static ARCH: &str = "Linux-64bit";
27#[cfg(target_os = "windows")]
28static ARCH: &str = "windows-amd64";
29
30static VERSION: &str = std::env!("CARGO_PKG_VERSION");
31
32#[cfg(not(target_os = "windows"))]
33fn fix_permissions(local_file: &File) {
34    //set permissions
35    use std::os::unix::prelude::PermissionsExt;
36    let permissions = std::fs::Permissions::from_mode(0o755);
37    local_file.set_permissions(permissions).unwrap();
38}
39
40/// initialises a hugo build
41///
42/// fetches the binary from github if required
43pub fn init() -> HugoBuilder {
44    // fetch binary from github
45    let url = format!("https://github.com/gohugoio/hugo/releases/download/v{VERSION}/hugo_extended_{VERSION}_{ARCH}.tar.gz");
46    let out_dir = std::env::var("OUT_DIR").unwrap();
47    let out_path = Path::new(&out_dir);
48    // use profile path to store executable
49    // e.g. ./target/debug/build/hugo
50    let out_path = out_path.parent().unwrap().parent().unwrap();
51    let mut binary_name = out_path.join("hugo");
52
53    // check for already downloaded binary
54    let mut binary_exists = false;
55    let result = out_path.read_dir().expect("reading OUT_DIR");
56    for file in result {
57        let entry = file.unwrap();
58        if entry.file_name().to_string_lossy().contains("hugo") {
59            binary_exists = true;
60            binary_name = entry.path();
61        }
62    }
63    if !binary_exists {
64        // download fresh binary
65        let result = reqwest::blocking::get(url).unwrap();
66        let bytes = result.bytes().expect("downloading the hugo binary failed");
67        let decompressor = GzDecoder::new(&bytes[..]);
68        let mut archive = Archive::new(decompressor);
69        for entry in archive.entries().unwrap() {
70            let mut file = entry.unwrap();
71            let file_path = file.path().unwrap();
72            let is_binary = file_path.starts_with("hugo");
73            let target_file_name = out_path.join(&file_path);
74            let mut bytes: Vec<u8> = vec![];
75            _ = file.read_to_end(&mut bytes).unwrap();
76            let mut local_file = File::create(target_file_name.clone()).unwrap();
77            local_file.write_all(&bytes).unwrap();
78            if is_binary {
79                binary_name = out_path.join(target_file_name.clone());
80                #[cfg(not(target_os = "windows"))]
81                fix_permissions(&local_file);
82            }
83        }
84    }
85    HugoBuilder {
86        binary: binary_name,
87        ..Default::default()
88    }
89}
90
91impl HugoBuilder {
92    /// defines source directory for the hugo build
93    pub fn with_input(self, path: PathBuf) -> HugoBuilder {
94        let mut cpy = self;
95        cpy.input_path = Some(path);
96        cpy
97    }
98    /// defines target directory for the hugo build
99    pub fn with_output(self, path: PathBuf) -> HugoBuilder {
100        let mut cpy = self;
101        cpy.output_path = Some(path);
102        cpy
103    }
104    pub fn build(self) -> Result<Output, std::io::Error> {
105        let base = std::env::var("CARGO_MANIFEST_DIR").unwrap();
106        let input = match self.input_path {
107            None => {
108                println!("cargo:warning=no input path set, using ./site");
109                Path::new(&base).join("site")
110            }
111            Some(val) => val,
112        };
113        let output = match self.output_path {
114            None => {
115                println!("cargo:warning=no output path set, using ./target/site");
116                Path::new(&base).join("target").join("site")
117            }
118            Some(val) => val,
119        };
120        Command::new(self.binary)
121            .arg("-s")
122            .arg(input)
123            .arg("-d")
124            .arg(output)
125            .output()
126    }
127}