owtree/owtree.rs
1//! **owtree** -- _Rust version_
2//!
3//! ## show the directory structure for owserver
4//!
5//! **owtree** a tool in the 1-wire file system **OWFS**
6//!
7//! This Rust version of **owtree** is part of **owrust** -- the _Rust language_ OWFS programs
8//! * **OWFS** [documentation](https://owfs.org) and [code](https://github.com/owfs/owfs)
9//! * **owrust** [repository](https://github.com/alfille/owrust)
10//!
11//! ## PURPOSE
12//! Show the 1-wire directory structure and devices. Similar to the unix `tree` program.
13//!
14//! ## SYNTAX
15//! ```
16//! owtree [OPTIONS] PATH
17//! ```
18//!
19//! ## OPTIONS
20//! * `-s IP:port` (default `localhost:4304`)
21//! * `--dir` Add trailing **/** for directory elements
22//! * `--bare` Suppress non-device entries
23//! * `--prune` Even more spare output suppressing convenience files like `id` and `crc`
24//! * -h for full list of options
25//!
26//! ## PATH
27//! * 1-wire path
28//! * default is root **/**
29//! * more than one path can be given
30//!
31//! ## USAGE
32//! * owserver must be running in a network-accessible location
33//! * `owtree` is a command line program
34//! * output to stdout
35//! * errors to stderr
36//! * can be "piped" to uther programs like `less` and `grep`
37//!
38//! ## EXAMPLE
39//! ### Read root 1-wire directory
40//! ```
41//! owtree -s localhost:4304 | head -30
42//! ```
43//! ```text
44//! /
45//! ├── 10.67C6697351FF
46//! │ ├── address
47//! │ ├── alias
48//! │ ├── crc8
49//! │ ├── errata
50//! │ │ ├── die
51//! │ │ ├── trim
52//! │ │ ├── trimblanket
53//! │ │ └── trimvalid
54//! │ ├── family
55//! │ ├── id
56//! │ ├── latesttemp
57//! │ ├── locator
58//! │ ├── power
59//! │ ├── r_address
60//! │ ├── r_id
61//! │ ├── r_locator
62//! │ ├── scratchpad
63//! │ ├── temperature
64//! │ ├── temphigh
65//! │ ├── templow
66//! │ └── type
67//! ├── 05.4AEC29CDBAAB
68//! │ ├── PIO
69//! │ ├── address
70//! │ ├── alias
71//! │ ├── crc8
72//! │ ├── family
73//! │ ├── id
74//! ...
75//! ```
76//! There is a lot of virtual information included
77//! * Everything is mirrored in the bus.x directories
78//! * an a mirror in uncached
79//! * Total line count `owtree | wc -l` = 6582
80//!
81//! ### Read __bare__ root 1-wire directory
82//! ```
83//! owtree -s localhost:4304 --bare | head -30
84//! ```
85//! ```text
86//! /
87//! ├── 10.67C6697351FF
88//! │ ├── address
89//! │ ├── alias
90//! │ ├── crc8
91//! │ ├── errata
92//! │ │ ├── die
93//! │ │ ├── trim
94//! │ │ ├── trimblanket
95//! │ │ └── trimvalid
96//! │ ├── family
97//! │ ├── id
98//! │ ├── latesttemp
99//! │ ├── locator
100//! │ ├── power
101//! │ ├── r_address
102//! │ ├── r_id
103//! │ ├── r_locator
104//! │ ├── scratchpad
105//! │ ├── temperature
106//! │ ├── temphigh
107//! │ ├── templow
108//! │ └── type
109//! ├── 05.4AEC29CDBAAB
110//! │ ├── PIO
111//! │ ├── address
112//! │ ├── alias
113//! │ ├── crc8
114//! │ ├── family
115//! │ ├── id
116//! ...
117//! ```
118//! * No virtual information included (not apparent in the snippet above)
119//! * Total line count `owtree --bare | wc -l` = 36
120//!
121//! ### Read __pruned__ root 1-wire directory
122//! ```
123//! owtree -s localhost:4304 --prune | head -30
124//! ```
125//! ```text
126//! /
127//! ├── 10.67C6697351FF
128//! │ ├── alias
129//! │ ├── errata
130//! │ │ ├── die
131//! │ │ ├── trim
132//! │ │ ├── trimblanket
133//! │ │ └── trimvalid
134//! │ ├── latesttemp
135//! │ ├── power
136//! │ ├── scratchpad
137//! │ ├── temperature
138//! │ ├── temphigh
139//! │ └── templow
140//! └── 05.4AEC29CDBAAB
141//! ├── PIO
142//! ├── alias
143//! └── sensed
144//! ```
145//! * `--prune` also triggers `--bare` automatically
146//! * No virtual information included
147//! * Convenience files (e.g. id) are suppressed
148//! * Total line count `owtree --bare | wc -l` = 18
149//! ### {c} 2025 Paul H Alfille -- MIT Licence
150
151// owrust project
152// https://github.com/alfille/owrust
153//
154// This is a Rust version of my C owfs code for talking to 1-wire devices via owserver
155// Basically owserver can talk to the physical devices, and provides network access via my "owserver protocol"
156
157use owrust::console::console_line;
158use owrust::parse_args::{OwTree, Parser};
159
160fn main() {
161 let mut owserver = owrust::new(); // create structure for owserver communication
162 let prog = OwTree;
163
164 // configure and get paths
165 match prog.command_line(&mut owserver) {
166 Ok(paths) => {
167 if paths.is_empty() {
168 // No path -- assume root
169 from_path(&mut owserver, "/".to_string());
170 } else {
171 // show tree for each path
172 for path in paths.into_iter() {
173 from_path(&mut owserver, path);
174 }
175 }
176 }
177 Err(e) => {
178 eprintln!("owtree trouble {}", e);
179 }
180 }
181}
182
183// start at path, printing and following directories recursively
184fn from_path(owserver: &mut owrust::OwMessage, path: String) {
185 let root = File::root(path);
186 root.root_print(owserver);
187}
188
189#[derive(Debug, Clone)]
190// Structure for a directory
191struct Dir {
192 contents: Vec<File>,
193}
194impl Dir {
195 // directory needs to call dirall to get a list of contents
196 fn new(owserver: &mut owrust::OwMessage, path: String) -> Self {
197 match owserver.dirallslash(&path) {
198 Ok(d) => Dir {
199 contents: d.into_iter().map(File::new).collect(),
200 },
201 Err(e) => {
202 eprintln!("Trouble reading directory {}: {} ", &path, e);
203 Dir::null_dir()
204 }
205 }
206 }
207 fn null_dir() -> Self {
208 Dir { contents: vec![] }
209 }
210 // print each file in directory
211 fn print(&self, owserver: &mut owrust::OwMessage, prefix: &String) {
212 let len = self.contents.len();
213 for (i, f) in self.contents.iter().enumerate() {
214 f.print(owserver, prefix, i == len - 1);
215 }
216 }
217}
218
219#[derive(Debug, Clone)]
220// file structure for each entry
221struct File {
222 path: String, // full path
223 name: String, // filename itself (for display)
224 dir: bool, // is this a directory?
225}
226impl File {
227 // parse file
228 fn new(path: String) -> Self {
229 let parts: Vec<String> = path.split('/').map(String::from).collect();
230 let len = parts.len();
231 if len == 0 {
232 File {
233 path,
234 name: "No name".to_string(),
235 dir: false,
236 }
237 } else if len == 1 {
238 File {
239 path,
240 name: parts[0].clone(),
241 dir: false,
242 }
243 } else if parts[len - 1].is_empty() {
244 // directory since null last element
245 File {
246 path,
247 name: parts[len - 2].clone(),
248 dir: true,
249 }
250 } else {
251 // regular file
252 File {
253 path,
254 name: parts[len - 1].clone(),
255 dir: false,
256 }
257 }
258 }
259 fn root(path: String) -> Self {
260 File {
261 path: path.clone(),
262 name: path.clone(),
263 dir: true,
264 }
265 }
266 fn root_print(&self, owserver: &mut owrust::OwMessage) {
267 // File
268 console_line(&self.name);
269 let dir = Dir::new(owserver, self.path.clone());
270 dir.print(owserver, &"".to_string());
271 }
272 // print each file with appropriate structure "prefix"
273 fn print(&self, owserver: &mut owrust::OwMessage, prefix: &String, last: bool) {
274 // File name printed
275 if last {
276 console_line(format!("{}{}{}", prefix, END, self.name));
277 } else {
278 console_line(format!("{}{}{}", prefix, NEXT, self.name));
279 }
280 // Dir followed
281 if self.dir {
282 let prefix: String = match last {
283 true => format!("{}{}", prefix, TAB),
284 false => format!("{}{}", prefix, RGT),
285 };
286 let dir = Dir::new(owserver, self.path.clone());
287 dir.print(owserver, &prefix);
288 }
289 }
290}
291
292const END: &str = "└── ";
293const RGT: &str = "│ ";
294const NEXT: &str = "├── ";
295const TAB: &str = " ";