@remigius42/morg 0.7.0 → 0.9.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 +35 -18
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +2 -0
- package/dist/cli/conversion.d.ts +3 -3
- package/dist/cli/conversion.js +4 -4
- package/dist/cli/flags.d.ts +1 -1
- package/dist/cli/flags.js +15 -1
- package/dist/cli/formats.d.ts +2 -1
- package/dist/cli/formats.js +1 -0
- package/dist/cli/presets.d.ts +13 -2
- package/dist/cli/presets.js +17 -3
- package/dist/cli.js +4 -5
- package/dist/config.d.ts +9 -4
- package/dist/config.js +2 -0
- package/dist/conversionOptions.d.ts +19 -3
- package/dist/conversionOptions.js +61 -0
- package/dist/core/bracedScripts.d.ts +7 -0
- package/dist/core/bracedScripts.js +10 -0
- package/dist/core/frontmatterBlock.d.ts +7 -0
- package/dist/core/frontmatterBlock.js +7 -2
- package/dist/core/lineSyntax.d.ts +13 -0
- package/dist/core/lineSyntax.js +16 -4
- package/dist/core/mdastToUniorg/blocks.js +4 -1
- package/dist/core/passthroughSource.d.ts +21 -0
- package/dist/core/passthroughSource.js +94 -0
- package/dist/core/uniorgToMdast/elements.js +5 -0
- package/dist/core/uniorgToMdast/shared.js +6 -1
- package/dist/index.d.ts +1 -2
- package/dist/markdownToOrg.js +19 -6
- package/dist/normalize.js +16 -2
- package/dist/options.d.ts +3 -7
- package/dist/orgToMarkdown.js +14 -4
- package/dist/presets/hooks.d.ts +49 -0
- package/dist/presets/hooks.js +72 -0
- package/dist/presets/logseq.d.ts +3 -22
- package/dist/presets/logseq.js +306 -195
- package/dist/presets/logseqOutline.d.ts +47 -0
- package/dist/presets/logseqOutline.js +428 -0
- package/dist/presets/logseqVanillaMarkdown.d.ts +9 -0
- package/dist/presets/logseqVanillaMarkdown.js +295 -0
- package/dist/presets/obsidian.js +4 -2
- package/dist/presets/registry.d.ts +1 -1
- package/dist/presets/registry.js +5 -3
- package/dist/presets/sides.d.ts +40 -0
- package/dist/presets/sides.js +42 -0
- package/dist/presets/types.d.ts +49 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ Bidirectional **Markdown ↔ Org-mode** converter, built on the
|
|
|
17
17
|
|
|
18
18
|
morg treats Org as a canonical plain-text format and Markdown (Obsidian,
|
|
19
19
|
generic) as the interop surface. Dialect conventions, such as
|
|
20
|
-
[Logseq](https://docs.logseq.com/)'s
|
|
21
|
-
|
|
20
|
+
[Logseq](https://docs.logseq.com/)'s outline of blocks and page
|
|
21
|
+
properties, are supported via presets.
|
|
22
22
|
|
|
23
23
|
## Round-trip convergence
|
|
24
24
|
|
|
@@ -60,7 +60,10 @@ color schemes.
|
|
|
60
60
|
Try morg without installing anything at
|
|
61
61
|
[morg.binarypoetry.ch](https://morg.binarypoetry.ch). All conversion
|
|
62
62
|
happens in your browser, nothing is uploaded (see [ADR
|
|
63
|
-
0003](docs/adr/0003-client-side-web-ui-on-github-pages.md)).
|
|
63
|
+
0003](docs/adr/0003-client-side-web-ui-on-github-pages.md)). Each side
|
|
64
|
+
names its format and dialect (Input: Org (Logseq), Output: Markdown);
|
|
65
|
+
the direction follows from the two, the same on both sides normalizes,
|
|
66
|
+
and ⇄ swaps them. The
|
|
64
67
|
chrome-less embed page (`/embed.html`, optionally with
|
|
65
68
|
`?theme=dark|light`) can be iframed into other sites. It posts its
|
|
66
69
|
content height to the host on every change, so the frame can follow it
|
|
@@ -121,6 +124,9 @@ echo "# Hello" | morg --from markdown
|
|
|
121
124
|
# Apply a dialect preset
|
|
122
125
|
morg --input page.md --output page.org --preset logseq
|
|
123
126
|
|
|
127
|
+
# Or one per side: read Logseq org, write Vanilla Markdown (ADR 0006)
|
|
128
|
+
morg --input page.org --output page.md --input-preset logseq
|
|
129
|
+
|
|
124
130
|
# Dropped constructs are reported on stderr; -s / --silent suppresses.
|
|
125
131
|
# Boolean flags take an optional value, so --silent false overrides a
|
|
126
132
|
# morg.toml that sets it
|
|
@@ -141,7 +147,7 @@ morg normalize --input notes.org --output notes.org
|
|
|
141
147
|
|
|
142
148
|
Options can live in a `morg.toml` (auto-discovered in the working
|
|
143
149
|
directory, or passed via `--config path`). Precedence: CLI flags >
|
|
144
|
-
config file > defaults:
|
|
150
|
+
config file > defaults, per side for the presets:
|
|
145
151
|
|
|
146
152
|
```toml
|
|
147
153
|
preset = "logseq"
|
|
@@ -198,28 +204,36 @@ preset })`: `preserveOrgisms` default `true`; `useHtml` (default
|
|
|
198
204
|
`- [x]`); headings become list items and do not restore on the
|
|
199
205
|
return trip; anything with priority, tags or content keeps its
|
|
200
206
|
heading and reports via `onWarning`
|
|
201
|
-
- `logseq(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
- `logseq()`: a page is Logseq's outline of blocks, converted block by
|
|
208
|
+
block: a headline (stars, a space, the block's content, an empty
|
|
209
|
+
block as the bare stars) ↔ a `-` bullet indented one tab per level,
|
|
210
|
+
its lines below the first two spaces further in. A block's content
|
|
211
|
+
is one fragment, so a code block or table that starts on the
|
|
212
|
+
headline line converts as a whole. `:heading: N` ↔ `- ## …`, a
|
|
213
|
+
block's property drawer ↔ `key:: value` lines; planning lines and
|
|
214
|
+
other drawers (`:LOGBOOK:`) stay as written. A heading outside the
|
|
215
|
+
bullets is a top-level block, as Logseq writes a page's first one.
|
|
216
|
+
Page properties map both directions: a first block of
|
|
209
217
|
`key:: value` lines and flat frontmatter entries ↔ leading
|
|
210
218
|
`#+key: value` lines, which Logseq reads as page properties;
|
|
211
219
|
frontmatter keys that act in Emacs (`todo`, `include`, …) stay inert.
|
|
212
|
-
|
|
213
|
-
text markers and `[#A]` priorities ↔ org keywords/priorities, page
|
|
220
|
+
Task markers and `[#A]` priorities stay text, page
|
|
214
221
|
references `[[page]]` and labeled forms `[label]([[page]])` ↔ org
|
|
215
222
|
fuzzy links `[[page][label]]`, block refs `[label](((uuid)))` ↔
|
|
216
223
|
`[[((uuid))][label]]`, and `^^highlight^^` markup survives verbatim
|
|
217
224
|
(it would otherwise re-parse as superscripts).
|
|
218
225
|
- `obsidian()`: wikilinks `[[Page]]` / `[[Page|alias]]` ↔ org fuzzy links
|
|
226
|
+
- `inputPreset` / `outputPreset` (on both conversions): the dialect
|
|
227
|
+
the input is read in and the one the output is written in (ADR
|
|
228
|
+
0006); leaving one out is Vanilla. `preset` sets both, but leaves a
|
|
229
|
+
side Vanilla whose format the preset has no dialect for (Obsidian
|
|
230
|
+
writes no org); a side preset without one throws, and so does
|
|
231
|
+
`preset` naming another preset than a side preset.
|
|
219
232
|
|
|
220
233
|
- `normalizeMarkdown(md, { preset })` / `normalizeOrg(org, { preset })`
|
|
221
234
|
(CLI: `morg normalize`): one full round trip to morg's canonical
|
|
222
|
-
form, a fixed point
|
|
235
|
+
form, a fixed point, within one dialect: different presets per side
|
|
236
|
+
throw. Canonicalization, not styling: org-isms and
|
|
223
237
|
md-isms are rewritten exactly as a conversion would rewrite them.
|
|
224
238
|
Normalize with the same preset/config you will convert with, since
|
|
225
239
|
convergence is per-config (ADR 0002).
|
|
@@ -245,8 +259,8 @@ Two pipelines, each with a two-phase transformation separating the
|
|
|
245
259
|
dialect-agnostic core from dialect presets:
|
|
246
260
|
|
|
247
261
|
```text
|
|
248
|
-
md → org: remark-parse → mdast→uniorg (core) →
|
|
249
|
-
org → md: uniorg-parse →
|
|
262
|
+
md → org: remark-parse → mdast→uniorg (core) → read md dialect → write org dialect → uniorg-stringify
|
|
263
|
+
org → md: uniorg-parse → read org dialect → write md dialect → uniorg→mdast (core) → remark-stringify
|
|
250
264
|
```
|
|
251
265
|
|
|
252
266
|
Formatting is controlled by shaping the AST (e.g. inserting newline text nodes),
|
|
@@ -260,7 +274,10 @@ Project vocabulary lives in [CONTEXT.md](CONTEXT.md); design decisions in
|
|
|
260
274
|
|
|
261
275
|
The core conversion surface is feature-complete and validated against
|
|
262
276
|
real-world Logseq org vaults (edge cases found there live on as
|
|
263
|
-
anonymized fixtures, e.g. `tests/fixtures/logseq-vault.org`)
|
|
277
|
+
anonymized fixtures, e.g. `tests/fixtures/logseq-vault.org`), and
|
|
278
|
+
the conversions between a Logseq dialect and Vanilla Org or Markdown
|
|
279
|
+
are checked by round trips of that vault and of public Markdown and
|
|
280
|
+
Org documentation from either side; the
|
|
264
281
|
client-side [Web UI](https://morg.binarypoetry.ch) is deployed from
|
|
265
282
|
`main`. The npm package is `@remigius42/morg`, since the bare `morg`
|
|
266
283
|
name is taken, and pushing a `v*` tag publishes it. Most of the code is
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface CliArgs {
|
|
|
7
7
|
inputFile: string | undefined;
|
|
8
8
|
outputFile: string | undefined;
|
|
9
9
|
presetName: string | undefined;
|
|
10
|
+
inputPresetName: string | undefined;
|
|
11
|
+
outputPresetName: string | undefined;
|
|
10
12
|
silent: boolean | undefined;
|
|
11
13
|
taskCheckboxes: boolean | undefined;
|
|
12
14
|
interpretHtml: boolean | undefined;
|
package/dist/cli/args.js
CHANGED
package/dist/cli/conversion.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { MorgConfig } from "../config.js";
|
|
2
2
|
import type { MarkdownStyleOptions } from "../options.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { PresetOptions } from "../presets/sides.js";
|
|
4
4
|
import type { CliArgs } from "./args.js";
|
|
5
|
-
export declare function buildConversionOptions(cli: CliArgs, config: MorgConfig,
|
|
5
|
+
export declare function buildConversionOptions(cli: CliArgs, config: MorgConfig, presets: PresetOptions): {
|
|
6
6
|
mdToOrgOptions: import("../options.js").MarkdownToOrgOptions;
|
|
7
7
|
orgToMdOptions: import("../options.js").OrgToMarkdownOptions & {
|
|
8
8
|
markdownStyle: MarkdownStyleOptions;
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
|
-
export declare function convert(inputContent: string, fromFormat: string, normalize: boolean, cli: CliArgs, config: MorgConfig,
|
|
11
|
+
export declare function convert(inputContent: string, fromFormat: string, normalize: boolean, cli: CliArgs, config: MorgConfig, presets: PresetOptions): string;
|
package/dist/cli/conversion.js
CHANGED
|
@@ -3,7 +3,7 @@ import { convertOrgToMarkdown } from "../orgToMarkdown.js";
|
|
|
3
3
|
import { normalizeMarkdown, normalizeOrg } from "../normalize.js";
|
|
4
4
|
import { buildConversionOptions as layerOptions } from "../conversionOptions.js";
|
|
5
5
|
import { CliError } from "./error.js";
|
|
6
|
-
export function buildConversionOptions(cli, config,
|
|
6
|
+
export function buildConversionOptions(cli, config, presets) {
|
|
7
7
|
// an explicit --silent wins over the config; dropped constructs are
|
|
8
8
|
// reported on stderr unless it ends up on
|
|
9
9
|
const silent = cli.silent ?? config.silent ?? false;
|
|
@@ -23,14 +23,14 @@ export function buildConversionOptions(cli, config, preset) {
|
|
|
23
23
|
recordStyle: cli.recordStyle,
|
|
24
24
|
markdownStyle
|
|
25
25
|
}, config, {
|
|
26
|
-
|
|
26
|
+
...presets,
|
|
27
27
|
onWarning,
|
|
28
28
|
...(config.orgismKeys && { orgismKeys: config.orgismKeys })
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
-
export function convert(inputContent, fromFormat, normalize, cli, config,
|
|
31
|
+
export function convert(inputContent, fromFormat, normalize, cli, config, presets) {
|
|
32
32
|
try {
|
|
33
|
-
const { mdToOrgOptions, orgToMdOptions } = buildConversionOptions(cli, config,
|
|
33
|
+
const { mdToOrgOptions, orgToMdOptions } = buildConversionOptions(cli, config, presets);
|
|
34
34
|
if (normalize) {
|
|
35
35
|
return fromFormat === "markdown"
|
|
36
36
|
? normalizeMarkdown(inputContent, {
|
package/dist/cli/flags.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type StringOption = "fromFormat" | "toFormat" | "inputFile" | "outputFile" | "presetName" | "configPath";
|
|
1
|
+
type StringOption = "fromFormat" | "toFormat" | "inputFile" | "outputFile" | "presetName" | "inputPresetName" | "outputPresetName" | "configPath";
|
|
2
2
|
type BooleanOption = "silent" | "taskCheckboxes" | "interpretHtml" | "recordStyle";
|
|
3
3
|
type InfoOption = "help" | "version";
|
|
4
4
|
export type FlagSpec = ({
|
package/dist/cli/flags.js
CHANGED
|
@@ -34,10 +34,24 @@ export const FLAGS = [
|
|
|
34
34
|
{
|
|
35
35
|
names: ["--preset"],
|
|
36
36
|
arg: "<name>",
|
|
37
|
-
description: "
|
|
37
|
+
description: "Read and write an editor's dialect, for example logseq",
|
|
38
38
|
kind: "string",
|
|
39
39
|
key: "presetName"
|
|
40
40
|
},
|
|
41
|
+
{
|
|
42
|
+
names: ["--input-preset"],
|
|
43
|
+
arg: "<name>",
|
|
44
|
+
description: "Read the input in a preset's dialect",
|
|
45
|
+
kind: "string",
|
|
46
|
+
key: "inputPresetName"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
names: ["--output-preset"],
|
|
50
|
+
arg: "<name>",
|
|
51
|
+
description: "Write the output in a preset's dialect",
|
|
52
|
+
kind: "string",
|
|
53
|
+
key: "outputPresetName"
|
|
54
|
+
},
|
|
41
55
|
{
|
|
42
56
|
names: ["--config"],
|
|
43
57
|
arg: "<file>",
|
package/dist/cli/formats.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { CliArgs } from "./args.js";
|
|
2
|
+
import type { Format } from "../presets/sides.js";
|
|
2
3
|
export declare function inferFormats(cli: CliArgs): [fromFormat: string | undefined, toFormat: string | undefined];
|
|
3
|
-
export declare function validateFormats(fromFormat: string | undefined, toFormat: string | undefined, normalize: boolean):
|
|
4
|
+
export declare function validateFormats(fromFormat: string | undefined, toFormat: string | undefined, normalize: boolean): [fromFormat: Format, toFormat: Format];
|
package/dist/cli/formats.js
CHANGED
package/dist/cli/presets.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { MorgConfig } from "../config.js";
|
|
2
|
+
import type { PresetOptions } from "../presets/sides.js";
|
|
3
|
+
import type { CliArgs } from "./args.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the presets per side, flags over the config (ADR 0006).
|
|
6
|
+
* @param cli The parsed arguments.
|
|
7
|
+
* @param config The parsed `morg.toml`.
|
|
8
|
+
* @param from The input's format.
|
|
9
|
+
* @param to The output's format.
|
|
10
|
+
* @returns The conversion's preset options.
|
|
11
|
+
* @throws {CliError} If the names conflict, are unknown or lack a dialect.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolvePresets(cli: CliArgs, config: MorgConfig, from: "markdown" | "org", to: "markdown" | "org"): PresetOptions;
|
package/dist/cli/presets.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolvePresetOptions } from "../conversionOptions.js";
|
|
2
2
|
import { CliError } from "./error.js";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the presets per side, flags over the config (ADR 0006).
|
|
5
|
+
* @param cli The parsed arguments.
|
|
6
|
+
* @param config The parsed `morg.toml`.
|
|
7
|
+
* @param from The input's format.
|
|
8
|
+
* @param to The output's format.
|
|
9
|
+
* @returns The conversion's preset options.
|
|
10
|
+
* @throws {CliError} If the names conflict, are unknown or lack a dialect.
|
|
11
|
+
*/
|
|
12
|
+
export function resolvePresets(cli, config, from, to) {
|
|
13
|
+
const flags = {
|
|
14
|
+
...(cli.presetName && { preset: cli.presetName }),
|
|
15
|
+
...(cli.inputPresetName && { inputPreset: cli.inputPresetName }),
|
|
16
|
+
...(cli.outputPresetName && { outputPreset: cli.outputPresetName })
|
|
17
|
+
};
|
|
4
18
|
try {
|
|
5
|
-
return
|
|
19
|
+
return resolvePresetOptions([flags, config], from, to);
|
|
6
20
|
}
|
|
7
21
|
catch (error) {
|
|
8
22
|
throw new CliError(`Error: ${error.message}`);
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { parseArgs } from "./cli/args.js";
|
|
4
4
|
import { loadConfig } from "./cli/configFile.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolvePresets } from "./cli/presets.js";
|
|
6
6
|
import { inferFormats, validateFormats } from "./cli/formats.js";
|
|
7
7
|
import { convert } from "./cli/conversion.js";
|
|
8
8
|
import { CliError } from "./cli/error.js";
|
|
@@ -46,11 +46,10 @@ async function main() {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
const config = loadConfig(cli.configPath);
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
validateFormats(fromFormat, toFormat, cli.normalize);
|
|
49
|
+
const [fromFormat, toFormat] = validateFormats(...inferFormats(cli), cli.normalize);
|
|
50
|
+
const presets = resolvePresets(cli, config, fromFormat, toFormat);
|
|
52
51
|
const inputContent = await readInput(cli.inputFile);
|
|
53
|
-
const outputContent = convert(inputContent, fromFormat, cli.normalize, cli, config,
|
|
52
|
+
const outputContent = convert(inputContent, fromFormat, cli.normalize, cli, config, presets);
|
|
54
53
|
if (cli.outputFile) {
|
|
55
54
|
fs.writeFileSync(cli.outputFile, outputContent, "utf8");
|
|
56
55
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import type { MarkdownToOrgOptions, OrgToMarkdownOptions } from "./options.js";
|
|
2
|
+
type PresetKey = "preset" | "inputPreset" | "outputPreset";
|
|
2
3
|
/**
|
|
3
4
|
* Shape of `morg.toml`. Sections mirror the library options objects;
|
|
4
|
-
* `preset` and `silent` mirror their CLI
|
|
5
|
-
* by both directions. Precedence: CLI >
|
|
5
|
+
* `preset`, `inputPreset`, `outputPreset` and `silent` mirror their CLI
|
|
6
|
+
* flags; `orgismKeys` is shared by both directions. Precedence: CLI >
|
|
7
|
+
* config > defaults, per side for the presets.
|
|
6
8
|
*/
|
|
7
9
|
export interface MorgConfig {
|
|
8
10
|
preset?: string;
|
|
11
|
+
inputPreset?: string;
|
|
12
|
+
outputPreset?: string;
|
|
9
13
|
silent?: boolean;
|
|
10
14
|
orgismKeys?: Record<string, string>;
|
|
11
|
-
markdownToOrg?: Omit<MarkdownToOrgOptions,
|
|
12
|
-
orgToMarkdown?: Omit<OrgToMarkdownOptions,
|
|
15
|
+
markdownToOrg?: Omit<MarkdownToOrgOptions, PresetKey | "onWarning">;
|
|
16
|
+
orgToMarkdown?: Omit<OrgToMarkdownOptions, PresetKey | "onWarning" | "orgismKeys">;
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Parses a `morg.toml` source string. Unknown top-level keys are
|
|
@@ -18,3 +22,4 @@ export interface MorgConfig {
|
|
|
18
22
|
* @returns The parsed configuration.
|
|
19
23
|
*/
|
|
20
24
|
export declare function parseConfig(source: string): MorgConfig;
|
|
25
|
+
export {};
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MorgConfig } from "./config.js";
|
|
2
2
|
import type { MarkdownStyleOptions, MarkdownToOrgOptions, OrgToMarkdownOptions, Toggle } from "./options.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type Format, type PresetOptions } from "./presets/sides.js";
|
|
4
4
|
/** Explicitly requested option values; `undefined` leaves it to config. */
|
|
5
5
|
export interface ConversionOverrides {
|
|
6
6
|
taskCheckboxes?: boolean;
|
|
@@ -10,8 +10,7 @@ export interface ConversionOverrides {
|
|
|
10
10
|
markdownStyle?: MarkdownStyleOptions;
|
|
11
11
|
}
|
|
12
12
|
/** Values that apply to both directions. */
|
|
13
|
-
export interface SharedConversionOptions {
|
|
14
|
-
preset?: Preset;
|
|
13
|
+
export interface SharedConversionOptions extends PresetOptions {
|
|
15
14
|
onWarning?: (message: string) => void;
|
|
16
15
|
orgismKeys?: Record<string, string>;
|
|
17
16
|
}
|
|
@@ -30,3 +29,20 @@ export declare function buildConversionOptions(overrides: ConversionOverrides, c
|
|
|
30
29
|
markdownStyle: MarkdownStyleOptions;
|
|
31
30
|
};
|
|
32
31
|
};
|
|
32
|
+
/** Preset names as one layer (CLI, form or config) sets them. */
|
|
33
|
+
export interface PresetNames {
|
|
34
|
+
preset?: string;
|
|
35
|
+
inputPreset?: string;
|
|
36
|
+
outputPreset?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolves the presets of a conversion over layers of names (ADR 0006):
|
|
40
|
+
* per side, the first layer that sets the side or `preset` wins.
|
|
41
|
+
* @param layers Preset names by layer, highest first.
|
|
42
|
+
* @param from The input's format.
|
|
43
|
+
* @param to The output's format; the input's for normalizing.
|
|
44
|
+
* @returns The conversion's preset options.
|
|
45
|
+
* @throws If a layer sets `preset` and another side preset, or a side
|
|
46
|
+
* preset has no dialect for its side's format, or normalizing names two.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolvePresetOptions(layers: PresetNames[], from: Format, to: Format): PresetOptions;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveSide } from "./presets/sides.js";
|
|
2
|
+
import { createPreset } from "./presets/registry.js";
|
|
1
3
|
/**
|
|
2
4
|
* Layers explicit overrides over the config file, per direction. Used by
|
|
3
5
|
* both adapters (CLI and Web UI) so the documented precedence (CLI or
|
|
@@ -32,3 +34,62 @@ export function buildConversionOptions(overrides, config, shared) {
|
|
|
32
34
|
};
|
|
33
35
|
return { mdToOrgOptions, orgToMdOptions };
|
|
34
36
|
}
|
|
37
|
+
function sideName(layers, key) {
|
|
38
|
+
for (const layer of layers) {
|
|
39
|
+
if (layer[key]) {
|
|
40
|
+
return { name: layer[key], shorthand: false };
|
|
41
|
+
}
|
|
42
|
+
if (layer.preset) {
|
|
43
|
+
return { name: layer.preset, shorthand: true };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
// across layers the higher one wins; within one, two names conflict
|
|
49
|
+
function rejectConflict(layer) {
|
|
50
|
+
for (const key of ["inputPreset", "outputPreset"]) {
|
|
51
|
+
const side = layer[key];
|
|
52
|
+
if (layer.preset && side && side !== layer.preset) {
|
|
53
|
+
throw new Error(`preset '${layer.preset}' conflicts with ${key} '${side}'`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function sidePreset(name, format, side) {
|
|
58
|
+
const preset = createPreset(name?.name);
|
|
59
|
+
return name?.shorthand
|
|
60
|
+
? resolveSide(undefined, preset, format, side)
|
|
61
|
+
: resolveSide(preset, undefined, format, side);
|
|
62
|
+
}
|
|
63
|
+
// normalizing goes there and back within one dialect (ADR 0006), so
|
|
64
|
+
// both sides must name the same preset, which then sets both ways
|
|
65
|
+
function normalizePresetOptions(layers) {
|
|
66
|
+
const input = sideName(layers, "inputPreset")?.name ?? "vanilla";
|
|
67
|
+
const output = sideName(layers, "outputPreset")?.name ?? "vanilla";
|
|
68
|
+
if (input !== output) {
|
|
69
|
+
throw new Error(`normalize takes one preset; got inputPreset '${input}' and outputPreset '${output}'`);
|
|
70
|
+
}
|
|
71
|
+
const preset = createPreset(input);
|
|
72
|
+
return preset ? { preset } : {};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolves the presets of a conversion over layers of names (ADR 0006):
|
|
76
|
+
* per side, the first layer that sets the side or `preset` wins.
|
|
77
|
+
* @param layers Preset names by layer, highest first.
|
|
78
|
+
* @param from The input's format.
|
|
79
|
+
* @param to The output's format; the input's for normalizing.
|
|
80
|
+
* @returns The conversion's preset options.
|
|
81
|
+
* @throws If a layer sets `preset` and another side preset, or a side
|
|
82
|
+
* preset has no dialect for its side's format, or normalizing names two.
|
|
83
|
+
*/
|
|
84
|
+
export function resolvePresetOptions(layers, from, to) {
|
|
85
|
+
layers.forEach(rejectConflict);
|
|
86
|
+
if (from === to) {
|
|
87
|
+
return normalizePresetOptions(layers);
|
|
88
|
+
}
|
|
89
|
+
const input = sidePreset(sideName(layers, "inputPreset"), from, "input");
|
|
90
|
+
const output = sidePreset(sideName(layers, "outputPreset"), to, "output");
|
|
91
|
+
return {
|
|
92
|
+
...(input && { inputPreset: input }),
|
|
93
|
+
...(output && { outputPreset: output })
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -4,6 +4,13 @@ import type { OrgData } from "uniorg";
|
|
|
4
4
|
* holds a bare underscore or caret org would read as a script.
|
|
5
5
|
*/
|
|
6
6
|
export declare function requireBracedScripts(uniorgAst: OrgData): void;
|
|
7
|
+
/**
|
|
8
|
+
* Whether org→md would consume a `^:{}` setting at the head of `org`:
|
|
9
|
+
* its text reads a script org would otherwise take for one.
|
|
10
|
+
* @param org The org text, the setting included.
|
|
11
|
+
* @returns Whether the setting is taken as the one md→org adds.
|
|
12
|
+
*/
|
|
13
|
+
export declare function consumesBracedScripts(org: string): boolean;
|
|
7
14
|
/**
|
|
8
15
|
* org→md: parses org, honoring its `^:` setting, and consuming `^:{}`
|
|
9
16
|
* where the text needs it: md→org adds it only then, so anywhere else
|
|
@@ -135,6 +135,16 @@ function takeBracedScripts(uniorgAst) {
|
|
|
135
135
|
return node.value !== "";
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Whether org→md would consume a `^:{}` setting at the head of `org`:
|
|
140
|
+
* its text reads a script org would otherwise take for one.
|
|
141
|
+
* @param org The org text, the setting included.
|
|
142
|
+
* @returns Whether the setting is taken as the one md→org adds.
|
|
143
|
+
*/
|
|
144
|
+
export function consumesBracedScripts(org) {
|
|
145
|
+
// text without a bare script candidate skips the parse
|
|
146
|
+
return (BARE_SCRIPT_RE.test(org) && readsBareScripts(bracedScriptsParser.parse(org)));
|
|
147
|
+
}
|
|
138
148
|
/**
|
|
139
149
|
* org→md: parses org, honoring its `^:` setting, and consuming `^:{}`
|
|
140
150
|
* where the text needs it: md→org adds it only then, so anywhere else
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OrgData } from "uniorg";
|
|
2
2
|
import { type Scalar } from "yaml";
|
|
3
|
+
export declare const FRONTMATTER_BLOCK_BEGIN = "#+begin_comment morg_frontmatter";
|
|
3
4
|
export interface FrontmatterNode {
|
|
4
5
|
type: "morg-frontmatter";
|
|
5
6
|
yaml: string;
|
|
@@ -15,6 +16,12 @@ export declare function isFrontmatterNode(node: {
|
|
|
15
16
|
* @param uniorgAst The document.
|
|
16
17
|
*/
|
|
17
18
|
export declare function renderFileHeader(uniorgAst: OrgData): void;
|
|
19
|
+
/**
|
|
20
|
+
* The Frontmatter Block carrying YAML through org (ADR 0005).
|
|
21
|
+
* @param yaml The YAML source, without its `---` fences.
|
|
22
|
+
* @returns The block's org text.
|
|
23
|
+
*/
|
|
24
|
+
export declare function frontmatterBlock(yaml: string): string;
|
|
18
25
|
/**
|
|
19
26
|
* md→org: takes the top-level entries `take` maps to keywords out of
|
|
20
27
|
* the frontmatter; the rest stays verbatim, comments included.
|
|
@@ -4,7 +4,7 @@ import { CST, isMap, isScalar, isSeq, Lexer, parseDocument, visit } from "yaml";
|
|
|
4
4
|
// frontmatter travels verbatim in a comment block marked as morg's
|
|
5
5
|
// (ADR 0005); uniorg keeps neither the block's parameter nor its
|
|
6
6
|
// unescaped value, so the block is rendered as raw text
|
|
7
|
-
const FRONTMATTER_BLOCK_BEGIN = "#+begin_comment morg_frontmatter";
|
|
7
|
+
export const FRONTMATTER_BLOCK_BEGIN = "#+begin_comment morg_frontmatter";
|
|
8
8
|
export function isFrontmatterNode(node) {
|
|
9
9
|
return node.type === "morg-frontmatter";
|
|
10
10
|
}
|
|
@@ -51,7 +51,12 @@ function detachLeadingKeywords(uniorgAst) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The Frontmatter Block carrying YAML through org (ADR 0005).
|
|
56
|
+
* @param yaml The YAML source, without its `---` fences.
|
|
57
|
+
* @returns The block's org text.
|
|
58
|
+
*/
|
|
59
|
+
export function frontmatterBlock(yaml) {
|
|
55
60
|
const body = yaml ? `${escapeBlockLines(yaml)}\n` : "";
|
|
56
61
|
return `${FRONTMATTER_BLOCK_BEGIN}\n${body}#+end_comment\n`;
|
|
57
62
|
}
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import type { Parent } from "unist";
|
|
2
2
|
import { type Node } from "./render.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whether org reads a line as anything but a plain paragraph.
|
|
5
|
+
* @param line The line, without its line break.
|
|
6
|
+
* @returns Whether it reads as org line syntax.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readsAsLineSyntax(line: string): boolean;
|
|
3
9
|
/**
|
|
4
10
|
* md→org: a paragraph line org would read as line syntax (an escaped
|
|
5
11
|
* `1\.` or `\*`, or a lazy continuation line) gets a leading zero-width
|
|
6
12
|
* space, or it would turn into a list item, headline, comment or table.
|
|
7
13
|
*/
|
|
8
14
|
export declare function escapeLineSyntax(tree: Parent): void;
|
|
15
|
+
/**
|
|
16
|
+
* Whether org reads a text as just one passthrough element.
|
|
17
|
+
* @param text The org text.
|
|
18
|
+
* @returns Whether it is one block, drawer, keyword … org→md writes as
|
|
19
|
+
* its org text.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readsAsPassthrough(text: string): boolean;
|
|
9
22
|
/**
|
|
10
23
|
* md→org: whether a node is a paragraph org→md wrote as the org text of
|
|
11
24
|
* one passthrough element, which goes back as it is.
|
package/dist/core/lineSyntax.js
CHANGED
|
@@ -9,8 +9,12 @@ import { delimiters, isInline, locate, positionParser, renderInline, tryParse }
|
|
|
9
9
|
// `:` (`1.`, `a)`, `CLOCK:`, `_.`); any other line (one starting with a
|
|
10
10
|
// link, markup or code, say) is text, and skips the parse
|
|
11
11
|
const MAY_BE_LINE_SYNTAX_RE = /^(?:[-+]|\*+)(?:\s|$)|^(?:-{5}|\+-|#(?:\s|$|\+)|[|:]|\[fn:|\\begin\{|%%\()|^[\p{L}\p{N}_]+[.):]/u;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Whether org reads a line as anything but a plain paragraph.
|
|
14
|
+
* @param line The line, without its line break.
|
|
15
|
+
* @returns Whether it reads as org line syntax.
|
|
16
|
+
*/
|
|
17
|
+
export function readsAsLineSyntax(line) {
|
|
14
18
|
if (!MAY_BE_LINE_SYNTAX_RE.test(line)) {
|
|
15
19
|
return false;
|
|
16
20
|
}
|
|
@@ -135,8 +139,16 @@ function isPassthrough(starts) {
|
|
|
135
139
|
if (!first || !MAY_BE_LINE_SYNTAX_RE.test(first.line)) {
|
|
136
140
|
return false;
|
|
137
141
|
}
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
return readsAsPassthrough(starts.map(({ line }) => line).join("\n"));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Whether org reads a text as just one passthrough element.
|
|
146
|
+
* @param text The org text.
|
|
147
|
+
* @returns Whether it is one block, drawer, keyword … org→md writes as
|
|
148
|
+
* its org text.
|
|
149
|
+
*/
|
|
150
|
+
export function readsAsPassthrough(text) {
|
|
151
|
+
const [only, ...rest] = tryParse(`${text}\n`)?.children ?? [];
|
|
140
152
|
return !rest.length && PASSTHROUGH_TYPES.has(only?.type ?? "");
|
|
141
153
|
}
|
|
142
154
|
/**
|
|
@@ -125,8 +125,11 @@ export function transformMdastCode(node) {
|
|
|
125
125
|
value: `${node.value}\n`
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
// the fence's meta is the block's switches and header arguments,
|
|
129
|
+
// which uniorg-stringify writes after the language
|
|
130
|
+
const language = [node.lang, node.meta].filter(Boolean).join(" ");
|
|
128
131
|
return (node.lang
|
|
129
|
-
? { type: "src-block", language
|
|
132
|
+
? { type: "src-block", language, value: node.value }
|
|
130
133
|
: {
|
|
131
134
|
type: "example-block",
|
|
132
135
|
value: node.value
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Root } from "mdast";
|
|
2
|
+
/** Whether a line opens an org block (`#+begin_name`). */
|
|
3
|
+
export declare function isOrgBlockStart(line: string): boolean;
|
|
4
|
+
/** Whether a line opens an org drawer (`:NAME:`). */
|
|
5
|
+
export declare function isDrawerStart(line: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* The line that ends the org block or drawer a line opens.
|
|
8
|
+
* @param lines The lines.
|
|
9
|
+
* @param start The index of the opening line.
|
|
10
|
+
* @returns The index of the `#+end_name` or `:END:` line, or -1.
|
|
11
|
+
*/
|
|
12
|
+
export declare function orgElementEnd(lines: string[], start: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* md→org: a passthrough element's org text may hold lines Markdown reads
|
|
15
|
+
* as syntax of its own (a blank line and an indented one, a `#`, `-`
|
|
16
|
+
* or `>` line); the top-level nodes it parsed into become one paragraph
|
|
17
|
+
* of the source text again, which goes back to org as it is.
|
|
18
|
+
* @param mdast The parsed Markdown.
|
|
19
|
+
* @param markdown Its source.
|
|
20
|
+
*/
|
|
21
|
+
export declare function keepPassthroughSource(mdast: Root, markdown: string): void;
|