sommark 4.0.3 → 4.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SomMark v4 <img src="assets/smark.logo.png" width="80" align="right">
1
+ # SomMark <img src="assets/smark.logo.png" width="80" align="right">
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/sommark.svg)](https://www.npmjs.com/package/sommark)
4
4
  [![license](https://img.shields.io/npm/l/sommark.svg)](https://github.com/Adam-Elmi/SomMark/blob/master/LICENSE)
@@ -9,126 +9,327 @@ SomMark uses explicit structural boundaries to ensure your document remains stab
9
9
 
10
10
  ---
11
11
 
12
- ## Simple Showcase
12
+ ## Install
13
13
 
14
- SomMark uses blocks for structure. A block starts with `[identifier]` and must end with `[end]`.
14
+ #### 1. Global (Command Line)
15
+ Install SomMark globally to use the `sommark` command.
16
+ ```bash
17
+ npm install -g sommark
18
+ ```
19
+
20
+ To verify it is working, check the version:
21
+ ```bash
22
+ sommark --version
23
+ ```
24
+
25
+ #### 2. Local (Project API)
26
+ Add SomMark to your project as a dependency:
27
+ ```bash
28
+ npm install sommark
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Features
34
+
35
+ ### Blocks
36
+
37
+ Blocks are the primary containers in SomMark, used to group, style, and organize structural content. A block starts with `[identifier]` and closes with a matching `[end]` or self-closing `!` if the block does not have any content, for example `[br!]` or `[img = src: "logo.png" !]`.
15
38
 
16
- ### HTML
17
39
  ```ini
18
- [h1]Welcome to SomMark[end]
40
+ [div = class: "container"]
41
+ [h1]Welcome to SomMark[end]
42
+ [p]Structured content, zero guesswork.[end]
43
+ [hr = class: "divider" !]
44
+ [end]
45
+ ```
46
+ [Read more about blocks](./docs/syntax/block.md)
19
47
 
20
- [div = class: "main"]
21
- This is content inside a container block.
22
- [p]
23
- Blocks are the most important part of SomMark.
24
- (This is a span-like inline statement)->(css: "color: green")
25
- [end]
48
+ ### Self-Closing Blocks
49
+
50
+ Blocks that carry no body content close instantly with `!`.
51
+
52
+ ```ini
53
+ [br!]
54
+ [hr = class: "divider" !]
55
+ [img = src: "logo.png", alt: "SomMark Logo" !]
56
+ ```
57
+ [Read more about self-closing blocks](./docs/syntax/self-closing.md)
58
+
59
+ ### Inline Elements
60
+
61
+ Apply styling or formatting to specific spans of text within a block using `(inline text)->(identifier = args)` or `(inline text)->(identifier: args)`. Internal newlines and duplicate spaces are automatically collapsed into a single space.
62
+
63
+ ```ini
64
+ [p]
65
+ This is (important text)->(bold).
66
+ Visit the (SomMark Website)->(link = "https://sommark.org", target: "_blank").
26
67
  [end]
27
68
  ```
69
+ [Read more about inline elements](./docs/syntax/inline.md)
70
+
71
+ ### Props (Properties)
72
+
73
+ Pass metadata (called **Props**, similar to component properties in React/Vue) to Blocks, Inline Elements, and At-Blocks. SomMark supports positional, named, and mixed props with multiple value types (JS data, placeholders, and variables).
74
+
75
+ > [!NOTE]
76
+ > In the current major version, SomMark passes these properties under the **`args`** key to custom JavaScript `render` functions for backward compatibility. This will be renamed to `props` in the next major version.
77
+
78
+ * **Blocks**: Passed after `=` (e.g., `[div = "container"][end]`).
79
+ * **Inline Elements**: Passed after `=` or `:` inside the identifier parenthesis (e.g., `(text)->(link = "url")`).
80
+ * **At-Blocks**: Passed after `:` and terminated with `;` (e.g., `@_code: lang: "js";`).
28
81
 
29
- ### JSON
30
- SomMark can represent complex data structures through its specialized mappers.
31
82
  ```ini
32
- [object]
33
- [string = key: "name"]Adam Elmi[end]
34
- [number = key: "age"]25[end]
35
- [array = key: "skills"]
36
- [string]JavaScript[end]
37
- [string]SomMark[end]
38
- [end]
83
+ # Blocks (using '=')
84
+ [div = "container", class: "flex"][end]
85
+
86
+ # Inline Elements (using '=' or ':')
87
+ (Visit Website)->(link = "https://sommark.org", target: "_blank")
88
+
89
+ # At-Blocks (using ':' and terminated with ';')
90
+ @_code: lang: "js", active: js{true}, user: v{userId};
91
+ console.log("Hello World");
92
+ @_end_@
93
+
94
+ # Passing static logic to props
95
+ [Date = year: static ${ new Date().getFullYear() }$][end]
96
+ ```
97
+
98
+ ### At-Blocks -- Raw Content
99
+
100
+ At-blocks capture raw text. Brackets, tags, and comments inside them are treated as literal characters and never parsed.
101
+
102
+ ```ini
103
+ @_code_@: lang: "javascript";
104
+ const items = [1, 2, 3];
105
+ // This [div] won't be parsed
106
+ @_end_@
107
+ ```
108
+
109
+ ### Comments
110
+
111
+ Single-line and multi-line. Completely removed from the compiled output.
112
+
113
+ ```ini
114
+ # This is a single-line comment
115
+
116
+ ###
117
+ This is a multi-line comment.
118
+ Nothing here reaches the output.
119
+ ###
120
+ ```
121
+
122
+ ### Static Logic -- Compile-Time JavaScript
123
+
124
+ Run JavaScript during compilation inside a sandboxed QuickJS VM. The result replaces the block inline.
125
+
126
+ ```ini
127
+ [footer]
128
+ Copyright static ${ new Date().getFullYear() }$
39
129
  [end]
40
130
  ```
41
131
 
42
- ### XML
43
- SomMark produces clean, structured XML ideal for configuration and data manifests.
132
+ Use it in props too:
133
+
44
134
  ```ini
45
- [xml = version: "1.0"][end]
46
- [project = name: "SomMark-App"]
47
- [metadata]
48
- [author]Adam Elmi[end]
49
- [version]4.0.0[end]
50
- [end]
51
- [settings]
52
- [database = type: "postgres"]
53
- [host]localhost[end]
54
- [port]5432[end]
55
- [end]
56
- [end]
135
+ [Date = year: static ${ new Date().getFullYear() }$][end]
136
+ ```
137
+
138
+ ### Runtime Logic -- Client-Side JavaScript
139
+
140
+ Preserve JavaScript in the compiled output. It runs in the browser, not during compilation.
141
+
142
+ ```ini
143
+ [button]
144
+ runtime ${
145
+ self.addEventListener("click", () => alert("Hello from SomMark"))
146
+ }$
147
+ Click Me
57
148
  [end]
58
149
  ```
59
150
 
60
- ### MDX
61
- Use the JavaScript data layer to pass native data to your components.
151
+ ### For-Each Loop
152
+
153
+ Iterate over arrays and render blocks for each item. Access the current item and its index.
154
+
62
155
  ```ini
63
- [h1]MDX Portfolio[end]
156
+ [for-each = static ${ [{name: "Adam"}, {name: "Hawa"}, {name: "Ilham"}] }$, as: "user"]
157
+ [li]static ${ user.name }$ -- position static ${ user_index }$[end]
158
+ [end]
159
+ ```
160
+
161
+ ### Modules and Components
64
162
 
65
- [Gallery =
66
- images: js{["nature.jpg", "tech.jpg"]},
67
- active: js{true}
68
- ]
69
- This block uses native JS arrays and booleans.
163
+ Split your project into reusable `.smark` files. Import them, pass props, and inject body content through slots.
164
+
165
+ **`components/Card.smark`**
166
+ ```ini
167
+ [div = class: "card"]
168
+ [h2]v{title}[end]
169
+ [div = class: "card-body"]
170
+ [slot][end]
171
+ [end]
70
172
  [end]
71
173
  ```
72
174
 
73
- ### Markdown
74
- Use placeholders to inject dynamic text into your templates.
175
+ **`page.smark`**
75
176
  ```ini
76
- [h1]Hello p{username}[end]
177
+ [import = Card: "./components/Card.smark"][end]
77
178
 
78
- [quote]
79
- You are reading documentation on p{siteName}.
80
- SomMark is an extensible language.
179
+ [Card = title: "Featured Product"]
180
+ This content fills the slot inside the card.
81
181
  [end]
182
+ ```
183
+
184
+ You can also inject a module without passing body content:
82
185
 
83
- [hr][end]
186
+ ```ini
187
+ [import = Card: "./components/Card.smark"][end]
188
+ [$use-module = Card][end]
84
189
  ```
85
190
 
86
191
  ---
87
192
 
88
- ## How It Works
193
+ ## Output Formats
89
194
 
90
- SomMark is an extensible language that processes content through a four-stage pipeline:
195
+ Write once, compile to any of these:
91
196
 
92
- 1. **Lexing**: The engine scans the source and converts it into a stream of tokens.
93
- 2. **Parsing**: Tokens are organized into a hierarchical tree called an **AST** (Abstract Syntax Tree).
94
- 3. **Mapping**: This is the translation layer. You define how identifiers (like `[h1]`) look in the target language.
95
- 4. **Transpilation**: The engine walks the AST and uses the Mapper to generate the final string output.
96
-
97
- The **Module System** enables a "Declare-then-Inject" pattern, allowing you to import mappers and create scalable projects with full recursive support.
197
+ | Format | CLI Flag | Extension |
198
+ |----------|--------------|-----------|
199
+ | HTML | `--html` | `.html` |
200
+ | Markdown | `--markdown` | `.md` |
201
+ | MDX | `--mdx` | `.mdx` |
202
+ | JSON | `--json` | `.json` |
203
+ | JSONC | `--jsonc` | `.jsonc` |
204
+ | XML | `--xml` | `.xml` |
205
+ | Text | `--text` | `.txt` |
98
206
 
99
207
  ---
100
208
 
101
- ## Installation
102
-
103
- Install the SomMark CLI globally:
209
+ ## CLI
104
210
 
105
- ```bash
106
- npm install -g sommark
211
+ ```
212
+ sommark init Create a smark.config.js file
213
+ sommark --html <file> Compile to HTML
214
+ sommark --markdown <file> Compile to Markdown
215
+ sommark --html <file> -o <name> <dir> Set output filename and directory
216
+ sommark --html --print <file> Print compiled output to terminal
217
+ sommark --lex <file> Show the token stream
218
+ sommark --parse <file> Show the syntax tree
219
+ sommark show config [file] Show the resolved configuration
220
+ sommark -v Show version
221
+ sommark -h Show help
107
222
  ```
108
223
 
109
224
  ---
110
225
 
111
- ## Usage in Node.js
226
+ ## Programmatic Usage
112
227
 
113
- ```javascript
228
+ ```js
114
229
  import SomMark from "sommark";
115
230
 
116
- const sm = new SomMark({
117
- src: "[h1]Hello World[end]",
118
- format: "html"
231
+ const engine = new SomMark({
232
+ src: '[h1]Hello World[end]',
233
+ format: "html",
119
234
  });
120
235
 
121
- const output = await sm.transpile();
236
+ const output = await engine.transpile();
122
237
  // <h1>Hello World</h1>
123
238
  ```
124
239
 
125
240
  ---
126
241
 
242
+ ## Configuration
243
+
244
+ Run `sommark init` to generate a `smark.config.js` with all available settings:
245
+
246
+ ```js
247
+ export default {
248
+ format: "html",
249
+ removeComments: true,
250
+ generateRuntimeOutput: false,
251
+ hideRuntimeOutput: false,
252
+ customProps: [],
253
+ placeholders: {},
254
+ importAliases: { "@": "./" },
255
+ fallbackTarget: "style",
256
+ outputValidator: null,
257
+ baseDir: null,
258
+ showSpinner: true,
259
+ security: {
260
+ allowRaw: true,
261
+ maxDepth: 5,
262
+ timeout: 5000,
263
+ allowFetch: true,
264
+ allowHttp: false,
265
+ allowedOrigins: [],
266
+ allowedExtensions: [],
267
+ sanitize: null,
268
+ },
269
+ outputDir: "./",
270
+ outputFile: "output",
271
+ };
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Security
277
+
278
+ All embedded JavaScript runs inside a **QuickJS sandbox** with strict restrictions:
279
+
280
+ | Setting | Default | What it does |
281
+ |---------------------|---------|----------------------------------------------|
282
+ | `allowRaw` | `true` | Allow raw code in static blocks |
283
+ | `maxDepth` | `5` | Maximum import nesting depth |
284
+ | `timeout` | `5000` | Script execution time limit (ms) |
285
+ | `allowFetch` | `true` | Allow network requests from scripts |
286
+ | `allowHttp` | `false` | Block insecure HTTP (HTTPS only by default) |
287
+ | `allowedOrigins` | `null` | Restrict fetch to specific domains |
288
+ | `allowedExtensions` | `null` | Restrict which file types can be imported |
289
+ | `sanitize` | `null` | Custom function to sanitize HTML output |
290
+
291
+ ---
292
+
293
+ ## Custom Output Rules
294
+
295
+ Define your own tags and rendering logic with the Mapper API:
296
+
297
+ ```js
298
+ import SomMark, { Mapper } from "sommark";
299
+
300
+ const mapper = new Mapper("custom");
301
+
302
+ mapper.register("alert", (node) => {
303
+ return `<div class="alert">${node.body}</div>`;
304
+ });
305
+
306
+ const engine = new SomMark({
307
+ src: '[alert]Check your configuration.[end]',
308
+ format: "html",
309
+ mapperFile: mapper,
310
+ });
311
+
312
+ const output = await engine.transpile();
313
+ ```
314
+
315
+ Full reference in [`docs/api/Mapper`](docs/api/Mapper).
316
+
317
+ ---
318
+
127
319
  ## Documentation
128
320
 
129
- Read the full guides in the `docs/` folder:
321
+ | Topic | Location |
322
+ |------------------|-----------------------------------------------|
323
+ | Syntax Reference | [`docs/syntax/`](docs/syntax) |
324
+ | Core API | [`docs/api/Core/`](docs/api/Core) |
325
+ | Mapper API | [`docs/api/Mapper/`](docs/api/Mapper) |
326
+ | Sandbox API | [`docs/api/Sandbox/`](docs/api/Sandbox) |
327
+ | Output Formats | [`docs/languages/`](docs/languages) |
328
+ | CLI Guide | [`docs/cli/`](docs/cli) |
329
+ | Configuration | [`docs/cli/config.md`](docs/cli/config.md) |
330
+
331
+ ---
332
+
333
+ ## License
130
334
 
131
- * **[Syntax Guide](docs/syntax/syntax.md)**: Rules for writing Blocks, At-Blocks, and Inlines.
132
- * **[Core Logic](docs/core/core.md)**: Deep dive into the engine architecture.
133
- * **[Mapper API](docs/core/mapper.md)**: How to create your own translation layers.
134
- * **[Module System](docs/core/module-system.md)**: Managing multi-file projects.
335
+ [MIT](LICENSE) -- Adam Elmi
package/cli/cli.mjs CHANGED
@@ -70,7 +70,7 @@ async function main() {
70
70
  runColor(args[1]);
71
71
  return;
72
72
  }
73
-
73
+
74
74
 
75
75
  // 6. Lex
76
76
  if (command === "--lex") {
@@ -5,7 +5,9 @@ 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
7
  import Json from "../../mappers/languages/json.js";
8
+ import Jsonc from "../../mappers/languages/jsonc.js";
8
9
  import XML from "../../mappers/languages/xml.js";
10
+ import TEXT from "../../mappers/languages/text.js";
9
11
  import { extensions } from "../constants.js";
10
12
  import { isExist, readContent, createFile } from "../helpers/file.js";
11
13
  import { loadConfig } from "../helpers/config.js";
@@ -78,7 +80,7 @@ export async function runBuild(format_option, sourcePath, outputFlag, outputFile
78
80
  let mapperFile = config.mapperFile || config.mappingFile;
79
81
 
80
82
  if (!mapperFile) {
81
- mapperFile = format === "html" ? HTML : format === "markdown" ? MARKDOWN : format === "mdx" ? MDX : format === "json" ? Json : format === "xml" ? XML : null;
83
+ mapperFile = format === "html" ? HTML : format === "markdown" ? MARKDOWN : format === "mdx" ? MDX : format === "json" ? Json : format === "jsonc" ? Jsonc : format === "xml" ? XML : format === "text" ? TEXT : null;
82
84
  }
83
85
 
84
86
  // CLI Overrides
@@ -28,7 +28,9 @@ export function getHelp(unknown_option = true) {
28
28
  "{N} <$green:--html$> <$cyan: Transpile to HTML$>",
29
29
  "{N} <$green:--markdown$> <$cyan: Transpile to Markdown$>",
30
30
  "{N} <$green:--mdx$> <$cyan: Transpile to MDX$>",
31
+ "{N} <$green:--xml$> <$cyan: Transpile to XML$>",
31
32
  "{N} <$green:--json$> <$cyan: Transpile to JSON$>",
33
+ "{N} <$green:--jsonc$> <$cyan: Transpile to JSON with Comments (JSONC)$>",
32
34
  "{N} <$green:--text$> <$cyan: Transpile to plain text$>",
33
35
  "{N} <$green:--lex$> <$cyan: Print lexer tokens to console$>",
34
36
  "{N} <$green:--parse$> <$cyan: Print parser AST to console$>",
@@ -19,12 +19,31 @@ export async function runInit() {
19
19
  * Generated by 'smark init'
20
20
  */
21
21
  export default {
22
- format: "html", // Target output format (html, markdown, mdx, json, xml)
23
- removeComments: true, // Strip SomMark comments from the final output
24
- customProps: [], // Whitelisted HTML attributes (prevents CSS hijacking)
25
- placeholders: {}, // Global p{key} variables for content injection
26
- outputDir: "./", // Where to save the transpiled files
27
- outputFile: "output", // Default output filename
22
+ format: "html", // Target output format (html, markdown, mdx, json, xml, jsonc, text)
23
+ removeComments: true, // Strip SomMark comments from the final output
24
+ generateRuntimeOutput: false, // Generate only VM script bundles, ignoring markup layouts
25
+ hideRuntimeOutput: false, // Strip all <script> tags from final compiled HTML outputs
26
+ customProps: [], // Whitelisted HTML attributes
27
+ placeholders: {}, // Global p{key} placeholders for content injection
28
+ importAliases: { // Custom path aliases for modules (e.g. { "@": "./src/components" })
29
+ "@": "./"
30
+ },
31
+ fallbackTarget: "style", // Where unrecognized attributes go: "style", "class", or false to disable
32
+ outputValidator: null, // Custom callback function: async (transpiledOutput) => { ... }
33
+ baseDir: null, // Base directory for resolving relative module imports
34
+ showSpinner: true, // Display a dynamic spinner in the terminal during transpilation
35
+ security: { // Sandbox and security restrictions for module execution
36
+ allowRaw: true, // Permit raw code blocks in static logic blocks
37
+ maxDepth: 5, // Maximum allowed import nesting depth
38
+ timeout: 5000, // Timeout in milliseconds for scripts to execute
39
+ allowFetch: true, // Allow fetch queries inside runtime logic block execution
40
+ allowHttp: false, // Disallow http requests (highly recommended to keep false)
41
+ allowedOrigins: [], // Whitelisted HTTP origins/domains for fetch requests (e.g. ["api.github.com"])
42
+ allowedExtensions: [], // Whitelisted file extensions for local module imports (e.g. [".smark", ".json"])
43
+ sanitize: null, // Custom output HTML string sanitization function: (html) => { ... }
44
+ },
45
+ outputDir: "./", // Where to save the transpiled files
46
+ outputFile: "output", // Default output filename
28
47
  };
29
48
  `;
30
49
 
package/cli/constants.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  /** @type {Array<string>} List of recognized CLI flags and commands. */
7
- export const options = ["-v", "--version", "-h", "--help", "--html", "--markdown", "--mdx", "--json", "--text", "--xml", "--print", "-p", "--lex", "--parse", "list"];
7
+ export const options = ["-v", "--version", "-h", "--help", "--html", "--markdown", "--mdx", "--json", "--jsonc", "--text", "--xml", "--print", "-p", "--lex", "--parse", "list"];
8
8
 
9
9
  /** @type {Object<string, string>} Map of output formats to their respective file extensions. */
10
10
  export const extensions = {
@@ -13,5 +13,6 @@ export const extensions = {
13
13
  markdown: "md",
14
14
  mdx: "mdx",
15
15
  json: "json",
16
+ jsonc: "jsonc",
16
17
  xml: "xml"
17
18
  };
@@ -6,11 +6,13 @@ import HTML from "../../mappers/languages/html.js";
6
6
  import MARKDOWN from "../../mappers/languages/markdown.js";
7
7
  import MDX from "../../mappers/languages/mdx.js";
8
8
  import Json from "../../mappers/languages/json.js";
9
+ import Jsonc from "../../mappers/languages/jsonc.js";
10
+ import XML from "../../mappers/languages/xml.js";
9
11
  import { isExist } from "./file.js";
10
12
  import { loadConfig } from "./config.js";
11
- import { htmlFormat, markdownFormat, mdxFormat, jsonFormat, textFormat } from "../../core/formats.js";
13
+ import { htmlFormat, markdownFormat, mdxFormat, jsonFormat, jsoncFormat, xmlFormat, textFormat } from "../../core/formats.js";
12
14
 
13
- const default_mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json, [textFormat]: null };
15
+ const default_mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json, [jsoncFormat]: Jsonc, [xmlFormat]: XML, [textFormat]: null };
14
16
 
15
17
  // ========================================================================== //
16
18
  // Transpile Function //
@@ -50,6 +52,7 @@ export async function transpile({ src, format, filename = null, mapperFile = "",
50
52
  format,
51
53
  filename,
52
54
  mapperFile: finalMapper,
55
+ showSpinner: finalConfig.showSpinner !== false
53
56
  });
54
57
 
55
58
  return await smark.transpile();
@@ -36,6 +36,7 @@ export const HTML_PROPS = new Set([
36
36
  "decoding",
37
37
  "crossorigin",
38
38
  "charset",
39
+ "content",
39
40
  "action",
40
41
  "method",
41
42
  "enctype",