diff --git a/lib/index.js b/lib/index.js index 4ab88e1..2901867 100644 --- a/lib/index.js +++ b/lib/index.js @@ -25,51 +25,72 @@ class Documentation { 'Test' ] - static async load (file) { - const filePath = fileURLToPath(new URL(`../docs/${file}.md`, import.meta.url)) - const fileContent = (await readFile(filePath)).toString() - const page = new Page(file, fileContent) - await page.load() - return page - } - version + // load documentation content async load () { - const [pages, version] = await Promise.all([ - Promise.all(Documentation.pages.map(page => - this.constructor.load(page) - )), - this.#getVersion() + const [version, ...pages] = await Promise.all([ + this.#getVersion(), + ...Documentation.pages.map((file, index) => + this.#getPage(file, index) + ) ]) - this.pages = pages this.version = version + this.pages = pages + await Promise.all(this.pages.map(page => page.load())) return this } + // get version number of documentation from package.json async #getVersion () { - const filePath = fileURLToPath(new URL('../package.json', import.meta.url)) - const fileContent = (await readFile(filePath)).toString() + const fileContent = await this.#getFileContent('../package.json') return JSON.parse(fileContent).version } + + // create page instances + async #getPage (file, index) { + const fileContent = await this.#getFileContent(`../docs/${file}.md`) + const entries = marked.lexer(fileContent) + return new Page(this, file, index, entries) + } + + // retrieve raw text content from files + async #getFileContent (path) { + const filePath = fileURLToPath(new URL(path, import.meta.url)) + return (await readFile(filePath)).toString() + } } class Page { - #file + key title body sections = [] - #raw + #documentation + #file + #index #entries - constructor (file, raw) { + constructor (documentation, file, index, [header, ...entries]) { + this.#documentation = documentation this.#file = file - this.#raw = raw + this.#index = index + this.#entries = entries + this.title = header.text } async load () { - this.#entries = marked.lexer(this.#raw) + // assigns unique key value for page + if (this.rawKey === 'page') { + this.key = `page_${this.#index + 1}` + } else { + const prior = this.documentation.pages.slice(0, this.#index) + const existing = prior.filter(({ rawKey }) => rawKey === this.rawKey) + const ending = existing.length ? `_${existing.length + 1}` : '' + this.key = `${this.rawKey}${ending}` + } + // process page content const bodyBuffer = [] const sectionBuffer = [] @@ -79,15 +100,10 @@ class Page { } } - this.#entries.forEach(entry => { - if ((entry.type === 'heading') && (entry.depth <= 2)) { - if (entry.depth === 1) { - if (!this.title) this.title = entry.text - } - if (entry.depth === 2) { - processSection() - sectionBuffer.push(entry) - } + for (const entry of this.#entries) { + if ((entry.type === 'heading') && (entry.depth === 2)) { + processSection() + sectionBuffer.push(entry) } else { if (sectionBuffer.length) { sectionBuffer.push(entry) @@ -95,16 +111,21 @@ class Page { bodyBuffer.push(entry) } } - }) + } processSection() + + // concats raw body content for page this.body = bodyBuffer.map(({ raw }) => raw).join('') + + // load all sections existing under this page await Promise.all(this.sections.map(section => section.load())) return this } - get key () { + // generates the raw un-deduped key value for page + get rawKey () { return ( - this.#file + (this.title || this.#file) .replace(/[^a-zA-Z0-9]+/g, ' ') .trim() .replace(/\s+/g, '-') @@ -113,11 +134,8 @@ class Page { ) } - toJSON () { - return { - key: this.key, - ...this - } + get documentation () { + return this.#documentation } } @@ -125,7 +143,7 @@ class Section { key title body - subSections = [] + subsections = [] #entries #parent @@ -134,85 +152,80 @@ class Section { this.#entries = entries this.title = header.text + // assigns unique key value for section const prior = [ - this.page.key, + ...this.documentation.pages, ...this.page.sections - .map(section => [section, ...section.subSections]) + .map(section => [section, ...section.subsections]) .flat() - .map(section => section.rawKey) ] + const existing = prior.filter(({ rawKey }) => rawKey === this.rawKey) + const ending = (existing.length || ['section', 'subsection'].includes(this.rawKey)) + ? `_${existing.length + 1}` + : '' - const existing = prior - .filter(key => key === this.rawKey) - - this.key = `${ - this.rawKey - }${ - (existing.length || ['page', 'section', 'subsection'].includes(this.rawKey)) - ? (`_${existing.length + 1}`) - : '' - }` + this.key = `${this.rawKey}${ending}` } async load () { + // procee section content const bodyBuffer = [] - const subSectionBuffer = [] + const subsectionBuffer = [] const processSubSection = () => { - if (subSectionBuffer.length) { - this.subSections.push(new this.constructor(this, subSectionBuffer.splice(0, subSectionBuffer.length))) + if (subsectionBuffer.length) { + this.subsections.push(new this.constructor(this, subsectionBuffer.splice(0, subsectionBuffer.length))) } } - this.#entries.forEach(entry => { + for (const entry of this.#entries) { if ((entry.type === 'heading') && (entry.depth === 3)) { processSubSection() - subSectionBuffer.push(entry) + subsectionBuffer.push(entry) } else { - if (subSectionBuffer.length) { - subSectionBuffer.push(entry) + if (subsectionBuffer.length) { + subsectionBuffer.push(entry) } else { bodyBuffer.push(entry) } } - }) + } processSubSection() + + // concats raw body content for section this.body = bodyBuffer.map(({ raw }) => raw).join('') - await Promise.all(this.subSections.map(section => section.load())) + + // load all subsections existing under this section + await Promise.all(this.subsections.map(section => section.load())) return this } + // generates the raw un-deduped key value for section get rawKey () { - return ( - // The resulting raw keyvalue might be shared with another - // section/subsection within this page. The value will be - // iterated to de-duplicate within the Section's constructor - this.title - .replace(/[^a-zA-Z0-9]+/g, ' ') - .trim() - .replace(/\s+/g, '-') - .toLowerCase() || - ( - // if there is a parent section that is using a non-default raw key value - (this.section?.rawKey && this.section.rawKey !== 'section') - // use the parent raw key value - ? this.section.rawKey - // otherwise check if the parent page has a non-default raw key value - : (this.page?.key && this.page.key !== 'page') - // use the parent raw key value - ? this.page.key - // fallback to default value for section raw key value - : this.section - ? 'subsection' - : 'section' - ) - ) + const baseKey = this.title + .replace(/[^a-zA-Z0-9]+/g, ' ') + .trim() + .replace(/\s+/g, '-') + .toLowerCase() + if (baseKey) return baseKey + + const sectionKey = this.section?.rawKey + if (sectionKey && sectionKey !== 'section') return sectionKey + + const pageKey = this.page?.rawKey + if (pageKey && pageKey !== 'page') return pageKey + + return this.section ? 'subsection' : 'section' } get parent () { return this.#parent } + get documentation () { + return this.page.documentation + } + get page () { return this.parent.parent || this.parent }