Seems to work!

This commit is contained in:
Emerson Rosen-Jones 2025-05-27 17:20:00 -04:00
parent 81eed730aa
commit 264c4402ec
6 changed files with 2111 additions and 13 deletions

64
src/cli.rs Normal file
View file

@ -0,0 +1,64 @@
use clap::{self, Parser, Subcommand};
use crate::{db::{Bookmark, Id, Store}, Result};
#[derive(Parser)]
pub struct Cli {
#[command(subcommand)]
command: Command,
#[arg(short='r')]
/// Whether the command should be applied to the reading list instead
/// of the bookmark list
pub(crate) is_reading_list: bool,
}
#[derive(Subcommand)]
enum Command {
/// Add an entry to the selected list
Add {
link: String,
#[arg(default_value = "")]
comment: String,
},
/// Fetch the link from an entry in the selected list.
/// If fetching from reading list, the item will be removed as well.
Get {
id: Id,
},
/// List entries from the selected list.
/// The format is ID LINK # COMMENT
List,
/// Remove an entry, printing out its link in the process.
Rm {
id: Id,
},
}
impl Cli {
pub async fn run(self, mut store: Store) -> Result<()> {
match self.command {
Command::Add { link, comment } => {
let bookmark = Bookmark::new(link, comment);
store.add(bookmark).await?;
},
Command::Get { id } => {
let bookmark = store.get(id).await?;
if self.is_reading_list {
store.rm(id).await?;
}
println!("{}", bookmark.link);
},
Command::List => {
let bookmarks = store.list().await?;
for Bookmark { id, link, comment } in bookmarks.iter() {
println!("{id} {link} # {comment}");
}
},
Command::Rm { id } => {
let bookmark = store.get(id).await?;
store.rm(id).await?;
println!("{}", bookmark.link);
},
}
Ok(())
}
}

103
src/db.rs Normal file
View file

@ -0,0 +1,103 @@
use crate::Result;
use anyhow::anyhow;
use sqlx::{self, ConnectOptions, SqliteConnection};
use sqlx::sqlite::SqliteConnectOptions;
pub(crate) type Id = i64;
#[derive(sqlx::FromRow)]
pub struct Bookmark {
#[sqlx(rename="rowid")]
pub id: Id,
pub link: String,
pub comment: String,
}
impl Bookmark {
pub fn new(link: String, comment: String) -> Self {
Bookmark { id: 0, link, comment }
}
}
pub struct Store {
conn: SqliteConnection,
table: &'static str,
}
impl Store {
pub async fn new(location: &str, is_reading_list: bool) -> Result<Self> {
let conn = location.parse::<SqliteConnectOptions>()?
.connect().await?;
let table = {
if is_reading_list { "reading_list" } else { "bookmarks" }
};
let mut result = Self { conn, table };
result.create_tables().await?;
Ok(result)
}
pub async fn list(&mut self) -> Result<Vec<Bookmark>> {
let query = format!(
"SELECT rowid, link, comment
FROM {}
ORDER BY timestamp",
self.table
);
Ok(sqlx::query_as(&query)
.fetch_all(&mut self.conn)
.await?)
}
pub async fn add(&mut self, bookmark: Bookmark) -> Result<()> {
let query = format!(
"INSERT INTO {} VALUES
($1, $2, unixepoch('now'))",
self.table
);
sqlx::query(&query)
.bind(bookmark.link)
.bind(bookmark.comment)
.execute(&mut self.conn).await?;
Ok(())
}
pub async fn get(&mut self, id: Id)
-> Result<Bookmark> {
let query = format!(
"SELECT rowid, link, comment
FROM {}
WHERE rowid = $1",
self.table
);
let result = sqlx::query_as(&query)
.bind(id)
.fetch_optional(&mut self.conn)
.await?
.ok_or(anyhow!("Could not find a bookmark with that ID!"))?;
Ok(result)
}
pub async fn rm(&mut self, id: Id) -> Result<()> {
let query = format!(
"DELETE FROM {}
WHERE rowid = $1",
self.table
);
sqlx::query(&query).bind(id).execute(&mut self.conn).await?;
Ok(())
}
async fn create_tables(&mut self) -> Result<()> {
for table in ["bookmarks", "reading_list"] {
let query = format!(
"CREATE TABLE IF NOT EXISTS {}
(link TEXT, comment TEXT, timestamp DATETIME)",
table
);
sqlx::query(&query).execute(&mut self.conn).await?;
}
Ok(())
}
}

View file

@ -1,15 +1,21 @@
struct Bookmark {
link: String,
comment: String,
}
mod cli;
mod db;
/* Desired actions:
* - add new bookmark/reading list item
* - get bookmark
* - get RL item and remove/delist
* - ensure nothing is lost accidentally
*/
use anyhow::anyhow;
use clap::Parser;
use cli::Cli;
fn main() {
println!("Hello, world!");
pub(crate) type Result<T> = anyhow::Result<T>;
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let location = std::env::var("BOOKMARK_DB_PATH")
.map_err(|_| anyhow!(
"No database path set! \
Please provide one with the BOOKMARK_DB_PATH environment \
variable.".to_string()
))?;
let store = db::Store::new(&location, cli.is_reading_list).await?;
cli.run(store).await
}