@wdprlib/parser 5.1.3 → 5.1.5

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.cjs CHANGED
@@ -1298,17 +1298,22 @@ function parseInlineUntil(ctx, endType) {
1298
1298
  }
1299
1299
  // packages/parser/src/parser/rules/block/parsing/attributes/names.ts
1300
1300
  function consumeAttributeName(ctx, startPos, startConsumed, startName, options) {
1301
+ if (startName === "_" && isAttributeWordToken(ctx.tokens[startPos])) {
1302
+ startName += ctx.tokens[startPos]?.value ?? "";
1303
+ startPos++;
1304
+ startConsumed++;
1305
+ }
1301
1306
  return options.strikeHyphens ? consumeRawNameSuffix(ctx, startPos, startConsumed, startName, options.hyphenatedNames) : consumeSafeNameSuffix(ctx, startPos, startConsumed, startName);
1302
1307
  }
1303
1308
  function isAttributeNameToken(token5) {
1304
- return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1309
+ return isAttributeWordToken(token5) || token5?.type === "UNDERSCORE";
1305
1310
  }
1306
1311
  function consumeSafeNameSuffix(ctx, startPos, startConsumed, startName) {
1307
1312
  let name = startName;
1308
1313
  let pos = startPos;
1309
1314
  let consumed = startConsumed;
1310
- while (ctx.tokens[pos]?.type === "TEXT" && ctx.tokens[pos]?.value === "-" && isAttributeNameToken(ctx.tokens[pos + 1])) {
1311
- name += "-";
1315
+ while (isAttributeNameSeparator(ctx.tokens[pos]) && isAttributeWordToken(ctx.tokens[pos + 1])) {
1316
+ name += ctx.tokens[pos]?.value ?? "";
1312
1317
  pos++;
1313
1318
  consumed++;
1314
1319
  name += ctx.tokens[pos]?.value ?? "";
@@ -1321,18 +1326,18 @@ function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenate
1321
1326
  let name = startName;
1322
1327
  let pos = startPos;
1323
1328
  let consumed = startConsumed;
1324
- while (isHyphenToken(ctx.tokens[pos])) {
1325
- while (isHyphenToken(ctx.tokens[pos])) {
1326
- if (hyphenatedNames) {
1329
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1330
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1331
+ if (hyphenatedNames || ctx.tokens[pos]?.type === "UNDERSCORE") {
1327
1332
  name += ctx.tokens[pos]?.value ?? "-";
1328
1333
  }
1329
1334
  pos++;
1330
1335
  consumed++;
1331
1336
  }
1332
- if (!isAttributeNameToken(ctx.tokens[pos])) {
1337
+ if (!isAttributeWordToken(ctx.tokens[pos])) {
1333
1338
  break;
1334
1339
  }
1335
- if (hyphenatedNames) {
1340
+ if (hyphenatedNames || name.endsWith("_")) {
1336
1341
  name += ctx.tokens[pos]?.value ?? "";
1337
1342
  }
1338
1343
  pos++;
@@ -1340,8 +1345,11 @@ function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenate
1340
1345
  }
1341
1346
  return { name, pos, consumed };
1342
1347
  }
1343
- function isHyphenToken(token5) {
1344
- return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER";
1348
+ function isAttributeWordToken(token5) {
1349
+ return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1350
+ }
1351
+ function isAttributeNameSeparator(token5) {
1352
+ return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER" || token5?.type === "UNDERSCORE";
1345
1353
  }
1346
1354
 
1347
1355
  // packages/parser/src/parser/rules/block/parsing/attributes/values.ts
@@ -4456,18 +4464,18 @@ var listPagesModuleRule = {
4456
4464
  wrapper,
4457
4465
  rss
4458
4466
  } = args;
4459
- const linkTo = args["link-to"] ?? args.linkto;
4460
- const createdBy = args["created-by"] ?? args.createdby;
4461
- const createdAt = args["created-at"] ?? args.createdat;
4462
- const updatedAt = args["updated-at"] ?? args.updatedat;
4463
- const perPage = args["per-page"] ?? args.perpage;
4464
- const prependLine = args["prepend-line"] ?? args.prependline;
4465
- const appendLine = args["append-line"] ?? args.appendline;
4466
- const rssDescription = args["rss-description"] ?? args.rssdescription;
4467
- const rssHome = args["rss-home"] ?? args.rsshome;
4468
- const rssLimit = args["rss-limit"] ?? args.rsslimit;
4469
- const rssOnly = args["rss-only"] ?? args.rssonly;
4470
- const urlAttrPrefix = args["url-attr-prefix"] ?? args.urlattrprefix;
4467
+ const linkTo = args["link-to"] ?? args.linkto ?? args.link_to;
4468
+ const createdBy = args["created-by"] ?? args.createdby ?? args.created_by;
4469
+ const createdAt = args["created-at"] ?? args.createdat ?? args.created_at;
4470
+ const updatedAt = args["updated-at"] ?? args.updatedat ?? args.updated_at;
4471
+ const perPage = args["per-page"] ?? args.perpage ?? args.per_page;
4472
+ const prependLine = args["prepend-line"] ?? args.prependline ?? args.prepend_line;
4473
+ const appendLine = args["append-line"] ?? args.appendline ?? args.append_line;
4474
+ const rssDescription = args["rss-description"] ?? args.rssdescription ?? args.rss_description;
4475
+ const rssHome = args["rss-home"] ?? args.rsshome ?? args.rss_home;
4476
+ const rssLimit = args["rss-limit"] ?? args.rsslimit ?? args.rss_limit;
4477
+ const rssOnly = args["rss-only"] ?? args.rssonly ?? args.rss_only;
4478
+ const urlAttrPrefix = args["url-attr-prefix"] ?? args.urlattrprefix ?? args.url_attr_prefix;
4471
4479
  const rawArgs = { ...args };
4472
4480
  return {
4473
4481
  module: "list-pages",
@@ -4692,10 +4700,10 @@ function parseModuleClose(ctx, startPos) {
4692
4700
  }
4693
4701
  let pos = startPos + 1 + closeNameResult.consumed;
4694
4702
  let consumed = 1 + closeNameResult.consumed;
4695
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
4696
- pos++;
4697
- consumed++;
4698
- }
4703
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE")
4704
+ return null;
4705
+ pos++;
4706
+ consumed++;
4699
4707
  if (ctx.tokens[pos]?.type === "NEWLINE") {
4700
4708
  pos++;
4701
4709
  consumed++;
@@ -5979,7 +5987,12 @@ function setsEqual(left, right) {
5979
5987
  var URL_RESOLVABLE_FIELDS = [
5980
5988
  { attr: "offset", queryKey: "offset", type: "number" },
5981
5989
  { attr: "limit", queryKey: "limit", type: "number" },
5982
- { attr: "per-page", queryKey: "perPage", type: "number" },
5990
+ {
5991
+ attr: "per-page",
5992
+ queryKey: "perPage",
5993
+ type: "number",
5994
+ aliases: ["perpage", "per_page"]
5995
+ },
5983
5996
  { attr: "order", queryKey: "order", type: "string" },
5984
5997
  { attr: "tags", queryKey: "tags", type: "string", urlAttrs: ["tag"] },
5985
5998
  { attr: "category", queryKey: "category", type: "string" },
@@ -5987,9 +6000,24 @@ var URL_RESOLVABLE_FIELDS = [
5987
6000
  { attr: "range", queryKey: "range", type: "string" },
5988
6001
  { attr: "name", queryKey: "name", type: "string" },
5989
6002
  { attr: "fullname", queryKey: "fullname", type: "string" },
5990
- { attr: "created-at", queryKey: "createdAt", type: "string" },
5991
- { attr: "updated-at", queryKey: "updatedAt", type: "string" },
5992
- { attr: "created-by", queryKey: "createdBy", type: "string" },
6003
+ {
6004
+ attr: "created-at",
6005
+ queryKey: "createdAt",
6006
+ type: "string",
6007
+ aliases: ["createdat", "created_at"]
6008
+ },
6009
+ {
6010
+ attr: "updated-at",
6011
+ queryKey: "updatedAt",
6012
+ type: "string",
6013
+ aliases: ["updatedat", "updated_at"]
6014
+ },
6015
+ {
6016
+ attr: "created-by",
6017
+ queryKey: "createdBy",
6018
+ type: "string",
6019
+ aliases: ["createdby", "created_by"]
6020
+ },
5993
6021
  { attr: "rating", queryKey: "rating", type: "string" },
5994
6022
  { attr: "votes", queryKey: "votes", type: "string" },
5995
6023
  { attr: "reverse", queryKey: "reverse", type: "boolean" }
@@ -6312,7 +6340,7 @@ function resolveQuery(requirement, urlParams) {
6312
6340
  const { query, rawAttributes, urlAttrPrefix } = requirement;
6313
6341
  const resolved = { ...query };
6314
6342
  for (const field of URL_RESOLVABLE_FIELDS) {
6315
- const rawValue = rawAttributes[field.attr];
6343
+ const rawValue = [field.attr, ...field.aliases ?? []].map((attr) => rawAttributes[attr]).find((value) => value !== undefined);
6316
6344
  if (!rawValue)
6317
6345
  continue;
6318
6346
  const resolvedValue = resolveUrlValue(rawValue, getUrlParamNames(field), urlParams, urlAttrPrefix);
@@ -6775,12 +6803,9 @@ function isRestOfLineBlank(source, pos) {
6775
6803
 
6776
6804
  // packages/parser/src/parser/rules/block/module/include/directive.ts
6777
6805
  function parseIncludeDirective(inner) {
6778
- const normalized = inner.includes(`
6779
- `) ? inner.replaceAll(`
6780
- `, " ") : inner;
6781
- const parts = normalized.split("|");
6806
+ const parts = inner.split("|");
6782
6807
  const firstSegment = parts[0].trim();
6783
- const spaceIndex = firstSegment.indexOf(" ");
6808
+ const spaceIndex = firstSegment.search(/\s/);
6784
6809
  let target;
6785
6810
  const varSegments = [];
6786
6811
  if (spaceIndex !== -1) {
package/dist/index.js CHANGED
@@ -1233,17 +1233,22 @@ function parseInlineUntil(ctx, endType) {
1233
1233
  }
1234
1234
  // packages/parser/src/parser/rules/block/parsing/attributes/names.ts
1235
1235
  function consumeAttributeName(ctx, startPos, startConsumed, startName, options) {
1236
+ if (startName === "_" && isAttributeWordToken(ctx.tokens[startPos])) {
1237
+ startName += ctx.tokens[startPos]?.value ?? "";
1238
+ startPos++;
1239
+ startConsumed++;
1240
+ }
1236
1241
  return options.strikeHyphens ? consumeRawNameSuffix(ctx, startPos, startConsumed, startName, options.hyphenatedNames) : consumeSafeNameSuffix(ctx, startPos, startConsumed, startName);
1237
1242
  }
1238
1243
  function isAttributeNameToken(token5) {
1239
- return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1244
+ return isAttributeWordToken(token5) || token5?.type === "UNDERSCORE";
1240
1245
  }
1241
1246
  function consumeSafeNameSuffix(ctx, startPos, startConsumed, startName) {
1242
1247
  let name = startName;
1243
1248
  let pos = startPos;
1244
1249
  let consumed = startConsumed;
1245
- while (ctx.tokens[pos]?.type === "TEXT" && ctx.tokens[pos]?.value === "-" && isAttributeNameToken(ctx.tokens[pos + 1])) {
1246
- name += "-";
1250
+ while (isAttributeNameSeparator(ctx.tokens[pos]) && isAttributeWordToken(ctx.tokens[pos + 1])) {
1251
+ name += ctx.tokens[pos]?.value ?? "";
1247
1252
  pos++;
1248
1253
  consumed++;
1249
1254
  name += ctx.tokens[pos]?.value ?? "";
@@ -1256,18 +1261,18 @@ function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenate
1256
1261
  let name = startName;
1257
1262
  let pos = startPos;
1258
1263
  let consumed = startConsumed;
1259
- while (isHyphenToken(ctx.tokens[pos])) {
1260
- while (isHyphenToken(ctx.tokens[pos])) {
1261
- if (hyphenatedNames) {
1264
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1265
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
1266
+ if (hyphenatedNames || ctx.tokens[pos]?.type === "UNDERSCORE") {
1262
1267
  name += ctx.tokens[pos]?.value ?? "-";
1263
1268
  }
1264
1269
  pos++;
1265
1270
  consumed++;
1266
1271
  }
1267
- if (!isAttributeNameToken(ctx.tokens[pos])) {
1272
+ if (!isAttributeWordToken(ctx.tokens[pos])) {
1268
1273
  break;
1269
1274
  }
1270
- if (hyphenatedNames) {
1275
+ if (hyphenatedNames || name.endsWith("_")) {
1271
1276
  name += ctx.tokens[pos]?.value ?? "";
1272
1277
  }
1273
1278
  pos++;
@@ -1275,8 +1280,11 @@ function consumeRawNameSuffix(ctx, startPos, startConsumed, startName, hyphenate
1275
1280
  }
1276
1281
  return { name, pos, consumed };
1277
1282
  }
1278
- function isHyphenToken(token5) {
1279
- return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER";
1283
+ function isAttributeWordToken(token5) {
1284
+ return token5?.type === "TEXT" || token5?.type === "IDENTIFIER";
1285
+ }
1286
+ function isAttributeNameSeparator(token5) {
1287
+ return token5?.type === "TEXT" && token5.value === "-" || token5?.type === "STRIKE_MARKER" || token5?.type === "UNDERSCORE";
1280
1288
  }
1281
1289
 
1282
1290
  // packages/parser/src/parser/rules/block/parsing/attributes/values.ts
@@ -4391,18 +4399,18 @@ var listPagesModuleRule = {
4391
4399
  wrapper,
4392
4400
  rss
4393
4401
  } = args;
4394
- const linkTo = args["link-to"] ?? args.linkto;
4395
- const createdBy = args["created-by"] ?? args.createdby;
4396
- const createdAt = args["created-at"] ?? args.createdat;
4397
- const updatedAt = args["updated-at"] ?? args.updatedat;
4398
- const perPage = args["per-page"] ?? args.perpage;
4399
- const prependLine = args["prepend-line"] ?? args.prependline;
4400
- const appendLine = args["append-line"] ?? args.appendline;
4401
- const rssDescription = args["rss-description"] ?? args.rssdescription;
4402
- const rssHome = args["rss-home"] ?? args.rsshome;
4403
- const rssLimit = args["rss-limit"] ?? args.rsslimit;
4404
- const rssOnly = args["rss-only"] ?? args.rssonly;
4405
- const urlAttrPrefix = args["url-attr-prefix"] ?? args.urlattrprefix;
4402
+ const linkTo = args["link-to"] ?? args.linkto ?? args.link_to;
4403
+ const createdBy = args["created-by"] ?? args.createdby ?? args.created_by;
4404
+ const createdAt = args["created-at"] ?? args.createdat ?? args.created_at;
4405
+ const updatedAt = args["updated-at"] ?? args.updatedat ?? args.updated_at;
4406
+ const perPage = args["per-page"] ?? args.perpage ?? args.per_page;
4407
+ const prependLine = args["prepend-line"] ?? args.prependline ?? args.prepend_line;
4408
+ const appendLine = args["append-line"] ?? args.appendline ?? args.append_line;
4409
+ const rssDescription = args["rss-description"] ?? args.rssdescription ?? args.rss_description;
4410
+ const rssHome = args["rss-home"] ?? args.rsshome ?? args.rss_home;
4411
+ const rssLimit = args["rss-limit"] ?? args.rsslimit ?? args.rss_limit;
4412
+ const rssOnly = args["rss-only"] ?? args.rssonly ?? args.rss_only;
4413
+ const urlAttrPrefix = args["url-attr-prefix"] ?? args.urlattrprefix ?? args.url_attr_prefix;
4406
4414
  const rawArgs = { ...args };
4407
4415
  return {
4408
4416
  module: "list-pages",
@@ -4627,10 +4635,10 @@ function parseModuleClose(ctx, startPos) {
4627
4635
  }
4628
4636
  let pos = startPos + 1 + closeNameResult.consumed;
4629
4637
  let consumed = 1 + closeNameResult.consumed;
4630
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
4631
- pos++;
4632
- consumed++;
4633
- }
4638
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE")
4639
+ return null;
4640
+ pos++;
4641
+ consumed++;
4634
4642
  if (ctx.tokens[pos]?.type === "NEWLINE") {
4635
4643
  pos++;
4636
4644
  consumed++;
@@ -5914,7 +5922,12 @@ function setsEqual(left, right) {
5914
5922
  var URL_RESOLVABLE_FIELDS = [
5915
5923
  { attr: "offset", queryKey: "offset", type: "number" },
5916
5924
  { attr: "limit", queryKey: "limit", type: "number" },
5917
- { attr: "per-page", queryKey: "perPage", type: "number" },
5925
+ {
5926
+ attr: "per-page",
5927
+ queryKey: "perPage",
5928
+ type: "number",
5929
+ aliases: ["perpage", "per_page"]
5930
+ },
5918
5931
  { attr: "order", queryKey: "order", type: "string" },
5919
5932
  { attr: "tags", queryKey: "tags", type: "string", urlAttrs: ["tag"] },
5920
5933
  { attr: "category", queryKey: "category", type: "string" },
@@ -5922,9 +5935,24 @@ var URL_RESOLVABLE_FIELDS = [
5922
5935
  { attr: "range", queryKey: "range", type: "string" },
5923
5936
  { attr: "name", queryKey: "name", type: "string" },
5924
5937
  { attr: "fullname", queryKey: "fullname", type: "string" },
5925
- { attr: "created-at", queryKey: "createdAt", type: "string" },
5926
- { attr: "updated-at", queryKey: "updatedAt", type: "string" },
5927
- { attr: "created-by", queryKey: "createdBy", type: "string" },
5938
+ {
5939
+ attr: "created-at",
5940
+ queryKey: "createdAt",
5941
+ type: "string",
5942
+ aliases: ["createdat", "created_at"]
5943
+ },
5944
+ {
5945
+ attr: "updated-at",
5946
+ queryKey: "updatedAt",
5947
+ type: "string",
5948
+ aliases: ["updatedat", "updated_at"]
5949
+ },
5950
+ {
5951
+ attr: "created-by",
5952
+ queryKey: "createdBy",
5953
+ type: "string",
5954
+ aliases: ["createdby", "created_by"]
5955
+ },
5928
5956
  { attr: "rating", queryKey: "rating", type: "string" },
5929
5957
  { attr: "votes", queryKey: "votes", type: "string" },
5930
5958
  { attr: "reverse", queryKey: "reverse", type: "boolean" }
@@ -6247,7 +6275,7 @@ function resolveQuery(requirement, urlParams) {
6247
6275
  const { query, rawAttributes, urlAttrPrefix } = requirement;
6248
6276
  const resolved = { ...query };
6249
6277
  for (const field of URL_RESOLVABLE_FIELDS) {
6250
- const rawValue = rawAttributes[field.attr];
6278
+ const rawValue = [field.attr, ...field.aliases ?? []].map((attr) => rawAttributes[attr]).find((value) => value !== undefined);
6251
6279
  if (!rawValue)
6252
6280
  continue;
6253
6281
  const resolvedValue = resolveUrlValue(rawValue, getUrlParamNames(field), urlParams, urlAttrPrefix);
@@ -6710,12 +6738,9 @@ function isRestOfLineBlank(source, pos) {
6710
6738
 
6711
6739
  // packages/parser/src/parser/rules/block/module/include/directive.ts
6712
6740
  function parseIncludeDirective(inner) {
6713
- const normalized = inner.includes(`
6714
- `) ? inner.replaceAll(`
6715
- `, " ") : inner;
6716
- const parts = normalized.split("|");
6741
+ const parts = inner.split("|");
6717
6742
  const firstSegment = parts[0].trim();
6718
- const spaceIndex = firstSegment.indexOf(" ");
6743
+ const spaceIndex = firstSegment.search(/\s/);
6719
6744
  let target;
6720
6745
  const varSegments = [];
6721
6746
  if (spaceIndex !== -1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdprlib/parser",
3
- "version": "5.1.3",
3
+ "version": "5.1.5",
4
4
  "description": "Parser for Wikidot markup",
5
5
  "keywords": [
6
6
  "ast",
@@ -79,10 +79,9 @@ function parseModuleClose(
79
79
  let pos = startPos + 1 + closeNameResult.consumed;
80
80
  let consumed = 1 + closeNameResult.consumed;
81
81
 
82
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
83
- pos++;
84
- consumed++;
85
- }
82
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") return null;
83
+ pos++;
84
+ consumed++;
86
85
  if (ctx.tokens[pos]?.type === "NEWLINE") {
87
86
  pos++;
88
87
  consumed++;
@@ -13,11 +13,10 @@ export function parseIncludeDirective(inner: string): {
13
13
  location: PageRef;
14
14
  assignments: IncludeAssignment[];
15
15
  } {
16
- const normalized = inner.includes("\n") ? inner.replaceAll("\n", " ") : inner;
17
- const parts = normalized.split("|");
16
+ const parts = inner.split("|");
18
17
  const firstSegment = parts[0]!.trim();
19
18
 
20
- const spaceIndex = firstSegment.indexOf(" ");
19
+ const spaceIndex = firstSegment.search(/\s/);
21
20
  let target: string;
22
21
  const varSegments: string[] = [];
23
22
 
@@ -3,9 +3,9 @@
3
3
  * Parser rule for the Wikidot `[[module ListPages ...]]` block.
4
4
  *
5
5
  * Parses the module's attributes into a structured `list-pages` Module AST node.
6
- * Handles both hyphenated (`link-to`) and concatenated (`linkto`) attribute name
7
- * formats, as Wikidot normalizes both to lowercase. The raw attribute values are
8
- * preserved in the `attributes` field for `@URL` resolution by external applications.
6
+ * Handles hyphenated (`link-to`), concatenated (`linkto`), and snake_case (`link_to`)
7
+ * attribute name formats. The raw attribute values are preserved in the `attributes`
8
+ * field for `@URL` resolution by external applications.
9
9
  *
10
10
  * @module
11
11
  */
@@ -51,19 +51,19 @@ export const listPagesModuleRule: ModuleRule = {
51
51
  wrapper,
52
52
  rss,
53
53
  } = args;
54
- // Hyphenated attributes (stored with hyphens in lowercase)
55
- const linkTo = args["link-to"] ?? args.linkto;
56
- const createdBy = args["created-by"] ?? args.createdby;
57
- const createdAt = args["created-at"] ?? args.createdat;
58
- const updatedAt = args["updated-at"] ?? args.updatedat;
59
- const perPage = args["per-page"] ?? args.perpage;
60
- const prependLine = args["prepend-line"] ?? args.prependline;
61
- const appendLine = args["append-line"] ?? args.appendline;
62
- const rssDescription = args["rss-description"] ?? args.rssdescription;
63
- const rssHome = args["rss-home"] ?? args.rsshome;
64
- const rssLimit = args["rss-limit"] ?? args.rsslimit;
65
- const rssOnly = args["rss-only"] ?? args.rssonly;
66
- const urlAttrPrefix = args["url-attr-prefix"] ?? args.urlattrprefix;
54
+ // Compound attributes accept Wikidot's supported spelling variants.
55
+ const linkTo = args["link-to"] ?? args.linkto ?? args.link_to;
56
+ const createdBy = args["created-by"] ?? args.createdby ?? args.created_by;
57
+ const createdAt = args["created-at"] ?? args.createdat ?? args.created_at;
58
+ const updatedAt = args["updated-at"] ?? args.updatedat ?? args.updated_at;
59
+ const perPage = args["per-page"] ?? args.perpage ?? args.per_page;
60
+ const prependLine = args["prepend-line"] ?? args.prependline ?? args.prepend_line;
61
+ const appendLine = args["append-line"] ?? args.appendline ?? args.append_line;
62
+ const rssDescription = args["rss-description"] ?? args.rssdescription ?? args.rss_description;
63
+ const rssHome = args["rss-home"] ?? args.rsshome ?? args.rss_home;
64
+ const rssLimit = args["rss-limit"] ?? args.rsslimit ?? args.rss_limit;
65
+ const rssOnly = args["rss-only"] ?? args.rssonly ?? args.rss_only;
66
+ const urlAttrPrefix = args["url-attr-prefix"] ?? args.urlattrprefix ?? args.url_attr_prefix;
67
67
 
68
68
  // Store all raw arguments for @URL resolution by external apps
69
69
  const rawArgs: Record<string, string> = { ...args };
@@ -1,9 +1,27 @@
1
1
  import type { ListPagesQuery } from "../types";
2
2
 
3
3
  export type UrlResolvableField =
4
- | { attr: string; queryKey: StringUrlQueryKey; type: "string"; urlAttrs?: readonly string[] }
5
- | { attr: string; queryKey: NumberUrlQueryKey; type: "number"; urlAttrs?: readonly string[] }
6
- | { attr: string; queryKey: BooleanUrlQueryKey; type: "boolean"; urlAttrs?: readonly string[] };
4
+ | {
5
+ attr: string;
6
+ queryKey: StringUrlQueryKey;
7
+ type: "string";
8
+ aliases?: readonly string[];
9
+ urlAttrs?: readonly string[];
10
+ }
11
+ | {
12
+ attr: string;
13
+ queryKey: NumberUrlQueryKey;
14
+ type: "number";
15
+ aliases?: readonly string[];
16
+ urlAttrs?: readonly string[];
17
+ }
18
+ | {
19
+ attr: string;
20
+ queryKey: BooleanUrlQueryKey;
21
+ type: "boolean";
22
+ aliases?: readonly string[];
23
+ urlAttrs?: readonly string[];
24
+ };
7
25
 
8
26
  type StringUrlQueryKey = Extract<
9
27
  keyof ListPagesQuery,
@@ -31,7 +49,12 @@ type BooleanUrlQueryKey = Extract<keyof ListPagesQuery, "reverse">;
31
49
  export const URL_RESOLVABLE_FIELDS: readonly UrlResolvableField[] = [
32
50
  { attr: "offset", queryKey: "offset", type: "number" },
33
51
  { attr: "limit", queryKey: "limit", type: "number" },
34
- { attr: "per-page", queryKey: "perPage", type: "number" },
52
+ {
53
+ attr: "per-page",
54
+ queryKey: "perPage",
55
+ type: "number",
56
+ aliases: ["perpage", "per_page"],
57
+ },
35
58
  { attr: "order", queryKey: "order", type: "string" },
36
59
  { attr: "tags", queryKey: "tags", type: "string", urlAttrs: ["tag"] },
37
60
  { attr: "category", queryKey: "category", type: "string" },
@@ -39,9 +62,24 @@ export const URL_RESOLVABLE_FIELDS: readonly UrlResolvableField[] = [
39
62
  { attr: "range", queryKey: "range", type: "string" },
40
63
  { attr: "name", queryKey: "name", type: "string" },
41
64
  { attr: "fullname", queryKey: "fullname", type: "string" },
42
- { attr: "created-at", queryKey: "createdAt", type: "string" },
43
- { attr: "updated-at", queryKey: "updatedAt", type: "string" },
44
- { attr: "created-by", queryKey: "createdBy", type: "string" },
65
+ {
66
+ attr: "created-at",
67
+ queryKey: "createdAt",
68
+ type: "string",
69
+ aliases: ["createdat", "created_at"],
70
+ },
71
+ {
72
+ attr: "updated-at",
73
+ queryKey: "updatedAt",
74
+ type: "string",
75
+ aliases: ["updatedat", "updated_at"],
76
+ },
77
+ {
78
+ attr: "created-by",
79
+ queryKey: "createdBy",
80
+ type: "string",
81
+ aliases: ["createdby", "created_by"],
82
+ },
45
83
  { attr: "rating", queryKey: "rating", type: "string" },
46
84
  { attr: "votes", queryKey: "votes", type: "string" },
47
85
  { attr: "reverse", queryKey: "reverse", type: "boolean" },
@@ -22,7 +22,9 @@ export function resolveQuery(
22
22
  const resolved: ListPagesQuery = { ...query };
23
23
 
24
24
  for (const field of URL_RESOLVABLE_FIELDS) {
25
- const rawValue = rawAttributes[field.attr];
25
+ const rawValue = [field.attr, ...(field.aliases ?? [])]
26
+ .map((attr) => rawAttributes[attr])
27
+ .find((value) => value !== undefined);
26
28
  if (!rawValue) continue;
27
29
 
28
30
  const resolvedValue = resolveUrlValue(
@@ -19,13 +19,19 @@ export function consumeAttributeName(
19
19
  startName: string,
20
20
  options: AttributeNameOptions,
21
21
  ): AttributeNameResult {
22
+ if (startName === "_" && isAttributeWordToken(ctx.tokens[startPos])) {
23
+ startName += ctx.tokens[startPos]?.value ?? "";
24
+ startPos++;
25
+ startConsumed++;
26
+ }
27
+
22
28
  return options.strikeHyphens
23
29
  ? consumeRawNameSuffix(ctx, startPos, startConsumed, startName, options.hyphenatedNames)
24
30
  : consumeSafeNameSuffix(ctx, startPos, startConsumed, startName);
25
31
  }
26
32
 
27
33
  export function isAttributeNameToken(token: Token | undefined): token is Token {
28
- return token?.type === "TEXT" || token?.type === "IDENTIFIER";
34
+ return isAttributeWordToken(token) || token?.type === "UNDERSCORE";
29
35
  }
30
36
 
31
37
  function consumeSafeNameSuffix(
@@ -38,12 +44,8 @@ function consumeSafeNameSuffix(
38
44
  let pos = startPos;
39
45
  let consumed = startConsumed;
40
46
 
41
- while (
42
- ctx.tokens[pos]?.type === "TEXT" &&
43
- ctx.tokens[pos]?.value === "-" &&
44
- isAttributeNameToken(ctx.tokens[pos + 1])
45
- ) {
46
- name += "-";
47
+ while (isAttributeNameSeparator(ctx.tokens[pos]) && isAttributeWordToken(ctx.tokens[pos + 1])) {
48
+ name += ctx.tokens[pos]?.value ?? "";
47
49
  pos++;
48
50
  consumed++;
49
51
  name += ctx.tokens[pos]?.value ?? "";
@@ -65,20 +67,20 @@ function consumeRawNameSuffix(
65
67
  let pos = startPos;
66
68
  let consumed = startConsumed;
67
69
 
68
- while (isHyphenToken(ctx.tokens[pos])) {
69
- while (isHyphenToken(ctx.tokens[pos])) {
70
- if (hyphenatedNames) {
70
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
71
+ while (isAttributeNameSeparator(ctx.tokens[pos])) {
72
+ if (hyphenatedNames || ctx.tokens[pos]?.type === "UNDERSCORE") {
71
73
  name += ctx.tokens[pos]?.value ?? "-";
72
74
  }
73
75
  pos++;
74
76
  consumed++;
75
77
  }
76
78
 
77
- if (!isAttributeNameToken(ctx.tokens[pos])) {
79
+ if (!isAttributeWordToken(ctx.tokens[pos])) {
78
80
  break;
79
81
  }
80
82
 
81
- if (hyphenatedNames) {
83
+ if (hyphenatedNames || name.endsWith("_")) {
82
84
  name += ctx.tokens[pos]?.value ?? "";
83
85
  }
84
86
  pos++;
@@ -88,6 +90,14 @@ function consumeRawNameSuffix(
88
90
  return { name, pos, consumed };
89
91
  }
90
92
 
91
- function isHyphenToken(token: Token | undefined): boolean {
92
- return (token?.type === "TEXT" && token.value === "-") || token?.type === "STRIKE_MARKER";
93
+ function isAttributeWordToken(token: Token | undefined): boolean {
94
+ return token?.type === "TEXT" || token?.type === "IDENTIFIER";
95
+ }
96
+
97
+ function isAttributeNameSeparator(token: Token | undefined): boolean {
98
+ return (
99
+ (token?.type === "TEXT" && token.value === "-") ||
100
+ token?.type === "STRIKE_MARKER" ||
101
+ token?.type === "UNDERSCORE"
102
+ );
93
103
  }