use std::path::{Path, PathBuf};
use assert_cmd::Command;
use serde_json::{Value, json};
use tempfile::TempDir;
#[derive(Clone, Copy, Debug)]
pub enum SourceBoundary {
Direct,
Subprocess,
}
pub const SOURCE_BOUNDARIES: [SourceBoundary; 2] =
[SourceBoundary::Direct, SourceBoundary::Subprocess];
impl SourceBoundary {
pub fn source(self, plugin: &str, config: Value) -> Value {
self.source_with_secrets(plugin, config, &[])
}
pub fn source_with_secrets(self, plugin: &str, config: Value, secrets: &[&str]) -> Value {
match self {
Self::Direct => json!({"plugin": plugin, "config": config}),
Self::Subprocess => json!({
"plugin": "subprocess",
"config": {
"command": env!("CARGO_BIN_EXE_onetaskgraph-source"),
"secrets": secrets,
"settings": {"kind": plugin, "config": config},
},
}),
}
}
pub fn config_path(self, field: &str) -> String {
match self {
Self::Direct => format!("sources.work.config.{field}"),
Self::Subprocess => format!("sources.work.config.settings.config.{field}"),
}
}
pub fn config_variable(self, field: &str) -> String {
format!(
"ONETASKGRAPH_{}",
self.config_path(field)
.split('.')
.map(|segment| segment.to_ascii_uppercase().replace('-', "_"))
.collect::<Vec<_>>()
.join("__")
)
}
}
pub struct Sandbox {
_directory: TempDir,
root: PathBuf,
}
impl Sandbox {
pub fn new() -> Self {
let directory = TempDir::new().expect("a temporary directory");
let root = resolved(directory.path());
std::fs::create_dir_all(root.join("project")).expect("the project tree");
std::fs::create_dir_all(root.join("home/onetaskgraph")).expect("the config home");
Self {
_directory: directory,
root,
}
}
pub fn project(&self) -> PathBuf {
self.root.join("project")
}
pub fn config_home(&self) -> PathBuf {
self.root.join("home")
}
pub fn project_document(&self, text: &str) -> PathBuf {
write(self.project().join("onetaskgraph.yaml"), text)
}
pub fn user_document(&self, text: &str) -> PathBuf {
write(self.config_home().join("onetaskgraph/config.yaml"), text)
}
pub fn secrets_file(&self, text: &str) -> PathBuf {
write(self.config_home().join("onetaskgraph/secrets.env"), text)
}
pub fn subdirectory(&self, relative: &str) -> PathBuf {
let path = self.project().join(relative);
std::fs::create_dir_all(&path).expect("a directory under the project tree");
path
}
pub fn command(&self) -> Command {
self.command_in(&self.project())
}
pub fn command_in(&self, directory: &Path) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_onetaskgraph"));
for (name, _) in std::env::vars() {
if name.starts_with("ONETASKGRAPH_") {
command.env_remove(name);
}
}
command
.current_dir(directory)
.env("XDG_CONFIG_HOME", self.config_home())
.env_remove("HOME");
command
}
pub fn unreadable(&self, relative: &str) -> PathBuf {
let path = self.config_home().join(relative);
std::fs::create_dir_all(&path).expect("a directory in the file's place");
path
}
}
#[cfg(not(windows))]
fn resolved(path: &Path) -> PathBuf {
path.canonicalize().expect("the temporary root resolves")
}
#[cfg(windows)]
fn resolved(path: &Path) -> PathBuf {
path.to_path_buf()
}
fn write(path: PathBuf, text: &str) -> PathBuf {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("the directory holding the file");
}
std::fs::write(&path, text).expect("the file is written");
path
}
pub fn one_source(boundary: SourceBoundary) -> String {
serde_json::to_string(&json!({
"sources": {
"work": boundary.source("in-memory", json!({
"capabilities": {"max_page_size": 20}
}))
}
}))
.expect("a one-source document")
}
pub fn stdout(output: &std::process::Output) -> String {
String::from_utf8(output.stdout.clone()).expect("stdout is UTF-8")
}
pub fn stderr(output: &std::process::Output) -> String {
String::from_utf8(output.stderr.clone()).expect("stderr is UTF-8")
}