Skip to content

Commit dad08cc

Browse files
authored
Merge pull request #143 from cooklang/feat/templates
feat: add basic (very) support for templates
2 parents 2a3f13c + 3538d20 commit dad08cc

File tree

15 files changed

+177
-31
lines changed

15 files changed

+177
-31
lines changed

Cargo.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,33 @@ path = "src/main.rs"
1313
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1414

1515
[dependencies]
16-
clap = { version = "4.5", features = ["derive"] }
17-
cooklang = { version = "0.16.1" }
16+
anstream = "0.6"
17+
anstyle = "1"
18+
anstyle-yansi = "2"
1819
anyhow = "1"
20+
axum = { version = "0.8" }
1921
camino = { version = "1", features = ["serde1"] }
22+
clap = { version = "4.5", features = ["derive"] }
23+
cooklang = { version = "0.16.1" }
24+
cooklang-find = { version = "0.2.1" }
25+
cooklang-import = "0.4.1"
26+
cooklang-reports = { version = "0.1" }
27+
directories = "6"
28+
humantime = "2"
29+
mime_guess = "2.0"
2030
once_cell = "1"
21-
serde_json = "1.0"
22-
anstream = "0.6"
23-
tracing = "0.1"
24-
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
31+
open = "5.3"
32+
openssl = { version = "0.10", features = ["vendored"] }
33+
regex = "1"
34+
rust-embed = "8"
2535
serde = "1.0"
26-
directories = "6"
36+
serde_json = "1.0"
2737
serde_yaml = "0.9"
28-
rust-embed = "8"
38+
tabular = { version = "0.2", features = ["ansi-cell"] }
39+
textwrap = { version = "0.16", features = ["terminal_size"] }
2940
tokio = { version = "1", features = ["full"] }
30-
axum = { version = "0.8" }
3141
tower = { version = "0.5", features = ["util"] }
3242
tower-http = { version = "0.6", features = ["fs", "trace", "cors"] }
33-
mime_guess = "2.0"
34-
open = "5.3"
35-
openssl = { version = "0.10", features = ["vendored"] }
36-
cooklang-find = { version = "0.2.1" }
37-
textwrap = { version = "0.16", features = ["terminal_size"] }
38-
tabular = { version = "0.2", features = ["ansi-cell"] }
43+
tracing = "0.1"
44+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
3945
yansi = "1"
40-
anstyle-yansi = "2"
41-
humantime = "2"
42-
anstyle = "1"
43-
regex = "1"
44-
cooklang-import = "0.4.1"

seed/Breakfast/Easy Pancakes.cook

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ servings: 2
33
tags: breakfast
44
---
55

6-
Crack the @eggs{3} into a blender, then add the @flour{125%g}, @milk{250%ml} and @sea salt{pinch}, and blitz until smooth.
6+
Crack the @eggs{3} into a blender, then add the @flour{125%g},
7+
@milk{250%ml} and @sea salt{1%pinch}, and blitz until smooth.
78

89
Pour into a bowl and leave to stand for 15 minutes.
910

10-
Melt the butter (or a drizzle of @olive oil{} if you want to be a bit healthier) in a large non-stick frying pan on a medium heat, then tilt the pan so the butter coats the surface.
11+
Melt @olive oil{1%drizzle} in a large non-stick frying pan on
12+
a medium heat, then tilt the pan so the butter coats the surface.
1113

12-
Pour in 1 ladle of batter and tilt again, so that the batter spreads all over the base, then cook for 1 to 2 minutes, or until it starts to come away from the sides.
14+
Pour in 1 ladle of batter and tilt again, so that the batter
15+
spreads all over the base, then cook for 1 to 2 minutes,
16+
or until it starts to come away from the sides.
1317

14-
Once golden underneath, flip the pancake over and cook for 1 further minute, or until cooked through.
18+
Once golden underneath, flip the pancake over and cook
19+
for 1 further minute, or until cooked through.
1520

1621
Serve straightaway with your favourite topping.

seed/db/eggs/meta.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
storage:
2+
shelf life: 30
3+
fridge life: 60
4+
density: 1.03

seed/db/eggs/shopping.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
price_per_unit: 0.25 # 1 egg

seed/db/flour/shopping.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
price_per_unit: 0.0015 # 1g

seed/db/milk/shopping.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
price_per_unit: 0.001 # 1ml

seed/db/olive oil/shopping.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
price_per_unit: 0.005 # 1ml

seed/db/sea salt/shopping.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
price_per_unit: 0.001 # 1g

seed/reports/cost.md.jinja

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Cost Report
2+
{% set ns = namespace() %}
3+
{%- set ns.total = 0 %}
4+
{%- for ingredient in ingredients %}
5+
{%- set price = db(ingredient.name ~ '.shopping.price_per_unit') * (ingredient.quantity.value | float) %}
6+
* {{ ingredient.name }}: ${{ price | format_price(2) }}
7+
{%- set ns.total = ns.total + price %}
8+
{%- endfor %}
9+
10+
Total: ${{ ns.total | format_price }}

seed/reports/ingredients.md.jinja

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{ metadata }}
2+
3+
Scale: {{ scale }}x
4+
5+
## Ingredients
6+
{%- for ingredient in ingredients %}
7+
- {{ ingredient.name }} {{ ingredient.quantity }}
8+
{%- endfor %}
9+

src/args.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
use clap::{Parser, Subcommand};
3232

33-
use crate::{recipe, search, seed, server, shopping_list, import};
33+
use crate::{import, recipe, report, search, seed, server, shopping_list};
3434

3535
#[derive(Parser, Debug)]
3636
#[command(
@@ -71,4 +71,8 @@ pub enum Command {
7171
/// Import a recipe from a URL
7272
#[command(alias = "i")]
7373
Import(import::ImportArgs),
74+
75+
/// Generate a report from a recipe using a Jinja2 template
76+
#[command(alias = "rp")]
77+
Report(report::ReportArgs),
7478
}

src/import.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ pub struct ImportArgs {
1717
pub fn run(_ctx: &Context, args: ImportArgs) -> Result<()> {
1818
let recipe = tokio::runtime::Runtime::new()?.block_on(async {
1919
if args.skip_conversion {
20-
let recipe = fetch_recipe(&args.url).await.map_err(|e| anyhow::anyhow!("{}", e))?;
20+
let recipe = fetch_recipe(&args.url)
21+
.await
22+
.map_err(|e| anyhow::anyhow!("{}", e))?;
2123
Ok(format!(
2224
"{}\n\n[Ingredients]\n{}\n\n[Instructions]\n{}",
23-
recipe.name,
24-
recipe.ingredients,
25-
recipe.instructions
25+
recipe.name, recipe.ingredients, recipe.instructions
2626
))
2727
} else {
28-
import_recipe(&args.url).await.map_err(|e| anyhow::anyhow!("{}", e))
28+
import_recipe(&args.url)
29+
.await
30+
.map_err(|e| anyhow::anyhow!("{}", e))
2931
}
3032
})?;
3133

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ use cooklang::CooklangParser;
3737
use once_cell::sync::OnceCell;
3838

3939
// commands
40+
mod import;
4041
mod recipe;
42+
mod report;
4143
mod search;
4244
mod seed;
4345
mod server;
4446
mod shopping_list;
45-
mod import;
4647

4748
// other modules
4849
mod args;
@@ -67,6 +68,7 @@ pub fn main() -> Result<()> {
6768
Command::Seed(args) => seed::run(&ctx, args),
6869
Command::Search(args) => search::run(&ctx, args),
6970
Command::Import(args) => import::run(&ctx, args),
71+
Command::Report(args) => report::run(&ctx, args),
7072
}
7173
}
7274

src/report.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use crate::util::split_recipe_name_and_scaling_factor;
2+
use anyhow::{Context, Result};
3+
use camino::Utf8PathBuf;
4+
use clap::{CommandFactory, Parser};
5+
use cooklang_reports::{config::Config, render_template_with_config};
6+
use std::{fs, path::PathBuf};
7+
8+
#[derive(Parser, Debug)]
9+
pub struct ReportArgs {
10+
/// Path to the Jinja2 template file
11+
#[arg(short, long)]
12+
template: Utf8PathBuf,
13+
14+
/// Path to the recipe file (can include scaling factor with :N suffix)
15+
#[arg()]
16+
recipe: String,
17+
18+
/// Path to the datastore directory (optional)
19+
#[arg(short, long)]
20+
datastore: Option<Utf8PathBuf>,
21+
}
22+
23+
pub fn run(_ctx: &crate::Context, args: ReportArgs) -> Result<()> {
24+
// Print warning about prototype feature
25+
eprintln!("⚠️ Warning: The report command is a prototype feature and will change in future versions.");
26+
27+
// Split recipe name and scaling factor
28+
let (recipe_name, scaling_factor) = split_recipe_name_and_scaling_factor(&args.recipe)
29+
.map(|(name, factor)| {
30+
let scale = factor.parse::<f64>().unwrap_or_else(|err| {
31+
let mut cmd = crate::CliArgs::command();
32+
cmd.error(
33+
clap::error::ErrorKind::InvalidValue,
34+
format!("Invalid scaling factor for '{name}': {err}"),
35+
)
36+
.exit()
37+
});
38+
(name, scale)
39+
})
40+
.unwrap_or((&args.recipe, 1.0));
41+
42+
// Read the recipe file
43+
let recipe = fs::read_to_string(recipe_name)
44+
.with_context(|| format!("Failed to read recipe file: {}", recipe_name))?;
45+
46+
// Read the template file
47+
let template = fs::read_to_string(&args.template)
48+
.with_context(|| format!("Failed to read template file: {}", args.template))?;
49+
50+
// Create config with scale and optional datastore
51+
let mut builder = Config::builder();
52+
builder.scale(scaling_factor);
53+
54+
if let Some(datastore) = args.datastore {
55+
builder.datastore_path(PathBuf::from(datastore));
56+
}
57+
58+
let config = builder.build();
59+
60+
// Render the report
61+
let report = render_template_with_config(&recipe, &template, &config)
62+
.with_context(|| "Failed to render report from template")?;
63+
64+
// Print the report
65+
println!("{}", report);
66+
67+
Ok(())
68+
}

0 commit comments

Comments
 (0)