@barefootjs/jinja 0.21.4 → 0.24.1

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/dist/index.js CHANGED
@@ -189196,18 +189196,18 @@ var jinjaAdapter = new JinjaAdapter;
189196
189196
  // src/conformance-pins.ts
189197
189197
  var conformancePins = {
189198
189198
  "static-array-from-props": [
189199
- { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2087" }
189199
+ { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2321" }
189200
189200
  ],
189201
189201
  "static-array-from-props-with-component": [
189202
- { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2087" }
189202
+ { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2321" }
189203
189203
  ],
189204
189204
  "filter-nested-callback-predicate": [
189205
- { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
189205
+ { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2320" }
189206
189206
  ],
189207
189207
  "filter-nested-find-predicate": [
189208
- { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
189208
+ { code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2320" }
189209
189209
  ],
189210
- "dangerous-inner-html-dynamic": [{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2215" }],
189210
+ "dangerous-inner-html-dynamic": [{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2319" }],
189211
189211
  "date-method-uncatalogued": [{ code: "BF021", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2274" }]
189212
189212
  };
189213
189213
  // src/render-divergences.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/jinja",
3
- "version": "0.21.4",
3
+ "version": "0.24.1",
4
4
  "description": "Jinja2 adapter for BarefootJS — compiles IR to .jinja templates and ships the Python BarefootJS rendering runtime; runs under any Python web framework (Flask, etc.)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,14 +53,14 @@
53
53
  "directory": "packages/adapter-jinja"
54
54
  },
55
55
  "dependencies": {
56
- "@barefootjs/shared": "0.21.4"
56
+ "@barefootjs/shared": "0.24.1"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@barefootjs/jsx": ">=0.2.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@barefootjs/adapter-tests": "0.1.0",
63
- "@barefootjs/jsx": "0.21.4",
63
+ "@barefootjs/jsx": "0.24.1",
64
64
  "typescript": "^5.0.0"
65
65
  }
66
66
  }
@@ -958,6 +958,133 @@ class BarefootJS:
958
958
  )
959
959
  return 0
960
960
 
961
+ _FORMAT_DATE_TZ_RE = re.compile(r"^([+-])(\d{2}):(\d{2})$")
962
+ _FORMAT_DATE_TOKEN_RE = re.compile(r"YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D")
963
+
964
+ # `names`-table section offsets (spec/template-helpers.md "format_date",
965
+ # #2334) -- mirrors packages/client/src/format-date.ts's
966
+ # MONTHS_WIDE/MONTHS_ABBR/WEEKDAYS_WIDE/WEEKDAYS_ABBR constants.
967
+ _FORMAT_DATE_MONTHS_WIDE = 0
968
+ _FORMAT_DATE_MONTHS_ABBR = 12
969
+ _FORMAT_DATE_WEEKDAYS_WIDE = 24
970
+ _FORMAT_DATE_WEEKDAYS_ABBR = 31
971
+
972
+ def format_date(
973
+ self, recv: Any, pattern: str, tz: str, names: Optional[list] = None
974
+ ) -> str:
975
+ """`format_date(recv, pattern, tz, names)` -- the lowering target for
976
+ a `formatDate(date, pattern, timeZone, names)` call (#2324 numeric
977
+ tokens, #2334 name tokens; spec entry "format_date"). `recv` follows
978
+ the same receiver contract as `date` above -- this runtime's own
979
+ `datetime` or an ISO-8601 string; a nil or unparseable receiver
980
+ renders `''` (never a crash, never "now"). Mirrors
981
+ packages/client/src/format-date.ts byte-for-byte. Canonical arity is
982
+ 4 -- the lowering always supplies `tz` and `names` (defaulting
983
+ `'UTC'` and `[]`).
984
+
985
+ `tz` is `'UTC'` or a fixed offset matching `±HH:MM`. The helper is
986
+ total: any other value -- an IANA zone name, a malformed offset --
987
+ normalizes to a zero-minute shift (UTC), so every backend degrades
988
+ identically instead of dragging in host tzdata. This is pure
989
+ arithmetic (`_FORMAT_DATE_TZ_RE` + an epoch-ms shift), never
990
+ `zoneinfo`. The shifted instant's UTC calendar fields are what the
991
+ pattern tokens read -- the shifted UTC clock face IS the local
992
+ clock face at that offset. The shift and field derivation run on
993
+ integers (Hinnant civil-from-days), NOT on a shifted `datetime`:
994
+ a positive offset on 9999-12-31 lands in year 10000, past
995
+ `datetime.max`, and must render `10000-…` like the JS reference
996
+ instead of overflowing (golden vector "positive offset pushes the
997
+ far-future instant past year 9999"). The weekday used by `dddd`/
998
+ `ddd` comes from the SAME integer path -- `(shifted_ms // 86_400_000
999
+ + 4) % 7` (Python floor division; `+4` because epoch day 0,
1000
+ 1970-01-01, is a Thursday, and the table is Sunday-first so
1001
+ `weekday == 0` must mean Sunday) -- rather than re-deriving it from
1002
+ `yoe`/`doy`, so it stays correct across the same year-10000
1003
+ overflow boundary the civil-from-days math above already handles.
1004
+
1005
+ `pattern` is scanned longest-match over
1006
+ `YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D` (`_FORMAT_DATE_TOKEN_RE`,
1007
+ alternation ordered so longer tokens win before their shorter
1008
+ prefixes); every other character -- including multi-byte ones like
1009
+ 年/月/日 -- passes through literally. `YYYY` is `abs(year)`
1010
+ zero-padded to 4 digits, `-`-prefixed for a negative year; `MM`/`DD`
1011
+ zero-pad to 2; `M`/`D` are bare. `MMMM`/`MMM` read `names[month-1]`
1012
+ / `names[12+month-1]` (wide/abbreviated month name); `dddd`/`ddd`
1013
+ read `names[24+weekday]` / `names[31+weekday]` (wide/abbreviated
1014
+ weekday name, Sunday-first) -- `names` is the flat table documented
1015
+ on the module's `formatDate` JS sibling: `[0..11]` wide months,
1016
+ `[12..23]` abbreviated months, `[24..30]` wide weekdays, `[31..37]`
1017
+ abbreviated weekdays. An index past the end of (or an altogether
1018
+ missing) `names` table renders `''` for that token, never an
1019
+ `IndexError`."""
1020
+ names = names or []
1021
+
1022
+ def _name_at(index: int) -> str:
1023
+ return names[index] if 0 <= index < len(names) else ""
1024
+
1025
+ if isinstance(recv, datetime.datetime):
1026
+ dt = recv
1027
+ else:
1028
+ s = str(recv)
1029
+ if s.endswith("Z"):
1030
+ s = s[:-1] + "+00:00"
1031
+ try:
1032
+ dt = datetime.datetime.fromisoformat(s)
1033
+ except ValueError:
1034
+ return ""
1035
+ dt = dt.replace(tzinfo=datetime.timezone.utc) if dt.tzinfo is None else dt.astimezone(datetime.timezone.utc)
1036
+ m = self._FORMAT_DATE_TZ_RE.match(tz)
1037
+ offset_minutes = 0
1038
+ if m:
1039
+ offset_minutes = (-1 if m.group(1) == "-" else 1) * (int(m.group(2)) * 60 + int(m.group(3)))
1040
+ delta = dt - _DATE_EPOCH
1041
+ epoch_ms = (delta.days * 86_400 + delta.seconds) * 1_000 + delta.microseconds // 1_000
1042
+ shifted_ms = epoch_ms + offset_minutes * 60_000
1043
+ # Hinnant civil-from-days over Python's floor division (exact for
1044
+ # negative epochs and for days past datetime.max).
1045
+ z = shifted_ms // 86_400_000 + 719_468
1046
+ era = z // 146_097
1047
+ doe = z - era * 146_097
1048
+ yoe = (doe - doe // 1_460 + doe // 36_524 - doe // 146_096) // 365
1049
+ doy = doe - (365 * yoe + yoe // 4 - yoe // 100)
1050
+ mp = (5 * doy + 2) // 153
1051
+ day = doy - (153 * mp + 2) // 5 + 1
1052
+ month = mp + 3 if mp < 10 else mp - 9
1053
+ year = yoe + era * 400 + (1 if month <= 2 else 0)
1054
+ yyyy = ("-" if year < 0 else "") + f"{abs(year):04d}"
1055
+ # Weekday from the SAME integer path as the civil-from-days fields
1056
+ # above (NOT re-derived from yoe/doy) -- 0=Sunday, matching the
1057
+ # table's Sunday-first layout. `(shifted_ms // 86_400_000)` is the
1058
+ # signed epoch-day count (Python floor division, exact for negative
1059
+ # epochs); `+4` anchors it since epoch day 0 (1970-01-01) is a
1060
+ # Thursday.
1061
+ days = shifted_ms // 86_400_000
1062
+ weekday = (days + 4) % 7
1063
+
1064
+ def _sub(match: "re.Match[str]") -> str:
1065
+ token = match.group(0)
1066
+ if token == "YYYY":
1067
+ return yyyy
1068
+ if token == "MMMM":
1069
+ return _name_at(self._FORMAT_DATE_MONTHS_WIDE + month - 1)
1070
+ if token == "MMM":
1071
+ return _name_at(self._FORMAT_DATE_MONTHS_ABBR + month - 1)
1072
+ if token == "MM":
1073
+ return f"{month:02d}"
1074
+ if token == "M":
1075
+ return str(month)
1076
+ if token == "DD":
1077
+ return f"{day:02d}"
1078
+ if token == "D":
1079
+ return str(day)
1080
+ if token == "dddd":
1081
+ return _name_at(self._FORMAT_DATE_WEEKDAYS_WIDE + weekday)
1082
+ if token == "ddd":
1083
+ return _name_at(self._FORMAT_DATE_WEEKDAYS_ABBR + weekday)
1084
+ return token
1085
+
1086
+ return self._FORMAT_DATE_TOKEN_RE.sub(_sub, pattern)
1087
+
961
1088
  # -----------------------------------------------------------------
962
1089
  # Array / String method helpers (#1448 Tier A)
963
1090
  # -----------------------------------------------------------------
@@ -139,6 +139,7 @@ BINDINGS = {
139
139
  "abs": bf.abs,
140
140
  "to_fixed": lambda *a: bf.to_fixed(*a),
141
141
  "date": lambda recv, op: bf.date(recv, op),
142
+ "format_date": lambda recv, pattern, tz, names=None: bf.format_date(recv, pattern, tz, names),
142
143
  "lower": bf.lc,
143
144
  "upper": bf.uc,
144
145
  "trim": bf.trim,
@@ -42,15 +42,15 @@ export const conformancePins: ConformancePins = {
42
42
  // see the "Loop array `<name>` is a local computed value" diagnostic.
43
43
  // Fixing the underlying gap (computed-array-from-props as a loop
44
44
  // source) is out of scope for #2087; tracked as a follow-up at
45
- // https://github.com/piconic-ai/barefootjs/issues/2087.
45
+ // https://github.com/piconic-ai/barefootjs/issues/2321.
46
46
  'static-array-from-props': [
47
- { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2087' },
47
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2321' },
48
48
  ],
49
49
  // BF101 (unresolvable computed loop array, see above) fires; BF103
50
50
  // (imported child in the loop body) no longer does now that the
51
51
  // conformance harness passes `siblingTemplatesRegistered: true` (#2205).
52
52
  'static-array-from-props-with-component': [
53
- { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2087' },
53
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2321' },
54
54
  ],
55
55
  // Rest-destructure / structured-path `.map()` callbacks (#2087 Phase B):
56
56
  // `isLowerableLoopDestructure` now admits fixed bindings at any
@@ -80,12 +80,12 @@ export const conformancePins: ConformancePins = {
80
80
  // lossy, same as xslate. The `/* @client */` twin
81
81
  // (`filter-nested-callback-predicate-client`) has no pin here: it must
82
82
  // render clean on every adapter, which asserts the suppression contract.
83
- // https://github.com/piconic-ai/barefootjs/issues/2038
83
+ // Faithful lowering tracked: https://github.com/piconic-ai/barefootjs/issues/2320 (successor to #2038)
84
84
  'filter-nested-callback-predicate': [
85
- { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
85
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' },
86
86
  ],
87
87
  'filter-nested-find-predicate': [
88
- { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
88
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' },
89
89
  ],
90
90
  // NB: TOP-LEVEL `.find` / `.findIndex` / `.findLast` / `.findLastIndex`
91
91
  // (text position) are NOT pinned here — like xslate (unlike mojo, which
@@ -101,8 +101,8 @@ export const conformancePins: ConformancePins = {
101
101
  // `dangerouslySetInnerHTML={{ __html: '...' }}` is spliced directly into
102
102
  // the template as trusted raw text (`resolveDangerousInnerHtml`, #2207).
103
103
  // A dynamic/signal-derived value still refuses with BF101 — see the
104
- // `dangerous-inner-html-dynamic` fixture/pin below (tracked: #2215).
105
- 'dangerous-inner-html-dynamic': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2215' }],
104
+ // `dangerous-inner-html-dynamic` fixture/pin below (tracked: #2319, successor to #2215).
105
+ 'dangerous-inner-html-dynamic': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2319' }],
106
106
  // #2273: a method call on a prop typed as a built-in host rich type
107
107
  // (Date, Map, …) has no catalogued lowering in any adapter — this is a
108
108
  // compiler-level refusal (`checkRichTypeMethodCalls`, wired ahead of