1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::{
    fs::File,
    io::{Read, Write},
    path::{Path, PathBuf},
    process::{Command, Output},
};

use flate2::read::GzDecoder;
use tar::Archive;

#[cfg(test)]
mod test;

#[derive(Debug, Default, Clone)]
pub struct HugoBuilder {
    /// path to the hugo binary
    binary: PathBuf,
    /// source directory
    input_path: Option<PathBuf>,
    /// target directory
    output_path: Option<PathBuf>,
}

#[cfg(target_os = "macos")]
static ARCH: &str = "darwin-universal";
#[cfg(target_os = "linux")]
static ARCH: &str = "Linux-64bit";
#[cfg(target_os = "windows")]
static ARCH: &str = "windows-amd64";

static VERSION: &str = std::env!("CARGO_PKG_VERSION");

#[cfg(not(target_os = "windows"))]
fn fix_permissions(local_file: &File) {
    //set permissions
    use std::os::unix::prelude::PermissionsExt;
    let permissions = std::fs::Permissions::from_mode(0o755);
    local_file.set_permissions(permissions).unwrap();
}

/// initialises a hugo build
///
/// fetches the binary from github if required
pub fn init() -> HugoBuilder {
    // fetch binary from github
    let url = format!("https://github.com/gohugoio/hugo/releases/download/v{VERSION}/hugo_extended_{VERSION}_{ARCH}.tar.gz");
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let out_path = Path::new(&out_dir);
    // use profile path to store executable
    // e.g. ./target/debug/build/hugo
    let out_path = out_path.parent().unwrap().parent().unwrap();
    let mut binary_name = out_path.join("hugo");

    // check for already downloaded binary
    let mut binary_exists = false;
    let result = out_path.read_dir().expect("reading OUT_DIR");
    for file in result {
        let entry = file.unwrap();
        if entry.file_name().to_string_lossy().contains("hugo") {
            binary_exists = true;
            binary_name = entry.path();
        }
    }
    if !binary_exists {
        // download fresh binary
        let result = reqwest::blocking::get(url).unwrap();
        let bytes = result.bytes().expect("downloading the hugo binary failed");
        let decompressor = GzDecoder::new(&bytes[..]);
        let mut archive = Archive::new(decompressor);
        for entry in archive.entries().unwrap() {
            let mut file = entry.unwrap();
            let file_path = file.path().unwrap();
            let is_binary = file_path.starts_with("hugo");
            let target_file_name = out_path.join(&file_path);
            let mut bytes: Vec<u8> = vec![];
            _ = file.read_to_end(&mut bytes).unwrap();
            let mut local_file = File::create(target_file_name.clone()).unwrap();
            local_file.write_all(&bytes).unwrap();
            if is_binary {
                binary_name = out_path.join(target_file_name.clone());
                #[cfg(not(target_os = "windows"))]
                fix_permissions(&local_file);
            }
        }
    }
    HugoBuilder {
        binary: binary_name,
        ..Default::default()
    }
}

impl HugoBuilder {
    /// defines source directory for the hugo build
    pub fn with_input(self, path: PathBuf) -> HugoBuilder {
        let mut cpy = self;
        cpy.input_path = Some(path);
        cpy
    }
    /// defines target directory for the hugo build
    pub fn with_output(self, path: PathBuf) -> HugoBuilder {
        let mut cpy = self;
        cpy.output_path = Some(path);
        cpy
    }
    pub fn build(self) -> Result<Output, std::io::Error> {
        let base = std::env::var("CARGO_MANIFEST_DIR").unwrap();
        let input = match self.input_path {
            None => {
                println!("cargo:warning=no input path set, using ./site");
                Path::new(&base).join("site")
            }
            Some(val) => val,
        };
        let output = match self.output_path {
            None => {
                println!("cargo:warning=no output path set, using ./target/site");
                Path::new(&base).join("target").join("site")
            }
            Some(val) => val,
        };
        Command::new(self.binary)
            .arg("-s")
            .arg(input)
            .arg("-d")
            .arg(output)
            .output()
    }
}