use super::{
StdlibConfig, StdlibState, collections, command, network, path, time,
which::{self, WhichConfig, WorkspaceSkipList},
};
use anyhow::Context;
use camino::Utf8Path;
#[cfg(unix)]
use cap_std::fs::FileTypeExt;
use cap_std::{ambient_authority, fs, fs_utf8::Dir};
use minijinja::{
Environment, Error, ErrorKind, State,
value::{Kwargs, Value},
};
use std::sync::Arc;
use crate::localization::{self, keys};
type FileTest = (&'static str, fn(fs::FileType) -> bool);
pub fn register(env: &mut Environment<'_>) -> anyhow::Result<StdlibState> {
let root = Dir::open_ambient_dir(".", ambient_authority())
.context(localization::message(keys::STDLIB_REGISTER_OPEN_DIR))?;
let cwd = std::env::current_dir()
.context(localization::message(keys::STDLIB_REGISTER_RESOLVE_DIR))?;
let path = camino::Utf8PathBuf::from_path_buf(cwd).map_err(|path| {
anyhow::anyhow!(
"{}",
localization::message(keys::STDLIB_REGISTER_DIR_NON_UTF8)
.with_arg("path", path.display().to_string())
)
})?;
register_with_config(
env,
StdlibConfig::new(root)?.with_workspace_root_path(path)?,
)
}
pub fn register_with_config(
env: &mut Environment<'_>,
config: StdlibConfig,
) -> anyhow::Result<StdlibState> {
let state = StdlibState::default();
register_read_only_helpers(env, &config);
time::register_functions(env);
let impure = state.impure_flag();
let (network_config, command_config) = config.into_components();
network::register_functions(env, Arc::clone(&impure), network_config);
command::register(env, impure, command_config);
Ok(state)
}
pub(crate) fn register_manifest_query(env: &mut Environment<'_>) -> StdlibState {
let state = StdlibState::default();
register_query_helpers(env);
time::register_query_functions(env);
register_disabled_query_helpers(env);
state
}
fn register_read_only_helpers(env: &mut Environment<'_>, config: &StdlibConfig) {
register_file_tests(env);
path::register_filters(env, config.home_directory().clone());
collections::register_filters(env);
let which_cache_capacity = config.which_cache_capacity();
let which_skip_dirs = WorkspaceSkipList::from_names(config.workspace_skip_dirs());
let which_cwd = config
.workspace_root_path()
.map(|path| Arc::new(path.to_path_buf()));
let which_path = config.path_override().cloned();
let which_config =
WhichConfig::new(which_cwd, which_path, which_skip_dirs, which_cache_capacity)
.with_pathext_override(config.pathext_override().cloned());
which::register(env, which_config);
}
fn register_query_helpers(env: &mut Environment<'_>) {
path::register_query_filters(env);
collections::register_filters(env);
}
fn register_disabled_query_helpers(env: &mut Environment<'_>) {
env.add_function("env", |_variable: String| -> Result<String, Error> {
Err(manifest_query_operation_error("env"))
});
env.add_function("glob", |_pattern: String| -> Result<Value, Error> {
Err(manifest_query_operation_error("glob"))
});
env.add_function(
"fetch",
|_url: String, _kwargs: Kwargs| -> Result<Value, Error> {
Err(manifest_query_operation_error("fetch"))
},
);
env.add_filter(
"shell",
|_state: &State,
_value: Value,
_command: String,
_options: Option<Value>|
-> Result<Value, Error> { Err(manifest_query_operation_error("shell")) },
);
env.add_filter(
"grep",
|_state: &State,
_value: Value,
_pattern: String,
_flags: Option<Value>,
_options: Option<Value>|
-> Result<Value, Error> { Err(manifest_query_operation_error("grep")) },
);
env.add_filter(
"contents",
|_value: String, _encoding: Option<String>| -> Result<String, Error> {
Err(manifest_query_operation_error("contents"))
},
);
}
fn manifest_query_operation_error(operation: &str) -> Error {
Error::new(
ErrorKind::InvalidOperation,
format!(
"{operation} is disabled while rendering `netsuke help targets`; \
manifest queries permit only non-disclosing, side-effect-free \
template helpers"
),
)
}
#[must_use]
pub fn value_from_bytes(bytes: Vec<u8>) -> Value {
match String::from_utf8(bytes) {
Ok(text) => Value::from(text),
Err(err) => Value::from_bytes(err.into_bytes()),
}
}
#[cfg(unix)]
const FILE_TESTS: &[FileTest] = &[
("dir", is_dir),
("file", is_file),
("symlink", is_symlink),
("pipe", is_fifo),
("block_device", is_block_device),
("char_device", is_char_device),
("device", is_device),
];
#[cfg(not(unix))]
const FILE_TESTS: &[FileTest] = &[
("dir", is_dir),
("file", is_file),
("symlink", is_symlink),
("pipe", is_fifo),
("block_device", is_block_device),
("char_device", is_char_device),
("device", is_device),
];
fn register_file_tests(env: &mut Environment<'_>) {
for &(name, pred) in FILE_TESTS {
env.add_test(name, move |val: Value| -> Result<bool, Error> {
if let Some(s) = val.as_str() {
return path::file_type_matches(Utf8Path::new(s), pred);
}
Ok(false)
});
}
}
fn is_dir(ft: fs::FileType) -> bool {
ft.is_dir()
}
fn is_file(ft: fs::FileType) -> bool {
ft.is_file()
}
fn is_symlink(ft: fs::FileType) -> bool {
ft.is_symlink()
}
#[cfg(unix)]
fn is_fifo(ft: fs::FileType) -> bool {
ft.is_fifo()
}
#[cfg(not(unix))]
fn is_fifo(_ft: fs::FileType) -> bool {
false
}
#[cfg(unix)]
fn is_block_device(ft: fs::FileType) -> bool {
ft.is_block_device()
}
#[cfg(not(unix))]
fn is_block_device(_ft: fs::FileType) -> bool {
false
}
#[cfg(unix)]
fn is_char_device(ft: fs::FileType) -> bool {
ft.is_char_device()
}
#[cfg(not(unix))]
fn is_char_device(_ft: fs::FileType) -> bool {
false
}
#[cfg(unix)]
fn is_device(ft: fs::FileType) -> bool {
is_block_device(ft) || is_char_device(ft)
}
#[cfg(not(unix))]
fn is_device(_ft: fs::FileType) -> bool {
false
}