Compare commits
2 Commits
1d091bd419
...
616f8095e2
| Author | SHA1 | Date | |
|---|---|---|---|
| 616f8095e2 | |||
| 66514eb192 |
@@ -15,7 +15,7 @@ fn main() {
|
|||||||
|
|
||||||
println!("Here are {} recipes :", result.len());
|
println!("Here are {} recipes :", result.len());
|
||||||
for rec in result {
|
for rec in result {
|
||||||
println!("*************\n{}", rec.title);
|
println!("*************\n{}\n({:?})", rec.title, rec.category);
|
||||||
println!("-------------\n");
|
println!("-------------\n");
|
||||||
println!("{}", rec.ingredients);
|
println!("{}", rec.ingredients);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ extern crate diesel;
|
|||||||
use std::io::{Read, stdin};
|
use std::io::{Read, stdin};
|
||||||
use diesel::SqliteConnection;
|
use diesel::SqliteConnection;
|
||||||
use self::cookbook::*;
|
use self::cookbook::*;
|
||||||
use self::models::NewRecipe;
|
use self::models::{NewRecipe, fields::RecipeCategory};
|
||||||
|
|
||||||
struct CreateRecipe<'a> {
|
struct CreateRecipe<'a> {
|
||||||
connection: &'a SqliteConnection,
|
connection: &'a SqliteConnection,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
|
category: Option<RecipeCategory>,
|
||||||
ingredients: String,
|
ingredients: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ impl<'a> CreateRecipe<'a> {
|
|||||||
CreateRecipe{
|
CreateRecipe{
|
||||||
connection: conn,
|
connection: conn,
|
||||||
title: "New recipe",
|
title: "New recipe",
|
||||||
|
category: None,
|
||||||
ingredients: String::new(),
|
ingredients: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ extern crate dotenv;
|
|||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
|
||||||
mod recipe;
|
|
||||||
mod importer;
|
mod importer;
|
||||||
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|||||||
@@ -2,11 +2,66 @@ use super::schema::recipes;
|
|||||||
use super::schema::ingredients;
|
use super::schema::ingredients;
|
||||||
use super::diesel::prelude::*;
|
use super::diesel::prelude::*;
|
||||||
|
|
||||||
|
pub mod fields {
|
||||||
|
use diesel::{
|
||||||
|
backend::Backend,
|
||||||
|
sqlite::Sqlite,
|
||||||
|
sql_types::SmallInt,
|
||||||
|
deserialize::{self, FromSql},
|
||||||
|
serialize::{self, Output, ToSql},
|
||||||
|
};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// All recipes have a single associated category
|
||||||
|
/// representing the main use of the resulting preparation.
|
||||||
|
///
|
||||||
|
/// It is stored as Integer
|
||||||
|
#[derive(Debug, Clone, FromSqlRow, AsExpression)]
|
||||||
|
#[repr(i16)]
|
||||||
|
pub enum RecipeCategory {
|
||||||
|
Breakfast = 0,
|
||||||
|
Starter = 1,
|
||||||
|
MainCourse = 2,
|
||||||
|
Dessert = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecipeCategory {
|
||||||
|
fn from_id(id: i16) -> Option<RecipeCategory> {
|
||||||
|
match id {
|
||||||
|
0 => Some(RecipeCategory::Breakfast),
|
||||||
|
1 => Some(RecipeCategory::Starter),
|
||||||
|
2 => Some(RecipeCategory::MainCourse),
|
||||||
|
3 => Some(RecipeCategory::Dessert),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql<SmallInt, Sqlite> for RecipeCategory {
|
||||||
|
fn from_sql(bytes: Option<&<Sqlite as Backend>::RawValue>) -> deserialize::Result<Self> {
|
||||||
|
if let Some(result) = RecipeCategory::from_id(
|
||||||
|
<i16 as FromSql<SmallInt,Sqlite>>::from_sql(bytes)?)
|
||||||
|
{ Ok(result) }
|
||||||
|
else { Err("Invalid RecipeCategory id".into()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql<SmallInt, Sqlite> for RecipeCategory {
|
||||||
|
fn to_sql<W: Write>(&self, out: &mut Output<W, Sqlite>) -> serialize::Result {
|
||||||
|
let as_int = self.clone() as i16;
|
||||||
|
<i16 as ToSql<SmallInt, Sqlite>>::to_sql(&as_int, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Data for a recipe stored in DB
|
||||||
#[derive(Debug, Clone, Queryable)]
|
#[derive(Debug, Clone, Queryable)]
|
||||||
pub struct Recipe {
|
pub struct Recipe {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub category: i32,
|
pub category: fields::RecipeCategory,
|
||||||
pub ingredients: String,
|
pub ingredients: String,
|
||||||
pub preparation: String,
|
pub preparation: String,
|
||||||
}
|
}
|
||||||
@@ -15,13 +70,13 @@ pub struct Recipe {
|
|||||||
#[table_name="recipes"]
|
#[table_name="recipes"]
|
||||||
pub struct NewRecipe<'a> {
|
pub struct NewRecipe<'a> {
|
||||||
pub title: &'a str,
|
pub title: &'a str,
|
||||||
pub category: i32,
|
pub category: i16,
|
||||||
pub ingredients: &'a str,
|
pub ingredients: &'a str,
|
||||||
pub preparation: &'a str,
|
pub preparation: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NewRecipe<'a> {
|
impl<'a> NewRecipe<'a> {
|
||||||
pub fn new(title: &'a str, category: i32, ingredients: &'a str, preparation: &'a str) -> Self {
|
pub fn new(title: &'a str, category: i16, ingredients: &'a str, preparation: &'a str) -> Self {
|
||||||
NewRecipe{
|
NewRecipe{
|
||||||
title,
|
title,
|
||||||
category,
|
category,
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
/// Tag associated with ingredients
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum FoodTag {
|
|
||||||
Viande,
|
|
||||||
Poisson,
|
|
||||||
Legume,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Categories for recipe, to organize them for navigation and planning
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum RecipeCategory {
|
|
||||||
PetitDejeuner,
|
|
||||||
Entree,
|
|
||||||
Plat,
|
|
||||||
Dessert,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A collection of tags, to be improved
|
|
||||||
type FoodTags = Vec<FoodTag>;
|
|
||||||
|
|
||||||
/// Nutritionnal values per 100g, to be improved
|
|
||||||
#[derive(Default)]
|
|
||||||
struct NutritionnalValues;
|
|
||||||
|
|
||||||
/// An individual ingredient
|
|
||||||
#[derive(Default)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct Ingredient {
|
|
||||||
/// All known alias of a same ingredient
|
|
||||||
alias: Vec<String>,
|
|
||||||
/// Nutrionnal values per 100g
|
|
||||||
nutrition: NutritionnalValues,
|
|
||||||
/// Associated tags to constraint planning
|
|
||||||
tags: FoodTags,
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ table! {
|
|||||||
recipes (id) {
|
recipes (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
title -> Text,
|
title -> Text,
|
||||||
category -> Integer,
|
category -> SmallInt,
|
||||||
ingredients -> Text,
|
ingredients -> Text,
|
||||||
preparation -> Text,
|
preparation -> Text,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use self::planner::solver::{Variables, Domain, Problem, Constraint};
|
|||||||
/// Lunch => RecipeCategory::MainCourse
|
/// Lunch => RecipeCategory::MainCourse
|
||||||
/// Dinner => RecipeCategory::MainCourse
|
/// Dinner => RecipeCategory::MainCourse
|
||||||
type Day = String;
|
type Day = String;
|
||||||
|
const DAYS: &[&str] = &["Lundi", "Mardi", "Mercredi"];
|
||||||
|
|
||||||
enum Meals {
|
enum Meals {
|
||||||
Breakfast(Day),
|
Breakfast(Day),
|
||||||
@@ -36,7 +37,7 @@ enum RecipeCategory {
|
|||||||
/// It may also contains an initial value for each variable
|
/// It may also contains an initial value for each variable
|
||||||
fn generate_variables<V>(domain: &Domain<V>) -> Vec<(String, &Domain<V>, Option<&V>)> {
|
fn generate_variables<V>(domain: &Domain<V>) -> Vec<(String, &Domain<V>, Option<&V>)> {
|
||||||
let mut vars = Vec::new();
|
let mut vars = Vec::new();
|
||||||
for day in &["Lundi", "Mardi", "Mercredi"] {
|
for day in DAYS {
|
||||||
vars.push((Meals::Lunch(day.to_string()).into(), domain, None));
|
vars.push((Meals::Lunch(day.to_string()).into(), domain, None));
|
||||||
vars.push((Meals::Dinner(day.to_string()).into(), domain, None));
|
vars.push((Meals::Dinner(day.to_string()).into(), domain, None));
|
||||||
}
|
}
|
||||||
@@ -48,6 +49,19 @@ fn ingredients_contains<'a>(assign: &Variables<'a,Recipe>) -> bool {
|
|||||||
&& !assign.get("Mardi_Lunch").unwrap().unwrap().ingredients.contains("Patates")
|
&& !assign.get("Mardi_Lunch").unwrap().unwrap().ingredients.contains("Patates")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn pretty_output(res: &Variables<Recipe>) -> String {
|
||||||
|
let mut repr = String::new();
|
||||||
|
for (var,value) in res {
|
||||||
|
let value = match value {
|
||||||
|
Some(rec) => &rec.title,
|
||||||
|
None => "---",
|
||||||
|
};
|
||||||
|
repr.push_str(&format!("{} => {}\n", var, value));
|
||||||
|
}
|
||||||
|
repr
|
||||||
|
}
|
||||||
|
|
||||||
fn get_planning_all_results() -> String {
|
fn get_planning_all_results() -> String {
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
let possible_values = recipes::load_all(&conn);
|
let possible_values = recipes::load_all(&conn);
|
||||||
@@ -56,10 +70,13 @@ fn get_planning_all_results() -> String {
|
|||||||
for (var, dom, ini) in generate_variables(&domain) {
|
for (var, dom, ini) in generate_variables(&domain) {
|
||||||
problem = problem.add_variable(var, dom, ini);
|
problem = problem.add_variable(var, dom, ini);
|
||||||
}
|
}
|
||||||
let mut problem = problem.add_constraint(ingredients_contains)
|
let mut problem = problem
|
||||||
|
.add_constraint(
|
||||||
|
ingredients_contains
|
||||||
|
)
|
||||||
.finish();
|
.finish();
|
||||||
let results = problem.solve_all();
|
let results = problem.solve_all();
|
||||||
format!("{:#?}\nTotal = {}", &results.first(), results.len())
|
format!("{}\nTotal = {}", pretty_output(&results.first().unwrap()), results.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -7,23 +7,64 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>Cook Assistant</h1>
|
<h1>Cook Assistant</h1>
|
||||||
<p>Fetch value from wasm : {{ value }}</p>
|
<!-- Details View -->
|
||||||
<strong>{{ message }}</strong>
|
<section v-if="active_view > -1">
|
||||||
|
<button @click="setActiveView(-1)">X close</button>
|
||||||
|
<h4>{{ items[active_view].title }}</h4>
|
||||||
|
<p>{{ categories[items[active_view].category].name }}</p>
|
||||||
|
</section>
|
||||||
|
<!-- Category List View -->
|
||||||
|
<section v-else>
|
||||||
|
<div v-if="active_category == -1">
|
||||||
|
<div v-for="c in categories" :key="c.id">
|
||||||
|
<button @click="setActiveCategory(c.id)">{{ c.name }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<button @click="setActiveCategory(-1)"><< back</button>
|
||||||
|
<p>{{ categories[active_category].name }}</p>
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in displayed" :key="item.id">
|
||||||
|
<a href="" @click.prevent="setActiveView(item.id)">{{ item.title }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
var getWasmValue = function() {
|
|
||||||
return 42
|
|
||||||
};
|
|
||||||
|
|
||||||
var app = new Vue({
|
var app = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
message: 'Hello ! We are trying out here...',
|
categories: [
|
||||||
|
{id: 0, name: "Petit-déjeuner"},
|
||||||
|
{id: 1, name: "Plat principal"},
|
||||||
|
{id: 2, name: "Dessert"}
|
||||||
|
],
|
||||||
|
active_category: -1,
|
||||||
|
active_view: -1,
|
||||||
|
items: [
|
||||||
|
{id: 0, title: "Raclette", category: 1},
|
||||||
|
{id: 1, title: "Tartiflette", category: 1},
|
||||||
|
{id: 2, title: "Pancakes", category: 0},
|
||||||
|
{id: 3, title: "Crêpes", category: 2}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setActiveCategory: function(id) {
|
||||||
|
this.active_category = id;
|
||||||
|
},
|
||||||
|
setActiveView: function(id) {
|
||||||
|
this.active_view = id;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
value: getWasmValue,
|
displayed: function() {
|
||||||
|
return this.items.filter(
|
||||||
|
rec => rec.category == this.active_category
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user