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)
44 .join(".hexz")
45 .join("workspaces")
46 .join(&id);
47
48 std::fs::create_dir_all(&hexz_root)?;
49
50 let overlay_dir = hexz_root.join("overlay");
51 std::fs::create_dir_all(overlay_dir)?;
52
53 let base_archive = if let Some(b) = base_archive {
54 Some(std::fs::canonicalize(b)?)
55 } else {
56 None
57 };
58
59 let config = WorkspaceConfig {
60 base_archive,
61 overlay_path: None,
62 host_cwd: None,
63 remotes: std::collections::HashMap::new(),
64 };
65
66 let config_path = hexz_root.join("config.json");
67 let f = std::fs::File::create(config_path)?;
68 serde_json::to_writer_pretty(f, &config)?;
69
70 Ok(Self {
71 root: abs_path,
72 config,
73 })
74 }
75
76 pub fn save(&self) -> Result<()> {
78 let config_path = self.metadata_dir().join("config.json");
79 let f = std::fs::File::create(config_path)?;
80 serde_json::to_writer_pretty(f, &self.config)?;
81 Ok(())
82 }
83
84 pub fn find(start_path: &Path) -> Result<Option<Self>> {
86 let mut current = if start_path.exists() {
87 std::fs::canonicalize(start_path)?
88 } else {
89 return Ok(None);
90 };
91
92 loop {
93 let local_config = current.join(".hexz").join("config.json");
95 if local_config.exists() {
96 let f = std::fs::File::open(local_config)?;
97 let config: WorkspaceConfig = serde_json::from_reader(f)?;
98 return Ok(Some(Self {
99 root: current,
100 config,
101 }));
102 }
103
104 let mut s = DefaultHasher::new();
106 current.hash(&mut s);
107 let id = format!("{:x}", s.finish());
108 let home = std::env::var("HOME").context("HOME not set")?;
109 let hexz_root = PathBuf::from(home)
110 .join(".hexz")
111 .join("workspaces")
112 .join(id);
113 let config_path = hexz_root.join("config.json");
114
115 if config_path.exists() {
116 let f = std::fs::File::open(config_path)?;
117 let config: WorkspaceConfig = serde_json::from_reader(f)?;
118 return Ok(Some(Self {
119 root: current,
120 config,
121 }));
122 }
123
124 if let Some(parent) = current.parent() {
125 current = parent.to_path_buf();
126 } else {
127 break;
128 }
129 }
130
131 Ok(None)
132 }
133
134 pub fn overlay_path(&self) -> PathBuf {
136 if let Some(ref p) = self.config.overlay_path {
137 return p.clone();
138 }
139 self.metadata_dir().join("overlay")
140 }
141
142 pub fn metadata_dir(&self) -> PathBuf {
144 let mut s = DefaultHasher::new();
145 self.root.hash(&mut s);
146 let id = format!("{:x}", s.finish());
147 let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into());
148 PathBuf::from(home)
149 .join(".hexz")
150 .join("workspaces")
151 .join(id)
152 }
153}