~prma/ddd

bcacb2825c53c1e926ad7ffa4159b3c68780016f — Perma Alesheikh 5 months ago 9492461 v0.2.5
add ability to edit a noun

Signed-off-by: Perma Alesheikh <me@prma.dev>
8 files changed, 143 insertions(+), 37 deletions(-)

M Cargo.lock
M Cargo.toml
M src/arugments.rs
M src/attempt.rs
M src/commands.rs
M src/main.rs
M src/nouns.rs
M src/storage.rs
M Cargo.lock => Cargo.lock +1 -1
@@ 306,7 306,7 @@ dependencies = [

[[package]]
name = "der_die_das"
version = "0.2.4"
version = "0.2.5"
dependencies = [
 "clap",
 "clap_complete_command",

M Cargo.toml => Cargo.toml +1 -1
@@ 1,6 1,6 @@
[package]
name = "der_die_das"
version = "0.2.4"
version = "0.2.5"
edition = "2021"
authors = ["Perma Alesheikh <me@prma.dev>"]
license = "EUPL-1.2"

M src/arugments.rs => src/arugments.rs +21 -0
@@ 37,6 37,10 @@ pub enum Commands {
    /// ask stuff.
    #[command(subcommand)]
    Ask(AskCommands),

    /// edit Items.
    #[command(subcommand)]
    Edit(EditCommands),
}

#[derive(Clone, Debug, Subcommand)]


@@ 52,6 56,23 @@ pub enum NewCommands {
        group: Option<String>,
    },
}

#[derive(Clone, Debug, Subcommand)]
pub enum EditCommands {
    Noun {
        #[arg(short = 'i', long, value_name = "ID")]
        id: i128,
        #[arg(short = 'a', long, value_name = "ARTICLES")]
        articles: Option<Vec<Article>>,
        #[arg(short = 'w', long, value_name = "WORD")]
        word: Option<String>,
        #[arg(short = 'm', long, value_name = "MEANING")]
        meaning: Option<String>,
        #[arg(short = 'g', long, value_name = "GROUP")]
        group: Option<String>,
    },
}

#[derive(Clone, Debug, Subcommand)]
pub enum ListCommands {
    Nouns,

M src/attempt.rs => src/attempt.rs +1 -1
@@ 16,7 16,7 @@ pub enum Conclusion {
    WrongArticle(Article),
}

pub trait Repo {
pub trait Repo: Clone {
    /// Saves an attempt
    ///
    /// # Errors

M src/commands.rs => src/commands.rs +49 -29
@@ 1,7 1,7 @@
use der_die_das::{
    attempt::{Attempt, Repo as _},
    attempt::{self, Attempt},
    history::History,
    nouns::Repo as _,
    nouns::{self},
};
use inquire::{
    validator::{ErrorMessage, StringValidator, Validation},


@@ 15,10 15,7 @@ use color_eyre::{
    Result,
};
use comfy_table::{Cell, Row};
use der_die_das::{
    nouns::{Article, Noun},
    storage::Storage,
};
use der_die_das::nouns::{Article, Noun};
use deunicode::deunicode;

use crate::{config::GroupConfiguration, importer};


@@ 29,7 26,7 @@ pub fn new_noun(
    meaning: String,
    article: Vec<Article>,
    group: Option<String>,
    storage: &Storage,
    storage: impl nouns::Repo,
) -> Result<()> {
    if word.is_empty() {
        return Err(eyre!("word must be at least 1 character"));


@@ 47,10 44,10 @@ pub fn new_noun(
    Ok(())
}

pub fn list_nouns(storage: &Storage) -> Result<()> {
pub fn list_nouns(noun_repo: impl nouns::Repo, attempt_repo: impl attempt::Repo) -> Result<()> {
    let h = History {
        nouns: storage.all_nouns()?,
        attempts: storage.all_attempts()?,
        nouns: noun_repo.all_nouns()?,
        attempts: attempt_repo.all_attempts()?,
    };

    h.grouped_confidence_map()


@@ 153,7 150,7 @@ impl StringValidator for ValidateLastWordShouldMatchTheSpelling {
    }
}

fn ask_noun(storage: &Storage, noun: &Noun) -> Result<()> {
fn ask_noun(attempt_repo: impl attempt::Repo, noun: &Noun) -> Result<()> {
    let prompt = format!("{} {}\n>", "___".reversed(), noun.word.reversed());
    let resp = inquire::Text::new(&prompt)
        .with_help_message(&noun.meaning)


@@ 182,7 179,7 @@ fn ask_noun(storage: &Storage, noun: &Noun) -> Result<()> {

    let now = time::OffsetDateTime::now_utc();
    if noun.articles.contains(&article) {
        storage.save_attempt(Attempt {
        attempt_repo.save_attempt(Attempt {
            id: now.unix_timestamp_nanos(),
            at: now,
            for_word: noun.id,


@@ 202,19 199,23 @@ fn ask_noun(storage: &Storage, noun: &Noun) -> Result<()> {
                .bold()
        );

        storage.save_attempt(Attempt {
        attempt_repo.clone().save_attempt(Attempt {
            id: now.unix_timestamp_nanos(),
            at: now,
            for_word: noun.id,
            what_happened: der_die_das::attempt::Conclusion::WrongArticle(article),
        })?;
        ask_noun(storage, noun)
        ask_noun(attempt_repo, noun)
    }
}

fn get_confidence(storage: &Storage, id: i128) -> Result<(Noun, u8)> {
    let nouns = storage.all_nouns()?;
    let attempts = storage.all_attempts()?;
fn get_confidence(
    noun_repo: impl nouns::Repo,
    attempt_repo: impl attempt::Repo,
    id: i128,
) -> Result<(Noun, u8)> {
    let nouns = noun_repo.all_nouns()?;
    let attempts = attempt_repo.all_attempts()?;
    let h = History { nouns, attempts };
    h.confidence_map()
        .into_iter()


@@ 222,9 223,13 @@ fn get_confidence(storage: &Storage, id: i128) -> Result<(Noun, u8)> {
        .ok_or_eyre("Some how the word is not there any more!")
}

pub fn ask_next(storage: &Storage, group_config: &GroupConfiguration) -> Result<()> {
    let nouns = storage.all_nouns()?;
    let attempts = storage.all_attempts()?;
pub fn ask_next(
    noun_repo: impl nouns::Repo,
    attempt_repo: impl attempt::Repo,
    group_config: &GroupConfiguration,
) -> Result<()> {
    let nouns = noun_repo.clone().all_nouns()?;
    let attempts = attempt_repo.clone().all_attempts()?;
    let h = History { nouns, attempts };

    let chosen = if group_config.enable {


@@ 234,9 239,9 @@ pub fn ask_next(storage: &Storage, group_config: &GroupConfiguration) -> Result<
    }
    .ok_or_eyre("there are no words available for asking. Have you added some?")?;

    ask_noun(storage, &chosen.0)?;
    ask_noun(attempt_repo.clone(), &chosen.0)?;

    let (_, conf) = get_confidence(storage, chosen.0.id)?;
    let (_, conf) = get_confidence(noun_repo, attempt_repo, chosen.0.id)?;

    println!(
        "The confidence for the word is now at {}.",


@@ 246,9 251,13 @@ pub fn ask_next(storage: &Storage, group_config: &GroupConfiguration) -> Result<
    Ok(())
}

pub fn group_ask(storage: &Storage, group_config: &GroupConfiguration) -> Result<()> {
    let nouns = storage.all_nouns()?;
    let attempts = storage.all_attempts()?;
pub fn group_ask(
    noun_repo: &impl nouns::Repo,
    attempt_repo: &impl attempt::Repo,
    group_config: &GroupConfiguration,
) -> Result<()> {
    let nouns = noun_repo.clone().all_nouns()?;
    let attempts = attempt_repo.clone().all_attempts()?;
    let h = History { nouns, attempts };

    let gr = h


@@ 260,9 269,9 @@ pub fn group_ask(storage: &Storage, group_config: &GroupConfiguration) -> Result
        println!();
        println!("{}/{}", (i + 1).bold(), number_of_words_in_gr);

        ask_noun(storage, &noun)?;
        ask_noun(attempt_repo.clone(), &noun)?;

        let (_, conf) = get_confidence(storage, noun.id)?;
        let (_, conf) = get_confidence(noun_repo.clone(), attempt_repo.clone(), noun.id)?;

        println!(
            "The confidence for the word is now at {}.",


@@ 273,7 282,7 @@ pub fn group_ask(storage: &Storage, group_config: &GroupConfiguration) -> Result
    })
}

pub fn import_nouns(storage: &Storage, at: PathBuf) -> Result<()> {
pub fn import_nouns(noun_repo: &impl nouns::Repo, at: PathBuf) -> Result<()> {
    if !at.try_exists()? {
        return Err(eyre!(format!(
            "Such file or directory does not exist: {}",


@@ 296,7 305,18 @@ pub fn import_nouns(storage: &Storage, at: PathBuf) -> Result<()> {
    imported_groups
        .into_iter()
        .flat_map(Vec::<Noun>::from)
        .try_for_each(|n| storage.save_noun(n))?;
        .try_for_each(|n| noun_repo.clone().save_noun(n))?;

    Ok(())
}

pub fn edit_noun(
    noun_repo: impl nouns::Repo,
    id: i128,
    article: Option<Vec<Article>>,
    word: Option<String>,
    meaning: Option<String>,
    group: Option<String>,
) -> Result<()> {
    noun_repo.edit_noun_by_id(id, article, word, meaning, group)
}

M src/main.rs => src/main.rs +17 -4
@@ 56,14 56,27 @@ fn main() -> Result<()> {
                ),
            },
            Commands::List(list_commands) => match list_commands {
                arugments::ListCommands::Nouns => commands::list_nouns(&storage),
                arugments::ListCommands::Nouns => commands::list_nouns(&storage, &storage),
            },
            Commands::Import(import_commands) => match import_commands {
                arugments::ImportCommands::Nouns { at } => commands::import_nouns(&storage, at),
                arugments::ImportCommands::Nouns { at } => commands::import_nouns(&&storage, at),
            },
            Commands::Ask(ask_commands) => match ask_commands {
                arugments::AskCommands::Nouns => commands::ask_next(&storage, &configs.group),
                arugments::AskCommands::Group => commands::group_ask(&storage, &configs.group),
                arugments::AskCommands::Nouns => {
                    commands::ask_next(&storage, &storage, &configs.group)
                }
                arugments::AskCommands::Group => {
                    commands::group_ask(&&storage, &&storage, &configs.group)
                }
            },
            Commands::Edit(edit_commands) => match edit_commands {
                arugments::EditCommands::Noun {
                    articles,
                    word,
                    meaning,
                    group,
                    id,
                } => commands::edit_noun(&storage, id, articles, word, meaning, group),
            },
        },
        None => todo!(),

M src/nouns.rs => src/nouns.rs +16 -1
@@ 32,7 32,7 @@ impl Display for Article {
    }
}

pub trait Repo {
pub trait Repo: Clone {
    /// Saves one noun.
    ///
    /// # Errors


@@ 63,4 63,19 @@ pub trait Repo {
    /// This function will return an error if a noun by that ID does not exist
    /// or there is an IO failure.
    fn delete_noun_by_id(self, id: i128) -> Result<()>;

    /// Edit noun
    ///
    /// # Errors
    ///
    /// This function will return an error if a noun by that ID does not exist
    /// or there is an IO failure.
    fn edit_noun_by_id(
        self,
        id: i128,
        article: Option<Vec<Article>>,
        word: Option<String>,
        meaning: Option<String>,
        group: Option<String>,
    ) -> Result<()>;
}

M src/storage.rs => src/storage.rs +37 -0
@@ 121,6 121,43 @@ impl nouns::Repo for &Storage {
        std::fs::remove_file(&file)?;
        Ok(())
    }

    fn edit_noun_by_id(
        self,
        id: i128,
        article: Option<Vec<Article>>,
        word: Option<String>,
        meaning: Option<String>,
        group: Option<String>,
    ) -> Result<()> {
        let mut n = self.find_noun_by_id(id)?;

        if let Some(x) = article {
            n.articles = x;
        };

        if let Some(x) = word {
            n.word = x;
        };

        if let Some(x) = meaning {
            n.meaning = x;
        };
        if let Some(x) = group {
            n.group = x;
        };

        let file = self.words_path().join(format!("{id}.json"));

        if !file.try_exists()? {
            return Err(eyre!("could not find a word with that id."));
        }

        let json_content = serde_json::to_string_pretty(&NounFile::from(n))?;

        fs_extra::file::write_all(file, &json_content)?;
        Ok(())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]