sommark 3.3.3 → 4.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.
Files changed (61) hide show
  1. package/README.md +98 -82
  2. package/assets/logo.json +28 -0
  3. package/assets/smark.logo.png +0 -0
  4. package/assets/smark.logo.svg +21 -0
  5. package/cli/cli.mjs +8 -16
  6. package/cli/commands/build.js +24 -4
  7. package/cli/commands/color.js +22 -26
  8. package/cli/commands/help.js +10 -10
  9. package/cli/commands/init.js +19 -42
  10. package/cli/commands/print.js +20 -12
  11. package/cli/commands/show.js +4 -0
  12. package/cli/commands/version.js +6 -0
  13. package/cli/constants.js +9 -5
  14. package/cli/helpers/config.js +11 -0
  15. package/cli/helpers/file.js +17 -6
  16. package/cli/helpers/transpile.js +7 -8
  17. package/core/errors.js +49 -25
  18. package/core/formats.js +7 -3
  19. package/core/formatter.js +215 -0
  20. package/core/helpers/config-loader.js +37 -56
  21. package/core/labels.js +21 -9
  22. package/core/lexer.js +491 -212
  23. package/core/modules.js +164 -0
  24. package/core/parser.js +516 -389
  25. package/core/tokenTypes.js +36 -1
  26. package/core/transpiler.js +237 -151
  27. package/core/validator.js +79 -0
  28. package/formatter/mark.js +203 -43
  29. package/formatter/tag.js +202 -32
  30. package/grammar.ebnf +57 -50
  31. package/helpers/colorize.js +26 -13
  32. package/helpers/escapeHTML.js +13 -6
  33. package/helpers/kebabize.js +6 -0
  34. package/helpers/peek.js +9 -0
  35. package/helpers/removeChar.js +26 -13
  36. package/helpers/safeDataParser.js +114 -0
  37. package/helpers/utils.js +140 -158
  38. package/index.js +198 -188
  39. package/mappers/languages/html.js +105 -213
  40. package/mappers/languages/json.js +122 -171
  41. package/mappers/languages/markdown.js +355 -108
  42. package/mappers/languages/mdx.js +76 -114
  43. package/mappers/languages/xml.js +114 -0
  44. package/mappers/mapper.js +152 -123
  45. package/mappers/shared/index.js +22 -0
  46. package/package.json +26 -6
  47. package/SOMMARK-SPEC.md +0 -481
  48. package/cli/commands/list.js +0 -124
  49. package/constants/html_tags.js +0 -146
  50. package/core/pluginManager.js +0 -149
  51. package/core/plugins/comment-remover.js +0 -47
  52. package/core/plugins/module-system.js +0 -176
  53. package/core/plugins/raw-content-plugin.js +0 -78
  54. package/core/plugins/rules-validation-plugin.js +0 -231
  55. package/core/plugins/sommark-format.js +0 -244
  56. package/coverage_test.js +0 -21
  57. package/debug.js +0 -15
  58. package/helpers/camelize.js +0 -2
  59. package/helpers/defaultTheme.js +0 -3
  60. package/test_format_fix.js +0 -42
  61. package/v3-todo.smark +0 -73
package/index.js CHANGED
@@ -6,97 +6,59 @@ 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 TagBuilder from "./formatter/tag.js";
10
- import MarkdownBuilder from "./formatter/mark.js";
9
+ import XML from "./mappers/languages/xml.js";
11
10
  import { runtimeError } from "./core/errors.js";
12
- import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat } from "./core/formats.js";
11
+ import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat, xmlFormat } from "./core/formats.js";
13
12
  import TOKEN_TYPES from "./core/tokenTypes.js";
14
13
  import * as labels from "./core/labels.js";
15
- import PluginManager from "./core/pluginManager.js";
16
- import ModuleSystem from "./core/plugins/module-system.js";
17
- import RawContentPlugin from "./core/plugins/raw-content-plugin.js";
18
- import CommentRemover from "./core/plugins/comment-remover.js";
19
- import RulesValidationPlugin from "./core/plugins/rules-validation-plugin.js";
20
- import SomMarkFormat from "./core/plugins/sommark-format.js";
14
+ import { resolveModules } from "./core/modules.js";
15
+ import { formatAST } from "./core/formatter.js";
16
+ import { validateAST } from "./core/validator.js";
21
17
  import { enableColor } from "./helpers/colorize.js";
22
- import { htmlTable, list, parseList, safeArg, todo } from "./helpers/utils.js";
18
+ import { safeArg } from "./helpers/utils.js";
23
19
 
24
20
 
25
- export const BUILT_IN_PLUGINS = [ModuleSystem, RawContentPlugin, CommentRemover, RulesValidationPlugin, SomMarkFormat];
26
-
21
+ /**
22
+ * The SomMark Core Engine.
23
+ * Processes SomMark code and turns it into different formats.
24
+ */
27
25
  class SomMark {
28
- constructor({ src, format, mapperFile = null, includeDocument = true, plugins = [], excludePlugins = [], priority = [], filename = "anonymous" }) {
26
+ static Mapper = Mapper;
27
+ /**
28
+ * Creates a new SomMark engine.
29
+ *
30
+ * @param {Object} options - Settings for the engine.
31
+ * @param {string} options.src - The SomMark code to process.
32
+ * @param {string} options.format - The final format you want (like 'html' or 'markdown').
33
+ * @param {Mapper|null} [options.mapperFile=null] - Custom rules for formatting.
34
+ * @param {string} [options.filename="anonymous"] - The name of the file, used for errors and settings.
35
+ * @param {boolean} [options.removeComments=true] - If true, comments will be removed from the final code.
36
+ * @param {Object} [options.placeholders={}] - Values to use for {placeholders}.
37
+ * @param {Object} [options.placeholder={}] - Alias for placeholders (backward compatibility).
38
+ * @param {Array<string>} [options.customProps=[]] - Allowed custom HTML attributes.
39
+ * @param {Array<string>} [options.importStack=[]] - Tracking for circular dependencies.
40
+ */
41
+ constructor({ src, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholder = {}, placeholders = {}, customProps = [], importStack = [] }) {
29
42
  this.src = src;
30
- this.format = format;
43
+ this.targetFormat = format;
31
44
  this.mapperFile = mapperFile;
32
- this.priority = priority;
33
45
  this.filename = filename;
46
+ this.removeComments = removeComments;
47
+ this.placeholders = { ...placeholder, ...placeholders };
48
+ this.customProps = customProps;
49
+ this.importStack = importStack;
34
50
  this.warnings = [];
35
51
  this._prepared = false;
36
52
 
37
- // 1. Identify which built-in plugins should be active by default
38
- const inactiveByDefault = ["raw-content", "sommark-format"];
39
- let activeBuiltIns = BUILT_IN_PLUGINS.filter(p =>
40
- !inactiveByDefault.includes(p.name) && !excludePlugins.includes(p.name)
41
- );
42
-
43
- // 2. Process 'plugins' array:
44
- // - If string, look up in BUILT_IN_PLUGINS
45
- // - If object with { name, options }, it's a built-in override
46
- // - If object with { plugin, options }, it's an external override
47
- // - If object without name/plugin but with other keys, it's a direct plugin object
48
- let processedPlugins = [];
49
- let manuallyActivatedNames = [];
50
-
51
- plugins.forEach(p => {
52
- if (typeof p === "string") {
53
- const builtIn = BUILT_IN_PLUGINS.find(bp => bp.name === p);
54
- if (builtIn) {
55
- processedPlugins.push({ ...builtIn }); // Clone to avoid mutation
56
- manuallyActivatedNames.push(p);
57
- }
58
- } else if (typeof p === "object" && p !== null) {
59
- if (p.name && p.options && !p.type) {
60
- // Built-in Override: { name: "raw-content", options: { ... } }
61
- const builtIn = BUILT_IN_PLUGINS.find(bp => bp.name === p.name);
62
- if (builtIn) {
63
- processedPlugins.push({
64
- ...builtIn,
65
- options: { ...builtIn.options, ...p.options }
66
- });
67
- manuallyActivatedNames.push(p.name);
68
- }
69
- } else if (p.plugin && p.options) {
70
- // External Override: { plugin: myPlugin, options: { ... } }
71
- processedPlugins.push({
72
- ...p.plugin,
73
- options: { ...p.plugin.options, ...p.options }
74
- });
75
- } else {
76
- // Direct Plugin Object
77
- processedPlugins.push(p);
78
- }
79
- }
80
- });
81
-
82
- // 3. Merge: Default active built-ins (minus ones manually re-added) + Processed Plugins
83
- const finalPlugins = [
84
- ...activeBuiltIns
85
- .filter(p => !manuallyActivatedNames.includes(p.name))
86
- .map(p => ({ ...p })), // Clone defaults for isolation
87
- ...processedPlugins
88
- ];
89
-
90
- this.plugins = finalPlugins;
91
- this.pluginManager = new PluginManager(this.plugins, this.priority);
53
+ // Create a random token to safely wrap data
54
+ this.moduleIdentityToken = `$_SM_MOD_${Math.random().toString(36).slice(2, 7)}_$`;
92
55
 
93
56
  this.Mapper = Mapper;
94
- this.includeDocument = includeDocument;
95
57
 
96
- const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json, [textFormat]: new Mapper() };
58
+ const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json, [xmlFormat]: XML, [textFormat]: new Mapper() };
97
59
 
98
- if (!this.mapperFile && this.format) {
99
- const DefaultMapper = mapperFiles[this.format];
60
+ if (!this.mapperFile && this.targetFormat) {
61
+ const DefaultMapper = mapperFiles[this.targetFormat];
100
62
  if (DefaultMapper) {
101
63
  this.mapperFile = DefaultMapper.clone();
102
64
  }
@@ -104,17 +66,58 @@ class SomMark {
104
66
  this.mapperFile = this.mapperFile.clone();
105
67
  }
106
68
 
69
+ if (this.mapperFile) {
70
+ this.mapperFile.options.removeComments = this.removeComments;
71
+ this.mapperFile.options.moduleIdentityToken = this.moduleIdentityToken;
72
+ this.mapperFile.options.filename = this.filename;
73
+
74
+ // Initialize custom props whitelist
75
+ if (this.customProps && this.customProps.length > 0) {
76
+ const props = Array.isArray(this.customProps) ? this.customProps : [this.customProps];
77
+ props.forEach(prop => this.mapperFile.customProps.add(prop));
78
+ }
79
+ }
80
+
107
81
  this._initializeMappers();
108
82
  }
109
83
 
84
+ /**
85
+ * Backward compatibility alias for placeholders.
86
+ */
87
+ get placeholder() {
88
+ return this.placeholders;
89
+ }
90
+
91
+ set placeholder(val) {
92
+ this.placeholders = val;
93
+ }
94
+
95
+ /**
96
+ * Adds a new rule or changes an existing one.
97
+ *
98
+ * @param {string} id - The name of the tag.
99
+ * @param {Function} render - The function that formats the tag.
100
+ * @param {Object} [options] - Extra settings for the tag.
101
+ */
110
102
  register = (id, render, options) => {
111
103
  this.mapperFile.register(id, render, options);
112
104
  };
113
105
 
106
+ /**
107
+ * Copies rules from other mappers.
108
+ *
109
+ * @param {...Mapper} mappers - The mappers to copy from.
110
+ */
114
111
  inherit = (...mappers) => {
115
112
  this.mapperFile.inherit(...mappers);
116
113
  };
117
114
 
115
+ /**
116
+ * Gets a rule by its name.
117
+ *
118
+ * @param {string} id - The tag name.
119
+ * @returns {Object|null}
120
+ */
118
121
  get = id => {
119
122
  return this.mapperFile.get(id);
120
123
  };
@@ -128,18 +131,12 @@ class SomMark {
128
131
  };
129
132
 
130
133
  _initializeMappers() {
131
- // 1. Check if a plugin provides a mapper for this format
132
- const pluginMapper = this.pluginManager.getFormatMapper(this.format);
133
- if (pluginMapper) {
134
- this.mapperFile = pluginMapper.clone();
135
- }
136
-
137
- if (!this.format) {
134
+ if (!this.targetFormat) {
138
135
  runtimeError(["{line}<$red:Undefined Format$>: <$yellow:Format argument is not defined.$>{line}"]);
139
136
  }
140
137
 
141
- if (!this.mapperFile && this.format) {
142
- runtimeError([`{line}<$red:Unknown Format$>: <$yellow:Mapper for format '${this.format}' not found.$>{line}`]);
138
+ if (!this.mapperFile && this.targetFormat) {
139
+ runtimeError([`{line}<$red:Unknown Format$>: <$yellow:Mapper for format '${this.targetFormat}' not found.$>{line}`]);
143
140
  }
144
141
  }
145
142
 
@@ -147,149 +144,150 @@ class SomMark {
147
144
  this.warnings.push(message);
148
145
  }
149
146
 
150
- async _applyScopedPreprocessors(src) {
151
- let processed = await this.pluginManager.runPreprocessor(src, "top-level", this);
152
-
153
- // Helper for async regex replacement
154
- const asyncReplace = async (str, regex, scope) => {
155
- if (typeof str !== "string") return str;
156
- const matches = [...str.matchAll(regex)];
157
- if (matches.length === 0) return str;
158
-
159
- // Process all matches in parallel for efficiency
160
- const replacements = await Promise.all(
161
- matches.map(async match => {
162
- // match[2] is the group for content inside quotes/brackets/whatever depending on the regex
163
- let contentToProcess;
164
- if (scope === "arguments") contentToProcess = match[2];
165
- if (scope === "content") contentToProcess = match[2];
166
-
167
- if (contentToProcess !== undefined) {
168
- const processedContent = await this.pluginManager.runPreprocessor(contentToProcess, scope, this);
169
- // Reconstruct the match with processed content
170
- if (scope === "arguments") return match[0].replace(match[2], processedContent);
171
- if (scope === "content") return match[0].replace(match[2], processedContent);
172
- }
173
- return match[0];
174
- })
175
- );
176
-
177
- // Reconstruct string by replacing matches in order
178
- let i = 0;
179
- return str.replace(regex, () => replacements[i++]);
180
- };
181
-
182
- // 1. Process Arguments Scope [...]
183
- const argRegex = /\[\s*([a-zA-Z0-9\-_$]+)\s*(?:=\s*((?:[^"\\\]]|\\[\s\S]|"[^"]*")*))?\s*\]/g;
184
- processed = await asyncReplace(processed, argRegex, "arguments");
185
-
186
- // 2. Process Content Scope
187
- const contentRegex = /(\]\s*)([\s\S]*?)(\s*\[\s*end\s*\])/g;
188
- processed = await asyncReplace(processed, contentRegex, "content");
189
-
190
- return processed;
191
- }
192
-
147
+
193
148
  _ensurePrepared() {
194
149
  if (this._prepared) return;
195
150
 
196
- // 1. Resolve Dynamic Formats from Plugins if built-in failed
197
- if (!this.mapperFile) {
198
- const PluginMapper = this.pluginManager.getFormatMapper(this.format);
199
- if (PluginMapper) {
200
- this.mapperFile = PluginMapper.clone ? PluginMapper.clone() : PluginMapper;
201
- }
202
- }
203
-
204
151
  // Final check
205
152
  if (!this.mapperFile) {
206
153
  runtimeError([
207
- `{line}<$red:Unknown Format$>: <$yellow:No mapper found for format:$> <$green:'${this.format}'$>`,
208
- `{N}<$yellow:Make sure you have registered a plugin that provides this format.$>{line}`
154
+ `{line}<$red:Unknown Format$>: <$yellow:No mapper found for format:$> <$green:'${this.targetFormat}'$>`,
155
+ `{N}<$yellow:Make sure you have registered format mapper correctly.$>{line}`
209
156
  ]);
210
157
  }
211
158
 
212
- // Run active registration hooks from plugins
213
- this.pluginManager.runRegisterHooks(this);
214
-
215
- // 2. Extend Mapper with static plugins definitions
216
- const extensions = this.pluginManager.getMapperExtensions();
217
- if (extensions.outputs.length > 0) {
218
- for (const out of extensions.outputs) {
219
- // Support both object {id, render, options} and array [id, render, options]
220
- if (Array.isArray(out)) {
221
- const [id, render, options = {}] = out;
222
- this.register(id, render, options);
223
- } else if (typeof out === "object" && out !== null) {
224
- const renderFn = out.register || out.render;
225
- if (typeof renderFn === "function") {
226
- this.register(out.id, renderFn, out.options || {});
227
- }
228
- }
229
- }
230
- }
231
-
232
- // Add recognized arguments if provided by plugins
233
- if (extensions.rules && extensions.rules.recognizedArguments) {
234
- if (Array.isArray(extensions.rules.recognizedArguments)) {
235
- extensions.rules.recognizedArguments.forEach(arg => this.mapperFile.extraProps.add(arg));
236
- }
237
- }
238
-
239
159
  this._prepared = true;
240
160
  }
241
161
 
162
+ /**
163
+ * Breaks the code into small pieces called tokens.
164
+ *
165
+ * @param {string} [src=this.src] - The code to break apart.
166
+ * @returns {Promise<Array<Object>>} - The list of tokens.
167
+ */
242
168
  async lex(src = this.src) {
243
169
  this._ensurePrepared();
244
170
  if (src !== this.src) this.src = src;
245
- const processedSrc = await this._applyScopedPreprocessors(this.src);
246
- let tokens = lexer(processedSrc, this.filename);
247
- tokens = await this.pluginManager.runAfterLex(tokens);
171
+ let tokens = lexer(this.src, this.filename);
248
172
  return tokens;
249
173
  }
250
174
 
175
+ /**
176
+ * Organizes the code into a tree structure.
177
+ * Also handles modules and checks for errors.
178
+ *
179
+ * @param {string} [src=this.src] - Optional source override.
180
+ * @returns {Promise<Array<Object>>} - The final code tree.
181
+ */
251
182
  async parse(src = this.src) {
252
183
  const tokens = await this.lex(src);
253
- let ast = parser(tokens, this.filename);
254
- ast = await this.pluginManager.runOnAst(ast, {
255
- mapperFile: this.mapperFile,
184
+ let ast = parser(tokens, this.filename, this.placeholders);
185
+
186
+ ast = await resolveModules(ast, {
187
+ mapperFile: this.mapperFile,
256
188
  filename: this.filename,
257
- format: this.format,
258
- instance: this
189
+ format: this.targetFormat,
190
+ instance: this,
191
+ importStack: this.importStack
259
192
  });
193
+
194
+ if (this.mapperFile) {
195
+ validateAST(ast, this.mapperFile, this);
196
+ }
197
+
198
+ return ast;
199
+ }
200
+
201
+ parseSync(src = this.src) {
202
+ this._ensurePrepared();
203
+ if (src !== this.src) this.src = src;
204
+ const tokens = lexer(this.src, this.filename);
205
+ let ast = parser(tokens, this.filename, this.placeholders);
206
+
207
+ if (this.mapperFile) {
208
+ validateAST(ast, this.mapperFile, this);
209
+ }
210
+
260
211
  return ast;
261
212
  }
262
213
 
214
+ /**
215
+ * Turns the SomMark code into the final format.
216
+ *
217
+ * @param {string} [src=this.src] - Optional source override.
218
+ * @returns {Promise<string>} - The finished code.
219
+ */
263
220
  async transpile(src = this.src) {
264
221
  if (src !== this.src) this.src = src;
265
222
  this._ensurePrepared();
266
223
 
267
224
  const ast = await this.parse(src);
225
+ let result = await transpiler({ ast, format: this.targetFormat, mapperFile: this.mapperFile });
226
+
227
+ return result;
228
+ }
268
229
 
269
- let result = await transpiler({ ast, format: this.format, mapperFile: this.mapperFile, includeDocument: this.includeDocument });
270
230
 
271
- // 3. Run Transformers
272
- return await this.pluginManager.runTransformers(result);
231
+ async format(options = {}) {
232
+ const tokens = await this.lex();
233
+ const ast = parser(tokens, this.filename);
234
+ return formatAST(ast, options);
235
+ }
236
+
237
+ formatSync(options = {}) {
238
+ const tokens = lexer(this.src, this.filename);
239
+ const ast = parser(tokens, this.filename);
240
+ return formatAST(ast, options);
273
241
  }
274
242
  }
275
243
 
276
- const lex = async (src, filename = "anonymous", plugins = [], excludePlugins = []) => {
277
- return await new SomMark({ src, filename, plugins, format: htmlFormat, excludePlugins }).lex();
244
+ /**
245
+ * A quick way to break code into tokens.
246
+ * Uses HTML settings by default.
247
+ *
248
+ * @param {string} src - The raw SomMark source.
249
+ * @param {string} [filename="anonymous"] - Filename for error context.
250
+ * @returns {Promise<Array<Object>>} - The list of tokens.
251
+ */
252
+ const lex = async (src, filename = "anonymous") => {
253
+ return await new SomMark({ src, filename, format: htmlFormat }).lex();
278
254
  };
279
255
 
280
- async function parse(src, filename = "anonymous", plugins = [], excludePlugins = []) {
256
+ /**
257
+ * A quick way to organize code into a tree.
258
+ * Uses HTML settings by default.
259
+ *
260
+ * @param {string} src - The raw SomMark source.
261
+ * @param {string} [filename="anonymous"] - Filename for error context.
262
+ * @returns {Promise<Array<Object>>} - The final code tree.
263
+ */
264
+ async function parse(src, filename = "anonymous") {
281
265
  if (!src) {
282
266
  runtimeError([`{line}<$red:Missing Source:$> <$yellow:The 'src' argument is required for parsing.$>{line}`]);
283
267
  }
284
- return await new SomMark({ src, filename, plugins, format: htmlFormat, excludePlugins }).parse();
268
+ return await new SomMark({ src, filename, format: htmlFormat }).parse();
285
269
  }
286
270
 
271
+ /**
272
+ * The easiest way to process SomMark code.
273
+ *
274
+ * @param {Object} options - Transpilation options.
275
+ * @param {string} options.src - Raw source code.
276
+ * @param {string} [options.format="html"] - Target format.
277
+ * @param {string} [options.filename="anonymous"] - Filename for context.
278
+ * @param {Mapper|null} [options.mapperFile=null] - Custom rules for formatting.
279
+ * @param {boolean} [options.removeComments=true] - Strip comments.
280
+ * @param {Object} [options.placeholders={}] - Global placeholders.
281
+ * @param {Object} [options.placeholder={}] - Alias for placeholders.
282
+ * @param {Array<string>} [options.customProps=[]] - Custom attribute whitelist.
283
+ * @returns {Promise<string>} - Transpiled output.
284
+ */
287
285
  async function transpile(options = {}) {
288
- const { src, format = htmlFormat, filename = "anonymous", mapperFile = null, includeDocument = true, plugins = [], excludePlugins = [], priority = [] } = options;
286
+ const { src, format = htmlFormat, filename = "anonymous", mapperFile = null, removeComments = true, placeholder = {}, placeholders = {}, customProps = [] } = options;
289
287
  if (typeof options !== "object" || options === null) {
290
288
  runtimeError([`{line}<$red:Invalid Options:$> <$yellow:The options argument must be a non-null object.$>{line}`]);
291
289
  }
292
- const knownProps = ["src", "format", "filename", "mapperFile", "includeDocument", "plugins", "excludePlugins", "priority"];
290
+ const knownProps = ["src", "format", "filename", "mapperFile", "removeComments", "placeholder", "placeholders", "customProps"];
293
291
  Object.keys(options).forEach(key => {
294
292
  if (!knownProps.includes(key)) {
295
293
  runtimeError([
@@ -301,13 +299,29 @@ async function transpile(options = {}) {
301
299
  runtimeError([`{line}<$red:Missing Source:$> <$yellow:The 'src' argument is required for transpilation.$>{line}`]);
302
300
  }
303
301
 
304
- const sm = new SomMark({ src, format, filename, mapperFile, includeDocument, plugins, excludePlugins, priority });
302
+ const sm = new SomMark({ src, format, filename, mapperFile, removeComments, placeholder, placeholders, customProps });
305
303
  return await sm.transpile();
306
304
  }
307
305
 
306
+ /**
307
+ * A quick, synchronous way to get tokens.
308
+ *
309
+ * @param {string} src - Raw source code.
310
+ * @returns {Array<Object>} - The list of tokens.
311
+ */
308
312
  const lexSync = src => lexer(src);
309
313
 
310
- const parseSync = src => parser(lexer(src));
314
+ /**
315
+ * A quick, synchronous way to get the code tree.
316
+ *
317
+ * @param {string} src - Raw source code.
318
+ * @param {Object} [options={}] - Parsing options.
319
+ * @returns {Array<Object>} - The code tree.
320
+ */
321
+ const parseSync = (src, options = {}) => {
322
+ const { format = htmlFormat, filename = "anonymous", mapperFile = null, removeComments = true, placeholder = {}, placeholders = {}, customProps = [] } = options;
323
+ return new SomMark({ src, format, filename, mapperFile, removeComments, placeholder, placeholders, customProps }).parseSync();
324
+ };
311
325
 
312
326
  import { findAndLoadConfig } from "./core/helpers/config-loader.js";
313
327
 
@@ -316,23 +330,19 @@ export {
316
330
  MARKDOWN,
317
331
  MDX,
318
332
  Json,
333
+ XML,
319
334
  Mapper,
320
- TagBuilder,
321
- MarkdownBuilder,
322
335
  FORMATS,
323
336
  lex,
324
337
  parse,
325
338
  transpile,
326
339
  lexSync,
327
340
  parseSync,
341
+ formatAST,
328
342
  TOKEN_TYPES,
329
343
  labels,
330
344
  enableColor,
331
- htmlTable,
332
- list,
333
- parseList,
334
345
  safeArg,
335
- todo,
336
346
  findAndLoadConfig
337
347
  };
338
348
  export default SomMark;