Skip to content

Commit

Permalink
Say no to openssl (#168)
Browse files Browse the repository at this point in the history
`cargo-generate` is a very powerful project, but the `openssl`
dependency is causing many issues to Windows users.
Furthermore, it brings _a lot_ of heavy dependencies to `pavexc_cli`,
significantly slowing down its compilation times.

This PR removes the dependency from `cargo-generate` in favour of a
stripped down version that only contains the feature we need in `pavex
new`.
  • Loading branch information
LukeMathWalker authored Jan 18, 2024
1 parent 2155773 commit 27da98b
Show file tree
Hide file tree
Showing 17 changed files with 879 additions and 649 deletions.
632 changes: 31 additions & 601 deletions libs/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion libs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["./pavex*", "persist_if_changed"]
members = ["./pavex*", "generate_from_path", "persist_if_changed"]
resolver = "2"

[workspace.dependencies]
Expand Down
19 changes: 19 additions & 0 deletions libs/generate_from_path/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "generate_from_path"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.79"
toml = "0.8.8"
path-absolutize = "3.1.1"
tracing = "0.1.40"
tempfile = "3"
fs-err = "2.11.0"
liquid = "0.26.4"
liquid-core = "0.26.4"
heck = "0.5.0-rc.1"
indicatif = "0.17.7"
walkdir = "2.4.0"
sanitize-filename = "0.5"
regex = "1.10.2"
180 changes: 180 additions & 0 deletions libs/generate_from_path/src/filenames.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use crate::template::LiquidObjectResource;

use crate::template::render_string_gracefully;
use liquid::Parser;
use std::path::{Component, Path, PathBuf};

pub fn substitute_filename(
filepath: &Path,
parser: &Parser,
context: &mut LiquidObjectResource,
) -> Result<PathBuf, anyhow::Error> {
let mut path = PathBuf::new();
for elem in filepath.components() {
match elem {
Component::Normal(e) => {
let parsed = render_string_gracefully(context, parser, e.to_str().unwrap())?;
let parsed = sanitize_filename(parsed.as_str());
path.push(parsed);
}
other => path.push(other),
}
}
Ok(path)
}

fn sanitize_filename(filename: &str) -> String {
use sanitize_filename::sanitize_with_options;

let options = sanitize_filename::Options {
truncate: true, // true by default, truncates to 255 bytes
replacement: "_", // str to replace sanitized chars/strings
..sanitize_filename::Options::default()
};

sanitize_with_options(filename, options)
}

#[cfg(test)]
mod tests {
use super::*;
use liquid::{model::Value, Object};

#[test]
fn should_do_happy_path() {
assert_eq!(
substitute_filename("{{author}}.rs", &mut prepare_context("sassman")).unwrap(),
"sassman.rs"
);
#[cfg(unix)]
assert_eq!(
substitute_filename(
"/tmp/project/{{author}}.rs",
&mut prepare_context("sassman")
)
.unwrap(),
"/tmp/project/sassman.rs"
);
#[cfg(unix)]
assert_eq!(
substitute_filename(
"/tmp/project/{{author}}/{{author}}.rs",
&mut prepare_context("sassman")
)
.unwrap(),
"/tmp/project/sassman/sassman.rs"
);
#[cfg(windows)]
assert_eq!(
substitute_filename(
"C:/tmp/project/{{author}}.rs",
&mut prepare_context("sassman")
)
.unwrap(),
"C:\\tmp\\project\\sassman.rs"
);
#[cfg(windows)]
assert_eq!(
substitute_filename(
"C:/tmp/project/{{author}}/{{author}}.rs",
&mut prepare_context("sassman")
)
.unwrap(),
"C:\\tmp\\project\\sassman\\sassman.rs"
);
}

#[test]
fn should_prevent_invalid_filenames() {
#[cfg(unix)]
assert_eq!(
substitute_filename("/tmp/project/{{author}}.rs", &mut prepare_context("s/a/s"))
.unwrap(),
"/tmp/project/s_a_s.rs"
);
#[cfg(unix)]
assert_eq!(
substitute_filename(
"/tmp/project/{{author}}/{{author}}.rs",
&mut prepare_context("s/a/s")
)
.unwrap(),
"/tmp/project/s_a_s/s_a_s.rs"
);
#[cfg(windows)]
assert_eq!(
substitute_filename(
"C:/tmp/project/{{author}}.rs",
&mut prepare_context("s/a/s")
)
.unwrap(),
"C:\\tmp\\project\\s_a_s.rs"
);
#[cfg(windows)]
assert_eq!(
substitute_filename(
"C:/tmp/project/{{author}}/{{author}}.rs",
&mut prepare_context("s/a/s")
)
.unwrap(),
"C:\\tmp\\project\\s_a_s\\s_a_s.rs"
);
}

#[test]
fn should_prevent_exploitation() {
#[cfg(unix)]
assert_eq!(
substitute_filename(
"/tmp/project/{{author}}.rs",
&mut prepare_context("../../etc/passwd")
)
.unwrap(),
"/tmp/project/.._.._etc_passwd.rs"
);
#[cfg(unix)]
assert_eq!(
substitute_filename(
"/tmp/project/{{author}}/main.rs",
&mut prepare_context("../../etc/passwd")
)
.unwrap(),
"/tmp/project/.._.._etc_passwd/main.rs"
);
#[cfg(windows)]
assert_eq!(
substitute_filename(
"C:/tmp/project/{{author}}.rs",
&mut prepare_context("../../etc/passwd")
)
.unwrap(),
"C:\\tmp\\project\\.._.._etc_passwd.rs"
);
#[cfg(windows)]
assert_eq!(
substitute_filename(
"C:/tmp/project/{{author}}/main.rs",
&mut prepare_context("../../etc/passwd")
)
.unwrap(),
"C:\\tmp\\project\\.._.._etc_passwd\\main.rs"
);
}

//region wrapper helpers
fn prepare_context(value: &str) -> LiquidObjectResource {
let mut ctx = Object::default();
ctx.entry("author")
.or_insert(Value::scalar(value.to_string()));

ctx
}

fn substitute_filename(f: &str, ctx: &mut LiquidObjectResource) -> anyhow::Result<String> {
let parser = Parser::default();

super::substitute_filename(f.as_ref(), &parser, ctx)
.map(|p| p.to_str().unwrap().to_string())
}
//endregion
}
49 changes: 49 additions & 0 deletions libs/generate_from_path/src/ignore_me.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::fs::remove_file;
use std::path::{Path, PathBuf};

/// Takes the directory path and removes the files/directories specified in the
/// `.genignore` file
/// It handles all errors internally
pub fn remove_unneeded_files(
dir: &Path,
ignored_files: &Option<Vec<String>>,
verbose: bool,
) -> anyhow::Result<()> {
let mut items = vec![];
if let Some(ignored_files) = ignored_files {
for f in ignored_files {
let mut p = PathBuf::new();
p.push(dir);
p.push(f);
items.push(p);
}
}
remove_dir_files(&items, verbose);
Ok(())
}

pub fn remove_dir_files(files: impl IntoIterator<Item = impl Into<PathBuf>>, verbose: bool) {
for item in files
.into_iter()
.map(|i| i.into() as PathBuf)
.filter(|file| file.exists())
{
let ignore_message = format!("Ignoring: {}", &item.display());
if item.is_dir() {
fs_err::remove_dir_all(&item).unwrap();
if verbose {
tracing::info!("{ignore_message}");
}
} else if item.is_file() {
remove_file(&item).unwrap();
if verbose {
tracing::info!("{ignore_message}");
}
} else {
tracing::warn!(
"The given paths are neither files nor directories! {}",
&item.display()
);
}
}
}
Loading

0 comments on commit 27da98b

Please sign in to comment.