Skip to content
This repository has been archived by the owner on Nov 13, 2023. It is now read-only.

Commit

Permalink
#21 - Create a settings loader/manager
Browse files Browse the repository at this point in the history
Signed-off-by: Wright, Christopher R <Christopher.Wright@ca.com>
  • Loading branch information
Wright, Christopher R authored and Wright, Christopher R committed Sep 14, 2018
1 parent 46fa054 commit 7555654
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 2 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"tsdoc": "0.0.4",
"tslint": "5.7.0",
"typedoc": "0.9.0",
"typescript": "2.7.2",
"typescript": "3.0.3",
"uuid": "3.2.1",
"yargs-parser": "9.0.2"
},
Expand Down
35 changes: 34 additions & 1 deletion packages/imperative/src/Imperative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ import { OverridesLoader } from "./OverridesLoader";
import { ImperativeProfileManagerFactory } from "./profiles/ImperativeProfileManagerFactory";
import { ImperativeConfig } from "./ImperativeConfig";
import { EnvironmentalVariableSettings } from "./env/EnvironmentalVariableSettings";
import { AppSettings } from "../../settings";
import {join} from "path";
import {existsSync, mkdirSync, writeFileSync} from "fs";
import * as jsonfile from "jsonfile";

export class Imperative {

Expand Down Expand Up @@ -99,6 +103,9 @@ export class Imperative {
ConfigurationValidator.validate(config);
ImperativeConfig.instance.loadedConfig = config;

// Initialize our settings file
this.initAppSettings();

/* TODO: Create some logger placeholder that just caches messages
* until we can initialize logging. This allows us to call methods that
* use logging just like any method (but before logging is initialized).
Expand Down Expand Up @@ -196,6 +203,9 @@ export class Imperative {
*/
initializationComplete();
} catch (error) {
if (error.report) {
writeFileSync(`${process.cwd()}/imperative_debug.log`, error.report);
}
initializationFailed(
error instanceof ImperativeError ?
error :
Expand Down Expand Up @@ -320,6 +330,30 @@ export class Imperative {
return this.mLog;
}

/**
* Load the correct {@link AppSettings} instance from values located in the
* cli home folder.
*/
private static initAppSettings() {
const cliSettingsRoot = join(ImperativeConfig.instance.cliHome, "settings");
const cliSettingsFile = join(cliSettingsRoot, "imperative.json");

AppSettings.initialize(
cliSettingsFile,
(settingsFile, defaultSettings) => {
if (!existsSync(cliSettingsRoot)) {
mkdirSync(cliSettingsRoot);
}

jsonfile.writeFileSync(settingsFile, defaultSettings, {
spaces: 2
});

return defaultSettings;
}
);
}

/**
* Init log object such that subsequent calls to the Logger.getImperativeLogger() (or
* other similar calls), will contain all necessary categories for logging.
Expand Down Expand Up @@ -493,5 +527,4 @@ export class Imperative {
}
return api;
}

}
12 changes: 12 additions & 0 deletions packages/settings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* This program and the accompanying materials are made available under the terms of the *
* Eclipse Public License v2.0 which accompanies this distribution, and is available at *
* https://www.eclipse.org/legal/epl-v20.html *
* *
* SPDX-License-Identifier: EPL-2.0 *
* *
* Copyright Contributors to the Zowe Project. *
* *
*/

export * from "./src/AppSettings";
114 changes: 114 additions & 0 deletions packages/settings/src/AppSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* This program and the accompanying materials are made available under the terms of the *
* Eclipse Public License v2.0 which accompanies this distribution, and is available at *
* https://www.eclipse.org/legal/epl-v20.html *
* *
* SPDX-License-Identifier: EPL-2.0 *
* *
* Copyright Contributors to the Zowe Project. *
* *
*/

import {readFileSync} from "jsonfile";
import {existsSync} from "fs";
import {ISettingsFile} from "./doc/ISettingsFile";

/**
* A recovery function to handle when a settings file is missing on an initialization function.
*
* @param settingsFile The file that could not be found
* @param defaultSettings The default settings provided by {@link AppSettings}
*
* @returns The settings content to be merged into the defaults.
*/
export type FileRecovery = (settingsFile: string, defaultSettings: ISettingsFile) => ISettingsFile;

/**
* This class represents settings for an Imperative CLI application that can be configured
* by an end user by modifying a settings file. @TODO expand on this doc
*/
export class AppSettings {
/**
*
* @param settingsFile The settings file to load from.
* @param missingFileRecovery A recovery function when the settings file isn't found
*
* @throws {@link SettingsAlreadyInitialized} When the settings singleton has previously been initialized.
*/
public static initialize(settingsFile: string, missingFileRecovery ?: FileRecovery): AppSettings {
if (AppSettings.mInstance) {
// Throw an error imported at runtime so that we minimize file that get included
// on startup.
const { SettingsAlreadyInitialized } = require("./errors/index");
throw new SettingsAlreadyInitialized();
}

AppSettings.mInstance = new AppSettings(settingsFile, missingFileRecovery);
return AppSettings.mInstance;
}


/**
* This is an internal reference to the static settings instance.
*/
private static mInstance: AppSettings;

/**
* Get the singleton instance of the app settings object that was initialized
* within the {@link AppSettings.initialize} function.
*
* @returns A singleton AppSettings object
*
* @throws {@link SettingsNotInitialized} When the settings singleton has not been initialized.
*/
public static get instance(): AppSettings {
if (AppSettings.mInstance == null) {
// Throw an error imported at runtime so that we minimize file that get included
// on startup.
const { SettingsNotInitialized } = require("./errors/index");
throw new SettingsNotInitialized();
}

return AppSettings.mInstance;
}

/**
* Internal reference to the overrides settings. The defaults should
* all be false, indicating that there are no overrides to be done.
*/
public readonly settings: ISettingsFile;

/**
* Constructs a new settings object
*
* @param settingsFile The full path to a settings file to load.
* @param missingFileRecovery A recovery function for when the settings file didn't exist
*/
constructor(settingsFile: string, missingFileRecovery ?: FileRecovery) {
let settings: ISettingsFile;
const defaultSettings: ISettingsFile = {
overrides: {
CredentialManager: false
}
};

try {
// Try to load the file immediately, if it fails we will then
// try to recover
settings = readFileSync(settingsFile);
} catch (up) {
if (missingFileRecovery && !existsSync(settingsFile)) {
settings = missingFileRecovery(settingsFile, defaultSettings);
} else {
// Throw up if there is no recovery function or there was a recovery
// function but the file already existed. (Indicates there was a bigger
// issue at play)
throw up;
}
}
this.settings = {
...defaultSettings,
...settings
};
}
}
41 changes: 41 additions & 0 deletions packages/settings/src/doc/ISettingsFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* This program and the accompanying materials are made available under the terms of the *
* Eclipse Public License v2.0 which accompanies this distribution, and is available at *
* https://www.eclipse.org/legal/epl-v20.html *
* *
* SPDX-License-Identifier: EPL-2.0 *
* *
* Copyright Contributors to the Zowe Project. *
* *
*/

import {IImperativeOverrides} from "../../../imperative/src/doc/IImperativeOverrides";

/**
* This interface defines the structure of the settings file.
*/
export interface ISettingsFile {
/**
* The overrides object determines which items will be used for any overrides in
* the overrides loader. Overrides can come from the base cli or plugins.
*/
overrides: {
/**
* This object can have any key present in the {@link IImperativeOverrides} object
* as a valid setting, allowing us to be dynamic.
*
* Possible Values
* ---------------
* false - Use the default overrides defined by the base cli application. If
* the base application doesn't provide the override for this key,
* Imperative's default will be used.
*
* string - A string value indicates that there is an installed plugin that
* contains an overrides for this value. The string is the name of
* the plugin. If the plugin doesn't provide the override, a warning
* will be logged to the console, the value will be left unchanged
* and we will act as if the key was null.
*/
[K in keyof IImperativeOverrides]-?: false | string; // All keys of IImperativeOverrides now become required
};
}
23 changes: 23 additions & 0 deletions packages/settings/src/errors/SettingsAlreadyInitialized.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* This program and the accompanying materials are made available under the terms of the *
* Eclipse Public License v2.0 which accompanies this distribution, and is available at *
* https://www.eclipse.org/legal/epl-v20.html *
* *
* SPDX-License-Identifier: EPL-2.0 *
* *
* Copyright Contributors to the Zowe Project. *
* *
*/

import {ImperativeError} from "../../../error/";

/**
* This class represents an error that is thrown when a second settings singleton attempts to initialize.
*/
export class SettingsAlreadyInitialized extends ImperativeError {
constructor() {
super({
msg: "AppSettings can only be initialized once per application. Please use AppSettings.instance instead!"
});
}
}
23 changes: 23 additions & 0 deletions packages/settings/src/errors/SettingsNotInitialized.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* This program and the accompanying materials are made available under the terms of the *
* Eclipse Public License v2.0 which accompanies this distribution, and is available at *
* https://www.eclipse.org/legal/epl-v20.html *
* *
* SPDX-License-Identifier: EPL-2.0 *
* *
* Copyright Contributors to the Zowe Project. *
* *
*/

import {ImperativeError} from "../../../error";

/**
* This class represents an error thrown when a null singleton {@link AppSettings} object is referenced.
*/
export class SettingsNotInitialized extends ImperativeError {
constructor() {
super({
msg: "AppSettings have not been initialized yet. Please initialize using AppSettings.initialize(...) first!"
});
}
}
13 changes: 13 additions & 0 deletions packages/settings/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* This program and the accompanying materials are made available under the terms of the *
* Eclipse Public License v2.0 which accompanies this distribution, and is available at *
* https://www.eclipse.org/legal/epl-v20.html *
* *
* SPDX-License-Identifier: EPL-2.0 *
* *
* Copyright Contributors to the Zowe Project. *
* *
*/

export * from "./SettingsAlreadyInitialized";
export * from "./SettingsNotInitialized";

0 comments on commit 7555654

Please sign in to comment.