sommark 2.2.0 → 2.3.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,25 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## v2.3.0 (2026-02-23)
5
+
6
+ ### Added
7
+
8
+ * Added missing JSON support in the CLI.
9
+ * Added two new methods: makeFrontmatter and raw_js_imports.
10
+ * Added new documentation.
11
+
12
+ ### Fixed
13
+
14
+ * Fixed MDX format output error caused by extra whitespaces.
15
+ * Fixed colon issue in atblock body.
16
+
17
+ ### Improved
18
+
19
+ * Improved several internal methods.
20
+ * Updated and enhanced tests.
21
+
22
+
3
23
  ## v2.2.0 (2026-02-23)
4
24
  ## Features
5
25
  - Added JSON support
package/README.md CHANGED
@@ -5,15 +5,37 @@ SomMark is a declarative, extensible markup language for structured content that
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
+ <!--License-->
9
+ <a href="https://www.npmjs.com/package/sommark" target="_blank">
8
10
  <img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" />
11
+ </a>
12
+
13
+ <!--Npm Version-->
14
+ <a href="https://www.npmjs.com/package/sommark" target="_blank">
9
15
  <img src="https://img.shields.io/npm/v/sommark?style=flat-square" />
10
- <img src="https://img.shields.io/badge/type-markup%20language-purple?style=flat-square" />
11
- <img src="https://img.shields.io/badge/html-supported-orange?style=flat-square" />
12
- <img src="https://img.shields.io/badge/markdown-supported-lightyellow?style=flat-square" />
13
- <img src="https://img.shields.io/badge/mdx-supported-lightblue?style=flat-square" />
14
- <img src="https://img.shields.io/badge/json-supported-yellow?style=flat-square" />
16
+ </a>
17
+
18
+ <!--Language Type-->
19
+ <img src="https://img.shields.io/badge/type-markup%20language-white?style=flat-square" />
20
+
21
+ <!--SomMark Playground-->
22
+ <a href="https://adam-elmi.github.io/SomMark-Playground" target="_blank">
23
+ <img
24
+ src="https://img.shields.io/badge/SomMark-Playground-blue?style=flat-square"
25
+ alt="SomMark Playground Badge" />
26
+ </a>
15
27
  </p>
16
28
 
29
+
30
+ ----
31
+
32
+ ## Try SomMark Playground
33
+
34
+ Test SomMark features live here:
35
+ [https://adam-elmi.github.io/SomMark-Playground/](https://adam-elmi.github.io/SomMark-Playground/)
36
+
37
+ ----
38
+
17
39
  # SomMark v2
18
40
 
19
41
  > [!WARNING]
@@ -21,7 +43,6 @@ SomMark is a declarative, extensible markup language for structured content that
21
43
 
22
44
  **SomMark** lets you write structured content that can be converted to HTML, Markdown, JSON, or other formats. Unlike standard Markdown, it uses explicit syntax for blocks and elements, making content easier to process, customize, and transform.
23
45
 
24
-
25
46
  # Installation
26
47
 
27
48
  To install the Command Line Interface (CLI) globally:
@@ -58,20 +79,28 @@ Hello World
58
79
  `;
59
80
 
60
81
  const smark = new SomMark({
61
- src: source,
62
- format: "html"
82
+ src: source,
83
+ format: "html"
63
84
  });
64
85
 
65
86
  console.log(await smark.transpile());
66
87
  ```
67
88
 
89
+ ## Supported Languages
90
+
91
+ * HTML
92
+ * Markdown
93
+ * MDX (Only ready components)
94
+ * JSON
95
+
96
+
68
97
  # Documentation
69
98
 
70
99
  Detailed guides and API references are available in the `docs/` directory:
71
100
 
72
- * **[Syntax Guide](docs/syntax.md)**: Master SomMark syntax (Blocks, Inline, At-Blocks).
73
- * **[Core API](docs/core.md)**: Programmatic usage of the library (`transpile`, `lex`, `parse`).
74
- * **[Mapper API](docs/mapper.md)**: Guide for creating custom mappers and rules.
75
- * **[CLI Reference](docs/cli.md)**: Command line options and configurations.
76
- * **[API Quick Reference](docs/api)**: Fast lookup for all classes and functions.
77
- * **[Configuration Reference](docs/config.md)**: Guide for creating custom configurations.
101
+ - **[Syntax Guide](docs/syntax.md)**: Master SomMark syntax (Blocks, Inline, At-Blocks).
102
+ - **[Core API](docs/core.md)**: Programmatic usage of the library (`transpile`, `lex`, `parse`).
103
+ - **[Mapper API](docs/mapper.md)**: Guide for creating custom mappers and rules.
104
+ - **[CLI Reference](docs/cli.md)**: Command line options and configurations.
105
+ - **[API Quick Reference](docs/api)**: Fast lookup for all classes and functions.
106
+ - **[Configuration Reference](docs/config.md)**: Guide for creating custom configurations.
@@ -4,6 +4,7 @@ import { cliError, formatMessage } from "../../core/errors.js";
4
4
  import HTML from "../../mappers/languages/html.js";
5
5
  import MARKDOWN from "../../mappers/languages/markdown.js";
6
6
  import MDX from "../../mappers/languages/mdx.js";
7
+ import Json from "../../mappers/languages/json.js";
7
8
  import { extensions } from "../constants.js";
8
9
  import { isExist, readContent, createFile } from "../helpers/file.js";
9
10
  import { loadConfig } from "../helpers/config.js";
@@ -56,7 +57,7 @@ export async function runBuild(format_option, sourcePath, outputFlag, outputFile
56
57
  let mappingFile = config.mappingFile;
57
58
 
58
59
  if (!mappingFile) {
59
- mappingFile = format === "html" ? HTML : format === "markdown" ? MARKDOWN : format === "mdx" ? MDX : null;
60
+ mappingFile = format === "html" ? HTML : format === "markdown" ? MARKDOWN : format === "mdx" ? MDX : format === "json" ? Json : null;
60
61
  }
61
62
 
62
63
  // CLI Overrides
@@ -6,40 +6,41 @@ import { options } from "../constants.js";
6
6
  // ========================================================================== //
7
7
 
8
8
  export function getHelp(unknown_option = true) {
9
- const msg = [
10
- `${unknown_option && process.argv[2] ? `<$red:Unrecognized option$> <$blue: '${process.argv[2]}'$>` : ""}`,
11
- "{N}<$yellow:Usage:$> <$blue:sommark [option]$>",
9
+ const msg = [
10
+ `${unknown_option && process.argv[2] ? `<$red:Unrecognized option$> <$blue: '${process.argv[2]}'$>` : ""}`,
11
+ "{N}<$yellow:Usage:$> <$blue:sommark [option]$>",
12
12
 
13
- "{N}{N}<$yellow:Global Options:$>",
14
- "{N} <$green:-h, --help$> <$cyan: Show help message$>",
15
- "{N} <$green:-v, --version$> <$cyan: Show version information$>",
16
- "{N} <$green:init$> <$cyan: Initialize global SomMark configuration directory$>",
17
- "{N} <$green:show config$> <$cyan: Display the absolute paths to the active SomMark configuration files$>",
13
+ "{N}{N}<$yellow:Global Options:$>",
14
+ "{N} <$green:-h, --help$> <$cyan: Show help message$>",
15
+ "{N} <$green:-v, --version$> <$cyan: Show version information$>",
16
+ "{N} <$green:init$> <$cyan: Initialize global SomMark configuration directory$>",
17
+ "{N} <$green:show config$> <$cyan: Display the absolute paths to the active SomMark configuration files$>",
18
18
 
19
- "{N}{N}<$yellow:Transpilation Options:$>",
20
- "{N}<$yellow:Usage:$> <$blue:sommark [option] [targetFile] [option] [outputFile] [outputDir]$>",
21
- "{N} <$green:--html$> <$cyan: Transpile to HTML$>",
22
- "{N} <$green:--markdown$> <$cyan: Transpile to Markdown$>",
23
- "{N} <$green:--mdx$> <$cyan: Transpile to MDX$>",
24
- "{N} <$green:--text$> <$cyan: Transpile to plain text$>",
19
+ "{N}{N}<$yellow:Transpilation Options:$>",
20
+ "{N}<$yellow:Usage:$> <$blue:sommark [option] [targetFile] [option] [outputFile] [outputDir]$>",
21
+ "{N} <$green:--html$> <$cyan: Transpile to HTML$>",
22
+ "{N} <$green:--markdown$> <$cyan: Transpile to Markdown$>",
23
+ "{N} <$green:--mdx$> <$cyan: Transpile to MDX$>",
24
+ "{N} <$green:--json$> <$cyan: Transpile to json$>",
25
+ "{N} <$green:--text$> <$cyan: Transpile to plain text$>",
25
26
 
26
- "{N}{N}<$yellow:Output Options:$>",
27
- "{N} <$green:-p, --print$> <$cyan: Print output to console (stdout)$>",
28
- "{N} <$green:-o$> <$cyan: Specify output filename (and optionally directory)$>",
27
+ "{N}{N}<$yellow:Output Options:$>",
28
+ "{N} <$green:-p, --print$> <$cyan: Print output to console (stdout)$>",
29
+ "{N} <$green:-o$> <$cyan: Specify output filename (and optionally directory)$>",
29
30
 
30
- "{N}{N}<$yellow:Examples:$>",
31
- "{N} <$magenta:1. Basic usage:$> <$blue:sommark --html input.smark$>",
32
- "{N} <$magenta:2. Print to console:$> <$blue:sommark --html -p input.smark$>",
33
- "{N} <$magenta:3. Custom output:$> <$blue:sommark --html input.smark -o myOutput ./dist/$>"
34
- ].join("");
35
- const help_msg = formatMessage(msg);
31
+ "{N}{N}<$yellow:Examples:$>",
32
+ "{N} <$magenta:1. Basic usage:$> <$blue:sommark --html input.smark$>",
33
+ "{N} <$magenta:2. Print to console:$> <$blue:sommark --html -p input.smark$>",
34
+ "{N} <$magenta:3. Custom output:$> <$blue:sommark --html input.smark -o myOutput ./dist/$>"
35
+ ].join("");
36
+ const help_msg = formatMessage(msg);
36
37
 
37
- if (!options.includes(process.argv[2]) && unknown_option) {
38
- console.log(help_msg);
39
- process.exit(0);
40
- } else if (process.argv[2] === "-h" || process.argv[2] === "--help") {
41
- console.log(help_msg);
42
- process.exit(0);
43
- }
44
- return help_msg;
38
+ if (!options.includes(process.argv[2]) && unknown_option) {
39
+ console.log(help_msg);
40
+ process.exit(0);
41
+ } else if (process.argv[2] === "-h" || process.argv[2] === "--help") {
42
+ console.log(help_msg);
43
+ process.exit(0);
44
+ }
45
+ return help_msg;
45
46
  }
package/cli/constants.js CHANGED
@@ -2,11 +2,12 @@
2
2
  // CLI Constants //
3
3
  // ========================================================================== //
4
4
 
5
- export const options = ["-v", "--version", "-h", "--help", "--html", "--markdown", "--mdx", "--text", "--print", "-p"];
5
+ export const options = ["-v", "--version", "-h", "--help", "--html", "--markdown", "--mdx", "json", "--text", "--print", "-p"];
6
6
 
7
7
  export const extensions = {
8
- text: "txt",
9
- html: "html",
10
- markdown: "md",
11
- mdx: "mdx"
8
+ text: "txt",
9
+ html: "html",
10
+ markdown: "md",
11
+ mdx: "mdx",
12
+ json: "json"
12
13
  };
package/core/lexer.js CHANGED
@@ -289,7 +289,6 @@ function lexer(src) {
289
289
  i += temp_str.length - 1;
290
290
  // Update Metadata
291
291
  updateMetadata(temp_str);
292
- scope_state = true;
293
292
  addToken(TOKEN_TYPES.OPEN_AT, temp_str);
294
293
  // is next token end keyword?
295
294
  const endKey = concatChar(src, i + 1, ["_"]);
@@ -330,7 +329,8 @@ function lexer(src) {
330
329
  previous_value === at_value ||
331
330
  previous_value === BLOCKCOLON ||
332
331
  previous_value === ATBLOCKCOLON ||
333
- previous_value === INLINECOLON)
332
+ previous_value === INLINECOLON) &&
333
+ !scope_state
334
334
  ) {
335
335
  // Update Metadata
336
336
  updateMetadata(current_char);
@@ -391,6 +391,7 @@ function lexer(src) {
391
391
  // Update Metadata
392
392
  updateMetadata(current_char);
393
393
  addToken(TOKEN_TYPES.SEMICOLON, current_char);
394
+ scope_state = true;
394
395
  previous_value = current_char;
395
396
  }
396
397
  // ========================================================================== //
@@ -574,10 +575,11 @@ function lexer(src) {
574
575
  // Token: Text //
575
576
  // ========================================================================== //
576
577
  else {
578
+ if (previous_value === "_@+") scope_state = true;
577
579
  context = concatText(src, i, scope_state, [
578
580
  [":", previous_value === inline_id_2],
579
581
  [",", previous_value === block_value || previous_value === at_value || previous_value === inline_value],
580
- [":", previous_value === "_@+" || previous_value === at_value],
582
+ [":", (previous_value === "_@+" && !scope_state) || previous_value === at_value],
581
583
  [";", previous_value === at_value],
582
584
  [")", previous_value === inline_value]
583
585
  ]);
@@ -119,10 +119,11 @@ async function generateOutput(ast, i, format, mapper_file) {
119
119
  const block_formats = [htmlFormat, mdxFormat, jsonFormat];
120
120
  if (target) {
121
121
  validateRules(target, node.args, "", node.type);
122
+ const placeholder = format === mdxFormat && node.body.length > 1 ? "\n<%smark>\n" : "<%smark>";
122
123
  result += block_formats.includes(format)
123
- ? `${target.render({ args: node.args, content: "<%smark>", ast: expose_for_fmts.includes(format) ? ast[i] : null })}`
124
- : target.render({ args: node.args, content: "" });
125
- // Body nodes
124
+ ? `${format === mdxFormat ? "\n" : ""}${target.render({ args: node.args, content: placeholder, ast: expose_for_fmts.includes(format) ? ast[i] : null }) + (format === mdxFormat ? "\n" : "")}`
125
+ : target.render({ args: node.args, content: "" }) + (format === mdxFormat ? "\n" : "");
126
+ // Body nodes
126
127
  for (let j = 0; j < node.body.length; j++) {
127
128
  const body_node = node.body[j];
128
129
  switch (body_node.type) {
@@ -141,10 +142,11 @@ async function generateOutput(ast, i, format, mapper_file) {
141
142
  target = matchedValue(mapper_file.outputs, body_node.id);
142
143
  if (target) {
143
144
  validateRules(target, body_node.args, body_node.value, body_node.type);
144
- context += target.render({
145
- args: body_node.args.length > 0 ? body_node.args : [],
146
- content: format === htmlFormat || format === mdxFormat ? escapeHTML(body_node.value) : body_node.value
147
- });
145
+ context +=
146
+ target.render({
147
+ args: body_node.args.length > 0 ? body_node.args : [],
148
+ content: format === htmlFormat || format === mdxFormat ? escapeHTML(body_node.value) : body_node.value
149
+ }) + (format === mdxFormat ? "\n" : "");
148
150
  }
149
151
  break;
150
152
  // ========================================================================== //
@@ -159,7 +161,7 @@ async function generateOutput(ast, i, format, mapper_file) {
159
161
  if (shouldEscape) {
160
162
  body_node.content = escapeHTML(body_node.content);
161
163
  }
162
- context += target.render({ args: body_node.args, content: body_node.content });
164
+ context += target.render({ args: body_node.args, content: body_node.content }) + (format === mdxFormat ? "\n" : "");
163
165
  }
164
166
  break;
165
167
  // ========================================================================== //
package/formatter/mark.js CHANGED
@@ -1,5 +1,5 @@
1
1
  class MarkdownBuilder {
2
- constructor() { }
2
+ constructor() {}
3
3
  // ========================================================================== //
4
4
  // Headings //
5
5
  // ========================================================================== //
@@ -64,29 +64,23 @@ class MarkdownBuilder {
64
64
  // ========================================================================== //
65
65
  // Code Block //
66
66
  // ========================================================================== //
67
- codeBlock(code, language) {
68
- if (!code) {
69
- return "";
70
- }
71
- const blocks = [];
72
- if (Array.isArray(code)) {
73
- for (const code_block of code) {
74
- blocks.push(code_block);
75
- }
76
- if (!language) {
77
- return "\n```" + "\n" + blocks.join("\n") + "\n```\n";
78
- }
79
- return "\n```" + language + "\n" + blocks.join("\n") + "\n```\n";
80
- } else if (typeof code === "string") {
81
- if (!language) {
82
- return "\n```" + "\n" + code + "\n```\n";
83
- }
84
- return "\n```" + language + "\n" + code + "\n```\n";
85
- }
86
- if (!code && !language) {
67
+ codeBlock(code, language = "") {
68
+ if (!code) return "";
69
+
70
+ const normalizeContent = c => {
71
+ if (Array.isArray(c)) return c.join("\n");
72
+ if (typeof c === "string") return c;
87
73
  return "";
88
- }
74
+ };
75
+
76
+ const content = normalizeContent(code);
77
+
78
+ if (!content) return "";
79
+
80
+ const lang = language ? language : "";
81
+ return `\n\`\`\`${lang}\n${content}\`\`\`\n`;
89
82
  }
83
+
90
84
  // ========================================================================== //
91
85
  // Horizontal rule //
92
86
  // ========================================================================== //
@@ -97,22 +91,13 @@ class MarkdownBuilder {
97
91
  // Escape //
98
92
  // ========================================================================== //
99
93
  escape(text) {
100
- const special_char = ["\\", "*", "_", "{", "}", "[", "]", "(", ")", "#", "+", "-", ".", "!", ">", "|"];
101
- if (!text) {
102
- return "";
103
- }
94
+ if (!text) return "";
104
95
 
105
- text = text
106
- .split("")
107
- .map(char => {
108
- if (special_char.includes(char)) {
109
- return `\\${char}`;
110
- }
111
- return char;
112
- })
113
- .join("");
114
- return text;
96
+ const special = /[\\*_{}\[\]()#+\-.!>|]/g;
97
+
98
+ return text.replace(special, "\\$&");
115
99
  }
100
+
116
101
  // ========================================================================== //
117
102
  // Table //
118
103
  // ========================================================================== //
@@ -133,15 +118,15 @@ class MarkdownBuilder {
133
118
  }
134
119
  rows = rows.map(row => {
135
120
  let columns;
136
- if (typeof row === 'string') {
137
- columns = row.split(',').map(c => c.trim());
121
+ if (typeof row === "string") {
122
+ columns = row.split(",").map(c => c.trim());
138
123
  } else if (Array.isArray(row)) {
139
124
  columns = row.map(c => String(c).trim());
140
125
  } else {
141
126
  return "";
142
127
  }
143
128
 
144
- if (columns.length > 0 && columns[0].startsWith('-')) {
129
+ if (columns.length > 0 && columns[0].startsWith("-")) {
145
130
  columns[0] = `- ${columns[0].substring(1).trim()}`;
146
131
  }
147
132
 
package/formatter/tag.js CHANGED
@@ -38,16 +38,16 @@ class TagBuilder {
38
38
  props(propsList) {
39
39
  const list = Array.isArray(propsList) ? propsList : [propsList];
40
40
  if (list.length > 0) {
41
- for (const propEntry of list) {
41
+ for (const propEntry of list) {
42
42
  if (typeof propEntry !== "object" || propEntry === null) {
43
- throw new TypeError("prop expects an object with property { type }");
43
+ throw new TypeError("prop expects an object with property { __type__ }");
44
44
  }
45
45
 
46
- if (!Object.prototype.hasOwnProperty.call(propEntry, "type")) {
47
- throw new TypeError("prop expects an object with property { type }");
46
+ if (!Object.prototype.hasOwnProperty.call(propEntry, "__type__")) {
47
+ throw new TypeError("prop expects an object with property { __type__ }");
48
48
  }
49
49
 
50
- const { type, ...rest } = propEntry;
50
+ const { __type__, ...rest } = propEntry;
51
51
  const entries = Object.entries(rest);
52
52
 
53
53
  if (entries.length === 0) {
@@ -56,7 +56,7 @@ class TagBuilder {
56
56
 
57
57
  const [key, value] = entries[0];
58
58
 
59
- switch (type) {
59
+ switch (__type__) {
60
60
  case "string":
61
61
  this.#attr.push(`${key}="${escapeHTML(String(value))}"`);
62
62
  break;
package/mappers/mapper.js CHANGED
@@ -27,7 +27,7 @@ class Mapper {
27
27
  };
28
28
 
29
29
  this.#customHeaderContent = "";
30
-
30
+
31
31
  this.highlightCode = null;
32
32
  this.escapeHTML = escapeHTML;
33
33
  this.styles = [];
@@ -358,5 +358,53 @@ class Mapper {
358
358
 
359
359
  return fallBack;
360
360
  };
361
+ makeFrontmatter = entries => {
362
+ if (!entries || typeof entries !== "object") {
363
+ console.warn(`From function: ${this.makeFrontmatter.name}: invalid entries, returning empty string.`);
364
+ return "";
365
+ }
366
+ const keys = Object.keys(entries);
367
+ if (keys.length === 0) {
368
+ console.warn(`From function: ${this.makeFrontmatter.name}: invalid entries, returning empty string.`);
369
+ return "";
370
+ }
371
+ const body = keys
372
+ .map(key => {
373
+ const value = entries[key];
374
+ if (Array.isArray(value)) {
375
+ const list = value.map(item => ` - ${JSON.stringify(item)}`).join("\n");
376
+ return `${key}:\n${list}`;
377
+ }
378
+ return `${key}: ${JSON.stringify(value)}`;
379
+ })
380
+ .join("\n");
381
+ return `---\n${body}\n---\n`;
382
+ };
383
+
384
+ raw_js_imports = imports => {
385
+ if (!Array.isArray(imports)) {
386
+ console.warn("raw_js_imports: imports must be an array");
387
+ return "";
388
+ }
389
+
390
+ if (imports.length === 0) {
391
+ console.warn("raw_js_imports: imports array is empty");
392
+ return "";
393
+ }
394
+
395
+ return imports
396
+ .map((imp, index) => {
397
+ if (!imp?.name || !imp?.path) {
398
+ console.warn(`raw_js_imports: invalid import entry at index ${index}`);
399
+ return "";
400
+ }
401
+
402
+ const path = JSON.stringify(imp.path);
403
+ const newline = index === imports.length - 1 ? "\n\n" : "\n";
404
+
405
+ return `import ${imp.name} from ${path};${newline}`;
406
+ })
407
+ .join("");
408
+ };
361
409
  }
362
410
  export default Mapper;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sommark",
3
- "version": "2.2.0",
3
+ "version": "2.3.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": {