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
@@ -1,146 +0,0 @@
1
- export const HTML_TAGS = new Set([
2
- // Main root
3
- "html",
4
-
5
- // Document metadata
6
- "base",
7
- "head",
8
- "link",
9
- "meta",
10
- "style",
11
- "title",
12
-
13
- // Sectioning root
14
- "body",
15
-
16
- // Content sectioning
17
- "address",
18
- "article",
19
- "aside",
20
- "footer",
21
- "header",
22
- "h1",
23
- "h2",
24
- "h3",
25
- "h4",
26
- "h5",
27
- "h6",
28
- "hgroup",
29
- "main",
30
- "nav",
31
- "section",
32
- "search",
33
-
34
- // Text content
35
- "blockquote",
36
- "dd",
37
- "div",
38
- "dl",
39
- "dt",
40
- "figcaption",
41
- "figure",
42
- "hr",
43
- "li",
44
- "menu",
45
- "ol",
46
- "p",
47
- "pre",
48
- "ul",
49
-
50
- // Inline text semantics
51
- "a",
52
- "abbr",
53
- "b",
54
- "bdi",
55
- "bdo",
56
- "br",
57
- "cite",
58
- "code",
59
- "data",
60
- "dfn",
61
- "em",
62
- "i",
63
- "kbd",
64
- "mark",
65
- "q",
66
- "rp",
67
- "rt",
68
- "ruby",
69
- "s",
70
- "samp",
71
- "small",
72
- "span",
73
- "strong",
74
- "sub",
75
- "sup",
76
- "time",
77
- "u",
78
- "var",
79
- "wbr",
80
-
81
- // Image and multimedia
82
- "area",
83
- "audio",
84
- "img",
85
- "map",
86
- "track",
87
- "video",
88
-
89
- // Embedded content
90
- "embed",
91
- "iframe",
92
- "object",
93
- "picture",
94
- "portal",
95
- "source",
96
-
97
- // SVG and MathML
98
- "svg",
99
- "math",
100
-
101
- // Scripting
102
- "canvas",
103
- "noscript",
104
- "script",
105
-
106
- // Demarcating edits
107
- "del",
108
- "ins",
109
-
110
- // Table content
111
- "caption",
112
- "col",
113
- "colgroup",
114
- "table",
115
- "tbody",
116
- "td",
117
- "tfoot",
118
- "th",
119
- "thead",
120
- "tr",
121
-
122
- // Forms
123
- "button",
124
- "datalist",
125
- "fieldset",
126
- "form",
127
- "input",
128
- "label",
129
- "legend",
130
- "meter",
131
- "optgroup",
132
- "option",
133
- "output",
134
- "progress",
135
- "select",
136
- "textarea",
137
-
138
- // Interactive elements
139
- "details",
140
- "dialog",
141
- "summary",
142
-
143
- // Web Components
144
- "slot",
145
- "template"
146
- ]);
@@ -1,149 +0,0 @@
1
- /**
2
- * Plugin Manager
3
- * Handles sorting by priority and running hooks at various stages.
4
- */
5
- export default class PluginManager {
6
- constructor(plugins = [], priority = []) {
7
- // ========================================================================== //
8
- // Plugin Sorting by Priority //
9
- // ========================================================================== //
10
- this.plugins = [...plugins].sort((a, b) => {
11
- const getIndex = (p) => {
12
- const index = priority.findIndex(item => {
13
- if (typeof item === "string") {
14
- return item === p.name;
15
- }
16
- return item === p;
17
- });
18
- return index;
19
- };
20
-
21
- const indexA = getIndex(a);
22
- const indexB = getIndex(b);
23
-
24
- if (indexA !== -1 && indexB !== -1) return indexA - indexB;
25
- if (indexA !== -1) return -1;
26
- if (indexB !== -1) return 1;
27
-
28
- // Default rule: built-ins first, then external/user-defined
29
- const isBuiltInA = ["module-system", "raw-content", "comment-remover", "rules-validation", "sommark-format"].includes(a.name);
30
- const isBuiltInB = ["module-system", "raw-content", "comment-remover", "rules-validation", "sommark-format"].includes(b.name);
31
-
32
- if (isBuiltInA && !isBuiltInB) return -1;
33
- if (!isBuiltInA && isBuiltInB) return 1;
34
-
35
- return 0;
36
- });
37
- }
38
-
39
- // ========================================================================== //
40
- // Preprocessing Hooks (Before Lexing) //
41
- // ========================================================================== //
42
- async runPreprocessor(src, scope, context) {
43
- let processedSrc = src;
44
- const preprocessors = this.plugins.filter(p => {
45
- const types = Array.isArray(p.type) ? p.type : [p.type];
46
- return types.includes("preprocessor") && (p.scope === scope || !p.scope);
47
- });
48
-
49
- for (const plugin of preprocessors) {
50
- if (typeof plugin.beforeLex === "function") {
51
- plugin.context = context;
52
- processedSrc = await plugin.beforeLex.call(plugin, processedSrc);
53
- }
54
- }
55
- return processedSrc;
56
- }
57
-
58
- // ========================================================================== //
59
- // Post-Lexing Hooks (Token Transformation) //
60
- // ========================================================================== //
61
- async runAfterLex(tokens) {
62
- let processedTokens = tokens;
63
- const afterLexers = this.plugins.filter(p => {
64
- const types = Array.isArray(p.type) ? p.type : [p.type];
65
- return (types.includes("lexer") || types.includes("after-lexer")) && typeof p.afterLex === "function";
66
- });
67
-
68
- for (const plugin of afterLexers) {
69
- processedTokens = await plugin.afterLex.call(plugin, processedTokens);
70
- }
71
- return processedTokens;
72
- }
73
-
74
- // ========================================================================== //
75
- // AST Transformation Hooks (After Parsing) //
76
- // ========================================================================== //
77
- async runOnAst(ast, context = {}) {
78
- let processedAst = ast;
79
- const astPlugins = this.plugins.filter(p => {
80
- const types = Array.isArray(p.type) ? p.type : [p.type];
81
- return (types.includes("parser") || types.includes("on-ast")) && typeof p.onAst === "function";
82
- });
83
-
84
- for (const plugin of astPlugins) {
85
- processedAst = await plugin.onAst.call(plugin, processedAst, context);
86
- }
87
- return processedAst;
88
- }
89
-
90
- // ========================================================================== //
91
- // Mapper Extensions (Tag Definitions and Rules) //
92
- // ========================================================================== //
93
- getMapperExtensions() {
94
- const extensions = { outputs: [], rules: {} };
95
- const mapperPlugins = this.plugins.filter(p => {
96
- const types = Array.isArray(p.type) ? p.type : [p.type];
97
- return types.includes("mapper");
98
- });
99
-
100
- for (const plugin of mapperPlugins) {
101
- if (plugin.outputs) {
102
- extensions.outputs.push(...plugin.outputs);
103
- }
104
- if (plugin.rules) {
105
- extensions.rules = { ...extensions.rules, ...plugin.rules };
106
- }
107
- }
108
- return extensions;
109
- }
110
-
111
- // ========================================================================== //
112
- // Registration Hooks //
113
- // ========================================================================== //
114
- runRegisterHooks(sm) {
115
- for (const plugin of this.plugins) {
116
- const registerFn = plugin.registerOutput || plugin.register;
117
- if (typeof registerFn === "function") {
118
- registerFn.call(plugin, sm);
119
- }
120
- }
121
- }
122
-
123
- // ========================================================================== //
124
- // Post-Processing Hooks (Final Output Transformation) //
125
- // ========================================================================== //
126
- async runTransformers(output) {
127
- let processedOutput = output;
128
- const transformers = this.plugins.filter(p => {
129
- const types = Array.isArray(p.type) ? p.type : [p.type];
130
- return types.includes("transform") || types.includes("postprocessor");
131
- });
132
-
133
- for (const plugin of transformers) {
134
- const transformFn = plugin.transform || plugin.afterTranspile;
135
- if (typeof transformFn === "function") {
136
- processedOutput = await transformFn.call(plugin, processedOutput);
137
- }
138
- }
139
- return processedOutput;
140
- }
141
-
142
- // ========================================================================== //
143
- // Format-Specific Mapper Retrieval //
144
- // ========================================================================== //
145
- getFormatMapper(formatName) {
146
- const plugin = this.plugins.find(p => p.format === formatName && p.mapper);
147
- return plugin ? plugin.mapper : null;
148
- }
149
- }
@@ -1,47 +0,0 @@
1
- import { COMMENT } from "../labels.js";
2
-
3
- /**
4
- * Comment Remover Plugin
5
- * Removes all comments from the document so they don't appear in the final output.
6
- */
7
- export default {
8
- name: "comment-remover",
9
- type: "on-ast",
10
- author: "Adam-Elmi",
11
- description: "Removes all comments from the document so they don't appear in the final output.",
12
- onAst: function (ast) {
13
- // ========================================================================== //
14
- // Recursive function to filter out comments //
15
- // ========================================================================== //
16
- const cleanNodes = (nodes) => {
17
- if (!Array.isArray(nodes)) return nodes;
18
-
19
- return nodes
20
- .filter(node => node.type !== COMMENT)
21
- .map(node => {
22
- if (node.body && Array.isArray(node.body)) {
23
- return {
24
- ...node,
25
- body: cleanNodes(node.body)
26
- };
27
- }
28
- return node;
29
- });
30
- };
31
- // ========================================================================== //
32
- // Handle both root array and individual node objects //
33
- // ========================================================================== //
34
- if (Array.isArray(ast)) {
35
- return cleanNodes(ast);
36
- }
37
-
38
- if (ast && ast.body) {
39
- return {
40
- ...ast,
41
- body: cleanNodes(ast.body)
42
- };
43
- }
44
-
45
- return ast;
46
- }
47
- };
@@ -1,176 +0,0 @@
1
- import fs from "node:fs";
2
- import fsPromises from "node:fs/promises";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { runtimeError } from "../errors.js";
6
-
7
- const normalizePath = (filename) => {
8
- if (!filename || filename === "anonymous") return process.cwd();
9
- if (filename.startsWith("file://")) {
10
- try {
11
- return fileURLToPath(filename);
12
- } catch (e) {
13
- return filename;
14
- }
15
- }
16
- return path.resolve(process.cwd(), filename);
17
- };
18
-
19
- /**
20
- * Module System Plugin
21
- * Allows you to import and use content from other SomMark, CSS, or JS files.
22
- */
23
- const ModuleSystem = {
24
- name: "module-system",
25
- type: ["preprocessor", "on-ast"],
26
- author: "Adam-Elmi",
27
- description: "Allows you to import and use content from other SomMark, CSS, or JS files.",
28
- scope: "top-level",
29
- // ========================================================================== //
30
- // 1. Preprocessing phase (before lexing) //
31
- // ========================================================================== //
32
- beforeLex(src) {
33
- const importRegex = /\[import\s*=\s*([^:]+)\s*:\s*(?:"([^"]+)"|'([^']+)')\s*\]\[end\]/g;
34
- const usageRegex = /\[\$use-module\s*=\s*([^\]]+)\]\[end\]/g;
35
- const filename = this.context?.filename || "anonymous";
36
- const absFilename = normalizePath(filename);
37
- const baseDir = filename === "anonymous" ? absFilename : path.dirname(absFilename);
38
-
39
- const localModules = new Map();
40
-
41
- // ========================================================================== //
42
- // 1. Collect all module imports //
43
- // ========================================================================== //
44
- let match;
45
- while ((match = importRegex.exec(src)) !== null) {
46
- const alias = match[1].trim();
47
- const pathValue = (match[2] || match[3]).trim();
48
- const ext = path.extname(pathValue).slice(1);
49
- localModules.set(alias, { path: pathValue, ext });
50
- }
51
-
52
- // ========================================================================== //
53
- // 2. Substitute usages for non-smark files //
54
- // ========================================================================== //
55
- let processed = src.replace(usageRegex, (match, alias) => {
56
- alias = alias.trim();
57
- if (localModules.has(alias)) {
58
- const mod = localModules.get(alias);
59
- if (mod.ext !== "smark") {
60
- const absPath = path.resolve(baseDir, mod.path);
61
- if (fs.existsSync(absPath)) {
62
- return fs.readFileSync(absPath, "utf-8");
63
- }
64
- }
65
- }
66
- return match;
67
- });
68
-
69
- // ========================================================================== //
70
- // 3. Remove non-smark import declarations //
71
- // ========================================================================== //
72
- processed = processed.replace(importRegex, (match, alias, p1, p2) => {
73
- const pathValue = p1 || p2;
74
- const ext = path.extname(pathValue).slice(1);
75
- if (ext !== "smark") return "";
76
- return match;
77
- });
78
-
79
- return processed;
80
- },
81
- // ========================================================================== //
82
- // 2. AST Transformation phase //
83
- // ========================================================================== //
84
- async onAst(ast, context) {
85
- const modules = new Map();
86
- const options = this.options || {};
87
- const supportedExtensions = options.supportedExtensions || ["smark", "css", "js"];
88
- const filename = context.filename || "anonymous";
89
- const absFilename = normalizePath(filename);
90
- const baseDir = filename === "anonymous" ? absFilename : path.dirname(absFilename);
91
-
92
- // ========================================================================== //
93
- // Helper to recursively process nodes //
94
- // ========================================================================== //
95
- const processNodes = async (nodes, baseDir) => {
96
- for (let i = 0; i < nodes.length; i++) {
97
- const node = nodes[i];
98
-
99
- if (node.type === "Import") {
100
- // ========================================================================== //
101
- // Handle Import Node: [import = alias: "path"] //
102
- // ========================================================================== //
103
- const alias = Object.keys(node.args).find(k => isNaN(k));
104
- let filePath = alias ? node.args[alias] : node.args[0];
105
- if (typeof filePath === "string") {
106
- filePath = filePath.trim().replace(/^["']|["']$/g, "");
107
- }
108
-
109
- const absolutePath = path.resolve(baseDir, filePath);
110
-
111
- if (!fs.existsSync(absolutePath)) {
112
- runtimeError([`<$red:Module Path Error:$> File not found: <$magenta:${filePath}$> at line <$yellow:${node.range.start.line + 1}$>`]);
113
- }
114
-
115
- // ========================================================================== //
116
- // Validate Extension //
117
- // ========================================================================== //
118
- const ext = path.extname(absolutePath).slice(1);
119
- if (!supportedExtensions.includes(ext)) {
120
- runtimeError([`<$red:Module Extension Error:$> Unsupported extension .${ext} for file ${filePath}`]);
121
- }
122
-
123
- modules.set(alias, { path: absolutePath, type: ext });
124
-
125
- // ========================================================================== //
126
- // Remove import node from AST //
127
- // ========================================================================== //
128
- nodes.splice(i, 1);
129
- i--;
130
- } else if (node.type === "$use-module") {
131
- // 2. Handle Usage Node: [$use-module = alias]
132
- const alias = node.args[0];
133
- if (!alias || !modules.has(alias)) {
134
- runtimeError([`<$red:Module Usage Error:$> Undefined module alias <$magenta:${alias}$> at line <$yellow:${node.range.start.line + 1}$>`]);
135
- }
136
-
137
- const mod = modules.get(alias);
138
- if (mod.type === "smark") {
139
- // ========================================================================== //
140
- // Recursive Parse the sub-file AST //
141
- // ========================================================================== //
142
- const content = fs.readFileSync(mod.path, "utf-8");
143
- const SomMark = context.instance.constructor;
144
- const subSmark = new SomMark({
145
- src: content,
146
- format: context.format,
147
- filename: mod.path,
148
- plugins: context.instance.plugins,
149
- priority: context.instance.priority,
150
- includeDocument: false
151
- });
152
-
153
- // ========================================================================== //
154
- // Parse the sub-file AST //
155
- // ========================================================================== //
156
- const subAst = await subSmark.parse();
157
-
158
- // ========================================================================== //
159
- // Splice results into current body //
160
- // ========================================================================== //
161
- nodes.splice(i, 1, ...subAst);
162
- i += subAst.length - 1;
163
- }
164
- } else if (node.body && Array.isArray(node.body)) {
165
- await processNodes(node.body, baseDir);
166
- }
167
- }
168
- };
169
-
170
- await processNodes(ast, baseDir);
171
- return ast;
172
- }
173
- };
174
-
175
-
176
- export default ModuleSystem;
@@ -1,78 +0,0 @@
1
- /**
2
- * Raw Content Plugin
3
- * Lets you write raw code or text inside special blocks without it being processed by SomMark.
4
- */
5
- const RawContentPlugin = {
6
- name: "raw-content",
7
- type: ["preprocessor", "on-ast"],
8
- author: "Adam-Elmi",
9
- description: "Lets you write raw code or text inside special blocks without it being processed by SomMark.",
10
- scope: "top-level",
11
- options: {
12
- // ========================================================================== //
13
- // Target blocks to treat as raw //
14
- // ========================================================================== //
15
- targetBlocks: ["mdx", "raw"]
16
- },
17
- // ========================================================================== //
18
- // 1. Preprocessing phase (escaping raw blocks) //
19
- // ========================================================================== //
20
- beforeLex(src) {
21
- let processed = src;
22
- const options = this.options || {};
23
- const targetBlocks = options.targetBlocks || ["mdx", "raw"];
24
-
25
- targetBlocks.forEach(tag => {
26
- const regex = new RegExp(`\\[${tag}([^ \\]]*|\\s*=[^\\]]*)?\\]([\\s\\S]*?)\\[end\\]`, "g");
27
- processed = processed.replace(regex, (match, argsPart, content) => {
28
- const escapedContent = content
29
- .replace(/\\/g, "\\\\")
30
- .replace(/\[/g, "\\[")
31
- .replace(/\]/g, "\\]")
32
- .replace(/@_/g, "\\@_")
33
- .replace(/_@/g, "\\_@")
34
- .replace(/={1}/g, "\\=")
35
- .replace(/:/g, "\\:")
36
- .replace(/#/g, "\\#")
37
- .replace(/,/g, "\\,")
38
- .replace(/\(/g, "\\(")
39
- .replace(/\)/g, "\\)")
40
- .replace(/ /g, "\u200B ")
41
- .replace(/\t/g, "\u200B\t")
42
- .replace(/\n/g, "\u200B\n")
43
- .replace(/\r/g, "\u200B\r");
44
- return `[${tag}${argsPart || ""}]${escapedContent}[end]`;
45
- });
46
- });
47
- return processed;
48
- },
49
- // ========================================================================== //
50
- // 2. AST Transformation phase (unescaping whitespace) //
51
- // ========================================================================== //
52
- onAst(ast) {
53
- const options = this.options || {};
54
- const targetBlocks = (options.targetBlocks || ["mdx", "raw", "code"]).map(t => t.toLowerCase());
55
-
56
- const processNodes = (nodes) => {
57
- if (!Array.isArray(nodes)) return;
58
- nodes.forEach(node => {
59
- if (node.type === "Block" && targetBlocks.includes(node.id.toLowerCase())) {
60
- if (node.body) {
61
- node.body.forEach(child => {
62
- if (child.type === "Text") {
63
- child.text = child.text.replace(/\u200B/g, "");
64
- }
65
- });
66
- }
67
- }
68
- if (node.body) processNodes(node.body);
69
- });
70
- };
71
-
72
- const root = Array.isArray(ast) ? ast : [ast];
73
- processNodes(root);
74
- return ast;
75
- }
76
- };
77
-
78
- export default RawContentPlugin;