@barefootjs/rust 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/runtime/src/date.rs +67 -16
- package/runtime/src/runtime.rs +8 -4
- package/runtime/tests/helper_vectors.rs +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/rust",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "minijinja (Rust) adapter for BarefootJS — compiles IR to .j2 templates and ships a Rust rendering runtime (packages/adapter-rust/runtime/); runs under any Rust web framework (axum, etc.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
"directory": "packages/adapter-rust"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@barefootjs/shared": "0.
|
|
57
|
+
"@barefootjs/shared": "0.25.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@barefootjs/jsx": ">=0.2.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
64
|
-
"@barefootjs/jsx": "0.
|
|
64
|
+
"@barefootjs/jsx": "0.25.0",
|
|
65
65
|
"typescript": "^5.0.0"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/runtime/src/date.rs
CHANGED
|
@@ -188,12 +188,38 @@ fn parse_tz_offset(tz: &str) -> i64 {
|
|
|
188
188
|
sign * (hh * 60 + mm)
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
/// `
|
|
192
|
-
/// `
|
|
193
|
-
///
|
|
194
|
-
///
|
|
195
|
-
|
|
196
|
-
|
|
191
|
+
/// `names`-table section offsets (#2334, mirrors `MONTHS_WIDE` /
|
|
192
|
+
/// `MONTHS_ABBR` / `WEEKDAYS_WIDE` / `WEEKDAYS_ABBR` in
|
|
193
|
+
/// `packages/client/src/format-date.ts` and the Go runtime's `bf.go`
|
|
194
|
+
/// `monthsWide` / `monthsAbbr` / `weekdaysWide` / `weekdaysAbbr` constants).
|
|
195
|
+
const MONTHS_WIDE: usize = 0;
|
|
196
|
+
const MONTHS_ABBR: usize = 12;
|
|
197
|
+
const WEEKDAYS_WIDE: usize = 24;
|
|
198
|
+
const WEEKDAYS_ABBR: usize = 31;
|
|
199
|
+
|
|
200
|
+
/// Read one `names` table entry (#2334): `names` is the runtime's own
|
|
201
|
+
/// [`JsValue`] array value (whatever array shape `bf.arr`-equivalent
|
|
202
|
+
/// template output materializes into -- see [`crate::runtime::mj_to_js`]'s
|
|
203
|
+
/// `Seq`/`Iterable` arm, which is what a compiled minijinja template's
|
|
204
|
+
/// 4th `format_date` argument becomes by the time it reaches here). A
|
|
205
|
+
/// missing/out-of-range index, a non-array `names`, or a non-string
|
|
206
|
+
/// element all render `""` -- the same total, zero-value discipline as an
|
|
207
|
+
/// unparseable date, mirroring `names[index] ?? ''` in the JS reference
|
|
208
|
+
/// (`packages/client/src/format-date.ts`'s `nameAt`) and `formatDateName`
|
|
209
|
+
/// in the Go runtime's `bf.go`.
|
|
210
|
+
fn name_at(names: &JsValue, index: usize) -> &str {
|
|
211
|
+
match names.as_array().and_then(|arr| arr.get(index)) {
|
|
212
|
+
Some(JsValue::String(s)) => s.as_str(),
|
|
213
|
+
_ => "",
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/// `format_date(recv, pattern, tz, names)` -- the lowering target for a
|
|
218
|
+
/// `formatDate(date, pattern, timeZone, names)` call
|
|
219
|
+
/// (spec/template-helpers.md "format_date", #2324, #2334): a total,
|
|
220
|
+
/// deterministic date-pattern formatter -- no locale, no host timezone, no
|
|
221
|
+
/// `SystemTime::now`. Mirrors `packages/client/src/format-date.ts` (the
|
|
222
|
+
/// JS-normative reference) and the Go runtime's `FormatDate` byte-for-byte.
|
|
197
223
|
///
|
|
198
224
|
/// `recv` follows the `date` helper's receiver contract exactly (see
|
|
199
225
|
/// [`epoch_ms_of`]): this runtime's own [`JsValue::Date`] or an ISO-8601
|
|
@@ -207,17 +233,29 @@ fn parse_tz_offset(tz: &str) -> i64 {
|
|
|
207
233
|
/// fields (not the original instant's) are what the pattern tokens read --
|
|
208
234
|
/// the shifted UTC clock face IS the local clock face at that offset.
|
|
209
235
|
///
|
|
236
|
+
/// `names` (#2334) is a flat table in fixed layout: `[0..11]` wide month
|
|
237
|
+
/// names, `[12..23]` abbreviated month names, `[24..30]` wide weekday names
|
|
238
|
+
/// (Sunday-first), `[31..37]` abbreviated weekday names -- see [`name_at`].
|
|
239
|
+
/// The caller owns the values (locale selection is not this function's
|
|
240
|
+
/// job); this only indexes the table.
|
|
241
|
+
///
|
|
210
242
|
/// `pattern` is scanned left-to-right, longest-match, for the token set
|
|
211
|
-
/// `YYYY|MM|DD|M|D` (checking
|
|
212
|
-
///
|
|
213
|
-
///
|
|
214
|
-
///
|
|
215
|
-
/// advances by whole characters only (ASCII token
|
|
216
|
-
/// 1/2/4 ASCII bytes; the fallback branch consumes
|
|
217
|
-
/// `len_utf8`), so slicing never lands mid-codepoint.
|
|
218
|
-
/// `abs(year)` zero-padded to 4 digits, `-`-prefixed for a
|
|
219
|
-
/// `MM`/`DD` zero-pad to 2; `M`/`D` are bare.
|
|
220
|
-
|
|
243
|
+
/// `YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D` (checking longer tokens before
|
|
244
|
+
/// shorter ones at each position, the same alternation-order discipline the
|
|
245
|
+
/// Go/JS ports use); every other character -- including multi-byte ones
|
|
246
|
+
/// like 年/月/日 or 月-name table values -- passes through/renders
|
|
247
|
+
/// literally. The scan advances by whole characters only (ASCII token
|
|
248
|
+
/// matches consume exactly 1/2/4 ASCII bytes; the fallback branch consumes
|
|
249
|
+
/// one full `char` via `len_utf8`), so slicing never lands mid-codepoint.
|
|
250
|
+
/// `YYYY` is `abs(year)` zero-padded to 4 digits, `-`-prefixed for a
|
|
251
|
+
/// negative year; `MM`/`DD` zero-pad to 2; `M`/`D` are bare. `MMMM`/`MMM`
|
|
252
|
+
/// read `names[month-1]` / `names[12+month-1]`; `dddd`/`ddd` read
|
|
253
|
+
/// `names[24+weekday]` / `names[31+weekday]`, where `weekday` (0 = Sunday)
|
|
254
|
+
/// is derived from the shifted epoch day count via
|
|
255
|
+
/// `(days + 4).rem_euclid(7)` -- 1970-01-01 (`days == 0`) is a Thursday,
|
|
256
|
+
/// and `rem_euclid` (not `%`, which truncates toward zero in Rust) keeps
|
|
257
|
+
/// the result in `[0, 6]` for a negative (pre-1970) `days` too.
|
|
258
|
+
pub fn format_date(recv: &JsValue, pattern: &str, tz: &str, names: &JsValue) -> String {
|
|
221
259
|
let ms = match epoch_ms_of(recv) {
|
|
222
260
|
Some(ms) => ms,
|
|
223
261
|
None => return String::new(),
|
|
@@ -227,6 +265,7 @@ pub fn format_date(recv: &JsValue, pattern: &str, tz: &str) -> String {
|
|
|
227
265
|
let days = shifted.div_euclid(MS_PER_DAY);
|
|
228
266
|
let (year, month, day) = civil_from_days(days);
|
|
229
267
|
let yyyy = if year < 0 { format!("-{:04}", -year) } else { format!("{year:04}") };
|
|
268
|
+
let weekday = (days + 4).rem_euclid(7) as usize; // 0 = Sunday; epoch (days=0) is Thursday
|
|
230
269
|
|
|
231
270
|
let mut out = String::with_capacity(pattern.len());
|
|
232
271
|
let mut i = 0;
|
|
@@ -235,12 +274,24 @@ pub fn format_date(recv: &JsValue, pattern: &str, tz: &str) -> String {
|
|
|
235
274
|
if rest.starts_with("YYYY") {
|
|
236
275
|
out.push_str(&yyyy);
|
|
237
276
|
i += 4;
|
|
277
|
+
} else if rest.starts_with("MMMM") {
|
|
278
|
+
out.push_str(name_at(names, MONTHS_WIDE + (month - 1) as usize));
|
|
279
|
+
i += 4;
|
|
280
|
+
} else if rest.starts_with("MMM") {
|
|
281
|
+
out.push_str(name_at(names, MONTHS_ABBR + (month - 1) as usize));
|
|
282
|
+
i += 3;
|
|
238
283
|
} else if rest.starts_with("MM") {
|
|
239
284
|
out.push_str(&format!("{month:02}"));
|
|
240
285
|
i += 2;
|
|
241
286
|
} else if rest.starts_with("DD") {
|
|
242
287
|
out.push_str(&format!("{day:02}"));
|
|
243
288
|
i += 2;
|
|
289
|
+
} else if rest.starts_with("dddd") {
|
|
290
|
+
out.push_str(name_at(names, WEEKDAYS_WIDE + weekday));
|
|
291
|
+
i += 4;
|
|
292
|
+
} else if rest.starts_with("ddd") {
|
|
293
|
+
out.push_str(name_at(names, WEEKDAYS_ABBR + weekday));
|
|
294
|
+
i += 3;
|
|
244
295
|
} else if rest.starts_with('M') {
|
|
245
296
|
out.push_str(&month.to_string());
|
|
246
297
|
i += 1;
|
package/runtime/src/runtime.rs
CHANGED
|
@@ -1338,12 +1338,16 @@ impl Object for BfInstance {
|
|
|
1338
1338
|
// string; `date::date` normalizes and dispatches both. `op`
|
|
1339
1339
|
// stays a plain string arg, never routed through `js_number`.
|
|
1340
1340
|
"date" => Ok(js_to_mj(&date::date(a(0), a(1).as_str().unwrap_or("")))),
|
|
1341
|
-
// `format_date(recv, pattern, tz)` (spec/template-helpers.md
|
|
1342
|
-
// "format_date", #2324) -- total, locale-free date-pattern
|
|
1341
|
+
// `format_date(recv, pattern, tz, names)` (spec/template-helpers.md
|
|
1342
|
+
// "format_date", #2324, #2334) -- total, locale-free date-pattern
|
|
1343
1343
|
// formatting layered on the same `recv` normalization as
|
|
1344
|
-
// `date` above.
|
|
1344
|
+
// `date` above. `names` stays the array-shaped `JsValue` that
|
|
1345
|
+
// `mj_to_js`'s `Seq`/`Iterable` arm already produced above (the
|
|
1346
|
+
// compiler's lowering always passes 4 args); a non-array or
|
|
1347
|
+
// missing `names` degrades to `""` for every name token, per
|
|
1348
|
+
// `date::name_at`. See `date::format_date`'s docstring for the
|
|
1345
1349
|
// full contract.
|
|
1346
|
-
"format_date" => Ok(MjValue::from(date::format_date(a(0), a(1).as_str().unwrap_or(""), a(2).as_str().unwrap_or("")))),
|
|
1350
|
+
"format_date" => Ok(MjValue::from(date::format_date(a(0), a(1).as_str().unwrap_or(""), a(2).as_str().unwrap_or(""), a(3)))),
|
|
1347
1351
|
|
|
1348
1352
|
// -- Array / string method helpers (#1448 Tier A) ------------------
|
|
1349
1353
|
"includes" => Ok(MjValue::from(includes(a(0), a(1)))),
|
|
@@ -150,7 +150,7 @@ fn call_binding(fn_name: &str, args: &[JsValue]) -> Option<JsValue> {
|
|
|
150
150
|
JsValue::String(num::to_fixed(runtime::js_number(&a(0)), digits))
|
|
151
151
|
}
|
|
152
152
|
"date" => date::date(&a(0), a(1).as_str().unwrap_or("")),
|
|
153
|
-
"format_date" => JsValue::String(date::format_date(&a(0), a(1).as_str().unwrap_or(""), a(2).as_str().unwrap_or(""))),
|
|
153
|
+
"format_date" => JsValue::String(date::format_date(&a(0), a(1).as_str().unwrap_or(""), a(2).as_str().unwrap_or(""), &a(3))),
|
|
154
154
|
"lower" => JsValue::String(runtime::js_string(&a(0)).to_lowercase()),
|
|
155
155
|
"upper" => JsValue::String(runtime::js_string(&a(0)).to_uppercase()),
|
|
156
156
|
"trim" => JsValue::String(runtime::trim(&a(0))),
|