UNPKG

myst-cli

Version:
160 lines (159 loc) 7.61 kB
import fs from 'node:fs'; import { join, resolve } from 'node:path'; import { isDirectory, isUrl } from 'myst-cli-utils'; import { RuleId } from 'myst-common'; import { loadConfig } from '../config.js'; import { combineProjectCitationRenderers } from '../process/citations.js'; import { loadFile } from '../process/file.js'; import { selectors } from '../store/index.js'; import { projects } from '../store/reducers.js'; import { addWarningForFile } from '../utils/addWarningForFile.js'; import { getAllBibTexFilesOnPath } from '../utils/getAllBibtexFiles.js'; import { tocFile, validateSphinxTOC } from '../utils/toc.js'; import { projectFromPath } from './fromPath.js'; import { projectFromTOC, projectFromSphinxTOC, getIgnoreFiles } from './fromTOC.js'; import { writeTOCToConfigFile } from './toTOC.js'; import { binaryName, readableName } from '../utils/whiteLabelling.js'; /** * Load project structure from disk * * @param session session with logging * @param path root directory of project, relative to current directory * @param opts `index`, including path relative to current directory; default is 'index.md' * or 'readme.md' in 'path' directory * * If JupyterBook '_toc.yml' exists in path, project structure will be derived from that. * In this case, index will be ignored in favor of root from '_toc.yml' * If '_toc.yml' does not exist, project structure will be built from the local file/folder structure. */ export async function loadProjectFromDisk(session, path, opts) { var _a, _b; path = path || resolve('.'); if (!(opts === null || opts === void 0 ? void 0 : opts.reloadProject)) { const cachedProject = selectors.selectLocalProject(session.store.getState(), path); if (cachedProject) return cachedProject; } await loadConfig(session, path, opts); const state = session.store.getState(); const projectConfig = selectors.selectLocalProjectConfig(state, path); const projectConfigFile = (_a = selectors.selectLocalConfigFile(state, path)) !== null && _a !== void 0 ? _a : join(path, session.configFiles[0]); if (!projectConfig && (opts === null || opts === void 0 ? void 0 : opts.warnOnNoConfig)) { addWarningForFile(session, projectConfigFile, `Loading project from path with no config file: ${path}\nConsider running "${binaryName()} init --project" in that directory`, 'warn', { ruleId: RuleId.projectConfigExists }); } let newProject; let { index, writeTOC } = opts || {}; let legacyToc = false; const siteConfig = selectors.selectLocalSiteConfig(state, path); const folders = !!((_b = siteConfig === null || siteConfig === void 0 ? void 0 : siteConfig.options) === null || _b === void 0 ? void 0 : _b.folders); const sphinxTOCFile = validateSphinxTOC(session, path) ? tocFile(path) : undefined; if ((projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.toc) !== undefined) { newProject = projectFromTOC(session, path, projectConfig.toc, 1, projectConfigFile, { urlFolders: folders, }); if (sphinxTOCFile) { addWarningForFile(session, sphinxTOCFile, `Ignoring legacy Jupyter Book TOC in favor of myst.yml TOC: ${sphinxTOCFile}`, 'warn', { ruleId: RuleId.encounteredLegacyTOC, }); } if (writeTOC) session.log.warn('Not writing the table of contents, it already exists!'); writeTOC = false; } else if (sphinxTOCFile) { // Legacy validator legacyToc = true; if (!writeTOC) { // Do not warn if user is explicitly upgrading toc // TODO: Add this back as a warning rather than debug as we surface this feature more session.log.debug(`Encountered legacy Jupyter Book TOC: ${sphinxTOCFile}`); session.log.debug(`To upgrade to a ${readableName()} TOC, try running \`${binaryName()} init --write-toc\``); // addWarningForFile( // session, // filename, // `Encountered legacy jupyterbook TOC: ${sphinxTOCFile}`, // 'warn', // { // ruleId: RuleId.encounteredLegacyTOC, // note: 'To upgrade to a MyST TOC, try running `myst init --write-toc`', // }, // ); } newProject = projectFromSphinxTOC(session, path, undefined, { urlFolders: folders }); } else { const project = selectors.selectLocalProject(state, path); if (!index && !(project === null || project === void 0 ? void 0 : project.implicitIndex) && (project === null || project === void 0 ? void 0 : project.file)) { // If there is no new index, keep the original unless it was implicit previously index = project.file; } newProject = projectFromPath(session, path, index, { urlFolders: folders }); } if (!newProject) { throw new Error(`Could not load project from ${path}`); } if (writeTOC) { if (legacyToc) { session.log.info(`⬆️ Upgrading legacy Jupyter Book TOC to ${readableName()}: ${tocFile(path)}`); } session.log.info(`💾 Writing new TOC to: ${projectConfigFile}`); await writeTOCToConfigFile(newProject, projectConfigFile, projectConfigFile); } const allBibFiles = getAllBibTexFilesOnPath(session, path, getIgnoreFiles(session, path)); let bibliography; if (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.bibliography) { const bibConfigPath = `${projectConfigFile}#bibliography`; bibliography = projectConfig.bibliography.filter((bib) => { if (allBibFiles.includes(bib)) return true; if (isUrl(bib)) return true; if (fs.existsSync(bib)) { allBibFiles.push(bib); return true; } addWarningForFile(session, projectConfigFile, `Bibliography file ${bib} not found`, 'warn', { ruleId: RuleId.bibFileExists, }); return false; }); allBibFiles.forEach((bib) => { if (bibliography.includes(bib)) return; session.log.debug(`🔍 ${bib} exists, but the file is not referenced in ${bibConfigPath}`); }); } else { bibliography = allBibFiles; } await Promise.all(bibliography.map((p) => loadFile(session, p, path, '.bib'))); const project = { ...newProject, bibliography }; session.store.dispatch(projects.actions.receive(project)); combineProjectCitationRenderers(session, path); return project; } export async function findProjectsOnPath(session, path) { let projectPaths = []; const content = fs.readdirSync(path); if (session.configFiles.filter((file) => content.includes(file)).length) { await loadConfig(session, path); if (selectors.selectLocalProjectConfig(session.store.getState(), path)) { projectPaths.push(path); } } const projs = await Promise.all(content .map((dir) => join(path, dir)) .filter((file) => isDirectory(file)) .map(async (dir) => await findProjectsOnPath(session, dir))); projs.forEach((p) => { projectPaths = projectPaths.concat(p); }); return projectPaths; } export function filterPages(project) { const pages = [ { file: project.file, slug: project.index, level: 1 }, ...project.pages.filter((page) => 'file' in page), ]; return pages; }