feat: add --update mode, --meta/--meta-plugin flags, streaming diff
- Add --update mode to modify tags and metadata for existing items by ID
- Add --meta key=value flag to set metadata during save/update
- Add --meta key (bare) to delete metadata keys or filter by existence
- Add --meta-plugin/-M name:{json} flag for plugin options via CLI
- Env meta plugin now uses options from --meta-plugin instead of only env vars
- Stream decompressed content to diff via /dev/fd pipes (no temp files)
- Wire --list-format CLI arg to settings (was parsed but ignored)
- Allow --info to accept tags (was restricted to numeric IDs only)
- Change DB meta filtering to HashMap<String, Option<String>> for exact match + key existence
- Fix fcntl error checking in diff pre_exec
- Fix README inaccuracies (delete by tag, nonexistent --digest flag, meta plugin key names)
This commit is contained in:
109
src/args.rs
109
src/args.rs
@@ -24,52 +24,56 @@ pub struct Args {
|
||||
/// Struct for mode-specific arguments, defining CLI flags for different operations.
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
pub struct ModeArgs {
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["get", "diff", "list", "delete", "info", "status"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["get", "diff", "list", "delete", "info", "update", "status"]))]
|
||||
#[arg(help("Save an item using any tags or metadata provided"))]
|
||||
pub save: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "diff", "list", "delete", "info", "status"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "diff", "list", "delete", "info", "update", "status"]))]
|
||||
#[arg(help(
|
||||
"Get an item either by it's ID or by a combination of matching tags and metatdata"
|
||||
))]
|
||||
pub get: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "list", "delete", "info", "status"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "list", "delete", "info", "update", "status"]))]
|
||||
#[arg(help("Show a diff between two items by ID"))]
|
||||
pub diff: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "get", "diff", "delete", "info", "status"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "get", "diff", "delete", "info", "update", "status"]))]
|
||||
#[arg(help("List items, filtering on tags or metadata if given"))]
|
||||
pub list: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "get", "diff", "list", "info", "status"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "get", "diff", "list", "info", "update", "status"]))]
|
||||
#[arg(help("Delete items either by ID or by matching tags"))]
|
||||
#[arg(requires = "ids_or_tags")]
|
||||
pub delete: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "get", "diff", "list", "delete", "status"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "get", "diff", "list", "delete", "update", "status"]))]
|
||||
#[arg(help(
|
||||
"Get an item either by it's ID or by a combination of matching tags and metatdata"
|
||||
))]
|
||||
pub info: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short('S'), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "server", "status_plugins"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short('u'), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "status"]))]
|
||||
#[arg(help("Update an item's tags and metadata by ID"))]
|
||||
pub update: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), short('S'), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "update", "server", "status_plugins"]))]
|
||||
#[arg(help("Show status of directories and supported compression algorithms"))]
|
||||
pub status: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "status", "server"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "update", "status", "server"]))]
|
||||
#[arg(help("Show available plugins and their configurations"))]
|
||||
pub status_plugins: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "status"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "update", "status"]))]
|
||||
#[arg(help("Start REST HTTP server"))]
|
||||
pub server: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "status", "server"]))]
|
||||
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "update", "status", "server"]))]
|
||||
#[arg(help("Generate default configuration and output to stdout"))]
|
||||
pub generate_config: bool,
|
||||
|
||||
#[arg(help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "status", "server", "generate_config"]))]
|
||||
#[arg(help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "update", "status", "server", "generate_config"]))]
|
||||
#[arg(help("Generate shell completion script (bash, zsh, fish, elvish, powershell)"))]
|
||||
pub generate_completion: Option<Shell>,
|
||||
|
||||
@@ -92,6 +96,78 @@ pub struct ModeArgs {
|
||||
pub server_key: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Represents a meta plugin argument with optional JSON config.
|
||||
///
|
||||
/// Parsed from `name` or `name:{"options":{...},"outputs":{...}}` syntax.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MetaPluginArg {
|
||||
pub name: String,
|
||||
pub options: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl FromStr for MetaPluginArg {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Some((name, json_str)) = s.split_once(':') {
|
||||
let value: serde_json::Value = serde_json::from_str(json_str)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid JSON for meta plugin '{}': {}", name, e))?;
|
||||
Ok(MetaPluginArg {
|
||||
name: name.to_string(),
|
||||
options: Some(value),
|
||||
})
|
||||
} else {
|
||||
Ok(MetaPluginArg {
|
||||
name: s.to_string(),
|
||||
options: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a metadata key-value argument.
|
||||
///
|
||||
/// Parsed from `key=value` (set) or `key` (delete/filter by existence).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MetaArg {
|
||||
/// Set metadata with a value.
|
||||
Set { key: String, value: String },
|
||||
/// Bare key without a value (delete in update mode, filter by existence otherwise).
|
||||
Key(String),
|
||||
}
|
||||
|
||||
impl MetaArg {
|
||||
/// Returns the key.
|
||||
pub fn key(&self) -> &str {
|
||||
match self {
|
||||
MetaArg::Set { key, .. } | MetaArg::Key(key) => key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value if this is a Set variant.
|
||||
pub fn value(&self) -> Option<&str> {
|
||||
match self {
|
||||
MetaArg::Set { value, .. } => Some(value),
|
||||
MetaArg::Key(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MetaArg {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Some((key, value)) = s.split_once('=') {
|
||||
Ok(MetaArg::Set {
|
||||
key: key.to_string(),
|
||||
value: value.to_string(),
|
||||
})
|
||||
} else {
|
||||
Ok(MetaArg::Key(s.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct for item-specific arguments, such as compression and plugins.
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
pub struct ItemArgs {
|
||||
@@ -102,11 +178,16 @@ pub struct ItemArgs {
|
||||
#[arg(
|
||||
help_heading("Item Options"),
|
||||
short('M'),
|
||||
long,
|
||||
long = "meta-plugin",
|
||||
value_parser = clap::value_parser!(MetaPluginArg),
|
||||
env("KEEP_META_PLUGINS")
|
||||
)]
|
||||
#[arg(help("Meta plugins to use when saving items"))]
|
||||
pub meta_plugins: Vec<String>,
|
||||
#[arg(help("Meta plugin to use (repeatable): name or name:{json}"))]
|
||||
pub meta_plugins: Vec<MetaPluginArg>,
|
||||
|
||||
#[arg(help_heading("Item Options"), long)]
|
||||
#[arg(help("Metadata key=value to set (or key to delete in --update)"))]
|
||||
pub meta: Vec<String>,
|
||||
|
||||
#[arg(help_heading("Item Options"), long, env("KEEP_FILTERS"))]
|
||||
#[arg(help("Filter string to apply to content when getting items"))]
|
||||
|
||||
Reference in New Issue
Block a user