sommark 3.3.4 → 4.0.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.
Files changed (62) hide show
  1. package/README.md +98 -82
  2. package/assets/logo.json +28 -0
  3. package/assets/smark.logo.png +0 -0
  4. package/assets/smark.logo.svg +21 -0
  5. package/cli/cli.mjs +7 -17
  6. package/cli/commands/build.js +26 -6
  7. package/cli/commands/color.js +22 -26
  8. package/cli/commands/help.js +10 -10
  9. package/cli/commands/init.js +20 -31
  10. package/cli/commands/print.js +18 -16
  11. package/cli/commands/show.js +4 -0
  12. package/cli/commands/version.js +6 -0
  13. package/cli/constants.js +9 -5
  14. package/cli/helpers/config.js +11 -0
  15. package/cli/helpers/file.js +17 -6
  16. package/cli/helpers/transpile.js +15 -17
  17. package/core/errors.js +49 -25
  18. package/core/formats.js +7 -3
  19. package/core/formatter.js +215 -0
  20. package/core/helpers/config-loader.js +40 -75
  21. package/core/labels.js +21 -9
  22. package/core/lexer.js +491 -212
  23. package/core/modules.js +164 -0
  24. package/core/parser.js +516 -389
  25. package/core/tokenTypes.js +36 -1
  26. package/core/transpiler.js +238 -154
  27. package/core/validator.js +79 -0
  28. package/formatter/mark.js +203 -43
  29. package/formatter/tag.js +202 -32
  30. package/grammar.ebnf +57 -50
  31. package/helpers/colorize.js +26 -13
  32. package/helpers/dedent.js +19 -0
  33. package/helpers/escapeHTML.js +13 -6
  34. package/helpers/kebabize.js +6 -0
  35. package/helpers/peek.js +9 -0
  36. package/helpers/removeChar.js +26 -13
  37. package/helpers/safeDataParser.js +114 -0
  38. package/helpers/utils.js +140 -158
  39. package/index.js +186 -188
  40. package/mappers/languages/html.js +105 -213
  41. package/mappers/languages/json.js +122 -171
  42. package/mappers/languages/markdown.js +355 -108
  43. package/mappers/languages/mdx.js +76 -120
  44. package/mappers/languages/xml.js +114 -0
  45. package/mappers/mapper.js +152 -123
  46. package/mappers/shared/index.js +22 -0
  47. package/package.json +26 -6
  48. package/SOMMARK-SPEC.md +0 -481
  49. package/cli/commands/list.js +0 -124
  50. package/constants/html_tags.js +0 -146
  51. package/core/pluginManager.js +0 -149
  52. package/core/plugins/comment-remover.js +0 -47
  53. package/core/plugins/module-system.js +0 -176
  54. package/core/plugins/raw-content-plugin.js +0 -78
  55. package/core/plugins/rules-validation-plugin.js +0 -231
  56. package/core/plugins/sommark-format.js +0 -244
  57. package/coverage_test.js +0 -21
  58. package/debug.js +0 -15
  59. package/helpers/camelize.js +0 -2
  60. package/helpers/defaultTheme.js +0 -3
  61. package/test_format_fix.js +0 -42
  62. package/v3-todo.smark +0 -73
package/helpers/utils.js CHANGED
@@ -1,188 +1,170 @@
1
1
  import { sommarkError } from "../core/errors.js";
2
+ import * as labels from "../core/labels.js";
2
3
 
3
4
  /**
4
- * Checks if a todo item should be checked.
5
- * @param {string} status
6
- * @returns {boolean}
5
+ * Checks if a value is a plain object.
6
+ * @param {any} val - The value to check.
7
+ * @returns {boolean} - True if it's an object.
7
8
  */
8
- export function todo(status = "") {
9
- return (status || "").trim() === "x" || (status || "").trim().toLowerCase() === "done";
10
- }
9
+ export const isObject = (val) => typeof val === "object" && val !== null && !Array.isArray(val);
11
10
 
12
11
  /**
13
- * Matches a target ID within an array of output registration objects.
12
+ * Checks if a value is an array.
13
+ * @param {any} val - The value to check.
14
+ * @returns {boolean} - True if it's an array.
14
15
  */
15
- export function matchedValue(outputs, targetId) {
16
- let result;
17
- for (const outputValue of outputs) {
18
- if (typeof outputValue.id === "string") {
19
- if (outputValue.id === targetId) {
20
- result = outputValue;
21
- break;
22
- }
23
- } else if (Array.isArray(outputValue.id)) {
24
- for (const id of outputValue.id) {
25
- if (id === targetId) {
26
- result = outputValue;
27
- break;
28
- }
29
- }
30
- }
31
- }
32
- return result;
33
- }
16
+ export const isArray = (val) => Array.isArray(val);
34
17
 
35
18
  /**
36
- * Safe argument retrieval with validation.
19
+ * Checks if a value is a string.
20
+ * @param {any} val - The value to check.
21
+ * @returns {boolean} - True if it's a string.
37
22
  */
38
- export function safeArg(args, index, key, type = null, setType = null, fallBack = null) {
39
- if (!Array.isArray(args)) {
40
- sommarkError([`{line}<$red:TypeError:$> <$yellow:args must be an array$>{line}`]);
41
- }
42
-
43
- if (index === undefined && key === undefined) {
44
- sommarkError([`{line}<$red:ReferenceError:> <$yellow:At least one of 'index' or 'key' must be provided$>{line}`]);
45
- }
46
-
47
- const validate = value => {
48
- if (value === undefined) return false;
49
- if (!type) return true;
50
- const evaluated = setType ? setType(value) : value;
51
- return typeof evaluated === type;
52
- };
53
-
54
- if (index !== undefined && validate(args[index])) {
55
- return args[index];
56
- }
57
-
58
- if (key !== undefined && validate(args[key])) {
59
- return args[key];
60
- }
61
-
62
- return fallBack;
63
- }
23
+ export const isString = (val) => typeof val === "string";
64
24
 
65
25
  /**
66
- * Calculates the Levenshtein distance between two strings.
67
- * @param {string} a
68
- * @param {string} b
69
- * @returns {number}
26
+ * Checks if a value is a number and not NaN.
27
+ * @param {any} val - The value to check.
28
+ * @returns {boolean} - True if it's a valid number.
70
29
  */
71
- export function levenshtein(a, b) {
72
- const matrix = [];
73
- for (let i = 0; i <= b.length; i++) matrix[i] = [i];
74
- for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
75
-
76
- for (let i = 1; i <= b.length; i++) {
77
- for (let j = 1; j <= a.length; j++) {
78
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
79
- matrix[i][j] = matrix[i - 1][j - 1];
80
- } else {
81
- matrix[i][j] = Math.min(
82
- matrix[i - 1][j - 1] + 1,
83
- matrix[i][j - 1] + 1,
84
- matrix[i - 1][j] + 1
85
- );
86
- }
87
- }
88
- }
89
- return matrix[b.length][a.length];
90
- }
30
+ export const isNumber = (val) => typeof val === "number" && !isNaN(val);
91
31
 
92
32
  /**
93
- * Renders an HTML table.
33
+ * Checks if a value is true or false.
34
+ * @param {any} val - The value to check.
35
+ * @returns {boolean} - True if it's a boolean.
94
36
  */
95
- export function htmlTable(data, headers, escapeFn = (t) => t) {
96
- if (!data) return "";
97
-
98
- if (typeof data === "string") {
99
- data = data.split(/\r?\n/);
100
- } else if (!Array.isArray(data) || data.length === 0) {
101
- return "";
102
- }
103
-
104
- let tableHTML = `<table class="sommark-table">\n<thead>\n<tr>`;
105
- for (const header of headers) {
106
- tableHTML += `<th>${escapeFn(header)}</th>`;
107
- }
108
- tableHTML += "</tr>\n</thead>\n<tbody>\n";
109
-
110
- for (const row of data) {
111
- const trimmedRow = row.trim();
112
- if (!trimmedRow) continue;
113
-
114
- const rowData = trimmedRow.split(/(?<!\\),/).map(cell => {
115
- let text = cell.trim();
116
- if (text.endsWith(";")) text = text.slice(0, -1);
117
- return text.replace(/\\(.)/g, "$1");
118
- });
119
-
120
- tableHTML += "<tr>";
121
- for (const cell of rowData) {
122
- tableHTML += `<td>${escapeFn(cell.trim())}</td>`;
123
- }
124
- tableHTML += "</tr>\n";
125
- }
37
+ export const isBool = (val) => typeof val === "boolean";
126
38
 
127
- tableHTML += "</tbody>\n</table>";
128
- return tableHTML;
39
+ /**
40
+ * Checks if a value is null.
41
+ * @param {any} val - The value to check.
42
+ * @returns {boolean} - True if it's null.
43
+ */
44
+ export const isNull = (val) => val === null;
45
+
46
+ /**
47
+ * Checks if a value is missing (undefined).
48
+ * @param {any} val - The value to check.
49
+ * @returns {boolean} - True if it's undefined.
50
+ */
51
+ export const isUndefined = (val) => val === undefined;
52
+
53
+ /**
54
+ * Finds a matching output definition for a tag ID from a list of registered outputs.
55
+ * Uses case-insensitive matching. Handles both string and array-based ID definitions.
56
+ *
57
+ * @param {Array<Object>} outputs - List of registered tag outputs.
58
+ * @param {string} targetId - The tag identifier to look for.
59
+ * @returns {Object|undefined} - The matched output entry or undefined.
60
+ */
61
+ export function matchedValue(outputs, targetId) {
62
+ if (!outputs || !targetId) return undefined;
63
+ for (let i = outputs.length - 1; i >= 0; i--) {
64
+ const outputValue = outputs[i];
65
+ const lowerTarget = targetId.toLowerCase();
66
+
67
+ if (typeof outputValue.id === "string") {
68
+ if (outputValue.id.toLowerCase() === lowerTarget) {
69
+ return outputValue;
70
+ }
71
+ } else if (Array.isArray(outputValue.id)) {
72
+ if (outputValue.id.some(id => id.toLowerCase() === lowerTarget)) {
73
+ return outputValue;
74
+ }
75
+ }
76
+ }
77
+ return undefined;
129
78
  }
130
79
 
131
80
  /**
132
- * Parses a hierarchical list.
81
+ * Safely retrieves an argument value, supporting both positional (number) and named (string) keys.
82
+ * Includes validation against a specific type and optional type casting.
83
+ *
84
+ * @param {Object} options - Resolution options.
85
+ * @param {Object} options.args - The argument object from an AST node.
86
+ * @param {number} [options.index] - The positional index (for V4 Global Indexing).
87
+ * @param {string} [options.key] - The named key.
88
+ * @param {string|null} [options.type=null] - Expected typeof result (e.g., 'string', 'boolean').
89
+ * @param {Function|null} [options.setType=null] - Optional function to cast the value before validation.
90
+ * @param {any} [options.fallBack=null] - Value to return if resolution or validation fails.
91
+ * @returns {any} - The resolved argument value or the fallback.
133
92
  */
134
- export function parseList(data, indentSize = 2) {
135
- if (typeof data === "string") {
136
- data = data.split("\n");
137
- }
138
- const root = { level: -1, children: [] };
139
- const stack = [root];
140
-
141
- const getLevel = line => {
142
- const spaces = line.match(/^\s*/)[0].length;
143
- return Math.floor(spaces / indentSize);
144
- };
145
-
146
- for (const raw of data) {
147
- if (!raw.trim()) continue;
148
- const level = getLevel(raw);
149
- const text = raw.trim();
150
-
151
- const node = { text, children: [] };
152
-
153
- while (stack.length && stack[stack.length - 1].level >= level) {
154
- stack.pop();
93
+ export function safeArg({ args, index, key, type = null, setType = null, fallBack = null }) {
94
+ if (typeof args !== 'object' || args === null) {
95
+ sommarkError([`{line}<$red:TypeError:$> <$yellow:args must be an object$>{line}`]);
96
+ }
97
+
98
+ if (index === undefined && key === undefined) {
99
+ sommarkError([`{line}<$red:ReferenceError:> <$yellow:At least one of 'index' or 'key' must be provided$>{line}`]);
100
+ }
101
+
102
+ const validate = value => {
103
+ if (value === undefined) return false;
104
+
105
+ // Handle explicit type check functions (e.g., isObject, isArray)
106
+ if (typeof type === 'function') {
107
+ return type(value);
108
+ }
109
+
110
+ if (!type) return true;
111
+ const evaluated = setType ? setType(value) : value;
112
+ return typeof evaluated === type;
113
+ };
114
+
115
+ if (index !== undefined && validate(args[index])) {
116
+ return args[index];
155
117
  }
156
118
 
157
- stack[stack.length - 1].children.push(node);
158
- stack.push({ ...node, level });
159
- }
119
+ if (key !== undefined && validate(args[key])) {
120
+ return args[key];
121
+ }
160
122
 
161
- return root.children;
123
+ return fallBack;
162
124
  }
163
125
 
164
126
  /**
165
- * Renders a list (ul/ol).
127
+ * Extracts and returns all positional arguments from the Object-based 'args' structure of V4.
128
+ *
129
+ * @param {Object} args - The AST node's argument object.
130
+ * @returns {Array<any>} - An ordered array of positional argument values.
166
131
  */
167
- export function list(data, as = "ul", escapeFn = (t) => t) {
168
- const nodes = parseList(data);
169
- if (!Array.isArray(nodes) || nodes.length === 0) return "";
170
-
171
- const tag = as === "ol" ? "ol" : "ul";
172
-
173
- const renderItems = items => {
174
- let html = `<${tag}>`;
175
- for (const item of items) {
176
- html += `<li>`;
177
- html += escapeFn(item.text);
178
- if (item.children && item.children.length > 0) {
179
- html += renderItems(item.children);
180
- }
181
- html += `</li>`;
182
- }
183
- html += `</${tag}>`;
184
- return html;
185
- };
132
+ export function getPositionalArgs(args) {
133
+ if (!args) return [];
134
+ const keys = Object.keys(args);
135
+ const result = keys
136
+ .filter(k => !isNaN(parseInt(k)))
137
+ .sort((a, b) => parseInt(a) - parseInt(b))
138
+ .map(k => args[k]);
139
+
140
+ return result;
141
+ }
186
142
 
187
- return renderItems(nodes);
143
+ /**
144
+ * Calculates the Levenshtein distance between two strings.
145
+ * Used for "Did you mean?" suggestions and fuzzy matching in validation.
146
+ *
147
+ * @param {string} a - First string.
148
+ * @param {string} b - Second string.
149
+ * @returns {number} - The edit distance between the two strings.
150
+ */
151
+ export function levenshtein(a, b) {
152
+ const matrix = [];
153
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
154
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
155
+
156
+ for (let i = 1; i <= b.length; i++) {
157
+ for (let j = 1; j <= a.length; j++) {
158
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
159
+ matrix[i][j] = matrix[i - 1][j - 1];
160
+ } else {
161
+ matrix[i][j] = Math.min(
162
+ matrix[i - 1][j - 1] + 1,
163
+ matrix[i][j - 1] + 1,
164
+ matrix[i - 1][j] + 1
165
+ );
166
+ }
167
+ }
168
+ }
169
+ return matrix[b.length][a.length];
188
170
  }