sommark 2.0.2 → 2.1.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/CHANGELOG.md +20 -0
- package/README.md +81 -7
- package/core/transpiler.js +31 -13
- package/helpers/loadCss.js +1 -1
- package/mappers/languages/html.js +35 -9
- package/mappers/mapper.js +8 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.1.1 (2026-02-09)
|
|
4
|
+
### Fixes
|
|
5
|
+
- Fixed undefined or null arguments
|
|
6
|
+
|
|
7
|
+
## v2.1.0 (2026-02-08)
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
- Fixed styles property to include css styles
|
|
11
|
+
|
|
12
|
+
### Removed
|
|
13
|
+
- Removed `getStyle` method
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
- Added new rule that handles self-closing tags
|
|
17
|
+
|
|
18
|
+
### Improvements
|
|
19
|
+
- Improved documentation
|
|
20
|
+
- Added missing property `enable_table_styles` from `/mappers/mapper.js`
|
|
21
|
+
|
|
22
|
+
|
|
3
23
|
## v2.0.2 (2026-02-03)
|
|
4
24
|
|
|
5
25
|
### Features
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# SomMark v2
|
|
17
17
|
|
|
18
18
|
> [!WARNING]
|
|
19
|
-
> Old version is no longer supported.
|
|
19
|
+
> Old version(v1) is no longer supported.
|
|
20
20
|
|
|
21
21
|
SomMark provides a way to write structured content that can be converted into other formats like HTML or Markdown. It is different from standard Markdown because it uses explicit syntax for blocks and elements, which makes it easier to process and customize.
|
|
22
22
|
|
|
@@ -93,6 +93,37 @@ Raw content here.
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
## General Rules
|
|
96
|
+
* SomMark Top-Level Rules:
|
|
97
|
+
- Only Blocks and comments are allowed at the top level.
|
|
98
|
+
- Inline Statements, Atblocks, and text cannot appear at the top level. They must be inside a Block, or it is invalid.
|
|
99
|
+
**Invalid Top-Level:**
|
|
100
|
+
```ini
|
|
101
|
+
Hello world ❌ (Text cannot be at top level)
|
|
102
|
+
|
|
103
|
+
Welcome to (SomMark)->(Bold) ❌ (Inline statement cannot be at top level)
|
|
104
|
+
|
|
105
|
+
@_Code_@: js;
|
|
106
|
+
function add(a, b) {
|
|
107
|
+
return a + b;
|
|
108
|
+
}
|
|
109
|
+
@_end_@ ❌ (Atblock cannot be at top level)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Valid Top-Level:**
|
|
113
|
+
```ini
|
|
114
|
+
[Block]
|
|
115
|
+
Hello world
|
|
116
|
+
Welcome to (SomMark)->(Bold) # Inline statement inside block is valid
|
|
117
|
+
|
|
118
|
+
@_Code_@: js;
|
|
119
|
+
function add(a, b) {
|
|
120
|
+
return a + b; # Treated as plain text
|
|
121
|
+
}
|
|
122
|
+
@_end_@
|
|
123
|
+
|
|
124
|
+
[end]
|
|
125
|
+
```
|
|
126
|
+
|
|
96
127
|
|
|
97
128
|
* **Identifiers**: Names can only contain letters and numbers.
|
|
98
129
|
* **Escape Character**: Use the backslash `\` to escape special characters (like colons or commas) inside arguments.
|
|
@@ -254,23 +285,65 @@ export default myMapper;
|
|
|
254
285
|
|
|
255
286
|
You can force strict rules on your content. If a rule is broken, SomMark will stop and show an error.
|
|
256
287
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const { tag } = myMapper;
|
|
288
|
+
### Argument Validation (`args`)
|
|
289
|
+
|
|
290
|
+
Validates the arguments passed to the tag.
|
|
261
291
|
|
|
292
|
+
```javascript
|
|
262
293
|
myMapper.register("User", ({ args }) => {
|
|
263
294
|
return tag("div").body(`User: ${args[0]}`);
|
|
264
295
|
}, {
|
|
265
296
|
rules: {
|
|
266
297
|
args: {
|
|
267
298
|
min: 1, // Must have at least 1 argument
|
|
268
|
-
|
|
299
|
+
max: 3, // Cannot have more than 3 arguments
|
|
300
|
+
required: ["id"], // The "id" named key is required
|
|
301
|
+
includes: ["id", "role", "age"] // Only these keys are allowed
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
- **`min`**: Minimum number of arguments required.
|
|
308
|
+
- **`max`**: Maximum number of arguments allowed.
|
|
309
|
+
- **`required`**: Array of keys that MUST be present in the arguments.
|
|
310
|
+
- **`includes`**: Whitelist of allowed argument keys. Any key not in this list will trigger an error.
|
|
311
|
+
|
|
312
|
+
### Content Validation (`content`)
|
|
313
|
+
|
|
314
|
+
Validates the inner content (body) of the block.
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
myMapper.register("Summary", ({ content }) => {
|
|
318
|
+
return tag("p").body(content);
|
|
319
|
+
}, {
|
|
320
|
+
rules: {
|
|
321
|
+
content: {
|
|
322
|
+
maxLength: 100 // Content must be 100 characters or less
|
|
269
323
|
}
|
|
270
324
|
}
|
|
271
325
|
});
|
|
272
326
|
```
|
|
273
|
-
|
|
327
|
+
|
|
328
|
+
- **`maxLength`**: Maximum length of the content string.
|
|
329
|
+
|
|
330
|
+
### Self-Closing Tags
|
|
331
|
+
|
|
332
|
+
Ensures a tag is used without content or children.
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
myMapper.register("Separator", () => {
|
|
336
|
+
return tag("hr").selfClose();
|
|
337
|
+
}, {
|
|
338
|
+
rules: {
|
|
339
|
+
is_Self_closing: true
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
- **`is_Self_closing`**: If `true`, SomMark will throw an error if the tag contains any content.
|
|
345
|
+
|
|
346
|
+
*Example input that passes:* `[Image = src: image.png, alt: Image][end]`
|
|
274
347
|
|
|
275
348
|
## Using Options
|
|
276
349
|
|
|
@@ -293,6 +366,7 @@ myMapper.register("Code", ({ content }) => {
|
|
|
293
366
|
}
|
|
294
367
|
}); // options
|
|
295
368
|
```
|
|
369
|
+
---
|
|
296
370
|
|
|
297
371
|
# License
|
|
298
372
|
|
package/core/transpiler.js
CHANGED
|
@@ -34,7 +34,7 @@ function validateRules(target, args, content) {
|
|
|
34
34
|
const { id } = target;
|
|
35
35
|
|
|
36
36
|
// Validate Args
|
|
37
|
-
if (rules.args) {
|
|
37
|
+
if (args && rules.args) {
|
|
38
38
|
const { min, max, required, includes } = rules.args;
|
|
39
39
|
const argKeys = Object.keys(args).filter(key => isNaN(parseInt(key))); // Get named keys
|
|
40
40
|
const argValues = Object.values(args);
|
|
@@ -44,14 +44,14 @@ function validateRules(target, args, content) {
|
|
|
44
44
|
if (min && argCount < min) {
|
|
45
45
|
transpilerError([
|
|
46
46
|
"{line}<$red:Validation Error:$> ",
|
|
47
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:requires at least$> <$green:${min}$> <$yellow:argument(s). Found$> <$red:${argCount}$>{line}`
|
|
47
|
+
`<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:requires at least$> <$green:${min}$> <$yellow:argument(s). Found$> <$red:${argCount}$>{line}`
|
|
48
48
|
]);
|
|
49
49
|
}
|
|
50
50
|
// Max Check
|
|
51
51
|
if (max && argCount > max) {
|
|
52
52
|
transpilerError([
|
|
53
53
|
"{line}<$red:Validation Error:$> ",
|
|
54
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:accepts at most$> <$green:${max}$> <$yellow:argument(s). Found$> <$red:${argCount}$>{line}`
|
|
54
|
+
`<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:accepts at most$> <$green:${max}$> <$yellow:argument(s). Found$> <$red:${argCount}$>{line}`
|
|
55
55
|
]);
|
|
56
56
|
}
|
|
57
57
|
// Required Keys Check
|
|
@@ -60,7 +60,7 @@ function validateRules(target, args, content) {
|
|
|
60
60
|
if (missingKeys.length > 0) {
|
|
61
61
|
transpilerError([
|
|
62
62
|
"{line}<$red:Validation Error:$> ",
|
|
63
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:is missing required argument(s):$> <$red:${missingKeys.join(", ")}$>{line}`
|
|
63
|
+
`<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:is missing required argument(s):$> <$red:${missingKeys.join(", ")}$>{line}`
|
|
64
64
|
]);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -70,7 +70,7 @@ function validateRules(target, args, content) {
|
|
|
70
70
|
if (invalidKeys.length > 0) {
|
|
71
71
|
transpilerError([
|
|
72
72
|
"{line}<$red:Validation Error:$> ",
|
|
73
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:contains invalid argument key(s):$> <$red:${invalidKeys.join(", ")}$>`,
|
|
73
|
+
`<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:contains invalid argument key(s):$> <$red:${invalidKeys.join(", ")}$>`,
|
|
74
74
|
`{N}<$yellow:Allowed keys are:$> <$green:${includes.join(", ")}$>{line}`
|
|
75
75
|
]);
|
|
76
76
|
}
|
|
@@ -78,12 +78,21 @@ function validateRules(target, args, content) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Validate Content
|
|
81
|
-
if (rules.content) {
|
|
81
|
+
if (content && rules.content) {
|
|
82
82
|
const { maxLength } = rules.content;
|
|
83
83
|
if (maxLength && content.length > maxLength) {
|
|
84
84
|
transpilerError([
|
|
85
85
|
"{line}<$red:Validation Error:$> ",
|
|
86
|
-
`<$yellow:Identifier$> <$blue:'${id}'$> <$yellow:content exceeds maximum length of$> <$green:${maxLength}$> <$yellow:characters. Found$> <$red:${content.length}$>{line}`
|
|
86
|
+
`<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:content exceeds maximum length of$> <$green:${maxLength}$> <$yellow:characters. Found$> <$red:${content.length}$>{line}`
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Validate is_Self_closing
|
|
91
|
+
if (id && rules.is_Self_closing) {
|
|
92
|
+
if (content) {
|
|
93
|
+
transpilerError([
|
|
94
|
+
"{line}<$red:Validation Error:$> ",
|
|
95
|
+
`<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:is self-closing tag and is not allowed to have a content | children$>{line}`
|
|
87
96
|
]);
|
|
88
97
|
}
|
|
89
98
|
}
|
|
@@ -108,6 +117,7 @@ async function generateOutput(ast, i, format, mapper_file) {
|
|
|
108
117
|
// Text //
|
|
109
118
|
// ========================================================================== //
|
|
110
119
|
case TEXT:
|
|
120
|
+
validateRules(target, body_node.args, body_node.text);
|
|
111
121
|
const shouldEscape = target && target.options && target.options.escape === false ? false : true;
|
|
112
122
|
context +=
|
|
113
123
|
(format === htmlFormat || format === mdxFormat) && shouldEscape ? escapeHTML(body_node.text) : body_node.text;
|
|
@@ -212,16 +222,24 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
|
|
|
212
222
|
}
|
|
213
223
|
if (includeDocument && format === htmlFormat) {
|
|
214
224
|
let finalHeader = mapperFile.header;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (styleContent) {
|
|
220
|
-
const styleTag = `<style>\n${styleContent}\n</style>`;
|
|
225
|
+
let styleContent = "";
|
|
226
|
+
const updateStyleTag = style => {
|
|
227
|
+
if (style) {
|
|
228
|
+
const styleTag = `<style>\n${style}\n</style>`;
|
|
221
229
|
if (!finalHeader.includes(styleTag)) {
|
|
222
230
|
finalHeader += styleTag + "\n";
|
|
223
231
|
}
|
|
224
232
|
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Inject Style Tag if code blocks exist
|
|
236
|
+
if (mapperFile.enable_highlightTheme && (output.includes("<pre") || output.includes("<code"))) {
|
|
237
|
+
mapperFile.addStyle(mapperFile.themes[mapperFile.currentTheme]);
|
|
238
|
+
styleContent = mapperFile.styles.join("\n");
|
|
239
|
+
updateStyleTag(styleContent);
|
|
240
|
+
} else {
|
|
241
|
+
styleContent = mapperFile.styles.join("\n");
|
|
242
|
+
updateStyleTag(styleContent);
|
|
225
243
|
}
|
|
226
244
|
|
|
227
245
|
const document = `<!DOCTYPE html>\n<html>\n${finalHeader}\n<body>\n${output}\n</body>\n</html>\n`;
|
package/helpers/loadCss.js
CHANGED
|
@@ -36,13 +36,29 @@ HTML.register("color", ({ args, content }) => {
|
|
|
36
36
|
});
|
|
37
37
|
// Link
|
|
38
38
|
HTML.register("link", ({ args, content }) => {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
return tag("a")
|
|
40
|
+
.attributes({ href: args[0].trim(), title: args[1] ? args[1].trim() : "" })
|
|
41
|
+
.body(content);
|
|
41
42
|
});
|
|
42
43
|
// Image
|
|
43
|
-
HTML.register(
|
|
44
|
-
|
|
45
|
-
})
|
|
44
|
+
HTML.register(
|
|
45
|
+
["image", "Image"],
|
|
46
|
+
({ args }) => {
|
|
47
|
+
const src = args && args["src"] ? args["src"] : "";
|
|
48
|
+
const alt = args && args["alt"] ? args["alt"] : "";
|
|
49
|
+
const width = args && args["width"] ? args["width"] : "",
|
|
50
|
+
height = args && args["height"] ? args["height"] : "";
|
|
51
|
+
return tag("img").attributes({ src, alt, width, height }).selfClose();
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
rules: {
|
|
55
|
+
is_Self_closing: true,
|
|
56
|
+
args: {
|
|
57
|
+
required: ["src"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
);
|
|
46
62
|
// Code
|
|
47
63
|
HTML.register(
|
|
48
64
|
["code", "Code"],
|
|
@@ -68,13 +84,23 @@ HTML.register(
|
|
|
68
84
|
{ escape: false }
|
|
69
85
|
);
|
|
70
86
|
// Horizontal Rule
|
|
71
|
-
HTML.register(
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
HTML.register(
|
|
88
|
+
"hr",
|
|
89
|
+
() => {
|
|
90
|
+
return tag("hr").selfClose();
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
rules: {
|
|
94
|
+
is_Self_closing: true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
);
|
|
74
98
|
// Todo
|
|
75
99
|
HTML.register("todo", ({ args, content }) => {
|
|
76
100
|
const checked = HTML.todo(content);
|
|
77
|
-
return tag("div").body(
|
|
101
|
+
return tag("div").body(
|
|
102
|
+
tag("input").attributes({ type: "checkbox", disabled: true, checked }).selfClose() + (args[0] ? args[0] : "")
|
|
103
|
+
);
|
|
78
104
|
});
|
|
79
105
|
|
|
80
106
|
export default HTML;
|
package/mappers/mapper.js
CHANGED
|
@@ -3,6 +3,7 @@ import MarkdownBuilder from "../formatter/mark.js";
|
|
|
3
3
|
import { highlightCode } from "../lib/highlight.js";
|
|
4
4
|
import escapeHTML from "../helpers/escapeHTML.js";
|
|
5
5
|
import atomOneDark from "../helpers/defaultTheme.js";
|
|
6
|
+
import loadCss from "../helpers/loadCss.js";
|
|
6
7
|
|
|
7
8
|
class Mapper {
|
|
8
9
|
#customHeaderContent;
|
|
@@ -31,12 +32,12 @@ class Mapper {
|
|
|
31
32
|
this.escapeHTML = escapeHTML;
|
|
32
33
|
this.styles = [];
|
|
33
34
|
this.env = "node";
|
|
34
|
-
|
|
35
35
|
// Theme Registry
|
|
36
36
|
this.themes = {
|
|
37
37
|
"atom-one-dark": atomOneDark
|
|
38
38
|
};
|
|
39
39
|
this.currentTheme = "atom-one-dark";
|
|
40
|
+
this.enable_table_styles = true;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
registerHighlightTheme(themes) {
|
|
@@ -51,17 +52,10 @@ class Mapper {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
getStyle() {
|
|
55
|
-
const themeCss = this.themes[this.currentTheme] || "";
|
|
56
|
-
const customCss = this.styles.join("\n");
|
|
57
|
-
return (themeCss + "\n" + customCss).trim();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
55
|
// ========================================================================== //
|
|
61
56
|
// Style Management //
|
|
62
57
|
// ========================================================================== //
|
|
63
58
|
|
|
64
|
-
|
|
65
59
|
addStyle(css) {
|
|
66
60
|
if (typeof css === "object" && css !== null) {
|
|
67
61
|
let styleString = "";
|
|
@@ -80,6 +74,11 @@ class Mapper {
|
|
|
80
74
|
}
|
|
81
75
|
}
|
|
82
76
|
|
|
77
|
+
loadCss = async (env = this.env, filePath) => {
|
|
78
|
+
const css = await loadCss(env, filePath);
|
|
79
|
+
this.addStyle(css);
|
|
80
|
+
};
|
|
81
|
+
|
|
83
82
|
// ========================================================================== //
|
|
84
83
|
// Header Generation //
|
|
85
84
|
// ========================================================================== //
|
|
@@ -311,7 +310,7 @@ class Mapper {
|
|
|
311
310
|
}
|
|
312
311
|
};
|
|
313
312
|
todo(checked = false) {
|
|
314
|
-
return checked.trim() === "x" ? true : false;
|
|
313
|
+
return checked.trim() === "x" || checked.trim().toLowerCase() === "done" ? true : false;
|
|
315
314
|
}
|
|
316
315
|
}
|
|
317
316
|
export default Mapper;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sommark",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "SomMark is a structural markup language for writing structured documents and converting them into HTML or Markdown or MDX(only ready components).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|