mirror of
https://github.com/PR0M3TH3AN/Marlin.git
synced 2025-09-08 23:28:44 +00:00
Format codebase with rustfmt
This commit is contained in:
@@ -28,7 +28,9 @@ fn generate_cheatsheet() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for (cmd_name_val, cmd_details_val) in cmds {
|
||||
let cmd_name = cmd_name_val.as_str().unwrap_or("");
|
||||
if let Value::Mapping(cmd_details) = cmd_details_val {
|
||||
if let Some(Value::Mapping(actions)) = cmd_details.get(&Value::String("actions".into())) {
|
||||
if let Some(Value::Mapping(actions)) =
|
||||
cmd_details.get(&Value::String("actions".into()))
|
||||
{
|
||||
for (action_name_val, action_body_val) in actions {
|
||||
let action_name = action_name_val.as_str().unwrap_or("");
|
||||
let flags = if let Value::Mapping(action_map) = action_body_val {
|
||||
@@ -45,7 +47,11 @@ fn generate_cheatsheet() -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
let flags_disp = if flags.is_empty() { "—" } else { &flags };
|
||||
table.push_str(&format!("| `{}` | {} |\n", format!("{} {}", cmd_name, action_name), flags_disp));
|
||||
table.push_str(&format!(
|
||||
"| `{}` | {} |\n",
|
||||
format!("{} {}", cmd_name, action_name),
|
||||
flags_disp
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,14 @@
|
||||
// src/cli.rs
|
||||
|
||||
pub mod link;
|
||||
pub mod annotate;
|
||||
pub mod coll;
|
||||
pub mod view;
|
||||
pub mod event;
|
||||
pub mod link;
|
||||
pub mod remind;
|
||||
pub mod state;
|
||||
pub mod task;
|
||||
pub mod remind;
|
||||
pub mod annotate;
|
||||
pub mod version;
|
||||
pub mod event;
|
||||
pub mod view;
|
||||
pub mod watch;
|
||||
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
@@ -77,9 +77,7 @@ pub enum Commands {
|
||||
Backup,
|
||||
|
||||
/// Restore from a backup file (overwrites current DB)
|
||||
Restore {
|
||||
backup_path: std::path::PathBuf,
|
||||
},
|
||||
Restore { backup_path: std::path::PathBuf },
|
||||
|
||||
/// Generate shell completions (hidden)
|
||||
#[command(hide = true)]
|
||||
@@ -132,6 +130,12 @@ pub enum Commands {
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum AttrCmd {
|
||||
Set { pattern: String, key: String, value: String },
|
||||
Ls { path: std::path::PathBuf },
|
||||
Set {
|
||||
pattern: String,
|
||||
key: String,
|
||||
value: String,
|
||||
},
|
||||
Ls {
|
||||
path: std::path::PathBuf,
|
||||
},
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// src/cli/annotate.rs
|
||||
use clap::{Subcommand, Args};
|
||||
use rusqlite::Connection;
|
||||
use crate::cli::Format;
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum AnnotateCmd {
|
||||
Add (ArgsAdd),
|
||||
Add(ArgsAdd),
|
||||
List(ArgsList),
|
||||
}
|
||||
|
||||
@@ -13,16 +13,20 @@ pub enum AnnotateCmd {
|
||||
pub struct ArgsAdd {
|
||||
pub file: String,
|
||||
pub note: String,
|
||||
#[arg(long)] pub range: Option<String>,
|
||||
#[arg(long)] pub highlight: bool,
|
||||
#[arg(long)]
|
||||
pub range: Option<String>,
|
||||
#[arg(long)]
|
||||
pub highlight: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsList { pub file_pattern: String }
|
||||
pub struct ArgsList {
|
||||
pub file_pattern: String,
|
||||
}
|
||||
|
||||
pub fn run(cmd: &AnnotateCmd, _conn: &mut Connection, _format: Format) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
AnnotateCmd::Add(a) => todo!("annotate add {:?}", a),
|
||||
AnnotateCmd::Add(a) => todo!("annotate add {:?}", a),
|
||||
AnnotateCmd::List(a) => todo!("annotate list {:?}", a),
|
||||
}
|
||||
}
|
||||
|
@@ -3,8 +3,8 @@
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
use crate::cli::Format; // local enum for text / json output
|
||||
use libmarlin::db; // core DB helpers from the library crate
|
||||
use crate::cli::Format; // local enum for text / json output
|
||||
use libmarlin::db; // core DB helpers from the library crate
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum CollCmd {
|
||||
@@ -36,11 +36,9 @@ pub struct ListArgs {
|
||||
///
|
||||
/// Returns the collection ID or an error if it doesn’t exist.
|
||||
fn lookup_collection_id(conn: &Connection, name: &str) -> anyhow::Result<i64> {
|
||||
conn.query_row(
|
||||
"SELECT id FROM collections WHERE name = ?1",
|
||||
[name],
|
||||
|r| r.get(0),
|
||||
)
|
||||
conn.query_row("SELECT id FROM collections WHERE name = ?1", [name], |r| {
|
||||
r.get(0)
|
||||
})
|
||||
.map_err(|_| anyhow::anyhow!("collection not found: {}", name))
|
||||
}
|
||||
|
||||
@@ -74,11 +72,7 @@ pub fn run(cmd: &CollCmd, conn: &mut Connection, fmt: Format) -> anyhow::Result<
|
||||
Format::Json => {
|
||||
#[cfg(feature = "json")]
|
||||
{
|
||||
println!(
|
||||
"{{\"collection\":\"{}\",\"added\":{}}}",
|
||||
a.name,
|
||||
ids.len()
|
||||
);
|
||||
println!("{{\"collection\":\"{}\",\"added\":{}}}", a.name, ids.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// src/cli/event.rs
|
||||
use clap::{Subcommand, Args};
|
||||
use rusqlite::Connection;
|
||||
use crate::cli::Format;
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum EventCmd {
|
||||
Add (ArgsAdd),
|
||||
Add(ArgsAdd),
|
||||
Timeline,
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct ArgsAdd {
|
||||
|
||||
pub fn run(cmd: &EventCmd, _conn: &mut Connection, _format: Format) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
EventCmd::Add(a) => todo!("event add {:?}", a),
|
||||
EventCmd::Timeline => todo!("event timeline"),
|
||||
EventCmd::Add(a) => todo!("event add {:?}", a),
|
||||
EventCmd::Timeline => todo!("event timeline"),
|
||||
}
|
||||
}
|
||||
|
@@ -1,15 +1,15 @@
|
||||
//! src/cli/link.rs – manage typed relationships between files
|
||||
|
||||
use clap::{Subcommand, Args};
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
use crate::cli::Format; // output selector
|
||||
use libmarlin::db; // ← switched from `crate::db`
|
||||
use crate::cli::Format; // output selector
|
||||
use libmarlin::db; // ← switched from `crate::db`
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum LinkCmd {
|
||||
Add(LinkArgs),
|
||||
Rm (LinkArgs),
|
||||
Rm(LinkArgs),
|
||||
List(ListArgs),
|
||||
Backlinks(BacklinksArgs),
|
||||
}
|
||||
@@ -17,7 +17,7 @@ pub enum LinkCmd {
|
||||
#[derive(Args, Debug)]
|
||||
pub struct LinkArgs {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub to: String,
|
||||
#[arg(long)]
|
||||
pub r#type: Option<String>,
|
||||
}
|
||||
@@ -70,7 +70,10 @@ pub fn run(cmd: &LinkCmd, conn: &mut Connection, format: Format) -> anyhow::Resu
|
||||
match format {
|
||||
Format::Text => {
|
||||
if let Some(t) = &args.r#type {
|
||||
println!("Removed link '{}' → '{}' [type='{}']", args.from, args.to, t);
|
||||
println!(
|
||||
"Removed link '{}' → '{}' [type='{}']",
|
||||
args.from, args.to, t
|
||||
);
|
||||
} else {
|
||||
println!("Removed link '{}' → '{}'", args.from, args.to);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// src/cli/remind.rs
|
||||
use clap::{Subcommand, Args};
|
||||
use rusqlite::Connection;
|
||||
use crate::cli::Format;
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum RemindCmd {
|
||||
@@ -11,8 +11,8 @@ pub enum RemindCmd {
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsSet {
|
||||
pub file_pattern: String,
|
||||
pub timestamp: String,
|
||||
pub message: String,
|
||||
pub timestamp: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub fn run(cmd: &RemindCmd, _conn: &mut Connection, _format: Format) -> anyhow::Result<()> {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// src/cli/state.rs
|
||||
use clap::{Subcommand, Args};
|
||||
use rusqlite::Connection;
|
||||
use crate::cli::Format;
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum StateCmd {
|
||||
@@ -11,16 +11,24 @@ pub enum StateCmd {
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsSet { pub file_pattern: String, pub new_state: String }
|
||||
pub struct ArgsSet {
|
||||
pub file_pattern: String,
|
||||
pub new_state: String,
|
||||
}
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsTrans { pub from_state: String, pub to_state: String }
|
||||
pub struct ArgsTrans {
|
||||
pub from_state: String,
|
||||
pub to_state: String,
|
||||
}
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsLog { pub file_pattern: String }
|
||||
pub struct ArgsLog {
|
||||
pub file_pattern: String,
|
||||
}
|
||||
|
||||
pub fn run(cmd: &StateCmd, _conn: &mut Connection, _format: Format) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
StateCmd::Set(a) => todo!("state set {:?}", a),
|
||||
StateCmd::TransitionsAdd(a)=> todo!("state transitions-add {:?}", a),
|
||||
StateCmd::Log(a) => todo!("state log {:?}", a),
|
||||
StateCmd::Set(a) => todo!("state set {:?}", a),
|
||||
StateCmd::TransitionsAdd(a) => todo!("state transitions-add {:?}", a),
|
||||
StateCmd::Log(a) => todo!("state log {:?}", a),
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// src/cli/task.rs
|
||||
use clap::{Subcommand, Args};
|
||||
use rusqlite::Connection;
|
||||
use crate::cli::Format;
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum TaskCmd {
|
||||
@@ -10,9 +10,14 @@ pub enum TaskCmd {
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsScan { pub directory: String }
|
||||
pub struct ArgsScan {
|
||||
pub directory: String,
|
||||
}
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsList { #[arg(long)] pub due_today: bool }
|
||||
pub struct ArgsList {
|
||||
#[arg(long)]
|
||||
pub due_today: bool,
|
||||
}
|
||||
|
||||
pub fn run(cmd: &TaskCmd, _conn: &mut Connection, _format: Format) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// src/cli/version.rs
|
||||
use clap::{Subcommand, Args};
|
||||
use rusqlite::Connection;
|
||||
use crate::cli::Format;
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum VersionCmd {
|
||||
@@ -9,7 +9,9 @@ pub enum VersionCmd {
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ArgsDiff { pub file: String }
|
||||
pub struct ArgsDiff {
|
||||
pub file: String,
|
||||
}
|
||||
|
||||
pub fn run(cmd: &VersionCmd, _conn: &mut Connection, _format: Format) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
|
@@ -6,8 +6,8 @@ use anyhow::Result;
|
||||
use clap::{Args, Subcommand};
|
||||
use rusqlite::Connection;
|
||||
|
||||
use crate::cli::Format; // output selector stays local
|
||||
use libmarlin::db; // ← path switched from `crate::db`
|
||||
use crate::cli::Format; // output selector stays local
|
||||
use libmarlin::db; // ← path switched from `crate::db`
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum ViewCmd {
|
||||
|
@@ -5,8 +5,8 @@ use clap::Subcommand;
|
||||
use libmarlin::watcher::{WatcherConfig, WatcherState};
|
||||
use rusqlite::Connection;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::info;
|
||||
@@ -30,15 +30,15 @@ pub enum WatchCmd {
|
||||
/// Directory to watch (defaults to current directory)
|
||||
#[arg(default_value = ".")]
|
||||
path: PathBuf,
|
||||
|
||||
|
||||
/// Debounce window in milliseconds (default: 100ms)
|
||||
#[arg(long, default_value = "100")]
|
||||
debounce_ms: u64,
|
||||
},
|
||||
|
||||
|
||||
/// Show status of currently active watcher
|
||||
Status,
|
||||
|
||||
|
||||
/// Stop the currently running watcher
|
||||
Stop,
|
||||
}
|
||||
@@ -60,7 +60,7 @@ pub fn run(cmd: &WatchCmd, _conn: &mut Connection, _format: super::Format) -> Re
|
||||
let status = watcher.status()?;
|
||||
info!("Watcher started. Press Ctrl+C to stop watching.");
|
||||
info!("Watching {} paths", status.watched_paths.len());
|
||||
|
||||
|
||||
let start_time = Instant::now();
|
||||
let mut last_status_time = Instant::now();
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
@@ -80,7 +80,7 @@ pub fn run(cmd: &WatchCmd, _conn: &mut Connection, _format: super::Format) -> Re
|
||||
}
|
||||
|
||||
// Corrected line: removed the extra closing parenthesis
|
||||
if last_status_time.elapsed() > Duration::from_secs(10) {
|
||||
if last_status_time.elapsed() > Duration::from_secs(10) {
|
||||
let uptime = start_time.elapsed();
|
||||
info!(
|
||||
"Watcher running for {}s, processed {} events, queue: {}, state: {:?}",
|
||||
@@ -104,7 +104,9 @@ pub fn run(cmd: &WatchCmd, _conn: &mut Connection, _format: super::Format) -> Re
|
||||
Ok(())
|
||||
}
|
||||
WatchCmd::Status => {
|
||||
info!("Status command: No active watcher process to query in this CLI invocation model.");
|
||||
info!(
|
||||
"Status command: No active watcher process to query in this CLI invocation model."
|
||||
);
|
||||
info!("To see live status, run 'marlin watch start' which prints periodic updates.");
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -9,14 +9,8 @@
|
||||
mod cli; // sub-command definitions and argument structs
|
||||
|
||||
/* ── shared modules re-exported from libmarlin ─────────────────── */
|
||||
use libmarlin::{
|
||||
config,
|
||||
db,
|
||||
logging,
|
||||
scan,
|
||||
utils::determine_scan_root,
|
||||
};
|
||||
use libmarlin::db::take_dirty;
|
||||
use libmarlin::{config, db, logging, scan, utils::determine_scan_root};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{CommandFactory, Parser};
|
||||
@@ -24,13 +18,7 @@ use clap_complete::generate;
|
||||
use glob::Pattern;
|
||||
use shellexpand;
|
||||
use shlex;
|
||||
use std::{
|
||||
env,
|
||||
fs,
|
||||
io,
|
||||
path::Path,
|
||||
process::Command,
|
||||
};
|
||||
use std::{env, fs, io, path::Path, process::Command};
|
||||
use tracing::{debug, error, info};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@@ -57,7 +45,7 @@ fn main() -> Result<()> {
|
||||
match &args.command {
|
||||
Commands::Init | Commands::Backup | Commands::Restore { .. } => {}
|
||||
_ => match db::backup(&cfg.db_path) {
|
||||
Ok(p) => info!("Pre-command auto-backup created at {}", p.display()),
|
||||
Ok(p) => info!("Pre-command auto-backup created at {}", p.display()),
|
||||
Err(e) => error!("Failed to create pre-command auto-backup: {e}"),
|
||||
},
|
||||
}
|
||||
@@ -72,9 +60,8 @@ fn main() -> Result<()> {
|
||||
/* ---- init ------------------------------------------------ */
|
||||
Commands::Init => {
|
||||
info!("Database initialised at {}", cfg.db_path.display());
|
||||
let cwd = env::current_dir().context("getting current directory")?;
|
||||
let count = scan::scan_directory(&mut conn, &cwd)
|
||||
.context("initial scan failed")?;
|
||||
let cwd = env::current_dir().context("getting current directory")?;
|
||||
let count = scan::scan_directory(&mut conn, &cwd).context("initial scan failed")?;
|
||||
info!("Initial scan complete – indexed/updated {count} files");
|
||||
}
|
||||
|
||||
@@ -89,11 +76,8 @@ fn main() -> Result<()> {
|
||||
if dirty {
|
||||
let dirty_ids = take_dirty(&conn)?;
|
||||
for id in dirty_ids {
|
||||
let path: String = conn.query_row(
|
||||
"SELECT path FROM files WHERE id = ?1",
|
||||
[id],
|
||||
|r| r.get(0),
|
||||
)?;
|
||||
let path: String =
|
||||
conn.query_row("SELECT path FROM files WHERE id = ?1", [id], |r| r.get(0))?;
|
||||
scan::scan_directory(&mut conn, Path::new(&path))?;
|
||||
}
|
||||
} else {
|
||||
@@ -104,18 +88,18 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
/* ---- tag / attribute / search --------------------------- */
|
||||
Commands::Tag { pattern, tag_path } =>
|
||||
apply_tag(&conn, &pattern, &tag_path)?,
|
||||
Commands::Tag { pattern, tag_path } => apply_tag(&conn, &pattern, &tag_path)?,
|
||||
|
||||
Commands::Attr { action } => match action {
|
||||
cli::AttrCmd::Set { pattern, key, value } =>
|
||||
attr_set(&conn, &pattern, &key, &value)?,
|
||||
cli::AttrCmd::Ls { path } =>
|
||||
attr_ls(&conn, &path)?,
|
||||
cli::AttrCmd::Set {
|
||||
pattern,
|
||||
key,
|
||||
value,
|
||||
} => attr_set(&conn, &pattern, &key, &value)?,
|
||||
cli::AttrCmd::Ls { path } => attr_ls(&conn, &path)?,
|
||||
},
|
||||
|
||||
Commands::Search { query, exec } =>
|
||||
run_search(&conn, &query, exec)?,
|
||||
Commands::Search { query, exec } => run_search(&conn, &query, exec)?,
|
||||
|
||||
/* ---- maintenance ---------------------------------------- */
|
||||
Commands::Backup => {
|
||||
@@ -125,9 +109,8 @@ fn main() -> Result<()> {
|
||||
|
||||
Commands::Restore { backup_path } => {
|
||||
drop(conn);
|
||||
db::restore(&backup_path, &cfg.db_path).with_context(|| {
|
||||
format!("Failed to restore DB from {}", backup_path.display())
|
||||
})?;
|
||||
db::restore(&backup_path, &cfg.db_path)
|
||||
.with_context(|| format!("Failed to restore DB from {}", backup_path.display()))?;
|
||||
println!("Restored DB from {}", backup_path.display());
|
||||
db::open(&cfg.db_path).with_context(|| {
|
||||
format!("Could not open restored DB at {}", cfg.db_path.display())
|
||||
@@ -136,15 +119,15 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
/* ---- passthrough sub-modules (some still stubs) ---------- */
|
||||
Commands::Link(link_cmd) => cli::link::run(&link_cmd, &mut conn, args.format)?,
|
||||
Commands::Coll(coll_cmd) => cli::coll::run(&coll_cmd, &mut conn, args.format)?,
|
||||
Commands::View(view_cmd) => cli::view::run(&view_cmd, &mut conn, args.format)?,
|
||||
Commands::Link(link_cmd) => cli::link::run(&link_cmd, &mut conn, args.format)?,
|
||||
Commands::Coll(coll_cmd) => cli::coll::run(&coll_cmd, &mut conn, args.format)?,
|
||||
Commands::View(view_cmd) => cli::view::run(&view_cmd, &mut conn, args.format)?,
|
||||
Commands::State(state_cmd) => cli::state::run(&state_cmd, &mut conn, args.format)?,
|
||||
Commands::Task(task_cmd) => cli::task::run(&task_cmd, &mut conn, args.format)?,
|
||||
Commands::Remind(rm_cmd) => cli::remind::run(&rm_cmd, &mut conn, args.format)?,
|
||||
Commands::Annotate(a_cmd) => cli::annotate::run(&a_cmd, &mut conn, args.format)?,
|
||||
Commands::Version(v_cmd) => cli::version::run(&v_cmd, &mut conn, args.format)?,
|
||||
Commands::Event(e_cmd) => cli::event::run(&e_cmd, &mut conn, args.format)?,
|
||||
Commands::Task(task_cmd) => cli::task::run(&task_cmd, &mut conn, args.format)?,
|
||||
Commands::Remind(rm_cmd) => cli::remind::run(&rm_cmd, &mut conn, args.format)?,
|
||||
Commands::Annotate(a_cmd) => cli::annotate::run(&a_cmd, &mut conn, args.format)?,
|
||||
Commands::Version(v_cmd) => cli::version::run(&v_cmd, &mut conn, args.format)?,
|
||||
Commands::Event(e_cmd) => cli::event::run(&e_cmd, &mut conn, args.format)?,
|
||||
Commands::Watch(watch_cmd) => cli::watch::run(&watch_cmd, &mut conn, args.format)?,
|
||||
}
|
||||
|
||||
@@ -160,22 +143,19 @@ fn apply_tag(conn: &rusqlite::Connection, pattern: &str, tag_path: &str) -> Resu
|
||||
let mut current = Some(leaf_tag_id);
|
||||
while let Some(id) = current {
|
||||
tag_ids.push(id);
|
||||
current = conn.query_row(
|
||||
"SELECT parent_id FROM tags WHERE id=?1",
|
||||
[id],
|
||||
|r| r.get::<_, Option<i64>>(0),
|
||||
)?;
|
||||
current = conn.query_row("SELECT parent_id FROM tags WHERE id=?1", [id], |r| {
|
||||
r.get::<_, Option<i64>>(0)
|
||||
})?;
|
||||
}
|
||||
|
||||
let expanded = shellexpand::tilde(pattern).into_owned();
|
||||
let pat = Pattern::new(&expanded)
|
||||
.with_context(|| format!("Invalid glob pattern `{expanded}`"))?;
|
||||
let pat =
|
||||
Pattern::new(&expanded).with_context(|| format!("Invalid glob pattern `{expanded}`"))?;
|
||||
let root = determine_scan_root(&expanded);
|
||||
|
||||
let mut stmt_file = conn.prepare("SELECT id FROM files WHERE path=?1")?;
|
||||
let mut stmt_insert = conn.prepare(
|
||||
"INSERT OR IGNORE INTO file_tags(file_id, tag_id) VALUES (?1, ?2)",
|
||||
)?;
|
||||
let mut stmt_file = conn.prepare("SELECT id FROM files WHERE path=?1")?;
|
||||
let mut stmt_insert =
|
||||
conn.prepare("INSERT OR IGNORE INTO file_tags(file_id, tag_id) VALUES (?1, ?2)")?;
|
||||
|
||||
let mut count = 0usize;
|
||||
for entry in WalkDir::new(&root)
|
||||
@@ -184,7 +164,9 @@ fn apply_tag(conn: &rusqlite::Connection, pattern: &str, tag_path: &str) -> Resu
|
||||
.filter(|e| e.file_type().is_file())
|
||||
{
|
||||
let p = entry.path().to_string_lossy();
|
||||
if !pat.matches(&p) { continue; }
|
||||
if !pat.matches(&p) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match stmt_file.query_row([p.as_ref()], |r| r.get::<_, i64>(0)) {
|
||||
Ok(fid) => {
|
||||
@@ -199,10 +181,10 @@ fn apply_tag(conn: &rusqlite::Connection, pattern: &str, tag_path: &str) -> Resu
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) =>
|
||||
error!(file=%p, "not indexed – run `marlin scan` first"),
|
||||
Err(e) =>
|
||||
error!(file=%p, error=%e, "could not lookup file ID"),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => {
|
||||
error!(file=%p, "not indexed – run `marlin scan` first")
|
||||
}
|
||||
Err(e) => error!(file=%p, error=%e, "could not lookup file ID"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +195,8 @@ fn apply_tag(conn: &rusqlite::Connection, pattern: &str, tag_path: &str) -> Resu
|
||||
/* ---------- ATTRIBUTES ---------- */
|
||||
fn attr_set(conn: &rusqlite::Connection, pattern: &str, key: &str, value: &str) -> Result<()> {
|
||||
let expanded = shellexpand::tilde(pattern).into_owned();
|
||||
let pat = Pattern::new(&expanded)
|
||||
.with_context(|| format!("Invalid glob pattern `{expanded}`"))?;
|
||||
let pat =
|
||||
Pattern::new(&expanded).with_context(|| format!("Invalid glob pattern `{expanded}`"))?;
|
||||
let root = determine_scan_root(&expanded);
|
||||
|
||||
let mut stmt_file = conn.prepare("SELECT id FROM files WHERE path=?1")?;
|
||||
@@ -226,7 +208,9 @@ fn attr_set(conn: &rusqlite::Connection, pattern: &str, key: &str, value: &str)
|
||||
.filter(|e| e.file_type().is_file())
|
||||
{
|
||||
let p = entry.path().to_string_lossy();
|
||||
if !pat.matches(&p) { continue; }
|
||||
if !pat.matches(&p) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match stmt_file.query_row([p.as_ref()], |r| r.get::<_, i64>(0)) {
|
||||
Ok(fid) => {
|
||||
@@ -234,10 +218,10 @@ fn attr_set(conn: &rusqlite::Connection, pattern: &str, key: &str, value: &str)
|
||||
info!(file=%p, key, value, "attr set");
|
||||
count += 1;
|
||||
}
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) =>
|
||||
error!(file=%p, "not indexed – run `marlin scan` first"),
|
||||
Err(e) =>
|
||||
error!(file=%p, error=%e, "could not lookup file ID"),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => {
|
||||
error!(file=%p, "not indexed – run `marlin scan` first")
|
||||
}
|
||||
Err(e) => error!(file=%p, error=%e, "could not lookup file ID"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,12 +231,11 @@ fn attr_set(conn: &rusqlite::Connection, pattern: &str, key: &str, value: &str)
|
||||
|
||||
fn attr_ls(conn: &rusqlite::Connection, path: &Path) -> Result<()> {
|
||||
let fid = db::file_id(conn, &path.to_string_lossy())?;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT key, value FROM attributes WHERE file_id=?1 ORDER BY key"
|
||||
)?;
|
||||
for row in stmt
|
||||
.query_map([fid], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)))?
|
||||
{
|
||||
let mut stmt =
|
||||
conn.prepare("SELECT key, value FROM attributes WHERE file_id=?1 ORDER BY key")?;
|
||||
for row in stmt.query_map([fid], |r| {
|
||||
Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?))
|
||||
})? {
|
||||
let (k, v) = row?;
|
||||
println!("{k} = {v}");
|
||||
}
|
||||
@@ -268,7 +251,9 @@ fn run_search(conn: &rusqlite::Connection, raw_query: &str, exec: Option<String>
|
||||
parts.push(tok);
|
||||
} else if let Some(tag) = tok.strip_prefix("tag:") {
|
||||
for (i, seg) in tag.split('/').filter(|s| !s.is_empty()).enumerate() {
|
||||
if i > 0 { parts.push("AND".into()); }
|
||||
if i > 0 {
|
||||
parts.push("AND".into());
|
||||
}
|
||||
parts.push(format!("tags_text:{}", escape_fts(seg)));
|
||||
}
|
||||
} else if let Some(attr) = tok.strip_prefix("attr:") {
|
||||
@@ -310,11 +295,11 @@ fn run_search(conn: &rusqlite::Connection, raw_query: &str, exec: Option<String>
|
||||
run_exec(&hits, &cmd_tpl)?;
|
||||
} else {
|
||||
if hits.is_empty() {
|
||||
eprintln!(
|
||||
"No matches for query: `{raw_query}` (FTS expr: `{fts_expr}`)"
|
||||
);
|
||||
eprintln!("No matches for query: `{raw_query}` (FTS expr: `{fts_expr}`)");
|
||||
} else {
|
||||
for p in hits { println!("{p}"); }
|
||||
for p in hits {
|
||||
println!("{p}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -333,7 +318,9 @@ fn naive_substring_search(conn: &rusqlite::Connection, term: &str) -> Result<Vec
|
||||
continue;
|
||||
}
|
||||
if let Ok(meta) = fs::metadata(&p) {
|
||||
if meta.len() > 65_536 { continue; }
|
||||
if meta.len() > 65_536 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Ok(body) = fs::read_to_string(&p) {
|
||||
if body.to_lowercase().contains(&needle) {
|
||||
@@ -369,7 +356,9 @@ fn run_exec(paths: &[String], cmd_tpl: &str) -> Result<()> {
|
||||
format!("{cmd_tpl} {quoted}")
|
||||
};
|
||||
if let Some(mut parts) = shlex::split(&final_cmd) {
|
||||
if parts.is_empty() { continue; }
|
||||
if parts.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let prog = parts.remove(0);
|
||||
let status = Command::new(&prog).args(parts).status()?;
|
||||
if !status.success() {
|
||||
@@ -393,9 +382,9 @@ fn escape_fts(term: &str) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{apply_tag, attr_set, escape_fts, naive_substring_search, run_exec};
|
||||
use assert_cmd::Command;
|
||||
use tempfile::tempdir;
|
||||
use super::{apply_tag, attr_set, naive_substring_search, run_exec, escape_fts};
|
||||
|
||||
#[test]
|
||||
fn test_help_command() {
|
||||
@@ -483,7 +472,10 @@ mod tests {
|
||||
cmd_scan.env("MARLIN_DB_PATH", &db_path);
|
||||
cmd_scan.arg("scan");
|
||||
cmd_scan.assert().success();
|
||||
assert!(backups_dir.exists(), "Backups directory should exist after scan");
|
||||
assert!(
|
||||
backups_dir.exists(),
|
||||
"Backups directory should exist after scan"
|
||||
);
|
||||
let backups: Vec<_> = backups_dir.read_dir().unwrap().collect();
|
||||
assert_eq!(backups.len(), 1, "One backup should be created for scan");
|
||||
}
|
||||
@@ -504,7 +496,11 @@ mod tests {
|
||||
let tmp = tempdir().unwrap();
|
||||
let mut cmd = Command::cargo_bin("marlin").unwrap();
|
||||
cmd.env("MARLIN_DB_PATH", tmp.path().join("index.db"));
|
||||
cmd.arg("event").arg("add").arg("file.txt").arg("2025-05-20").arg("desc");
|
||||
cmd.arg("event")
|
||||
.arg("add")
|
||||
.arg("file.txt")
|
||||
.arg("2025-05-20")
|
||||
.arg("desc");
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.stderr(predicates::str::contains("not yet implemented"));
|
||||
@@ -516,8 +512,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tagging_and_attributes_update_db() {
|
||||
use std::fs::File;
|
||||
use libmarlin::scan::scan_directory;
|
||||
use std::fs::File;
|
||||
|
||||
let tmp = tempdir().unwrap();
|
||||
let file_path = tmp.path().join("a.txt");
|
||||
@@ -567,7 +563,11 @@ mod tests {
|
||||
fs::write(&script, "#!/bin/sh\necho $1 >> $LOGFILE\n").unwrap();
|
||||
std::env::set_var("LOGFILE", &log);
|
||||
|
||||
run_exec(&[f1.to_string_lossy().to_string()], &format!("sh {} {{}}", script.display())).unwrap();
|
||||
run_exec(
|
||||
&[f1.to_string_lossy().to_string()],
|
||||
&format!("sh {} {{}}", script.display()),
|
||||
)
|
||||
.unwrap();
|
||||
let logged = fs::read_to_string(&log).unwrap();
|
||||
assert!(logged.contains("hello.txt"));
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
mod cli {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Format { Text, Json }
|
||||
pub enum Format {
|
||||
Text,
|
||||
Json,
|
||||
}
|
||||
}
|
||||
|
||||
#[path = "../src/cli/coll.rs"]
|
||||
@@ -11,20 +14,41 @@ use libmarlin::db;
|
||||
#[test]
|
||||
fn coll_run_creates_and_adds() {
|
||||
let mut conn = db::open(":memory:").unwrap();
|
||||
conn.execute("INSERT INTO files(path,size,mtime) VALUES ('a.txt',0,0)", []).unwrap();
|
||||
conn.execute("INSERT INTO files(path,size,mtime) VALUES ('b.txt',0,0)", []).unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO files(path,size,mtime) VALUES ('a.txt',0,0)",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO files(path,size,mtime) VALUES ('b.txt',0,0)",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let create = coll::CollCmd::Create(coll::CreateArgs{ name: "Set".into() });
|
||||
let create = coll::CollCmd::Create(coll::CreateArgs { name: "Set".into() });
|
||||
coll::run(&create, &mut conn, cli::Format::Text).unwrap();
|
||||
|
||||
let coll_id: i64 = conn.query_row("SELECT id FROM collections WHERE name='Set'", [], |r| r.get(0)).unwrap();
|
||||
let coll_id: i64 = conn
|
||||
.query_row("SELECT id FROM collections WHERE name='Set'", [], |r| {
|
||||
r.get(0)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let add = coll::CollCmd::Add(coll::AddArgs{ name: "Set".into(), file_pattern: "*.txt".into() });
|
||||
let add = coll::CollCmd::Add(coll::AddArgs {
|
||||
name: "Set".into(),
|
||||
file_pattern: "*.txt".into(),
|
||||
});
|
||||
coll::run(&add, &mut conn, cli::Format::Text).unwrap();
|
||||
|
||||
let cnt: i64 = conn.query_row("SELECT COUNT(*) FROM collection_files WHERE collection_id=?1", [coll_id], |r| r.get(0)).unwrap();
|
||||
let cnt: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM collection_files WHERE collection_id=?1",
|
||||
[coll_id],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cnt, 2);
|
||||
|
||||
let list = coll::CollCmd::List(coll::ListArgs{ name: "Set".into() });
|
||||
let list = coll::CollCmd::List(coll::ListArgs { name: "Set".into() });
|
||||
coll::run(&list, &mut conn, cli::Format::Text).unwrap();
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
mod cli {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Format { Text, Json }
|
||||
pub enum Format {
|
||||
Text,
|
||||
Json,
|
||||
}
|
||||
}
|
||||
|
||||
#[path = "../src/cli/link.rs"]
|
||||
@@ -11,27 +14,43 @@ use libmarlin::db;
|
||||
#[test]
|
||||
fn link_run_add_and_rm() {
|
||||
let mut conn = db::open(":memory:").unwrap();
|
||||
conn.execute("INSERT INTO files(path,size,mtime) VALUES ('foo.txt',0,0)", []).unwrap();
|
||||
conn.execute("INSERT INTO files(path,size,mtime) VALUES ('bar.txt',0,0)", []).unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO files(path,size,mtime) VALUES ('foo.txt',0,0)",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO files(path,size,mtime) VALUES ('bar.txt',0,0)",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let add = link::LinkCmd::Add(link::LinkArgs {
|
||||
from: "foo.txt".into(),
|
||||
to: "bar.txt".into(),
|
||||
to: "bar.txt".into(),
|
||||
r#type: None,
|
||||
});
|
||||
link::run(&add, &mut conn, cli::Format::Text).unwrap();
|
||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM links", [], |r| r.get(0)).unwrap();
|
||||
let count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM links", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
assert_eq!(count, 1);
|
||||
|
||||
let list = link::LinkCmd::List(link::ListArgs { pattern: "foo.txt".into(), direction: None, r#type: None });
|
||||
let list = link::LinkCmd::List(link::ListArgs {
|
||||
pattern: "foo.txt".into(),
|
||||
direction: None,
|
||||
r#type: None,
|
||||
});
|
||||
link::run(&list, &mut conn, cli::Format::Text).unwrap();
|
||||
|
||||
let rm = link::LinkCmd::Rm(link::LinkArgs {
|
||||
from: "foo.txt".into(),
|
||||
to: "bar.txt".into(),
|
||||
to: "bar.txt".into(),
|
||||
r#type: None,
|
||||
});
|
||||
link::run(&rm, &mut conn, cli::Format::Text).unwrap();
|
||||
let remaining: i64 = conn.query_row("SELECT COUNT(*) FROM links", [], |r| r.get(0)).unwrap();
|
||||
let remaining: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM links", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
assert_eq!(remaining, 0);
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
mod cli {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Format { Text, Json }
|
||||
pub enum Format {
|
||||
Text,
|
||||
Json,
|
||||
}
|
||||
}
|
||||
|
||||
#[path = "../src/cli/view.rs"]
|
||||
@@ -11,17 +14,30 @@ use libmarlin::db;
|
||||
#[test]
|
||||
fn view_run_save_and_exec() {
|
||||
let mut conn = db::open(":memory:").unwrap();
|
||||
conn.execute("INSERT INTO files(path,size,mtime) VALUES ('TODO.txt',0,0)", []).unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO files(path,size,mtime) VALUES ('TODO.txt',0,0)",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let save = view::ViewCmd::Save(view::ArgsSave { view_name: "tasks".into(), query: "TODO".into() });
|
||||
let save = view::ViewCmd::Save(view::ArgsSave {
|
||||
view_name: "tasks".into(),
|
||||
query: "TODO".into(),
|
||||
});
|
||||
view::run(&save, &mut conn, cli::Format::Text).unwrap();
|
||||
|
||||
let stored: String = conn.query_row("SELECT query FROM views WHERE name='tasks'", [], |r| r.get(0)).unwrap();
|
||||
let stored: String = conn
|
||||
.query_row("SELECT query FROM views WHERE name='tasks'", [], |r| {
|
||||
r.get(0)
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(stored, "TODO");
|
||||
|
||||
let list = view::ViewCmd::List;
|
||||
view::run(&list, &mut conn, cli::Format::Text).unwrap();
|
||||
|
||||
let exec = view::ViewCmd::Exec(view::ArgsExec { view_name: "tasks".into() });
|
||||
let exec = view::ViewCmd::Exec(view::ArgsExec {
|
||||
view_name: "tasks".into(),
|
||||
});
|
||||
view::run(&exec, &mut conn, cli::Format::Text).unwrap();
|
||||
}
|
||||
|
@@ -25,8 +25,8 @@ fn spawn_demo_tree(root: &PathBuf) {
|
||||
fs::write(root.join("Projects/Alpha/draft2.md"), "- [x] TODO foo\n").unwrap();
|
||||
fs::write(root.join("Projects/Beta/final.md"), "done\n").unwrap();
|
||||
fs::write(root.join("Projects/Gamma/TODO.txt"), "TODO bar\n").unwrap();
|
||||
fs::write(root.join("Logs/app.log"), "ERROR omg\n").unwrap();
|
||||
fs::write(root.join("Reports/Q1.pdf"), "PDF\n").unwrap();
|
||||
fs::write(root.join("Logs/app.log"), "ERROR omg\n").unwrap();
|
||||
fs::write(root.join("Reports/Q1.pdf"), "PDF\n").unwrap();
|
||||
}
|
||||
|
||||
/// Shorthand for “run and must succeed”.
|
||||
@@ -38,7 +38,7 @@ fn ok(cmd: &mut Command) -> assert_cmd::assert::Assert {
|
||||
fn full_cli_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/* ── 1 ░ sandbox ───────────────────────────────────────────── */
|
||||
|
||||
let tmp = tempdir()?; // wiped on drop
|
||||
let tmp = tempdir()?; // wiped on drop
|
||||
let demo_dir = tmp.path().join("marlin_demo");
|
||||
spawn_demo_tree(&demo_dir);
|
||||
|
||||
@@ -53,9 +53,7 @@ fn full_cli_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
/* ── 2 ░ init ( auto-scan cwd ) ───────────────────────────── */
|
||||
|
||||
ok(marlin()
|
||||
.current_dir(&demo_dir)
|
||||
.arg("init"));
|
||||
ok(marlin().current_dir(&demo_dir).arg("init"));
|
||||
|
||||
/* ── 3 ░ tag & attr demos ─────────────────────────────────── */
|
||||
|
||||
@@ -74,12 +72,14 @@ fn full_cli_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/* ── 4 ░ quick search sanity checks ───────────────────────── */
|
||||
|
||||
marlin()
|
||||
.arg("search").arg("TODO")
|
||||
.arg("search")
|
||||
.arg("TODO")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("TODO.txt"));
|
||||
|
||||
marlin()
|
||||
.arg("search").arg("attr:reviewed=yes")
|
||||
.arg("search")
|
||||
.arg("attr:reviewed=yes")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("Q1.pdf"));
|
||||
|
||||
@@ -92,31 +92,29 @@ fn full_cli_flow() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
ok(marlin().arg("scan").arg(&demo_dir));
|
||||
|
||||
ok(marlin()
|
||||
.arg("link").arg("add")
|
||||
.arg(&foo).arg(&bar));
|
||||
ok(marlin().arg("link").arg("add").arg(&foo).arg(&bar));
|
||||
|
||||
marlin()
|
||||
.arg("link").arg("backlinks").arg(&bar)
|
||||
.arg("link")
|
||||
.arg("backlinks")
|
||||
.arg(&bar)
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo.txt"));
|
||||
|
||||
/* ── 6 ░ backup → delete DB → restore ────────────────────── */
|
||||
|
||||
let backup_path = String::from_utf8(
|
||||
marlin().arg("backup").output()?.stdout
|
||||
)?;
|
||||
let backup_path = String::from_utf8(marlin().arg("backup").output()?.stdout)?;
|
||||
let backup_file = backup_path.split_whitespace().last().unwrap();
|
||||
|
||||
fs::remove_file(&db_path)?; // simulate corruption
|
||||
ok(marlin().arg("restore").arg(backup_file)); // restore
|
||||
fs::remove_file(&db_path)?; // simulate corruption
|
||||
ok(marlin().arg("restore").arg(backup_file)); // restore
|
||||
|
||||
// Search must still work afterwards
|
||||
marlin()
|
||||
.arg("search").arg("TODO")
|
||||
.arg("search")
|
||||
.arg("TODO")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("TODO.txt"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,11 @@ use util::marlin;
|
||||
fn link_non_indexed_should_fail() {
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
std::fs::write(tmp.path().join("foo.txt"), "").unwrap();
|
||||
std::fs::write(tmp.path().join("bar.txt"), "").unwrap();
|
||||
@@ -21,9 +25,10 @@ fn link_non_indexed_should_fail() {
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.args([
|
||||
"link", "add",
|
||||
"link",
|
||||
"add",
|
||||
&tmp.path().join("foo.txt").to_string_lossy(),
|
||||
&tmp.path().join("bar.txt").to_string_lossy()
|
||||
&tmp.path().join("bar.txt").to_string_lossy(),
|
||||
])
|
||||
.assert()
|
||||
.failure()
|
||||
@@ -35,16 +40,19 @@ fn link_non_indexed_should_fail() {
|
||||
#[test]
|
||||
fn attr_set_on_non_indexed_file_should_warn() {
|
||||
let tmp = tempdir().unwrap();
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let ghost = tmp.path().join("ghost.txt");
|
||||
std::fs::write(&ghost, "").unwrap();
|
||||
|
||||
marlin(&tmp)
|
||||
.args(["attr","set",
|
||||
&ghost.to_string_lossy(),"foo","bar"])
|
||||
.args(["attr", "set", &ghost.to_string_lossy(), "foo", "bar"])
|
||||
.assert()
|
||||
.success() // exits 0
|
||||
.success() // exits 0
|
||||
.stderr(str::contains("not indexed"));
|
||||
}
|
||||
|
||||
@@ -52,14 +60,18 @@ fn attr_set_on_non_indexed_file_should_warn() {
|
||||
|
||||
#[test]
|
||||
fn coll_add_unknown_collection_should_fail() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let tmp = tempdir().unwrap();
|
||||
let file = tmp.path().join("doc.txt");
|
||||
std::fs::write(&file, "").unwrap();
|
||||
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
marlin(&tmp)
|
||||
.args(["coll","add","nope",&file.to_string_lossy()])
|
||||
.args(["coll", "add", "nope", &file.to_string_lossy()])
|
||||
.assert()
|
||||
.failure();
|
||||
}
|
||||
@@ -68,7 +80,7 @@ fn coll_add_unknown_collection_should_fail() {
|
||||
|
||||
#[test]
|
||||
fn restore_with_nonexistent_backup_should_fail() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
// create an empty DB first
|
||||
marlin(&tmp).arg("init").assert().success();
|
||||
@@ -79,4 +91,3 @@ fn restore_with_nonexistent_backup_should_fail() {
|
||||
.failure()
|
||||
.stderr(str::contains("Failed to restore"));
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
mod util;
|
||||
use util::marlin;
|
||||
|
||||
use predicates::{prelude::*, str}; // brings `PredicateBooleanExt::and`
|
||||
use predicates::{prelude::*, str}; // brings `PredicateBooleanExt::and`
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
@@ -13,15 +13,20 @@ use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn tag_should_add_hierarchical_tag_and_search_finds_it() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let tmp = tempdir().unwrap();
|
||||
let file = tmp.path().join("foo.md");
|
||||
fs::write(&file, "# test\n").unwrap();
|
||||
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
marlin(&tmp)
|
||||
.args(["tag", file.to_str().unwrap(), "project/md"])
|
||||
.assert().success();
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
marlin(&tmp)
|
||||
.args(["search", "tag:project/md"])
|
||||
@@ -34,15 +39,20 @@ fn tag_should_add_hierarchical_tag_and_search_finds_it() {
|
||||
|
||||
#[test]
|
||||
fn attr_set_then_ls_roundtrip() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let tmp = tempdir().unwrap();
|
||||
let file = tmp.path().join("report.pdf");
|
||||
fs::write(&file, "%PDF-1.4\n").unwrap();
|
||||
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
marlin(&tmp)
|
||||
.args(["attr", "set", file.to_str().unwrap(), "reviewed", "yes"])
|
||||
.assert().success();
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
marlin(&tmp)
|
||||
.args(["attr", "ls", file.to_str().unwrap()])
|
||||
@@ -62,11 +72,21 @@ fn coll_create_add_and_list() {
|
||||
fs::write(&a, "").unwrap();
|
||||
fs::write(&b, "").unwrap();
|
||||
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
marlin(&tmp).args(["coll", "create", "Set"]).assert().success();
|
||||
marlin(&tmp)
|
||||
.args(["coll", "create", "Set"])
|
||||
.assert()
|
||||
.success();
|
||||
for f in [&a, &b] {
|
||||
marlin(&tmp).args(["coll", "add", "Set", f.to_str().unwrap()]).assert().success();
|
||||
marlin(&tmp)
|
||||
.args(["coll", "add", "Set", f.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
}
|
||||
|
||||
marlin(&tmp)
|
||||
@@ -80,15 +100,22 @@ fn coll_create_add_and_list() {
|
||||
|
||||
#[test]
|
||||
fn view_save_list_and_exec() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
let todo = tmp.path().join("TODO.txt");
|
||||
fs::write(&todo, "remember the milk\n").unwrap();
|
||||
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// save & list
|
||||
marlin(&tmp).args(["view", "save", "tasks", "milk"]).assert().success();
|
||||
marlin(&tmp)
|
||||
.args(["view", "save", "tasks", "milk"])
|
||||
.assert()
|
||||
.success();
|
||||
marlin(&tmp)
|
||||
.args(["view", "list"])
|
||||
.assert()
|
||||
@@ -118,24 +145,30 @@ fn link_add_rm_and_list() {
|
||||
let mc = || marlin(&tmp);
|
||||
|
||||
mc().current_dir(tmp.path()).arg("init").assert().success();
|
||||
mc().args(["scan", tmp.path().to_str().unwrap()]).assert().success();
|
||||
mc().args(["scan", tmp.path().to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// add
|
||||
mc().args(["link", "add", foo.to_str().unwrap(), bar.to_str().unwrap()])
|
||||
.assert().success();
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// list (outgoing default)
|
||||
mc().args(["link", "list", foo.to_str().unwrap()])
|
||||
.assert().success()
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(str::contains("foo.txt").and(str::contains("bar.txt")));
|
||||
|
||||
// remove
|
||||
mc().args(["link", "rm", foo.to_str().unwrap(), bar.to_str().unwrap()])
|
||||
.assert().success();
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// list now empty
|
||||
mc().args(["link", "list", foo.to_str().unwrap()])
|
||||
.assert().success()
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(str::is_empty());
|
||||
}
|
||||
|
||||
@@ -154,19 +187,24 @@ fn scan_with_multiple_paths_indexes_all() {
|
||||
fs::write(&f1, "").unwrap();
|
||||
fs::write(&f2, "").unwrap();
|
||||
|
||||
marlin(&tmp).current_dir(tmp.path()).arg("init").assert().success();
|
||||
marlin(&tmp)
|
||||
.current_dir(tmp.path())
|
||||
.arg("init")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// multi-path scan
|
||||
marlin(&tmp)
|
||||
.args(["scan", dir_a.to_str().unwrap(), dir_b.to_str().unwrap()])
|
||||
.assert().success();
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// both files findable
|
||||
for term in ["one.txt", "two.txt"] {
|
||||
marlin(&tmp).args(["search", term])
|
||||
marlin(&tmp)
|
||||
.args(["search", term])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(str::contains(term));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
//! tests/util.rs
|
||||
//! Small helpers shared across integration tests.
|
||||
|
||||
use assert_cmd::Command;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tempfile::TempDir;
|
||||
use assert_cmd::Command;
|
||||
/// Absolute path to the freshly-built `marlin` binary.
|
||||
pub fn bin() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_BIN_EXE_marlin"))
|
||||
|
@@ -2,11 +2,11 @@ use std::thread;
|
||||
use std::time::Duration;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use marlin_cli::cli::{watch, Format};
|
||||
use marlin_cli::cli::watch::WatchCmd;
|
||||
use libc;
|
||||
use libmarlin::watcher::WatcherState;
|
||||
use libmarlin::{self as marlin, db};
|
||||
use libc;
|
||||
use marlin_cli::cli::watch::WatchCmd;
|
||||
use marlin_cli::cli::{watch, Format};
|
||||
|
||||
#[test]
|
||||
fn watch_start_and_stop_quickly() {
|
||||
@@ -20,7 +20,10 @@ fn watch_start_and_stop_quickly() {
|
||||
let mut conn = db::open(&db_path).unwrap();
|
||||
|
||||
let path = tmp.path().to_path_buf();
|
||||
let cmd = WatchCmd::Start { path: path.clone(), debounce_ms: 50 };
|
||||
let cmd = WatchCmd::Start {
|
||||
path: path.clone(),
|
||||
debounce_ms: 50,
|
||||
};
|
||||
|
||||
// send SIGINT shortly after watcher starts
|
||||
let t = thread::spawn(|| {
|
||||
|
Reference in New Issue
Block a user