diff --git a/.config/config.ron b/.config/config.ron index f5230f3..488ca47 100644 --- a/.config/config.ron +++ b/.config/config.ron @@ -1,87 +1,87 @@ ( directory: "/Users/suri/dev/projects/filaments/ZettelKasten", global_key_binds: { - "down": MoveDown, + "ctrl-c": Quit, "ctrl-z": Suspend, "up": MoveUp, - "ctrl-c": Quit, + "down": MoveDown, }, zk: ( keybinds: { - "ctrl-n": NewZettel, - "enter": OpenZettel, "tab": SwitchTo( page: Todo(Explorer), ), + "ctrl-n": NewZettel, + "enter": OpenZettel, }, ), todo: ( explorer: ( keybinds: { - "k": MoveUp, "1": SwitchTo( page: Todo(Explorer), ), + "t": NewTask, + "k": MoveUp, + "shift-g": NewGroup, + "enter": SwitchTo( + page: Todo(Inspector), + ), + "2": SwitchTo( + page: Todo(Inspector), + ), "tab": SwitchTo( page: Zk, ), "3": SwitchTo( page: Todo(TaskList), ), - "enter": SwitchTo( - page: Todo(Inspector), - ), - "shift-g": NewGroup, - "g": NewSubGroup, - "t": NewTask, "j": MoveDown, - "2": SwitchTo( - page: Todo(Inspector), - ), + "g": NewSubGroup, }, ), inspector: ( keybinds: { - "p": EditPriority, + "3": SwitchTo( + page: Todo(TaskList), + ), "tab": SwitchTo( page: Zk, ), - "n": EditName, - "d": EditDue, + "f": ToggleFinish, + "r": RandomColor, "1": SwitchTo( page: Todo(Explorer), ), - "3": SwitchTo( - page: Todo(TaskList), - ), "2": SwitchTo( page: Todo(Inspector), ), + "n": EditName, + "p": EditPriority, "o": OpenZettel, - "f": ToggleFinish, - "r": RandomColor + "d": EditDue, }, ), tasklist: ( keybinds: { - "enter": SwitchTo( - page: Todo(Inspector), - ), - "2": SwitchTo( - page: Todo(Inspector), - ), - "1": SwitchTo( - page: Todo(Explorer), + "tab": SwitchTo( + page: Zk, ), "3": SwitchTo( page: Todo(TaskList), ), + "2": SwitchTo( + page: Todo(Inspector), + ), "j": MoveDown, - "tab": SwitchTo( - page: Zk, + "enter": SwitchTo( + page: Todo(Inspector), ), "k": MoveUp, + "1": SwitchTo( + page: Todo(Explorer), + ), }, ), ), -) +) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2d0ab33..fcf4eb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2242,12 +2242,28 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "fil-core" +version = "0.1.0" +dependencies = [ + "async-recursion", + "chrono", + "dto", + "pulldown-cmark", + "ratatui", + "rayon", + "serde", + "thiserror 2.0.18", + "tokio", + "tracing", + "tree", +] + [[package]] name = "filaments" version = "0.1.0" dependencies = [ "anyhow", - "async-recursion", "async-trait", "better-panic", "chrono", @@ -2258,15 +2274,14 @@ dependencies = [ "dto", "eframe", "egui_graphs", + "fil-core", "futures", "human-panic", "notify", "nucleo-matcher", - "pulldown-cmark", "rand 0.10.1", "ratatui", "ratatui-textarea", - "rayon", "ron", "serde", "signal-hook 0.4.4", @@ -5575,9 +5590,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" dependencies = [ "bitflags 2.11.0", "getopts", @@ -5849,9 +5864,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", diff --git a/Cargo.toml b/Cargo.toml index 58fc83b..bb5e575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/dto", "crates/dto/migration", + "crates/fil-core", "crates/tree" ] @@ -28,11 +29,16 @@ pedantic = "deny" nursery = "deny" [workspace.dependencies] -serde = "1.0.228" -tracing = "0.1.44" +fil-core = { path = "crates/fil-core" } +tree = { path = "crates/tree" } +dto = {path = "crates/dto"} + +chrono = "0.4.44" rand = "0.10.1" +ratatui = "0.30.0" +serde = "1.0.228" tokio = { version = "1.51.1", features = ["full"] } -tree = {path = "crates/tree"} +tracing = "0.1.44" [package] name = "filaments" @@ -55,37 +61,37 @@ path = "./src/main.rs" workspace = true [dependencies] -tracing.workspace = true -color-eyre = "0.6.5" -human-panic = "2.0.8" +fil-core = { workspace = true } +tree = { workspace = true } + +rand = { workspace = true } +ratatui = { workspace = true } +serde = { workspace = true, features = ["derive"] } +tokio = { workspace = true } +tracing = { workspace = true } + +async-trait = "0.1.89" better-panic = "0.3.0" -tokio = {workspace = true} +chrono = { workspace = true } +clap = { version = "4.6.0", features = ["derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] } +color-eyre = "0.6.5" crossterm = { version = "0.29.0", features = ["event-stream"] } -futures = "0.3.32" -ratatui = "0.30.0" -serde = { version = "1.0.228", features = ["derive"] } -tokio-util = "0.7.18" -signal-hook = "0.4.4" -strum = { version = "0.28.0", features = ["derive"] } directories = "6.0.0" -tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } -tracing-error = "0.2.1" -clap = { version = "4.6.0", features = ["derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] } -dto = {path="./crates/dto"} +dto = {workspace = true} eframe = "0.34.1" -async-trait = "0.1.89" egui_graphs = "0.30.0" -rayon = "1.11.0" -rand = {workspace = true} -pulldown-cmark = { version = "0.13.3", features = ["simd"] } -ratatui-textarea = "0.8.0" +futures = "0.3.32" +human-panic = "2.0.8" +notify = "8.2.0" nucleo-matcher = "0.3.1" +ratatui-textarea = "0.8.0" ron = "0.12.1" +signal-hook = "0.4.4" +strum = { version = "0.28.0", features = ["derive"] } +tokio-util = "0.7.18" tower-lsp = "0.20.0" -notify = "8.2.0" -tree = {workspace = true} -async-recursion = "1.1.1" -chrono = "0.4.44" +tracing-error = "0.2.1" +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } [build-dependencies] anyhow = "1.0.102" diff --git a/crates/dto/src/lib.rs b/crates/dto/src/lib.rs index f170019..f7ff23f 100644 --- a/crates/dto/src/lib.rs +++ b/crates/dto/src/lib.rs @@ -9,6 +9,7 @@ pub use sea_orm::{Database, DatabaseConnection}; pub use sea_orm::ActiveModelTrait; pub use sea_orm::ActiveValue; pub use sea_orm::ColumnTrait; +pub use sea_orm::DbErr; pub use sea_orm::EntityTrait; pub use sea_orm::IntoActiveModel; pub use sea_orm::QueryFilter; diff --git a/crates/fil-core/Cargo.toml b/crates/fil-core/Cargo.toml new file mode 100644 index 0000000..9565eda --- /dev/null +++ b/crates/fil-core/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "fil-core" +version.workspace = true +edition.workspace = true +authors.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +readme.workspace = true +keywords.workspace = true +rust-version.workspace = true + +[dependencies] +chrono = {workspace = true} +dto = {workspace = true} +ratatui = {workspace = true} +serde = {workspace = true} +tracing = {workspace = true} +tokio = {workspace = true} + +async-recursion = "1.1.1" +pulldown-cmark = { version = "0.13.3", features = ["simd"] } +thiserror = "2.0.18" +tree = {workspace = true} +rayon = "1.11.0" + + + + +[lints] +workspace = true diff --git a/src/types/color.rs b/crates/fil-core/src/color.rs similarity index 92% rename from src/types/color.rs rename to crates/fil-core/src/color.rs index e38fb47..d4e5f0a 100644 --- a/src/types/color.rs +++ b/crates/fil-core/src/color.rs @@ -3,8 +3,8 @@ use std::fmt::Display; use dto::ColorDTO; use ratatui::style::Color as RatColor; -/// Agnostic Color type, -/// internally represented as rgb +/// Agnostic Color type. +/// internally represented as RGB #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default)] pub struct Color(ColorDTO); @@ -29,7 +29,6 @@ impl From for ColorDTO { impl From for RatColor { fn from(value: Color) -> Self { let rgb = value.0.to_rgb8(); - Self::Rgb(rgb.r, rgb.g, rgb.b) } } diff --git a/crates/fil-core/src/due.rs b/crates/fil-core/src/due.rs new file mode 100644 index 0000000..4f7b026 --- /dev/null +++ b/crates/fil-core/src/due.rs @@ -0,0 +1,243 @@ +use std::fmt::Display; + +use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveDateTime}; + +use crate::{Error, Result}; + +const DATE_FMT_STR: &str = "%Y-%m-%d %I:%M:%S %p"; +const END_OF_DAY_HOUR: u32 = 23; +const END_OF_DAY_MINUTE: u32 = 59; +const END_OF_DAY_SECOND: u32 = 59; + +const NO_DUE_WORDS: &[&str] = &[ + "", "none", "never", "n/a", "na", "-", "--", "null", "nil", "no", "no due date", +]; + +const DATETIME_FORMATS: &[&str] = &[ + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%dT%H:%M", + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d %H:%M", + "%m/%d/%Y %H:%M:%S", + "%m/%d/%Y %H:%M", + "%m-%d-%Y %H:%M:%S", + "%m-%d-%Y %H:%M", + "%b %d %Y %H:%M:%S", + "%b %d %Y %H:%M", + "%B %d %Y %H:%M:%S", + "%B %d %Y %H:%M", + "%b %d, %Y %H:%M:%S", + "%b %d, %Y %H:%M", + "%B %d, %Y %H:%M:%S", + "%B %d, %Y %H:%M", + "%m/%d/%Y %I:%M %p", + "%m/%d/%Y %I:%M:%S %p", + "%m-%d-%Y %I:%M %p", + "%m-%d-%Y %I:%M:%S %p", + "%b %d, %Y %I:%M %p", + "%B %d, %Y %I:%M %p", + "%b %d, %Y %I:%M:%S %p", + "%B %d, %Y %I:%M:%S %p", +]; + +const DATE_FORMATS: &[&str] = &[ + "%Y-%m-%d", "%Y/%m/%d", "%Y.%m.%d", "%Y%m%d", "%m/%d/%Y", "%m-%d-%Y", "%b %d %Y", + "%B %d %Y", "%b %d, %Y", "%B %d, %Y", "%b %Y", "%B %Y", "%m/%Y", "%m-%Y", +]; + +const YEARLESS_FORMATS: &[&str] = &["%m/%d", "%m-%d"]; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Due(pub Option); + +impl Due { + pub const fn has_date(&self) -> bool { + self.0.is_some() + } +} + +impl From> for Due { + fn from(value: Option) -> Self { + Self(value) + } +} + +impl From for Option { + fn from(value: Due) -> Self { + value.0 + } +} + +impl Display for Due { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = self.0.map_or_else( + || "None".to_string(), + |d| d.format(DATE_FMT_STR).to_string(), + ); + write!(f, "{str}") + } +} + +impl TryFrom<&str> for Due { + type Error = Error; + + fn try_from(value: &str) -> Result { + parse_due_date(value).map(Into::into) + } +} + +const fn naive_date_to_dt(d: chrono::NaiveDate) -> chrono::NaiveDateTime { + d.and_hms_opt(END_OF_DAY_HOUR, END_OF_DAY_MINUTE, END_OF_DAY_SECOND) + .unwrap() +} + +fn last_day_of_month(year: i32, month: u32) -> Option { + let (next_year, next_month) = if month == 12 { + (year + 1, 1) + } else { + (year, month + 1) + }; + + NaiveDate::from_ymd_opt(next_year, next_month, 1) + .and_then(|date| date.pred_opt()) + .map(|date| date.day()) +} + +fn add_months(today: NaiveDate, months: i64) -> Option { + let months = i32::try_from(months).ok()?; + let total_months = today.month0().cast_signed() + months; + let year = today.year() + total_months.div_euclid(12); + let month = total_months.rem_euclid(12).cast_unsigned() + 1; + let day = today.day().min(last_day_of_month(year, month)?); + + NaiveDate::from_ymd_opt(year, month, day) +} + +fn parse_relative_offset(lower: &str, today: NaiveDate) -> Option { + let lower = lower.strip_prefix("in ")?.trim(); + let mut parts = lower.split_whitespace(); + let n: i64 = parts.next()?.parse().ok()?; + let unit = parts.next()?.trim_end_matches('s'); + + match unit { + "day" => Some(today + Duration::days(n)), + "week" => Some(today + Duration::weeks(n)), + "month" => add_months(today, n), + "year" => add_months(today, n.checked_mul(12)?), + _ => None, + } +} + +fn parse_weekday(lower: &str, today: NaiveDate) -> Option { + use chrono::Weekday::{Fri, Mon, Sat, Sun, Thu, Tue, Wed}; + + let (force_next, word) = lower + .strip_prefix("next ") + .map_or((false, lower), |rest| (true, rest.trim())); + + let target = match word { + "monday" | "mon" => Mon, + "tuesday" | "tue" | "tues" => Tue, + "wednesday" | "wed" => Wed, + "thursday" | "thu" | "thur" | "thurs" => Thu, + "friday" | "fri" => Fri, + "saturday" | "sat" => Sat, + "sunday" | "sun" => Sun, + _ => return None, + }; + let mut days_ahead = i64::from(target.num_days_from_monday()) + - i64::from(today.weekday().num_days_from_monday()); + if days_ahead <= 0 || force_next { + days_ahead += 7; + } + + Some(today + Duration::days(days_ahead)) +} + +fn parse_relative_word(lower: &str, today: NaiveDate) -> Option { + match lower { + "tomorrow" | "tmrw" | "tmr" => Some(today + Duration::days(1)), + "yesterday" => Some(today - Duration::days(1)), + "today" | "now" | "eod" | "end of day" => Some(today), + "eow" | "end of week" => { + let days = (7 - today.weekday().num_days_from_sunday()) % 7; + Some(today + Duration::days(i64::from(days))) + } + "eom" | "end of month" => last_day_of_month(today.year(), today.month()) + .and_then(|day| NaiveDate::from_ymd_opt(today.year(), today.month(), day)), + "eoy" | "end of year" => NaiveDate::from_ymd_opt(today.year(), 12, 31), + _ => None, + } +} + +fn parse_datetime(s: &str) -> Option { + DATETIME_FORMATS + .iter() + .find_map(|fmt| NaiveDateTime::parse_from_str(s, fmt).ok()) +} + +fn parse_date(s: &str) -> Option { + DATE_FORMATS + .iter() + .find_map(|fmt| NaiveDate::parse_from_str(s, fmt).ok()) +} + +fn parse_yearless_date(s: &str, today: NaiveDate) -> Option { + YEARLESS_FORMATS.iter().find_map(|fmt| { + NaiveDate::parse_from_str( + &format!("{s}/{}", today.year()), + &format!("{fmt}/{}", "%Y"), + ) + .ok() + }) +} + +fn parse_unix_timestamp(s: &str) -> Option { + let n = s.parse::().ok()?; + let secs = if n > 10_000_000_000 { n / 1000 } else { n }; + + DateTime::from_timestamp(secs, 0).map(|dt| dt.naive_utc()) +} + +fn strip_date_noise(s: &str) -> String { + s.chars() + .filter(|c| c.is_alphanumeric() || matches!(*c, '-' | '/' | ' ')) + .collect::() +} + +fn parse_due_date(s: &str) -> Result> { + let s = s.trim(); + + if NO_DUE_WORDS.contains(&s.to_lowercase().as_str()) { + return Ok(None); + } + + let lower = s.to_lowercase(); + let today = Local::now().date_naive(); + + if let Some(d) = parse_relative_word(&lower, today) + .or_else(|| parse_relative_offset(&lower, today)) + .or_else(|| parse_weekday(&lower, today)) + { + return Ok(Some(naive_date_to_dt(d))); + } + + let normalized = s.trim_end_matches('Z'); + + if let Some(dt) = parse_datetime(normalized).or_else(|| parse_unix_timestamp(s)) { + return Ok(Some(dt)); + } + + if let Some(d) = parse_date(normalized).or_else(|| parse_yearless_date(normalized, today)) { + return Ok(Some(naive_date_to_dt(d))); + } + + let stripped = strip_date_noise(s); + if let Some(d) = parse_date(stripped.trim()) { + return Ok(Some(naive_date_to_dt(d))); + } + + Err(Error::InvalidDueDate { + input: s.to_owned(), + }) +} diff --git a/crates/fil-core/src/error.rs b/crates/fil-core/src/error.rs new file mode 100644 index 0000000..94aeae5 --- /dev/null +++ b/crates/fil-core/src/error.rs @@ -0,0 +1,39 @@ +use std::{io, path::PathBuf}; + +use dto::DbErr; +use thiserror::Error; +use tree::NodeIdError; + +/// use anyhow for this +#[derive(Error, Debug)] +pub enum Error { + #[error("invalid zettel path `path` : {reason}")] + InvalidZettelPath { path: PathBuf, reason: &'static str }, + + #[error("Failed to parse Zettel at `path` : {reason}")] + FailedZettelParse { path: PathBuf, reason: &'static str }, + + #[error("could not parse {input:?} as a due date")] + InvalidDueDate { input: String }, + + #[error("IO error!")] + IoError(#[from] io::Error), + + #[error("Database Error!")] + DbError(#[from] DbErr), + + #[error("Invalid Workspace!")] + InvalidWorkspace, + + #[error("Invalid Priority")] + InvalidPriority, + + #[error("Group does not exist!")] + GroupDoesNotExist, + + #[error("Tree Error!")] + TreeError(#[from] NodeIdError), +} + +/// Custom Result type that uses `fil-core::Error` +pub type Result = std::result::Result; diff --git a/src/types/group.rs b/crates/fil-core/src/group.rs similarity index 91% rename from src/types/group.rs rename to crates/fil-core/src/group.rs index 9d259e6..50ee499 100644 --- a/src/types/group.rs +++ b/crates/fil-core/src/group.rs @@ -1,10 +1,9 @@ -use color_eyre::eyre::Result; use dto::{ DateTime, GroupActiveModel, GroupEntity, GroupModelEx, IntoActiveModel as _, NanoId, TagActiveModel, TagEntity, ZettelEntity, }; -use crate::types::{Kasten, Priority, Tag, Zettel, frontmatter}; +use crate::{Kasten, Priority, Zettel, frontmatter, zettel::Tag}; /// A `Group` which contains tasks! #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -32,7 +31,7 @@ impl Group { name: impl Into, parent_id: Option, kt: &mut Kasten, - ) -> Result { + ) -> crate::Result { let name = name.into(); let tag: Tag = TagActiveModel::builder() .set_name(name.clone()) @@ -42,10 +41,8 @@ impl Group { let tag_id = tag.id.clone(); - // then create the zettel for the group let zettel = Zettel::new(name.clone(), kt, vec![tag]).await?; - // then insert that shi let inserted = GroupActiveModel::builder() .set_name(name) .set_parent_group_id(parent_id) @@ -69,7 +66,7 @@ impl Group { .insert(&kt.db) .await?; - // group should also have the accompanying tag for it. + // Group should also have the accompanying tag for it. let group: Self = GroupEntity::load() .with(TagEntity) .with((ZettelEntity, TagEntity)) @@ -87,7 +84,7 @@ impl Group { id: NanoId, new_name: impl Into, kt: &mut Kasten, - ) -> Result<()> { + ) -> crate::Result<()> { let new_name = new_name.into(); let g = GroupEntity::load() @@ -110,12 +107,12 @@ impl Group { Tag::alter_name(tag_id, &new_name, kt).await?; - Zettel::alter_name(zettel_id.into(), &new_name, kt).await?; + Zettel::alter_title(zettel_id.into(), &new_name, kt).await?; Ok(()) } - pub async fn alter_priority(id: NanoId, new_prio: Priority, kt: &Kasten) -> Result<()> { + pub async fn alter_priority(id: NanoId, new_prio: Priority, kt: &Kasten) -> crate::Result<()> { GroupEntity::load() .filter_by_nano_id(id) .one(&kt.db) @@ -129,7 +126,7 @@ impl Group { Ok(()) } - /// Calcualtes the `p_score` of this `Group` + /// Calculates the `p_score` of this `Group` pub fn p_score(&self, parent_score: f64) -> f64 { self.priority.p_score() * parent_score } diff --git a/src/types/kasten/index/mod.rs b/crates/fil-core/src/kasten/index/mod.rs similarity index 87% rename from src/types/kasten/index/mod.rs rename to crates/fil-core/src/kasten/index/mod.rs index f080b6c..0970fd7 100644 --- a/src/types/kasten/index/mod.rs +++ b/crates/fil-core/src/kasten/index/mod.rs @@ -1,9 +1,9 @@ +use crate::zettel::frontmatter::FrontMatter; use std::{ collections::{HashMap, HashSet}, path::{Path, PathBuf}, }; -use color_eyre::eyre::Result; use dto::{ ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, NanoId, QueryFilter, TagActiveModel, TagEntity, ZettelEntity, ZettelModelEx, @@ -13,7 +13,7 @@ use pulldown_cmark::{Event, Options, Parser}; use rayon::iter::{ParallelBridge, ParallelIterator}; use tracing::info; -use crate::types::{FrontMatter, Link, ZettelId, frontmatter::Body}; +use crate::zettel::{Link, ZettelId, frontmatter::Body}; #[derive(Debug, Clone)] pub struct Index { @@ -31,7 +31,7 @@ pub struct ZettelOnDisk { impl Index { /// Parses the `root` path to construct an `Index`. - pub fn tabulate(root: &Path) -> Result { + pub fn tabulate(root: &Path) -> crate::Result { let root = root.canonicalize()?; let mut zods = HashMap::new(); @@ -48,24 +48,26 @@ impl Index { .and_then(|ext| ext.to_str()) .is_some_and(|ext| ext == "md") }) - .map(|entry| -> Result<(ZettelId, ZettelOnDisk, Vec)> { - let path = entry.path(); - let zid: ZettelId = path.as_path().try_into()?; - let (fm, body) = FrontMatter::extract_from_file(&path)?; - - let outgoing_links = Self::_parse_outgoing_links(&zid, &body); - - Ok(( - zid, - ZettelOnDisk { - fm, - body, - path: path.canonicalize()?, - }, - outgoing_links, - )) - }) - .collect::>>()? + .map( + |entry| -> crate::Result<(ZettelId, ZettelOnDisk, Vec)> { + let path = entry.path(); + let zid: ZettelId = path.as_path().try_into()?; + let (fm, body) = FrontMatter::extract_from_file(&path)?; + + let outgoing_links = Self::_parse_outgoing_links(&zid, &body); + + Ok(( + zid, + ZettelOnDisk { + fm, + body, + path: path.canonicalize()?, + }, + outgoing_links, + )) + }, + ) + .collect::>>()? .into_iter() // .par_bridge() .for_each(|(id, zod, zettel_outgoing_links)| { @@ -130,7 +132,7 @@ impl Index { } /// Updates the interal state of the `Index` for the provided `Zid`. - pub fn process_zid(&mut self, zid: &ZettelId) -> Result<()> { + pub fn process_zid(&mut self, zid: &ZettelId) -> crate::Result<()> { let zod = self.get_zod_mut(zid); let (fm, body) = FrontMatter::extract_from_file(&zod.path)?; @@ -147,7 +149,7 @@ impl Index { &mut self, zid: &ZettelId, db: &DatabaseConnection, - ) -> Result<()> { + ) -> crate::Result<()> { let fm = &mut self.get_zod_mut(zid).fm; let mut model = ZettelEntity::find_by_nano_id(zid.clone()) @@ -171,7 +173,7 @@ impl Index { &mut self, zid: &ZettelId, db: &DatabaseConnection, - ) -> Result<()> { + ) -> crate::Result<()> { let fm = &mut self.get_zod_mut(zid).fm; let mut tag_strings = fm.tag_strings.clone(); diff --git a/src/types/kasten/index/zod.rs b/crates/fil-core/src/kasten/index/zod.rs similarity index 100% rename from src/types/kasten/index/zod.rs rename to crates/fil-core/src/kasten/index/zod.rs diff --git a/src/types/kasten/mod.rs b/crates/fil-core/src/kasten/mod.rs similarity index 73% rename from src/types/kasten/mod.rs rename to crates/fil-core/src/kasten/mod.rs index 98ba941..6a5b127 100644 --- a/src/types/kasten/mod.rs +++ b/crates/fil-core/src/kasten/mod.rs @@ -3,7 +3,6 @@ use std::{ sync::Arc, }; -use color_eyre::eyre::{Context, Result}; use dto::{Database, DatabaseConnection, Migrator, MigratorTrait}; use tokio::{ fs::{File, create_dir_all}, @@ -11,7 +10,7 @@ use tokio::{ }; use tracing::debug; -use crate::types::{FrontMatter, ZettelId}; +use crate::{frontmatter::FrontMatter, zettel::ZettelId}; mod index; pub use index::Index; @@ -41,26 +40,24 @@ impl Kasten { /// /// Note: this means that there should already exist a valid `Kasten` /// at that path. - pub async fn instansiate(root: impl Into) -> Result { + pub async fn instansiate(root: impl Into) -> crate::Result { let root = root.into(); let db_conn_string = format!( "sqlite://{}", root.clone() .join(".filaments/filaments.db") .canonicalize() - .context("Invalid Filaments workspace!!")? + .map_err(|_| crate::Error::InvalidWorkspace)? .to_string_lossy() ); debug!("connecting to {db_conn_string}"); - let conn = Database::connect(db_conn_string) - .await - .context("Failed to connect to the database in the filaments workspace!")?; + let conn = Database::connect(db_conn_string).await?; let index = Index::tabulate(&root)?; - // run da migrations every time we connect, just in case + // Run migrations every time we connect. Migrator::up(&conn, None).await?; let todo_tree = TodoTree::construct(&conn).await?; @@ -75,15 +72,13 @@ impl Kasten { } /// Create a new `Kasten` at the provided `path`. - pub async fn initialize(path: impl Into) -> Result { + pub async fn initialize(path: impl Into) -> crate::Result { let path = path.into(); let filaments_dir = path.join(".filaments"); - // create the dir - create_dir_all(&filaments_dir) - .await - .context("Failed to create the filaments directory!")?; + // create the Dir + create_dir_all(&filaments_dir).await?; let filaments_dir = filaments_dir.canonicalize()?; @@ -95,13 +90,10 @@ impl Kasten { )) } - /// processes the `Zettel` for the provided `ZettelId`, + /// Process the `Zettel` for the provided `ZettelId`, /// meaning it updates the internal state of the `Kasten` /// with the changes in `Zettel`. - pub async fn process_path(&mut self, path: impl AsRef) -> Result<()> { - //NOTE: need to clone to get around borrowing rules but - // ideally we dont have to do this, kind of cringe imo. - + pub async fn process_path(&mut self, path: impl AsRef) -> crate::Result<()> { let path = path.as_ref().canonicalize()?; let zid = ZettelId::try_from(path.as_path())?; @@ -117,9 +109,9 @@ impl Kasten { ); } - // incase the path of the zettel changed + // in-case the path of the zettel changed self.index.update_path_for_zid(&zid, path.clone()); - // let the index process the zettel, basically update the internal state of the zod + // Let the index process the zettel, basically update the internal state of the `zod` self.index.process_zid(&zid)?; // and then we sync tags self.index.sync_tags_with_db(&zid, &self.db).await?; diff --git a/src/types/kasten/todo_tree.rs b/crates/fil-core/src/kasten/todo_tree.rs similarity index 95% rename from src/types/kasten/todo_tree.rs rename to crates/fil-core/src/kasten/todo_tree.rs index b09dfad..c794b53 100644 --- a/src/types/kasten/todo_tree.rs +++ b/crates/fil-core/src/kasten/todo_tree.rs @@ -1,13 +1,12 @@ use std::{cmp::Ordering, collections::HashMap}; -use color_eyre::eyre::{Context, Result}; use dto::{ ColumnTrait as _, DatabaseConnection, GroupColumns, GroupEntity, NanoId, QueryFilter as _, TagEntity, TaskColumns, TaskEntity, ZettelEntity, }; use tree::{InsertBehavior, Node, NodeId, Tree}; -use crate::types::{Group, Task}; +use crate::{Group, Task}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum TodoNodeKind { @@ -41,14 +40,12 @@ pub struct TodoTree { } impl TodoTree { - pub async fn construct(db: &DatabaseConnection) -> Result { + pub async fn construct(db: &DatabaseConnection) -> crate::Result { let mut tree = Tree::::new(); - let root_id = tree - .insert( - Node::new(TodoNode::new(TodoNodeKind::Root, 0, 1.0)), - InsertBehavior::AsRoot, - ) - .with_context(|| "Could not create root node.")?; + let root_id = tree.insert( + Node::new(TodoNode::new(TodoNodeKind::Root, 0, 1.0)), + InsertBehavior::AsRoot, + )?; let root_groups: Vec = GroupEntity::load() .with(TagEntity) @@ -84,7 +81,7 @@ impl TodoTree { group: Box, depth: usize, parent_p_score: f64, - ) -> Result<()> { + ) -> crate::Result<()> { let group_id = group.id.clone(); let p_score = group.p_score(parent_p_score); diff --git a/crates/fil-core/src/lib.rs b/crates/fil-core/src/lib.rs new file mode 100644 index 0000000..f8783f2 --- /dev/null +++ b/crates/fil-core/src/lib.rs @@ -0,0 +1,29 @@ +//! The core of `Filaments`. +//! +//! Holds a majority of the logic + +/// RGB wrapper. +mod color; +pub use color::Color; + +mod due; +pub use due::*; + +mod priority; +pub use priority::Priority; + +mod kasten; +pub use kasten::*; + +mod zettel; +pub use zettel::*; + +/// Errors specific to `Filaments` +mod error; +pub use error::*; + +mod task; +pub use task::Task; + +mod group; +pub use group::Group; diff --git a/src/types/priority.rs b/crates/fil-core/src/priority.rs similarity index 50% rename from src/types/priority.rs rename to crates/fil-core/src/priority.rs index f5f9bfc..a7b426c 100644 --- a/src/types/priority.rs +++ b/crates/fil-core/src/priority.rs @@ -1,18 +1,15 @@ use std::fmt::Display; -use color_eyre::eyre::eyre; use dto::PriorityDTO; /// An Enum for the various `Priority` levels /// for `Task`s and `Group`s #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Priority { - field1: PriorityDTO, -} +pub struct Priority(PriorityDTO); impl Priority { pub const fn p_score(&self) -> f64 { - match self.field1 { + match self.0 { PriorityDTO::Asap => 1.0, PriorityDTO::High => 0.9, PriorityDTO::Medium => 0.75, @@ -24,43 +21,35 @@ impl Priority { impl From for Priority { fn from(value: PriorityDTO) -> Self { - Self { field1: value } + Self(value) } } impl From for PriorityDTO { fn from(value: Priority) -> Self { - value.field1 + value.0 } } impl Display for Priority { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.field1) + write!(f, "{}", self.0) } } impl TryFrom<&str> for Priority { - type Error = color_eyre::Report; + type Error = crate::Error; fn try_from(value: &str) -> Result { - match value.to_ascii_lowercase().chars().next() { - Some('a') => Ok(Self { - field1: PriorityDTO::Asap, - }), - Some('h') => Ok(Self { - field1: PriorityDTO::High, - }), - Some('m') => Ok(Self { - field1: PriorityDTO::Medium, - }), - Some('l') => Ok(Self { - field1: PriorityDTO::Low, - }), - Some('f') => Ok(Self { - field1: PriorityDTO::Far, - }), - _ => Err(eyre!("Invalid Priority!")), - } + let dto = match value.to_ascii_lowercase().chars().next() { + Some('a') => PriorityDTO::Asap, + Some('h') => PriorityDTO::High, + Some('m') => PriorityDTO::Medium, + Some('l') => PriorityDTO::Low, + Some('f') => PriorityDTO::Far, + _ => return Err(crate::Error::InvalidPriority), + }; + + Ok(Self(dto)) } } diff --git a/src/types/task.rs b/crates/fil-core/src/task.rs similarity index 91% rename from src/types/task.rs rename to crates/fil-core/src/task.rs index af484a7..ac3183c 100644 --- a/src/types/task.rs +++ b/crates/fil-core/src/task.rs @@ -1,11 +1,10 @@ use chrono::Local; -use color_eyre::eyre::{Context, Result, eyre}; use dto::{ DateTime, GroupEntity, HasOne, IntoActiveModel as _, NanoId, TagEntity, TaskActiveModel, TaskEntity, TaskModelEx, ZettelEntity, }; -use crate::types::{Due, Group, Kasten, Priority, Zettel, frontmatter}; +use crate::{Group, Kasten, Priority, Zettel, due::Due, frontmatter}; /// a `Task` that you have to complete! #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -33,16 +32,15 @@ impl Task { kt: &mut Kasten, due: Option, priority: Priority, - ) -> Result { + ) -> crate::Result { let name = name.into(); let parent = GroupEntity::load() .with(TagEntity) .filter_by_nano_id(parent_id) .one(&kt.db) - .await - .with_context(|| "failed to communicate with db")? - .ok_or_else(|| eyre!("could not find the group"))?; + .await? + .ok_or(crate::Error::GroupDoesNotExist)?; let HasOne::Loaded(tag) = parent.tag else { panic!("this has to be loaded since we just loaded it right above") @@ -94,7 +92,7 @@ impl Task { id: NanoId, new_name: impl Into, kt: &mut Kasten, - ) -> Result<()> { + ) -> crate::Result<()> { let new_name = new_name.into(); let task = TaskEntity::load() @@ -111,12 +109,12 @@ impl Task { .save(&kt.db) .await?; - Zettel::alter_name(zettel_id.into(), new_name, kt).await?; + Zettel::alter_title(zettel_id.into(), new_name, kt).await?; Ok(()) } - pub async fn alter_priority(id: NanoId, new_prio: Priority, kt: &Kasten) -> Result<()> { + pub async fn alter_priority(id: NanoId, new_prio: Priority, kt: &Kasten) -> crate::Result<()> { TaskEntity::load() .filter_by_nano_id(id) .one(&kt.db) @@ -130,7 +128,11 @@ impl Task { Ok(()) } - pub async fn alter_due(id: NanoId, new_due: Option, kt: &Kasten) -> Result<()> { + pub async fn alter_due( + id: NanoId, + new_due: Option, + kt: &Kasten, + ) -> crate::Result<()> { TaskEntity::load() .filter_by_nano_id(id) .one(&kt.db) @@ -144,7 +146,7 @@ impl Task { Ok(()) } - pub async fn toggle_finish(id: NanoId, kt: &Kasten) -> Result<()> { + pub async fn toggle_finish(id: NanoId, kt: &Kasten) -> crate::Result<()> { let now = Local::now().naive_local(); let model = TaskEntity::load() diff --git a/src/types/frontmatter.rs b/crates/fil-core/src/zettel/frontmatter.rs similarity index 69% rename from src/types/frontmatter.rs rename to crates/fil-core/src/zettel/frontmatter.rs index 81690ad..6c0568c 100644 --- a/src/types/frontmatter.rs +++ b/crates/fil-core/src/zettel/frontmatter.rs @@ -1,15 +1,14 @@ use std::{fmt::Display, path::Path}; -// use chrono::format::StrftimeItems; -use color_eyre::eyre::{Result, eyre}; -use egui_graphs::Node; use serde::{Deserialize, Serialize}; use dto::DateTime; -use crate::types::{Link, Zettel}; +use crate::Error; -pub(super) const DATE_FMT_STR: &str = "%Y-%m-%d %I:%M:%S %p"; +use super::Zettel; + +pub const DATE_FMT_STR: &str = "%Y-%m-%d %I:%M:%S %p"; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct FrontMatter { @@ -35,13 +34,14 @@ impl FrontMatter { } } + //TODO: this needs to be moved to the visualizer... /// Apply the features of `FrontMatter` onto a /// `Node` - pub fn apply_node_transform(&self, node: &mut Node) { - node.set_label(self.title.clone()); - let disp = node.display_mut(); - disp.radius = 100.0; - } + // pub fn apply_node_transform(&self, node: &mut Node) { + // node.set_label(self.title.clone()); + // let disp = node.display_mut(); + // disp.radius = 100.0; + // } /// Reads in file and returns the front matter as well as the content after it. /// expected format for front matter as follows @@ -52,10 +52,13 @@ impl FrontMatter { /// Tags: @Daily @barber /// --- /// ``` - pub fn extract_from_file(path: impl AsRef) -> Result<(Self, Body)> { + pub fn extract_from_file(path: impl AsRef) -> crate::Result<(Self, Body)> { let path = path.as_ref(); let string = std::fs::read_to_string(path)?; - Self::extract_from_str(&string) + Self::extract_from_str(&string).map_err(|reason| crate::Error::FailedZettelParse { + path: path.to_path_buf(), + reason, + }) } /// Returns the front matter as well as the content after it. @@ -67,19 +70,19 @@ impl FrontMatter { /// Tags: @Daily @barber /// --- /// ``` - pub fn extract_from_str(string: impl Into) -> Result<(Self, Body)> { + fn extract_from_str(string: impl Into) -> Result<(Self, Body), &'static str> { let string: String = string.into(); // we just want to strictly match this, else we error let lines: Vec<_> = string.lines().collect(); - let delim_check = |line_number: usize| -> Result<()> { + let delim_check = |line_number: usize| -> Result<(), &'static str> { let delim = lines .get(line_number) - .ok_or_else(|| eyre!(format!("Line Number {line_number} doesnt exist!")))? + .ok_or("frontmatter delimiter line does not exist")? .trim(); if delim != "---" { - return Err(eyre!("FrontMatter Deliminator Corrupted!")); + return Err("frontmatter delimiter corrupted"); } Ok(()) }; @@ -90,25 +93,27 @@ impl FrontMatter { //extract name let title = lines .get(1) - .ok_or_else(|| eyre!("Title line doesn't exist!".to_owned()))? + .ok_or("title line does not exist")? .strip_prefix("Title: ") - .ok_or_else(|| eyre!("Title line doesn't start with \"Title: \" ".to_owned(),))?; + .ok_or("title line does not start with Title prefix")?; let created_at = lines .get(2) - .ok_or_else(|| eyre!("Date line doesn't exist!".to_owned()))? + .ok_or("date line does not exist")? .strip_prefix("Date: ") - .ok_or_else(|| eyre!("Date line doesn't start with \"Date: \" ".to_owned(),)) - .map(|date_str| DateTime::parse_from_str(date_str, DATE_FMT_STR))? - .map_err(|err| eyre!(err.to_string()))?; + .ok_or("date line does not start with Date prefix") + .and_then(|date_str| { + DateTime::parse_from_str(date_str, DATE_FMT_STR) + .map_err(|_| "date line is not a valid frontmatter date") + })?; let tag_strings: Vec = lines .get(3) - .ok_or_else(|| eyre!("Tag line doesn't exist!".to_owned()))? + .ok_or("tag line does not exist")? .strip_prefix("Tags: ") - .ok_or_else(|| eyre!("Tag line doesn't start with \"Tags: \" ".to_owned(),))? + .ok_or("tag line does not start with Tags prefix")? .split_whitespace() - .filter_map(|tag| tag.strip_prefix("@").map(ToOwned::to_owned)) + .filter_map(|tag| tag.strip_prefix('@').map(ToOwned::to_owned)) .collect::>(); delim_check(4)?; @@ -118,7 +123,7 @@ impl FrontMatter { Ok((Self::new(title, created_at, tag_strings), remaining)) } - pub fn flush_to_file(&self, path: impl AsRef) -> Result<()> { + pub fn flush_to_file(&self, path: impl AsRef) -> crate::Result<()> { let path = path.as_ref(); let string = std::fs::read_to_string(path)?; @@ -130,7 +135,10 @@ impl FrontMatter { .skip(1) .find(|(_, line)| line.trim() == "---") .map(|(i, _)| i) - .ok_or_else(|| eyre!("Could not find closing front matter delimiter"))?; + .ok_or_else(|| Error::FailedZettelParse { + path: path.to_path_buf(), + reason: "Could not find closing front matter delimiter", + })?; let body = lines[closing_delim + 1..].join("\n"); @@ -169,14 +177,12 @@ mod tests { use dto::DateTime; - use crate::types::{FrontMatter, frontmatter::DATE_FMT_STR}; - - // use crate::{FrontMatter, zettel::frontmatter::DATE_FMT_STR}; + use super::*; #[test] fn test_extract_from_string() { let test_suite: [(&'static str, (FrontMatter, &'static str)); 1] = [( - r"--- + r"--- Title: LOL Date: 2025-01-01 12:50:19 AM Tags: @whoa @barber diff --git a/src/types/zettel/id.rs b/crates/fil-core/src/zettel/id.rs similarity index 74% rename from src/types/zettel/id.rs rename to crates/fil-core/src/zettel/id.rs index 11ac921..772c6e0 100644 --- a/src/types/zettel/id.rs +++ b/crates/fil-core/src/zettel/id.rs @@ -3,10 +3,11 @@ use std::{ path::{Path, PathBuf}, }; -use color_eyre::eyre::{Error, eyre}; use dto::NanoId; use serde::{Deserialize, Serialize}; +use crate::Error; + /// A `ZettelId` is essentially a `NanoId`, /// with some `Zettel` specific helpers written /// onto it @@ -44,28 +45,32 @@ impl TryFrom<&Path> for ZettelId { type Error = Error; fn try_from(value: &Path) -> Result { + let invalid = |reason| Error::InvalidZettelPath { + path: value.to_path_buf(), + reason, + }; + let extension = value .extension() .and_then(|ext| ext.to_str()) - .ok_or_else(|| eyre!("Unable to turn file extension into string".to_owned(),))?; + .ok_or_else(|| invalid("missing or not-supported extension"))?; if extension != "md" { - return Err(eyre!(format!("Wrong extension: {extension}, expected .md"))); + return Err(invalid("expected .md extension")); } - let id: Self = (value - .file_name() - .ok_or_else(|| eyre!("Invalid File Name!".to_owned()))? - .to_str() - .ok_or_else(|| eyre!("File Name cannot be translated into str!".to_owned(),))? - .strip_suffix(".md") - .expect("we statically verify this right above") - .split('-')) - .next() - .ok_or_else(|| eyre!("Unable to get the first part of the file name!"))? - .into(); - - Ok(id) + let stem = value + .file_stem() + .and_then(|stem| stem.to_str()) + .ok_or_else(|| invalid("missing file stem! "))?; + + let id = stem + .split('-') + .next() + .filter(|id| id.is_empty()) + .ok_or_else(|| invalid("missing id before first dash"))?; + + Ok(Self(id.into())) } } diff --git a/src/types/link.rs b/crates/fil-core/src/zettel/link.rs similarity index 93% rename from src/types/link.rs rename to crates/fil-core/src/zettel/link.rs index 5c2be67..12dc079 100644 --- a/src/types/link.rs +++ b/crates/fil-core/src/zettel/link.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::ZettelId; +use super::ZettelId; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Link { diff --git a/src/types/zettel/mod.rs b/crates/fil-core/src/zettel/mod.rs similarity index 78% rename from src/types/zettel/mod.rs rename to crates/fil-core/src/zettel/mod.rs index c39e21c..b4df6db 100644 --- a/src/types/zettel/mod.rs +++ b/crates/fil-core/src/zettel/mod.rs @@ -5,17 +5,20 @@ use dto::{ ZettelModelEx, }; -use color_eyre::eyre::{Context, Result}; use dto::NanoId; use tokio::{fs::File, io::AsyncWriteExt}; -use crate::types::{ - FrontMatter, Index, Kasten, Tag, - frontmatter::{self, Body}, -}; +use crate::zettel::frontmatter::{Body, FrontMatter}; +use crate::{Index, Kasten}; mod id; +mod link; +mod tag; + +pub mod frontmatter; pub use id::ZettelId; +pub use link::*; +pub use tag::*; /// A `Zettel` is a note about a single idea. /// It can have many `Tag`s, just meaning it can fall under many @@ -33,8 +36,11 @@ pub struct Zettel { } impl Zettel { - /// fetches the `Zettel` with the provided `ZettelId`, returning `None` if not found. - pub async fn fetch_from_db(zid: &ZettelId, db: &DatabaseConnection) -> Result> { + /// Fetches the `Zettel` with the provided `ZettelId`, returning `None` if not found. + pub async fn fetch_from_db( + zid: &ZettelId, + db: &DatabaseConnection, + ) -> crate::Result> { Ok(ZettelEntity::load() .filter_by_nano_id(zid.0.clone()) .with(TagEntity) @@ -43,15 +49,14 @@ impl Zettel { .map(Into::into)) } - pub async fn alter_name( + pub async fn alter_title( id: ZettelId, new_name: impl Into, kt: &mut Kasten, - ) -> Result<()> { + ) -> crate::Result<()> { let new_name = new_name.into(); - // ok we need to change it on the actual zettel and then change - // it in the frontmatter + // Need to change the name on the zettel and then change it on the front_matter. let _ = ZettelEntity::load() .filter_by_nano_id(id.clone()) .one(&kt.db) @@ -76,7 +81,7 @@ impl Zettel { } /// Gets the tags from the database for this `Zettel` and write it to the file. - pub async fn write_tags_from_db(id: ZettelId, kt: &mut Kasten) -> Result<()> { + pub async fn write_tags_from_db(id: ZettelId, kt: &mut Kasten) -> crate::Result<()> { let zettel = Self::fetch_from_db(&id, &kt.db).await?.expect("must exist"); let file_path = zettel.absolute_path(&kt.index); @@ -87,24 +92,19 @@ impl Zettel { Ok(()) } - pub async fn new(title: impl Into, kt: &mut Kasten, tags: Vec) -> Result { - // fn new(title: impl Into) -> Result { + pub async fn new( + title: impl Into, + kt: &mut Kasten, + tags: Vec, + ) -> crate::Result { let title = title.into(); - - // make a file that has a random identifier, and then - // also has the name "title" let nano_id = NanoId::default(); let local_file_path = format!("{nano_id}.md"); let absolute_file_path = kt.root.clone().join(&local_file_path); - // now we have to create the file - let mut file = File::create_new(&absolute_file_path) - .await - .with_context(|| { - format!("Failed to create file at local file path: {local_file_path}") - })?; + let mut file = File::create_new(&absolute_file_path).await?; let inserted = { let mut am = ZettelActiveModel::builder() @@ -123,7 +123,6 @@ impl Zettel { am.insert(&kt.db).await? }; - // need to load tags... let zettel = ZettelEntity::load() .filter_by_nano_id(inserted.nano_id) .with(TagEntity) @@ -139,14 +138,7 @@ impl Zettel { file.write_all(front_matter.to_string().as_bytes()).await?; - kt.process_path(&absolute_file_path) - .await - .with_context(|| { - format!( - "Kasten fails to process new Zettel at path: {}", - absolute_file_path.display(), - ) - })?; + kt.process_path(&absolute_file_path).await?; Ok(zettel.into()) } diff --git a/src/types/tag.rs b/crates/fil-core/src/zettel/tag.rs similarity index 91% rename from src/types/tag.rs rename to crates/fil-core/src/zettel/tag.rs index 287132e..ebd2f24 100644 --- a/src/types/tag.rs +++ b/crates/fil-core/src/zettel/tag.rs @@ -1,7 +1,6 @@ -use color_eyre::eyre::Result; use dto::{IntoActiveModel as _, NanoId, TagEntity, TagModel, TagModelEx, ZettelEntity}; -use crate::types::{Color, Kasten, Zettel}; +use crate::{Color, Kasten, Zettel}; /// Represents a `Tag` in a `ZettelKasten` note taking method. /// Easy way to link multiple notes under one simple word. @@ -23,7 +22,7 @@ impl Tag { id: NanoId, new_name: impl Into, kt: &mut Kasten, - ) -> Result<()> { + ) -> crate::Result<()> { let new_name = new_name.into(); TagEntity::load() @@ -36,7 +35,7 @@ impl Tag { .update(&kt.db) .await?; - // fetch all the zettels for this tag + // Fetch all the zettels for this tag let tag = TagEntity::load() .filter_by_nano_id(id) .with(ZettelEntity) @@ -56,7 +55,7 @@ impl Tag { Ok(()) } - pub async fn randomize_color(id: NanoId, kt: &Kasten) -> Result<()> { + pub async fn randomize_color(id: NanoId, kt: &Kasten) -> crate::Result<()> { let new_color = Color::default(); TagEntity::load() diff --git a/src/cli/process.rs b/src/cli/process.rs index 3daa1db..537c5b9 100644 --- a/src/cli/process.rs +++ b/src/cli/process.rs @@ -5,13 +5,13 @@ use std::{ }; use color_eyre::eyre::{Context, Result}; +use fil_core::{Group, Kasten, Priority, Task, Zettel}; use tower_lsp::{LspService, Server}; use crate::{ cli::{Commands, ZettelSubcommand}, config::{Config, get_config_dir}, lsp::Backend, - types::{Group, Kasten, Priority, Task, Zettel}, }; impl Commands { diff --git a/src/types/deimos.rs b/src/deimos.rs similarity index 96% rename from src/types/deimos.rs rename to src/deimos.rs index 173f43c..069987e 100644 --- a/src/types/deimos.rs +++ b/src/deimos.rs @@ -1,13 +1,11 @@ use color_eyre::eyre::Context; +use fil_core::{KastenHandle, ZettelId}; use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Result, Watcher as _}; use tracing::{error, info}; use tokio::sync::mpsc::UnboundedSender; -use crate::{ - tui::Signal, - types::{KastenHandle, ZettelId}, -}; +use crate::tui::Signal; #[derive(Debug)] pub struct Deimos { diff --git a/src/types/filaments.rs b/src/filaments.rs similarity index 98% rename from src/types/filaments.rs rename to src/filaments.rs index 747b17f..629b57c 100644 --- a/src/types/filaments.rs +++ b/src/filaments.rs @@ -5,8 +5,7 @@ use egui_graphs::{ Graph, Node, petgraph::{Directed, graph::NodeIndex, prelude::StableGraph}, }; - -use crate::types::{Index, Link, ZettelId, kasten::ZettelOnDisk}; +use fil_core::{Index, Link, ZettelId, ZettelOnDisk}; pub type ZkGraph = Graph; diff --git a/src/lsp/mod.rs b/src/lsp/mod.rs index 64a43b1..6053e22 100644 --- a/src/lsp/mod.rs +++ b/src/lsp/mod.rs @@ -2,6 +2,7 @@ use std::fs; use color_eyre::eyre::eyre; use dto::{NanoId, TagEntity, ZettelEntity}; +use fil_core::{Kasten, Tag, Zettel, ZettelId}; use tower_lsp::{ Client, LanguageServer, jsonrpc::{self, Result}, @@ -13,8 +14,6 @@ use tower_lsp::{ }, }; -use crate::types::{Kasten, Tag, Zettel, ZettelId}; - #[derive(Debug)] pub struct Backend { client: Client, @@ -67,17 +66,19 @@ impl LanguageServer for Backend { return Ok(None); }; - let Ok::(zid) = params + let Ok(file_path) = params .text_document_position .text_document .uri .to_file_path() - .map_err(|()| eyre!("failed to turn into file path")) - .and_then(TryInto::try_into) else { return Ok(None); }; + let Ok::(zid) = file_path.try_into() else { + return Ok(None); + }; + let responses = match trigger { // Link completion "[[" => ZettelEntity::load() diff --git a/src/main.rs b/src/main.rs index acc42ba..d51105a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,25 +3,21 @@ use std::{process, sync::Arc}; -use crate::{ - cli::Cli, - config::Config, - tui::TuiApp, - types::{Deimos, Kasten, KastenHandle}, - viz::FilViz, -}; +use crate::{cli::Cli, config::Config, deimos::Deimos, tui::TuiApp, viz::FilViz}; use clap::Parser; +use fil_core::{Kasten, KastenHandle}; use tokio::sync::{RwLock, mpsc}; use tracing::debug; mod cli; mod config; +mod deimos; mod errors; +mod filaments; mod logging; mod lsp; mod tui; -mod types; mod viz; fn main() -> color_eyre::Result<()> { diff --git a/src/tui/app.rs b/src/tui/app.rs index 0760587..32dddfc 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -2,6 +2,7 @@ use std::{process::Command, thread::spawn}; use color_eyre::eyre::{Context, Result}; use crossterm::event::KeyEvent; +use fil_core::{KastenHandle, TodoTree, ZettelId}; use ratatui::layout::Rect; use serde::{Deserialize, Serialize}; use strum::{Display, EnumIter}; @@ -11,7 +12,6 @@ use tracing::{debug, info, trace}; use crate::{ config::Config, tui::{Event, Tui, components::Viewport}, - types::{KastenHandle, TodoTree, ZettelId}, }; pub use crate::tui::components::TodoRegion; @@ -62,7 +62,6 @@ impl App { Ok(Self { tick_rate, frame_rate, - // components: vec![Box::new(Zk::new(kh.clone()).await?)], components: vec![Box::new(Viewport::new(kh.clone()))], should_quit: false, should_suspend: false, diff --git a/src/tui/components/todo/explorer.rs b/src/tui/components/todo/explorer.rs index 1f92879..5a1010f 100644 --- a/src/tui/components/todo/explorer.rs +++ b/src/tui/components/todo/explorer.rs @@ -1,3 +1,4 @@ +use fil_core::{Group, TodoNode, TodoNodeKind, TodoTree}; use ratatui::{ style::{Color, Modifier, Style, Stylize}, text::{Line, Span, Text}, @@ -6,8 +7,6 @@ use ratatui::{ use tracing::debug; use tree::NodeId; -use crate::types::{Group, TodoNode, TodoNodeKind, TodoTree}; - #[derive(Debug)] pub struct Explorer<'text> { pub render_list: ratatui::widgets::List<'text>, diff --git a/src/tui/components/todo/inspector/groupview.rs b/src/tui/components/todo/inspector/groupview.rs index 0131c36..a6bf69f 100644 --- a/src/tui/components/todo/inspector/groupview.rs +++ b/src/tui/components/todo/inspector/groupview.rs @@ -1,3 +1,4 @@ +use fil_core::{Group, Index}; use ratatui::{ layout::{Constraint, Layout}, style::Style, @@ -5,10 +6,7 @@ use ratatui::{ }; use ratatui_textarea::TextArea; -use crate::{ - tui::components::preview::Preview, - types::{Group, Index}, -}; +use crate::tui::components::preview::Preview; #[derive(Debug, Clone)] pub struct GroupView<'text> { diff --git a/src/tui/components/todo/inspector/mod.rs b/src/tui/components/todo/inspector/mod.rs index 3daa746..17bc129 100644 --- a/src/tui/components/todo/inspector/mod.rs +++ b/src/tui/components/todo/inspector/mod.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use crossterm::event::{KeyCode, KeyEvent}; use dto::NanoId; +use fil_core::{Due, Group, KastenHandle, Priority, Tag, Task, TodoNode, TodoNodeKind}; use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Rect}, @@ -10,12 +11,9 @@ use ratatui::{ use ratatui_textarea::CursorMove; use tokio::sync::mpsc::UnboundedSender; -use crate::{ - tui::{ - Signal, - components::{Component, DEFAULT_NAME}, - }, - types::{Due, Group, KastenHandle, Priority, Tag, Task, TodoNode, TodoNodeKind}, +use crate::tui::{ + Signal, + components::{Component, DEFAULT_NAME}, }; mod rootview; @@ -237,7 +235,7 @@ impl Component for Inspector<'_> { return Ok(None); }; - // if its finished, we arent going to edit the due date lol + // If its finished, we arent going to edit the due date lol if let TodoNodeKind::Task(task) = &kt.todo_tree.get_node_by_nano_id(inspecting).data().kind && task.finished_at.is_some() diff --git a/src/tui/components/todo/inspector/taskview.rs b/src/tui/components/todo/inspector/taskview.rs index c0e869f..5db5cd2 100644 --- a/src/tui/components/todo/inspector/taskview.rs +++ b/src/tui/components/todo/inspector/taskview.rs @@ -1,3 +1,4 @@ +use fil_core::{Index, Task}; use ratatui::{ layout::{Constraint, Layout}, style::Style, @@ -5,10 +6,7 @@ use ratatui::{ }; use ratatui_textarea::TextArea; -use crate::{ - tui::components::preview::Preview, - types::{Index, Task}, -}; +use crate::tui::components::preview::Preview; #[derive(Debug, Clone)] pub struct TaskView<'text> { diff --git a/src/tui/components/todo/mod.rs b/src/tui/components/todo/mod.rs index b6c841b..872bbfa 100644 --- a/src/tui/components/todo/mod.rs +++ b/src/tui/components/todo/mod.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use color_eyre::eyre::Result; use crossterm::event::KeyEvent; +use fil_core::{Group, KastenHandle, Priority, Task}; use ratatui::{ Frame, layout::{Constraint, Layout, Rect, Size}, @@ -11,10 +12,7 @@ use strum::{Display, EnumIter}; use tokio::sync::mpsc::UnboundedSender; use tracing::debug; -use crate::{ - tui::{Page, Signal, components::Component}, - types::{Group, KastenHandle, Priority, Task}, -}; +use crate::tui::{Page, Signal, components::Component}; mod explorer; use explorer::Explorer; diff --git a/src/tui/components/todo/tasklist.rs b/src/tui/components/todo/tasklist.rs index e7afee3..083a100 100644 --- a/src/tui/components/todo/tasklist.rs +++ b/src/tui/components/todo/tasklist.rs @@ -1,4 +1,5 @@ #![expect(dead_code)] +use fil_core::{TodoNode, TodoNodeKind, TodoTree}; use ratatui::{ style::{Color, Style}, text::{Line, Span, Text}, @@ -6,8 +7,6 @@ use ratatui::{ }; use tree::NodeId; -use crate::types::{TodoNode, TodoNodeKind, TodoTree}; - pub struct TaskList<'text> { pub render_list: List<'text>, pub id_list: Vec, @@ -43,7 +42,7 @@ impl TaskList<'_> { items.reverse(); let render_list = List::new(items.into_iter().map(|(node, id)| { - let TodoNodeKind::Task(_) = node.data().kind else { + let TodoNodeKind::Task(_): TodoNodeKind = node.data().kind else { unreachable!("we already filtered for this earlier") }; diff --git a/src/tui/components/viewport/mod.rs b/src/tui/components/viewport/mod.rs index ed1ab05..c8a219b 100644 --- a/src/tui/components/viewport/mod.rs +++ b/src/tui/components/viewport/mod.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use color_eyre::eyre::Result; use crossterm::event::KeyEvent; +use fil_core::KastenHandle; use ratatui::{ Frame, layout::{Constraint, Layout, Rect, Size}, @@ -8,13 +9,10 @@ use ratatui::{ use tokio::sync::mpsc::UnboundedSender; use tracing::debug; -use crate::{ - tui::{ - Signal, - app::Page, - components::{Component, Todo, Zk}, - }, - types::KastenHandle, +use crate::tui::{ + Signal, + app::Page, + components::{Component, Todo, Zk}, }; pub struct Viewport<'text> { diff --git a/src/tui/components/zk/mod.rs b/src/tui/components/zk/mod.rs index 7b6d4f1..b495dcf 100644 --- a/src/tui/components/zk/mod.rs +++ b/src/tui/components/zk/mod.rs @@ -2,14 +2,12 @@ use async_trait::async_trait; use color_eyre::eyre::{Context as _, ContextCompat, Result}; use crossterm::event::KeyEvent; use dto::{QueryOrder, TagEntity, ZettelColumns, ZettelEntity}; +use fil_core::{KastenHandle, Zettel}; use ratatui::{prelude::*, widgets::ListState}; use tokio::sync::mpsc::UnboundedSender; use tracing::info; -use crate::{ - tui::{Page, Signal, components::Component}, - types::{KastenHandle, Zettel}, -}; +use crate::tui::{Page, Signal, components::Component}; pub mod preview; mod search; @@ -202,7 +200,7 @@ impl Zk<'_> { .all(&kt.db) .await?; - // im being a good boy and dropping this as soon as im done with the db + // Im being a good boy and dropping this as soon as im done with the db drop(kt); // for now we are going to just read that shit every time... diff --git a/src/tui/components/zk/search.rs b/src/tui/components/zk/search.rs index bf098aa..5008863 100644 --- a/src/tui/components/zk/search.rs +++ b/src/tui/components/zk/search.rs @@ -1,4 +1,5 @@ use color_eyre::eyre::Error; +use fil_core::{KastenHandle, Zettel}; use nucleo_matcher::{ Matcher, Utf32Str, pattern::{CaseMatching, Normalization, Pattern}, @@ -10,8 +11,6 @@ use ratatui::{ }; use ratatui_textarea::TextArea; -use crate::types::{KastenHandle, Zettel}; - #[derive(Clone)] pub struct Search<'text> { pub query: TextArea<'text>, diff --git a/src/tui/components/zk/zettel_list.rs b/src/tui/components/zk/zettel_list.rs index 9508ac9..a8b6eea 100644 --- a/src/tui/components/zk/zettel_list.rs +++ b/src/tui/components/zk/zettel_list.rs @@ -1,11 +1,10 @@ +use fil_core::{Zettel, ZettelId}; use ratatui::{ style::{Color, Modifier, Style, Stylize}, text::{Line, Span, Text}, widgets::{List, ListState}, }; -use crate::types::{Zettel, ZettelId}; - pub struct ZettelList<'text> { pub render_list: ratatui::widgets::List<'text>, pub id_list: Vec, diff --git a/src/tui/components/zk/zettel_view.rs b/src/tui/components/zk/zettel_view.rs index baf98ed..a71f520 100644 --- a/src/tui/components/zk/zettel_view.rs +++ b/src/tui/components/zk/zettel_view.rs @@ -1,3 +1,4 @@ +use fil_core::Zettel; use ratatui::{ layout::{Constraint, Layout}, style::Style, @@ -5,8 +6,6 @@ use ratatui::{ widgets::{Block, BorderType, Borders, Paragraph, Widget}, }; -use crate::types::Zettel; - /// A `Widget` that represents a `Zettel` #[derive(Debug, Clone)] pub struct ZettelView<'text> { diff --git a/src/tui/signal.rs b/src/tui/signal.rs index 56d47f6..8b5c4f1 100644 --- a/src/tui/signal.rs +++ b/src/tui/signal.rs @@ -1,14 +1,12 @@ use std::path::PathBuf; use dto::NanoId; +use fil_core::{Link, ZettelId}; use strum::Display; use serde::{Deserialize, Serialize}; -use crate::{ - tui::Page, - types::{Link, ZettelId}, -}; +use crate::tui::Page; /// The varying signals that can be emitted. #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] diff --git a/src/types/due.rs b/src/types/due.rs deleted file mode 100644 index e69ad06..0000000 --- a/src/types/due.rs +++ /dev/null @@ -1,251 +0,0 @@ -use crate::types::frontmatter; -use chrono::Datelike; -use std::fmt::Display; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Due(pub Option); - -impl Due { - pub const fn has_date(&self) -> bool { - self.0.is_some() - } -} - -impl From> for Due { - fn from(value: Option) -> Self { - Self(value) - } -} - -impl From for Option { - fn from(value: Due) -> Self { - value.0 - } -} - -impl Display for Due { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let str = self.0.map_or_else( - || "None".to_string(), - |d| d.format(frontmatter::DATE_FMT_STR).to_string(), - ); - write!(f, "{str}") - } -} - -impl TryFrom<&str> for Due { - type Error = color_eyre::Report; - fn try_from(value: &str) -> Result { - parse_due_date(value) - .map(Into::into) - .map_err(|e| color_eyre::eyre::eyre!(e)) - } -} - -//NOTE: everything after this was written by claude because I am -// way too fucking lazy to do all this due date parsing bs, I did audit it though. - -const fn naive_date_to_dt(d: chrono::NaiveDate) -> chrono::NaiveDateTime { - d.and_hms_opt(23, 59, 59).unwrap() -} - -#[expect(clippy::cast_possible_truncation)] -fn parse_relative_offset(lower: &str, today: chrono::NaiveDate) -> Option { - let lower = lower.strip_prefix("in ")?.trim(); - let mut parts = lower.splitn(2, ' '); - let n: i64 = parts.next()?.parse().ok()?; - let unit = parts.next()?.trim_end_matches('s'); - match unit { - "day" => Some(today + chrono::Duration::days(n)), - "week" => Some(today + chrono::Duration::weeks(n)), - "month" => { - let total_months = today.month0().cast_signed() + n as i32; - let year = today.year() + total_months / 12; - let month = (total_months % 12).cast_unsigned() + 1; - chrono::NaiveDate::from_ymd_opt(year, month, today.day()).or_else(|| { - chrono::NaiveDate::from_ymd_opt(year, month + 1, 1).and_then(|d| d.pred_opt()) - }) - } - "year" => { - chrono::NaiveDate::from_ymd_opt(today.year() + n as i32, today.month(), today.day()) - } - _ => None, - } -} - -fn parse_weekday(lower: &str, today: chrono::NaiveDate) -> Option { - use chrono::Weekday::{Fri, Mon, Sat, Sun, Thu, Tue, Wed}; - let (force_next, word) = lower - .strip_prefix("next ") - .map_or((false, lower), |rest| (true, rest.trim())); - let target = match word { - "monday" | "mon" => Mon, - "tuesday" | "tue" | "tues" => Tue, - "wednesday" | "wed" => Wed, - "thursday" | "thu" | "thur" | "thurs" => Thu, - "friday" | "fri" => Fri, - "saturday" | "sat" => Sat, - "sunday" | "sun" => Sun, - _ => return None, - }; - let mut days_ahead = i64::from(target.num_days_from_monday()) - - i64::from(today.weekday().num_days_from_monday()); - if days_ahead <= 0 || force_next { - days_ahead += 7; - } - Some(today + chrono::Duration::days(days_ahead)) -} - -#[expect(clippy::too_many_lines)] -fn parse_due_date(s: &str) -> Result, String> { - use chrono::{Datelike, Local, NaiveDate, NaiveDateTime}; - - let s = s.trim(); - - // 1. Empty / explicit "no due date" sentinels - if s.is_empty() - || matches!( - s.to_lowercase().as_str(), - "none" | "never" | "n/a" | "na" | "-" | "--" | "null" | "nil" | "no" | "no due date" - ) - { - return Ok(None); - } - - let lower = s.to_lowercase(); - let today = Local::now().date_naive(); - - // 2. Relative human words - let relative: Option = match lower.as_str() { - "tomorrow" | "tmrw" | "tmr" => Some(today + chrono::Duration::days(1)), - "yesterday" => Some(today - chrono::Duration::days(1)), - "today" | "now" | "eod" | "end of day" => Some(today), - "eow" | "end of week" => { - let days = (7 - today.weekday().num_days_from_sunday()) % 7; - Some(today + chrono::Duration::days(i64::from(days))) - } - "eom" | "end of month" => { - let next = if today.month() == 12 { - NaiveDate::from_ymd_opt(today.year() + 1, 1, 1) - } else { - NaiveDate::from_ymd_opt(today.year(), today.month() + 1, 1) - }; - next.and_then(|d| d.pred_opt()) - } - "eoy" | "end of year" => NaiveDate::from_ymd_opt(today.year(), 12, 31), - _ => None, - }; - if let Some(d) = relative { - return Ok(Some(naive_date_to_dt(d))); - } - - // 3. "in N days/weeks/months/years" - if let Some(d) = parse_relative_offset(&lower, today) { - return Ok(Some(naive_date_to_dt(d))); - } - - // 4. Named weekdays - if let Some(d) = parse_weekday(&lower, today) { - return Ok(Some(naive_date_to_dt(d))); - } - - let s_noz = s.trim_end_matches('Z'); - - // 5. Datetime formats - let datetime_fmts: &[&str] = &[ - // ISO 8601 - "%Y-%m-%dT%H:%M:%S", - "%Y-%m-%dT%H:%M", - "%Y-%m-%d %H:%M:%S", - "%Y-%m-%d %H:%M", - // MM/DD/YYYY - "%m/%d/%Y %H:%M:%S", - "%m/%d/%Y %H:%M", - // MM-DD-YYYY - "%m-%d-%Y %H:%M:%S", - "%m-%d-%Y %H:%M", - // Month name DD, YYYY - "%b %d %Y %H:%M:%S", - "%b %d %Y %H:%M", - "%B %d %Y %H:%M:%S", - "%B %d %Y %H:%M", - "%b %d, %Y %H:%M:%S", - "%b %d, %Y %H:%M", - "%B %d, %Y %H:%M:%S", - "%B %d, %Y %H:%M", - // 12-hour clock variants - "%m/%d/%Y %I:%M %p", - "%m/%d/%Y %I:%M:%S %p", - "%m-%d-%Y %I:%M %p", - "%m-%d-%Y %I:%M:%S %p", - "%b %d, %Y %I:%M %p", - "%B %d, %Y %I:%M %p", - "%b %d, %Y %I:%M:%S %p", - "%B %d, %Y %I:%M:%S %p", - ]; - for fmt in datetime_fmts { - if let Ok(dt) = NaiveDateTime::parse_from_str(s_noz, fmt) { - return Ok(Some(dt)); - } - } - - // 6. Date-only formats - let date_fmts: &[&str] = &[ - // ISO 8601 - "%Y-%m-%d", - "%Y/%m/%d", - "%Y.%m.%d", - "%Y%m%d", - // MM/DD/YYYY - "%m/%d/%Y", - "%m-%d-%Y", - // Month name DD, YYYY - "%b %d %Y", - "%B %d %Y", - "%b %d, %Y", - "%B %d, %Y", - // Month/Year only - "%b %Y", - "%B %Y", - "%m/%Y", - "%m-%Y", - ]; - for fmt in date_fmts { - if let Ok(d) = NaiveDate::parse_from_str(s_noz, fmt) { - return Ok(Some(naive_date_to_dt(d))); - } - } - - // 6b. Yearless shorthand e.g. "4/13" or "4-13" — fill in current year - let yearless_fmts: &[&str] = &["%m/%d", "%m-%d"]; - for fmt in yearless_fmts { - if let Ok(d) = NaiveDate::parse_from_str( - &format!("{}/{}", s_noz, today.year()), - &format!("{}/{}", fmt, "%Y"), - ) { - return Ok(Some(naive_date_to_dt(d))); - } - } - - // 7. Unix timestamp (seconds or milliseconds) - if let Ok(n) = s.parse::() { - let secs = if n > 10_000_000_000 { n / 1000 } else { n }; - if let Some(dt) = chrono::DateTime::from_timestamp(secs, 0) { - return Ok(Some(dt.naive_utc())); - } - } - - // 8. Strip noise and retry date-only - let stripped: String = s - .chars() - .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '/' || *c == ' ') - .collect(); - let stripped = stripped.trim(); - for fmt in date_fmts { - if let Ok(d) = NaiveDate::parse_from_str(stripped, fmt) { - return Ok(Some(naive_date_to_dt(d))); - } - } - - Err(format!("could not parse {s:?} as a due date")) -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index fc6a54e..0000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -mod color; -pub use color::Color; - -mod tag; -pub use tag::Tag; - -mod priority; -pub use priority::Priority; - -mod zettel; -pub use zettel::Zettel; -pub use zettel::ZettelId; - -mod due; -pub use due::Due; - -mod group; -pub use group::Group; - -mod task; -pub use task::Task; - -mod link; -pub use link::Link; - -mod filaments; -pub use filaments::Filaments; - -mod kasten; -pub use kasten::Index; -pub use kasten::Kasten; -pub use kasten::KastenHandle; -pub use kasten::TodoNode; -pub use kasten::TodoNodeKind; -pub use kasten::TodoTree; - -mod frontmatter; -pub use frontmatter::FrontMatter; - -mod deimos; -pub use deimos::Deimos; diff --git a/src/viz/mod.rs b/src/viz/mod.rs index b745d7c..9604af2 100644 --- a/src/viz/mod.rs +++ b/src/viz/mod.rs @@ -4,10 +4,9 @@ use futures::executor::block_on; use tokio::sync::mpsc::UnboundedReceiver; use tracing::debug; -use crate::{ - tui::Signal, - types::{Filaments, Index, KastenHandle}, -}; +use crate::{filaments::Filaments, tui::Signal}; + +use fil_core::{Index, KastenHandle}; /// The `Filaments Visualizer`, which is an instance of `eframe`, which uses `egui` pub struct FilViz {