Skip to content

Commit 85b91b0

Browse files
committed
fix: issue with . base path in server
1 parent 48e1a61 commit 85b91b0

File tree

4 files changed

+89
-42
lines changed

4 files changed

+89
-42
lines changed

README.md

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
CookCLI provides a suite of tools to create shopping lists and maintain recipes. We've built it to be simple and useful for automating your cooking and shopping routine with existing UNIX command line and scripting tools. It can also function as a webserver for your recipes, making them browsable on any device with a web browser.
44

5-
* [Example usage](#example-usage)
6-
* [Installation](#installation)
7-
* [Building from source](#building-from-source)
8-
* [Contribution](#contribution)
9-
* [License](#license)
5+
* [Example usage](#example-usage)
6+
* [Installation](#installation)
7+
* [Building from source](#building-from-source)
8+
* [Contribution](#contribution)
9+
* [License](#license)
1010

1111
## Example usage
1212

@@ -125,34 +125,38 @@ Run a web-server:
125125
You can find full documentation at https://cooklang.org/cli/help/ or by running `help` command.
126126

127127
```
128-
Usage: cook [OPTIONS] COMMAND
128+
A command-line interface for managing and working with Cooklang recipes
129129
130-
Options:
131-
-a, --aisle <aisle> Specify an aisle.conf file to override shopping list default settings
132-
-u, --units <units> Specify a units.conf file to override units default settings
133-
-i, --inflection <inflection> Specify an inflection.conf file to override default inflection settings
134-
-h, --help Show help information.
130+
Usage: cook <COMMAND>
135131
136132
Commands:
137-
recipe Manage recipes and recipe files
138-
shopping-list Create a shopping list
139-
server Run a webserver to serve your recipes on the web
140-
fetch Pull recipes from the community recipe repository
141-
version Show the CookCLI version information
142-
help Show the help text for a sub-command
133+
recipe Manage recipe files
134+
server Run a webserver to serve your recipes on the web
135+
shopping-list Create a shopping list [aliases: sl]
136+
seed Populate directory with seed recipes
137+
help Print this message or the help of the given subcommand(s)
138+
139+
Options:
140+
-h, --help Print help
141+
-V, --version Print version
142+
143+
Docs: https://cooklang.org/cli/help/
143144
```
144145

145146
## Installation
146147

147148
Download latest release for your platform from the [releases page](https://github.com/cooklang/CookCLI/releases) and add the file to your operating system's PATH.
148149

149-
On Linux (or [WSL](https://docs.microsoft.com/en-us/windows/wsl/about)), this is easy. Simply extract the binary into your binaries folder with `sudo unzip CookCLI_1.0.0_linux_amd64.zip -d /usr/local/bin/` (note: you may need to install the unzip package first).
150+
On Linux (or [WSL](https://docs.microsoft.com/en-us/windows/wsl/about)), this is easy. Simply extract the binary into your binaries folder (for example `/usr/local/bin/`).
150151

151152
On MacOS:
152153

153154
brew tap cooklang/tap
154155
brew install cooklang/tap/cook
155156

157+
With Cargo:
158+
159+
cargo install cookcli
156160

157161
## Building from source
158162

src/main.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use cooklang::Converter;
3636
use cooklang::CooklangParser;
3737
use cooklang::Extensions;
3838
use once_cell::sync::OnceCell;
39-
use std::path::Path;
39+
use crate::util::resolve_to_absolute_path;
4040

4141
// commands
4242
mod recipe;
@@ -100,13 +100,15 @@ fn configure_context() -> Result<Context> {
100100
_ => Utf8PathBuf::from("."),
101101
};
102102

103-
if !base_path.is_dir() {
104-
bail!("Base path is not a directory: {base_path}");
103+
let absolute_base_path = resolve_to_absolute_path(&base_path)?;
104+
105+
if !absolute_base_path.is_dir() {
106+
bail!("Base path is not a directory: {}", absolute_base_path);
105107
}
106108

107109
Ok(Context {
108110
parser: OnceCell::new(),
109-
base_path,
111+
base_path: absolute_base_path,
110112
})
111113
}
112114

@@ -127,15 +129,6 @@ fn configure_logging() {
127129
.init();
128130
}
129131

130-
pub fn resolve_path(base_path: &Utf8Path, path: &Path) -> Utf8PathBuf {
131-
let path = Utf8Path::from_path(path).expect(UTF8_PATH_PANIC);
132-
if path.is_absolute() {
133-
path.to_path_buf()
134-
} else {
135-
base_path.join(LOCAL_CONFIG_DIR).join(path)
136-
}
137-
}
138-
139132
pub fn global_file_path(name: &str) -> Result<Utf8PathBuf> {
140133
let dirs = directories::ProjectDirs::from("", "", APP_NAME)
141134
.context("Could not determine home directory path")?;

src/server/mod.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use std::{net::SocketAddr, sync::Arc};
4646
use tower_http::cors::CorsLayer;
4747
use tracing::info;
4848
use crate::util::split_recipe_name_and_scaling_factor;
49+
use crate::util::resolve_to_absolute_path;
4950

5051
#[derive(Debug, Args)]
5152
pub struct ServerArgs {
@@ -127,14 +128,17 @@ fn build_state(ctx: Context, args: ServerArgs) -> Result<Arc<AppState>> {
127128
let parser = parser.into_inner().unwrap();
128129

129130
let path = args.base_path.as_ref().unwrap_or(&base_path);
131+
let absolute_path = resolve_to_absolute_path(path)?;
130132

131-
if path.is_file() {
132-
bail!("Base path{} is not a directory", path);
133+
if absolute_path.is_file() {
134+
bail!("Base path {} is not a directory", absolute_path);
133135
}
134136

137+
tracing::info!("Using absolute base path: {:?}", absolute_path);
138+
135139
Ok(Arc::new(AppState {
136140
parser,
137-
base_path: path.clone(),
141+
base_path: absolute_path,
138142
aisle_path,
139143
}))
140144
}
@@ -175,7 +179,7 @@ mod ui {
175179
}
176180

177181
#[derive(RustEmbed)]
178-
#[folder = "./ui/public/"]
182+
#[folder = "ui/public/"]
179183
struct Assets;
180184

181185
async fn static_ui(uri: Uri) -> impl axum::response::IntoResponse {
@@ -241,6 +245,7 @@ fn check_path(p: &str) -> Result<(), StatusCode> {
241245
.components()
242246
.all(|c| matches!(c, Utf8Component::Normal(_)))
243247
{
248+
tracing::error!("Invalid path: {p}");
244249
return Err(StatusCode::BAD_REQUEST);
245250
}
246251
Ok(())
@@ -258,7 +263,10 @@ async fn shopping_list(
258263
.map(|(name, scaling_factor)| {
259264
let target = scaling_factor
260265
.parse::<f64>()
261-
.map_err(|_| StatusCode::BAD_REQUEST)?;
266+
.map_err(|_| {
267+
tracing::error!("Invalid scaling factor: {scaling_factor}");
268+
StatusCode::BAD_REQUEST
269+
})?;
262270
Ok::<_, StatusCode>((name, target))
263271
})
264272
.unwrap_or(Ok((entry.as_str(), 1.0)))?;
@@ -304,9 +312,16 @@ async fn all_recipes(
304312
State(state): State<Arc<AppState>>,
305313
) -> Result<Json<serde_json::Value>, StatusCode> {
306314
let recipes = cooklang_find::build_tree(&state.base_path)
307-
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
315+
.map_err(|e| {
316+
tracing::error!("Failed to build recipe tree: {:?}", e);
317+
StatusCode::INTERNAL_SERVER_ERROR
318+
})?;
308319

309-
let recipes = serde_json::to_value(recipes).unwrap();
320+
let recipes = serde_json::to_value(recipes)
321+
.map_err(|e| {
322+
tracing::error!("Failed to serialize recipes: {:?}", e);
323+
StatusCode::INTERNAL_SERVER_ERROR
324+
})?;
310325

311326
Ok(Json(recipes))
312327
}
@@ -329,10 +344,11 @@ async fn recipe(
329344
) -> Result<Json<serde_json::Value>, StatusCode> {
330345
check_path(&path)?;
331346

332-
let entry = cooklang_find::get_recipe(vec![&state.base_path], &Utf8PathBuf::from(path))
333-
.map_err(|_| StatusCode::NOT_FOUND)?;
334-
335-
tracing::info!("Entry path: {:?}", entry.path());
347+
let entry = cooklang_find::get_recipe(vec![&state.base_path], &Utf8PathBuf::from(&path))
348+
.map_err(|_| {
349+
tracing::error!("Recipe not found: {path}");
350+
StatusCode::NOT_FOUND
351+
})?;
336352

337353
let recipe = entry.recipe(query.scale.unwrap_or(1.0));
338354

src/util/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub mod cooklang_to_md;
3535
use anyhow::{Context as _, Result};
3636

3737
use camino::Utf8Path;
38+
use camino::Utf8PathBuf;
3839

3940
pub fn write_to_output<F>(output: Option<&Utf8Path>, f: F) -> Result<()>
4041
where
@@ -54,3 +55,36 @@ where
5455
pub fn split_recipe_name_and_scaling_factor(query: &str) -> Option<(&str, &str)> {
5556
query.trim().rsplit_once('@')
5657
}
58+
59+
/// Resolves a path to an absolute path. If the input path is already absolute,
60+
/// it is returned as is. Otherwise, it is resolved relative to the current working directory.
61+
/// The path is normalized to remove any `.` or `..` components.
62+
pub fn resolve_to_absolute_path(path: &Utf8Path) -> anyhow::Result<Utf8PathBuf> {
63+
let absolute = if path.is_absolute() {
64+
path.to_path_buf()
65+
} else {
66+
std::env::current_dir()
67+
.map_err(|e| {
68+
tracing::error!("Failed to get current directory: {:?}", e);
69+
anyhow::anyhow!("Failed to get current directory")
70+
})?
71+
.join(path)
72+
.try_into()
73+
.map_err(|e| {
74+
tracing::error!("Failed to convert path to UTF-8: {:?}", e);
75+
anyhow::anyhow!("Failed to convert path to UTF-8")
76+
})?
77+
};
78+
79+
// Normalize the path by resolving all components
80+
std::fs::canonicalize(&absolute)
81+
.map_err(|e| {
82+
tracing::error!("Failed to canonicalize path: {:?}", e);
83+
anyhow::anyhow!("Failed to canonicalize path")
84+
})?
85+
.try_into()
86+
.map_err(|e| {
87+
tracing::error!("Failed to convert canonicalized path to UTF-8: {:?}", e);
88+
anyhow::anyhow!("Failed to convert canonicalized path to UTF-8")
89+
})
90+
}

0 commit comments

Comments
 (0)