summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-03-31 18:30:23 +0300
committerLars Wirzenius <liw@liw.fi>2022-03-31 20:22:55 +0300
commit8643b23b7bdc8b56d5f87178871070739622c43e (patch)
tree20a3484dd0e0677b79bd0c57ce2979d03755c9c0 /src
parent4402c3a02e15a7d1538bad6df616bbef2983a600 (diff)
downloadcachedir-rs-8643b23b7bdc8b56d5f87178871070739622c43e.tar.gz
feat: implement find, tag, untag, is-tag
Sponsored-by: author
Diffstat (limited to 'src')
-rw-r--r--src/bin/cachedir.rs134
-rw-r--r--src/cachedir.rs3
2 files changed, 133 insertions, 4 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)
}
diff --git a/src/cachedir.rs b/src/cachedir.rs
deleted file mode 100644
index e7a11a9..0000000
--- a/src/cachedir.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- println!("Hello, world!");
-}