diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a4a3376 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +# Extend `enumerate-ordered` (new major version!) +Modify `enumerate-ordered` to add more sort-by key options (instead of just times,) including a `printf()`-like "string format" key option. + + diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..1f31bec --- /dev/null +++ b/src/format.rs @@ -0,0 +1,78 @@ +//! User-defined arbitrary format string ordering key spec & replacement. +use super::*; +use std::{ + path::Path, +}; + +/// Used to identify and parse a token in a format string `%[OPT...]C` where `C` is the character used to idenfity the type's parser as valid for the sequence. +pub trait ParseToken { + /// The returned order-by object that can be reduced to a comparable `Cow`-like slice for comparing with other tokens in a `Format` string. + type Substitute: Ord; //TODO: What trait bounds should be on this? Is it okay to just be `Ord`??? Or must it be something like... `: PartialOrd` instead? (XXX: Is `Ord` enough here? Since the list index of `ParseToken`s will be the same for every path in any given `Format` instance... How can we do this non-polymorphically??? Am I forgetting how to write Rust now...???? ehh...) + + // Parsing stage (during arg-parse). + + /// Attempt to check if `substring` should match this parser. + /// The layout of `substring` will be in the format `/%.*(.)/` where group 1 is the character matched (and `end_char` is the extracted codepoint of it.) + /// # Returns + /// If it does, the matched portion of `substring` should be returned. (__NOTE__: The returned string **must** be *within* `substring`.) + fn visit<'matched>(&self, substring: &'matched str, end_char: char) -> Option<&'matched str>; + + /// Parse from a matched `visit()` call into the options of this instance of the parser type. + /// The options encoded in `substring` should set the state in `self` (if there is state.) + /// If parsing fails, the parser should return an error (which will be wrapped in context by the caller before reporting.) + fn parse(&mut self, substring: &str) -> eyre::Result<()>; + + // Substituting state (during work for individual paths) + + /// Create a substitute for this specific substring (identified in parsing stage above) for this specific path that can inserted into the `Format`'s list of reconstituted substrings to be compared with other outputs of the *same token index* for the *same substring* of a different path in the *same `Format` host*. + /// + /// # Panics + /// A panic should be used to signal failure, as this method should not fail in general. Any possible failures should be reported in `parse()` + // TOOD: Should we pass more then the `path`? I think when this `OrderBy` mode is selected, we should *not* do the `statx()` of the pathname itself before the comparison, since it may be irrelevent to the operation of this specific token, so. + fn substitute(&self, path: &Path) -> Self::Substitute; +} + +/// Options available for filename/path-like tokens (see below.) +/// +/// # Example +/// The token `%p` and `%n` can be modified with `PathTokenOpt` as: `%[S..[-]E]n` or `%[S..[-]E]p` to slice the name. +struct PathTokenOpt { + slice: Option<(usize, Option)>, +} + +impl ParseToken for PathTokenOpt { + //TODO: Impl visit, parse & substitute for `%[S..[-]E]C` +} + +enum Token<'source> { + /// Part is a string literal, no replacement. + Literal(&'source str), + /// Specifically for escaped characters (e.g. `"%%"` -> `'%'`) + Escaped(char), + /// Part is `%[S..E]n`: Filename (not path, just the file *name*.) + Filename(PathTokenOpt), + /// Part is `%[S..E]p`: *Full* path. + Path(PathTokenOpt), + + //TODO: Add rest... +} + +impl<'source> ParseToken for Token<'source> { + + //TODO: Add forwarding impl for each variant (where needed, like `Filename` & `Path`; for literals and escapes this can be handled here.) + // XXX: `visit()` can be implemented without forwarding since implementors like `PathTokenOpt` can be invariant wrt. the thing they're operating on. + + //XXX: How are we going to define `Self::Substitute` here??? I think maybe having `Token` as an enum is not going to work here... + //TODO: We might need to do this with `dyn ParseToken` and have `Format::tokens: Vec>` instead or something... +} + +/// A parsed format-string whose values are replaced with that of the target file when comparing via order. +/// +/// TODO: See if we can make it work as `<&[&dyn AsRef + PartialOrd + '_] as PartialOrd>` or something in implementation to avoid allocating where possible when substituting the parsed tokens (order each split part of the string) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Format<'input> { + /// The parsed tokens + tokens: Vec>, + + //TODO: How to design this type...? See above `ParseToken` trait & its impl for `Token<'source>`... +} diff --git a/src/main.rs b/src/main.rs index ae4c13d..548ead1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ mod args; mod order; mod work; mod walk; +mod format; fn init_logging() -> eyre::Result<()> { diff --git a/src/work.rs b/src/work.rs index aee3f50..9849b7a 100644 --- a/src/work.rs +++ b/src/work.rs @@ -10,7 +10,7 @@ use std::{ sync::Arc, }; -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] pub enum OrderBy { #[default] @@ -18,6 +18,9 @@ pub enum OrderBy ChangeTime, AccessTime, ModifiedTime, + + /// User-provided formattable string + Arbitrary(format::Format<'static>) // XXX: This doesn't fit in the derivations of this enum... What can we do about this? Look through the codebase again and see where (and how) *exactly* this is used. } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]