Skip to content

Commit

Permalink
Working better on my personal notes library
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanpeach committed Nov 2, 2024
1 parent b26b1b6 commit 79bd6a0
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 193 deletions.
55 changes: 27 additions & 28 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ mod cli;
mod file;
use std::path::PathBuf;

use crate::sed::{ReplacePair, ReplacePairError};
use crate::{
file::{
content::wikilink::Alias,
name::{Filename, FilenameLowercase},
},
sed::{ReplacePair, ReplacePairError},
};
use bon::Builder;
use clap::Parser;
use getset::Getters;
use std::io;
use thiserror;
use toml;
Expand All @@ -23,43 +28,35 @@ pub enum Error {

/// Config which contains both the cli and the config file
/// Used to reconcile the two
#[derive(Getters, Builder)]
#[getset(get = "pub")]
#[derive(Builder)]
pub struct Config {
/// See [`self::cli::Config::directories`]
#[builder(default=vec![PathBuf::from(".")])]
directories: Vec<PathBuf>,
pub directories: Vec<PathBuf>,
/// See [`self::cli::Config::ngram_size`]
#[builder(default = 2)]
ngram_size: usize,
pub ngram_size: usize,
/// See [`self::cli::Config::boundary_pattern`]
#[builder(default=r"\s".to_owned())]
boundary_pattern: String,
pub boundary_pattern: String,
/// See [`self::cli::Config::wikilink_pattern`]
#[builder(default=r"#?\[\[(.*?)]]|#([A-Za-z0-9_]+)".to_owned())]
wikilink_pattern: String,
pub wikilink_pattern: String,
/// See [`self::cli::Config::filename_spacing_pattern`]
#[builder(default=r"___|__|-|_|\s".to_owned())]
filename_spacing_pattern: String,
pub filename_spacing_pattern: String,
/// See [`self::cli::Config::filename_match_threshold`]
#[builder(default = 2)]
filename_match_threshold: i64,
pub filename_match_threshold: i64,
/// See [`self::cli::Config::exclude`]
#[builder(default=vec![])]
exclude: Vec<String>,
/// See [`self::file::Config::title_to_filepath`]
#[builder(default=Ok(vec![vec![
ReplacePair::new(r"([A-Za-z0-1_-]+).md", r"\[\[$1\]\]").expect("Constant"),
ReplacePair::new(r"___", r"/").expect("Constant"),
]]))]
filepath_to_title: Result<Vec<Vec<ReplacePair>>, ReplacePairError>,
/// See [`self::file::Config::title_to_filepath`]
#[builder(default=Ok(vec![vec![
ReplacePair::new(r"\[\[(.*?)\]\]", r"$1.md").expect("Constant"),
ReplacePair::new(r"/", r"___").expect("Constant"),
ReplacePair::new(r"(.*)", r"../pages/$1").expect("Constant"),
]]))]
title_to_filepath: Result<Vec<Vec<ReplacePair>>, ReplacePairError>,
pub exclude: Vec<String>,
/// See [`self::file::Config::filename_to_alias`]
#[builder(default=Ok(ReplacePair::new(r"___", r"/").expect("Constant")))]
pub filename_to_alias: Result<ReplacePair<Filename, Alias>, ReplacePairError>,
/// See [`self::file::Config::alias_to_filename`]
#[builder(default=Ok(ReplacePair::new(r"/", r"___").expect("Constant")))]
pub alias_to_filename: Result<ReplacePair<Alias, FilenameLowercase>, ReplacePairError>,
}

/// Things which implement the partial config trait
Expand All @@ -74,8 +71,10 @@ pub trait Partial {
fn filename_spacing_pattern(&self) -> Option<String>;
fn filename_match_threshold(&self) -> Option<i64>;
fn exclude(&self) -> Option<Vec<String>>;
fn filepath_to_title(&self) -> Option<Result<Vec<Vec<ReplacePair>>, ReplacePairError>>;
fn title_to_filepath(&self) -> Option<Result<Vec<Vec<ReplacePair>>, ReplacePairError>>;
fn filename_to_alias(&self) -> Option<Result<ReplacePair<Filename, Alias>, ReplacePairError>>;
fn alias_to_filename(
&self,
) -> Option<Result<ReplacePair<Alias, FilenameLowercase>, ReplacePairError>>;
}

/// Now we implement a combine function for patrial configs which
Expand All @@ -92,8 +91,8 @@ fn combine_partials(partials: &[&dyn Partial]) -> Config {
.maybe_filename_spacing_pattern(partials.iter().find_map(|p| p.filename_spacing_pattern()))
.maybe_filename_match_threshold(partials.iter().find_map(|p| p.filename_match_threshold()))
.maybe_exclude(partials.iter().find_map(|p| p.exclude()))
.maybe_filepath_to_title(partials.iter().find_map(|p| p.filepath_to_title()))
.maybe_title_to_filepath(partials.iter().find_map(|p| p.title_to_filepath()))
.maybe_filename_to_alias(partials.iter().find_map(|p| p.filename_to_alias()))
.maybe_alias_to_filename(partials.iter().find_map(|p| p.alias_to_filename()))
.build()
}

Expand Down
14 changes: 11 additions & 3 deletions src/config/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use clap::Parser;
use std::path::PathBuf;

use crate::sed::{ReplacePair, ReplacePairError};
use crate::{
file::{
content::wikilink::Alias,
name::{Filename, FilenameLowercase},
},
sed::{ReplacePair, ReplacePairError},
};

use super::Partial;

Expand Down Expand Up @@ -77,10 +83,12 @@ impl Partial for Config {
Some(out)
}
}
fn filepath_to_title(&self) -> Option<Result<Vec<Vec<ReplacePair>>, ReplacePairError>> {
fn filename_to_alias(&self) -> Option<Result<ReplacePair<Filename, Alias>, ReplacePairError>> {
None
}
fn title_to_filepath(&self) -> Option<Result<Vec<Vec<ReplacePair>>, ReplacePairError>> {
fn alias_to_filename(
&self,
) -> Option<Result<ReplacePair<Alias, FilenameLowercase>, ReplacePairError>> {
None
}
}
83 changes: 38 additions & 45 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::sed::{ReplacePair, ReplacePairError};
use crate::{
file::{
content::wikilink::Alias,
name::{Filename, FilenameLowercase},
},
sed::{ReplacePair, ReplacePairError},
};

use super::{Error, Partial};

Expand Down Expand Up @@ -36,19 +42,15 @@ pub(super) struct Config {
#[serde(default)]
pub exclude: Vec<String>,

/// Link conversion to file path using sed regex
/// Each outer vec contains an inner vec of a sequence of find/replace pairs
/// Meaning you can have several different and independent sequences of find/replace pairs
/// This makes it easier to manage multiple different types of links and conversions
/// Convert an alias to a filename
/// Kinda like a sed command
#[serde(default)]
pub title_to_filepath: Vec<Vec<(String, String)>>,
pub alias_to_filename: (String, String),

/// Convert a filepath to the "title" lr name in a wikilink
/// Each outer vec contains an inner vec of a sequence of find/replace pairs
/// Meaning you can have several different and independent sequences of find/replace pairs
/// This makes it easier to manage multiple different types of links and conversions
/// Convert a filename to an alias
/// Kinda like a sed command
#[serde(default)]
pub filepath_to_title: Vec<Vec<(String, String)>>,
pub filename_to_alias: (String, String),
}

impl Config {
Expand Down Expand Up @@ -97,42 +99,33 @@ impl Partial for Config {
}
}

fn filepath_to_title(&self) -> Option<Result<Vec<Vec<ReplacePair>>, ReplacePairError>> {
let out = self.filepath_to_title.clone();
if out.is_empty() {
None
} else {
let mut res = Vec::new();
for inner in out {
let mut inner_res = Vec::new();
for (find, replace) in inner {
match ReplacePair::new(&find, &replace) {
Ok(pair) => inner_res.push(pair),
Err(e) => return Some(Err(e)),
}
}
res.push(inner_res);
}
Some(Ok(res))
fn alias_to_filename(
&self,
) -> Option<Result<ReplacePair<Alias, FilenameLowercase>, ReplacePairError>> {
let (to, from) = self.alias_to_filename.clone();
match (to.is_empty(), from.is_empty()) {
(true, true) => None,
(false, false) => Some(ReplacePair::new(&to, &from)),
(true, false) => Some(Err(ReplacePairError::ToError(regex::Error::Syntax(
"To is empty".to_string(),
)))),
(false, true) => Some(Err(ReplacePairError::FromError(regex::Error::Syntax(
"From is empty".to_string(),
)))),
}
}
fn title_to_filepath(&self) -> Option<Result<Vec<Vec<ReplacePair>>, ReplacePairError>> {
let out = self.title_to_filepath.clone();
if out.is_empty() {
None
} else {
let mut res = Vec::new();
for inner in out {
let mut inner_res = Vec::new();
for (find, replace) in inner {
match ReplacePair::new(&find, &replace) {
Ok(pair) => inner_res.push(pair),
Err(e) => return Some(Err(e)),
}
}
res.push(inner_res);
}
Some(Ok(res))

fn filename_to_alias(&self) -> Option<Result<ReplacePair<Filename, Alias>, ReplacePairError>> {
let (to, from) = self.alias_to_filename.clone();
match (to.is_empty(), from.is_empty()) {
(true, true) => None,
(false, false) => Some(ReplacePair::new(&to, &from)),
(true, false) => Some(Err(ReplacePairError::ToError(regex::Error::Syntax(
"To is empty".to_string(),
)))),
(false, true) => Some(Err(ReplacePairError::FromError(regex::Error::Syntax(
"From is empty".to_string(),
)))),
}
}
}
16 changes: 8 additions & 8 deletions src/file/content/front_matter.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
mod logseq;
mod yaml;

use super::Error;
use super::{wikilink::Alias, Error};

#[derive(Debug, Default, Clone)]
pub struct FrontMatter {
/// The aliases of the file
pub aliases: Vec<String>,
pub aliases: Vec<Alias>,
}

impl FrontMatter {
Expand All @@ -15,15 +15,15 @@ impl FrontMatter {
let out = yaml::Config::new(contents)?;
if !out.is_empty() {
return Ok(FrontMatter {
aliases: out.aliases.iter().map(|x| x.to_lowercase()).collect(),
aliases: out.aliases.iter().map(|x| Alias::new(x)).collect(),
});
}

// Try to parse as Logseq
let out = logseq::Config::new(contents)?;
if !out.is_empty() {
return Ok(FrontMatter {
aliases: out.aliases.iter().map(|x| x.to_lowercase()).collect(),
aliases: out.aliases.iter().map(|x| Alias::new(x)).collect(),
});
}

Expand All @@ -44,9 +44,9 @@ mod tests {
assert_eq!(
config.aliases,
vec![
"name1".to_string(),
"name2".to_string(),
"name3".to_string()
Alias::new("name1"),
Alias::new("name2"),
Alias::new("name3")
]
);
}
Expand All @@ -58,7 +58,7 @@ mod tests {
let config = FrontMatter::new(text).unwrap();
assert_eq!(
config.aliases,
vec!["a".to_string(), "b".to_string(), "c".to_string()]
vec![Alias::new("a"), Alias::new("b"), Alias::new("c")]
);
}
}
45 changes: 42 additions & 3 deletions src/file/content/wikilink.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
use std::fmt::{Display, Formatter};

use bon::Builder;
use getset::Getters;
use itertools::Itertools;
use miette::SourceSpan;

use crate::sed::RegexError;
use crate::{
config::Config,
file::name::Filename,
sed::{RegexError, ReplacePairError},
};

/// A linkable string, like that in a wikilink, or its corresponding filename
/// Aliases are always lowercase
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Alias(String);

impl Alias {
#[must_use]
pub fn new(alias: &str) -> Self {
Self(alias.to_lowercase())
}
}

impl Display for Alias {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl From<String> for Alias {
fn from(s: String) -> Self {
Self::new(&s)
}
}

impl Alias {
pub fn from_filename(filename: &Filename, config: &Config) -> Result<Alias, ReplacePairError> {
match config.filename_to_alias.clone() {
Ok(pair) => Ok(pair.apply(filename)),
Err(e) => Err(e),
}
}
}

#[derive(Builder, Getters, Clone, Debug)]
#[getset(get = "pub")]
pub struct Wikilink {
alias: String,
alias: Alias,
span: SourceSpan,
}

Expand All @@ -31,7 +70,7 @@ impl Wikilink {
wikilinks.push(
Wikilink::builder()
.span(SourceSpan::new(capture0.start().into(), capture0.len()))
.alias(alias.as_str().to_owned().to_lowercase())
.alias(Alias::new(alias.as_str()))
.build(),
);
}
Expand Down
Loading

0 comments on commit 79bd6a0

Please sign in to comment.