1use camino::{Utf8Path, Utf8PathBuf};
2use std::{
3 fs::{self, File},
4 io::{self, Read},
5};
6
7use anyhow::anyhow;
8
9mod list;
10mod record;
11mod replay;
12
13pub use list::list;
14pub use record::record;
15pub use replay::replay;
16
17pub struct Trace(Utf8PathBuf);
18
19impl Trace {
20 pub fn new(name: &str) -> anyhow::Result<Self> {
21 let root = traces_dir()?;
22 let dir = root.join(name);
23 if !dir.exists() {
24 return Err(anyhow!("Trace `{}` does not exist in `{}`", name, root));
25 }
26 Ok(Self(dir))
27 }
28
29 fn name_for_bin(bin: &Utf8Path) -> anyhow::Result<Self> {
30 let root = traces_dir()?;
31 let bin_name = bin
32 .file_name()
33 .ok_or_else(|| anyhow!("Can't get file name of bin"))?;
34 let mut dir = root.join(bin_name);
35
36 let mut suffix = 0;
37 loop {
38 if !dir.exists() {
39 break Ok(Self(dir));
40 }
41 suffix += 1;
42 dir.set_file_name(format!("{}-{}", bin_name, suffix));
43 }
44 }
45
46 pub fn name(&self) -> &str {
47 &self.0.file_name().expect("Trace dir shouldn't end in ..")
48 }
49
50 pub fn dir(&self) -> &Utf8Path {
51 self.0.as_path()
52 }
53
54 pub fn set_latest(&self) -> anyhow::Result<()> {
55 let root = self.0.parent().expect("Trace has parent");
56 fs::write(root.join("latest"), self.name())?;
57 Ok(())
58 }
59
60 pub fn latest() -> anyhow::Result<Self> {
61 let root = traces_dir()?;
62 let mut file = match File::open(root.join("latest")) {
63 Ok(f) => f,
64 Err(err) if err.kind() == io::ErrorKind::NotFound => {
65 return Err(anyhow!("No trace in `{}`", root));
66 }
67 Err(err) => return Err(err.into()),
68 };
69 let mut latest = String::new();
70 file.read_to_string(&mut latest)?;
71 let latest = latest.trim();
72 let latest = fs::canonicalize(root.join(latest))?;
73 let latest = Utf8PathBuf::from_path_buf(latest)
74 .map_err(|_| anyhow!("Path to target dir must be utf-8"))?;
75 Ok(Self(latest))
76 }
77}
78
79pub fn traces_dir() -> anyhow::Result<Utf8PathBuf> {
80 let meta = cargo_metadata::MetadataCommand::new().no_deps().exec()?;
81 let dir = meta.target_directory.join("rr");
82 fs::create_dir_all(&dir)?;
83 Ok(dir)
84}
85
86pub fn split_opts(opts: Option<&str>) -> Vec<&str> {
87 opts.map_or_else(Vec::new, |s| s.split(' ').collect())
88}