sommark 2.3.2 → 3.0.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 +47 -42
- package/SOMMARK-SPEC.md +483 -0
- package/cli/cli.mjs +42 -2
- package/cli/commands/color.js +36 -0
- package/cli/commands/help.js +7 -0
- package/cli/commands/init.js +2 -0
- package/cli/commands/list.js +119 -0
- package/cli/commands/print.js +61 -11
- package/cli/commands/show.js +24 -27
- package/cli/constants.js +1 -1
- package/cli/helpers/config.js +14 -4
- package/cli/helpers/transpile.js +27 -32
- package/constants/html_props.js +100 -0
- package/constants/html_tags.js +146 -0
- package/constants/void_elements.js +26 -0
- package/core/lexer.js +70 -39
- package/core/parser.js +100 -84
- package/core/pluginManager.js +139 -0
- package/core/plugins/comment-remover.js +47 -0
- package/core/plugins/module-system.js +137 -0
- package/core/plugins/quote-escaper.js +37 -0
- package/core/plugins/raw-content-plugin.js +72 -0
- package/core/plugins/rules-validation-plugin.js +197 -0
- package/core/plugins/sommark-format.js +211 -0
- package/core/transpiler.js +65 -198
- package/debug.js +9 -4
- package/format.js +23 -0
- package/formatter/mark.js +3 -3
- package/formatter/tag.js +6 -2
- package/grammar.ebnf +5 -5
- package/helpers/camelize.js +2 -0
- package/helpers/colorize.js +20 -14
- package/helpers/kebabize.js +2 -0
- package/helpers/utils.js +161 -0
- package/index.js +243 -44
- package/mappers/languages/html.js +200 -105
- package/mappers/languages/json.js +23 -4
- package/mappers/languages/markdown.js +88 -67
- package/mappers/languages/mdx.js +130 -2
- package/mappers/mapper.js +77 -246
- package/package.json +7 -5
- package/unformatted.smark +90 -0
- package/v3-todo.smark +75 -0
- package/CHANGELOG.md +0 -119
- package/helpers/loadCss.js +0 -46
|
@@ -2,7 +2,6 @@ import Mapper from "../mapper.js";
|
|
|
2
2
|
import { TEXT } from "../../core/labels.js";
|
|
3
3
|
import { transpilerError } from "../../core/errors.js";
|
|
4
4
|
|
|
5
|
-
const Json = new Mapper();
|
|
6
5
|
|
|
7
6
|
// ========================================================================== //
|
|
8
7
|
// Helpers //
|
|
@@ -156,17 +155,37 @@ function renderInline(node, parentType) {
|
|
|
156
155
|
}
|
|
157
156
|
}
|
|
158
157
|
|
|
158
|
+
class JsonMapper extends Mapper {
|
|
159
|
+
constructor() {
|
|
160
|
+
super();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
formatOutput(output) {
|
|
164
|
+
try {
|
|
165
|
+
return JSON.parse(JSON.stringify(output));
|
|
166
|
+
} catch (e) {
|
|
167
|
+
transpilerError([
|
|
168
|
+
"{line}<$red:JSON Format Error:$> ",
|
|
169
|
+
`<$yellow:Failed to parse generated JSON output.$>{N}<$cyan:Reason: $> <$magenta:'${e.message}'$>{line}`
|
|
170
|
+
]);
|
|
171
|
+
return output;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const Json = new JsonMapper();
|
|
177
|
+
|
|
159
178
|
// ========================================================================== //
|
|
160
179
|
// Main Registration //
|
|
161
180
|
// ========================================================================== //
|
|
162
181
|
|
|
163
182
|
const noop = () => "";
|
|
164
|
-
Json.register(["Object", "Array"], noop);
|
|
165
|
-
Json.register(["string", "number", "bool", "null", "array", "none"], noop);
|
|
183
|
+
Json.register(["Object", "Array"], noop, { type: "Block" });
|
|
184
|
+
Json.register(["string", "number", "bool", "null", "array", "none"], noop, { type: "Inline" });
|
|
166
185
|
|
|
167
186
|
Json.register("Json", ({ args, content, ast }) => {
|
|
168
187
|
if (!ast) return "";
|
|
169
188
|
return processNode(ast, null);
|
|
170
|
-
});
|
|
189
|
+
}, { type: "Block" });
|
|
171
190
|
|
|
172
191
|
export default Json;
|
|
@@ -1,112 +1,126 @@
|
|
|
1
1
|
import Mapper from "../mapper.js";
|
|
2
|
-
|
|
2
|
+
import HTML from "./html.js";
|
|
3
|
+
import { todo } from "../../helpers/utils.js";
|
|
4
|
+
|
|
5
|
+
class MarkdownMapper extends Mapper {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
}
|
|
9
|
+
comment(text) {
|
|
10
|
+
return `<!--${text.replace("#", "")}-->\n`;
|
|
11
|
+
}
|
|
12
|
+
formatOutput(output, includeDocument) {
|
|
13
|
+
const todoRegex = /@@TODO_BLOCK:([\s\S]*?):([\s\S]*?)@@/g;
|
|
14
|
+
const statusMarkers = ["done", "x", "X", "-", ""];
|
|
15
|
+
return output.replace(todoRegex, (match, body, arg0) => {
|
|
16
|
+
const bodyTrimmed = body.trim().toLowerCase();
|
|
17
|
+
const arg0Trimmed = arg0.trim().toLowerCase();
|
|
18
|
+
|
|
19
|
+
const bodyIsStatus = statusMarkers.includes(bodyTrimmed);
|
|
20
|
+
const arg0IsStatus = statusMarkers.includes(arg0Trimmed);
|
|
21
|
+
|
|
22
|
+
let finalStatus = arg0; // Default: arg is status
|
|
23
|
+
let finalTask = body; // Default: body is task
|
|
24
|
+
|
|
25
|
+
if (bodyIsStatus && !arg0IsStatus) {
|
|
26
|
+
finalStatus = body;
|
|
27
|
+
finalTask = arg0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return this.md.todo(todo(finalStatus), finalTask);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const MARKDOWN = new MarkdownMapper();
|
|
36
|
+
MARKDOWN.inherit(HTML);
|
|
3
37
|
const { md, safeArg } = MARKDOWN;
|
|
4
38
|
// Block
|
|
5
|
-
MARKDOWN.register(
|
|
39
|
+
MARKDOWN.register("Block", ({ content }) => {
|
|
6
40
|
return content;
|
|
41
|
+
}, { type: "Block" });
|
|
42
|
+
// Quote
|
|
43
|
+
MARKDOWN.register(["quote", "blockquote"], ({ content }) => {
|
|
44
|
+
return "\n" + content.trimEnd()
|
|
45
|
+
.split("\n")
|
|
46
|
+
.map(line => `> ${line}`)
|
|
47
|
+
.join("\n");
|
|
48
|
+
}, { type: "Block" });
|
|
49
|
+
// Headings (Block only, like HTML mapper)
|
|
50
|
+
["h1", "h2", "h3", "h4", "h5", "h6"].forEach(heading => {
|
|
51
|
+
MARKDOWN.register(heading, ({ content }) => {
|
|
52
|
+
const lvl = heading[1] && typeof Number(heading[1]) === "number" ? heading[1] : 1;
|
|
53
|
+
return md.heading(content, lvl);
|
|
54
|
+
}, { type: "Block" });
|
|
7
55
|
});
|
|
8
|
-
// Headings
|
|
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
|
-
);
|
|
22
|
-
// Inline Headings
|
|
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
|
-
);
|
|
36
56
|
// Bold
|
|
37
|
-
MARKDOWN.register(
|
|
57
|
+
MARKDOWN.register("bold", ({ content }) => {
|
|
38
58
|
return md.bold(content);
|
|
39
|
-
});
|
|
59
|
+
}, { type: "any" });
|
|
40
60
|
// Italic
|
|
41
|
-
MARKDOWN.register(
|
|
61
|
+
MARKDOWN.register("italic", ({ content }) => {
|
|
42
62
|
return md.italic(content);
|
|
43
|
-
});
|
|
63
|
+
}, { type: "any" });
|
|
44
64
|
// Bold and Italic (emphasis)
|
|
45
|
-
MARKDOWN.register(
|
|
65
|
+
MARKDOWN.register("emphasis", ({ content }) => {
|
|
46
66
|
return md.emphasis(content);
|
|
47
|
-
});
|
|
67
|
+
}, { type: "any" });
|
|
48
68
|
// Code Blocks
|
|
49
69
|
MARKDOWN.register(
|
|
50
|
-
|
|
70
|
+
"Code",
|
|
51
71
|
({ args, content }) => {
|
|
52
72
|
const lang = safeArg(args, 0, "lang", null, null, "text");
|
|
53
73
|
return md.codeBlock(content, lang);
|
|
54
74
|
},
|
|
55
75
|
{
|
|
56
76
|
escape: false,
|
|
57
|
-
|
|
58
|
-
type: "AtBlock"
|
|
59
|
-
}
|
|
77
|
+
type: "AtBlock"
|
|
60
78
|
}
|
|
61
79
|
);
|
|
62
80
|
// Link
|
|
63
81
|
MARKDOWN.register(
|
|
64
|
-
|
|
65
|
-
({ args
|
|
66
|
-
const url = safeArg(args, 0, "
|
|
82
|
+
"link",
|
|
83
|
+
({ args }) => {
|
|
84
|
+
const url = safeArg(args, 0, "src", null, null, "");
|
|
67
85
|
const title = safeArg(args, 1, "title", null, null, "");
|
|
68
|
-
|
|
86
|
+
const text = safeArg(args, 2, "alt", null, null, "");
|
|
87
|
+
return md.url("link", text, url, title);
|
|
69
88
|
},
|
|
70
89
|
{
|
|
71
|
-
|
|
72
|
-
type: "Inline"
|
|
73
|
-
}
|
|
90
|
+
type: "Block"
|
|
74
91
|
}
|
|
75
92
|
);
|
|
76
93
|
// Image
|
|
77
94
|
MARKDOWN.register(
|
|
78
|
-
|
|
79
|
-
({ args
|
|
80
|
-
const url = safeArg(args, 0, "
|
|
95
|
+
"image",
|
|
96
|
+
({ args }) => {
|
|
97
|
+
const url = safeArg(args, 0, "src", null, null, "");
|
|
81
98
|
const title = safeArg(args, 1, "title", null, null, "");
|
|
82
|
-
|
|
99
|
+
const alt = safeArg(args, 2, "alt", null, null, "");
|
|
100
|
+
return md.url("image", alt, url, title);
|
|
83
101
|
},
|
|
84
102
|
{
|
|
85
|
-
|
|
86
|
-
type: "Inline"
|
|
87
|
-
}
|
|
103
|
+
type: "Block"
|
|
88
104
|
}
|
|
89
105
|
);
|
|
90
106
|
// Horizontal Rule
|
|
91
107
|
MARKDOWN.register(
|
|
92
|
-
|
|
108
|
+
"hr",
|
|
93
109
|
({ args }) => {
|
|
94
110
|
const fmt = safeArg(args, 0, undefined, null, null, "*");
|
|
95
111
|
return md.horizontal(fmt);
|
|
96
112
|
},
|
|
97
113
|
{
|
|
98
|
-
|
|
99
|
-
type: "Block"
|
|
100
|
-
}
|
|
114
|
+
type: "Block"
|
|
101
115
|
}
|
|
102
116
|
);
|
|
103
117
|
// Escape Characters
|
|
104
|
-
MARKDOWN.register(
|
|
118
|
+
MARKDOWN.register("escape", ({ content }) => {
|
|
105
119
|
return md.escape(content);
|
|
106
|
-
});
|
|
120
|
+
}, { type: "any" });
|
|
107
121
|
// Table
|
|
108
122
|
MARKDOWN.register(
|
|
109
|
-
"
|
|
123
|
+
"Table",
|
|
110
124
|
({ args, content }) => {
|
|
111
125
|
return md.table(
|
|
112
126
|
args,
|
|
@@ -117,20 +131,27 @@ MARKDOWN.register(
|
|
|
117
131
|
.map(line => line.trim())
|
|
118
132
|
);
|
|
119
133
|
},
|
|
120
|
-
{ escape: false,
|
|
134
|
+
{ escape: false, type: "AtBlock" }
|
|
121
135
|
);
|
|
122
136
|
// List
|
|
123
137
|
MARKDOWN.register(
|
|
124
|
-
|
|
138
|
+
"list",
|
|
125
139
|
({ content }) => {
|
|
126
140
|
return content;
|
|
127
141
|
},
|
|
128
|
-
{ escape: false,
|
|
142
|
+
{ escape: false, type: "AtBlock" }
|
|
129
143
|
);
|
|
130
144
|
// Todo
|
|
131
145
|
MARKDOWN.register("todo", ({ args, content }) => {
|
|
132
|
-
const
|
|
133
|
-
|
|
146
|
+
const isPlaceholder = content.includes("__SOMMARK_BODY_PLACEHOLDER_");
|
|
147
|
+
if (isPlaceholder) {
|
|
148
|
+
return `@@TODO_BLOCK:${content}:${args[0] || ""}@@`;
|
|
149
|
+
}
|
|
150
|
+
const statusMarkers = ["done", "x", "X", "-", ""];
|
|
151
|
+
const isInline = !isPlaceholder && statusMarkers.includes(content.trim().toLowerCase()) && args.length > 0;
|
|
152
|
+
const status = isInline ? content : (args[0] || "");
|
|
153
|
+
const task = isInline ? (args[0] || "") : content;
|
|
154
|
+
const checked = todo(status);
|
|
134
155
|
return md.todo(checked, task);
|
|
135
|
-
});
|
|
156
|
+
}, { type: "any" });
|
|
136
157
|
export default MARKDOWN;
|
package/mappers/languages/mdx.js
CHANGED
|
@@ -1,5 +1,133 @@
|
|
|
1
1
|
import Mapper from "../mapper.js";
|
|
2
2
|
import MARKDOWN from "./markdown.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { HTML_TAGS } from "../../constants/html_tags.js";
|
|
4
|
+
import { HTML_PROPS } from "../../constants/html_props.js";
|
|
5
|
+
import { VOID_ELEMENTS } from "../../constants/void_elements.js";
|
|
6
|
+
import kebabize from "../../helpers/kebabize.js";
|
|
7
|
+
|
|
8
|
+
class MdxMapper extends Mapper {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
comment(text) {
|
|
13
|
+
return `{/*${text.replace("#", "")} */}\n`;
|
|
14
|
+
}
|
|
15
|
+
getUnknownTag(node) {
|
|
16
|
+
const tagName = node.id;
|
|
17
|
+
return {
|
|
18
|
+
render: ({ args, content }) => {
|
|
19
|
+
const element = this.tag(tagName);
|
|
20
|
+
element.props(this.jsxProps(args, tagName));
|
|
21
|
+
return element.body(content);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
jsxProps(args, tagName = "div") {
|
|
27
|
+
const jsxProps = [];
|
|
28
|
+
const styleObj = {};
|
|
29
|
+
const isHtmlTag = HTML_TAGS.has(tagName.toLowerCase());
|
|
30
|
+
|
|
31
|
+
const keys = Object.keys(args).filter(arg => isNaN(arg));
|
|
32
|
+
keys.forEach(key => {
|
|
33
|
+
let val = args[key];
|
|
34
|
+
const isEvent = key.toLowerCase().startsWith("on");
|
|
35
|
+
|
|
36
|
+
let k = key;
|
|
37
|
+
if (k === "class") k = "className";
|
|
38
|
+
|
|
39
|
+
// Quote stripping
|
|
40
|
+
if (typeof val === "string" && ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'")))) {
|
|
41
|
+
val = val.slice(1, -1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (k === "style") {
|
|
45
|
+
if (typeof val === "string") {
|
|
46
|
+
const pairs = val.includes(";") ? val.split(";") : val.split(",");
|
|
47
|
+
pairs.forEach(pair => {
|
|
48
|
+
let [prop, value] = pair.split(":").map(s => s.trim());
|
|
49
|
+
if (prop && value) {
|
|
50
|
+
const camelProp = prop.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
|
51
|
+
styleObj[camelProp] = value;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
} else if (typeof val === "object") {
|
|
55
|
+
Object.assign(styleObj, val);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
// Detection for expressions
|
|
59
|
+
const isBoolean = val === "true" || val === "false" || typeof val === "boolean";
|
|
60
|
+
const isNumeric = val !== "" && !isNaN(val) && typeof val !== "boolean";
|
|
61
|
+
const looksLikeExpression = typeof val === "string" &&
|
|
62
|
+
(/[0-9]/.test(val) && /[+\-*/%()]/.test(val)); // Math expression detection
|
|
63
|
+
|
|
64
|
+
const shouldBeJSXExpression = isEvent || isBoolean || isNumeric || looksLikeExpression;
|
|
65
|
+
|
|
66
|
+
let finalVal = val;
|
|
67
|
+
if (val === "true") finalVal = true;
|
|
68
|
+
if (val === "false") finalVal = false;
|
|
69
|
+
if (isNumeric && typeof val === "string") finalVal = Number(val);
|
|
70
|
+
|
|
71
|
+
jsxProps.push({
|
|
72
|
+
__type__: shouldBeJSXExpression ? "other" : "string",
|
|
73
|
+
[k]: finalVal
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (Object.keys(styleObj).length > 0) {
|
|
79
|
+
const styleStr = JSON.stringify(styleObj).replace(/"/g, "'").replace(/'([^']+)':/g, '$1:');
|
|
80
|
+
jsxProps.push({ __type__: "other", style: styleStr });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return jsxProps;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const MDX = new MdxMapper();
|
|
88
|
+
const { tag } = MDX;
|
|
89
|
+
|
|
90
|
+
MDX.inherit(MARKDOWN);
|
|
91
|
+
|
|
92
|
+
// Block for raw MDX content (ESM, etc.)
|
|
93
|
+
MDX.register("mdx", ({ content }) => content, { escape: false, type: "Block" });
|
|
94
|
+
|
|
95
|
+
// Re-register HTML tags to use jsxProps
|
|
96
|
+
HTML_TAGS.forEach(tagName => {
|
|
97
|
+
const capitalized = tagName.charAt(0).toUpperCase() + tagName.slice(1);
|
|
98
|
+
|
|
99
|
+
// Register even if it exists in MARKDOWN to override it with JSX version
|
|
100
|
+
const idsToRegister = [tagName, capitalized];
|
|
101
|
+
|
|
102
|
+
MDX.register(
|
|
103
|
+
idsToRegister,
|
|
104
|
+
({ args, content }) => {
|
|
105
|
+
const element = tag(tagName);
|
|
106
|
+
|
|
107
|
+
// Auto-ID for Headings
|
|
108
|
+
if (/^h[1-6]$/i.test(tagName) && !args.id && content && /^[A-Za-z0-9]/.test(content)) {
|
|
109
|
+
const id = content
|
|
110
|
+
.toString()
|
|
111
|
+
.toLowerCase()
|
|
112
|
+
.replace(/[^\w\s-]/g, "")
|
|
113
|
+
.replace(/\s+/g, "-");
|
|
114
|
+
args.id = id;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
element.props(MDX.jsxProps(args, tagName));
|
|
118
|
+
|
|
119
|
+
if (VOID_ELEMENTS.has(tagName)) {
|
|
120
|
+
return element.selfClose();
|
|
121
|
+
}
|
|
122
|
+
return element.body(content);
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: VOID_ELEMENTS.has(tagName) ? "Block" : ["Block", "Inline"],
|
|
126
|
+
rules: {
|
|
127
|
+
is_self_closing: VOID_ELEMENTS.has(tagName)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
5
133
|
export default MDX;
|