Skip to content

Commit

Permalink
Move SimpleName resolvers to RstGeneratorState
Browse files Browse the repository at this point in the history
Too much duplicate code proxying calls to SimpleNameResolver inside
RstGeneratorState when the state class can perform the resolution itself

Now SimpleNameResolver is just a wrapper around computed data about the
parsed document
  • Loading branch information
Trinovantes committed Jul 4, 2024
1 parent f17b72d commit bcf2f03
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 235 deletions.
174 changes: 107 additions & 67 deletions src/Generator/RstGeneratorState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SimpleName } from '../SimpleName.js'
import { normalizeSimpleName, SimpleName } from '../SimpleName.js'
import { RstNode } from '@/RstNode/RstNode.js'
import { convertUnicode } from '@/utils/convertUnicode.js'
import { RstDirective } from '@/RstNode/ExplicitMarkup/Directive.js'
Expand All @@ -19,6 +19,9 @@ import { SimpleNameResolver } from '@/Parser/Resolver/SimpleNameResolver.js'
import { RstFootnoteDef } from '@/RstNode/ExplicitMarkup/FootnoteDef.js'
import { RstFootnoteRef } from '@/RstNode/Inline/FootnoteRef.js'
import { RstParserOutput } from '@/Parser/RstParserState.js'
import { RstCitationRef } from '@/RstNode/Inline/CitationRef.js'
import { RstCitationDef } from '@/RstNode/ExplicitMarkup/CitationDef.js'
import { getAutoFootnoteSymbol } from '@/utils/getAutoFootnoteSymbol.js'

export type RstGeneratorInput = {
basePath: string // Base url all pages will be deployed to (default /)
Expand Down Expand Up @@ -50,22 +53,7 @@ type OutputBuffer = {

type VisitChildren = () => void

type ErrorableResolvers =
'resolveNodeToUrl' |
'resolveSimpleNameToUrl' |
'resolveFootnoteDefLabel' |
'resolveFootnoteRefLabel' |
'resolveFootnoteRefToDef'

type SimpleNameResolverProxy = {
[K in ErrorableResolvers]: (
...args: Parameters<SimpleNameResolver[K]>[0] extends RstNode
? Parameters<SimpleNameResolver[K]>
: [RstNode, ...Parameters<SimpleNameResolver[K]>]
) => Exclude<ReturnType<SimpleNameResolver[K]>, null>
}

export class RstGeneratorState implements SimpleNameResolverProxy {
export class RstGeneratorState {
private _globalHeaderBuffer = new Map<string, string>() // Content to write to top of output e.g. <script> <link> (indexed by string key so multiple nodes don't output the same content)
private _downloads = new Map<string, string>() // Maps source of download file (relative to basePath) to expected location to be served
private _outputBuffers: Array<OutputBuffer> = [
Expand Down Expand Up @@ -117,32 +105,14 @@ export class RstGeneratorState implements SimpleNameResolverProxy {
this.visitNode(this._currentParserOutput.root)
}

registerGlobalHeader(key: string, text: string) {
this._globalHeaderBuffer.set(key, text)
}

registerDownload(targetPath: string) {
const downloadSrc = targetPath.startsWith('/')
? joinFilePath(this._basePath, targetPath) // If given abs path, assume we want to search from basePath
: resolveFilePath(this._currentDocPath, targetPath) // Otherwise, assume we want to search relative from current doc

const fileHash = sha1(downloadSrc)
const fileName = downloadSrc.split('/').at(-1) ?? downloadSrc
const downloadDest = joinFilePath(this._basePath, '_downloads', fileHash, fileName)

this._downloads.set(downloadSrc, downloadDest)

return {
downloadSrc,
downloadDest,
fileName,
}
}

// ------------------------------------------------------------------------
// MARK: Getters
// ------------------------------------------------------------------------

get root(): RstDocument {
return this._currentParserOutput.root
}

get generatorOutput(): RstGeneratorOutput {
if (this._outputBuffers.length !== 1) {
throw new RstGeneratorError(this, 'Corrupt outputBuffer')
Expand Down Expand Up @@ -171,7 +141,7 @@ export class RstGeneratorState implements SimpleNameResolverProxy {
return str
}

get simpleNameResolver(): Omit<SimpleNameResolver, ErrorableResolvers> {
get simpleNameResolver(): SimpleNameResolver {
return this._currentParserOutput.simpleNameResolver
}

Expand Down Expand Up @@ -224,7 +194,7 @@ export class RstGeneratorState implements SimpleNameResolverProxy {
throw new RstGeneratorError(this, srcNode, `Failed to resolveExternalRef "${targetRef}"`)
}

const targetNode = parserOutput.simpleNameResolver.resolveSimpleNameFromOutside(targetRef)
const targetNode = parserOutput.simpleNameResolver.nodesTargetableFromOutside.get(targetRef)
if (!targetNode) {
throw new RstGeneratorError(this, srcNode, `Failed to resolveExternalRef "${targetRef}"`)
}
Expand All @@ -247,59 +217,103 @@ export class RstGeneratorState implements SimpleNameResolverProxy {
}

resolveNodeToUrl(node: RstNode): string {
const url = this._currentParserOutput.simpleNameResolver.resolveNodeToUrl(node)
const simpleName = this.simpleNameResolver.getSimpleName(node)
const url = this.resolveSimpleNameToUrl(simpleName)
if (!url) {
throw new RstGeneratorError(this, node, 'Failed to resolveNodeToUrl')
}

return url
}

resolveSimpleNameToUrl(srcNode: RstNode, simpleName: SimpleName): string {
const url = this._currentParserOutput.simpleNameResolver.resolveSimpleNameToUrl(simpleName)
if (!url) {
throw new RstGeneratorError(this, srcNode, 'Failed to resolveSimpleNameToUrl')
resolveNodeWithMultipleSimpleNamesToUrl(node: RstNode, simpleNames: Array<SimpleName>): string {
for (const simpleName of simpleNames) {
const url = this.resolveSimpleNameToUrl(simpleName)
if (url) {
return url
}
}

return url
throw new RstGeneratorError(this, node, 'Failed to resolveNodeWithMultipleSimpleNamesToUrl')
}

resolveMultipleSimpleNamesToUrl(srcNode: RstNode, simpleNames: Array<SimpleName>): string {
for (const simpleName of simpleNames) {
const url = this._currentParserOutput.simpleNameResolver.resolveSimpleNameToUrl(simpleName)
if (url) {
return url
resolveSimpleNameToUrl(simpleName: SimpleName): string | null {
const seenSimpleNames = new Set<SimpleName>() // To avoid infinite loops

while (true) {
if (seenSimpleNames.has(simpleName)) {
return null
}

const docTarget = this.simpleNameResolver.simpleNameToTarget.get(simpleName)
if (!docTarget) {
return null
}

const candidateTargetName = normalizeSimpleName(docTarget.target)
const isTargetTangible = (!docTarget.isAlias && docTarget.target.startsWith('#')) || (!docTarget.isAlias && !this.simpleNameResolver.simpleNameToTarget.has(candidateTargetName))
if (isTargetTangible) {
return docTarget.target
}

seenSimpleNames.add(simpleName)
simpleName = candidateTargetName
}
}

resolveCitationDef(citationRef: RstCitationRef): RstCitationDef {
const citationDef = this.simpleNameResolver.citationRefToDef.get(citationRef)
if (!citationDef) {
throw new RstGeneratorError(this, citationRef, 'Failed to resolveCitationDef')
}

throw new RstGeneratorError(this, srcNode, 'Failed to resolveMultipleSimpleNamesToUrl')
return citationDef
}

resolveFootnoteDefLabel(footnoteDef: RstFootnoteDef): string {
const label = this._currentParserOutput.simpleNameResolver.resolveFootnoteDefLabel(footnoteDef)
if (!label) {
throw new RstGeneratorError(this, footnoteDef, 'Failed to resolveFootnoteDefLabel')
resolveCitationDefBacklinks(citationDef: RstCitationDef): Array<SimpleName> {
const refs = this.simpleNameResolver.citationDefBacklinks.get(citationDef) ?? []
return refs.map((citationRef) => this.simpleNameResolver.getSimpleName(citationRef))
}

resolveFootnoteDef(footnoteRef: RstFootnoteRef): RstFootnoteDef {
const footnoteDef = this.simpleNameResolver.footnoteRefToDef.get(footnoteRef)
if (!footnoteDef) {
throw new RstGeneratorError(this, footnoteRef, 'Failed to resolveFootnoteDef')
}

return label
return footnoteDef
}

resolveFootnoteRefLabel(footnoteRef: RstFootnoteRef): string {
const label = this._currentParserOutput.simpleNameResolver.resolveFootnoteRefLabel(footnoteRef)
if (!label) {
throw new RstGeneratorError(this, footnoteRef, 'Failed to resolveFootnoteRefLabel')
resolveFootnoteDefBacklinks(footnoteDef: RstFootnoteDef): Array<SimpleName> {
const footnoteRefs = this.simpleNameResolver.footnoteDefBacklinks.get(footnoteDef) ?? []
return footnoteRefs.map((footnoteRef) => this.simpleNameResolver.getSimpleName(footnoteRef))
}

resolveFootnoteDefLabel(footnoteDef: RstFootnoteDef): string {
if (footnoteDef.isAutoSymbol) {
const symNum = this.simpleNameResolver.footnoteDefToSymNum.get(footnoteDef)
if (!symNum) {
throw new RstGeneratorError(this, footnoteDef, 'Failed to resolveFootnoteDefLabel')
}

return getAutoFootnoteSymbol(symNum)
}

const labelNum = this.simpleNameResolver.footnoteDefToLabelNum.get(footnoteDef)
if (!labelNum) {
throw new RstGeneratorError(this, footnoteDef, 'Failed to resolveFootnoteDefLabel')
}

return label
return labelNum.toString()
}

resolveFootnoteRefToDef(footnoteRef: RstFootnoteRef): RstFootnoteDef {
const footnoteDef = this._currentParserOutput.simpleNameResolver.resolveFootnoteRefToDef(footnoteRef)
resolveFootnoteRefLabel(footnoteRef: RstFootnoteRef): string {
const footnoteDef = this.simpleNameResolver.footnoteRefToDef.get(footnoteRef)
if (!footnoteDef) {
throw new RstGeneratorError(this, footnoteRef, 'Failed to resolveFootnoteRefToDef')
throw new RstGeneratorError(this, footnoteRef, 'Failed to resolveFootnoteRefLabel')
}

return footnoteDef
return this.resolveFootnoteDefLabel(footnoteDef)
}

// ------------------------------------------------------------------------
Expand Down Expand Up @@ -526,6 +540,32 @@ export class RstGeneratorState implements SimpleNameResolverProxy {
return this._disableCommentMarkup
}

// ------------------------------------------------------------------------
// MARK: Alt Writers
// ------------------------------------------------------------------------

registerGlobalHeader(key: string, text: string) {
this._globalHeaderBuffer.set(key, text)
}

registerDownload(targetPath: string) {
const downloadSrc = targetPath.startsWith('/')
? joinFilePath(this._basePath, targetPath) // If given abs path, assume we want to search from basePath
: resolveFilePath(this._currentDocPath, targetPath) // Otherwise, assume we want to search relative from current doc

const fileHash = sha1(downloadSrc)
const fileName = downloadSrc.split('/').at(-1) ?? downloadSrc
const downloadDest = joinFilePath(this._basePath, '_downloads', fileHash, fileName)

this._downloads.set(downloadSrc, downloadDest)

return {
downloadSrc,
downloadDest,
fileName,
}
}

// ------------------------------------------------------------------------
// MARK: Writers
// ------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit bcf2f03

Please sign in to comment.