diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db/db.rs | 160 | ||||
| -rw-r--r-- | src/db/mod.rs | 161 | ||||
| -rw-r--r-- | src/deck.rs | 8 | ||||
| -rw-r--r-- | src/gui/gui.rs | 98 | ||||
| -rw-r--r-- | src/gui/message.rs | 7 | ||||
| -rw-r--r-- | src/gui/mod.rs | 95 | ||||
| -rw-r--r-- | src/gui/question.rs | 90 | ||||
| -rw-r--r-- | src/gui/util.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 12 | ||||
| -rw-r--r-- | src/util/event.rs | 10 | ||||
| -rw-r--r-- | src/util/serialization.rs | 6 | ||||
| -rw-r--r-- | src/util/time.rs | 2 | 
12 files changed, 318 insertions, 333 deletions
| diff --git a/src/db/db.rs b/src/db/db.rs deleted file mode 100644 index b42da3f..0000000 --- a/src/db/db.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::{ -    model::{card::Card, entry::Entry}, -    space_repetition, -    util::serialization, -}; -use anyhow::Result; -use rusqlite::{params, Connection}; -use rusqlite_migration::{Migrations, M}; - -use crate::util::time; - -pub fn init(database: String) -> Result<Connection> { -    let mut conn = Connection::open(database)?; -    let migrations = Migrations::new(vec![ -        M::up(include_str!("sql/1-init.sql")), -        M::up(include_str!("sql/2-primary-key-question-responses.sql")), -    ]); -    migrations.to_latest(&mut conn)?; -    Ok(conn) -} - -pub fn last_deck_read(conn: &Connection) -> Option<u64> { -    let mut stmt = conn -        .prepare("SELECT deck_read FROM cards ORDER BY deck_read DESC LIMIT 1") -        .ok()?; - -    let mut rows = stmt.query([]).ok()?; -    let row = rows.next().ok()??; -    row.get(0).ok()? -} - -/// Synchronize the DB with the deck: -/// -/// - insert new cards, -/// - keep existing cards, -/// - hide unused cards (keep state in case the card is added back afterward). -pub fn synchronize(conn: &Connection, entries: Vec<Entry>) -> Result<()> { -    let now = time::seconds_since_unix_epoch()?; - -    let state = serde_json::to_string(&space_repetition::init())?; - -    for entry in entries { -        let concat_1 = serialization::words_to_line(&entry.part_1); -        let concat_2 = serialization::words_to_line(&entry.part_2); - -        for w in entry.part_1.iter() { -            insert(&conn, now, &w, &concat_2, &state)?; -        } - -        for w in entry.part_2.iter() { -            insert(&conn, now, &w, &concat_1, &state)?; -        } -    } - -    delete_read_before(&conn, now)?; - -    Ok(()) -} - -fn insert( -    conn: &Connection, -    now: u64, -    question: &String, -    responses: &String, -    state: &String, -) -> Result<()> { -    conn.execute( -        " -        INSERT INTO cards (question, responses, state, created, deck_read, ready) -        VALUES (?, ?, ?, ?, ?, ?) -        ON CONFLICT (question, responses) DO UPDATE SET deck_read = ?, deleted = null -        ", -        params![question, responses, state, now, now, now, now], -    )?; - -    Ok(()) -} - -fn delete_read_before(conn: &Connection, t: u64) -> Result<()> { -    conn.execute( -        "UPDATE cards SET deleted = ? WHERE deck_read < ?", -        params![t, t], -    )?; - -    Ok(()) -} - -pub fn pick_random_ready(conn: &Connection) -> Option<Card> { -    let now = time::seconds_since_unix_epoch().ok()?; - -    let mut stmt = conn -        .prepare( -            " -        SELECT question, responses, state, ready -        FROM cards -        WHERE deleted IS NULL AND ready <= ? -        ORDER BY RANDOM() -        LIMIT 1 -        ", -        ) -        .ok()?; - -    let mut rows = stmt.query([now]).ok()?; -    let row = rows.next().ok()??; -    let state_str: String = row.get(2).ok()?; -    let responses_str: String = row.get(1).ok()?; - -    Some(Card { -        question: row.get(0).ok()?, -        responses: serialization::line_to_words(&responses_str), -        state: serde_json::from_str(&state_str).ok()?, -        ready: row.get(3).ok()?, -    }) -} - -pub fn next_ready(conn: &Connection) -> Option<u64> { -    let mut stmt = conn -        .prepare( -            " -        SELECT ready  -        FROM cards  -        WHERE deleted IS NULL -        ORDER BY ready -        LIMIT 1 -    ", -        ) -        .ok()?; - -    let mut rows = stmt.query([]).ok()?; -    let row = rows.next().ok()??; -    row.get(0).ok()? -} - -pub fn count_available(conn: &Connection) -> Option<i32> { -    let now = time::seconds_since_unix_epoch().ok()?; -    let mut stmt = conn -        .prepare("SELECT COUNT(*) FROM cards WHERE ready <= ? AND deleted IS NULL") -        .ok()?; - -    let mut rows = stmt.query([now]).ok()?; -    let row = rows.next().ok()??; -    row.get(0).ok()? -} - -pub fn update(conn: &Connection, question: &String, state: &space_repetition::State) -> Result<()> { -    let now = time::seconds_since_unix_epoch()?; -    let ready = now + state.get_interval_seconds(); -    let state_str = serde_json::to_string(state)?; - -    conn.execute( -        " -        UPDATE cards -        SET state = ?, updated = ?, ready = ? -        WHERE question = ? -        ", -        params![state_str, now, ready, question], -    )?; - -    Ok(()) -} diff --git a/src/db/mod.rs b/src/db/mod.rs index dec1023..f59aad5 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1 +1,160 @@ -pub mod db; +use crate::{ +    model::{card::Card, entry::Entry}, +    space_repetition, +    util::serialization, +}; +use anyhow::Result; +use rusqlite::{params, Connection}; +use rusqlite_migration::{Migrations, M}; + +use crate::util::time; + +pub fn init(database: String) -> Result<Connection> { +    let mut conn = Connection::open(database)?; +    let migrations = Migrations::new(vec![ +        M::up(include_str!("sql/1-init.sql")), +        M::up(include_str!("sql/2-primary-key-question-responses.sql")), +    ]); +    migrations.to_latest(&mut conn)?; +    Ok(conn) +} + +pub fn last_deck_read(conn: &Connection) -> Option<u64> { +    let mut stmt = conn +        .prepare("SELECT deck_read FROM cards ORDER BY deck_read DESC LIMIT 1") +        .ok()?; + +    let mut rows = stmt.query([]).ok()?; +    let row = rows.next().ok()??; +    row.get(0).ok()? +} + +/// Synchronize the DB with the deck: +/// +/// - insert new cards, +/// - keep existing cards, +/// - hide unused cards (keep state in case the card is added back afterward). +pub fn synchronize(conn: &Connection, entries: Vec<Entry>) -> Result<()> { +    let now = time::seconds_since_unix_epoch()?; + +    let state = serde_json::to_string(&space_repetition::init())?; + +    for entry in entries { +        let concat_1 = serialization::words_to_line(&entry.part_1); +        let concat_2 = serialization::words_to_line(&entry.part_2); + +        for w in entry.part_1.iter() { +            insert(conn, now, w, &concat_2, &state)?; +        } + +        for w in entry.part_2.iter() { +            insert(conn, now, w, &concat_1, &state)?; +        } +    } + +    delete_read_before(conn, now)?; + +    Ok(()) +} + +fn insert( +    conn: &Connection, +    now: u64, +    question: &str, +    responses: &str, +    state: &str, +) -> Result<()> { +    conn.execute( +        " +        INSERT INTO cards (question, responses, state, created, deck_read, ready) +        VALUES (?, ?, ?, ?, ?, ?) +        ON CONFLICT (question, responses) DO UPDATE SET deck_read = ?, deleted = null +        ", +        params![question, responses, state, now, now, now, now], +    )?; + +    Ok(()) +} + +fn delete_read_before(conn: &Connection, t: u64) -> Result<()> { +    conn.execute( +        "UPDATE cards SET deleted = ? WHERE deck_read < ?", +        params![t, t], +    )?; + +    Ok(()) +} + +pub fn pick_random_ready(conn: &Connection) -> Option<Card> { +    let now = time::seconds_since_unix_epoch().ok()?; + +    let mut stmt = conn +        .prepare( +            " +        SELECT question, responses, state, ready +        FROM cards +        WHERE deleted IS NULL AND ready <= ? +        ORDER BY RANDOM() +        LIMIT 1 +        ", +        ) +        .ok()?; + +    let mut rows = stmt.query([now]).ok()?; +    let row = rows.next().ok()??; +    let state_str: String = row.get(2).ok()?; +    let responses_str: String = row.get(1).ok()?; + +    Some(Card { +        question: row.get(0).ok()?, +        responses: serialization::line_to_words(&responses_str), +        state: serde_json::from_str(&state_str).ok()?, +        ready: row.get(3).ok()?, +    }) +} + +pub fn next_ready(conn: &Connection) -> Option<u64> { +    let mut stmt = conn +        .prepare( +            " +        SELECT ready  +        FROM cards  +        WHERE deleted IS NULL +        ORDER BY ready +        LIMIT 1 +    ", +        ) +        .ok()?; + +    let mut rows = stmt.query([]).ok()?; +    let row = rows.next().ok()??; +    row.get(0).ok()? +} + +pub fn count_available(conn: &Connection) -> Option<i32> { +    let now = time::seconds_since_unix_epoch().ok()?; +    let mut stmt = conn +        .prepare("SELECT COUNT(*) FROM cards WHERE ready <= ? AND deleted IS NULL") +        .ok()?; + +    let mut rows = stmt.query([now]).ok()?; +    let row = rows.next().ok()??; +    row.get(0).ok()? +} + +pub fn update(conn: &Connection, question: &str, state: &space_repetition::State) -> Result<()> { +    let now = time::seconds_since_unix_epoch()?; +    let ready = now + state.get_interval_seconds(); +    let state_str = serde_json::to_string(state)?; + +    conn.execute( +        " +        UPDATE cards +        SET state = ?, updated = ?, ready = ? +        WHERE question = ? +        ", +        params![state_str, now, ready, question], +    )?; + +    Ok(()) +} diff --git a/src/deck.rs b/src/deck.rs index e0f9fab..a414a02 100644 --- a/src/deck.rs +++ b/src/deck.rs @@ -32,14 +32,14 @@ pub fn read(deck: &str) -> Result<Vec<Entry>> {          let line = line?;          let line = line.trim(); -        if !line.starts_with("#") && !line.is_empty() { -            if !line.starts_with("-") { +        if !line.starts_with('#') && !line.is_empty() { +            if !line.starts_with('-') {                  return Err(Error::from(ParseError {                      line: index + 1,                      message: "an entry should starts with “-”.".to_string(),                  }));              } else { -                let translation = line[1..].trim().split(":").collect::<Vec<&str>>(); +                let translation = line[1..].trim().split(':').collect::<Vec<&str>>();                  if translation.len() != 2 {                      return Err(Error::from(ParseError {                          line: index + 1, @@ -68,7 +68,7 @@ pub fn read(deck: &str) -> Result<Vec<Entry>> {      Ok(entries)  } -pub fn pp_from_path(path: &String) -> Option<String> { +pub fn pp_from_path(path: &str) -> Option<String> {      Some(capitalize(          Path::new(&path).with_extension("").file_name()?.to_str()?,      )) diff --git a/src/gui/gui.rs b/src/gui/gui.rs deleted file mode 100644 index 92b1a72..0000000 --- a/src/gui/gui.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::deck; -use crate::util::time; -use crate::{db::db, gui::message, gui::question, space_repetition, util::event::Events}; -use anyhow::Result; -use rusqlite::Connection; -use std::{fs, io, time::Duration}; -use termion::{raw::IntoRawMode, raw::RawTerminal, screen::AlternateScreen}; -use tui::{backend::TermionBackend, Terminal}; - -type Term = Terminal<TermionBackend<AlternateScreen<RawTerminal<io::Stdout>>>>; - -pub fn terminal() -> Result<Term> { -    let stdout = io::stdout().into_raw_mode()?; -    let stdout = AlternateScreen::from(stdout); -    let backend = TermionBackend::new(stdout); -    Ok(Terminal::new(backend)?) -} - -pub fn synchronize( -    conn: &Connection, -    term: &mut Term, -    events: &Events, -    deck_path: &str, -    deck_name: &str, -) -> Result<()> { -    let last_modified = time::seconds_since_unix_epoch_of(fs::metadata(deck_path)?.modified()?)?; -    let last_deck_read = db::last_deck_read(&conn); -    let must_synchronize = last_deck_read.map(|r| r < last_modified).unwrap_or(true); - -    if must_synchronize { -        let _ = message::show(term, events, &deck_name, "Synchronization du deck…", false); -        time::wait_at_least( -            || db::synchronize(&conn, deck::read(&deck_path)?), -            Duration::from_secs(1), -        )?; -    } - -    Ok(()) -} - -pub fn start( -    conn: &Connection, -    term: &mut Term, -    events: &Events, -    deck_name: &String, -) -> Result<()> { -    let mut answers = 0; - -    loop { -        let now = time::seconds_since_unix_epoch()?; -        let title = title(deck_name, answers, db::count_available(&conn).unwrap_or(0)); - -        match db::pick_random_ready(&conn) { -            Some(card) => { -                let difficulty = question::ask(term, events, &title, &card)?; -                answers += 1; -                db::update( -                    &conn, -                    &card.question, -                    &space_repetition::update(card.state, difficulty), -                )?; -            } -            None => { -                let message = match db::next_ready(&conn) { -                    Some(ready) => format!( -                        "Prochaine carte disponible dans {}.", -                        time::pp_duration(ready - now) -                    ), -                    None => format!("Aucune carte n’est disponible. Votre deck est-il vide ?"), -                }; -                let _ = message::show(term, events, &title, &message, true); -                break; -            } -        } -    } - -    Ok(()) -} - -fn title(deck_name: &String, answers: i32, available_cards: i32) -> String { -    if answers == 0 && available_cards == 0 { -        deck_name.to_string() -    } else if available_cards == 0 { -        format!( -            "{} ({} / {})", -            deck_name, -            answers, -            answers + available_cards -        ) -    } else { -        format!( -            "{} ({} / {})", -            deck_name, -            answers + 1, -            answers + available_cards -        ) -    } -} diff --git a/src/gui/message.rs b/src/gui/message.rs index 28a1d2c..b938150 100644 --- a/src/gui/message.rs +++ b/src/gui/message.rs @@ -33,11 +33,8 @@ pub fn show<B: Backend>(          })?;          if wait { -            if let Event::Input(key) = events.next()? { -                match key { -                    Key::Char('q') => break, -                    _ => (), -                } +            if let Event::Input(Key::Char('q')) = events.next()? { +                break              }          } else {              break; diff --git a/src/gui/mod.rs b/src/gui/mod.rs index f351eba..92cd943 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,4 +1,97 @@ -pub mod gui;  pub mod message;  pub mod question;  pub mod util; + +use crate::deck; +use crate::util::time; +use crate::{db, space_repetition, util::event::Events}; +use anyhow::Result; +use rusqlite::Connection; +use std::{fs, io, time::Duration}; +use termion::{raw::IntoRawMode, raw::RawTerminal, screen::AlternateScreen}; +use tui::{backend::TermionBackend, Terminal}; + +type Term = Terminal<TermionBackend<AlternateScreen<RawTerminal<io::Stdout>>>>; + +pub fn terminal() -> Result<Term> { +    let stdout = io::stdout().into_raw_mode()?; +    let stdout = AlternateScreen::from(stdout); +    let backend = TermionBackend::new(stdout); +    Ok(Terminal::new(backend)?) +} + +pub fn synchronize( +    conn: &Connection, +    term: &mut Term, +    events: &Events, +    deck_path: &str, +    deck_name: &str, +) -> Result<()> { +    let last_modified = time::seconds_since_unix_epoch_of(fs::metadata(deck_path)?.modified()?)?; +    let last_deck_read = db::last_deck_read(conn); +    let must_synchronize = last_deck_read.map(|r| r < last_modified).unwrap_or(true); + +    if must_synchronize { +        let _ = message::show(term, events, deck_name, "Synchronization du deck…", false); +        time::wait_at_least( +            || db::synchronize(conn, deck::read(deck_path)?), +            Duration::from_secs(1), +        )?; +    } + +    Ok(()) +} + +pub fn start(conn: &Connection, term: &mut Term, events: &Events, deck_name: &str) -> Result<()> { +    let mut answers = 0; + +    loop { +        let now = time::seconds_since_unix_epoch()?; +        let title = title(deck_name, answers, db::count_available(conn).unwrap_or(0)); + +        match db::pick_random_ready(conn) { +            Some(card) => { +                let difficulty = question::ask(term, events, &title, &card)?; +                answers += 1; +                db::update( +                    conn, +                    &card.question, +                    &space_repetition::update(card.state, difficulty), +                )?; +            } +            None => { +                let message = match db::next_ready(conn) { +                    Some(ready) => format!( +                        "Prochaine carte disponible dans {}.", +                        time::pp_duration(ready - now) +                    ), +                    None => "Aucune carte n’est disponible. Votre deck est-il vide ?".to_string(), +                }; +                let _ = message::show(term, events, &title, &message, true); +                break; +            } +        } +    } + +    Ok(()) +} + +fn title(deck_name: &str, answers: i32, available_cards: i32) -> String { +    if answers == 0 && available_cards == 0 { +        deck_name.to_string() +    } else if available_cards == 0 { +        format!( +            "{} ({} / {})", +            deck_name, +            answers, +            answers + available_cards +        ) +    } else { +        format!( +            "{} ({} / {})", +            deck_name, +            answers + 1, +            answers + available_cards +        ) +    } +} diff --git a/src/gui/question.rs b/src/gui/question.rs index 211bcda..5f060e3 100644 --- a/src/gui/question.rs +++ b/src/gui/question.rs @@ -28,7 +28,7 @@ enum Answer {  pub fn ask<B: Backend>(      terminal: &mut Terminal<B>,      events: &Events, -    title: &String, +    title: &str,      card: &Card,  ) -> Result<Difficulty> {      let mut state = State { @@ -59,7 +59,7 @@ pub fn ask<B: Backend>(              let question = Paragraph::new(util::center_vertically(chunks[1], &card.question))                  .style(match state.answer {                      Answer::Write => { -                        if state.input == "" { +                        if state.input.is_empty() {                              Style::default().fg(Color::Yellow)                          } else {                              Style::default() @@ -86,47 +86,44 @@ pub fn ask<B: Backend>(                  .wrap(Wrap { trim: true });              f.render_widget(answer, chunks[2]); -            match state.answer { -                Answer::Difficulty { -                    difficulty: selected, -                } => { -                    if !is_correct(&state.input, &card.responses) || card.responses.len() > 1 { -                        let paragraph = Paragraph::new(util::center_vertically( -                            chunks[3], -                            &serialization::words_to_line(&card.responses), -                        )) -                        .alignment(Alignment::Center); -                        f.render_widget(paragraph, chunks[3]); -                    }; +            if let Answer::Difficulty { +                difficulty: selected, +            } = state.answer +            { +                if !is_correct(&state.input, &card.responses) || card.responses.len() > 1 { +                    let paragraph = Paragraph::new(util::center_vertically( +                        chunks[3], +                        &serialization::words_to_line(&card.responses), +                    )) +                    .alignment(Alignment::Center); +                    f.render_widget(paragraph, chunks[3]); +                }; -                    let difficulties = card.state.difficulties(); -                    let l = difficulties.len(); -                    let sep = Span::styled(" • ", Style::default()); -                    let tabs = difficulties -                        .iter() -                        .enumerate() -                        .map(|(i, d)| { -                            let style = if *d == selected { -                                Style::default() -                                    .fg(Color::Yellow) -                                    .add_modifier(Modifier::UNDERLINED) -                            } else { -                                Style::default().add_modifier(Modifier::DIM) -                            }; -                            let d = Span::styled(difficulty::label(*d), style); -                            if i < l - 1 { -                                [d, sep.clone()].to_vec() -                            } else { -                                [d].to_vec() -                            } -                        }) -                        .collect::<Vec<Vec<Span>>>() -                        .concat(); -                    let p = -                        Paragraph::new(Text::from(Spans::from(tabs))).alignment(Alignment::Center); -                    f.render_widget(p, chunks[4]); -                } -                _ => {} +                let difficulties = card.state.difficulties(); +                let l = difficulties.len(); +                let sep = Span::styled(" • ", Style::default()); +                let tabs = difficulties +                    .iter() +                    .enumerate() +                    .map(|(i, d)| { +                        let style = if *d == selected { +                            Style::default() +                                .fg(Color::Yellow) +                                .add_modifier(Modifier::UNDERLINED) +                        } else { +                            Style::default().add_modifier(Modifier::DIM) +                        }; +                        let d = Span::styled(difficulty::label(*d), style); +                        if i < l - 1 { +                            [d, sep.clone()].to_vec() +                        } else { +                            [d].to_vec() +                        } +                    }) +                    .collect::<Vec<Vec<Span>>>() +                    .concat(); +                let p = Paragraph::new(Text::from(Spans::from(tabs))).alignment(Alignment::Center); +                f.render_widget(p, chunks[4]);              }          })?; @@ -171,15 +168,14 @@ pub fn ask<B: Backend>(      }  } -fn is_correct(input: &String, responses: &Vec<String>) -> bool { +fn is_correct(input: &str, responses: &[String]) -> bool {      responses          .iter() -        .map(|r| r.split("(").collect::<Vec<&str>>()[0].trim()) -        .collect::<Vec<&str>>() -        .contains(&input.as_str()) +        .map(|r| r.split('(').collect::<Vec<&str>>()[0].trim()) +        .any(|x| x == input)  } -fn relative_element<T: Clone + PartialEq>(xs: &Vec<T>, x: &T, ri: i32) -> Option<T> { +fn relative_element<T: Clone + PartialEq>(xs: &[T], x: &T, ri: i32) -> Option<T> {      let i = xs.iter().position(|t| t == x)? as i32 + ri;      if i >= 0 && i < xs.len() as i32 {          Some(xs[i as usize].clone()) diff --git a/src/gui/util.rs b/src/gui/util.rs index 38ed1e7..2314aba 100644 --- a/src/gui/util.rs +++ b/src/gui/util.rs @@ -12,7 +12,7 @@ pub fn title(str: &str) -> Paragraph {      )  } -pub fn center_vertically(chunk: Rect, text: &String) -> String { +pub fn center_vertically(chunk: Rect, text: &str) -> String {      let text_lines = text.lines().count();      let chunk_inner_lines: usize = (chunk.height - 2).into();      let blank_lines = chunk_inner_lines - text_lines; diff --git a/src/main.rs b/src/main.rs index 3e3e741..bed2ce1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,15 +19,15 @@ struct Opt {  fn main() -> Result<()> {      let deck_path = Opt::from_args().deck; -    let conn = db::db::init(db_path(&deck_path))?; -    let deck_name = deck::pp_from_path(&deck_path).unwrap_or("Deck".to_string()); -    let mut term = gui::gui::terminal()?; +    let conn = db::init(db_path(&deck_path))?; +    let deck_name = deck::pp_from_path(&deck_path).unwrap_or_else(|| "Deck".to_string()); +    let mut term = gui::terminal()?;      let events = Events::new(); -    gui::gui::synchronize(&conn, &mut term, &events, &deck_path, &deck_name)?; -    gui::gui::start(&conn, &mut term, &events, &deck_name) +    gui::synchronize(&conn, &mut term, &events, &deck_path, &deck_name)?; +    gui::start(&conn, &mut term, &events, &deck_name)  } -fn db_path(deck_path: &String) -> String { +fn db_path(deck_path: &str) -> String {      let mut path = PathBuf::from(deck_path);      path.set_extension("db");      path.to_string_lossy().to_string() diff --git a/src/util/event.rs b/src/util/event.rs index 33ee9ec..05d8581 100644 --- a/src/util/event.rs +++ b/src/util/event.rs @@ -43,12 +43,10 @@ impl Events {              let tx = tx.clone();              thread::spawn(move || {                  let stdin = io::stdin(); -                for evt in stdin.keys() { -                    if let Ok(key) = evt { -                        if let Err(err) = tx.send(Event::Input(key)) { -                            eprintln!("{}", err); -                            return; -                        } +                for key in stdin.keys().flatten() { +                    if let Err(err) = tx.send(Event::Input(key)) { +                        eprintln!("{}", err); +                        return;                      }                  }              }) diff --git a/src/util/serialization.rs b/src/util/serialization.rs index cc2899f..61b3a83 100644 --- a/src/util/serialization.rs +++ b/src/util/serialization.rs @@ -1,10 +1,10 @@ -pub fn line_to_words(line: &String) -> Vec<String> { -    line.split("|") +pub fn line_to_words(line: &str) -> Vec<String> { +    line.split('|')          .map(|w| w.trim().to_string())          .filter(|w| !w.is_empty())          .collect()  } -pub fn words_to_line(words: &Vec<String>) -> String { +pub fn words_to_line(words: &[String]) -> String {      words.join(" | ")  } diff --git a/src/util/time.rs b/src/util/time.rs index d9a9f72..b8a85e6 100644 --- a/src/util/time.rs +++ b/src/util/time.rs @@ -3,7 +3,7 @@ use std::thread;  use std::time::SystemTime;  pub fn seconds_since_unix_epoch() -> Result<u64> { -    Ok(seconds_since_unix_epoch_of(SystemTime::now())?) +    seconds_since_unix_epoch_of(SystemTime::now())  }  pub fn seconds_since_unix_epoch_of(time: SystemTime) -> Result<u64> { | 
