@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/rust",
3
- "version": "0.23.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.23.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.23.0",
64
+ "@barefootjs/jsx": "0.25.0",
65
65
  "typescript": "^5.0.0"
66
66
  }
67
67
  }
@@ -188,12 +188,38 @@ fn parse_tz_offset(tz: &str) -> i64 {
188
188
  sign * (hh * 60 + mm)
189
189
  }
190
190
 
191
- /// `format_date(recv, pattern, tz)` -- the lowering target for a
192
- /// `formatDate(date, pattern, timeZone)` call (spec/template-helpers.md
193
- /// "format_date", #2324): a total, deterministic date-pattern formatter --
194
- /// no locale, no host timezone, no `SystemTime::now`. Mirrors
195
- /// `packages/client/src/format-date.ts` (the JS-normative reference) and
196
- /// the Go runtime's `FormatDate` byte-for-byte.
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 the 4-char token before the 2-char tokens
212
- /// before the 1-char tokens at each position, the same alternation-order
213
- /// discipline the Go/JS ports use); every other character -- including
214
- /// multi-byte ones like 年/月/日 -- passes through literally. The scan
215
- /// advances by whole characters only (ASCII token matches consume exactly
216
- /// 1/2/4 ASCII bytes; the fallback branch consumes one full `char` via
217
- /// `len_utf8`), so slicing never lands mid-codepoint. `YYYY` is
218
- /// `abs(year)` zero-padded to 4 digits, `-`-prefixed for a negative year;
219
- /// `MM`/`DD` zero-pad to 2; `M`/`D` are bare.
220
- pub fn format_date(recv: &JsValue, pattern: &str, tz: &str) -> String {
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;
@@ -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. See `date::format_date`'s docstring for the
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))),