@rhinostone/swig-django 2.9.11 → 2.9.13

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/lib/filters.js CHANGED
@@ -2,6 +2,15 @@ var utils = require('@rhinostone/swig-core/lib/utils'),
2
2
  dateFormatter = require('@rhinostone/swig-core/lib/dateformatter'),
3
3
  iterateFilter = require('@rhinostone/swig-core/lib/filters').iterateFilter;
4
4
 
5
+ /*!
6
+ * Text of a filter input, as Django's stringfilter gives it: None for null, and for a
7
+ * variable that fails to resolve where Django reads it as None (if, for, firstof).
8
+ * @private
9
+ */
10
+ function asText(input) {
11
+ return (input === undefined || input === null) ? 'None' : String(input);
12
+ }
13
+
5
14
  /**
6
15
  * Django filter catalog.
7
16
  *
@@ -18,6 +27,10 @@ var utils = require('@rhinostone/swig-core/lib/utils'),
18
27
  *
19
28
  * Fidelity notes (cross-checked against Django 5.2 `defaultfilters.py`):
20
29
  *
30
+ * - **None.** The string filters read None, and a variable that fails to
31
+ * resolve where Django reads it as None (`if`, `for`, `firstof`), as the
32
+ * text None, as Django's `stringfilter` does. `escape`, `escapejs` and
33
+ * `safe` keep the empty string of `{{ x }}`.
21
34
  * - **`.safe` vs Django `is_safe`.** Django's `is_safe=True` does NOT bypass
22
35
  * autoescape — it only means the filter preserves an already-safe input.
23
36
  * Only filters that Django wraps in `mark_safe()` (always returning trusted
@@ -121,7 +134,10 @@ function urlQuote(value, safe) {
121
134
  /**
122
135
  * Add the argument to the value. Numeric addition is tried first (Django's
123
136
  * `int(value) + int(arg)`); on failure it falls back to concatenation
124
- * (strings and arrays), then to the empty string.
137
+ * (strings and arrays), then to the empty string: a string and a number, a
138
+ * list or a mapping with anything but a list, and two mappings give the empty
139
+ * string, since Python cannot add them. A None or missing input or argument
140
+ * gives the empty string too.
125
141
  *
126
142
  * @example
127
143
  * {{ 4|add:2 }}
@@ -136,19 +152,30 @@ function urlQuote(value, safe) {
136
152
  * @return {*}
137
153
  */
138
154
  exports.add = function (input, arg) {
155
+ if (input === undefined || input === null || arg === undefined || arg === null) {
156
+ return '';
157
+ }
158
+ if (utils.isArray(input) && utils.isArray(arg)) {
159
+ return input.concat(arg);
160
+ }
161
+ // Python raises on a list or a mapping with anything else and on two mappings,
162
+ // and Django then gives an empty string.
163
+ if (typeof input === 'object' || typeof arg === 'object') {
164
+ return '';
165
+ }
166
+ // Python's int() reads a boolean as a number.
167
+ if (typeof input === 'boolean') { input = Number(input); }
168
+ if (typeof arg === 'boolean') { arg = Number(arg); }
139
169
  var a = parseInt(input, 10),
140
170
  b = parseInt(arg, 10);
141
171
  if (!isNaN(a) && !isNaN(b) && String(input).trim() === String(a) && String(arg).trim() === String(b)) {
142
172
  return a + b;
143
173
  }
144
- if (utils.isArray(input) && utils.isArray(arg)) {
145
- return input.concat(arg);
146
- }
147
- try {
148
- return input + arg;
149
- } catch (e) {
174
+ // Python's + raises on two different types.
175
+ if (typeof input !== typeof arg || (typeof input !== 'string' && typeof input !== 'number')) {
150
176
  return '';
151
177
  }
178
+ return input + arg;
152
179
  };
153
180
 
154
181
  /**
@@ -163,7 +190,7 @@ exports.add = function (input, arg) {
163
190
  * @return {string}
164
191
  */
165
192
  exports.addslashes = function (input) {
166
- return String(input).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/'/g, "\\'");
193
+ return asText(input).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/'/g, "\\'");
167
194
  };
168
195
 
169
196
  /**
@@ -179,7 +206,7 @@ exports.addslashes = function (input) {
179
206
  * @return {string}
180
207
  */
181
208
  exports.capfirst = function (input) {
182
- var s = String(input);
209
+ var s = asText(input);
183
210
  if (!s.length) {
184
211
  return s;
185
212
  }
@@ -200,7 +227,7 @@ exports.capfirst = function (input) {
200
227
  * @return {string}
201
228
  */
202
229
  exports.center = function (input, width) {
203
- var s = String(input),
230
+ var s = asText(input),
204
231
  w = parseInt(width, 10),
205
232
  marg,
206
233
  left,
@@ -226,7 +253,7 @@ exports.center = function (input, width) {
226
253
  * @return {string}
227
254
  */
228
255
  exports.cut = function (input, arg) {
229
- return String(input).split(String(arg)).join('');
256
+ return asText(input).split(String(arg)).join('');
230
257
  };
231
258
 
232
259
  /**
@@ -246,7 +273,7 @@ exports.cut = function (input, arg) {
246
273
  * @param {string} [format='F j, Y']
247
274
  * @param {number} [offset] Timezone offset in minutes west of GMT.
248
275
  * @param {string} [abbr] Output timezone abbreviation.
249
- * @return {string}
276
+ * @return {string} Formatted date string, or an empty string when the input is undefined, null or no date.
250
277
  */
251
278
  exports.date = function (input, format, offset, abbr) {
252
279
  var l,
@@ -263,6 +290,10 @@ exports.date = function (input, format, offset, abbr) {
263
290
  l = format.length;
264
291
  date = new dateFormatter.DateZ(input);
265
292
 
293
+ if (isNaN(date.dateZ.getTime())) {
294
+ return '';
295
+ }
296
+
266
297
  if (offset) {
267
298
  date.setTimezoneOffset(offset, abbr);
268
299
  }
@@ -565,7 +596,7 @@ exports.floatformat = function (input, arg) {
565
596
  a = utils.trimEnd(String(arg), 'gu');
566
597
  p = parseInt(a, 10);
567
598
  if (isNaN(p)) {
568
- return String(input);
599
+ return (input === undefined || input === null) ? '' : String(input);
569
600
  }
570
601
  }
571
602
 
@@ -595,7 +626,7 @@ exports.floatformat = function (input, arg) {
595
626
  * @return {string}
596
627
  */
597
628
  exports.force_escape = function (input) {
598
- return exports.escape(String(input));
629
+ return exports.escape(asText(input));
599
630
  };
600
631
  exports.force_escape.safe = true;
601
632
 
@@ -644,7 +675,7 @@ exports.get_digit = function (input, arg) {
644
675
  * @return {string}
645
676
  */
646
677
  exports.iriencode = function (input) {
647
- var s = String(input),
678
+ var s = asText(input),
648
679
  out = '',
649
680
  i,
650
681
  c,
@@ -756,7 +787,7 @@ exports.length = function (input) {
756
787
  * @return {string}
757
788
  */
758
789
  exports.linebreaks = function (input) {
759
- var paras = normalizeNewlines(input).split(/\n{2,}/);
790
+ var paras = normalizeNewlines(asText(input)).split(/\n{2,}/);
760
791
  return utils.map(paras, function (p) {
761
792
  return '<p>' + exports.escape(p).replace(/\n/g, '<br>') + '</p>';
762
793
  }).join('\n\n');
@@ -775,7 +806,7 @@ exports.linebreaks.safe = true;
775
806
  * @return {string}
776
807
  */
777
808
  exports.linebreaksbr = function (input) {
778
- return exports.escape(normalizeNewlines(input)).replace(/\n/g, '<br>');
809
+ return exports.escape(normalizeNewlines(asText(input))).replace(/\n/g, '<br>');
779
810
  };
780
811
  exports.linebreaksbr.safe = true;
781
812
 
@@ -792,7 +823,7 @@ exports.linebreaksbr.safe = true;
792
823
  * @return {string}
793
824
  */
794
825
  exports.linenumbers = function (input) {
795
- var lines = String(input).split('\n'),
826
+ var lines = asText(input).split('\n'),
796
827
  width = String(lines.length).length;
797
828
  return utils.map(lines, function (line, i) {
798
829
  var n = String(i + 1);
@@ -817,7 +848,7 @@ exports.linenumbers.safe = true;
817
848
  * @return {string}
818
849
  */
819
850
  exports.ljust = function (input, width) {
820
- var s = String(input),
851
+ var s = asText(input),
821
852
  w = parseInt(width, 10);
822
853
  if (isNaN(w) || s.length >= w) {
823
854
  return s;
@@ -833,10 +864,13 @@ exports.ljust = function (input, width) {
833
864
  * {{ "SWIG"|lower }}
834
865
  * // => swig
835
866
  *
836
- * @param {*} input
867
+ * @param {*} input None reads as the text None.
837
868
  * @return {*}
838
869
  */
839
870
  exports.lower = function (input) {
871
+ if (input === undefined || input === null) {
872
+ input = 'None';
873
+ }
840
874
  var out = iterateFilter.apply(exports.lower, arguments);
841
875
  if (out !== undefined) {
842
876
  return out;
@@ -856,7 +890,7 @@ exports.lower = function (input) {
856
890
  * @return {Array}
857
891
  */
858
892
  exports.make_list = function (input) {
859
- return String(input).split('');
893
+ return asText(input).split('');
860
894
  };
861
895
 
862
896
  /**
@@ -876,7 +910,7 @@ exports.phone2numeric = function (input) {
876
910
  j: '5', k: '5', l: '5', m: '6', n: '6', o: '6', p: '7', q: '7', r: '7',
877
911
  s: '7', t: '8', u: '8', v: '8', w: '9', x: '9', y: '9', z: '9'
878
912
  };
879
- return String(input).toLowerCase().replace(/[a-z]/g, function (c) {
913
+ return asText(input).toLowerCase().replace(/[a-z]/g, function (c) {
880
914
  return map[c];
881
915
  });
882
916
  };
@@ -971,7 +1005,7 @@ exports.pluralize = function (input, arg) {
971
1005
  * @return {string}
972
1006
  */
973
1007
  exports.rjust = function (input, width) {
974
- var s = String(input),
1008
+ var s = asText(input),
975
1009
  w = parseInt(width, 10);
976
1010
  if (isNaN(w) || s.length >= w) {
977
1011
  return s;
@@ -1051,7 +1085,7 @@ exports.slice = function (input, arg) {
1051
1085
  * @return {string}
1052
1086
  */
1053
1087
  exports.slugify = function (input) {
1054
- var value = String(input);
1088
+ var value = asText(input);
1055
1089
  if (value.normalize) {
1056
1090
  value = value.normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
1057
1091
  }
@@ -1077,7 +1111,7 @@ exports.striptags = function (input) {
1077
1111
  // after them (about 5 s for 100 000 characters); stripTags gives the same
1078
1112
  // output in linear time.
1079
1113
  // return String(input).replace(/(<([^>]+)>)/ig, '');
1080
- return utils.stripTags(String(input));
1114
+ return utils.stripTags(asText(input));
1081
1115
  };
1082
1116
 
1083
1117
  /**
@@ -1111,7 +1145,7 @@ exports.time = function (input, format, offset, abbr) {
1111
1145
  * @return {string}
1112
1146
  */
1113
1147
  exports.title = function (input) {
1114
- return String(input).replace(/\w\S*/g, function (str) {
1148
+ return asText(input).replace(/\w\S*/g, function (str) {
1115
1149
  return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
1116
1150
  });
1117
1151
  };
@@ -1151,7 +1185,7 @@ function pyInt(arg) {
1151
1185
  * @return {string}
1152
1186
  */
1153
1187
  exports.truncatechars = function (input, arg) {
1154
- var s = String(input),
1188
+ var s = asText(input),
1155
1189
  n = pyInt(arg);
1156
1190
  if (isNaN(n)) {
1157
1191
  return s;
@@ -1183,7 +1217,7 @@ exports.truncatewords = function (input, arg) {
1183
1217
  var n = pyInt(arg),
1184
1218
  words;
1185
1219
  if (isNaN(n)) {
1186
- return String(input);
1220
+ return asText(input);
1187
1221
  }
1188
1222
  if (n <= 0) {
1189
1223
  return '';
@@ -1191,7 +1225,7 @@ exports.truncatewords = function (input, arg) {
1191
1225
  // A trailing \s+$ takes quadratic time on a long run of whitespace inside
1192
1226
  // the value; utils.strip gives the same output in linear time.
1193
1227
  // words = String(input).replace(/^\s+|\s+$/g, '').split(/\s+/);
1194
- words = utils.strip(String(input)).split(/\s+/);
1228
+ words = utils.strip(asText(input)).split(/\s+/);
1195
1229
  if (words.length === 1 && words[0] === '') {
1196
1230
  return '';
1197
1231
  }
@@ -1209,10 +1243,13 @@ exports.truncatewords = function (input, arg) {
1209
1243
  * {{ "swig"|upper }}
1210
1244
  * // => SWIG
1211
1245
  *
1212
- * @param {*} input
1246
+ * @param {*} input None reads as the text None.
1213
1247
  * @return {*}
1214
1248
  */
1215
1249
  exports.upper = function (input) {
1250
+ if (input === undefined || input === null) {
1251
+ input = 'None';
1252
+ }
1216
1253
  var out = iterateFilter.apply(exports.upper, arguments);
1217
1254
  if (out !== undefined) {
1218
1255
  return out;
@@ -1235,7 +1272,7 @@ exports.upper = function (input) {
1235
1272
  */
1236
1273
  exports.urlencode = function (input, safe) {
1237
1274
  var safeChars = (safe === undefined || safe === null) ? '/' : String(safe);
1238
- return urlQuote(input, safeChars);
1275
+ return urlQuote(asText(input), safeChars);
1239
1276
  };
1240
1277
 
1241
1278
  /**
@@ -1250,7 +1287,7 @@ exports.urlencode = function (input, safe) {
1250
1287
  * @return {number}
1251
1288
  */
1252
1289
  exports.wordcount = function (input) {
1253
- var m = String(input).match(/\S+/g);
1290
+ var m = asText(input).match(/\S+/g);
1254
1291
  return m ? m.length : 0;
1255
1292
  };
1256
1293
 
@@ -1274,9 +1311,9 @@ exports.wordwrap = function (input, width) {
1274
1311
  i,
1275
1312
  word;
1276
1313
  if (isNaN(w) || w <= 0) {
1277
- return String(input);
1314
+ return asText(input);
1278
1315
  }
1279
- words = String(input).split(/(\s+)/);
1316
+ words = asText(input).split(/(\s+)/);
1280
1317
  lines = [];
1281
1318
  cur = '';
1282
1319
  for (i = 0; i < words.length; i += 1) {
package/lib/index.js CHANGED
@@ -32,7 +32,7 @@ exports.name = 'django';
32
32
  *
33
33
  * @type {String}
34
34
  */
35
- exports.version = "2.9.11";
35
+ exports.version = "2.9.13";
36
36
 
37
37
  /**
38
38
  * Expression-level parser — Pratt-style recursive descent that consumes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhinostone/swig-django",
3
- "version": "2.9.11",
3
+ "version": "2.9.13",
4
4
  "description": "Django-syntax frontend for the @rhinostone/swig-core template engine. Part of the @rhinostone/swig multi-flavor family.",
5
5
  "keywords": [
6
6
  "template",
@@ -22,7 +22,7 @@
22
22
  "node": ">=12"
23
23
  },
24
24
  "peerDependencies": {
25
- "@rhinostone/swig-core": "2.9.11"
25
+ "@rhinostone/swig-core": "2.9.13"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"