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 = "    ";