sommark 2.1.1 → 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 +14 -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/cli/helpers/transpile.js +7 -9
- 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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import Mapper from "../mapper.js";
|
|
2
|
+
import { TEXT } from "../../core/labels.js";
|
|
3
|
+
import { transpilerError } from "../../core/errors.js";
|
|
4
|
+
|
|
5
|
+
const Json = new Mapper();
|
|
6
|
+
|
|
7
|
+
// ========================================================================== //
|
|
8
|
+
// Helpers //
|
|
9
|
+
// ========================================================================== //
|
|
10
|
+
|
|
11
|
+
function escapeString(str) {
|
|
12
|
+
return JSON.stringify(str);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function processNode(node, parentType = null) {
|
|
16
|
+
if (!node) return "";
|
|
17
|
+
|
|
18
|
+
if (node.id === "Object" || node.id === "Array" || node.id === "Json") {
|
|
19
|
+
return renderBlock(node, parentType);
|
|
20
|
+
} else if (["string", "number", "bool", "null", "array", "none"].includes(node.id)) {
|
|
21
|
+
return renderInline(node, parentType);
|
|
22
|
+
} else if (node.type === TEXT) {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderBlock(node, parentType) {
|
|
29
|
+
let output = "";
|
|
30
|
+
let key = "";
|
|
31
|
+
let isRoot = node.id === "Json";
|
|
32
|
+
let type = node.id === "Array" || (isRoot && node.args.includes("array")) ? "array" : "object";
|
|
33
|
+
|
|
34
|
+
// ========================================================================== //
|
|
35
|
+
// Key //
|
|
36
|
+
// ========================================================================== //
|
|
37
|
+
if (!isRoot) {
|
|
38
|
+
if (parentType === "object") {
|
|
39
|
+
key = node.args && node.args[0] ? escapeString(node.args[0]) : null;
|
|
40
|
+
if (!key) {
|
|
41
|
+
key = '"unknown_key"';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ========================================================================== //
|
|
47
|
+
// Children //
|
|
48
|
+
// ========================================================================== //
|
|
49
|
+
let children = [];
|
|
50
|
+
if (node.body && node.body.length > 0) {
|
|
51
|
+
for (const child of node.body) {
|
|
52
|
+
const childOutput = processNode(child, type);
|
|
53
|
+
if (childOutput) {
|
|
54
|
+
children.push(childOutput);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let content = children.join(",");
|
|
60
|
+
let wrapper = type === "array" ? `[${content}]` : `{${content}}`;
|
|
61
|
+
|
|
62
|
+
if (key) {
|
|
63
|
+
return `${key}:${wrapper}`;
|
|
64
|
+
} else {
|
|
65
|
+
if (parentType === "object") {
|
|
66
|
+
if (!node.args || !node.args[0]) {
|
|
67
|
+
transpilerError([`{line}<$red:JSON Error:$> <$yellow:Blocks inside an Object must have a key argument.$>{line}`]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return wrapper;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderInline(node, parentType) {
|
|
75
|
+
let key = null;
|
|
76
|
+
let value = "";
|
|
77
|
+
|
|
78
|
+
// ========================================================================== //
|
|
79
|
+
// Value //
|
|
80
|
+
// ========================================================================== //
|
|
81
|
+
if (node.id === "string") {
|
|
82
|
+
if (!node.args || node.args.length === 0) {
|
|
83
|
+
transpilerError([`{line}<$red:JSON Error:$> <$yellow:String inline must have a value.$>{line}`]);
|
|
84
|
+
}
|
|
85
|
+
value = escapeString(node.args[0] || "");
|
|
86
|
+
} else if (node.id === "number") {
|
|
87
|
+
if (!node.args || node.args.length === 0 || isNaN(Number(node.args[0]))) {
|
|
88
|
+
transpilerError([`{line}<$red:JSON Error:$> <$yellow:Invalid or missing number value for inline.$>{line}`]);
|
|
89
|
+
}
|
|
90
|
+
value = node.args[0];
|
|
91
|
+
} else if (node.id === "bool") {
|
|
92
|
+
if (!node.args || (node.args[0] !== "true" && node.args[0] !== "false")) {
|
|
93
|
+
transpilerError([`{line}<$red:JSON Error:$> <$yellow:Bool inline must be 'true' or 'false'.$>{line}`]);
|
|
94
|
+
}
|
|
95
|
+
value = node.args[0] === "true" ? "true" : "false";
|
|
96
|
+
} else if (node.id === "null") {
|
|
97
|
+
value = "null";
|
|
98
|
+
} else if (node.id === "array") {
|
|
99
|
+
// ========================================================================== //
|
|
100
|
+
// Inline array //
|
|
101
|
+
// ========================================================================== //
|
|
102
|
+
// (data)->(array: 1, 2, 3)
|
|
103
|
+
// args = ["1", " 2", " 3"]
|
|
104
|
+
const items = node.args.map(arg => {
|
|
105
|
+
const trimmed = arg.trim();
|
|
106
|
+
if (trimmed === "null") return "null";
|
|
107
|
+
if (trimmed === "true" || trimmed === "false") return trimmed;
|
|
108
|
+
if (!isNaN(parseFloat(trimmed)) && isFinite(trimmed)) return trimmed;
|
|
109
|
+
return escapeString(trimmed);
|
|
110
|
+
});
|
|
111
|
+
value = `[${items.join(",")}]`;
|
|
112
|
+
} else if (node.id === "none") {
|
|
113
|
+
// Special case: (-)->(none: val)
|
|
114
|
+
if (parentType === "object") {
|
|
115
|
+
transpilerError([
|
|
116
|
+
`{line}<$red:JSON Error:$> <$yellow:'none' inline is not allowed directly inside an Object. It must be inside an Array.$>{line}`
|
|
117
|
+
]);
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// (-)->(none: 1, 2, null) -> [1, 2, null] -> args.length > 1
|
|
122
|
+
// (-)->(none: true) -> true -> args.length == 1
|
|
123
|
+
|
|
124
|
+
if (node.args.length > 1) {
|
|
125
|
+
const items = node.args.map(arg => {
|
|
126
|
+
const trimmed = arg.trim();
|
|
127
|
+
if (trimmed === "null") return "null";
|
|
128
|
+
if (trimmed === "true" || trimmed === "false") return trimmed;
|
|
129
|
+
if (!isNaN(parseFloat(trimmed)) && isFinite(trimmed)) return trimmed;
|
|
130
|
+
return escapeString(trimmed);
|
|
131
|
+
});
|
|
132
|
+
value = `[${items.join(",")}]`;
|
|
133
|
+
} else {
|
|
134
|
+
const arg = node.args[0] || "";
|
|
135
|
+
const trimmed = arg.trim();
|
|
136
|
+
if (trimmed === "null") value = "null";
|
|
137
|
+
else if (trimmed === "true" || trimmed === "false") value = trimmed;
|
|
138
|
+
else if (!isNaN(parseFloat(trimmed)) && isFinite(trimmed)) value = trimmed;
|
|
139
|
+
else value = escapeString(trimmed);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (parentType === "object") {
|
|
144
|
+
if (node.id === "none") return "";
|
|
145
|
+
|
|
146
|
+
if (!node.value) {
|
|
147
|
+
transpilerError([
|
|
148
|
+
`{line}<$red:JSON Error:$> <$yellow:Inline elements inside an Object must have an identifier (key).$>{line}`
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
key = escapeString(node.value);
|
|
153
|
+
return `${key}:${value}`;
|
|
154
|
+
} else {
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ========================================================================== //
|
|
160
|
+
// Main Registration //
|
|
161
|
+
// ========================================================================== //
|
|
162
|
+
|
|
163
|
+
const noop = () => "";
|
|
164
|
+
Json.register(["Object", "Array"], noop);
|
|
165
|
+
Json.register(["string", "number", "bool", "null", "array", "none"], noop);
|
|
166
|
+
|
|
167
|
+
Json.register("Json", ({ args, content, ast }) => {
|
|
168
|
+
if (!ast) return "";
|
|
169
|
+
return processNode(ast, null);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export default Json;
|
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
import Mapper from "../mapper.js";
|
|
2
2
|
const MARKDOWN = new Mapper();
|
|
3
|
-
const { md } = MARKDOWN;
|
|
3
|
+
const { md, safeArg } = MARKDOWN;
|
|
4
4
|
// Block
|
|
5
|
-
MARKDOWN.register("Block", ({ content }) => {
|
|
6
|
-
return content;
|
|
7
|
-
});
|
|
8
|
-
// Section
|
|
9
|
-
MARKDOWN.register("Section", ({ content }) => {
|
|
5
|
+
MARKDOWN.register(["Block", "block", "Section", "section"], ({ content }) => {
|
|
10
6
|
return content;
|
|
11
7
|
});
|
|
12
8
|
// Headings
|
|
13
|
-
MARKDOWN.register(
|
|
14
|
-
|
|
15
|
-
})
|
|
9
|
+
MARKDOWN.register(
|
|
10
|
+
["Heading", "heading"],
|
|
11
|
+
({ args, content }) => {
|
|
12
|
+
const level = safeArg(args, 0, "level", "number", Number, 1);
|
|
13
|
+
const title = safeArg(args, 1, "title", null, null, "");
|
|
14
|
+
return md.heading(title, level) + content;
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
rules: {
|
|
18
|
+
type: "Block"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
);
|
|
16
22
|
// Inline Headings
|
|
17
|
-
["h1", "h2", "h3", "h4", "h5", "h6"].forEach(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
["h1", "h2", "h3", "h4", "h5", "h6"].forEach(
|
|
24
|
+
heading => {
|
|
25
|
+
MARKDOWN.register(heading, ({ content }) => {
|
|
26
|
+
const lvl = heading[1] && typeof Number(heading[1]) === "number" ? heading[1] : 1;
|
|
27
|
+
return md.heading(content, lvl);
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
rules: {
|
|
32
|
+
type: "Inline"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
22
36
|
// Bold
|
|
23
37
|
MARKDOWN.register(["bold", "b"], ({ content }) => {
|
|
24
38
|
return md.bold(content);
|
|
@@ -35,26 +49,59 @@ MARKDOWN.register(["emphasis", "e"], ({ content }) => {
|
|
|
35
49
|
MARKDOWN.register(
|
|
36
50
|
["code", "Code", "codeBlock", "CodeBlock"],
|
|
37
51
|
({ args, content }) => {
|
|
38
|
-
|
|
52
|
+
const lang = safeArg(args, 0, "lang", null, null, "text");
|
|
53
|
+
return md.codeBlock(content, lang);
|
|
39
54
|
},
|
|
40
|
-
{
|
|
55
|
+
{
|
|
56
|
+
escape: false,
|
|
57
|
+
rules: {
|
|
58
|
+
type: "AtBlock"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
41
61
|
);
|
|
42
62
|
// Link
|
|
43
|
-
MARKDOWN.register(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
MARKDOWN.register(
|
|
64
|
+
["link", "Link"],
|
|
65
|
+
({ args, content }) => {
|
|
66
|
+
const url = safeArg(args, 0, "url", null, null, "");
|
|
67
|
+
const title = safeArg(args, 1, "title", null, null, "");
|
|
68
|
+
return md.url("link", content, url, title);
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
rules: {
|
|
72
|
+
type: "Inline"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
);
|
|
47
76
|
// Image
|
|
48
|
-
MARKDOWN.register(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
MARKDOWN.register(
|
|
78
|
+
["image", "Image"],
|
|
79
|
+
({ args, content }) => {
|
|
80
|
+
const url = safeArg(args, 0, "url", null, null, "");
|
|
81
|
+
const title = safeArg(args, 1, "title", null, null, "");
|
|
82
|
+
return md.url("image", content, url, title);
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
rules: {
|
|
86
|
+
type: "Inline"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
);
|
|
52
90
|
// Horizontal Rule
|
|
53
|
-
MARKDOWN.register(
|
|
54
|
-
|
|
55
|
-
})
|
|
91
|
+
MARKDOWN.register(
|
|
92
|
+
["horizontal", "hr", "h"],
|
|
93
|
+
({ args }) => {
|
|
94
|
+
const fmt = safeArg(args, 0, undefined, null, null, "*");
|
|
95
|
+
return md.horizontal(fmt);
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
rules: {
|
|
99
|
+
type: "Block"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
);
|
|
56
103
|
// Escape Characters
|
|
57
|
-
MARKDOWN.register(["escape", "s"], ({ content }) => {
|
|
104
|
+
MARKDOWN.register(["escape", "Escape", "s"], ({ content }) => {
|
|
58
105
|
return md.escape(content);
|
|
59
106
|
});
|
|
60
107
|
// Table
|
|
@@ -70,7 +117,7 @@ MARKDOWN.register(
|
|
|
70
117
|
.map(line => line.trim())
|
|
71
118
|
);
|
|
72
119
|
},
|
|
73
|
-
{ escape: false }
|
|
120
|
+
{ escape: false, rules: { type: "AtBlock" } }
|
|
74
121
|
);
|
|
75
122
|
// List
|
|
76
123
|
MARKDOWN.register(
|
|
@@ -78,11 +125,12 @@ MARKDOWN.register(
|
|
|
78
125
|
({ content }) => {
|
|
79
126
|
return content;
|
|
80
127
|
},
|
|
81
|
-
{ escape: false }
|
|
128
|
+
{ escape: false, rules: { type: "AtBlock" } }
|
|
82
129
|
);
|
|
83
130
|
// Todo
|
|
84
131
|
MARKDOWN.register("todo", ({ args, content }) => {
|
|
85
132
|
const checked = content === "x" ? true : false;
|
|
86
|
-
|
|
133
|
+
const task = safeArg(args, 0, "task", null, null, "");
|
|
134
|
+
return md.todo(checked, task);
|
|
87
135
|
});
|
|
88
136
|
export default MARKDOWN;
|
package/mappers/mapper.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import TagBuilder from "../formatter/tag.js";
|
|
2
2
|
import MarkdownBuilder from "../formatter/mark.js";
|
|
3
|
-
import { highlightCode } from "../lib/highlight.js";
|
|
4
3
|
import escapeHTML from "../helpers/escapeHTML.js";
|
|
5
4
|
import atomOneDark from "../helpers/defaultTheme.js";
|
|
6
5
|
import loadCss from "../helpers/loadCss.js";
|
|
6
|
+
import { sommarkError } from "../core/errors.js";
|
|
7
7
|
|
|
8
8
|
class Mapper {
|
|
9
9
|
#customHeaderContent;
|
|
@@ -28,15 +28,16 @@ class Mapper {
|
|
|
28
28
|
|
|
29
29
|
this.#customHeaderContent = "";
|
|
30
30
|
|
|
31
|
-
this.highlightCode =
|
|
31
|
+
this.highlightCode = null;
|
|
32
32
|
this.escapeHTML = escapeHTML;
|
|
33
33
|
this.styles = [];
|
|
34
34
|
this.env = "node";
|
|
35
35
|
// Theme Registry
|
|
36
36
|
this.themes = {
|
|
37
|
+
"sommark-default": "pre{padding: 5px; background-color: #f6f8fa; font-family: monospace}",
|
|
37
38
|
"atom-one-dark": atomOneDark
|
|
38
39
|
};
|
|
39
|
-
this.currentTheme = "atom-one-dark";
|
|
40
|
+
this.currentTheme = this.highlightCode && typeof this.highlightCode === "function" ? "atom-one-dark" : "sommark-default";
|
|
40
41
|
this.enable_table_styles = true;
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -171,12 +172,15 @@ class Mapper {
|
|
|
171
172
|
// Formatters //
|
|
172
173
|
// ========================================================================== //
|
|
173
174
|
code = (args, content) => {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
const lang = this.safeArg(args, 0, "lang", null, null, "text");
|
|
176
|
+
const code = content || "";
|
|
177
|
+
let value = content;
|
|
178
|
+
const code_element = this.tag("code");
|
|
179
|
+
if (this.highlightCode && typeof this.highlightCode === "function") {
|
|
180
|
+
code_element.attributes({ class: `hljs language-${lang}` });
|
|
181
|
+
value = this.highlightCode(code, lang);
|
|
182
|
+
}
|
|
183
|
+
return this.tag("pre").body(code_element.body(value));
|
|
180
184
|
};
|
|
181
185
|
htmlTable = (data, headers, defaultStyle = true) => {
|
|
182
186
|
const isAddedStyle = this.styles.some(s => s.includes(".sommark-table"));
|
|
@@ -312,5 +316,47 @@ class Mapper {
|
|
|
312
316
|
todo(checked = false) {
|
|
313
317
|
return checked.trim() === "x" || checked.trim().toLowerCase() === "done" ? true : false;
|
|
314
318
|
}
|
|
319
|
+
safeArg = (args, index, key, type = null, setType = null, fallBack = null) => {
|
|
320
|
+
if (!Array.isArray(args)) {
|
|
321
|
+
sommarkError([`{line}<$red:TypeError:$> <$yellow:args must be an array$>{line}`]);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (index === undefined && key === undefined) {
|
|
325
|
+
sommarkError([`{line}<$red:ReferenceError:> <$yellow:At least one of 'index' or 'key' must be provided$>{line}`]);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (index !== undefined && typeof index !== "number") {
|
|
329
|
+
sommarkError([`{line}<$red:TypeError:$> <$yellow:index must be a number$>{line}`]);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (key !== undefined && typeof key !== "string") {
|
|
333
|
+
sommarkError([`{line}<$red:TypeError:$> <$yellow:key must be a string$>{line}`]);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (type !== null && typeof type !== "string") {
|
|
337
|
+
sommarkError([`{line}<$red:TypeError:$> <$yellow:type must be a string$>{line}`]);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (setType !== null && typeof setType !== "function") {
|
|
341
|
+
sommarkError([`{line}<$red:TypeError:$> <$yellow:setType must be a function$>{line}`]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const validate = value => {
|
|
345
|
+
if (value === undefined) return false;
|
|
346
|
+
if (!type) return true;
|
|
347
|
+
const evaluated = setType ? setType(value) : value;
|
|
348
|
+
return typeof evaluated === type;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
if (index !== undefined && validate(args[index])) {
|
|
352
|
+
return args[index];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (key !== undefined && validate(args[key])) {
|
|
356
|
+
return args[key];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return fallBack;
|
|
360
|
+
};
|
|
315
361
|
}
|
|
316
362
|
export default Mapper;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sommark",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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": {
|
|
@@ -42,10 +42,7 @@
|
|
|
42
42
|
"url": "https://github.com/Adam-Elmi/SomMark/issues"
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://github.com/Adam-Elmi/SomMark#readme",
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"highlight.js": "^11.11.1"
|
|
47
|
-
},
|
|
48
45
|
"devDependencies": {
|
|
49
46
|
"vitest": "^4.0.16"
|
|
50
47
|
}
|
|
51
|
-
}
|
|
48
|
+
}
|
package/lib/highlight.js
DELETED