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 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
- ```javascript
258
- import { Mapper } from "sommark";
259
- const myMapper = new Mapper();
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
- required: ["id"] // The "id" key is required
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
- *Example input that passes:* `[User = Adam, id:123] ... [end]`
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
 
@@ -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
- // 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.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": {