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 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
- ```javascript
258
- import { Mapper } from "sommark";
259
- const myMapper = new Mapper();
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
- required: ["id"] // The "id" key is required
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
- *Example input that passes:* `[User = Adam, id:123] ... [end]`
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
 
@@ -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
- // Inject Style Tag if code blocks exist
217
- if (mapperFile.enable_highlightTheme && (output.includes("<pre") || output.includes("<code")) && mapperFile.getStyle) {
218
- const styleContent = mapperFile.getStyle();
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`;
@@ -1,4 +1,4 @@
1
- export async function loadCss(env, filePath) {
1
+ async function loadCss(env, filePath) {
2
2
  if (!env) {
3
3
  env = typeof window !== "undefined" && typeof window.document !== "undefined" ? "browser" : "node";
4
4
  }
@@ -36,13 +36,29 @@ HTML.register("color", ({ args, content }) => {
36
36
  });
37
37
  // Link
38
38
  HTML.register("link", ({ args, content }) => {
39
- console.log(args);
40
- return tag("a").attributes({ href: args[0].trim(), title: args[1] ? args[1].trim() : "" }).body(content);
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("image", ({ args, content }) => {
44
- return tag("img").attributes({ src: args[0].trim(), alt: content }).selfClose();
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("hr", () => {
72
- return tag("hr").selfClose();
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(tag("input").attributes({ type: "checkbox", disabled: true, checked }).selfClose() + (args[0] ? args[0] : ""));
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.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": {