diff options
Diffstat (limited to 'src/bin/cachedir.rs')
-rw-r--r-- | src/bin/cachedir.rs | 134 |
1 files changed, 133 insertions, 1 deletions
diff --git a/src/bin/cachedir.rs b/src/bin/cachedir.rs index e7a11a9..4d35ae8 100644 --- a/src/bin/cachedir.rs +++ b/src/bin/cachedir.rs @@ -1,3 +1,135 @@ +//! `cachedir` is a tiny utility for tagging, finding, and untagging +//! directories as cache directories, according to the +//! <http://www.bford.info/cachedir/> specification. +//! +//! ~~~sh +//! $ cachedir find $HOME +//! $ cachedir tag $HOME/.cache +//! $ cachedir find $HOME +//! /home/liw/.cache +//! $ cachedir untag $HOME/.cache +//! $ cachedir find $HOME +//! $ +//! ~~~ + +use clap::{Parser, Subcommand}; +use std::fs::{read, remove_file, write}; +use std::path::{Path, PathBuf}; +use walkdir::{DirEntry, WalkDir}; + +/// Name of tag file. +const CACHEDIR_TAG: &str = "CACHEDIR.TAG"; + +/// Prefix of contents of tag file. +const TAG: &str = "Signature: 8a477f597d28d172789f06886806bc55"; + +/// The main program. fn main() { - println!("Hello, world!"); + if let Err(e) = real_main() { + eprintln!("ERROR: {}", e); + std::process::exit(1); + } +} + +/// Fallible main program. +fn real_main() -> anyhow::Result<()> { + let args = Args::parse(); + match args.cmd { + Command::Find { dirs } => walk(&dirs, find)?, + Command::Tag { dirs } => walk(&dirs, tag)?, + Command::Untag { dirs } => walk(&dirs, untag)?, + Command::IsCache { dir } => is_cache(dir)?, + } + Ok(()) +} + +/// Command line arguments. +#[derive(Debug, Parser)] +struct Args { + #[clap(subcommand)] + cmd: Command, +} + +/// Subcommands. +#[derive(Debug, Subcommand)] +enum Command { + /// Find cache directories starting at given directories. + Find { dirs: Vec<PathBuf> }, + /// Add cache directory tag to given directories. + Tag { dirs: Vec<PathBuf> }, + /// Remove cache directory tag from given directories. + Untag { dirs: Vec<PathBuf> }, + /// Is the single given directory tagged as a cache directory? + IsCache { dir: PathBuf }, +} + +/// Is the given directory a cache? +fn is_cache(dir: PathBuf) -> Result<(), std::io::Error> { + let filename = dir.join(CACHEDIR_TAG); + if !is_cachedir_tag(&filename)? { + std::process::exit(1); + } + Ok(()) +} + +/// Is the given directory a cache? +fn find(entry: &DirEntry) -> Result<(), std::io::Error> { + let filename = tag_filename(entry); + if is_cachedir_tag(&filename)? { + println!("{}", entry.path().display()); + } + Ok(()) +} + +/// Add a tag file to the given directory. +fn tag(entry: &DirEntry) -> Result<(), std::io::Error> { + let filename = tag_filename(entry); + if !is_cachedir_tag(&filename)? { + write(filename, TAG)?; + } + Ok(()) +} + +/// Remove a tag file from the given directory. +fn untag(entry: &DirEntry) -> Result<(), std::io::Error> { + let filename = tag_filename(entry); + if is_cachedir_tag(&filename)? { + remove_file(filename)?; + } + Ok(()) +} + +/// Iterate over all directories, call given function for each. +fn walk<F>(dirs: &[PathBuf], mut func: F) -> Result<(), std::io::Error> +where + F: FnMut(&DirEntry) -> Result<(), std::io::Error>, +{ + for dir in dirs { + for entry in WalkDir::new(dir) { + let entry = entry?; + if entry.metadata()?.is_dir() { + func(&entry)?; + } + } + } + Ok(()) +} + +/// Is the given file a cache directory tag? +fn is_cachedir_tag(filename: &Path) -> Result<bool, std::io::Error> { + if filename.exists() { + let data = read(filename)?; + if data.len() >= TAG.len() { + Ok(&data[..TAG.len()] == TAG.as_bytes()) + } else { + Ok(false) + } + } else { + Ok(false) + } +} + +/// Return path to tag file in a directory. +fn tag_filename(entry: &DirEntry) -> PathBuf { + entry.path().join(CACHEDIR_TAG) } |