hexz_cli/cmd/data/
workspace.rs1use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::hash_map::DefaultHasher;
6use std::hash::{Hash, Hasher};
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Serialize, Deserialize)]
11pub struct WorkspaceConfig {
12 pub base_archive: Option<PathBuf>,
14 pub overlay_path: Option<PathBuf>,
16 pub host_cwd: Option<PathBuf>,
18 #[serde(default)]
20 pub remotes: std::collections::HashMap<String, String>,
21}
22
23#[derive(Debug)]
25pub struct Workspace {
26 pub root: PathBuf,
28 pub config: WorkspaceConfig,
30}
31
32impl Workspace {
33 pub fn init(path: &Path, base_archive: Option<PathBuf>) -> Result<Self> {
35 let abs_path = std::fs::canonicalize(path)?;
36
37 let mut s = DefaultHasher::new();
39 abs_path.hash(&mut s);
40 let id = format!("{:x}", s.finish());
41
42 let home = std::env::var("HOME").context("HOME not set")?;
43 let hexz_root = PathBuf::from(home).join(".hexz").join("workspaces").join(&id);
44
45 std::fs::create_dir_all(&hexz_root)?;
46
47 let overlay_dir = hexz_root.join("overlay");
48 std::fs::create_dir_all(overlay_dir)?;
49
50 let base_archive = if let Some(b) = base_archive {
51 Some(std::fs::canonicalize(b)?)
52 } else {
53 None
54 };
55
56 let config = WorkspaceConfig {
57 base_archive,
58 overlay_path: None,
59 host_cwd: None,
60 remotes: std::collections::HashMap::new(),
61 };
62
63 let config_path = hexz_root.join("config.json");
64 let f = std::fs::File::create(config_path)?;
65 serde_json::to_writer_pretty(f, &config)?;
66
67 Ok(Self {
68 root: abs_path,
69 config,
70 })
71 }
72
73 pub fn save(&self) -> Result<()> {
75 let config_path = self.metadata_dir().join("config.json");
76 let f = std::fs::File::create(config_path)?;
77 serde_json::to_writer_pretty(f, &self.config)?;
78 Ok(())
79 }
80
81 pub fn find(start_path: &Path) -> Result<Option<Self>> {
83 let mut current = if start_path.exists() {
84 std::fs::canonicalize(start_path)?
85 } else {
86 return Ok(None);
87 };
88
89 loop {
90 let local_config = current.join(".hexz").join("config.json");
92 if local_config.exists() {
93 let f = std::fs::File::open(local_config)?;
94 let config: WorkspaceConfig = serde_json::from_reader(f)?;
95 return Ok(Some(Self {
96 root: current,
97 config,
98 }));
99 }
100
101 let mut s = DefaultHasher::new();
103 current.hash(&mut s);
104 let id = format!("{:x}", s.finish());
105 let home = std::env::var("HOME").context("HOME not set")?;
106 let hexz_root = PathBuf::from(home).join(".hexz").join("workspaces").join(id);
107 let config_path = hexz_root.join("config.json");
108
109 if config_path.exists() {
110 let f = std::fs::File::open(config_path)?;
111 let config: WorkspaceConfig = serde_json::from_reader(f)?;
112 return Ok(Some(Self {
113 root: current,
114 config,
115 }));
116 }
117
118 if let Some(parent) = current.parent() {
119 current = parent.to_path_buf();
120 } else {
121 break;
122 }
123 }
124
125 Ok(None)
126 }
127
128 pub fn overlay_path(&self) -> PathBuf {
130 if let Some(ref p) = self.config.overlay_path {
131 return p.clone();
132 }
133 self.metadata_dir().join("overlay")
134 }
135
136 pub fn metadata_dir(&self) -> PathBuf {
138 let mut s = DefaultHasher::new();
139 self.root.hash(&mut s);
140 let id = format!("{:x}", s.finish());
141 let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into());
142 PathBuf::from(home).join(".hexz").join("workspaces").join(id)
143 }
144}