@gemini-tools/gemtext
v0.0.4
Published
Parser for the Gemtext markup format used in the Gemini protocol
Readme
gemtext
A simple parser for the Gemtext markup based on the Gemini spec.
Usage
Parsing
import {
GemtextLineType,
GemtextLine,
parseGemtext,
serializeGemtext,
} from '@gemini-tools/gemtext';
declare const content: string;
const lines: Array<GemtextLine> = parseGemtext(content);
for (const line in lines) {
if (line.type === GemTextLineType.TEXT) {
// line -> { type: GemtextLineType.TEXT; text: string }
}
if (line.type === GemTextLineType.LINK) {
// line -> { type: GemtextLineType.LINK; url: string; text: string | null }
}
if (line.type === GemTextLineType.PRE) {
// line -> { type: GemtextLineType.PRE; alt: string; lines: Array<string> }
}
if (line.type === GemTextLineType.HEADER) {
// line -> { type: GemtextLineType.HEADER; level: number; text: string | null }
}
if (line.type === GemTextLineType.LIST_ITEM) {
// line -> { type: GemtextLineType.LIST_ITEM; text: string }
}
if (line.type === GemTextLineType.QUOTE) {
// line -> { type: GemtextLineType.QUOTE; text: string }
}
}Serialization
Convert parsed AST back to gemtext format:
// Parse gemtext
const lines = parseGemtext(gemtextString);
// Modify the AST as needed
lines.push({
type: GemtextLineType.LINK,
url: 'https://example.com',
text: 'New link',
});
// Serialize back to gemtext
const output = serializeGemtext(lines);
console.log(output);Round-Trip Parsing
Parsing and serializing preserves semantic content:
const original = parseGemtext(input);
const serialized = serializeGemtext(original);
const roundTrip = parseGemtext(serialized);
// original and roundTrip are structurally identicalStreaming Support
For large files or streaming sources, use the generator functions:
Sync Generators
import {
parseGemtextLines,
serializeGemtextLines,
} from '@gemini-tools/gemtext';
// Parse line-by-line from an array
const lines = ['# Header', 'Text', '=> https://example.com Link'];
for (const parsed of parseGemtextLines(lines)) {
console.log(parsed.type, parsed);
}
// Serialize incrementally
const ast = [
{ type: GemtextLineType.HEADER, level: 1, text: 'Title' },
{ type: GemtextLineType.TEXT, text: 'Content' },
];
for (const line of serializeGemtextLines(ast)) {
console.log(line); // Outputs gemtext strings one at a time
}Async Generators
import {
streamParseGemtext,
streamSerializeGemtext,
} from '@gemini-tools/gemtext';
import * as fs from 'fs';
import * as readline from 'readline';
// Parse from file stream
const fileStream = fs.createReadStream('document.gmi', { encoding: 'utf-8' });
const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
for await (const parsed of streamParseGemtext(rl)) {
console.log('Streamed:', parsed);
}
// Full async pipeline: read file -> parse -> serialize
const input = fs.createReadStream('input.gmi', { encoding: 'utf-8' });
const lines = readline.createInterface({ input, crlfDelay: Infinity });
for await (const line of streamSerializeGemtext(streamParseGemtext(lines))) {
process.stdout.write(line + '\n');
}