summaryrefslogtreecommitdiff
path: root/src/resource.rs
blob: 6f2b33b6dc86f5f32bbb67c14a191c9704b8c9c2 (plain)
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
129
130
131
132
133
134
135
136
//! Resources for subplot.
//!
//! This module encapsulates a mechanism for subplot to find resource files.
//! Resource files come from a number of locations, such as embedded into the
//! binary, from template paths given on the CLI, or from paths built into the
//! binary and present on disk.

use std::io::{self, Cursor, Read};
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
/// Options which relate to resource management
///
/// To use this, include them *flat* in your options struct, and then after
/// parsing, call the [ResourceOpts::handle()] function.
pub struct ResourceOpts {
    #[structopt(
        long,
        number_of_values = 1,
        help = "Look for code templates and other resources in DIR",
        name = "DIR"
    )]
    resources: Vec<PathBuf>,
}

impl ResourceOpts {
    /// Handle any supplied resource related arguments
    pub fn handle(&self) {
        for rpath in &self.resources {
            add_search_path(rpath);
        }
    }
}

use lazy_static::lazy_static;

lazy_static! {
    static ref SEARCH_PATHS: Mutex<Vec<PathBuf>> = {
        let ret = Vec::new();
        Mutex::new(ret)
    };
    static ref TEMPLATE_NAME: Mutex<Option<String>> = Mutex::new(None);
}

static EMBEDDED_FILES: &[(&str, &[u8])] = include!(concat!(env!("OUT_DIR"), "/embedded_files.inc"));

/// Put a path at the back of the queue to search in
pub fn add_search_path<P: AsRef<Path>>(path: P) {
    SEARCH_PATHS
        .lock()
        .expect("Unable to lock SEARCH_PATHS")
        .push(path.as_ref().into());
}

/// Set the template name, for use in searching for content...
pub fn set_template(template: &str) {
    *TEMPLATE_NAME.lock().expect("Unable to lock TEMPLATE_NAME") = Some(template.to_string());
}

/// Open a file for reading, honouring search paths established during
/// startup, and falling back to potentially embedded file content
pub fn open<P: AsRef<Path>>(subpath: P) -> io::Result<Box<dyn Read>> {
    let subpath = subpath.as_ref();
    match internal_open(subpath) {
        Ok(r) => Ok(r),
        Err(e) => {
            let template = TEMPLATE_NAME.lock().expect("Unable to lock TEMPLATE_NAME");
            if let Some(templ) = template.as_deref() {
                let subpath = Path::new(templ).join(subpath);
                match internal_open(&subpath) {
                    Ok(r) => Ok(r),
                    Err(sub_e) => {
                        if sub_e.kind() != io::ErrorKind::NotFound
                            && e.kind() == io::ErrorKind::NotFound
                        {
                            Err(sub_e)
                        } else {
                            Err(e)
                        }
                    }
                }
            } else {
                Err(e)
            }
        }
    }
}

fn internal_fallback_path() -> Option<&'static Path> {
    match env!("FALLBACK_PATH") {
        "" => None,
        s => Some(Path::new(s)),
    }
}

fn internal_open(subpath: &Path) -> io::Result<Box<dyn Read>> {
    let search_paths = SEARCH_PATHS.lock().expect("Unable to lock SEARCH_PATHS");
    let search_paths = search_paths.iter().map(|p| p.as_path());
    let search_paths = std::iter::empty()
        .chain(Some(Path::new(".")))
        .chain(search_paths)
        .chain(internal_fallback_path());
    let mut ret = Err(io::Error::new(
        io::ErrorKind::NotFound,
        format!("Unable to find {} in resource paths", subpath.display()),
    ));
    for basepath in search_paths {
        let full_path = basepath.join(subpath);
        ret = std::fs::File::open(full_path);
        if ret.is_ok() {
            break;
        }
    }

    match ret {
        Ok(ret) => Ok(Box::new(ret)),
        Err(e) => {
            if let Some(data) = EMBEDDED_FILES.iter().find(|e| Path::new(e.0) == subpath) {
                Ok(Box::new(Cursor::new(data.1)))
            } else {
                Err(e)
            }
        }
    }
}

/// Read a file, honouring search paths established during startup, and
/// falling back to potentially embedded file content
pub fn read_as_string<P: AsRef<Path>>(subpath: P) -> io::Result<String> {
    let mut f = open(subpath)?;
    let mut ret = String::with_capacity(8192);
    f.read_to_string(&mut ret)?;
    Ok(ret)
}