summaryrefslogtreecommitdiff
path: root/src/bin/cachedir.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/cachedir.rs')
-rw-r--r--src/bin/cachedir.rs134
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)
}