Compare commits

..

2 Commits

Author SHA1 Message Date
616f8095e2 Adds basic vuejs functionnality, implements RecipeCategory custom field 2019-02-01 15:58:58 +01:00
66514eb192 Adds pretty_output 2019-02-01 13:53:50 +01:00
8 changed files with 133 additions and 55 deletions

View File

@@ -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);
} }

View File

@@ -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(),
} }
} }

View File

@@ -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::*;

View File

@@ -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,

View File

@@ -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,
}

View File

@@ -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,
} }

View File

@@ -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() {

View File

@@ -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>