sommark 2.0.2 → 2.1.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/CHANGELOG.md +16 -0
- package/README.md +80 -7
- package/core/transpiler.js +29 -11
- 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,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.1.0 (2026-02-08)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- Fixed styles property to include css styles
|
|
7
|
+
|
|
8
|
+
### Removed
|
|
9
|
+
- Removed `getStyle` method
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
- Added new rule that handles self-closing tags
|
|
13
|
+
|
|
14
|
+
### Improvements
|
|
15
|
+
- Improved documentation
|
|
16
|
+
- Added missing property `enable_table_styles` from `/mappers/mapper.js`
|
|
17
|
+
|
|
18
|
+
|
|
3
19
|
## v2.0.2 (2026-02-03)
|
|
4
20
|
|
|
5
21
|
### 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,36 @@ 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
|
+
```
|
|
96
126
|
|
|
97
127
|
* **Identifiers**: Names can only contain letters and numbers.
|
|
98
128
|
* **Escape Character**: Use the backslash `\` to escape special characters (like colons or commas) inside arguments.
|
|
@@ -254,23 +284,65 @@ export default myMapper;
|
|
|
254
284
|
|
|
255
285
|
You can force strict rules on your content. If a rule is broken, SomMark will stop and show an error.
|
|
256
286
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const { tag } = myMapper;
|
|
287
|
+
### Argument Validation (`args`)
|
|
288
|
+
|
|
289
|
+
Validates the arguments passed to the tag.
|
|
261
290
|
|
|
291
|
+
```javascript
|
|
262
292
|
myMapper.register("User", ({ args }) => {
|
|
263
293
|
return tag("div").body(`User: ${args[0]}`);
|
|
264
294
|
}, {
|
|
265
295
|
rules: {
|
|
266
296
|
args: {
|
|
267
297
|
min: 1, // Must have at least 1 argument
|
|
268
|
-
|
|
298
|
+
max: 3, // Cannot have more than 3 arguments
|
|
299
|
+
required: ["id"], // The "id" named key is required
|
|
300
|
+
includes: ["id", "role", "age"] // Only these keys are allowed
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
- **`min`**: Minimum number of arguments required.
|
|
307
|
+
- **`max`**: Maximum number of arguments allowed.
|
|
308
|
+
- **`required`**: Array of keys that MUST be present in the arguments.
|
|
309
|
+
- **`includes`**: Whitelist of allowed argument keys. Any key not in this list will trigger an error.
|
|
310
|
+
|
|
311
|
+
### Content Validation (`content`)
|
|
312
|
+
|
|
313
|
+
Validates the inner content (body) of the block.
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
myMapper.register("Summary", ({ content }) => {
|
|
317
|
+
return tag("p").body(content);
|
|
318
|
+
}, {
|
|
319
|
+
rules: {
|
|
320
|
+
content: {
|
|
321
|
+
maxLength: 100 // Content must be 100 characters or less
|
|
269
322
|
}
|
|
270
323
|
}
|
|
271
324
|
});
|
|
272
325
|
```
|
|
273
|
-
|
|
326
|
+
|
|
327
|
+
- **`maxLength`**: Maximum length of the content string.
|
|
328
|
+
|
|
329
|
+
### Self-Closing Tags
|
|
330
|
+
|
|
331
|
+
Ensures a tag is used without content or children.
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
myMapper.register("Separator", () => {
|
|
335
|
+
return tag("hr").selfClose();
|
|
336
|
+
}, {
|
|
337
|
+
rules: {
|
|
338
|
+
is_Self_closing: true
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
- **`is_Self_closing`**: If `true`, SomMark will throw an error if the tag contains any content.
|
|
344
|
+
|
|
345
|
+
*Example input that passes:* `[Image = src: image.png, alt: Image][end]`
|
|
274
346
|
|
|
275
347
|
## Using Options
|
|
276
348
|
|
|
@@ -293,6 +365,7 @@ myMapper.register("Code", ({ content }) => {
|
|
|
293
365
|
}
|
|
294
366
|
}); // options
|
|
295
367
|
```
|
|
368
|
+
---
|
|
296
369
|
|
|
297
370
|
# License
|
|
298
371
|
|
package/core/transpiler.js
CHANGED
|
@@ -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
|
}
|
|
@@ -83,7 +83,16 @@ function validateRules(target, args, 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 (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.0
|
|
3
|
+
"version": "2.1.0",
|
|
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": {
|