UNPKG

@curvenote/cli

Version:
132 lines (131 loc) 6.21 kB
import path from 'node:path'; import YAML from 'js-yaml'; import { prepareToWrite } from 'myst-cli'; import { writeFileToFolder } from 'myst-cli-utils'; import { KINDS, oxaLink } from '@curvenote/blocks'; import { fillPageFrontmatter } from 'myst-frontmatter'; import { createId, toMyst } from '@curvenote/schema'; import { pageFrontmatterFromDTOAndThumbnail, projectFrontmatterFromDTO, saveAffiliations, } from '../../frontmatter.js'; import { Block, Project, Version } from '../../models.js'; import { resolvePath } from '../../utils/index.js'; import { remoteExportWrapper } from './utils/remoteExportWrapper.js'; import { getChildren } from './utils/getChildren.js'; import { localizationOptions } from './utils/localizationOptions.js'; import { walkArticle } from './utils/walkArticle.js'; import { writeBibtex } from './utils/writeBibtex.js'; import { writeImagesToFiles } from './utils/writeImagesToFiles.js'; /** * Pull content from a version of kind Output, cache in mdast snippets, and return {mdast} directive * * Note: the output data cached here is directly from the API; it still needs to be minified in * the transformOutputs transform. * */ async function createOutputSnippet(session, version, name, mdastSnippets) { const response = await session.fetch(version.data.links.download); if (!response.ok) return ''; const outputData = (await response.json()); const snippetId = `${name}#${createId()}`; mdastSnippets[snippetId] = { type: 'output', data: outputData, }; return `\`\`\`{mdast} ${snippetId}\n\`\`\``; } export async function articleToMarkdown(session, versionId, opts) { var _a, _b, _c; const [block, version] = await Promise.all([ new Block(session, versionId).get(), new Version(session, versionId).get(), getChildren(session, versionId), ]); const { data } = version; if (data.kind !== KINDS.Article) throw new Error('Not an article'); const article = await walkArticle(session, data); const imageFilenames = await writeImagesToFiles(session, article.images, { buildPath: opts === null || opts === void 0 ? void 0 : opts.path, basePath: (_a = opts === null || opts === void 0 ? void 0 : opts.images) !== null && _a !== void 0 ? _a : 'images', simple: true, }); const localization = localizationOptions(session, imageFilenames, article.references); const mdastName = `${opts.filename.replace(/\.md$/, '')}.mdast.json`; const articleMdastSnippets = {}; const content = await Promise.all(article.children.map(async (child) => { var _a, _b; if (!child.version) return ''; const blockData = { oxa: oxaLink('', child.version.id), tags: ((_a = child.block) === null || _a === void 0 ? void 0 : _a.data.tags) || [], part: ((_b = child.block) === null || _b === void 0 ? void 0 : _b.data.part) || undefined, }; let md = ''; let mdastSnippets = {}; if (opts.keepOutputs && child.version.data.kind === KINDS.Output) { // Reprocess output here, ignoring Output state from walkArticle md = await createOutputSnippet(session, child.version, mdastName, mdastSnippets); } else if (child.state) { const myst = toMyst(child.state.doc, { ...localization, renderers: { iframe: 'myst' }, createMdastImportId() { return `${mdastName}#${createId()}`; }, }); md = myst.content; mdastSnippets = myst.mdastSnippets; } if (Object.keys(mdastSnippets).length) { Object.assign(articleMdastSnippets, mdastSnippets); } return `+++ ${JSON.stringify(blockData)}\n\n${md}`; })); const project = await new Project(session, block.id.project).get(); saveAffiliations(session, project.data); let frontmatter = await pageFrontmatterFromDTOAndThumbnail(session, resolvePath(opts.path, opts.filename), block.data, version.data.date); const validationOpts = { property: 'frontmatter', file: opts.filename, messages: {}, errorLogFn: (message) => { session.log.error(`Validation error: ${message}`); }, warningLogFn: (message) => { session.log.warn(`Validation: ${message}`); }, }; if (!opts.ignoreProjectFrontmatter) { const projectFrontmatter = projectFrontmatterFromDTO(session, project.data); frontmatter = fillPageFrontmatter(frontmatter, projectFrontmatter, validationOpts); } const metadata = YAML.dump(prepareToWrite(frontmatter)); let titleString = `---\n${metadata}---\n\n`; if (!opts.titleOnlyInFrontmatter) { // TODO: Remove the title when Jupyter Book allows title to be defined in the yaml. // https://github.com/executablebooks/MyST-Parser/pull/492 titleString += `# ${block.data.title}\n\n`; } let file = titleString + content.join('\n\n'); if (opts.renderReferences && Object.keys(article.references).length > 0) { file += '\n\n### References\n\n```{bibliography}\n:filter: docname in docnames\n```'; } file += '\n\n'; writeFileToFolder(resolvePath(opts.path, opts.filename), file); if (Object.keys(articleMdastSnippets).length) { const normalizedSnippets = Object.fromEntries(Object.entries(articleMdastSnippets).map(([k, v]) => [k.split('#')[1], v])); writeFileToFolder(resolvePath(opts.path, mdastName), JSON.stringify(normalizedSnippets, null, 2)); } if ((_b = opts.writeBibtex) !== null && _b !== void 0 ? _b : true) { session.log.debug('Writing bib file...'); // Write out the references await writeBibtex(session, article.references, (_c = opts === null || opts === void 0 ? void 0 : opts.bibtex) !== null && _c !== void 0 ? _c : 'main.bib', { path: path.join(opts.path || '', path.dirname(opts.filename)), alwaysWriteFile: false, }); } return article; } export const oxaLinkToMarkdown = remoteExportWrapper(articleToMarkdown);