Skip to content

Commit

Permalink
feat: replace JSON store with support for importing from files
Browse files Browse the repository at this point in the history
fix(common): remove unused input prompt functions

fix(dockerignore): do not ignore import files

fix(gitignore): update import file names

fix(persistence): remove dependency on 'question' crate

docs: replace migration from JSON store to import from file

feat: replace JSON store with support for importing from files

fix(common): remove unused methods on 'List'

fix(common): remove unused methods on 'Items'

fix(common): move 'Section' to separate module
  • Loading branch information
suchapalaver committed Jan 4, 2024
1 parent 8501d98 commit 79a0237
Show file tree
Hide file tree
Showing 27 changed files with 195 additions and 1,451 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
!Cargo.*
!crates
!.env
!items.json
!list.json
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ target/
# These are backup files generated by rustfmt
**/*.rs.bk

# Local JSON 'groceries.json' and 'list.json' files
# Local JSON 'items.json' and 'list.json' files
*.json

/sqlite.db
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions crates/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use std::fmt::{self, Display};

use common::{
commands::ApiCommand,
item::{Item, Name, Section},
item::{Item, Name},
items::Items,
list::List,
recipes::{Ingredients, Recipe},
section::Section,
};
use persistence::store::{Store, StoreDispatch, StoreError, StoreResponse, StoreType};

Expand Down Expand Up @@ -120,7 +121,7 @@ pub enum ApiResponse {
FetchedRecipe((Recipe, Ingredients)),
ItemAlreadyAdded(Name),
Items(Items),
JsonToSqlite,
ImportToSqlite,
List(List),
NothingReturned(ApiCommand),
Recipes(Vec<Recipe>),
Expand Down Expand Up @@ -159,12 +160,12 @@ impl Display for ApiResponse {
Self::ItemAlreadyAdded(item) => writeln!(f, "\nitem already added: {item}"),
Self::Items(items) => {
writeln!(f)?;
for item in items.collection() {
for item in items.collection_iter() {
writeln!(f, "{item}")?;
}
Ok(())
}
Self::JsonToSqlite => writeln!(f, "\nJSON to SQLite data store migration successful"),
Self::ImportToSqlite => writeln!(f, "\nJSON to SQLite data store migration successful"),
Self::List(list) => {
writeln!(f)?;
for item in list.items() {
Expand Down Expand Up @@ -216,7 +217,7 @@ impl From<StoreResponse> for ApiResponse {
StoreResponse::FetchedRecipe(item) => Self::FetchedRecipe(item),
StoreResponse::ItemAlreadyAdded(item) => Self::ItemAlreadyAdded(item),
StoreResponse::Items(item) => Self::Items(item),
StoreResponse::JsonToSqlite => Self::JsonToSqlite,
StoreResponse::ImportToSqlite => Self::ImportToSqlite,
StoreResponse::List(item) => Self::List(item),
StoreResponse::NothingReturned(item) => Self::NothingReturned(item),
StoreResponse::Recipes(item) => Self::Recipes(item),
Expand Down
5 changes: 3 additions & 2 deletions crates/common/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use url::Url;

use crate::{
item::{Name, Section},
item::Name,
recipes::{Ingredients, Recipe},
section::Section,
};

#[derive(Debug)]
pub enum ApiCommand {
Add(Add),
Delete(Delete),
FetchRecipe(Url),
MigrateJsonDbToSqlite,
ImportFromJson,
Read(Read),
Update(Update),
}
Expand Down
86 changes: 1 addition & 85 deletions crates/common/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,6 @@
use crate::{
item::{Item, SECTIONS},
recipes::Recipe,
};
use crate::item::Item;
use question::{Answer, Question};

pub fn user_wants_to_add_item() -> Answer {
Question::new("Add an item to our library?")
.default(question::Answer::NO)
.show_defaults()
.confirm()
}

pub fn user_wants_to_print_list() -> Answer {
Question::new("Print shopping list?")
.default(question::Answer::NO)
.show_defaults()
.confirm()
}

pub fn user_wants_to_add_more_recipe_ingredients_to_list() -> Answer {
Question::new("Add more recipe ingredients to our list?")
.default(question::Answer::NO)
.show_defaults()
.confirm()
}

pub fn user_wants_to_add_items_to_list() -> Answer {
Question::new("Add items to list?")
.default(question::Answer::NO)
.show_defaults()
.confirm()
}

// Returns `None` in case user wishes to skip being asked further.
pub fn user_wants_to_add_item_to_list(item: &Item) -> Option<bool> {
let res = Question::new(&format!(
Expand All @@ -50,59 +19,6 @@ pub fn user_wants_to_add_item_to_list(item: &Item) -> Option<bool> {
}
}

pub fn user_wants_to_save_list() -> Answer {
Question::new("Save current list?")
.default(question::Answer::NO)
.show_defaults()
.confirm()
}

// Returns `None` in case user wishes to skip being asked further.
pub fn user_wants_to_add_recipe_to_list(recipe: &Recipe) -> Option<bool> {
let res = Question::new(&format!(
"Shall we add {recipe}? (*y*, *n* for next recipe, *s* to skip to end of recipes)",
))
.acceptable(vec!["y", "n", "s"])
.until_acceptable()
.default(Answer::RESPONSE("n".to_string()))
.ask();

match res {
Some(Answer::RESPONSE(res)) if &res == "y" => Some(true),
Some(Answer::RESPONSE(res)) if &res == "s" => None,
_ => Some(false),
}
}

pub fn item_from_user() -> String {
let ans = Question::new(
"What is the item?\n\
e.g. 'bread'",
)
.ask();

if let Some(Answer::RESPONSE(ans)) = ans {
ans
} else {
item_from_user()
}
}

pub fn section_from_user() -> String {
if let Some(Answer::RESPONSE(ans)) = Question::new(
"What is the section?\n\
e.g. 'bread'",
)
.acceptable(SECTIONS.to_vec())
.until_acceptable()
.ask()
{
ans
} else {
section_from_user()
}
}

pub fn item_matches(item: &Item) -> Answer {
Question::new(&format!("is *{item}* a match?"))
.default(question::Answer::NO)
Expand Down
52 changes: 3 additions & 49 deletions crates/common/src/item.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use std::fmt;

use crate::recipes::Recipe;
use crate::{recipes::Recipe, section::Section};

/// An item used in recipes or bought separately
///
Expand Down Expand Up @@ -37,36 +37,21 @@ impl Item {
self.recipes.as_ref()
}

pub fn add_recipe(&mut self, recipe: &str) {
let recipe = recipe.into();
if let Some(recipes) = &mut self.recipes {
if !recipes.contains(&recipe) {
recipes.push(recipe);
}
} else {
self.recipes = Some(vec![recipe]);
}
}

pub fn delete_recipe(&mut self, name: &str) {
if let Some(vec) = self.recipes.as_mut() {
vec.retain(|x| x.as_str() != name)
}
}

pub fn with_section(mut self, section: impl Into<String>) -> Self {
self.section = Some(Section(section.into()));
pub fn with_section(mut self, section: &str) -> Self {
self.section = Some(section.into());
self
}

pub fn with_recipes(mut self, recipes: &[Recipe]) -> Self {
self.recipes = Some(recipes.to_vec());
self
}

pub(crate) fn matches(&self, s: impl Into<String>) -> bool {
s.into().split(' ').all(|word| !self.name.0.contains(word))
}
}

impl fmt::Display for Item {
Expand Down Expand Up @@ -101,34 +86,3 @@ impl Name {
&self.0
}
}

pub const SECTIONS: [&str; 5] = ["fresh", "pantry", "protein", "dairy", "freezer"];

#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
pub struct Section(String);

impl From<&str> for Section {
fn from(value: &str) -> Self {
Self(value.trim().to_lowercase())
}
}

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

impl Section {
pub fn new(sec: impl Into<String>) -> Self {
Self(sec.into())
}

pub fn as_str(&self) -> &str {
&self.0
}

pub fn contains(&self, s: &Section) -> bool {
self.0.contains(s.as_str())
}
}
79 changes: 2 additions & 77 deletions crates/common/src/items.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use serde::{Deserialize, Serialize};

use crate::{
item::{Item, Section},
recipes::{Ingredients, Recipe},
Load,
};
use crate::{item::Item, recipes::Recipe, section::Section, Load};

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct Items {
Expand Down Expand Up @@ -33,84 +29,13 @@ impl Items {
Self::default()
}

pub fn collection(&self) -> impl Iterator<Item = &Item> {
pub fn collection_iter(&self) -> impl Iterator<Item = &Item> {
self.collection.iter()
}

pub fn get_item_matches(&self, name: &str) -> impl Iterator<Item = &Item> {
self.collection
.iter()
.filter(|item| item.matches(name))
.collect::<Vec<_>>()
.into_iter()
}

pub fn add_item(&mut self, item: Item) {
if !self.collection.iter().any(|i| i.name() == item.name()) {
self.collection.push(item);
}
}

pub fn delete_item(&mut self, name: &str) {
self.collection = self
.collection
.drain(..)
.filter(|item| item.name().as_str() != name)
.collect();
}

pub fn items(&self) -> impl Iterator<Item = &Item> {
self.sections
.iter()
.flat_map(|section| {
self.collection.iter().filter(|item| {
item.section()
.map_or(false, |item_section| item_section.contains(section))
})
})
.collect::<Vec<_>>()
.into_iter()
}

pub fn recipes(&self) -> impl Iterator<Item = &Recipe> {
self.recipes.iter()
}

pub fn add_recipe(&mut self, name: &str, ingredients: &str) {
let ingredients = Ingredients::from_input_string(ingredients);

ingredients
.iter()
.for_each(|ingredient| self.add_item(ingredient.into()));

self.collection
.iter_mut()
.filter(|item| ingredients.contains(item.name()))
.for_each(|item| item.add_recipe(name));

self.recipes.push(name.into());
}

pub fn delete_recipe(&mut self, name: &str) {
self.recipes = self
.recipes
.drain(..)
.filter(|recipe| recipe.as_str() != name)
.collect();

for item in &mut self.collection {
item.delete_recipe(name);
}
}

pub fn recipe_ingredients(&self, recipe: &Recipe) -> impl Iterator<Item = &Item> {
self.collection
.iter()
.filter(|item| {
item.recipes()
.map_or(false, |recipes| recipes.contains(recipe))
})
.collect::<Vec<_>>()
.into_iter()
}
}
1 change: 1 addition & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod item;
pub mod items;
pub mod list;
pub mod recipes;
pub mod section;
pub mod telemetry;

use std::{
Expand Down
Loading

0 comments on commit 79a0237

Please sign in to comment.