Skip to content

Commit

Permalink
Merge pull request #77 from Unibeautify/dependency-manager
Browse files Browse the repository at this point in the history
Add support for configuration of dependencies
  • Loading branch information
stevenzeck committed Apr 10, 2018
2 parents d263278 + 7c9812e commit 90eafbd
Show file tree
Hide file tree
Showing 14 changed files with 482 additions and 192 deletions.
35 changes: 32 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,39 @@
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"name": "Debug All Tests",
"runtimeVersion": "9.10.1",
"program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
"args": ["--config", "jest.config.js", "--runInBand"],
"args": [
"--config",
"jest.config.js",
"--runInBand",
"--coverage",
"false"
],
"runtimeArgs": ["--nolazy"],
"console": "internalConsole",
"env": {
"NODE_ENV": "test"
},
"protocol": "inspector",
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/dist/"]
},
{
"type": "node",
"request": "launch",
"name": "Debug Current Tests File",
"runtimeVersion": "9.10.1",
"program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
"args": [
"--config",
"jest.config.js",
"--runInBand",
"--coverage",
"false",
"${fileBasename}"
],
"runtimeArgs": ["--nolazy"],
"console": "internalConsole",
"env": {
Expand All @@ -21,4 +50,4 @@
"outFiles": ["${workspaceRoot}/dist/"]
}
]
}
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 18 additions & 10 deletions src/DependencyManager/Dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ export abstract class Dependency {
private _version?: Version;
private _errors: Error[] = [];

constructor(protected options: DependencyOptions) {}
constructor(
protected definition: DependencyDefinition,
protected options: DependencyOptions
) {}

public load(): Promise<boolean> {
if (this.isInstalled) {
Expand Down Expand Up @@ -44,7 +47,7 @@ export abstract class Dependency {
}

private versionFromText(text: string): string | undefined {
const { parseVersion } = this.options;
const { parseVersion } = this.definition;
if (!parseVersion) {
return text;
}
Expand Down Expand Up @@ -93,11 +96,11 @@ export abstract class Dependency {
}

public get name(): string {
return this.options.name;
return this.definition.name;
}

public get required(): boolean {
return !Boolean(this.options.optional);
return !Boolean(this.definition.optional);
}

public get version(): Version | undefined {
Expand All @@ -109,7 +112,7 @@ export abstract class Dependency {
}
}

export interface BaseDependencyOptions {
export interface BaseDependencyDefinition {
// tslint:disable-next-line:no-reserved-keywords
type: DependencyType;
name: string;
Expand All @@ -132,14 +135,15 @@ export type DependencyVersionParserFunction = (
text: string
) => string | undefined;

export interface NodeDependencyOptions extends BaseDependencyOptions {
export interface NodeDependencyDefinition extends BaseDependencyDefinition {
// tslint:disable-next-line:no-reserved-keywords
type: DependencyType.Node;
// tslint:disable-next-line:no-reserved-keywords
package: string;
}

export interface ExecutableDependencyOptions extends BaseDependencyOptions {
export interface ExecutableDependencyDefinition
extends BaseDependencyDefinition {
// tslint:disable-next-line:no-reserved-keywords
type: DependencyType.Executable;
program: string;
Expand All @@ -149,6 +153,10 @@ export interface ExecutableDependencyOptions extends BaseDependencyOptions {
};
}

export type DependencyOptions =
| NodeDependencyOptions
| ExecutableDependencyOptions;
export type DependencyDefinition =
| NodeDependencyDefinition
| ExecutableDependencyDefinition;

export interface DependencyOptions {
path?: string;
}
22 changes: 15 additions & 7 deletions src/DependencyManager/DependencyFactory.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { Dependency, DependencyType, DependencyOptions } from "./Dependency";
import {
Dependency,
DependencyType,
DependencyDefinition,
DependencyOptions,
} from "./Dependency";
import { NodeDependency } from "./NodeDependency";
import { ExecutableDependency } from "./ExecutableDependency";

export class DependencyFactory {
constructor(private options: DependencyOptions) {}
constructor(
private definition: DependencyDefinition,
private options: DependencyOptions
) {}

public dependency(): Dependency {
const { options } = this;
switch (options.type) {
const { definition, options } = this;
switch (definition.type) {
case DependencyType.Node:
return new NodeDependency(options);
return new NodeDependency(definition, options);
case DependencyType.Executable:
return new ExecutableDependency(options);
return new ExecutableDependency(definition, options);
default:
throw new Error(
`Dependency type not found for: ${JSON.stringify(options)}`
`Dependency type not found for: ${JSON.stringify(definition)}`
);
}
}
Expand Down
100 changes: 73 additions & 27 deletions src/DependencyManager/DependencyManager.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,93 @@
// tslint:disable:no-reserved-keywords
import { DependencyFactory } from "./DependencyFactory";
import { Dependency, DependencyOptions } from "./Dependency";
import {
Dependency,
DependencyDefinition,
DependencyOptions,
} from "./Dependency";

export class DependencyManager {
private readonly dependencies: Dependency[];
private readonly lookup: {
[name: string]: Dependency;
};
private static registry: DependencyRegistry = {};

public static clearRegistry(): void {
this.registry = {};
}

constructor(
private beautifierName: string,
private dependencyDefinitions: DependencyDefinition[] = [],
private options: LanguageDependencyOptions = {}
) {
this.initializeDependencies();
}

public load(): Promise<boolean> {
return Promise.all(
this.dependencyDefinitions
.map(def => this.get(def.name))
.map(dep => dep.load())
).then(() => true);
}

private initializeDependencies(): void {
const lookup = DependencyManager.registry;
const beautifierLookup = lookup[this.beautifierName] || {};
lookup[this.beautifierName] = beautifierLookup;

constructor(dependencies: DependencyOptions[]) {
this.dependencies = dependencies.map(dependency =>
new DependencyFactory(dependency).dependency()
);
this.lookup = this.dependencies.reduce(
(lookup, dep) => ({
...lookup,
[dep.name]: dep,
}),
{}
);
this.dependencyDefinitions.forEach(def => {
const { name: dependencyName } = def;
const options = this.optionsForDependency(dependencyName);
const optionsKey = this.keyForOptions(options);
const depLookup = beautifierLookup[dependencyName] || {};
beautifierLookup[dependencyName] = depLookup;
depLookup[optionsKey] =
depLookup[optionsKey] || this.createDependency(def, options);
});
}

public has(name: string): boolean {
return Boolean(this.get(name));
}

public get<T extends Dependency>(name: string): T {
const dep = this.lookup[name] as T | undefined;
public get<T extends Dependency>(dependencyName: string): T {
const options = this.optionsForDependency(dependencyName);
const optionsKey = this.keyForOptions(options);
const lookup = this.registry[dependencyName] || {};
const dep = lookup[optionsKey] as T | undefined;
if (!dep) {
throw new Error(`Dependency with name ${name} not found.`);
throw new Error(`Dependency with name ${dependencyName} not found.`);
}
return dep;
}

public load(): Promise<boolean> {
return Promise.all(
this.dependencies.map(dep => {
return dep.load();
})
).then(() => true);
private optionsForDependency(dependencyName: string): DependencyOptions {
return this.options[dependencyName];
}

protected get registry(): DependencyRegistry[string] {
return DependencyManager.registry[this.beautifierName];
}

protected createDependency(
definition: DependencyDefinition,
options: DependencyOptions
): Dependency {
return new DependencyFactory(definition, options).dependency();
}

private keyForOptions(options: DependencyOptions = {}): string {
return JSON.stringify(options);
}
}

export interface DependencyMap {
[name: string]: Dependency;
export interface LanguageDependencyOptions {
[dependencyName: string]: DependencyOptions;
}

export interface DependencyRegistry {
[beautifierName: string]: {
[dependencyName: string]: {
[optionsKey: string]: Dependency;
};
};
}
27 changes: 21 additions & 6 deletions src/DependencyManager/ExecutableDependency.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { spawn, SpawnOptions } from "child_process";

import { Dependency, ExecutableDependencyOptions } from "./Dependency";
import {
Dependency,
ExecutableDependencyDefinition,
DependencyOptions,
} from "./Dependency";

export class ExecutableDependency extends Dependency {
constructor(protected options: ExecutableDependencyOptions) {
super(options);
constructor(
protected definition: ExecutableDependencyDefinition,
options: DependencyOptions = {}
) {
super(definition, options);
}

protected loadVersion() {
return this.run({ args: this.versionArgs }).then(({ stdout }) => stdout);
}

private get versionArgs(): string[] {
return this.options.versionArgs || ["--version"];
return this.definition.versionArgs || ["--version"];
}

public run({
Expand All @@ -25,7 +32,7 @@ export class ExecutableDependency extends Dependency {
stdin?: any;
}): Promise<RunResponse> {
return this.resolveArgs(args).then(finalArgs =>
this.spawn({ exe: this.program, args: finalArgs, options, stdin })
this.spawn({ exe: this.pathOrProgram, args: finalArgs, options, stdin })
);
}

Expand All @@ -35,8 +42,16 @@ export class ExecutableDependency extends Dependency {
);
}

private get pathOrProgram(): string {
return this.programPath || this.program;
}

private get program(): string {
return this.options.program;
return this.definition.program;
}

private get programPath(): string | undefined {
return this.options.path;
}

private spawn({
Expand Down
15 changes: 11 additions & 4 deletions src/DependencyManager/NodeDependency.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Dependency, NodeDependencyOptions } from "./Dependency";
import {
Dependency,
NodeDependencyDefinition,
DependencyOptions,
} from "./Dependency";

// tslint:disable-next-line:no-require-imports no-var-requires
const requireg = require("requireg");

export class NodeDependency extends Dependency {
constructor(protected options: NodeDependencyOptions) {
super(options);
constructor(
protected definition: NodeDependencyDefinition,
options: DependencyOptions = {}
) {
super(definition, options);
}

protected loadVersion() {
Expand Down Expand Up @@ -65,6 +72,6 @@ export class NodeDependency extends Dependency {
}

private get packageName(): string {
return this.options.package;
return this.definition.package;
}
}
Loading

0 comments on commit 90eafbd

Please sign in to comment.