Skip to main content

cargo_zng/res/
built_in.rs

1//! Built-in tools
2
3use std::{
4    env, fs,
5    io::{self, BufRead as _},
6    path::{Path, PathBuf},
7};
8
9use crate::util;
10
11/// Env var set to the Cargo workspace directory that is parent to the res source.
12///
13/// Note that the tool also runs with this dir as working directory (`current_dir`).
14pub const ZR_WORKSPACE_DIR: &str = "ZR_WORKSPACE_DIR";
15/// Env var set to the resources source directory.
16pub const ZR_SOURCE_DIR: &str = "ZR_SOURCE_DIR";
17/// Env var set to the resources build target directory.
18///
19/// Note that this is the 'root' of the built resources, use [`ZR_TARGET_DD`] to get the
20/// parent dir of the target file inside the target directory.
21pub const ZR_TARGET_DIR: &str = "ZR_TARGET_DIR";
22/// Env var set to dir that the tool can use to store intermediary data for the specific request.
23///
24/// The cache key (dir name) is a hash of source, target, request and request content only.
25pub const ZR_CACHE_DIR: &str = "ZR_CACHE_DIR";
26
27/// Env var set to the request file that called the tool.
28pub const ZR_REQUEST: &str = "ZR_REQUEST";
29/// Env var set to the request file parent dir.
30pub const ZR_REQUEST_DD: &str = "ZR_REQUEST_DD";
31/// Env var set to the target file implied by the request file name.
32///
33/// That is, the request filename without `.zr-{tool}` and in the equivalent target subdirectory.
34pub const ZR_TARGET: &str = "ZR_TARGET";
35/// Env var set to the target file parent dir.
36pub const ZR_TARGET_DD: &str = "ZR_TARGET_DD";
37
38/// Env var set when it is running a tool that requested `zng-res::on-final=` again.
39pub const ZR_FINAL: &str = "ZR_FINAL";
40
41/// Env var set when it needs the tool print the help text shown in `cargo zng res --tools`.
42pub const ZR_HELP: &str = "ZR_HELP";
43
44/// Env var set to package.metadata.zng.about.app_id or "qualifier.org.app" in snake_case
45pub const ZR_APP_ID: &str = "ZR_APP_ID";
46/// Env var set to package.metadata.zng.about.app or package.name
47pub const ZR_APP: &str = "ZR_APP";
48/// Env var set to package.metadata.zng.about.org or the first package.authors
49pub const ZR_ORG: &str = "ZR_ORG";
50/// Env var set to package.version
51pub const ZR_VERSION: &str = "ZR_VERSION";
52/// Env var set to package.description
53pub const ZR_DESCRIPTION: &str = "ZR_DESCRIPTION";
54/// Env var set to package.homepage
55pub const ZR_HOMEPAGE: &str = "ZR_HOMEPAGE";
56/// Env var set to package.license
57pub const ZR_LICENSE: &str = "ZR_LICENSE";
58/// Env var set to package.name
59pub const ZR_PKG_NAME: &str = "ZR_PKG_NAME";
60/// Env var set to package.authors
61pub const ZR_PKG_AUTHORS: &str = "ZR_PKG_AUTHORS";
62/// Env var set to package.name in snake_case
63pub const ZR_CRATE_NAME: &str = "ZR_CRATE_NAME";
64/// Env var set to package.metadata.zng.about.qualifier or the first components `ZR_APP_ID` except the last two
65pub const ZR_QUALIFIER: &str = "ZR_QUALIFIER";
66
67/// Print the help and exit if is help request.
68pub fn help(help: &str) {
69    if env::var(ZR_HELP).is_ok() {
70        println!("{help}");
71        std::process::exit(0);
72    };
73}
74
75/// Get a `ZR_` path var.
76pub fn path(var: &str) -> PathBuf {
77    env::var(var).unwrap_or_else(|_| panic!("missing {var}")).into()
78}
79
80/// Format the path in the standard way used by cargo-zng.
81pub fn display_path(p: &Path) -> String {
82    let base = path(ZR_WORKSPACE_DIR);
83    let r = if let Ok(local) = p.strip_prefix(base) {
84        local.display().to_string()
85    } else {
86        p.display().to_string()
87    };
88
89    #[cfg(windows)]
90    return r.replace('\\', "/");
91
92    #[cfg(not(windows))]
93    r
94}
95
96fn read_line(path: &Path, expected: &str) -> io::Result<String> {
97    match read_lines(path).next() {
98        Some(r) => r.map(|(_, l)| l),
99        None => Err(io::Error::new(
100            io::ErrorKind::InvalidInput,
101            format!("expected {expected} in tool file content"),
102        )),
103    }
104}
105
106fn read_lines(path: &Path) -> impl Iterator<Item = io::Result<(usize, String)>> {
107    enum State {
108        Open(io::Result<fs::File>),
109        Lines(usize, io::Lines<io::BufReader<fs::File>>),
110        End,
111    }
112    // start -> open
113    let mut state = State::Open(fs::File::open(path));
114    std::iter::from_fn(move || {
115        loop {
116            match std::mem::replace(&mut state, State::End) {
117                State::Lines(count, mut lines) => {
118                    if let Some(l) = lines.next() {
119                        match l {
120                            // lines -> lines
121                            Ok(l) => {
122                                state = State::Lines(count + 1, lines);
123                                let test = l.trim();
124                                if !test.is_empty() && !test.starts_with('#') {
125                                    return Some(Ok((count, l)));
126                                }
127                            }
128                            // lines -> end
129                            Err(e) => {
130                                return Some(Err(e));
131                            }
132                        }
133                    }
134                }
135                State::Open(r) => match r {
136                    // open -> lines
137                    Ok(f) => state = State::Lines(1, io::BufReader::new(f).lines()),
138                    // open -> end
139                    Err(e) => return Some(Err(e)),
140                },
141                // end -> end
142                State::End => return None,
143            }
144        }
145    })
146}
147
148fn read_path(request_file: &Path) -> io::Result<PathBuf> {
149    read_line(request_file, "path").map(PathBuf::from)
150}
151
152pub(crate) fn symlink_warn(path: &Path) {
153    warn!("symlink ignored in `{}`, use zr-tools to 'link'", path.display());
154}
155
156pub const ENV_TOOL: &str = "ZNG_RES_TOOL";
157
158macro_rules! built_in {
159    ($($tool:tt),+ $(,)?) => {
160        $(
161            mod $tool;
162            use $tool::$tool;
163        )+
164
165        pub static BUILT_INS: &[&str] = &[
166            $(stringify!($tool),)+
167        ];
168        static BUILT_IN_FNS: &[fn()] = &[
169            $($tool,)+
170        ];
171    };
172}
173built_in! { copy, glob, rp, sh, shf, warn, fail, apk, l10n }
174
175pub(crate) use l10n::release_langs;
176pub(crate) use sh::sh_run;
177
178pub fn run() {
179    if let Ok(tool) = env::var(ENV_TOOL) {
180        // SAFETY: cargo-zng is single threaded
181        //
182        // Remove in case the tool calls cargo-zng again
183        unsafe {
184            env::remove_var(ENV_TOOL);
185        }
186
187        if let Some(i) = BUILT_INS.iter().position(|n| *n == tool.as_str()) {
188            (BUILT_IN_FNS[i])();
189            std::process::exit(0);
190        } else {
191            fatal!("`tool` is not a built-in tool");
192        }
193    }
194}