sommark 4.5.2 → 5.0.0

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 (42) hide show
  1. package/README.md +314 -178
  2. package/cli/cli.mjs +1 -1
  3. package/cli/commands/color.js +36 -14
  4. package/cli/commands/help.js +3 -0
  5. package/cli/commands/init.js +0 -2
  6. package/cli/constants.js +5 -2
  7. package/constants/html_props.js +66 -1
  8. package/constants/svg_elements.js +31 -0
  9. package/core/errors.js +5 -4
  10. package/core/evaluator.js +1 -2
  11. package/core/formats.js +7 -1
  12. package/core/helpers/config-loader.js +1 -3
  13. package/core/helpers/lib.js +1 -1
  14. package/core/labels.js +2 -15
  15. package/core/lexer.js +197 -313
  16. package/core/modules.js +13 -13
  17. package/core/parser.js +226 -535
  18. package/core/tokenTypes.js +6 -15
  19. package/core/transpiler.js +129 -110
  20. package/core/validator.js +6 -26
  21. package/dist/sommark.browser.js +1939 -2223
  22. package/dist/sommark.browser.lite.js +1937 -2220
  23. package/dist/sommark.lexer.js +392 -544
  24. package/dist/sommark.parser.js +604 -1200
  25. package/formatter/mark.js +34 -0
  26. package/formatter/tag.js +7 -33
  27. package/helpers/utils.js +15 -16
  28. package/index.js +9 -1
  29. package/index.shared.js +22 -12
  30. package/mappers/languages/csv.js +62 -0
  31. package/mappers/languages/html.js +21 -69
  32. package/mappers/languages/json.js +74 -156
  33. package/mappers/languages/jsonc.js +21 -63
  34. package/mappers/languages/markdown.js +159 -276
  35. package/mappers/languages/mdx.js +7 -62
  36. package/mappers/languages/text.js +2 -19
  37. package/mappers/languages/toml.js +231 -0
  38. package/mappers/languages/xml.js +25 -25
  39. package/mappers/languages/yaml.js +323 -0
  40. package/mappers/mapper.js +1 -22
  41. package/mappers/shared/index.js +3 -16
  42. package/package.json +5 -2
@@ -1,185 +1,103 @@
1
1
  import Mapper from "../mapper.js";
2
- import { getPositionalArgs, matchedValue, safeArg } from "../../helpers/utils.js";
2
+ import { getPositionalArgs, safeArg } from "../../helpers/utils.js";
3
+ import { transpilerError } from "../../core/errors.js";
3
4
 
4
- /**
5
- * JSON Mapper - Creates JSON output.
6
- * It manages the structure manually using 'handleAst: true'.
7
- */
5
+ const ITEM_SEP = "\x1F";
8
6
 
9
- /**
10
- * Returns a string representing the specified indentation level.
11
- */
12
7
  export function getIndent(depth) {
13
8
  return " ".repeat(depth);
14
9
  }
15
10
 
16
- /**
17
- * Escapes a string for use in a JSON property or value.
18
- * @param {string} str - The string to escape.
19
- * @param {boolean} [trim=false] - Whether to trim the string.
20
- */
21
11
  export function escapeString(str, trim = false) {
22
12
  let out = String(str);
23
13
  if (trim) out = out.trim();
24
14
  return JSON.stringify(out);
25
15
  }
26
16
 
27
- import evaluator from "../../core/evaluator.js";
28
-
29
- /**
30
- * Recursively extracts text content from a node, ignoring structural metadata.
31
- * Evaluates StaticLogic nodes using the Evaluator to inject build-time values.
32
- */
33
- async function getNodeText(node) {
34
- if (!node.body) return "";
35
- let text = "";
36
- for (const child of node.body) {
37
- if (child.type === "Text") text += child.text || "";
38
- else if (child.type === "StaticLogic") {
39
- try {
40
- const result = await evaluator.execute(child.code);
41
- if (result !== undefined && typeof result !== "object") {
42
- text += String(result);
43
- }
44
- } catch (err) {
45
- console.error(`\x1b[31mLogic Error in JSON mapper:\x1b[0m ${err.message}`);
46
- console.error(`\x1b[33mCode:\x1b[0m \x1b[34m${child.code}\x1b[0m`);
47
- }
48
- }
49
- else if (child.type === "Block") text += await getNodeText(child);
50
- }
51
- return text;
52
- }
53
-
54
- /**
55
- * Resolves the key-value pairing for a JSON member.
56
- */
57
- export function renderMember(args, value, inArray = false) {
58
- if (inArray) return value;
59
-
60
- const posArgs = getPositionalArgs(args);
61
- const key = args.key || posArgs[0]; // The 'key' rule determines the member name
62
-
63
- if (key) {
64
- return `${escapeString(key)}: ${value}`;
65
- }
17
+ export function renderMember(props, value, inArray = false) {
18
+ if (inArray) return value + ITEM_SEP;
19
+ const posArgs = getPositionalArgs(props);
20
+ const key = props.key || posArgs[0];
21
+ if (key) return `${escapeString(key)}: ${value}` + ITEM_SEP;
66
22
  return value;
67
23
  }
68
24
 
69
- /**
70
- * Formats a given node and tracks its indentation.
71
- */
72
- export async function renderNode(node, mapper, depth = 0, inArray = false) {
73
- const target = matchedValue(mapper.outputs, node.id) || mapper.getUnknownTag(node);
74
- if (!target) return "";
75
-
76
- evaluator.pushScope();
77
- const textContent = await getNodeText(node);
78
- const output = await target.render.call(mapper, {
79
- nodeType: node.type,
80
- args: node.args,
81
- content: "",
82
- textContent,
83
- ast: node,
84
- depth,
85
- inArray
86
- });
87
- await evaluator.popScope();
88
- return output;
89
- }
90
-
91
- /**
92
- * Formats the children of a node into a neat list.
93
- */
94
- export async function renderChildren(node, mapper, depth = 0, inArray = false) {
95
- let results = [];
96
- const childIndent = getIndent(depth + 1);
25
+ const Json = Mapper.define({});
97
26
 
98
- for (const child of node.body) {
99
- if (child.type === "Block") {
100
- const output = await renderNode(child, mapper, depth + 1, inArray);
101
- if (output) {
102
- results.push({ type: "Block", value: childIndent + output });
103
- }
104
- }
27
+ Json.register(["Object", "object"], async function ({ props, ast, depth = 0, inArray = false, renderChild }) {
28
+ let combined = "";
29
+ for (const child of ast.body) {
30
+ const out = await renderChild(child, { depth: depth + 1, inArray: false });
31
+ if (out) combined += out;
105
32
  }
106
-
107
- let finalOutput = "";
108
- for (let i = 0; i < results.length; i++) {
109
- const current = results[i];
110
- finalOutput += current.value;
111
-
112
- if (current.type === "Block") {
113
- // Add comma if there is another Block later
114
- let hasNextBlock = false;
115
- for (let j = i + 1; j < results.length; j++) {
116
- if (results[j].type === "Block") {
117
- hasNextBlock = true;
118
- break;
119
- }
120
- }
121
- if (hasNextBlock) finalOutput += ",";
122
- }
123
-
124
- if (i < results.length - 1) {
125
- finalOutput += "\n";
126
- }
33
+ const parts = combined.split(ITEM_SEP).map(v => v.trim()).filter(v => v !== "");
34
+ const value = parts.length === 0
35
+ ? "{}"
36
+ : `{\n${parts.map(p => getIndent(depth + 1) + p).join(",\n")}\n${getIndent(depth)}}`;
37
+ return renderMember(props, value, inArray);
38
+ }, { handleAst: true });
39
+
40
+ Json.register(["Array", "array"], async function ({ props, ast, depth = 0, inArray = false, renderChild }) {
41
+ let combined = "";
42
+ for (const child of ast.body) {
43
+ const out = await renderChild(child, { depth: depth + 1, inArray: true });
44
+ if (out) combined += out;
127
45
  }
128
- return finalOutput;
129
- }
130
-
131
- const Json = Mapper.define({});
132
-
133
- /**
134
- * The JSON object node rule.
135
- */
136
- Json.register(["Object", "object"], async ({ args, ast, depth = 0, inArray = false }) => {
137
- if (ast.body.length === 0) return renderMember(args, "{}", inArray);
138
- const content = await renderChildren(ast, Json, depth, false);
139
- const val = `{\n${content}\n${getIndent(depth)}}`;
140
- return renderMember(args, val, inArray);
141
- }, { type: "Block", handleAst: true });
142
-
143
- /**
144
- * The JSON array node rule.
145
- */
146
- Json.register(["Array", "array"], async ({ args, ast, depth = 0, inArray = false }) => {
147
- if (ast.body.length === 0) return renderMember(args, "[]", inArray);
148
- const content = await renderChildren(ast, Json, depth, true);
149
- const val = `[\n${content}\n${getIndent(depth)}]`;
150
- return renderMember(args, val, inArray);
151
- }, { type: "Block", handleAst: true });
152
-
153
- /**
154
- * JSON Primitives
155
- */
156
- Json.register("string", ({ args, textContent, inArray }) => {
46
+ const parts = combined.split(ITEM_SEP).map(v => v.trim()).filter(v => v !== "");
47
+ const value = parts.length === 0
48
+ ? "[]"
49
+ : `[\n${parts.map(p => getIndent(depth + 1) + p).join(",\n")}\n${getIndent(depth)}]`;
50
+ return renderMember(props, value, inArray);
51
+ }, { handleAst: true });
52
+
53
+ Json.register(["string", "str"], ({ props, textContent, inArray }) => {
157
54
  const trim = safeArg({
158
- args,
55
+ props,
159
56
  key: "trim",
160
57
  type: "boolean",
161
58
  setType: v => v === "true" || v === true,
162
59
  fallBack: false
163
60
  });
164
- const raw = safeArg({ args, index: inArray ? 0 : undefined, key: "value", fallBack: textContent });
165
- const val = escapeString(raw, trim);
166
- return renderMember(args, val, inArray);
167
- }, { type: "Block", handleAst: true });
61
+ const raw = safeArg({ props, index: inArray ? 0 : undefined, key: "value", fallBack: textContent });
62
+ return renderMember(props, escapeString(raw, trim), inArray);
63
+ }, { handleAst: true });
168
64
 
169
- Json.register("number", ({ args, textContent, inArray }) => {
170
- const raw = String(safeArg({ args, index: inArray ? 0 : undefined, key: "value", fallBack: textContent })).trim();
65
+ Json.register("number", ({ props, textContent, inArray }) => {
66
+ const raw = String(safeArg({ props, index: inArray ? 0 : undefined, key: "value", fallBack: textContent })).trim();
171
67
  const val = (isNaN(Number(raw)) || raw === "") ? "0" : raw;
172
- return renderMember(args, val, inArray);
173
- }, { type: "Block", handleAst: true });
174
-
175
- Json.register("bool", ({ args, textContent, inArray }) => {
176
- const raw = String(safeArg({ args, index: inArray ? 0 : undefined, key: "value", fallBack: textContent })).trim().toLowerCase();
177
- const val = (raw === "true" || raw === "1") ? "true" : "false";
178
- return renderMember(args, val, inArray);
179
- }, { type: "Block", handleAst: true });
180
-
181
- Json.register("null", ({ args, inArray }) => {
182
- return renderMember(args, "null", inArray);
183
- }, { type: "Block", handleAst: true });
68
+ return renderMember(props, val, inArray);
69
+ }, { handleAst: true });
70
+
71
+ Json.register("bool", ({ props, textContent, inArray }) => {
72
+ const raw = String(safeArg({ props, index: inArray ? 0 : undefined, key: "value", fallBack: textContent })).trim().toLowerCase();
73
+ return renderMember(props, (raw === "true" || raw === "1") ? "true" : "false", inArray);
74
+ }, { handleAst: true });
75
+
76
+ Json.register("null", ({ props, inArray }) => {
77
+ return renderMember(props, "null", inArray);
78
+ }, { handleAst: true });
79
+
80
+ Json.getUnknownTag = function (node) {
81
+ const key = node.id;
82
+ return {
83
+ render({ props, textContent, inArray = false }) {
84
+ if (inArray) {
85
+ transpilerError(
86
+ `Unknown tag '<$yellow:[${key}]$>' cannot be used inside <$yellow:[Array]$>.{N}Use <$cyan:[string]$>, <$cyan:[number]$>, <$cyan:[bool]$>, or <$cyan:[null]$> instead.`
87
+ );
88
+ }
89
+ const raw = String(
90
+ safeArg({ props, index: 0, key: "value", fallBack: textContent.trim() })
91
+ ).trim();
92
+ let val;
93
+ if (raw === "null") val = "null";
94
+ else if (raw === "true" || raw === "false") val = raw;
95
+ else if (raw !== "" && !isNaN(Number(raw))) val = raw;
96
+ else val = escapeString(raw);
97
+ return `${escapeString(key)}: ${val}` + ITEM_SEP;
98
+ },
99
+ options: { handleAst: true }
100
+ };
101
+ };
184
102
 
185
103
  export default Json;
@@ -1,54 +1,6 @@
1
- import Json, { renderNode, getIndent, renderMember } from "./json.js";
1
+ import Json, { getIndent, renderMember } from "./json.js";
2
2
 
3
- /**
4
- * JSONC Mapper - Creates JSON output with comments.
5
- * It inherits from the standard JSON mapper and adds comment support.
6
- */
7
-
8
- async function renderChildren(node, mapper, depth = 0, inArray = false) {
9
- let results = [];
10
- const childIndent = getIndent(depth + 1);
11
-
12
- for (const child of node.body) {
13
- if (child.type === "Block") {
14
- const output = await renderNode(child, mapper, depth + 1, inArray);
15
- if (output) {
16
- results.push({ type: "Block", value: childIndent + output });
17
- }
18
- } else if (child.type === "Comment") {
19
- if (!mapper.options?.removeComments) {
20
- results.push({ type: "Comment", value: childIndent + mapper.comment(child.text) });
21
- }
22
- } else if (child.type === "CommentBlock") {
23
- if (!mapper.options?.removeComments) {
24
- results.push({ type: "CommentBlock", value: childIndent + mapper.commentBlock(child.text, childIndent) });
25
- }
26
- }
27
- }
28
-
29
- let finalOutput = "";
30
- for (let i = 0; i < results.length; i++) {
31
- const current = results[i];
32
- finalOutput += current.value;
33
-
34
- if (current.type === "Block") {
35
- // Add comma if there is another Block later
36
- let hasNextBlock = false;
37
- for (let j = i + 1; j < results.length; j++) {
38
- if (results[j].type === "Block") {
39
- hasNextBlock = true;
40
- break;
41
- }
42
- }
43
- if (hasNextBlock) finalOutput += ",";
44
- }
45
-
46
- if (i < results.length - 1) {
47
- finalOutput += "\n";
48
- }
49
- }
50
- return finalOutput;
51
- }
3
+ const ITEM_SEP = "\x1F";
52
4
 
53
5
  const Jsonc = Json.clone();
54
6
 
@@ -64,19 +16,25 @@ Jsonc.commentBlock = function (text, indent = " ") {
64
16
  return `/* ${text} */`;
65
17
  };
66
18
 
67
- // Re-register Object and Array to use the new renderChildren logic
68
- Jsonc.register(["Object", "object"], async function ({ args, ast, depth = 0, inArray = false }) {
69
- if (ast.body.length === 0) return renderMember(args, "{}", inArray);
70
- const content = await renderChildren(ast, this, depth, false);
71
- const val = `{\n${content}\n${getIndent(depth)}}`;
72
- return renderMember(args, val, inArray);
73
- }, { type: "Block", handleAst: true });
19
+ async function renderStructure(props, ast, depth, inArray, childInArray, openBracket, closeBracket, renderChild) {
20
+ let combined = "";
21
+ for (const child of ast.body) {
22
+ const out = await renderChild(child, { depth: depth + 1, inArray: childInArray });
23
+ if (out) combined += out;
24
+ }
25
+ const parts = combined.split(ITEM_SEP).map(v => v.trim()).filter(v => v !== "");
26
+ const value = parts.length === 0
27
+ ? `${openBracket}${closeBracket}`
28
+ : `${openBracket}\n${parts.map(p => getIndent(depth + 1) + p).join(",\n")}\n${getIndent(depth)}${closeBracket}`;
29
+ return renderMember(props, value, inArray);
30
+ }
31
+
32
+ Jsonc.register(["Object", "object"], async function ({ props, ast, depth = 0, inArray = false, renderChild }) {
33
+ return renderStructure(props, ast, depth, inArray, false, "{", "}", renderChild);
34
+ }, { handleAst: true });
74
35
 
75
- Jsonc.register(["Array", "array"], async function ({ args, ast, depth = 0, inArray = false }) {
76
- if (ast.body.length === 0) return renderMember(args, "[]", inArray);
77
- const content = await renderChildren(ast, this, depth, true);
78
- const val = `[\n${content}\n${getIndent(depth)}]`;
79
- return renderMember(args, val, inArray);
80
- }, { type: "Block", handleAst: true });
36
+ Jsonc.register(["Array", "array"], async function ({ props, ast, depth = 0, inArray = false, renderChild }) {
37
+ return renderStructure(props, ast, depth, inArray, true, "[", "]", renderChild);
38
+ }, { handleAst: true });
81
39
 
82
40
  export default Jsonc;