Skip to main content

flodl_cli/
context.rs

1//! Execution context: project-local vs global (~/.flodl).
2//!
3//! When running inside a floDl project (detected by walking up from cwd),
4//! libtorch operations target `./libtorch/`. When standalone, they target
5//! `~/.flodl/libtorch/`.
6
7use std::env;
8use std::fs;
9use std::path::PathBuf;
10
11pub struct Context {
12    /// Root directory for libtorch storage. Either a project root or ~/.flodl.
13    pub root: PathBuf,
14    /// Whether we are operating inside a detected project.
15    pub is_project: bool,
16}
17
18impl Context {
19    /// Auto-detect: walk up from cwd looking for a flodl project, fall back
20    /// to `~/.flodl/`.
21    pub fn resolve() -> Self {
22        if let Some(project_root) = find_project_root() {
23            Context {
24                root: project_root,
25                is_project: true,
26            }
27        } else {
28            Context {
29                root: global_root(),
30                is_project: false,
31            }
32        }
33    }
34
35    /// The global root, whether or not cwd sits inside a project:
36    /// `$FLODL_HOME`, else `~/.flodl`.
37    ///
38    /// [`Context::resolve`] prefers a project root, which is right for a
39    /// developer managing their checkout's libtorch and wrong for a box
40    /// that is only *consuming* artifacts: a dial-in worker's project
41    /// root is frequently a read-only shared mount, so its acquisitions
42    /// (libtorch, the fetched source tree) belong under the root fdl
43    /// manages on that one box. The two are named apart on purpose.
44    pub fn global() -> Self {
45        Context {
46            root: global_root(),
47            is_project: false,
48        }
49    }
50
51    /// Force a specific root (for --path overrides).
52    pub fn with_root(root: PathBuf) -> Self {
53        let is_project = root.join("Cargo.toml").exists();
54        Context { root, is_project }
55    }
56
57    /// The libtorch directory under this root.
58    pub fn libtorch_dir(&self) -> PathBuf {
59        self.root.join("libtorch")
60    }
61
62    /// Print a short context line (for diagnostics).
63    pub fn label(&self) -> String {
64        if self.is_project {
65            format!("project ({})", self.root.display())
66        } else {
67            format!("global ({})", self.root.display())
68        }
69    }
70}
71
72/// Walk up from cwd looking for a flodl project.
73fn find_project_root() -> Option<PathBuf> {
74    let mut dir = env::current_dir().ok()?;
75    loop {
76        // Strongest signal: libtorch/.active exists here
77        if dir.join("libtorch/.active").exists() {
78            return Some(dir);
79        }
80        // Secondary signal: Cargo.toml mentioning flodl
81        let cargo_toml = dir.join("Cargo.toml");
82        if cargo_toml.exists()
83            && let Ok(contents) = fs::read_to_string(&cargo_toml)
84            && contents.contains("flodl")
85        {
86            return Some(dir);
87        }
88        if !dir.pop() {
89            return None;
90        }
91    }
92}
93
94/// Global root: $FLODL_HOME or ~/.flodl/
95fn global_root() -> PathBuf {
96    env::var("FLODL_HOME")
97        .map(PathBuf::from)
98        .unwrap_or_else(|_| home_dir().join(".flodl"))
99}
100
101pub(crate) fn home_dir() -> PathBuf {
102    env::var("HOME")
103        .or_else(|_| env::var("USERPROFILE"))
104        .map(PathBuf::from)
105        .unwrap_or_else(|_| PathBuf::from("."))
106}