nihilqui
Version:
Typescript .d.ts generator from GIR for gjs and node-gtk
260 lines (223 loc) • 8.49 kB
text/typescript
import { findFileInDirs, splitModuleName, pascalCase } from './utils.js'
import { Logger } from './logger.js'
import { Transformation } from './transformation.js'
import type { Dependency, GenerateConfig, GirInclude } from './types/index.js'
import type { GirModule } from './gir-module.js'
export class DependencyManager {
protected log: Logger
protected transformation: Transformation
cache: { [packageName: string]: Dependency } = {}
static instances: { [env: string]: DependencyManager } = {}
protected constructor(protected readonly config: GenerateConfig) {
this.transformation = new Transformation(config)
this.log = new Logger(config.environment, config.verbose, 'DependencyManager')
}
/**
* Get the DependencyManager singleton instance
*/
static getInstance(config: GenerateConfig): DependencyManager {
if (this.instances[config.environment]) {
return this.instances[config.environment]
}
this.instances[config.environment] = new DependencyManager(config)
return this.instances[config.environment]
}
protected parseArgs(namespaceOrPackageName: string, version?: string) {
let packageName: string
let namespace: string
if (version) {
namespace = namespaceOrPackageName
packageName = `${namespace}-${version}`
} else {
packageName = namespaceOrPackageName
const { namespace: _namespace, version: _version } = splitModuleName(packageName)
namespace = _namespace
version = _version
}
return { packageName, namespace, version }
}
/**
* Get all dependencies in the cache
* @returns All dependencies in the cache
*/
all(): Dependency[] {
return Object.values(this.cache)
}
/**
* Get the core dependencies
* @returns
*/
core(): Dependency[] {
return [this.get('GObject-2.0'), this.get('GLib-2.0')]
}
createImportProperties(namespace: string, packageName: string) {
const importPath = this.createImportPath(packageName)
const importDef = this.createImportDef(namespace, importPath)
return {
importPath,
importDef,
}
}
createImportPath(packageName: string): string {
const importName = this.transformation.transformImportName(packageName)
const importPath = this.config.package ? `${this.config.npmScope}/${importName}` : `./${importName}.js`
return importPath
}
createImportDef(namespace: string, importPath: string): string {
return this.config.noNamespace
? `import type * as ${namespace} from '${importPath}'`
: `import type ${namespace} from '${importPath}';`
}
/**
* Get the dependency object by packageName
* @param packageName The package name (with version affix) of the dependency
* @returns The dependency object
*/
get(packageName: string): Dependency
/**
* Get the dependency object by namespace and version
* @param namespace The namespace of the dependency
* @param version The version of the dependency
* @returns The dependency object
*/
get(namespace: string, version: string): Dependency
get(namespaceOrPackageName: string, version?: string): Dependency {
// Special case for Gjs
if (namespaceOrPackageName === 'Gjs') {
return this.getGjs()
} else if (namespaceOrPackageName === 'node-gtk') {
return this.getNodeGtk()
}
const args = this.parseArgs(namespaceOrPackageName, version)
version = args.version
const packageName = args.packageName
const namespace = args.namespace
if (this.cache[packageName]) {
const dep = this.cache[packageName]
return dep
}
const filename = `${packageName}.gir`
const { exists, path } = findFileInDirs(this.config.girDirectories, filename)
const dependency: Dependency = {
namespace,
exists,
filename,
path,
packageName,
importName: this.transformation.transformImportName(packageName),
version,
...this.createImportProperties(namespace, packageName),
}
this.cache[packageName] = dependency
return dependency
}
/**
* Get girModule for dependency
* @param girModules
* @param packageName
*/
getModule(girModules: GirModule[], dep: Dependency): GirModule | undefined {
return girModules.find(
(m) => m.packageName === dep.packageName && m.namespace === dep.namespace && m.version === dep.version,
)
}
/**
* Add all dependencies from an array of gir modules
* @param girModules
*/
addAll(girModules: GirModule[]): Dependency[] {
for (const girModule of girModules) {
this.get(girModule.namespace, girModule.version || '0.0')
}
return this.all()
}
/**
* Transforms a gir include object array to a dependency object array
* @param girIncludes - Array of gir includes
* @returns Array of dependencies
*/
fromGirIncludes(girIncludes: GirInclude[]): Dependency[] {
const dependencies: Dependency[] = []
for (const i of girIncludes) {
dependencies.unshift(this.get(i.$.name, i.$.version || '0.0'))
}
return dependencies
}
getAllPackageNames(): string[] {
return Object.keys(this.cache)
}
/**
* Check if multiple dependencies with the given namespace exist in the cache
* @param namespace The namespace of the dependency
* @returns
*/
hasConflict(namespace: string): boolean {
const packageNames = this.getAllPackageNames()
const candidates = packageNames.filter((packageName) => {
return packageName.startsWith(`${namespace}-`) && this.cache[packageName].namespace === namespace
})
return candidates.length > 1
}
/**
* Check if the given version is the latest version of the dependency
* @param namespace The namespace of the dependency
* @param version The version of the dependency
* @returns
*/
isLatestVersion(namespace: string, version: string): boolean {
const hasConflict = this.hasConflict(namespace)
if (!hasConflict) {
return true
}
const latestVersion = this.find(namespace)
return latestVersion?.version === version
}
/**
* Find a dependency by it's namespace from the cache, if multiple versions are found, the latest version is returned
* @param namespace The namespace of the dependency
* @returns The dependency object or null if not found
*/
find(namespace: string): Dependency | null {
// Special case for Gjs
if (namespace === 'Gjs') {
return this.getGjs()
} else if (namespace === 'node-gtk') {
return this.getNodeGtk()
}
const packageNames = this.getAllPackageNames()
const candidates = packageNames.filter((packageName) => {
return packageName.startsWith(`${namespace}-`) && this.cache[packageName].namespace === namespace
})
if (candidates.length > 1) {
this.log.warn(`Found multiple versions of ${namespace}: ${candidates.join(', ')}`)
}
const latestVersion = candidates.sort().pop()
if (latestVersion && this.cache[latestVersion]) {
const dep = this.cache[latestVersion]
return dep
}
return null
}
protected getPseudoPackage(packageName: string): Dependency {
if (this.cache[packageName]) {
return this.cache[packageName]
}
const dep: Dependency = {
namespace: pascalCase(packageName),
exists: true,
filename: '',
path: '',
packageName: packageName,
importName: this.transformation.transformImportName(packageName),
version: '0.0',
...this.createImportProperties(packageName, packageName),
}
return dep
}
getGjs(): Dependency {
return this.getPseudoPackage('Gjs')
}
getNodeGtk(): Dependency {
return this.getPseudoPackage('node-gtk')
}
}