Skip to content

Commit

Permalink
Merge pull request fermyon#2097 from itowlson/templates-pascal-case-f…
Browse files Browse the repository at this point in the history
…ilter

Add `dotted_pascal_case` template filter
  • Loading branch information
itowlson authored Nov 15, 2023
2 parents d373025 + e86b744 commit 9388eb2
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 3 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ update-cargo-locks:
test-unit:
RUST_LOG=$(LOG_LEVEL) cargo test --all --no-fail-fast -- --skip integration_tests --skip spinup_tests --skip cloud_tests --nocapture

.PHONY: test-crate
test-crate:
RUST_LOG=$(LOG_LEVEL) cargo test -p $(crate) --no-fail-fast -- --skip integration_tests --skip spinup_tests --skip cloud_tests --nocapture

.PHONY: test-integration
test-integration: test-kv test-sqlite
RUST_LOG=$(LOG_LEVEL) cargo test --test integration --no-fail-fast -- --skip spinup_tests --skip cloud_tests --nocapture
Expand Down
66 changes: 66 additions & 0 deletions crates/templates/src/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ use liquid_derive::FilterReflection;
// Filters that are added to the Liquid parser and allow templates to specify
// transformations of input strings

// ADDING A FILTER HERE IS NOT ENOUGH. You must also:
//
// * Register it with the template ParserBuilder in `Run::template_parser`.
// Otherwise Liquid won't know about it.
// * Add a test using the registration name to `test_filters` in run.rs.
// This ensures that the name->parser->filter chain is hooked up correctly.

#[derive(Clone, ParseFilter, FilterReflection)]
#[filter(
name = "kebab_case",
Expand Down Expand Up @@ -123,3 +130,62 @@ impl Filter for HttpWildcardFilter {
Ok(wildcard_route.to_value())
}
}

#[derive(Clone, ParseFilter, FilterReflection)]
#[filter(
name = "dotted_pascal_case",
description = "Change text to Dotted.Pascal.Case.",
parsed(DottedPascalCaseFilter)
)]
pub(crate) struct DottedPascalCaseFilterParser;

#[derive(Debug, Default, liquid_derive::Display_filter)]
#[name = "dotted_pascal_case"]
struct DottedPascalCaseFilter;

impl Filter for DottedPascalCaseFilter {
fn evaluate(
&self,
input: &dyn ValueView,
_runtime: &dyn Runtime,
) -> Result<liquid_core::model::Value, liquid_core::error::Error> {
let input = input
.as_scalar()
.ok_or_else(|| liquid_core::error::Error::with_msg("String expected"))?;

let input = input.into_string().to_string();

let result = input
.split('.')
.map(|s| s.to_upper_camel_case())
.collect::<Vec<_>>()
.join(".");

Ok(result.to_value())
}
}

#[cfg(test)]
mod test {
use liquid_core::{Filter, ValueView};

// Just to save cluttering the tests with ceremonial bits
fn dotted_pascal(input: &str) -> String {
let filter = super::DottedPascalCaseFilter;
let runtime = liquid_core::runtime::RuntimeBuilder::new().build();
filter
.evaluate(&input.to_value(), &runtime)
.map(|v| v.into_scalar().unwrap().into_string().to_string())
.unwrap()
}

#[test]
fn test_dotted_pascal_case() {
assert_eq!("Fermyon.PetStore", dotted_pascal("Fermyon.PetStore"));
assert_eq!("FermyonPetStore", dotted_pascal("fermyon-pet-store"));
assert_eq!("FermyonPetStore", dotted_pascal("fermyon_pet_store"));
assert_eq!("Fermyon.PetStore", dotted_pascal("fermyon.pet-store"));
assert_eq!("Fermyon.PetStore", dotted_pascal("fermyon.pet_store"));
assert_eq!("FermyonPetStore", dotted_pascal("fermyon pet store"));
}
}
36 changes: 33 additions & 3 deletions crates/templates/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ impl Run {
let abs_snippet_file = snippets_dir.join(snippet_file);
let file_content = std::fs::read(abs_snippet_file)
.with_context(|| format!("Error reading snippet file {}", snippet_file))?;
let content = TemplateContent::infer_from_bytes(file_content, &self.template_parser())
let content = TemplateContent::infer_from_bytes(file_content, &Self::template_parser())
.with_context(|| format!("Error parsing snippet file {}", snippet_file))?;

match id {
Expand Down Expand Up @@ -302,7 +302,7 @@ impl Run {

// TODO: async when we know where things sit
fn read_all(&self, paths: Vec<PathBuf>) -> anyhow::Result<Vec<(PathBuf, TemplateContent)>> {
let template_parser = self.template_parser();
let template_parser = Self::template_parser();
let contents = paths
.iter()
.map(std::fs::read)
Expand Down Expand Up @@ -334,14 +334,44 @@ impl Run {
pathdiff::diff_paths(source, src_dir).map(|rel| (dest_dir.join(rel), cont))
}

fn template_parser(&self) -> liquid::Parser {
fn template_parser() -> liquid::Parser {
let builder = liquid::ParserBuilder::with_stdlib()
.filter(crate::filters::KebabCaseFilterParser)
.filter(crate::filters::PascalCaseFilterParser)
.filter(crate::filters::DottedPascalCaseFilterParser)
.filter(crate::filters::SnakeCaseFilterParser)
.filter(crate::filters::HttpWildcardFilterParser);
builder
.build()
.expect("can't fail due to no partials support")
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_filters() {
let data = liquid::object!({
"snaky": "originally_snaky",
"kebabby": "originally-kebabby",
"dotted": "originally.semi-dotted"
});
let parser = Run::template_parser();

let eval = |s: &str| parser.parse(s).unwrap().render(&data).unwrap();

let kebab = eval("{{ snaky | kebab_case }}");
assert_eq!("originally-snaky", kebab);

let snek = eval("{{ kebabby | snake_case }}");
assert_eq!("originally_kebabby", snek);

let pascal = eval("{{ snaky | pascal_case }}");
assert_eq!("OriginallySnaky", pascal);

let dotpas = eval("{{ dotted | dotted_pascal_case }}");
assert_eq!("Originally.SemiDotted", dotpas);
}
}

0 comments on commit 9388eb2

Please sign in to comment.