Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for configuration of dependencies #77

Merged
merged 11 commits into from
Apr 10, 2018
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