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.
- package/README.md +98 -82
- package/assets/logo.json +28 -0
- package/assets/smark.logo.png +0 -0
- package/assets/smark.logo.svg +21 -0
- package/cli/cli.mjs +7 -17
- package/cli/commands/build.js +26 -6
- package/cli/commands/color.js +22 -26
- package/cli/commands/help.js +10 -10
- package/cli/commands/init.js +20 -31
- package/cli/commands/print.js +18 -16
- package/cli/commands/show.js +4 -0
- package/cli/commands/version.js +6 -0
- package/cli/constants.js +9 -5
- package/cli/helpers/config.js +11 -0
- package/cli/helpers/file.js +17 -6
- package/cli/helpers/transpile.js +15 -17
- package/core/errors.js +49 -25
- package/core/formats.js +7 -3
- package/core/formatter.js +215 -0
- package/core/helpers/config-loader.js +40 -75
- package/core/labels.js +21 -9
- package/core/lexer.js +491 -212
- package/core/modules.js +164 -0
- package/core/parser.js +516 -389
- package/core/tokenTypes.js +36 -1
- package/core/transpiler.js +238 -154
- package/core/validator.js +79 -0
- package/formatter/mark.js +203 -43
- package/formatter/tag.js +202 -32
- package/grammar.ebnf +57 -50
- package/helpers/colorize.js +26 -13
- package/helpers/dedent.js +19 -0
- package/helpers/escapeHTML.js +13 -6
- package/helpers/kebabize.js +6 -0
- package/helpers/peek.js +9 -0
- package/helpers/removeChar.js +26 -13
- package/helpers/safeDataParser.js +114 -0
- package/helpers/utils.js +140 -158
- package/index.js +186 -188
- package/mappers/languages/html.js +105 -213
- package/mappers/languages/json.js +122 -171
- package/mappers/languages/markdown.js +355 -108
- package/mappers/languages/mdx.js +76 -120
- package/mappers/languages/xml.js +114 -0
- package/mappers/mapper.js +152 -123
- package/mappers/shared/index.js +22 -0
- package/package.json +26 -6
- package/SOMMARK-SPEC.md +0 -481
- package/cli/commands/list.js +0 -124
- package/constants/html_tags.js +0 -146
- package/core/pluginManager.js +0 -149
- package/core/plugins/comment-remover.js +0 -47
- package/core/plugins/module-system.js +0 -176
- package/core/plugins/raw-content-plugin.js +0 -78
- package/core/plugins/rules-validation-plugin.js +0 -231
- package/core/plugins/sommark-format.js +0 -244
- package/coverage_test.js +0 -21
- package/debug.js +0 -15
- package/helpers/camelize.js +0 -2
- package/helpers/defaultTheme.js +0 -3
- package/test_format_fix.js +0 -42
- package/v3-todo.smark +0 -73
package/constants/html_tags.js
DELETED
|
@@ -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
|
-
]);
|
package/core/pluginManager.js
DELETED
|
@@ -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;
|