snix/fun/tvldb/src/keyword.rs
Vincent Ambo 78bfb66a95 style(tvldb): Add blank lines between items
This makes the code slightly more readable. For users that use editors
without semantic navigation, this also makes it easier to jump around
between items in the files.

I looked into whether a rustfmt setting exists for this, but
unfortunately the answer is currently no.

Change-Id: I37b19fa6ab038c71b924c45dbc12b298e660e8cf
Reviewed-on: https://cl.tvl.fyi/c/depot/+/827
Reviewed-by: BuildkiteCI
Reviewed-by: eta <eta@theta.eu.org>
Tested-by: BuildkiteCI
2020-07-01 17:32:42 +00:00

193 lines
5.9 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::models::{Entry, Keyword, NewEntry, NewKeyword};
use diesel::pg::PgConnection;
use diesel::prelude::*;
use failure::Error;
use std::borrow::Cow;
pub struct KeywordDetails {
pub keyword: Keyword,
pub entries: Vec<Entry>,
}
impl KeywordDetails {
pub fn learn(&mut self, nick: &str, text: &str, dbc: &PgConnection) -> Result<usize, Error> {
let now = ::chrono::Utc::now().naive_utc();
let ins = NewEntry {
keyword_id: self.keyword.id,
idx: (self.entries.len() + 1) as _,
text,
creation_ts: now,
created_by: nick,
};
let new = {
use crate::schema::entries;
::diesel::insert_into(entries::table)
.values(ins)
.get_result(dbc)?
};
self.entries.push(new);
Ok(self.entries.len())
}
pub fn process_moves(&mut self, moves: &[(i32, i32)], dbc: &PgConnection) -> Result<(), Error> {
for (oid, new_idx) in moves {
{
use crate::schema::entries::dsl::*;
::diesel::update(entries.filter(id.eq(oid)))
.set(idx.eq(new_idx))
.execute(dbc)?;
}
}
self.entries = Self::get_entries(self.keyword.id, dbc)?;
Ok(())
}
pub fn swap(&mut self, idx_a: usize, idx_b: usize, dbc: &PgConnection) -> Result<(), Error> {
let mut moves = vec![];
for ent in self.entries.iter() {
if ent.idx == idx_a as i32 {
moves.push((ent.id, idx_b as i32));
}
if ent.idx == idx_b as i32 {
moves.push((ent.id, idx_a as i32));
}
}
if moves.len() != 2 {
Err(format_err!("Invalid swap operation."))?;
}
self.process_moves(&moves, dbc)?;
Ok(())
}
pub fn update(&mut self, idx: usize, val: &str, dbc: &PgConnection) -> Result<(), Error> {
let ent = self
.entries
.get_mut(idx.saturating_sub(1))
.ok_or(format_err!("No such element to update."))?;
{
use crate::schema::entries::dsl::*;
::diesel::update(entries.filter(id.eq(ent.id)))
.set(text.eq(val))
.execute(dbc)?;
}
ent.text = val.to_string();
Ok(())
}
pub fn delete(&mut self, idx: usize, dbc: &PgConnection) -> Result<(), Error> {
// step 1: delete the element
{
let ent = self
.entries
.get(idx.saturating_sub(1))
.ok_or(format_err!("No such element to delete."))?;
{
use crate::schema::entries::dsl::*;
::diesel::delete(entries.filter(id.eq(ent.id))).execute(dbc)?;
}
}
// step 2: move all the elements in front of it back one
let mut moves = vec![];
for ent in self.entries.iter() {
if idx > ent.idx as _ {
moves.push((ent.id, ent.idx.saturating_sub(1)));
}
}
self.process_moves(&moves, dbc)?;
Ok(())
}
pub fn add_zwsp_to_name(name: &str) -> Option<String> {
let second_index = name.char_indices().nth(1).map(|(i, _)| i)?;
let (start, end) = name.split_at(second_index);
Some(format!("{}{}", start, end))
}
pub fn format_entry(&self, idx: usize) -> Option<String> {
if let Some(ent) = self.entries.get(idx.saturating_sub(1)) {
let gen_clr = if self.keyword.chan == "*" {
"\x0307"
} else {
""
};
let zwsp_name = Self::add_zwsp_to_name(&self.keyword.name)
.unwrap_or_else(|| self.keyword.name.clone());
Some(format!(
"\x02{}{}\x0f\x0315[{}/{}]\x0f: {} \x0f\x0314[{}]\x0f",
gen_clr,
zwsp_name,
idx,
self.entries.len(),
ent.text,
ent.creation_ts.date()
))
} else {
None
}
}
pub fn get_or_create(word: &str, c: &str, dbc: &PgConnection) -> Result<Self, Error> {
if let Some(ret) = Self::get(word, c, dbc)? {
Ok(ret)
} else {
Ok(Self::create(word, c, dbc)?)
}
}
pub fn create(word: &str, c: &str, dbc: &PgConnection) -> Result<Self, Error> {
let val = NewKeyword {
name: word,
chan: c,
};
let ret: Keyword = {
use crate::schema::keywords;
::diesel::insert_into(keywords::table)
.values(val)
.get_result(dbc)?
};
Ok(KeywordDetails {
keyword: ret,
entries: vec![],
})
}
fn get_entries(kid: i32, dbc: &PgConnection) -> Result<Vec<Entry>, Error> {
let entries: Vec<Entry> = {
use crate::schema::entries::dsl::*;
entries
.filter(keyword_id.eq(kid))
.order_by(idx.asc())
.load(dbc)?
};
Ok(entries)
}
pub fn get<'a, T: Into<Cow<'a, str>>>(
word: T,
c: &str,
dbc: &PgConnection,
) -> Result<Option<Self>, Error> {
let word = word.into();
let keyword: Option<Keyword> = {
use crate::schema::keywords::dsl::*;
keywords
.filter(name.ilike(word).and(chan.eq(c).or(chan.eq("*"))))
.first(dbc)
.optional()?
};
if let Some(k) = keyword {
let entries = Self::get_entries(k.id, dbc)?;
if let Some(e0) = entries.get(0) {
if e0.text.starts_with("see: ") {
return Self::get(e0.text.replace("see: ", ""), c, dbc);
}
}
Ok(Some(KeywordDetails {
keyword: k,
entries,
}))
} else {
Ok(None)
}
}
}