sommark 2.1.2 → 2.2.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 +10 -0
- package/README.md +3 -299
- package/cli/cli.mjs +15 -1
- package/cli/commands/help.js +2 -0
- package/cli/commands/init.js +64 -0
- package/cli/commands/show.js +46 -0
- package/cli/commands/version.js +1 -1
- package/cli/helpers/config.js +14 -5
- package/core/formats.js +3 -2
- package/core/lexer.js +20 -13
- package/core/parser.js +18 -9
- package/core/transpiler.js +34 -20
- package/formatter/mark.js +3 -6
- package/grammar.ebnf +0 -1
- package/index.js +6 -4
- package/mappers/languages/html.js +71 -26
- package/mappers/languages/json.js +172 -0
- package/mappers/languages/markdown.js +79 -31
- package/mappers/mapper.js +55 -9
- package/package.json +2 -5
- package/lib/highlight.js +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.2.0 (2026-02-23)
|
|
4
|
+
## Features
|
|
5
|
+
- Added JSON support
|
|
6
|
+
- Added type rules to validate element type
|
|
7
|
+
- Improved documentation
|
|
8
|
+
- Improved mapper files
|
|
9
|
+
- Removed **highlight.js** dependency
|
|
10
|
+
- Added new CLI feature: automatic configuration file creation
|
|
11
|
+
|
|
12
|
+
|
|
3
13
|
## v2.1.2 (2026-02-09)
|
|
4
14
|
### Fixes
|
|
5
15
|
- Fixed cli print functionality
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<img width="2000" height="491" alt="SomMark Cover" src="https://raw.githubusercontent.com/Adam-Elmi/SomMark/master/assets/smark_bg.png" />
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
|
|
4
|
+
SomMark is a declarative, extensible markup language for structured content that can be converted to HTML, Markdown, MDX, JSON, and more.
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
<img src="https://img.shields.io/badge/html-supported-orange?style=flat-square" />
|
|
12
12
|
<img src="https://img.shields.io/badge/markdown-supported-lightyellow?style=flat-square" />
|
|
13
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" />
|
|
14
15
|
</p>
|
|
15
16
|
|
|
16
17
|
# SomMark v2
|
|
@@ -18,118 +19,8 @@
|
|
|
18
19
|
> [!WARNING]
|
|
19
20
|
> Old version(v1) is no longer supported.
|
|
20
21
|
|
|
21
|
-
SomMark
|
|
22
|
+
**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.
|
|
22
23
|
|
|
23
|
-
# Features
|
|
24
|
-
|
|
25
|
-
* **Explicit Syntax**: Every element has a clear start and end, which reduces parsing errors.
|
|
26
|
-
* **Customizable Mappers**: You can convert SomMark content into any format (HTML, Markdown, JSON, etc.) by using Mappers.
|
|
27
|
-
* **Validation**: You can define rules to check your content, such as limiting the number of arguments or requiring specific keys.
|
|
28
|
-
* **MDX Support**: SomMark can map to existing React components ("Only Ready Components").
|
|
29
|
-
> [!NOTE]
|
|
30
|
-
> SomMark does not parse raw JSX. It only maps to ready components.
|
|
31
|
-
|
|
32
|
-
# Syntax
|
|
33
|
-
|
|
34
|
-
SomMark uses three main types of elements: Blocks, Inline Statements, and At-Blocks.
|
|
35
|
-
|
|
36
|
-
## 1. Block
|
|
37
|
-
|
|
38
|
-
A Block is a container that holds content. It can contain text or other nested blocks.
|
|
39
|
-
|
|
40
|
-
**With Arguments**
|
|
41
|
-
You can pass data to a block using arguments. Arguments can have keys.
|
|
42
|
-
```ini
|
|
43
|
-
[Alert = urgent, title: Warning]
|
|
44
|
-
System maintenance in 10 minutes.
|
|
45
|
-
This block uses a flag (urgent) and a key-value pair (title).
|
|
46
|
-
[end]
|
|
47
|
-
```
|
|
48
|
-
> [!NOTE]
|
|
49
|
-
> The colon `:` separates keys and values.
|
|
50
|
-
> Keys are optional.
|
|
51
|
-
|
|
52
|
-
**Without Arguments**
|
|
53
|
-
```ini
|
|
54
|
-
[Note]
|
|
55
|
-
This is a simple block.
|
|
56
|
-
[end]
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## 2. Inline Statement
|
|
60
|
-
|
|
61
|
-
Inline Statements are used to format specific parts of text.
|
|
62
|
-
|
|
63
|
-
**With Arguments**
|
|
64
|
-
```ini
|
|
65
|
-
This text is (bold)->(bold) and this is (red)->(color: red).
|
|
66
|
-
```
|
|
67
|
-
> [!NOTE]
|
|
68
|
-
> Inline arguments are values only. Keys are not supported.
|
|
69
|
-
|
|
70
|
-
**Without Arguments**
|
|
71
|
-
```ini
|
|
72
|
-
(Click here)->(Button)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## 3. At-Block
|
|
76
|
-
|
|
77
|
-
At-Blocks are used for specific content like code snippets or tables. The content inside an At-Block is treated as plain text and cannot contain other SomMark elements.
|
|
78
|
-
|
|
79
|
-
**With Arguments**
|
|
80
|
-
```ini
|
|
81
|
-
@_Code_@: javascript;
|
|
82
|
-
console.log("This is raw code.");
|
|
83
|
-
@_end_@
|
|
84
|
-
```
|
|
85
|
-
> [!NOTE]
|
|
86
|
-
> You must use a semi-colon `;` to end the argument list.
|
|
87
|
-
|
|
88
|
-
**Without Arguments**
|
|
89
|
-
```ini
|
|
90
|
-
@_Raw_@
|
|
91
|
-
Raw content here.
|
|
92
|
-
@_end_@
|
|
93
|
-
```
|
|
94
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
* **Identifiers**: Names can only contain letters and numbers.
|
|
129
|
-
* **Escape Character**: Use the backslash `\` to escape special characters (like colons or commas) inside arguments.
|
|
130
|
-
* **Colons**: inside Block and At-Block arguments, the colon (`:`) separates names from values.
|
|
131
|
-
* **Semi-Colons**: The semi-colon (`;`) is only used in At-Blocks to assert the end of the argument list.
|
|
132
|
-
* **Whitespace**: SomMark ignores extra spaces and newlines, so you can format your code however you like.
|
|
133
24
|
|
|
134
25
|
# Installation
|
|
135
26
|
|
|
@@ -184,190 +75,3 @@ Detailed guides and API references are available in the `docs/` directory:
|
|
|
184
75
|
* **[CLI Reference](docs/cli.md)**: Command line options and configurations.
|
|
185
76
|
* **[API Quick Reference](docs/api)**: Fast lookup for all classes and functions.
|
|
186
77
|
* **[Configuration Reference](docs/config.md)**: Guide for creating custom configurations.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
# Configuration (Only for CLI)
|
|
191
|
-
|
|
192
|
-
You can create a `smark.config.js` file to configure the CLI.
|
|
193
|
-
|
|
194
|
-
```javascript
|
|
195
|
-
/* smark.config.js */
|
|
196
|
-
import myMapper from "./my-mapper.js";
|
|
197
|
-
|
|
198
|
-
export default {
|
|
199
|
-
outputFile: "output", // Default output filename
|
|
200
|
-
outputDir: "./dist", // Where to save files
|
|
201
|
-
mappingFile: myMapper // Use a custom mapper by default
|
|
202
|
-
};
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
# Creating Custom Mappers
|
|
206
|
-
|
|
207
|
-
Mappers tell SomMark how to convert your content. You can define rules, options, and how arguments are handled.
|
|
208
|
-
|
|
209
|
-
## Basic Structure
|
|
210
|
-
|
|
211
|
-
```javascript
|
|
212
|
-
import { Mapper } from "sommark";
|
|
213
|
-
const myMapper = new Mapper();
|
|
214
|
-
const { tag } = myMapper;
|
|
215
|
-
|
|
216
|
-
// Define a Block
|
|
217
|
-
myMapper.register("Alert", ({ args, content }) => {
|
|
218
|
-
// Access arguments by index or name
|
|
219
|
-
const type = args[0] || args.type || "info";
|
|
220
|
-
|
|
221
|
-
// Use TagBuilder to create HTML elements safely
|
|
222
|
-
return myMapper.tag("div")
|
|
223
|
-
.attributes({ class: `alert ${type}` })
|
|
224
|
-
.body(content);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
export default myMapper;
|
|
228
|
-
```
|
|
229
|
-
> [!WARNING]
|
|
230
|
-
> The `.body()` and `.selfClose()` methods return the final HTML string. You must treat them as the end of the builder chain. If you forget to call them, you will return a builder object instead of a string.
|
|
231
|
-
|
|
232
|
-
You also skip tag builder and use raw HTML.
|
|
233
|
-
|
|
234
|
-
```javascript
|
|
235
|
-
import { Mapper } from "sommark";
|
|
236
|
-
const myMapper = new Mapper();
|
|
237
|
-
|
|
238
|
-
myMapper.register("Alert", ({ args, content }) => {
|
|
239
|
-
// Access arguments by index or name
|
|
240
|
-
const type = args[0] || args.type || "info";
|
|
241
|
-
|
|
242
|
-
// Use raw HTML
|
|
243
|
-
return `<div class="alert ${type}">${content}</div>`;
|
|
244
|
-
});
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Mapping Multiple Identifiers
|
|
248
|
-
|
|
249
|
-
You can use an array of strings to map multiple names to the same output function.
|
|
250
|
-
|
|
251
|
-
```javascript
|
|
252
|
-
import { Mapper } from "sommark";
|
|
253
|
-
const myMapper = new Mapper();
|
|
254
|
-
const { tag } = myMapper;
|
|
255
|
-
|
|
256
|
-
// Both [code] and [Code] will use this mapper
|
|
257
|
-
myMapper.register(["code", "Code"], ({ content }) => {
|
|
258
|
-
return tag("pre").body(tag("code").body(content));
|
|
259
|
-
});
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
## Reusing Existing Mappers
|
|
263
|
-
|
|
264
|
-
You can borrow rules from default mappers to avoid rewriting them.
|
|
265
|
-
|
|
266
|
-
```javascript
|
|
267
|
-
import { Mapper, HTML } from "sommark";
|
|
268
|
-
const myMapper = new Mapper();
|
|
269
|
-
|
|
270
|
-
// Reuse the "Code" block from the default HTML mapper
|
|
271
|
-
const codeOutput = HTML.get("Code");
|
|
272
|
-
if (codeOutput) {
|
|
273
|
-
myMapper.register(codeOutput.id, codeOutput.render, codeOutput.options);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Add your own custom blocks...
|
|
277
|
-
myMapper.register("MyBlock", ({ content }) => {
|
|
278
|
-
return content;
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
export default myMapper;
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
## Using Rules (Validation)
|
|
285
|
-
|
|
286
|
-
You can force strict rules on your content. If a rule is broken, SomMark will stop and show an error.
|
|
287
|
-
|
|
288
|
-
### Argument Validation (`args`)
|
|
289
|
-
|
|
290
|
-
Validates the arguments passed to the tag.
|
|
291
|
-
|
|
292
|
-
```javascript
|
|
293
|
-
myMapper.register("User", ({ args }) => {
|
|
294
|
-
return tag("div").body(`User: ${args[0]}`);
|
|
295
|
-
}, {
|
|
296
|
-
rules: {
|
|
297
|
-
args: {
|
|
298
|
-
min: 1, // Must have at least 1 argument
|
|
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
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
```
|
|
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]`
|
|
347
|
-
|
|
348
|
-
## Using Options
|
|
349
|
-
|
|
350
|
-
Options change how SomMark processes the content inside the block.
|
|
351
|
-
|
|
352
|
-
```javascript
|
|
353
|
-
import { Mapper } from "sommark";
|
|
354
|
-
const myMapper = new Mapper();
|
|
355
|
-
const { tag } = myMapper;
|
|
356
|
-
|
|
357
|
-
myMapper.register("Code", ({ content }) => {
|
|
358
|
-
return tag("pre").body(content);
|
|
359
|
-
}, {
|
|
360
|
-
escape: false,
|
|
361
|
-
rules: {
|
|
362
|
-
args: {
|
|
363
|
-
min: 1,
|
|
364
|
-
required: ["id"]
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}); // options
|
|
368
|
-
```
|
|
369
|
-
---
|
|
370
|
-
|
|
371
|
-
# License
|
|
372
|
-
|
|
373
|
-
MIT
|
package/cli/cli.mjs
CHANGED
|
@@ -3,6 +3,8 @@ import { getHelp } from "./commands/help.js";
|
|
|
3
3
|
import { printVersion, printHeader } from "./commands/version.js";
|
|
4
4
|
import { runBuild } from "./commands/build.js";
|
|
5
5
|
import { printOutput } from "./commands/print.js";
|
|
6
|
+
import { runInit } from "./commands/init.js";
|
|
7
|
+
import { runShow } from "./commands/show.js";
|
|
6
8
|
import { extensions } from "./constants.js";
|
|
7
9
|
|
|
8
10
|
// ========================================================================== //
|
|
@@ -33,7 +35,19 @@ async function main() {
|
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
// 4.
|
|
38
|
+
// 4. Init
|
|
39
|
+
if (command === "init") {
|
|
40
|
+
await runInit();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 5. Show
|
|
45
|
+
if (command === "show") {
|
|
46
|
+
await runShow(args[1]);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 6. Print to Console ( -p or --print )
|
|
37
51
|
|
|
38
52
|
const format = command ? command.replace(/^--/, "") : "";
|
|
39
53
|
|
package/cli/commands/help.js
CHANGED
|
@@ -13,6 +13,8 @@ export function getHelp(unknown_option = true) {
|
|
|
13
13
|
"{N}{N}<$yellow:Global Options:$>",
|
|
14
14
|
"{N} <$green:-h, --help$> <$cyan: Show help message$>",
|
|
15
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$>",
|
|
16
18
|
|
|
17
19
|
"{N}{N}<$yellow:Transpilation Options:$>",
|
|
18
20
|
"{N}<$yellow:Usage:$> <$blue:sommark [option] [targetFile] [option] [outputFile] [outputDir]$>",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { cliError, formatMessage } from "../../core/errors.js";
|
|
5
|
+
|
|
6
|
+
// ========================================================================== //
|
|
7
|
+
// Init Command //
|
|
8
|
+
// ========================================================================== //
|
|
9
|
+
|
|
10
|
+
export function getConfigDir() {
|
|
11
|
+
const homeDir = os.homedir();
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
return path.join(process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"), "sommark");
|
|
14
|
+
} else if (process.platform === "darwin") {
|
|
15
|
+
return path.join(homeDir, "Library", "Application Support", "sommark");
|
|
16
|
+
} else {
|
|
17
|
+
return path.join(process.env.XDG_CONFIG_HOME || path.join(homeDir, ".config"), "sommark");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function runInit() {
|
|
22
|
+
try {
|
|
23
|
+
const configDir = getConfigDir();
|
|
24
|
+
const pluginsDir = path.join(configDir, "plugins");
|
|
25
|
+
const configFilePath = path.join(configDir, "smark.config.js");
|
|
26
|
+
|
|
27
|
+
// ======================================================
|
|
28
|
+
// Create directories
|
|
29
|
+
// ======================================================
|
|
30
|
+
|
|
31
|
+
await fs.mkdir(pluginsDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
// ======================================================
|
|
34
|
+
// Default configuration content
|
|
35
|
+
// ======================================================
|
|
36
|
+
|
|
37
|
+
const defaultConfigContent = `export default {
|
|
38
|
+
outputDir: "",
|
|
39
|
+
outputFile: "output",
|
|
40
|
+
mappingFile: "",
|
|
41
|
+
};
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
// ======================================================
|
|
45
|
+
// Check if config file already exists
|
|
46
|
+
// ======================================================
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await fs.access(configFilePath);
|
|
50
|
+
console.log(formatMessage(`<$yellow:Configuration already exists at:$> <$cyan:${configFilePath}$>`));
|
|
51
|
+
} catch {
|
|
52
|
+
// ======================================================
|
|
53
|
+
// Create default config file if it doesn't exist
|
|
54
|
+
// ======================================================
|
|
55
|
+
|
|
56
|
+
await fs.writeFile(configFilePath, defaultConfigContent, "utf-8");
|
|
57
|
+
console.log(formatMessage(`<$green:Initialized SomMark configuration at:$> <$cyan:${configFilePath}$>`));
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
cliError([
|
|
61
|
+
`{line}<$red:Failed to initialize SomMark configuration:$> <$magenta:${error.message}$>{line}`
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { cliError, formatMessage } from "../../core/errors.js";
|
|
4
|
+
import { getConfigDir } from "./init.js";
|
|
5
|
+
|
|
6
|
+
// ========================================================================== //
|
|
7
|
+
// Show Command //
|
|
8
|
+
// ========================================================================== //
|
|
9
|
+
|
|
10
|
+
export async function runShow(target) {
|
|
11
|
+
if (target === "config") {
|
|
12
|
+
try {
|
|
13
|
+
const configDir = getConfigDir();
|
|
14
|
+
const configFilePath = path.join(configDir, "smark.config.js");
|
|
15
|
+
const pluginsDir = path.join(configDir, "plugins");
|
|
16
|
+
//========================================================================== //
|
|
17
|
+
// Helper to check existence and format message //
|
|
18
|
+
//========================================================================== //
|
|
19
|
+
const checkPath = async (itemPath, isDir = false) => {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(itemPath);
|
|
22
|
+
return `<$green:${itemPath}$> <$cyan:(Exists)$>`;
|
|
23
|
+
} catch {
|
|
24
|
+
return `<$red:${itemPath}$> <$yellow:(Not Found)$>`;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const configStatus = await checkPath(configFilePath);
|
|
29
|
+
const pluginsStatus = await checkPath(pluginsDir, true);
|
|
30
|
+
|
|
31
|
+
console.log(formatMessage(`{N}<$yellow:SomMark Configuration Files:$>{N}`));
|
|
32
|
+
console.log(formatMessage(` <$magenta:Config File:$> ${configStatus}`));
|
|
33
|
+
console.log(formatMessage(` <$magenta:Plugins Dir:$> ${pluginsStatus}{N}`));
|
|
34
|
+
|
|
35
|
+
} catch (error) {
|
|
36
|
+
cliError([
|
|
37
|
+
`{line}<$red:Failed to retrieve configuration paths:$> <$magenta:${error.message}$>{line}`
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
cliError([
|
|
42
|
+
`{line}<$red:Invalid target for 'show' command:$> <$blue:'${target || ""}'$> `,
|
|
43
|
+
`<$yellow:Usage:$> <$cyan:sommark show config$>{line}`
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/cli/commands/version.js
CHANGED
|
@@ -14,7 +14,7 @@ export function printHeader() {
|
|
|
14
14
|
console.log(
|
|
15
15
|
[
|
|
16
16
|
`SomMark-${projectJson.version}`,
|
|
17
|
-
"SomMark is a structural markup language for writing structured
|
|
17
|
+
"SomMark is a structural markup language for writing structured content.",
|
|
18
18
|
"Copyright (C) Adam Elmi",
|
|
19
19
|
"Github: https://github.com/Adam-Elmi/SomMark"
|
|
20
20
|
]
|
package/cli/helpers/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import { isExist } from "./file.js";
|
|
4
|
+
import { getConfigDir } from "../commands/init.js";
|
|
4
5
|
|
|
5
6
|
// ========================================================================== //
|
|
6
7
|
// Configuration Loader //
|
|
@@ -8,7 +9,7 @@ import { isExist } from "./file.js";
|
|
|
8
9
|
|
|
9
10
|
const CONFIG_FILE_NAME = "smark.config.js";
|
|
10
11
|
const currentDir = process.cwd();
|
|
11
|
-
const
|
|
12
|
+
const localConfigPath = path.join(currentDir, CONFIG_FILE_NAME);
|
|
12
13
|
|
|
13
14
|
// ========================================================================== //
|
|
14
15
|
// Default Configuration //
|
|
@@ -16,7 +17,6 @@ const configPath = path.join(currentDir, CONFIG_FILE_NAME);
|
|
|
16
17
|
let config = {
|
|
17
18
|
outputFile: "output",
|
|
18
19
|
outputDir: "",
|
|
19
|
-
mode: "default",
|
|
20
20
|
mappingFile: ""
|
|
21
21
|
};
|
|
22
22
|
|
|
@@ -24,13 +24,22 @@ let config = {
|
|
|
24
24
|
// Load Configuration //
|
|
25
25
|
// ========================================================================== //
|
|
26
26
|
export async function loadConfig() {
|
|
27
|
-
|
|
27
|
+
const userConfigPath = path.join(getConfigDir(), CONFIG_FILE_NAME);
|
|
28
|
+
let targetConfigPath = null;
|
|
29
|
+
|
|
30
|
+
if (await isExist(userConfigPath)) {
|
|
31
|
+
targetConfigPath = userConfigPath;
|
|
32
|
+
} else if (await isExist(localConfigPath)) {
|
|
33
|
+
targetConfigPath = localConfigPath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (targetConfigPath) {
|
|
28
37
|
try {
|
|
29
|
-
const configURL = pathToFileURL(
|
|
38
|
+
const configURL = pathToFileURL(targetConfigPath).href;
|
|
30
39
|
const loadedModule = await import(configURL);
|
|
31
40
|
config = loadedModule.default || loadedModule;
|
|
32
41
|
} catch (error) {
|
|
33
|
-
console.error(`Error loading configuration file ${
|
|
42
|
+
console.error(`Error loading configuration file ${targetConfigPath}:`, error.message);
|
|
34
43
|
}
|
|
35
44
|
} else {
|
|
36
45
|
// console.log(`${CONFIG_FILE_NAME} not found. Using default configuration.`);
|
package/core/formats.js
CHANGED
|
@@ -2,8 +2,9 @@ const formats = {
|
|
|
2
2
|
textFormat: "text",
|
|
3
3
|
htmlFormat: "html",
|
|
4
4
|
markdownFormat: "markdown",
|
|
5
|
-
mdxFormat: "mdx"
|
|
5
|
+
mdxFormat: "mdx",
|
|
6
|
+
jsonFormat: "json"
|
|
6
7
|
};
|
|
7
8
|
|
|
8
|
-
export const {textFormat, htmlFormat, markdownFormat, mdxFormat} = formats;
|
|
9
|
+
export const {textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat} = formats;
|
|
9
10
|
export default formats;
|
package/core/lexer.js
CHANGED
|
@@ -67,7 +67,9 @@ function concatText(input, index, scope_state, extraConditions = []) {
|
|
|
67
67
|
text += char;
|
|
68
68
|
}
|
|
69
69
|
if (!text) {
|
|
70
|
-
sommarkError([
|
|
70
|
+
sommarkError([
|
|
71
|
+
`{line}From: <$magenta:concatText function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${text}'$>{line}`
|
|
72
|
+
]);
|
|
71
73
|
}
|
|
72
74
|
return text;
|
|
73
75
|
} else {
|
|
@@ -117,7 +119,9 @@ function concatEscape(input, index) {
|
|
|
117
119
|
sommarkError(["{line}<$red:Next character is not found:$> <$yellow:There is no character after escape character!$>{line}"]);
|
|
118
120
|
}
|
|
119
121
|
if (!str) {
|
|
120
|
-
sommarkError([
|
|
122
|
+
sommarkError([
|
|
123
|
+
`{line}From: <$magenta:concatEscape function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`
|
|
124
|
+
]);
|
|
121
125
|
}
|
|
122
126
|
if (WHITESPACE_SET.has(str[1])) {
|
|
123
127
|
const matchedCharacter = Array.from(WHITESPACE_SET).find(ch => ch === str[1]);
|
|
@@ -158,7 +162,9 @@ function concatChar(input, index, stop_at_char) {
|
|
|
158
162
|
]);
|
|
159
163
|
}
|
|
160
164
|
if (!str) {
|
|
161
|
-
sommarkError([
|
|
165
|
+
sommarkError([
|
|
166
|
+
`{line}From: <$magenta:concatChar function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`
|
|
167
|
+
]);
|
|
162
168
|
}
|
|
163
169
|
return str;
|
|
164
170
|
} else {
|
|
@@ -185,10 +191,10 @@ function lexer(src) {
|
|
|
185
191
|
tokens.push({ type, value, line, start, end, depth: depth_stack.length });
|
|
186
192
|
}
|
|
187
193
|
|
|
188
|
-
const updateMetadata =
|
|
194
|
+
const updateMetadata = text => {
|
|
189
195
|
const newlines = updateNewLine(text) || 0;
|
|
190
196
|
if (newlines > 0) {
|
|
191
|
-
const lines = text.split(
|
|
197
|
+
const lines = text.split("\n");
|
|
192
198
|
const lastLineLength = lines[lines.length - 1].length;
|
|
193
199
|
start = end + 1;
|
|
194
200
|
end = lastLineLength;
|
|
@@ -431,7 +437,13 @@ function lexer(src) {
|
|
|
431
437
|
// ========================================================================== //
|
|
432
438
|
// Token: Block Value //
|
|
433
439
|
// ========================================================================== //
|
|
434
|
-
else if (
|
|
440
|
+
else if (
|
|
441
|
+
(previous_value === "=" ||
|
|
442
|
+
previous_value === BLOCKCOMMA ||
|
|
443
|
+
previous_value === BLOCKCOLON ||
|
|
444
|
+
previous_value === block_value) &&
|
|
445
|
+
!scope_state
|
|
446
|
+
) {
|
|
435
447
|
temp_str = concatChar(src, i, ["]", "\\", ",", ":"]);
|
|
436
448
|
i += temp_str.length - 1;
|
|
437
449
|
const nextToken = peek(src, i, 1);
|
|
@@ -484,12 +496,7 @@ function lexer(src) {
|
|
|
484
496
|
previous_value === inline_value) &&
|
|
485
497
|
!scope_state
|
|
486
498
|
) {
|
|
487
|
-
temp_str = concatChar(src, i, [
|
|
488
|
-
")",
|
|
489
|
-
"\\",
|
|
490
|
-
",",
|
|
491
|
-
previous_value === INLINECOLON ? ":" : null
|
|
492
|
-
]);
|
|
499
|
+
temp_str = concatChar(src, i, [")", "\\", ",", previous_value === INLINECOLON ? ":" : null]);
|
|
493
500
|
i += temp_str.length - 1;
|
|
494
501
|
// Update Metadata
|
|
495
502
|
updateMetadata(temp_str);
|
|
@@ -588,7 +595,7 @@ function lexer(src) {
|
|
|
588
595
|
return tokens;
|
|
589
596
|
} else {
|
|
590
597
|
lexerError([
|
|
591
|
-
`{line}<$red:Invalid SomMark syntax:$> <$yellow:Expected source input to be a string, got$> <$blue: '${typeof src}'
|
|
598
|
+
`{line}<$red:Invalid SomMark syntax:$> ${src === "" ? "<$yellow: Got empty string '' $>" : `<$yellow:Expected source input to be a string, got$> <$blue: '${typeof src}'$>`}{line}`
|
|
592
599
|
]);
|
|
593
600
|
}
|
|
594
601
|
}
|