sommark 3.3.4 → 4.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 (61) 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 +24 -4
  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 +7 -12
  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 +29 -74
  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 +237 -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/escapeHTML.js +13 -6
  33. package/helpers/kebabize.js +6 -0
  34. package/helpers/peek.js +9 -0
  35. package/helpers/removeChar.js +26 -13
  36. package/helpers/safeDataParser.js +114 -0
  37. package/helpers/utils.js +140 -158
  38. package/index.js +198 -188
  39. package/mappers/languages/html.js +105 -213
  40. package/mappers/languages/json.js +122 -171
  41. package/mappers/languages/markdown.js +355 -108
  42. package/mappers/languages/mdx.js +76 -120
  43. package/mappers/languages/xml.js +114 -0
  44. package/mappers/mapper.js +152 -123
  45. package/mappers/shared/index.js +22 -0
  46. package/package.json +26 -6
  47. package/SOMMARK-SPEC.md +0 -481
  48. package/cli/commands/list.js +0 -124
  49. package/constants/html_tags.js +0 -146
  50. package/core/pluginManager.js +0 -149
  51. package/core/plugins/comment-remover.js +0 -47
  52. package/core/plugins/module-system.js +0 -176
  53. package/core/plugins/raw-content-plugin.js +0 -78
  54. package/core/plugins/rules-validation-plugin.js +0 -231
  55. package/core/plugins/sommark-format.js +0 -244
  56. package/coverage_test.js +0 -21
  57. package/debug.js +0 -15
  58. package/helpers/camelize.js +0 -2
  59. package/helpers/defaultTheme.js +0 -3
  60. package/test_format_fix.js +0 -42
  61. package/v3-todo.smark +0 -73
package/grammar.ebnf CHANGED
@@ -1,79 +1,86 @@
1
- (* SomMark EBNF Grammar Definition *)
1
+ (* SomMark V4 EBNF Grammar Specification *)
2
+
3
+ (* ========================================== *)
4
+ (* Basic Lexical Atoms *)
5
+ (* ========================================== *)
2
6
 
3
- (* Basic Tokens *)
4
7
  WhiteSpace = " " | "\t" | "\n" | "\r";
5
8
  Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
6
9
  Letter = "A" | ... | "Z" | "a" | ... | "z";
7
- SpecialChar = "[" | "]" | "(" | ")" | ":" | "-" | ">" | "@" | "_" | "=" | "," | ";";
8
- Identifier = (Letter | Digit | "_" | "$" | "-"), { Letter | Digit | "_" | "$" | "-" };
9
- EscapeChar = "\";
10
10
 
11
- (* General Rules *)
12
- (* 1. Identifiers can include letters, numbers, underscores (_), dollar signs ($), and hyphens (-). *)
13
- (* 2. Semi-colon is mandatory for ending Atblock headers. *)
14
- (* 3. Colon is a separator in Block and Atblock arguments; must be escaped if part of value. *)
11
+ (* Standard Identifiers (A-Z, 0-9, _, -, $) *)
12
+ SimpleID = (Letter | Digit | "_" | "$" | "-"), { Letter | Digit | "_" | "$" | "-" };
13
+
14
+ (* Block Identifiers (Adds support for Colons :) *)
15
+ BlockID = (Letter | Digit | "_" | "$" | "-" | ":"), { Letter | Digit | "_" | "$" | "-" | ":" };
16
+
17
+ EscapeChar = "\", ? any character except WhiteSpace ?;
18
+
19
+ (* "Junk" refers to whitespace and comments which the parser skips in headers *)
20
+ Comment = "#", { ? any character except "\n" ? }, "\n";
21
+ Junk = { WhiteSpace | Comment };
15
22
 
16
- (* Comments *)
17
- Comment = "#", { ? any character except "\n" ? }, "\n";
23
+ (* Prefix Layers for Dynamic Data *)
24
+ PrefixJS = "js{", { ? any character ? }, "}";
25
+ PrefixP = "p{", { ? any character ? }, "}";
26
+ PrefixLayer = PrefixJS | PrefixP;
18
27
 
19
28
  (* ========================================== *)
20
- (* Block Syntax *)
21
- (* Acts as container and allows nesting *)
29
+ (* Block Syntax (Containers) *)
22
30
  (* ========================================== *)
23
31
 
24
- Block = BlockStart, BlockBody, BlockEnd;
25
- BlockStart = "[", Identifier, [ "=", BlockArgs ], "]";
26
- BlockEnd = "[", "end", "]";
27
- BlockBody = { Block | InlineStatement | AtBlock | TextContent };
32
+ Block = BlockOpen, BlockBody, BlockEnd;
28
33
 
29
- (* Block Arguments: Key-Value or Value only *)
30
- BlockArgs = BlockArg, { ",", BlockArg };
31
- BlockArg = [ Key, ":" ], Value;
32
- Key = Identifier;
33
- (* Value accepts anything except restricted chars unless escaped *)
34
- Value = { ? any char except ",", "]" ? | EscapedChar };
35
- EscapedChar = EscapeChar, ? any character ?;
34
+ (* Flexible Header with Junk-Skipping. Blocks allow colons in ID. *)
35
+ BlockOpen = "[", Junk, BlockID, Junk, [ "=", Junk, BlockArgs, Junk ], "]";
36
+ BlockEnd = "[", Junk, "end", Junk, "]";
37
+ BlockBody = { Block | InlineStatement | AtBlock | TextContent | Comment | WhiteSpace };
36
38
 
37
- (* Example: [Identifier = arg1, arg2, three:arg3] *)
38
- (* Example: [Identifier] *)
39
+ (* Block Metadata (Arguments). Block keys allow colons. *)
40
+ BlockArgs = BlockArg, { Junk, ",", Junk, BlockArg };
41
+ BlockArg = [ BlockKey, Junk, ":", Junk ], Value;
39
42
 
43
+ BlockKey = BlockID | QuotedString;
44
+ Value = QuotedString | PrefixLayer | RawValue;
45
+
46
+ QuotedString = ("'" | '"'), { ? any character ? | EscapeChar }, ("'" | '"');
47
+ RawValue = { ? any char except ",", Junk, "]" ? | EscapeChar };
40
48
 
41
49
  (* ========================================== *)
42
50
  (* Inline Statement Syntax *)
43
- (* Does not allow nesting *)
44
51
  (* ========================================== *)
45
52
 
46
- InlineStatement = "(", InlineValue, ")", "->", "(", InlineIdentifier, [ ":", InlineArgs ], ")";
47
- InlineValue = { ? any char except ")" ? | EscapedChar };
48
- InlineIdentifier= Identifier;
49
-
50
- (* Inline Arguments: Value only, NO Key-Value support *)
51
- InlineArgs = InlineArg, { ",", InlineArg };
52
- InlineArg = { ? any char except ",", ")" ? | EscapedChar };
53
+ (* Inlines use SimpleID (NO COLONS allowed in ID) *)
54
+ InlineStatement = "(", InlineContent, ")", Junk, "->", Junk, "(", SimpleID, [ Junk, ":", Junk, InlineArgs ], ")";
53
55
 
54
- (* Example: (Value)->(Identifier: value1, value2) *)
55
- (* Example: (Value)->(Identifier) *)
56
+ (* Content allows balanced parentheses and placeholders *)
57
+ InlineContent = { ? any char except ")" ? | BalancedParen | EscapeChar | PrefixP };
58
+ BalancedParen = "(", InlineContent, ")";
56
59
 
60
+ (* Inline Arguments: Positional Only *)
61
+ InlineArgs = Value, { Junk, ",", Junk, Value };
57
62
 
58
63
  (* ========================================== *)
59
- (* Atblock Syntax *)
60
- (* Content is plain text, no nesting *)
64
+ (* At-Block Syntax (Raw Containers) *)
61
65
  (* ========================================== *)
62
66
 
63
- AtBlock = AtBlockStart, AtBlockContent, AtBlockEnd;
64
- AtBlockStart = "@_", Identifier, "_@", [ ":", AtBlockArgs ], ";";
65
- AtBlockEnd = "@_", "end", "_@";
67
+ AtBlock = AtHeader, AtBody, AtEnd;
68
+
69
+ (* At-Blocks use SimpleID (NO COLONS allowed in ID or Metadata Keys) *)
70
+ AtHeader = "@_", Junk, SimpleID, Junk, "_@", Junk, [ ":", Junk, AtBlockArgs, Junk ], ";";
71
+ AtEnd = "@_", Junk, "end", Junk, "_@";
66
72
 
67
- (* Atblock Arguments: Key-Value or Value only, terminated by semicolon *)
68
- AtBlockArgs = AtBlockArg, { ",", AtBlockArg };
69
- AtBlockArg = [ Key, ":" ], AtBlockValue;
70
- AtBlockValue = { ? any char except ",", ";" ? | EscapedChar };
73
+ AtBlockArgs = AtBlockArg, { Junk, ",", Junk, AtBlockArg };
74
+ AtBlockArg = [ SimpleID, Junk, ":", Junk ], (QuotedString | RawAtValue);
75
+ RawAtValue = { ? any char except ",", ";" ? | EscapeChar };
71
76
 
72
- AtBlockContent = { ? any character ? };
77
+ AtBody = { ? any character ? | EscapeChar };
73
78
 
74
- (* Example: @_Identifier_@: arg1, arg2; Content... @_end_@ *)
75
- (* Example: @_Identifier_@; Content... @_end_@ *)
79
+ (* ========================================== *)
80
+ (* Document Body *)
81
+ (* ========================================== *)
76
82
 
83
+ Document = { Block | InlineStatement | AtBlock | TextContent | Comment | WhiteSpace };
77
84
 
78
- (* Text Content *)
79
- TextContent = { ? any char except "[", "@_", "(", "#" ? | EscapedChar };
85
+ (* Text Content: Plain text with fallback behavior for symbols *)
86
+ TextContent = { ? any char except "[", "@_", "(", "#" ? | EscapeChar | PrefixP };
@@ -1,23 +1,36 @@
1
1
  const colors = {
2
- red: "\x1b[31m",
3
- green: "\x1b[32m",
4
- yellow: "\x1b[33m",
5
- blue: "\x1b[34m",
6
- magenta: "\x1b[35m",
7
- cyan: "\x1b[36m",
8
- reset: "\x1b[0m"
2
+ red: "\x1b[31m",
3
+ green: "\x1b[32m",
4
+ yellow: "\x1b[33m",
5
+ blue: "\x1b[34m",
6
+ magenta: "\x1b[35m",
7
+ cyan: "\x1b[36m",
8
+ reset: "\x1b[0m"
9
9
  };
10
10
 
11
+ /** @type {boolean} If true, the CLI will show colors. */
11
12
  export let useColor = false;
12
13
 
14
+ /**
15
+ * Turns colors on or off globally.
16
+ * @param {boolean} [enabled=true] - Set to true to see colors, false to hide them.
17
+ */
13
18
  export function enableColor(enabled = true) {
14
- useColor = enabled;
19
+ useColor = enabled;
15
20
  }
16
21
 
22
+ /**
23
+ * Wraps your text in a color if colors are turned on.
24
+ *
25
+ * @param {string} color - The color to use (red, green, yellow, blue, magenta, or cyan).
26
+ * @param {string} text - The text you want to color.
27
+ * @returns {string} - The colored text, or plain text if colors are off.
28
+ * @throws {Error} - Fails if you forget to provide the text.
29
+ */
17
30
  export default function colorize(color, text) {
18
- if (!text) throw new Error("argument 'text' is not defined.");
19
- if (useColor && color && colors[color]) {
20
- return colors[color] + text + colors["reset"];
21
- }
22
- return text;
31
+ if (!text) throw new Error("argument 'text' is not defined.");
32
+ if (useColor && color && colors[color]) {
33
+ return colors[color] + text + colors["reset"];
34
+ }
35
+ return text;
23
36
  }
@@ -1,8 +1,15 @@
1
+ /**
2
+ * Makes a string safe to display in HTML by replacing special characters.
3
+ * This helps prevent security issues and formatting errors.
4
+ *
5
+ * @param {string} str - The raw string to escape.
6
+ * @returns {string} - The HTML-safe string.
7
+ */
1
8
  export default function escapeHTML(str) {
2
- return String(str)
3
- .replace(/&/g, "&")
4
- .replace(/</g, "&lt;")
5
- .replace(/>/g, "&gt;")
6
- .replace(/"/g, "&quot;")
7
- .replace(/'/g, "&#39;");
9
+ return String(str)
10
+ .replace(/&/g, "&amp;")
11
+ .replace(/</g, "&lt;")
12
+ .replace(/>/g, "&gt;")
13
+ .replace(/"/g, "&quot;")
14
+ .replace(/'/g, "&#39;");
8
15
  }
@@ -1,2 +1,8 @@
1
+ /**
2
+ * Changes CamelCase or PascalCase names into kebab-case.
3
+ *
4
+ * @param {string} str - The string to convert.
5
+ * @returns {string} - The kebab-cased string.
6
+ */
1
7
  const kebabize = str => str.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
2
8
  export default kebabize;
package/helpers/peek.js CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Looks at an item in a list or string without moving your current position.
3
+ * You can look ahead or behind by using a positive or negative offset.
4
+ *
5
+ * @param {Array|string} input - The list or string to check.
6
+ * @param {number} index - Your current spot in the list.
7
+ * @param {number} offset - How many spots to look ahead or behind.
8
+ * @returns {any|null} - The item you found, or null if it is out of range.
9
+ */
1
10
  function peek(input, index, offset) {
2
11
  if (input === null || index < 0 || offset < -index) {
3
12
  return null;
@@ -1,19 +1,32 @@
1
+ /**
2
+ * Removes or collapses whitespace in a string based on the specified mode.
3
+ *
4
+ * @param {string} text - The source string.
5
+ * @param {'all'|'trim'|'collapse'} [mode="all"] - The whitespace removal strategy.
6
+ * @returns {string} - The processed string.
7
+ */
1
8
  function removeWhiteSpaces(text, mode = "all") {
2
- if (text == null) return "";
3
- text = String(text);
4
- switch (mode) {
5
- case "trim":
6
- return text.trim();
7
- case "collapse":
8
- return text.replace(/\s+/g, " ").trim();
9
- case "all":
10
- default:
11
- return text.replace(/\s+/g, "");
12
- }
9
+ if (text == null) return "";
10
+ text = String(text);
11
+ switch (mode) {
12
+ case "trim":
13
+ return text.trim();
14
+ case "collapse":
15
+ return text.replace(/\s+/g, " ").trim();
16
+ case "all":
17
+ default:
18
+ return text.replace(/\s+/g, "");
19
+ }
13
20
  }
14
21
 
22
+ /**
23
+ * Removes all newline characters (\r, \n) from a string.
24
+ * @param {string} text - The source string.
25
+ * @returns {string} - The processed string without newlines.
26
+ */
15
27
  function removeNewline(text) {
16
- if (text == null) return "";
17
- return String(text).replace(/[\r\n]+/g, "");
28
+ if (text == null) return "";
29
+ return String(text).replace(/[\r\n]+/g, "");
18
30
  }
31
+
19
32
  export { removeWhiteSpaces, removeNewline };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * A safe parser that turns Javascript-like strings into real objects and arrays.
3
+ * It is built to handle data structures without running any dangerous code or
4
+ * accessing other parts of your project.
5
+ *
6
+ * It supports:
7
+ * - Standard JSON: {"key": "val"}
8
+ * - Javascript-style: { key: 'val' }
9
+ * - Basic data: true, false, null, numbers, and strings
10
+ */
11
+ export function safeDataParse(str) {
12
+ if (typeof str !== "string") return str;
13
+ const s = str.trim();
14
+ if (!s) return null;
15
+
16
+ let index = 0;
17
+
18
+ function skipWhitespace() {
19
+ while (index < s.length && /\s/.test(s[index])) {
20
+ index++;
21
+ }
22
+ }
23
+
24
+ function parseValue() {
25
+ skipWhitespace();
26
+ const char = s[index];
27
+
28
+ if (char === '{') return parseObject();
29
+ if (char === '[') return parseArray();
30
+ if (char === '"' || char === "'") return parseString();
31
+
32
+ // Primitives or Unquoted identifiers
33
+ return parsePrimitiveOrIdentifier();
34
+ }
35
+
36
+ function parseString() {
37
+ const quote = s[index++];
38
+ let result = "";
39
+ while (index < s.length && s[index] !== quote) {
40
+ if (s[index] === '\\') index++; // Skip escape
41
+ result += s[index++];
42
+ }
43
+ index++; // Skip closing quote
44
+ return result;
45
+ }
46
+
47
+ function parseObject() {
48
+ index++; // Skip {
49
+ const obj = {};
50
+ skipWhitespace();
51
+
52
+ while (index < s.length && s[index] !== '}') {
53
+ skipWhitespace();
54
+ // Key can be unquoted, quoted "key", or quoted 'key'
55
+ let key;
56
+ if (s[index] === '"' || s[index] === "'") {
57
+ key = parseString();
58
+ } else {
59
+ let keyMatch = s.slice(index).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
60
+ if (!keyMatch) break;
61
+ key = keyMatch[0];
62
+ index += key.length;
63
+ }
64
+
65
+ skipWhitespace();
66
+ if (s[index] !== ':') break;
67
+ index++; // Skip :
68
+
69
+ obj[key] = parseValue();
70
+
71
+ skipWhitespace();
72
+ if (s[index] === ',') index++; // Skip optional comma
73
+ skipWhitespace();
74
+ }
75
+ index++; // Skip }
76
+ return obj;
77
+ }
78
+
79
+ function parseArray() {
80
+ index++; // Skip [
81
+ const arr = [];
82
+ skipWhitespace();
83
+
84
+ while (index < s.length && s[index] !== ']') {
85
+ arr.push(parseValue());
86
+ skipWhitespace();
87
+ if (s[index] === ',') index++; // Skip optional comma
88
+ skipWhitespace();
89
+ }
90
+ index++; // Skip ]
91
+ return arr;
92
+ }
93
+
94
+ function parsePrimitiveOrIdentifier() {
95
+ const start = index;
96
+ while (index < s.length && /[a-zA-Z0-9_$+\-.]/.test(s[index])) {
97
+ index++;
98
+ }
99
+ const token = s.slice(start, index);
100
+
101
+ if (token === "true") return true;
102
+ if (token === "false") return false;
103
+ if (token === "null") return null;
104
+ if (!isNaN(Number(token))) return Number(token);
105
+
106
+ return token; // Fallback to string if it looks like an identifier
107
+ }
108
+
109
+ try {
110
+ return parseValue();
111
+ } catch (e) {
112
+ return str; // Fallback to raw string if parsing fails
113
+ }
114
+ }