|
| 1 | +# Refactoring: Migrate Filters to Filter-Functions |
| 2 | + |
| 3 | +This document outlines the plan to migrate all filters from `src/filters/` to `src/filter_functions/`, enabling both function and filter syntax for each. |
| 4 | + |
| 5 | +## Goal |
| 6 | + |
| 7 | +Convert all existing filter-only implementations to unified filter-functions that support both syntaxes: |
| 8 | + |
| 9 | +```jinja |
| 10 | +{# Current: filter-only syntax #} |
| 11 | +{{ "Hello World" | slugify }} |
| 12 | +{{ 1048576 | filesizeformat }} |
| 13 | +
|
| 14 | +{# After migration: both syntaxes work #} |
| 15 | +{{ slugify(string="Hello World") }} |
| 16 | +{{ "Hello World" | slugify }} |
| 17 | +
|
| 18 | +{{ filesizeformat(bytes=1048576) }} |
| 19 | +{{ 1048576 | filesizeformat }} |
| 20 | +``` |
| 21 | + |
| 22 | +## Current State |
| 23 | + |
| 24 | +### src/filters/ (to be removed) |
| 25 | + |
| 26 | +**formatting.rs:** |
| 27 | +| Filter | Description | |
| 28 | +|--------|-------------| |
| 29 | +| `filesizeformat` | Format bytes as human-readable (KB, MB, GB) | |
| 30 | +| `urlencode` | URL-encode special characters | |
| 31 | + |
| 32 | +**string.rs:** |
| 33 | +| Filter | Description | |
| 34 | +|--------|-------------| |
| 35 | +| `slugify` | Convert to URL-friendly slug | |
| 36 | +| `indent` | Indent text by N spaces | |
| 37 | +| `dedent` | Remove common leading whitespace | |
| 38 | +| `quote` | Quote string (single/double/backtick) | |
| 39 | +| `escape_quotes` | Escape quotes in string | |
| 40 | +| `to_snake_case` | Convert to snake_case | |
| 41 | +| `to_camel_case` | Convert to camelCase | |
| 42 | +| `to_pascal_case` | Convert to PascalCase | |
| 43 | +| `to_kebab_case` | Convert to kebab-case | |
| 44 | +| `pad_left` | Pad string on left | |
| 45 | +| `pad_right` | Pad string on right | |
| 46 | +| `repeat` | Repeat string N times | |
| 47 | +| `reverse` | Reverse string | |
| 48 | + |
| 49 | +**Total: 15 filters to migrate** |
| 50 | + |
| 51 | +## Migration Plan |
| 52 | + |
| 53 | +### Phase 1: Create formatting.rs in filter_functions ✅ |
| 54 | + |
| 55 | +**File:** `src/filter_functions/formatting.rs` |
| 56 | + |
| 57 | +| Function/Filter | Parameters | Notes | |
| 58 | +|-----------------|------------|-------| |
| 59 | +| `filesizeformat` | `bytes` (number) | Format file size | |
| 60 | +| `urlencode` | `string` | URL-encode (uses percent_encoding crate) | |
| 61 | + |
| 62 | +**Note:** `urlencode` is similar to existing `url_encode` but uses different encoding. Keep both for backwards compatibility. |
| 63 | + |
| 64 | +Tasks: |
| 65 | +- [x] Create `src/filter_functions/formatting.rs` |
| 66 | +- [x] Implement `Filesizeformat` with FilterFunction trait |
| 67 | +- [x] Implement `Urlencode` with FilterFunction trait |
| 68 | +- [x] Add `pub mod formatting;` to `src/filter_functions/mod.rs` |
| 69 | +- [x] Register both in `register_all()` |
| 70 | +- [x] Fix/add unit tests under `tests/` folder testing function syntax |
| 71 | +- [x] Update integration tests in `tests/test_filters_integration.rs` for function syntax |
| 72 | +- [x] Update README.md with dual syntax examples |
| 73 | +- [x] Update REFACTOR_FILTERS.md with current state |
| 74 | +- [x] Run `cargo make qa` (508 tests passing) |
| 75 | + |
| 76 | +**Commit:** dd0bb3b |
| 77 | + |
| 78 | +### Phase 2: Add string filters to filter_functions/string.rs |
| 79 | + |
| 80 | +**File:** `src/filter_functions/string.rs` (append to existing) |
| 81 | + |
| 82 | +| Function/Filter | Parameters | Notes | |
| 83 | +|-----------------|------------|-------| |
| 84 | +| `slugify` | `string` | No extra params | |
| 85 | +| `indent` | `string`, `spaces` (optional, default 4) | | |
| 86 | +| `dedent` | `string` | No extra params | |
| 87 | +| `quote` | `string`, `style` (optional: single/double/backtick) | | |
| 88 | +| `escape_quotes` | `string` | No extra params | |
| 89 | +| `to_snake_case` | `string` | No extra params | |
| 90 | +| `to_camel_case` | `string` | No extra params | |
| 91 | +| `to_pascal_case` | `string` | No extra params | |
| 92 | +| `to_kebab_case` | `string` | No extra params | |
| 93 | +| `pad_left` | `string`, `length`, `char` (optional) | | |
| 94 | +| `pad_right` | `string`, `length`, `char` (optional) | | |
| 95 | +| `repeat` | `string`, `count` | | |
| 96 | +| `reverse` | `string` | No extra params | |
| 97 | + |
| 98 | +Tasks: |
| 99 | +- [ ] Add 13 new structs to `src/filter_functions/string.rs` |
| 100 | +- [ ] Implement FilterFunction trait for each |
| 101 | +- [ ] Register all in `src/filter_functions/mod.rs` |
| 102 | +- [ ] Fix/add unit tests under `tests/` folder testing function syntax |
| 103 | +- [ ] Update integration tests in `tests/test_filters_integration.rs` for function syntax |
| 104 | +- [ ] Update README.md with dual syntax examples |
| 105 | +- [ ] Update REFACTOR_FILTERS.md with current state |
| 106 | +- [ ] Run `cargo make qa` |
| 107 | + |
| 108 | +### Phase 3: Remove old filters module |
| 109 | + |
| 110 | +Tasks: |
| 111 | +- [ ] Remove `src/filters/formatting.rs` |
| 112 | +- [ ] Remove `src/filters/string.rs` |
| 113 | +- [ ] Remove `src/filters/mod.rs` |
| 114 | +- [ ] Remove `pub mod filters;` from `src/lib.rs` |
| 115 | +- [ ] Remove `crate::filters::register_all(env);` from `src/functions/mod.rs` |
| 116 | +- [ ] Fix/add unit tests under `tests/` folder (remove obsolete filter-only tests) |
| 117 | +- [ ] Update integration tests to verify both syntaxes still work |
| 118 | +- [ ] Update README.md (remove references to old filters module) |
| 119 | +- [ ] Update REFACTOR_FILTERS.md with current state |
| 120 | +- [ ] Run `cargo make qa` |
| 121 | +- [ ] Remove REFACTOR_FILTERS.md (migration complete) |
| 122 | + |
| 123 | +## Implementation Pattern |
| 124 | + |
| 125 | +Each filter becomes a struct implementing `FilterFunction`: |
| 126 | + |
| 127 | +```rust |
| 128 | +pub struct Slugify; |
| 129 | + |
| 130 | +impl Slugify { |
| 131 | + fn compute(input: &str) -> String { |
| 132 | + // Implementation here |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +impl FilterFunction for Slugify { |
| 137 | + const NAME: &'static str = "slugify"; |
| 138 | + |
| 139 | + fn call_as_function(kwargs: Kwargs) -> Result<Value, Error> { |
| 140 | + let string: String = kwargs.get("string")?; |
| 141 | + Ok(Value::from(Self::compute(&string))) |
| 142 | + } |
| 143 | + |
| 144 | + fn call_as_filter(value: &Value, _kwargs: Kwargs) -> Result<Value, Error> { |
| 145 | + let string = extract_string(value, "slugify")?; |
| 146 | + Ok(Value::from(Self::compute(&string))) |
| 147 | + } |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +## Expected Result |
| 152 | + |
| 153 | +After migration: |
| 154 | +- `src/filters/` directory removed entirely |
| 155 | +- All 15 filters available as both functions and filters |
| 156 | +- Full backwards compatibility maintained |
| 157 | +- New function syntax available for all |
| 158 | + |
| 159 | +## Checklist |
| 160 | + |
| 161 | +- [ ] Phase 1: Create formatting.rs (filesizeformat, urlencode) |
| 162 | +- [ ] Phase 2: Add string filters (13 filters) |
| 163 | +- [ ] Phase 3: Remove old filters module |
| 164 | +- [ ] All tests passing (`cargo make qa`) |
| 165 | +- [ ] REFACTOR_FILTERS.md removed (migration complete) |
0 commit comments