refactor: move library into it's own crate
This commit is contained in:
parent
91d702088d
commit
b31502fb37
13 changed files with 138 additions and 34 deletions
101
cli/src/config.rs
Normal file
101
cli/src/config.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use tracing_core::LevelFilter;
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum VerbosityLevel {
|
||||
Off,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
impl Default for VerbosityLevel {
|
||||
fn default() -> Self {
|
||||
VerbosityLevel::Error
|
||||
}
|
||||
}
|
||||
impl fmt::Display for VerbosityLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
VerbosityLevel::Off => {
|
||||
write!(f, "off")
|
||||
}
|
||||
VerbosityLevel::Error => {
|
||||
write!(f, "error")
|
||||
}
|
||||
VerbosityLevel::Warn => {
|
||||
write!(f, "warn")
|
||||
}
|
||||
VerbosityLevel::Info => {
|
||||
write!(f, "info")
|
||||
}
|
||||
VerbosityLevel::Debug => {
|
||||
write!(f, "debug")
|
||||
}
|
||||
VerbosityLevel::Trace => {
|
||||
write!(f, "trace")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<LevelFilter> for VerbosityLevel {
|
||||
fn into(self) -> LevelFilter {
|
||||
match self {
|
||||
VerbosityLevel::Off => LevelFilter::OFF,
|
||||
VerbosityLevel::Error => LevelFilter::ERROR,
|
||||
VerbosityLevel::Warn => LevelFilter::WARN,
|
||||
VerbosityLevel::Info => LevelFilter::INFO,
|
||||
VerbosityLevel::Debug => LevelFilter::DEBUG,
|
||||
VerbosityLevel::Trace => LevelFilter::TRACE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[clap(rename_all = "snake_case")]
|
||||
pub enum Command {
|
||||
/// Load task into the database from [path]
|
||||
LoadTasks{
|
||||
/// Path to the file
|
||||
path: PathBuf,
|
||||
},
|
||||
Query,
|
||||
Run,
|
||||
#[clap(skip)]
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for Command {
|
||||
fn default() -> Self {
|
||||
Command::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
#[command(subcommand)]
|
||||
#[serde(skip)]
|
||||
pub command: Command,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
short = 'v',
|
||||
default_value_t,
|
||||
global = true,
|
||||
help = "Increase logging verbosity"
|
||||
)]
|
||||
log_level: VerbosityLevel,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
||||
pub fn log_level(&self) -> LevelFilter {
|
||||
self.log_level.clone().into()
|
||||
}
|
||||
}
|
||||
33
cli/src/error.rs
Normal file
33
cli/src/error.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
Exception(&'static str),
|
||||
|
||||
#[error("{0}")]
|
||||
Runtime(String),
|
||||
|
||||
#[error("{0}")]
|
||||
Unhandled(&'static str),
|
||||
|
||||
#[error(transparent)]
|
||||
Sync(#[from] lib_sync_core::error::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Sqlx(#[from] sqlx::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Migration(#[from] sqlx::migrate::MigrateError),
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] tokio::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
ParseJson(#[from] serde_json::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Config(#[from] figment::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
5
cli/src/lib.rs
Normal file
5
cli/src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod config;
|
||||
pub mod readwise;
|
||||
mod error;
|
||||
|
||||
pub use error::*;
|
||||
70
cli/src/main.rs
Normal file
70
cli/src/main.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use clap::{CommandFactory, Parser};
|
||||
use figment::{
|
||||
providers::{Env, Serialized},
|
||||
Figment,
|
||||
};
|
||||
use readwise_bulk_upload::config::{Command, Config};
|
||||
use readwise_bulk_upload::readwise::DocumentPayload;
|
||||
use lib_sync_core::task_manager::{TaskManager, TaskStatus};
|
||||
use readwise_bulk_upload::{Error, Result};
|
||||
use std::fs::File;
|
||||
use directories::ProjectDirs;
|
||||
use tabled::Table;
|
||||
use tracing_subscriber;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Config::parse();
|
||||
let args: Config = Figment::new()
|
||||
.merge(Serialized::defaults(&cli))
|
||||
.merge(Env::prefixed("APP_"))
|
||||
.extract()?;
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(args.log_level())
|
||||
.init();
|
||||
|
||||
run(&cli.command).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(command: &Command) -> Result<()> {
|
||||
let project_dir = ProjectDirs::from("", "", env!("CARGO_PKG_NAME"))
|
||||
.ok_or(lib_sync_core::error::Error::Unhandled("Could not get standard directories"))?;
|
||||
|
||||
let task_manager = TaskManager::new(project_dir.data_dir()).await?;
|
||||
|
||||
match command {
|
||||
Command::LoadTasks { path } => {
|
||||
let file = File::open(path).map_err(|_| {
|
||||
Error::Runtime(format!(
|
||||
r#"The file "{}" could not be open"#,
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
|
||||
let documents: Vec<DocumentPayload> = serde_json::from_reader(file)?;
|
||||
|
||||
|
||||
task_manager.load_tasks(documents).await?;
|
||||
}
|
||||
Command::Query => {
|
||||
let tasks = task_manager.get_tasks::<DocumentPayload>(None, Some(25)).await?;
|
||||
|
||||
println!("{}", Table::new(tasks));
|
||||
}
|
||||
Command::Run => {
|
||||
task_manager.run_tasks::<DocumentPayload>(|task| {
|
||||
println!("{}", task.get_key());
|
||||
|
||||
TaskStatus::Completed
|
||||
}).await?;
|
||||
}
|
||||
Command::None => {
|
||||
Config::command().print_help()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
41
cli/src/readwise.rs
Normal file
41
cli/src/readwise.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use lib_sync_core::task_manager::TaskPayload;
|
||||
use chrono::{DateTime, Local};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct DocumentPayload {
|
||||
title: String,
|
||||
summary: Option<String>,
|
||||
url: String,
|
||||
#[serde(deserialize_with = "single_or_vec")]
|
||||
tags: Vec<String>,
|
||||
published_date: DateTime<Local>,
|
||||
location: String,
|
||||
}
|
||||
|
||||
impl Display for DocumentPayload {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
serde_json::to_string_pretty(self).map_err(|_| std::fmt::Error)?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskPayload for DocumentPayload {
|
||||
fn get_key(&self) -> String {
|
||||
self.url.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn single_or_vec<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<String>, D::Error> {
|
||||
Ok(match Value::deserialize(deserializer)? {
|
||||
Value::String(s) => vec![s.parse().map_err(de::Error::custom)?],
|
||||
Value::Array(arr) => arr.into_iter().map(|a| a.to_string()).collect(),
|
||||
Value::Null => Vec::new(),
|
||||
_ => return Err(de::Error::custom("wrong type")),
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue