summaryrefslogtreecommitdiff
path: root/src/resource.rs
blob: 9ac9da8353a6adba68d712ef49731fdf55f00a32 (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
137
138
139
140
141
142
143
144
145
//! 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 clap::Parser;
use std::io::{self, Cursor, Read};
use std::path::{Path, PathBuf};
use std::sync::Mutex;

#[allow(missing_docs)]
#[derive(Debug, Parser)]
// 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 {
    #[clap(
        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<P: AsRef<Path>>(&self, doc_path: Option<P>) {
        for rpath in &self.resources {
            add_search_path(rpath);
        }
        if let Some(doc_path) = doc_path.as_ref() {
            add_search_path(doc_path);
        }
    }
}

use lazy_static::lazy_static;

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

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

/// Retrieve the embedded file list
pub fn embedded_files() -> &'static [(&'static str, &'static [u8])] {
    EMBEDDED_FILES
}

/// 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());
}

/// Open a file for reading, honouring search paths established during
/// startup, and falling back to potentially embedded file content.
///
/// The search path sequence is:
///
/// First any path given to `--resources` in the order of the arguments
/// Second, it's relative to the input document.
/// Finally we check for an embedded file.
///
/// Then we repeat all the above, inserting 'common' as the template name, and
/// finally repeat the above inserting the real template name in the subpath
/// too.
fn open<P: AsRef<Path>>(subpath: P, template: Option<&str>) -> io::Result<Box<dyn Read>> {
    let subpath = subpath.as_ref();
    let plain = match internal_open(subpath) {
        Ok(r) => return Ok(r),
        Err(e) => e,
    };
    let commonpath = Path::new("common").join(subpath);
    let common = match internal_open(&commonpath) {
        Ok(r) => return Ok(r),
        Err(e) => e,
    };
    let templated = match template {
        Some(templ) => {
            let templpath = Path::new(templ).join(subpath);
            match internal_open(&templpath) {
                Ok(r) => return Ok(r),
                Err(e) => Some(e),
            }
        }
        None => None,
    };
    if plain.kind() != io::ErrorKind::NotFound {
        return Err(plain);
    }
    if common.kind() != io::ErrorKind::NotFound {
        return Err(common);
    }
    match templated {
        Some(e) => Err(e),
        None => Err(common),
    }
}

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(search_paths);
    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, template: Option<&str>) -> io::Result<String> {
    let mut f = open(subpath, template)?;
    let mut ret = String::with_capacity(8192);
    f.read_to_string(&mut ret)?;
    Ok(ret)
}