Skip to content

Commit

Permalink
feat: add support for upgrading glossaries
Browse files Browse the repository at this point in the history
  • Loading branch information
agoose77 committed May 29, 2024
1 parent e38fcc3 commit e7728be
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 1 deletion.
127 changes: 127 additions & 0 deletions packages/myst-cli/src/upgrade/syntax.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { mystParse } from 'myst-parser';
import fs from 'node:fs';
import { globSync } from 'glob';
import { selectAll } from 'unist-util-select';
import { toText } from 'myst-common';

type Line = {
content: string;
offset: number;
};

type LegacyGlossaryItem = {
termLines: Line[];
definitionLines: Line[];
};

export function upgradeGlossaries() {
const mdFiles = globSync('**/*.md');
mdFiles.forEach(upgradeGlossary);
}

const SPLIT_PATTERN = /\r\n|\r|\n/;

function upgradeGlossary(path: string) {
const backupFilePath = `${path}.myst.bak`;
if (fs.existsSync(backupFilePath)) {
return;
}
const data = fs.readFileSync(path).toString();
const documentLines = data.split(SPLIT_PATTERN);

const mdast = mystParse(data);
const glossaryNodes = selectAll('mystDirective[name=glossary]', mdast);

// Track the edit point
let editOffset = 0;
for (const node of glossaryNodes) {
const nodeLines = ((node as any).value as string).split(SPLIT_PATTERN);

// TODO: assert span items

// Flag tracking whether the line-processor expects definition lines
let inDefinition = false;
let indentSize = 0;

const entries: LegacyGlossaryItem[] = [];

// Parse lines into separate entries
for (let i = 0; i < nodeLines.length; i++) {
const line = nodeLines[i];
// Is the line a comment?
if (/^\.\.\s/.test(line) || !line.length) {
continue;
}
// Is the line a non-whitespace-leading line (term declaration)?
else if (/^[^\s]/.test(line[0])) {
// Comment
if (line.startsWith('.. ')) {
continue;
}

// Do we need to create a new entry?
if (inDefinition || !entries.length) {
// Close the current definition, open a new term
entries.push({
definitionLines: [],
termLines: [{ content: line, offset: i }],
});
inDefinition = false;
}
// Can we extend existing entry with an additional term?
else if (entries.length) {
entries[entries.length - 1].termLines.push({ content: line, offset: i });
}
}
// Open a definition
else if (!inDefinition) {
inDefinition = true;
indentSize = line.length - line.replace(/^\s+/, '').length;

if (entries.length) {
entries[entries.length - 1].definitionLines.push({
content: line.slice(indentSize),
offset: i,
});
}
}
}

// Build glossary
const newLines: string[] = [];

for (const entry of entries) {
const { termLines, definitionLines } = entry;

const definitionBody = definitionLines.map((line) => line.content).join('\n');
const [firstTerm, ...restTerms] = termLines;

// Initial definition
const [firstTermValue, ..._] = firstTerm.content.split(/\s+:\s+/);
newLines.push(firstTermValue, `: ${definitionBody}\n`);

if (restTerms) {
// Terms can contain markup, but we need the text-form to create a term reference
// TODO: what if something magical like an xref is used here? Assume not.
const parsedTerm = mystParse(firstTermValue);
const termName = toText(parsedTerm);
for (const { content } of restTerms) {
const [term, ..._] = content.split(/\s+:\s+/);
newLines.push(term, `: {term}\`${termName}\`\n`);
}
}
}
const nodeSpan = { start: node.position?.start?.line, stop: node.position?.end?.line };
const spanLength = nodeSpan.stop! - nodeSpan.start! - 1;
documentLines.splice(nodeSpan.start! + editOffset, spanLength, ...newLines);

// Offset our insert cursor
editOffset += newLines.length - spanLength;
}

// Update the file
if (glossaryNodes.length) {
fs.copyFileSync(path, backupFilePath);
fs.writeFileSync(path, documentLines.join('\n'));
}
}
5 changes: 4 additions & 1 deletion packages/myst-cli/src/upgrade/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import yaml from 'js-yaml';
import type { Config } from 'myst-config';
import { upgradeConfig, validateJupyterBookConfig } from './config.js';
import { upgradeTOC, validateSphinxExternalTOC } from './toc.js';
import { upgradeGlossaries } from './syntax.js';
import { defined } from './utils.js';
export async function upgrade(session: ISession, opts: any) {
// TODO: generalize and pull this out!
const upgradeLog: Record<string, any> = {
input: {
opts: opts,
Expand Down Expand Up @@ -37,6 +37,9 @@ export async function upgrade(session: ISession, opts: any) {
}
}

upgradeGlossaries();

// Write new myst.yml
fs.writeFileSync('myst.yml', yaml.dump(config));

writeJsonLogs(session, 'myst.upgrade.json', upgradeLog);
Expand Down

0 comments on commit e7728be

Please sign in to comment.