treeboot_core/
discovery.rs1use std::path::{Path, PathBuf};
2
3use serde::Serialize;
4
5use crate::context::resolve_worktree_path;
6use crate::{Error, Result, Worktree};
7
8const INIT_SCRIPT_PATHS: &[&str] = &[".treeboot.sh", ".treebootrc", ".config/treeboot/init"];
9const IGNORE_REASON_NOT_EXECUTABLE: &str = "not_executable";
10const CONFIG_PATHS: &[&str] = &[
11 ".treeboot.toml",
12 "treeboot.toml",
13 ".config/treeboot/config.toml",
14];
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct InitScriptDiscovery {
19 pub executable: Option<PathBuf>,
21 pub ignored: Vec<IgnoredInitScript>,
23}
24
25impl InitScriptDiscovery {
26 #[must_use]
28 pub fn discover(context: &Worktree) -> Self {
29 discover_scripts(&context.worktree_path)
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
35pub struct IgnoredInitScript {
36 pub path: PathBuf,
38 pub reason: &'static str,
42}
43
44pub(crate) fn discover_scripts(worktree_path: &Path) -> InitScriptDiscovery {
45 let mut ignored = Vec::new();
46
47 for relative in INIT_SCRIPT_PATHS {
48 let path = worktree_path.join(relative);
49
50 if !path.is_file() {
51 continue;
52 }
53
54 if is_executable(&path) {
55 return InitScriptDiscovery {
56 executable: Some(path),
57 ignored,
58 };
59 }
60
61 ignored.push(IgnoredInitScript {
62 path,
63 reason: IGNORE_REASON_NOT_EXECUTABLE,
64 });
65 }
66
67 InitScriptDiscovery {
68 executable: None,
69 ignored,
70 }
71}
72
73pub(crate) fn discover_config(
74 worktree_path: &Path,
75 requested_config: Option<&Path>,
76) -> Result<Option<PathBuf>> {
77 if let Some(path) = requested_config {
78 let path = resolve_worktree_path(worktree_path, path);
79
80 if path.is_file() {
81 return Ok(Some(path));
82 }
83
84 return Err(Error::ConfigNotFound(path));
85 }
86
87 Ok(CONFIG_PATHS
88 .iter()
89 .map(|relative| worktree_path.join(relative))
90 .find(|path| path.is_file()))
91}
92
93#[cfg(unix)]
94fn is_executable(path: &Path) -> bool {
95 use std::os::unix::fs::PermissionsExt;
96
97 path.metadata()
98 .map(|metadata| metadata.permissions().mode() & 0o111 != 0)
99 .unwrap_or(false)
100}
101
102#[cfg(not(unix))]
103fn is_executable(path: &Path) -> bool {
104 path.is_file()
105}