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.
- 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 +24 -4
- 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 +7 -12
- package/core/errors.js +49 -25
- package/core/formats.js +7 -3
- package/core/formatter.js +215 -0
- package/core/helpers/config-loader.js +29 -74
- 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 +237 -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/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 +198 -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
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { transpilerError } from "../errors.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Rules Validation Plugin
|
|
5
|
-
* Validates rules defined in mapper files.
|
|
6
|
-
* Supports rules for arguments (key, value) and content.
|
|
7
|
-
*/
|
|
8
|
-
const RulesValidationPlugin = {
|
|
9
|
-
name: "rules-validation",
|
|
10
|
-
type: "on-ast",
|
|
11
|
-
author: "Adam-Elmi",
|
|
12
|
-
description: "Checks your document to make sure all tags and arguments follow the rules set in the mapper.",
|
|
13
|
-
onAst(ast, { mapperFile, instance }) {
|
|
14
|
-
if (!mapperFile) return ast;
|
|
15
|
-
|
|
16
|
-
const validateNode = (node, parentTarget = null) => {
|
|
17
|
-
if (!node) return;
|
|
18
|
-
|
|
19
|
-
// ========================================================================== //
|
|
20
|
-
// 1. TEXT nodes validation //
|
|
21
|
-
// ========================================================================== //
|
|
22
|
-
if (node.type === "Text" && parentTarget) {
|
|
23
|
-
this.runValidations(node, parentTarget, null, node.text, "Text", mapperFile, instance);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ========================================================================== //
|
|
27
|
-
// 2. Identifier nodes validation (Block, Inline, AtBlock) //
|
|
28
|
-
// ========================================================================== //
|
|
29
|
-
if (node.id) {
|
|
30
|
-
const target = mapperFile.get(node.id);
|
|
31
|
-
if (target) {
|
|
32
|
-
this.runValidations(node, target, node.args, this.getContent(node), node.type, mapperFile, instance);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ========================================================================== //
|
|
37
|
-
// Recursive traversal //
|
|
38
|
-
// ========================================================================== //
|
|
39
|
-
if (node.body && Array.isArray(node.body)) {
|
|
40
|
-
const currentTarget = node.id ? mapperFile.get(node.id) : parentTarget;
|
|
41
|
-
node.body.forEach(child => validateNode(child, currentTarget));
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const root = Array.isArray(ast) ? ast : [ast];
|
|
46
|
-
root.forEach(node => validateNode(node));
|
|
47
|
-
|
|
48
|
-
return ast;
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
getContent(node) {
|
|
52
|
-
if (node.type === "Inline") return node.value;
|
|
53
|
-
if (node.type === "AtBlock") return node.content;
|
|
54
|
-
return "";
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
runValidations(node, target, args, content, type, mapperFile, instance) {
|
|
58
|
-
if (!target.options) return;
|
|
59
|
-
const rules = target.options.rules || {};
|
|
60
|
-
const id = Array.isArray(target.id) ? target.id.join(" | ") : target.id;
|
|
61
|
-
const context = instance ? { src: instance.src, range: node.range, filename: instance.filename } : null;
|
|
62
|
-
|
|
63
|
-
// ========================================================================== //
|
|
64
|
-
// 1. Validate Args Count & Keys //
|
|
65
|
-
// ========================================================================== //
|
|
66
|
-
if (args && rules.args) {
|
|
67
|
-
const { min, max, required, includes } = rules.args;
|
|
68
|
-
const argKeys = Object.keys(args).filter(key => isNaN(parseInt(key)));
|
|
69
|
-
const argCount = args.length;
|
|
70
|
-
|
|
71
|
-
if (min !== undefined && argCount < min) {
|
|
72
|
-
transpilerError(
|
|
73
|
-
[
|
|
74
|
-
"<$red:Validation Error:$> ",
|
|
75
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:requires at least$> <$green:${min}$> <$yellow:argument(s). Found$> <$red:${argCount}$>`
|
|
76
|
-
],
|
|
77
|
-
context
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
if (max !== undefined && argCount > max) {
|
|
81
|
-
transpilerError(
|
|
82
|
-
[
|
|
83
|
-
"<$red:Validation Error:$> ",
|
|
84
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:accepts at most$> <$green:${max}$> <$yellow:argument(s). Found$> <$red:${argCount}$>`
|
|
85
|
-
],
|
|
86
|
-
context
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
if (required && Array.isArray(required)) {
|
|
90
|
-
const missingKeys = required.filter(key => !Object.prototype.hasOwnProperty.call(args, key));
|
|
91
|
-
if (missingKeys.length > 0) {
|
|
92
|
-
transpilerError(
|
|
93
|
-
[
|
|
94
|
-
"<$red:Validation Error:$> ",
|
|
95
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:is missing required argument(s):$> <$red:${missingKeys.join(", ")}$>`
|
|
96
|
-
],
|
|
97
|
-
context
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (includes && Array.isArray(includes)) {
|
|
102
|
-
const invalidKeys = argKeys.filter(key => {
|
|
103
|
-
return !includes.includes(key) && !mapperFile.extraProps.has(key);
|
|
104
|
-
});
|
|
105
|
-
if (invalidKeys.length > 0) {
|
|
106
|
-
transpilerError(
|
|
107
|
-
[
|
|
108
|
-
"<$red:Validation Error:$> ",
|
|
109
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:contains invalid argument key(s):$> <$red:${invalidKeys.join(", ")}$>`,
|
|
110
|
-
`{N}<$yellow:Allowed keys are:$> <$green:${includes.join(", ")}$>`
|
|
111
|
-
],
|
|
112
|
-
context
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ========================================================================== //
|
|
119
|
-
// 2. Validation on Keys and Values //
|
|
120
|
-
// ========================================================================== //
|
|
121
|
-
if (args) {
|
|
122
|
-
const argKeys = Object.keys(args).filter(key => isNaN(parseInt(key)));
|
|
123
|
-
|
|
124
|
-
// Validate keys pattern
|
|
125
|
-
if (rules.keys) {
|
|
126
|
-
const keyPattern = rules.keys instanceof RegExp ? rules.keys : null;
|
|
127
|
-
if (keyPattern) {
|
|
128
|
-
const invalidKeys = argKeys.filter(key => !keyPattern.test(key));
|
|
129
|
-
if (invalidKeys.length > 0) {
|
|
130
|
-
transpilerError(
|
|
131
|
-
[
|
|
132
|
-
"<$red:Validation Error:$> ",
|
|
133
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:contains argument keys that do not match pattern $> <$green:${keyPattern.toString()}$>: <$red:${invalidKeys.join(", ")}$>`
|
|
134
|
-
],
|
|
135
|
-
context
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ========================================================================== //
|
|
142
|
-
// Validate specific values //
|
|
143
|
-
// ========================================================================== //
|
|
144
|
-
if (rules.values) {
|
|
145
|
-
for (const [key, value] of Object.entries(args)) {
|
|
146
|
-
if (isNaN(parseInt(key))) {
|
|
147
|
-
const valueRule = rules.values[key];
|
|
148
|
-
if (valueRule) {
|
|
149
|
-
if (valueRule instanceof RegExp && !valueRule.test(value)) {
|
|
150
|
-
transpilerError(
|
|
151
|
-
[
|
|
152
|
-
"<$red:Validation Error:$> ",
|
|
153
|
-
`<$yellow:Argument key$> <$blue:'${key}'$> <$yellow:in$> <$blue:'${id}'$> <$yellow:has invalid value:$> <$red:'${value}'$>{N}<$yellow:Expected to match pattern:$> <$green:${valueRule.toString()}$>`
|
|
154
|
-
],
|
|
155
|
-
context
|
|
156
|
-
);
|
|
157
|
-
} else if (typeof valueRule === "function" && !valueRule(value)) {
|
|
158
|
-
transpilerError(
|
|
159
|
-
[
|
|
160
|
-
"<$red:Validation Error:$> ",
|
|
161
|
-
`<$yellow:Argument key$> <$blue:'${key}'$> <$yellow:in$> <$blue:'${id}'$> <$yellow:failed custom validation for value:$> <$red:'${value}'$>`
|
|
162
|
-
],
|
|
163
|
-
context
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ========================================================================== //
|
|
173
|
-
// 3. Content Validation //
|
|
174
|
-
// ========================================================================== //
|
|
175
|
-
if (content !== undefined && rules.content) {
|
|
176
|
-
const { maxLength, match } = rules.content;
|
|
177
|
-
if (maxLength && content.length > maxLength) {
|
|
178
|
-
transpilerError(
|
|
179
|
-
[
|
|
180
|
-
"<$red:Validation Error:$> ",
|
|
181
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:content exceeds maximum length of$> <$green:${maxLength}$> <$yellow:characters. Found$> <$red:${content.length}$>`
|
|
182
|
-
],
|
|
183
|
-
context
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
if (match && match instanceof RegExp && !match.test(content)) {
|
|
187
|
-
transpilerError(
|
|
188
|
-
[
|
|
189
|
-
"<$red:Validation Error:$> ",
|
|
190
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:content does not match required pattern:$> <$green:${match.toString()}$>`
|
|
191
|
-
],
|
|
192
|
-
context
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ========================================================================== //
|
|
198
|
-
// 4. self-closing //
|
|
199
|
-
// ========================================================================== //
|
|
200
|
-
if (rules.is_self_closing && (type === "Block" || content)) {
|
|
201
|
-
if (content) {
|
|
202
|
-
transpilerError(
|
|
203
|
-
[
|
|
204
|
-
"<$red:Validation Error:$> ",
|
|
205
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:is self-closing tag and is not allowed to have a content | children$>`
|
|
206
|
-
],
|
|
207
|
-
context
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ========================================================================== //
|
|
213
|
-
// 5. Type Validation (Block, Inline, AtBlock) //
|
|
214
|
-
// ========================================================================== //
|
|
215
|
-
const typeToValidate = target.options.type;
|
|
216
|
-
if (typeToValidate && type !== "Text") {
|
|
217
|
-
const allowedTypes = Array.isArray(typeToValidate) ? typeToValidate : [typeToValidate];
|
|
218
|
-
if (!allowedTypes.includes("any") && !allowedTypes.includes(type)) {
|
|
219
|
-
transpilerError(
|
|
220
|
-
[
|
|
221
|
-
"<$red:Validation Error:$> ",
|
|
222
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:is expected to be type$> <$green:'${allowedTypes.join(" | ")}'$>{N}<$cyan:Received type: $> <$magenta:'${type}'$>`
|
|
223
|
-
],
|
|
224
|
-
context
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
export default RulesValidationPlugin;
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SomMark Format Plugin
|
|
3
|
-
* Cleans up and formats your SomMark code into a neat and readable style.
|
|
4
|
-
*/
|
|
5
|
-
export default {
|
|
6
|
-
name: "sommark-format",
|
|
7
|
-
type: "on-ast",
|
|
8
|
-
author: "Adam-Elmi",
|
|
9
|
-
description: "Cleans up and formats your SomMark code into a neat and readable style.",
|
|
10
|
-
options: {
|
|
11
|
-
indentString: "\t"
|
|
12
|
-
},
|
|
13
|
-
onAst(ast) {
|
|
14
|
-
const indentStr = this.options?.indentString || "\t";
|
|
15
|
-
// ========================================================================== //
|
|
16
|
-
// 1. Escaping Helpers //
|
|
17
|
-
// ========================================================================== //
|
|
18
|
-
|
|
19
|
-
const escapeArg = (val, type) => {
|
|
20
|
-
let escaped = String(val)
|
|
21
|
-
.replace(/\\/g, "\\\\")
|
|
22
|
-
.replace(/,/g, "\\,");
|
|
23
|
-
|
|
24
|
-
if (type === "Block" || type === "AtBlock") {
|
|
25
|
-
escaped = escaped.replace(/:/g, "\\:");
|
|
26
|
-
}
|
|
27
|
-
if (type === "AtBlock") {
|
|
28
|
-
escaped = escaped.replace(/;/g, "\\;");
|
|
29
|
-
}
|
|
30
|
-
if (type === "Block" && escaped.startsWith("=")) {
|
|
31
|
-
escaped = escaped.replace(/^=/, "\\=");
|
|
32
|
-
}
|
|
33
|
-
return escaped;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const escapeInlineValue = (val) => {
|
|
37
|
-
return String(val)
|
|
38
|
-
.replace(/\\/g, "\\\\")
|
|
39
|
-
.replace(/\)/g, "\\)");
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const escapeText = (str) => {
|
|
43
|
-
return String(str)
|
|
44
|
-
.replace(/\\/g, "\\\\")
|
|
45
|
-
.replace(/\[/g, "\\[")
|
|
46
|
-
.replace(/\]/g, "\\]")
|
|
47
|
-
.replace(/\(/g, "\\(")
|
|
48
|
-
.replace(/\)/g, "\\)")
|
|
49
|
-
.replace(/->/g, "\\->")
|
|
50
|
-
.replace(/@_/g, "\\@_")
|
|
51
|
-
.replace(/_@/g, "\\_@")
|
|
52
|
-
.replace(/#/g, "\\#");
|
|
53
|
-
};
|
|
54
|
-
// ========================================================================== //
|
|
55
|
-
// 2. Formatting Logic //
|
|
56
|
-
// ========================================================================== //
|
|
57
|
-
const shouldQuote = (val) => {
|
|
58
|
-
if (typeof val !== "string") return false;
|
|
59
|
-
return /[ \t\n\r,:[\]()@#]/.test(val);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const formatArgs = (args, type) => {
|
|
63
|
-
if (!args || args.length === 0) return "";
|
|
64
|
-
let usedKeys = new Set();
|
|
65
|
-
let formattedArgs = [];
|
|
66
|
-
|
|
67
|
-
for (let i = 0; i < args.length; i++) {
|
|
68
|
-
let val = args[i];
|
|
69
|
-
let matchedKey = null;
|
|
70
|
-
if (type !== "Inline") {
|
|
71
|
-
for (let key of Object.keys(args)) {
|
|
72
|
-
if (isNaN(parseInt(key)) && args[key] === val && !usedKeys.has(key)) {
|
|
73
|
-
matchedKey = key;
|
|
74
|
-
usedKeys.add(key);
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let escapedVal = escapeArg(val, type);
|
|
81
|
-
if (shouldQuote(val)) {
|
|
82
|
-
const quotedVal = String(val)
|
|
83
|
-
.replace(/\\/g, "\\\\")
|
|
84
|
-
.replace(/"/g, '\\"');
|
|
85
|
-
escapedVal = `"${quotedVal}"`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (matchedKey) {
|
|
89
|
-
formattedArgs.push(`${matchedKey}: ${escapedVal}`);
|
|
90
|
-
} else {
|
|
91
|
-
formattedArgs.push(escapedVal);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let res = formattedArgs.join(", ");
|
|
96
|
-
if (!res) return "";
|
|
97
|
-
|
|
98
|
-
if (type === "Block") return " = " + res;
|
|
99
|
-
if (type === "AtBlock") return ": " + res + ";";
|
|
100
|
-
if (type === "Inline") return ": " + res;
|
|
101
|
-
|
|
102
|
-
return res;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const formatBody = (body, depth) => {
|
|
106
|
-
if (!body || !Array.isArray(body)) return "";
|
|
107
|
-
const innerIndentStr = depth >= 0 ? indentStr.repeat(depth) : "";
|
|
108
|
-
let result = "";
|
|
109
|
-
let currentText = "";
|
|
110
|
-
|
|
111
|
-
const flushText = () => {
|
|
112
|
-
if (!currentText) return;
|
|
113
|
-
let cleanText = currentText
|
|
114
|
-
.replace(/[ \t]+/g, " ") // collapse horizontal spaces
|
|
115
|
-
.replace(/\n([ \t]*\n)+/g, "\n\n") // preserve max 1 empty line (paragraphs)
|
|
116
|
-
.trim();
|
|
117
|
-
|
|
118
|
-
if (cleanText) {
|
|
119
|
-
const indentedText = cleanText.split('\n').map(line => {
|
|
120
|
-
return line.trim() ? innerIndentStr + line.trim() : "";
|
|
121
|
-
}).join('\n');
|
|
122
|
-
result += `${indentedText}\n`;
|
|
123
|
-
}
|
|
124
|
-
currentText = "";
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
for (let i = 0; i < body.length; i++) {
|
|
128
|
-
const child = body[i];
|
|
129
|
-
if (child.type === "Text") {
|
|
130
|
-
let textStr = escapeText(child.text);
|
|
131
|
-
|
|
132
|
-
// ========================================================================== //
|
|
133
|
-
// Separate Text from a preceding Inline statement //
|
|
134
|
-
// ========================================================================== //
|
|
135
|
-
if (i > 0 && body[i - 1].type === "Inline") {
|
|
136
|
-
// Don't add space if the text starts with punctuation or already starts with whitespace
|
|
137
|
-
if (textStr.length > 0 && !/^\s/.test(textStr) && !/^[.,!?;:\])}>"']/.test(textStr)) {
|
|
138
|
-
textStr = " " + textStr;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
currentText += textStr;
|
|
142
|
-
} else if (child.type === "Inline") {
|
|
143
|
-
const argsStr = formatArgs(child.args, "Inline");
|
|
144
|
-
const inlineVal = child.value ? String(child.value).trim() : "";
|
|
145
|
-
const inlineStr = `(${escapeInlineValue(inlineVal)})->(${child.id}${argsStr})`;
|
|
146
|
-
|
|
147
|
-
if (i > 0) {
|
|
148
|
-
const prev = body[i - 1];
|
|
149
|
-
if (prev.type === "Inline") {
|
|
150
|
-
if (!/[ \t\n\r]$/.test(currentText)) currentText += " ";
|
|
151
|
-
} else if (prev.type === "Text") {
|
|
152
|
-
if (currentText.length > 0 && !/[ \t\n\r]$/.test(currentText)) {
|
|
153
|
-
// Don't add space if text ends with opening punctuation/quotes
|
|
154
|
-
if (!/[({\[<"']$/.test(currentText)) currentText += " ";
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
currentText += inlineStr;
|
|
159
|
-
} else {
|
|
160
|
-
// ========================================================================== //
|
|
161
|
-
// Helper: check if a block has no meaningful body content //
|
|
162
|
-
// ========================================================================== //
|
|
163
|
-
const isEmptyBlock = (node) => {
|
|
164
|
-
if (node.type !== "Block") return false;
|
|
165
|
-
if (!node.body || node.body.length === 0) return true;
|
|
166
|
-
// Body with only whitespace text nodes
|
|
167
|
-
return node.body.every(
|
|
168
|
-
n => n.type === "Text" && !n.text.trim()
|
|
169
|
-
);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
if (child.type === "Block" && isEmptyBlock(child)) {
|
|
173
|
-
// Keep empty blocks inline with surrounding text
|
|
174
|
-
const argsStr = formatArgs(child.args, "Block");
|
|
175
|
-
const blockStr = `[${child.id}${argsStr}][end]`;
|
|
176
|
-
|
|
177
|
-
if (i > 0) {
|
|
178
|
-
const prev = body[i - 1];
|
|
179
|
-
if (prev.type === "Text" || prev.type === "Inline") {
|
|
180
|
-
if (currentText.length > 0 && !/[ \t\n\r]$/.test(currentText)) {
|
|
181
|
-
if (!/[({\[<"']$/.test(currentText)) currentText += " ";
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
currentText += blockStr;
|
|
186
|
-
} else {
|
|
187
|
-
// ========================================================================== //
|
|
188
|
-
// Process Block, AtBlock, and Comment nodes //
|
|
189
|
-
// ========================================================================== //
|
|
190
|
-
flushText();
|
|
191
|
-
if (child.type === "Block") {
|
|
192
|
-
const argsStr = formatArgs(child.args, "Block");
|
|
193
|
-
result += `${innerIndentStr}[${child.id}${argsStr}]\n`;
|
|
194
|
-
result += formatBody(child.body, depth + 1);
|
|
195
|
-
result += `${innerIndentStr}[end]\n`;
|
|
196
|
-
} else if (child.type === "AtBlock") {
|
|
197
|
-
const argsStr = formatArgs(child.args, "AtBlock");
|
|
198
|
-
const atHeader = argsStr ? `@_${child.id}_@${argsStr}` : `@_${child.id}_@;`;
|
|
199
|
-
result += `${innerIndentStr}${atHeader}\n`;
|
|
200
|
-
if (child.content) {
|
|
201
|
-
// ========================================================================== //
|
|
202
|
-
// Remove leading spaces from messy text block and re-indent //
|
|
203
|
-
// ========================================================================== //
|
|
204
|
-
const lines = child.content.replace(/\r\n/g, '\n').split('\n');
|
|
205
|
-
while (lines.length && !lines[0].trim()) lines.shift();
|
|
206
|
-
while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
|
|
207
|
-
|
|
208
|
-
let minIndent = Infinity;
|
|
209
|
-
for (const line of lines) {
|
|
210
|
-
if (line.trim()) {
|
|
211
|
-
const leadingSpaces = line.match(/^[ \t]*/)[0].length;
|
|
212
|
-
if (leadingSpaces < minIndent) minIndent = leadingSpaces;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (minIndent === Infinity) minIndent = 0;
|
|
216
|
-
|
|
217
|
-
const indentedContent = lines.map(line => {
|
|
218
|
-
if (!line.trim()) return "";
|
|
219
|
-
return innerIndentStr + indentStr + line.substring(minIndent);
|
|
220
|
-
}).join('\n');
|
|
221
|
-
|
|
222
|
-
result += indentedContent + "\n";
|
|
223
|
-
}
|
|
224
|
-
result += `${innerIndentStr}@_end_@\n`;
|
|
225
|
-
} else if (child.type === "Comment") {
|
|
226
|
-
result += `${innerIndentStr}# ${child.text.replace(/^#+\s*/, "").trim()}\n`;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
flushText();
|
|
232
|
-
return result;
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const rootNodes = Array.isArray(ast) ? ast : [ast];
|
|
236
|
-
|
|
237
|
-
// ========================================================================== //
|
|
238
|
-
// The formatted string is available via `plugin.formattedSource` //
|
|
239
|
-
// ========================================================================== //
|
|
240
|
-
this.formattedSource = formatBody(rootNodes, 0).trim() + "\n";
|
|
241
|
-
|
|
242
|
-
return ast; // Return original AST
|
|
243
|
-
}
|
|
244
|
-
};
|
package/coverage_test.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import lexer from "./core/lexer.js";
|
|
2
|
-
|
|
3
|
-
const src = " [Block]\n content\n [end]";
|
|
4
|
-
console.log(`Source length: ${src.length}`);
|
|
5
|
-
|
|
6
|
-
const tokens = lexer(src);
|
|
7
|
-
let totalCovered = 0;
|
|
8
|
-
|
|
9
|
-
tokens.forEach((t, idx) => {
|
|
10
|
-
if (t.type === "EOF") return;
|
|
11
|
-
const len = t.value.length;
|
|
12
|
-
totalCovered += len;
|
|
13
|
-
console.log(`Token ${idx}: type=${t.type.padEnd(15)} structural=${(!!t.isStructural).toString().padEnd(6)} val=${JSON.stringify(t.value).padEnd(20)} range:(${t.range.start.line},${t.range.start.character}) - (${t.range.end.line},${t.range.end.character})`);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
console.log(`Total covered length: ${totalCovered}`);
|
|
17
|
-
if (totalCovered !== src.length) {
|
|
18
|
-
console.log(`GAP DETECTED: missing ${src.length - totalCovered} characters.`);
|
|
19
|
-
} else {
|
|
20
|
-
console.log("SUCCESS: 100% Coverage.");
|
|
21
|
-
}
|
package/debug.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import SomMark from "./index.js";
|
|
3
|
-
|
|
4
|
-
const buffer = await fs.readFile("./examples/markdown/tasks.smark");
|
|
5
|
-
const file_content = buffer.toString();
|
|
6
|
-
let smark = new SomMark({
|
|
7
|
-
src: file_content,
|
|
8
|
-
format: "markdown",
|
|
9
|
-
includeDocument: true,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// console.log(JSON.stringify(await smark.parse(), null, 2));
|
|
14
|
-
// console.log(await smark.lex());
|
|
15
|
-
console.log(await smark.transpile());
|
package/helpers/camelize.js
DELETED
package/helpers/defaultTheme.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
const atomOneDark = `pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}`;
|
|
2
|
-
|
|
3
|
-
export default atomOneDark;
|
package/test_format_fix.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import SomMark from './index.js';
|
|
2
|
-
|
|
3
|
-
async function test() {
|
|
4
|
-
const text = '[Block = attr: "val with space"]\n Content\n[end]\n';
|
|
5
|
-
const sm = new SomMark({
|
|
6
|
-
src: text,
|
|
7
|
-
format: 'html',
|
|
8
|
-
plugins: ['sommark-format']
|
|
9
|
-
});
|
|
10
|
-
await sm.parse();
|
|
11
|
-
const formatPlugin = sm.plugins.find(p => p.name === 'sommark-format');
|
|
12
|
-
const formatted = formatPlugin.formattedSource;
|
|
13
|
-
console.log("Original:\n", text);
|
|
14
|
-
console.log("Formatted:\n", formatted);
|
|
15
|
-
|
|
16
|
-
if (formatted.includes('"val with space"')) {
|
|
17
|
-
console.log("PASS: Quotes preserved for space-containing value.");
|
|
18
|
-
} else {
|
|
19
|
-
console.error("FAIL: Quotes LOST!");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const text2 = 'Text at top level\n(inline)->(bold)\n@_AtBlock_@;\n content\n@_end_@\n';
|
|
24
|
-
const sm2 = new SomMark({
|
|
25
|
-
src: text2,
|
|
26
|
-
format: 'html',
|
|
27
|
-
plugins: ['sommark-format']
|
|
28
|
-
});
|
|
29
|
-
await sm2.parse();
|
|
30
|
-
const formatted2 = sm2.plugins.find(p => p.name === 'sommark-format').formattedSource;
|
|
31
|
-
console.log("Top-level Original:\n", text2);
|
|
32
|
-
console.log("Top-level Formatted:\n", formatted2);
|
|
33
|
-
|
|
34
|
-
if (formatted2.includes('Text at top level') && formatted2.includes('(inline)->(bold)')) {
|
|
35
|
-
console.log("PASS: Top-level content preserved.");
|
|
36
|
-
} else {
|
|
37
|
-
console.error("FAIL: Top-level content LOST or corrupted!");
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
test();
|
package/v3-todo.smark
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
[Block = v3-todo-list]
|
|
2
|
-
(SomMark v3 Goals)->(h1)
|
|
3
|
-
|
|
4
|
-
# Completed Features & Fixes
|
|
5
|
-
(done)->(todo: Full support for all HTML5 tags)
|
|
6
|
-
(done)->(todo: Full support for Markdown and MDX)
|
|
7
|
-
(done)->(todo: Safe way to override how components look)
|
|
8
|
-
(done)->(todo: Fixed crashes and infinite loops)
|
|
9
|
-
(done)->(todo: Fixed block spacing and new lines)
|
|
10
|
-
(done)->(todo: Allowed using -, _, and $ in names)
|
|
11
|
-
(done)->(todo: Stopped generic tags from breaking custom ones)
|
|
12
|
-
(done)->(todo: Added 'lex' and 'parse' commands to the tool)
|
|
13
|
-
(done)->(todo: Fixed text output errors)
|
|
14
|
-
(done)->(todo: Fixed commas in small inline tags)
|
|
15
|
-
(done)->(todo: Fixed nested tags in examples)
|
|
16
|
-
(done)->(todo: Added CSS variable support like color: --primary)
|
|
17
|
-
(done)->(todo: Added more examples to the guide)
|
|
18
|
-
(done)->(todo: Auto-made ID links for headings)
|
|
19
|
-
(done)->(todo: Fixed the equal sign bug in the reader)
|
|
20
|
-
(done)->(todo: Fixed new line handling for big blocks)
|
|
21
|
-
(done)->(todo: Fixed start-of-line marks in Markdown)
|
|
22
|
-
(done)->(todo: Smart MDX tool for better styles and React)
|
|
23
|
-
(done)->(todo: Better plugin system with isolated parts)
|
|
24
|
-
(done)->(todo: Control plugins with easy 'options' settings)
|
|
25
|
-
(done)->(todo: Better naming rules for $, _, and -)
|
|
26
|
-
(done)->(todo: Made 'end' a special reserved word)
|
|
27
|
-
(done)->(todo: Organized tags into types: Block, Inline, and AtBlock)
|
|
28
|
-
(done)->(todo: Cleaned up internal code property names)
|
|
29
|
-
(done)->(todo: Multi-step plugin pipeline: before, during, after)
|
|
30
|
-
(done)->(todo: Sorted plugins by importance automatically)
|
|
31
|
-
(done)->(todo: Built-in tool to remove comments)
|
|
32
|
-
(done)->(todo: Tool to detect missing end tags)
|
|
33
|
-
(done)->(todo: The command tool now uses the core engine for everything)
|
|
34
|
-
(done)->(todo: Your local settings now take priority)
|
|
35
|
-
(done)->(todo: Added easy ways to add new mappers to the system)
|
|
36
|
-
(done)->(todo: Released Version 3)
|
|
37
|
-
(done)->(todo: Support for custom body placement in templates)
|
|
38
|
-
(done)->(todo: Better foundation for building new mappers)
|
|
39
|
-
(done)->(todo: Cleaner HTML and Markdown code builders)
|
|
40
|
-
(done)->(todo: Improved MDX support for better styles)
|
|
41
|
-
(done)->(todo: Better JSON output support)
|
|
42
|
-
(done)->(todo: Built-in tools: Quotes, Modules, Raw, and Validation)
|
|
43
|
-
(done)->(todo: Better plugin loading and merged settings)
|
|
44
|
-
(done)->(todo: Improved name checker in the reader)
|
|
45
|
-
(done)->(todo: Better cleanup for names and quotes)
|
|
46
|
-
(done)->(todo: Command tool now shares all settings with the core engine)
|
|
47
|
-
(done)->(todo: Cleaned up all old test files)
|
|
48
|
-
(done)->(todo: Added commands to see plugins and how they run)
|
|
49
|
-
(done)->(todo: Added command to see your current settings)
|
|
50
|
-
(done)->(todo: Added command to change tool colors)
|
|
51
|
-
(done)->(todo: Better way to handle unknown HTML tags automatically)
|
|
52
|
-
(done)->(todo: Security fix for template bypass tricks)
|
|
53
|
-
(done)->(todo: Safer and more robust way to read arguments)
|
|
54
|
-
(done)->(todo: Cleaner and more consistent error messages)
|
|
55
|
-
(done)->(todo: Forced semicolons at the start of AtBlocks)
|
|
56
|
-
(done)->(todo: Full guide for all tools and features)
|
|
57
|
-
(done)->(todo: List of all old and removed features)
|
|
58
|
-
(done)->(todo: Removed unused CSS and code pieces)
|
|
59
|
-
(done)->(todo: Standardized HTML comment style)
|
|
60
|
-
(done)->(todo: Moved common tools to a helper file)
|
|
61
|
-
(done)->(todo: Modernized all user documentation)
|
|
62
|
-
(done)->(todo: Created new guides for the plugin system)
|
|
63
|
-
(done)->(todo: Enabled essential tools by default)
|
|
64
|
-
(done)->(todo: Fixed commas in Markdown tables)
|
|
65
|
-
(done)->(todo: Improved the code highlighting system)
|
|
66
|
-
(done)->(todo: Cleaned up old and deprecated style settings)
|
|
67
|
-
(done)->(todo: Fixed the todo list checkmark bug)
|
|
68
|
-
(done)->(todo: Native quote support built into the engine)
|
|
69
|
-
(done)->(todo: Optimized for speed and instant startup)
|
|
70
|
-
(done)->(todo: Fixed structural bugs with nested blocks)
|
|
71
|
-
(done)->(todo: SomMark core now fully supports LSP for code editors)
|
|
72
|
-
(done)->(todo: You can now use Text, Inline tags, and AtBlocks at the top level without a Block)
|
|
73
|
-
[end]
|