@formfeed/engine 0.3.2 → 0.3.3

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.
Files changed (3) hide show
  1. package/index.cjs +19 -7
  2. package/index.js +19 -7
  3. package/package.json +1 -1
package/index.cjs CHANGED
@@ -270,6 +270,7 @@ function toDate(value, tz) {
270
270
  function escapeHtml(value) {
271
271
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
272
272
  }
273
+ var isNamedArgs = (v) => !!v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
273
274
  var str = (v) => v === void 0 || v === null ? "" : String(v);
274
275
  var opt = (v, fallback) => v === void 0 || v === null || v === "" ? fallback : v;
275
276
  var wordsByLang = {
@@ -304,7 +305,7 @@ var formatHelpers = [
304
305
  name: "number",
305
306
  doc: {
306
307
  signature: "number(value, decimals?, locale?)",
307
- description: "Formats a number with grouping and a fixed number of decimals.",
308
+ description: "Formats a number with grouping. With decimals, exactly that many (rounded half away from zero); without, the decimals the number has, at most three, so 1234.5 \u2192 1.234,5 and 2.34567 \u2192 2,346. Use number(0) for whole numbers.",
308
309
  example: "{{ qty | number(2) }} \u2192 1.234,00",
309
310
  category: "format"
310
311
  },
@@ -398,15 +399,26 @@ var formatHelpers = [
398
399
  {
399
400
  name: "round",
400
401
  doc: {
401
- signature: "round(value, decimals = 0)",
402
- description: "Rounds half away from zero.",
403
- example: "{{ 2.345 | round(2) }} gives 2.35",
402
+ signature: "round(value, decimals = 0, method = 'common')",
403
+ description: "Rounds to the given decimals, as Jinja2's round does: 'common' rounds half away from zero, 'floor' always down and 'ceil' always up.",
404
+ example: "{{ 2.345 | round(2) }} gives 2.35, {{ 2.349 | round(2, 'floor') }} gives 2.34",
404
405
  category: "format"
405
406
  },
406
- fn: (_ctx, value, decimals) => {
407
+ // `round(2, method='floor')` (Jinja2) and `round: 2, method: 'floor'` (Liquid) arrive as a
408
+ // trailing object, as does a Handlebars hash
409
+ fn: (_ctx, value, ...args) => {
407
410
  const n = toNumber(value);
408
- const f = 10 ** (decimals === void 0 || decimals === null ? 0 : Number(decimals));
409
- return Number.isNaN(n) ? "" : Math.round((Math.abs(n) + Number.EPSILON) * f) / f * Math.sign(n);
411
+ if (Number.isNaN(n)) return "";
412
+ const named = args.find(isNamedArgs) ?? {};
413
+ const [decimals, method] = args.filter((a) => !isNamedArgs(a));
414
+ const places = Number(opt(decimals, opt(named["precision"], opt(named["decimals"], 0))));
415
+ const how = String(opt(method, opt(named["method"], "common")));
416
+ const f = 10 ** places;
417
+ const scaled = Number((Math.abs(n) * f).toPrecision(15));
418
+ if (how === "floor") return (n < 0 ? -Math.ceil(scaled) : Math.floor(scaled)) / f;
419
+ if (how === "ceil") return (n < 0 ? -Math.floor(scaled) : Math.ceil(scaled)) / f;
420
+ if (how !== "common") throw new Error(`round: method must be 'common', 'floor' or 'ceil', not '${how}'`);
421
+ return Math.round(scaled + Number.EPSILON) / f * Math.sign(n);
410
422
  }
411
423
  },
412
424
  {
package/index.js CHANGED
@@ -140,6 +140,7 @@ function toDate(value, tz) {
140
140
  function escapeHtml(value) {
141
141
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
142
142
  }
143
+ var isNamedArgs = (v) => !!v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
143
144
  var str = (v) => v === void 0 || v === null ? "" : String(v);
144
145
  var opt = (v, fallback) => v === void 0 || v === null || v === "" ? fallback : v;
145
146
  var wordsByLang = {
@@ -174,7 +175,7 @@ var formatHelpers = [
174
175
  name: "number",
175
176
  doc: {
176
177
  signature: "number(value, decimals?, locale?)",
177
- description: "Formats a number with grouping and a fixed number of decimals.",
178
+ description: "Formats a number with grouping. With decimals, exactly that many (rounded half away from zero); without, the decimals the number has, at most three, so 1234.5 \u2192 1.234,5 and 2.34567 \u2192 2,346. Use number(0) for whole numbers.",
178
179
  example: "{{ qty | number(2) }} \u2192 1.234,00",
179
180
  category: "format"
180
181
  },
@@ -268,15 +269,26 @@ var formatHelpers = [
268
269
  {
269
270
  name: "round",
270
271
  doc: {
271
- signature: "round(value, decimals = 0)",
272
- description: "Rounds half away from zero.",
273
- example: "{{ 2.345 | round(2) }} gives 2.35",
272
+ signature: "round(value, decimals = 0, method = 'common')",
273
+ description: "Rounds to the given decimals, as Jinja2's round does: 'common' rounds half away from zero, 'floor' always down and 'ceil' always up.",
274
+ example: "{{ 2.345 | round(2) }} gives 2.35, {{ 2.349 | round(2, 'floor') }} gives 2.34",
274
275
  category: "format"
275
276
  },
276
- fn: (_ctx, value, decimals) => {
277
+ // `round(2, method='floor')` (Jinja2) and `round: 2, method: 'floor'` (Liquid) arrive as a
278
+ // trailing object, as does a Handlebars hash
279
+ fn: (_ctx, value, ...args) => {
277
280
  const n = toNumber(value);
278
- const f = 10 ** (decimals === void 0 || decimals === null ? 0 : Number(decimals));
279
- return Number.isNaN(n) ? "" : Math.round((Math.abs(n) + Number.EPSILON) * f) / f * Math.sign(n);
281
+ if (Number.isNaN(n)) return "";
282
+ const named = args.find(isNamedArgs) ?? {};
283
+ const [decimals, method] = args.filter((a) => !isNamedArgs(a));
284
+ const places = Number(opt(decimals, opt(named["precision"], opt(named["decimals"], 0))));
285
+ const how = String(opt(method, opt(named["method"], "common")));
286
+ const f = 10 ** places;
287
+ const scaled = Number((Math.abs(n) * f).toPrecision(15));
288
+ if (how === "floor") return (n < 0 ? -Math.ceil(scaled) : Math.floor(scaled)) / f;
289
+ if (how === "ceil") return (n < 0 ? -Math.floor(scaled) : Math.ceil(scaled)) / f;
290
+ if (how !== "common") throw new Error(`round: method must be 'common', 'floor' or 'ceil', not '${how}'`);
291
+ return Math.round(scaled + Number.EPSILON) / f * Math.sign(n);
280
292
  }
281
293
  },
282
294
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formfeed/engine",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "The Formfeed template engine (Jinja2, Liquid, Handlebars) for the browser and Node.",
5
5
  "license": "MIT",
6
6
  "type": "module",