mirror of
https://github.com/PR0M3TH3AN/Marlin.git
synced 2025-09-08 23:28:44 +00:00
updated CLI
This commit is contained in:
51
README.md
51
README.md
@@ -145,26 +145,26 @@ Paste & run each block in your terminal.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 0 Prepare & build
|
### 0 Prepare, build & install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone or cd into your Marlin repo
|
|
||||||
cd ~/Documents/GitHub/Marlin
|
cd ~/Documents/GitHub/Marlin
|
||||||
|
|
||||||
# Build the release binary
|
|
||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
|
||||||
|
sudo install -Dm755 target/release/marlin /usr/local/bin/marlin
|
||||||
|
````
|
||||||
|
|
||||||
|
> Now `marlin` is available everywhere.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1 Install on your PATH
|
### 1 Enable shell completion (optional but handy)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo install -Dm755 target/release/marlin /usr/local/bin/marlin
|
marlin completions bash > ~/.config/bash_completion.d/marlin
|
||||||
|
# or for zsh / fish, similarly...
|
||||||
```
|
```
|
||||||
|
|
||||||
> Now `marlin` is available everywhere.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2 Prepare a clean demo directory
|
### 2 Prepare a clean demo directory
|
||||||
@@ -184,16 +184,14 @@ printf "fake jpg\n" > ~/marlin_demo/Media/Photos/vacation.jpg
|
|||||||
### 3 Initialize & index files
|
### 3 Initialize & index files
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Use --verbose if you want full debug traces:
|
|
||||||
marlin init
|
marlin init
|
||||||
marlin scan ~/marlin_demo
|
marlin scan ~/marlin_demo
|
||||||
|
|
||||||
# or, to see every path tested:
|
# show every path tested:
|
||||||
marlin --verbose init
|
|
||||||
marlin --verbose scan ~/marlin_demo
|
marlin --verbose scan ~/marlin_demo
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Tip:** Rerun `marlin scan` after you add/remove/modify files; only changed files get re-indexed.
|
> Only changed files get re-indexed on subsequent runs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -204,11 +202,10 @@ marlin --verbose scan ~/marlin_demo
|
|||||||
marlin tag "~/marlin_demo/Projects/Alpha/**/*" project/alpha
|
marlin tag "~/marlin_demo/Projects/Alpha/**/*" project/alpha
|
||||||
|
|
||||||
# Mark all PDFs as reviewed
|
# Mark all PDFs as reviewed
|
||||||
marlin attr set "~/marlin_demo/**/*.pdf" reviewed yes
|
marlin attr set "~/marlin_demo/**/*.pdf" reviewed=yes
|
||||||
|
|
||||||
# (or with debug)
|
# Output as JSON instead:
|
||||||
marlin --verbose tag "~/marlin_demo/Projects/Alpha/**/*" project/alpha
|
marlin --format=json attr set "~/marlin_demo/**/*.pdf" reviewed=yes
|
||||||
marlin --verbose attr set "~/marlin_demo/**/*.pdf" reviewed yes
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -219,38 +216,34 @@ marlin --verbose attr set "~/marlin_demo/**/*.pdf" reviewed yes
|
|||||||
# By tag or filename
|
# By tag or filename
|
||||||
marlin search alpha
|
marlin search alpha
|
||||||
|
|
||||||
# Combined terms (AND across path+attrs)
|
# Combined terms:
|
||||||
marlin search "reviewed AND pdf"
|
marlin search "reviewed AND pdf"
|
||||||
|
|
||||||
# Run a command on each hit
|
# Run a command on each hit:
|
||||||
marlin search reviewed --exec "echo HIT → {}"
|
marlin search reviewed --exec 'echo HIT → {}'
|
||||||
|
|
||||||
# If things aren’t matching, add --verbose to see the underlying FTS query:
|
|
||||||
marlin --verbose search "reviewed AND pdf"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> `{}` in `--exec` is replaced with each file’s path.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 6 Backup & restore
|
### 6 Backup & restore
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Snapshot and store its name
|
# Snapshot
|
||||||
snap=$(marlin backup | awk '{print $NF}')
|
snap=$(marlin backup | awk '{print $NF}')
|
||||||
|
|
||||||
# Simulate data loss
|
# Simulate loss
|
||||||
rm ~/.local/share/marlin/index.db
|
rm ~/.local/share/marlin/index.db
|
||||||
|
|
||||||
# Restore instantly
|
# Restore
|
||||||
marlin restore "$snap"
|
marlin restore "$snap"
|
||||||
|
|
||||||
# Verify your files still show up
|
# Verify
|
||||||
marlin search reviewed
|
marlin search reviewed
|
||||||
```
|
```
|
||||||
|
|
||||||
> Backups live under `~/.local/share/marlin/backups` by default.
|
> Backups live under `~/.local/share/marlin/backups` by default.
|
||||||
|
|
||||||
|
|
||||||
##### What you just exercised
|
##### What you just exercised
|
||||||
|
|
||||||
| Command | Purpose |
|
| Command | Purpose |
|
||||||
|
81
src/cli.rs
81
src/cli.rs
@@ -1,15 +1,36 @@
|
|||||||
// src/cli.rs
|
// src/cli.rs
|
||||||
use std::path::PathBuf;
|
pub mod link;
|
||||||
use clap::{Parser, Subcommand};
|
pub mod coll;
|
||||||
|
pub mod view;
|
||||||
|
pub mod state;
|
||||||
|
pub mod task;
|
||||||
|
pub mod remind;
|
||||||
|
pub mod annotate;
|
||||||
|
pub mod version;
|
||||||
|
pub mod event;
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand, ArgEnum, Args, CommandFactory};
|
||||||
|
use clap_complete::Shell;
|
||||||
|
|
||||||
|
/// Output format for commands.
|
||||||
|
#[derive(ArgEnum, Clone, Copy, Debug)]
|
||||||
|
pub enum Format {
|
||||||
|
Text,
|
||||||
|
Json,
|
||||||
|
}
|
||||||
|
|
||||||
/// Marlin – metadata-driven file explorer (CLI utilities)
|
/// Marlin – metadata-driven file explorer (CLI utilities)
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about, propagate_version = true)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Enable debug logging and extra output
|
/// Enable debug logging and extra output
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
|
|
||||||
|
/// Output format (text or JSON)
|
||||||
|
#[arg(long, default_value = "text", value_enum, global = true)]
|
||||||
|
pub format: Format,
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
@@ -21,12 +42,15 @@ pub enum Commands {
|
|||||||
|
|
||||||
/// Scan one or more directories and populate the file index
|
/// Scan one or more directories and populate the file index
|
||||||
Scan {
|
Scan {
|
||||||
paths: Vec<PathBuf>,
|
/// Directories to scan (defaults to cwd)
|
||||||
|
paths: Vec<std::path::PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Tag files matching a glob pattern (hierarchical tags use `/`)
|
/// Tag files matching a glob pattern (hierarchical tags use `/`)
|
||||||
Tag {
|
Tag {
|
||||||
|
/// Glob or path pattern
|
||||||
pattern: String,
|
pattern: String,
|
||||||
|
/// Hierarchical tag name (`foo/bar`)
|
||||||
tag_path: String,
|
tag_path: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -46,14 +70,57 @@ pub enum Commands {
|
|||||||
/// Create a timestamped backup of the database
|
/// Create a timestamped backup of the database
|
||||||
Backup,
|
Backup,
|
||||||
|
|
||||||
/// Restore from a backup file (over-writes current DB)
|
/// Restore from a backup file (overwrites current DB)
|
||||||
Restore {
|
Restore {
|
||||||
backup_path: PathBuf,
|
backup_path: std::path::PathBuf,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Generate shell completions (hidden)
|
||||||
|
#[command(hide = true)]
|
||||||
|
Completions {
|
||||||
|
#[arg(value_enum)]
|
||||||
|
shell: Shell,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// File-to-file links
|
||||||
|
#[command(subcommand)]
|
||||||
|
Link { cmd: link::LinkCmd },
|
||||||
|
|
||||||
|
/// Collections (groups) of files
|
||||||
|
#[command(subcommand)]
|
||||||
|
Coll { cmd: coll::CollCmd },
|
||||||
|
|
||||||
|
/// Smart views (saved queries)
|
||||||
|
#[command(subcommand)]
|
||||||
|
View { cmd: view::ViewCmd },
|
||||||
|
|
||||||
|
/// Workflow states on files
|
||||||
|
#[command(subcommand)]
|
||||||
|
State { cmd: state::StateCmd },
|
||||||
|
|
||||||
|
/// TODO/tasks management
|
||||||
|
#[command(subcommand)]
|
||||||
|
Task { cmd: task::TaskCmd },
|
||||||
|
|
||||||
|
/// Reminders on files
|
||||||
|
#[command(subcommand)]
|
||||||
|
Remind { cmd: remind::RemindCmd },
|
||||||
|
|
||||||
|
/// File annotations and highlights
|
||||||
|
#[command(subcommand)]
|
||||||
|
Annotate { cmd: annotate::AnnotateCmd },
|
||||||
|
|
||||||
|
/// Version diffs
|
||||||
|
#[command(subcommand)]
|
||||||
|
Version { cmd: version::VersionCmd },
|
||||||
|
|
||||||
|
/// Calendar events & timelines
|
||||||
|
#[command(subcommand)]
|
||||||
|
Event { cmd: event::EventCmd },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum AttrCmd {
|
pub enum AttrCmd {
|
||||||
Set { pattern: String, key: String, value: String },
|
Set { pattern: String, key: String, value: String },
|
||||||
Ls { path: PathBuf },
|
Ls { path: std::path::PathBuf },
|
||||||
}
|
}
|
||||||
|
28
src/cli/annotate.rs
Normal file
28
src/cli/annotate.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// src/cli/annotate.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum AnnotateCmd {
|
||||||
|
Add (ArgsAdd),
|
||||||
|
List(ArgsList),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsAdd {
|
||||||
|
pub file: String,
|
||||||
|
pub note: String,
|
||||||
|
#[arg(long)] pub range: Option<String>,
|
||||||
|
#[arg(long)] pub highlight: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
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::List(a) => todo!("annotate list {:?}", a),
|
||||||
|
}
|
||||||
|
}
|
26
src/cli/coll.rs
Normal file
26
src/cli/coll.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// src/cli/coll.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum CollCmd {
|
||||||
|
Create(CreateArgs),
|
||||||
|
Add (AddArgs),
|
||||||
|
List (ListArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct CreateArgs { pub name: String }
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct AddArgs { pub name: String, pub file_pattern: String }
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ListArgs { pub name: String }
|
||||||
|
|
||||||
|
pub fn run(cmd: &CollCmd, conn: &mut Connection, format: Format) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
CollCmd::Create(a) => todo!("coll create {:?}", a),
|
||||||
|
CollCmd::Add(a) => todo!("coll add {:?}", a),
|
||||||
|
CollCmd::List(a) => todo!("coll list {:?}", a),
|
||||||
|
}
|
||||||
|
}
|
81
src/cli/commands.yaml
Normal file
81
src/cli/commands.yaml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# cli/commands.yaml
|
||||||
|
# Philosophy: one canonical spec stops drift between docs & code.
|
||||||
|
link:
|
||||||
|
description: "Manage typed relationships between files"
|
||||||
|
actions:
|
||||||
|
add:
|
||||||
|
args: [from, to]
|
||||||
|
flags: ["--type"]
|
||||||
|
rm:
|
||||||
|
args: [from, to]
|
||||||
|
flags: ["--type"]
|
||||||
|
list:
|
||||||
|
args: [pattern]
|
||||||
|
flags: ["--direction", "--type"]
|
||||||
|
backlinks:
|
||||||
|
args: [pattern]
|
||||||
|
|
||||||
|
coll:
|
||||||
|
description: "Manage named collections of files"
|
||||||
|
actions:
|
||||||
|
create:
|
||||||
|
args: [name]
|
||||||
|
add:
|
||||||
|
args: [name, file_pattern]
|
||||||
|
list:
|
||||||
|
args: [name]
|
||||||
|
|
||||||
|
view:
|
||||||
|
description: "Save and use smart views (saved queries)"
|
||||||
|
actions:
|
||||||
|
save:
|
||||||
|
args: [view_name, query]
|
||||||
|
list: {}
|
||||||
|
exec:
|
||||||
|
args: [view_name]
|
||||||
|
|
||||||
|
state:
|
||||||
|
description: "Track workflow states on files"
|
||||||
|
actions:
|
||||||
|
set:
|
||||||
|
args: [file_pattern, new_state]
|
||||||
|
transitions-add:
|
||||||
|
args: [from_state, to_state]
|
||||||
|
log:
|
||||||
|
args: [file_pattern]
|
||||||
|
|
||||||
|
task:
|
||||||
|
description: "Extract TODOs and manage tasks"
|
||||||
|
actions:
|
||||||
|
scan:
|
||||||
|
args: [directory]
|
||||||
|
list:
|
||||||
|
flags: ["--due-today"]
|
||||||
|
|
||||||
|
remind:
|
||||||
|
description: "Attach reminders to files"
|
||||||
|
actions:
|
||||||
|
set:
|
||||||
|
args: [file_pattern, timestamp, message]
|
||||||
|
|
||||||
|
annotate:
|
||||||
|
description: "Add notes or highlights to files"
|
||||||
|
actions:
|
||||||
|
add:
|
||||||
|
args: [file, note]
|
||||||
|
flags: ["--range", "--highlight"]
|
||||||
|
list:
|
||||||
|
args: [file_pattern]
|
||||||
|
|
||||||
|
version:
|
||||||
|
description: "Versioning and diffs"
|
||||||
|
actions:
|
||||||
|
diff:
|
||||||
|
args: [file]
|
||||||
|
|
||||||
|
event:
|
||||||
|
description: "Link files to dates/events"
|
||||||
|
actions:
|
||||||
|
add:
|
||||||
|
args: [file, date, description]
|
||||||
|
timeline: {}
|
24
src/cli/event.rs
Normal file
24
src/cli/event.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// src/cli/event.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum EventCmd {
|
||||||
|
Add (ArgsAdd),
|
||||||
|
Timeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsAdd {
|
||||||
|
pub file: String,
|
||||||
|
pub date: String,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
43
src/cli/link.rs
Normal file
43
src/cli/link.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// src/cli/link.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum LinkCmd {
|
||||||
|
Add(LinkArgs),
|
||||||
|
Rm (LinkArgs),
|
||||||
|
List(ListArgs),
|
||||||
|
Backlinks(BacklinksArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct LinkArgs {
|
||||||
|
pub from: String,
|
||||||
|
pub to: String,
|
||||||
|
#[arg(long)]
|
||||||
|
pub r#type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ListArgs {
|
||||||
|
pub pattern: String,
|
||||||
|
#[arg(long)]
|
||||||
|
pub direction: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub r#type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct BacklinksArgs {
|
||||||
|
pub pattern: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(cmd: &LinkCmd, conn: &mut Connection, format: Format) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
LinkCmd::Add(args) => todo!("link add {:?}", args),
|
||||||
|
LinkCmd::Rm(args) => todo!("link rm {:?}", args),
|
||||||
|
LinkCmd::List(args) => todo!("link list {:?}", args),
|
||||||
|
LinkCmd::Backlinks(args) => todo!("link backlinks {:?}", args),
|
||||||
|
}
|
||||||
|
}
|
22
src/cli/remind.rs
Normal file
22
src/cli/remind.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// src/cli/remind.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum RemindCmd {
|
||||||
|
Set(ArgsSet),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsSet {
|
||||||
|
pub file_pattern: String,
|
||||||
|
pub timestamp: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(cmd: &RemindCmd, conn: &mut Connection, format: Format) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
RemindCmd::Set(a) => todo!("remind set {:?}", a),
|
||||||
|
}
|
||||||
|
}
|
26
src/cli/state.rs
Normal file
26
src/cli/state.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// src/cli/state.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum StateCmd {
|
||||||
|
Set(ArgsSet),
|
||||||
|
TransitionsAdd(ArgsTrans),
|
||||||
|
Log(ArgsLog),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
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 }
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
22
src/cli/task.rs
Normal file
22
src/cli/task.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// src/cli/task.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum TaskCmd {
|
||||||
|
Scan(ArgsScan),
|
||||||
|
List(ArgsList),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsScan { pub directory: String }
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsList { #[arg(long)] pub due_today: bool }
|
||||||
|
|
||||||
|
pub fn run(cmd: &TaskCmd, conn: &mut Connection, format: Format) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
TaskCmd::Scan(a) => todo!("task scan {:?}", a),
|
||||||
|
TaskCmd::List(a) => todo!("task list {:?}", a),
|
||||||
|
}
|
||||||
|
}
|
18
src/cli/version.rs
Normal file
18
src/cli/version.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// src/cli/version.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum VersionCmd {
|
||||||
|
Diff(ArgsDiff),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsDiff { pub file: String }
|
||||||
|
|
||||||
|
pub fn run(cmd: &VersionCmd, conn: &mut Connection, format: Format) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
VersionCmd::Diff(a) => todo!("version diff {:?}", a),
|
||||||
|
}
|
||||||
|
}
|
24
src/cli/view.rs
Normal file
24
src/cli/view.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// src/cli/view.rs
|
||||||
|
use clap::{Subcommand, Args};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::cli::Format;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum ViewCmd {
|
||||||
|
Save(ArgsSave),
|
||||||
|
List,
|
||||||
|
Exec(ArgsExec),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsSave { pub view_name: String, pub query: String }
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ArgsExec { pub view_name: String }
|
||||||
|
|
||||||
|
pub fn run(cmd: &ViewCmd, conn: &mut Connection, format: Format) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
ViewCmd::Save(a) => todo!("view save {:?}", a),
|
||||||
|
ViewCmd::List => todo!("view list"),
|
||||||
|
ViewCmd::Exec(a)=> todo!("view exec {:?}", a),
|
||||||
|
}
|
||||||
|
}
|
54
src/main.rs
54
src/main.rs
@@ -6,48 +6,55 @@ mod logging;
|
|||||||
mod scan;
|
mod scan;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
|
use clap_complete::{generate, Shell};
|
||||||
use glob::Pattern;
|
use glob::Pattern;
|
||||||
use rusqlite::params;
|
use rusqlite::params;
|
||||||
use shellexpand;
|
use shellexpand;
|
||||||
use shlex;
|
use shlex;
|
||||||
use std::{env, path::PathBuf, process::Command};
|
use std::{env, io, path::PathBuf, process::Command};
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use cli::{AttrCmd, Cli, Commands};
|
use cli::{Cli, Commands, Format};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// Parse CLI and bootstrap logging
|
// Parse CLI and bootstrap logging
|
||||||
let args = Cli::parse();
|
let mut args = Cli::parse();
|
||||||
if args.verbose {
|
if args.verbose {
|
||||||
// switch on debug‐level logs
|
|
||||||
env::set_var("RUST_LOG", "debug");
|
env::set_var("RUST_LOG", "debug");
|
||||||
}
|
}
|
||||||
logging::init();
|
logging::init();
|
||||||
|
|
||||||
|
// Handle shell completions as a hidden command
|
||||||
|
if let Commands::Completions { shell } = args.command {
|
||||||
|
let mut cmd = Cli::command();
|
||||||
|
generate(shell, &mut cmd, "marlin", &mut io::stdout());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let cfg = config::Config::load()?;
|
let cfg = config::Config::load()?;
|
||||||
|
|
||||||
// Backup before any non-init, non-backup/restore command
|
// Backup before any non-init, non-backup/restore command
|
||||||
if !matches!(args.command, Commands::Init | Commands::Backup | Commands::Restore { .. }) {
|
match &args.command {
|
||||||
match db::backup(&cfg.db_path) {
|
Commands::Init | Commands::Backup | Commands::Restore { .. } => {}
|
||||||
|
_ => match db::backup(&cfg.db_path) {
|
||||||
Ok(path) => info!("Pre-command auto-backup created at {}", path.display()),
|
Ok(path) => info!("Pre-command auto-backup created at {}", path.display()),
|
||||||
Err(e) => error!("Failed to create pre-command auto-backup: {}", e),
|
Err(e) => error!("Failed to create pre-command auto-backup: {}", e),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open (and migrate) the DB
|
// Open (and migrate) the DB
|
||||||
let mut conn = db::open(&cfg.db_path)?;
|
let mut conn = db::open(&cfg.db_path)?;
|
||||||
|
|
||||||
|
// Dispatch
|
||||||
match args.command {
|
match args.command {
|
||||||
Commands::Init => {
|
Commands::Init => {
|
||||||
info!("Database initialised at {}", cfg.db_path.display());
|
info!("Database initialised at {}", cfg.db_path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Scan { paths } => {
|
Commands::Scan { paths } => {
|
||||||
// if none given, default to current dir
|
|
||||||
let scan_paths = if paths.is_empty() {
|
let scan_paths = if paths.is_empty() {
|
||||||
vec![env::current_dir()?]
|
vec![std::env::current_dir()?]
|
||||||
} else {
|
} else {
|
||||||
paths
|
paths
|
||||||
};
|
};
|
||||||
@@ -55,38 +62,43 @@ fn main() -> Result<()> {
|
|||||||
scan::scan_directory(&mut conn, &p)?;
|
scan::scan_directory(&mut conn, &p)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Tag { pattern, tag_path } => {
|
Commands::Tag { pattern, tag_path } => {
|
||||||
apply_tag(&conn, &pattern, &tag_path)?;
|
apply_tag(&conn, &pattern, &tag_path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Attr { action } => match action {
|
Commands::Attr { action } => match action {
|
||||||
AttrCmd::Set { pattern, key, value } => {
|
cli::AttrCmd::Set { pattern, key, value } => {
|
||||||
attr_set(&conn, &pattern, &key, &value)?;
|
attr_set(&conn, &pattern, &key, &value)?;
|
||||||
}
|
}
|
||||||
AttrCmd::Ls { path } => {
|
cli::AttrCmd::Ls { path } => {
|
||||||
attr_ls(&conn, &path)?;
|
attr_ls(&conn, &path)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands::Search { query, exec } => {
|
Commands::Search { query, exec } => {
|
||||||
run_search(&conn, &query, exec)?;
|
run_search(&conn, &query, exec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Backup => {
|
Commands::Backup => {
|
||||||
let path = db::backup(&cfg.db_path)?;
|
let path = db::backup(&cfg.db_path)?;
|
||||||
println!("Backup created: {}", path.display());
|
println!("Backup created: {}", path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Restore { backup_path } => {
|
Commands::Restore { backup_path } => {
|
||||||
drop(conn);
|
drop(conn);
|
||||||
db::restore(&backup_path, &cfg.db_path)
|
db::restore(&backup_path, &cfg.db_path)
|
||||||
.with_context(|| format!("Failed to restore DB from {}", backup_path.display()))?;
|
.with_context(|| format!("Failed to restore DB from {}", backup_path.display()))?;
|
||||||
println!("Restored DB file from {}", backup_path.display());
|
println!("Restored DB from {}", backup_path.display());
|
||||||
db::open(&cfg.db_path)
|
db::open(&cfg.db_path)
|
||||||
.with_context(|| format!("Could not open restored DB at {}", cfg.db_path.display()))?;
|
.with_context(|| format!("Could not open restored DB at {}", cfg.db_path.display()))?;
|
||||||
info!("Successfully opened and processed restored database.");
|
info!("Successfully opened restored database.");
|
||||||
}
|
}
|
||||||
|
// new domains delegate to their run() functions
|
||||||
|
Commands::Link { cmd } => cli::link::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::Coll { cmd } => cli::coll::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::View { cmd } => cli::view::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::State { cmd } => cli::state::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::Task { cmd } => cli::task::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::Remind { cmd } => cli::remind::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::Annotate { cmd } => cli::annotate::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::Version { cmd } => cli::version::run(&cmd, &mut conn, args.format)?,
|
||||||
|
Commands::Event { cmd } => cli::event::run(&cmd, &mut conn, args.format)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Reference in New Issue
Block a user