add initial macOS support #40
This commit is contained in:
@@ -36,9 +36,8 @@ export class ApplicationContext implements IApplicationContext {
|
||||
throw new Error(`os "${OperatingSystem[os]}" is not defined in application`);
|
||||
}
|
||||
const event: IApplicationContextChangedEvent = {
|
||||
newState: this.state,
|
||||
newCollection: this.collection,
|
||||
newOs: os,
|
||||
newState: this.states[os],
|
||||
oldState: this.states[this.currentOs],
|
||||
};
|
||||
this.contextChanged.notify(event);
|
||||
this.currentOs = os;
|
||||
|
||||
@@ -24,9 +24,8 @@ function getInitialOs(app: IApplication, environment: IEnvironment): OperatingSy
|
||||
return currentOs;
|
||||
}
|
||||
supportedOsList.sort((os1, os2) => {
|
||||
const os1SupportLevel = app.collections[os1].totalScripts;
|
||||
const os2SupportLevel = app.collections[os2].totalScripts;
|
||||
return os1SupportLevel - os2SupportLevel;
|
||||
const getPriority = (os: OperatingSystem) => app.getCollection(os).totalScripts;
|
||||
return getPriority(os2) - getPriority(os1);
|
||||
});
|
||||
return supportedOsList[0];
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
||||
import { IApplication } from '@/domain/IApplication';
|
||||
|
||||
export interface IApplicationContext {
|
||||
readonly currentOs: OperatingSystem;
|
||||
readonly app: IApplication;
|
||||
readonly collection: ICategoryCollection;
|
||||
readonly state: ICategoryCollectionState;
|
||||
readonly contextChanged: ISignal<IApplicationContextChangedEvent>;
|
||||
changeContext(os: OperatingSystem): void;
|
||||
@@ -15,6 +12,5 @@ export interface IApplicationContext {
|
||||
|
||||
export interface IApplicationContextChangedEvent {
|
||||
readonly newState: ICategoryCollectionState;
|
||||
readonly newCollection: ICategoryCollection;
|
||||
readonly newOs: OperatingSystem;
|
||||
readonly oldState: ICategoryCollectionState;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import { IUserSelection } from './Selection/IUserSelection';
|
||||
import { ICategoryCollectionState } from './ICategoryCollectionState';
|
||||
import { IApplicationCode } from './Code/IApplicationCode';
|
||||
import { ICategoryCollection } from '../../../domain/ICategoryCollection';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export class CategoryCollectionState implements ICategoryCollectionState {
|
||||
public readonly os: OperatingSystem;
|
||||
public readonly code: IApplicationCode;
|
||||
public readonly selection: IUserSelection;
|
||||
public readonly filter: IUserFilter;
|
||||
@@ -16,5 +18,6 @@ export class CategoryCollectionState implements ICategoryCollectionState {
|
||||
this.selection = new UserSelection(collection, []);
|
||||
this.code = new ApplicationCode(this.selection, collection.scripting);
|
||||
this.filter = new UserFilter(collection);
|
||||
this.os = collection.os;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ICodeBuilder } from './ICodeBuilder';
|
||||
const NewLine = '\n';
|
||||
const TotalFunctionSeparatorChars = 58;
|
||||
|
||||
export class CodeBuilder implements ICodeBuilder {
|
||||
export abstract class CodeBuilder implements ICodeBuilder {
|
||||
private readonly lines = new Array<string>();
|
||||
|
||||
// Returns current line starting from 0 (no lines), or 1 (have single line)
|
||||
@@ -29,7 +29,7 @@ export class CodeBuilder implements ICodeBuilder {
|
||||
}
|
||||
|
||||
public appendCommentLine(commentLine?: string): CodeBuilder {
|
||||
this.lines.push(`:: ${commentLine}`);
|
||||
this.lines.push(`${this.getCommentDelimiter()} ${commentLine}`);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -37,9 +37,8 @@ export class CodeBuilder implements ICodeBuilder {
|
||||
if (!name) { throw new Error('name cannot be empty or null'); }
|
||||
if (!code) { throw new Error('code cannot be empty or null'); }
|
||||
return this
|
||||
.appendLine()
|
||||
.appendCommentLineWithHyphensAround(name)
|
||||
.appendLine(`echo --- ${name}`)
|
||||
.appendLine(this.writeStandardOut(`--- ${name}`))
|
||||
.appendLine(code)
|
||||
.appendTrailingHyphensCommentLine();
|
||||
}
|
||||
@@ -62,4 +61,7 @@ export class CodeBuilder implements ICodeBuilder {
|
||||
public toString(): string {
|
||||
return this.lines.join(NewLine);
|
||||
}
|
||||
|
||||
protected abstract getCommentDelimiter(): string;
|
||||
protected abstract writeStandardOut(text: string): string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ICodeBuilder } from './ICodeBuilder';
|
||||
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
|
||||
import { BatchBuilder } from './Languages/BatchBuilder';
|
||||
import { ShellBuilder } from './Languages/ShellBuilder';
|
||||
|
||||
export class CodeBuilderFactory implements ICodeBuilderFactory {
|
||||
public create(language: ScriptingLanguage): ICodeBuilder {
|
||||
switch (language) {
|
||||
case ScriptingLanguage.shellscript: return new ShellBuilder();
|
||||
case ScriptingLanguage.batchfile: return new BatchBuilder();
|
||||
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ICodeBuilder } from './ICodeBuilder';
|
||||
|
||||
export interface ICodeBuilderFactory {
|
||||
create(language: ScriptingLanguage): ICodeBuilder;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
|
||||
|
||||
export class BatchBuilder extends CodeBuilder {
|
||||
protected getCommentDelimiter(): string {
|
||||
return '::';
|
||||
}
|
||||
protected writeStandardOut(text: string): string {
|
||||
return `echo ${text}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
|
||||
|
||||
export class ShellBuilder extends CodeBuilder {
|
||||
protected getCommentDelimiter(): string {
|
||||
return '#';
|
||||
}
|
||||
protected writeStandardOut(text: string): string {
|
||||
return `echo '${text}'`;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||
import { IUserScriptGenerator } from './IUserScriptGenerator';
|
||||
import { CodeBuilder } from './CodeBuilder';
|
||||
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
|
||||
import { CodePosition } from '../Position/CodePosition';
|
||||
import { IUserScript } from './IUserScript';
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { ICodeBuilder } from './ICodeBuilder';
|
||||
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
|
||||
import { CodeBuilderFactory } from './CodeBuilderFactory';
|
||||
|
||||
export class UserScriptGenerator implements IUserScriptGenerator {
|
||||
constructor(private readonly codeBuilderFactory: () => ICodeBuilder = () => new CodeBuilder()) {
|
||||
constructor(private readonly codeBuilderFactory: ICodeBuilderFactory = new CodeBuilderFactory()) {
|
||||
|
||||
}
|
||||
public buildCode(
|
||||
@@ -20,7 +21,7 @@ export class UserScriptGenerator implements IUserScriptGenerator {
|
||||
if (!selectedScripts.length) {
|
||||
return { code: '', scriptPositions };
|
||||
}
|
||||
let builder = this.codeBuilderFactory();
|
||||
let builder = this.codeBuilderFactory.create(scriptingDefinition.language);
|
||||
builder = initializeCode(scriptingDefinition.startCode, builder);
|
||||
for (const selection of selectedScripts) {
|
||||
scriptPositions = appendSelection(selection, scriptPositions, builder);
|
||||
@@ -52,16 +53,19 @@ function appendSelection(
|
||||
selection: SelectedScript,
|
||||
scriptPositions: Map<SelectedScript, ICodePosition>,
|
||||
builder: ICodeBuilder): Map<SelectedScript, ICodePosition> {
|
||||
const startPosition = builder.currentLine + 1;
|
||||
appendCode(selection, builder);
|
||||
const startPosition = builder.currentLine + 1; // Because first line will be empty to separate scripts
|
||||
builder = appendCode(selection, builder);
|
||||
const endPosition = builder.currentLine - 1;
|
||||
builder.appendLine();
|
||||
scriptPositions.set(selection, new CodePosition(startPosition, endPosition));
|
||||
const position = new CodePosition(startPosition, endPosition);
|
||||
scriptPositions.set(selection, position);
|
||||
return scriptPositions;
|
||||
}
|
||||
|
||||
function appendCode(selection: SelectedScript, builder: ICodeBuilder) {
|
||||
function appendCode(selection: SelectedScript, builder: ICodeBuilder): ICodeBuilder {
|
||||
const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name;
|
||||
const scriptCode = selection.revert ? selection.script.code.revert : selection.script.code.execute;
|
||||
builder.appendFunction(name, scriptCode);
|
||||
return builder
|
||||
.appendLine()
|
||||
.appendFunction(name, scriptCode);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ICodePosition } from './ICodePosition';
|
||||
export class CodePosition implements ICodePosition {
|
||||
|
||||
export class CodePosition implements ICodePosition {
|
||||
public get totalLines(): number {
|
||||
return this.endLine - this.startLine;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
||||
import { IFilterResult } from './IFilterResult';
|
||||
import { ISignal } from '@/infrastructure/Events/Signal';
|
||||
|
||||
export interface IUserFilter {
|
||||
readonly currentFilter: IFilterResult | undefined;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { IUserFilter } from './Filter/IUserFilter';
|
||||
import { IUserSelection } from './Selection/IUserSelection';
|
||||
import { IApplicationCode } from './Code/IApplicationCode';
|
||||
export { IUserSelection, IApplicationCode, IUserFilter };
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export interface ICategoryCollectionState {
|
||||
readonly code: IApplicationCode;
|
||||
readonly filter: IUserFilter;
|
||||
readonly selection: IUserSelection;
|
||||
readonly collection: ICategoryCollection;
|
||||
readonly os: OperatingSystem;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SelectedScript } from './SelectedScript';
|
||||
import { ISignal } from '@/infrastructure/Events/Signal';
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { ICategory } from '@/domain/ICategory';
|
||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
||||
|
||||
export interface IUserSelection {
|
||||
readonly changed: ISignal<ReadonlyArray<SelectedScript>>;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { parseCategoryCollection } from './CategoryCollectionParser';
|
||||
import WindowsData from 'js-yaml-loader!@/application/collections/windows.yaml';
|
||||
import MacOsData from 'js-yaml-loader!@/application/collections/macos.yaml';
|
||||
import { CollectionData } from 'js-yaml-loader!@/*';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { Application } from '@/domain/Application';
|
||||
@@ -10,10 +11,11 @@ import { Application } from '@/domain/Application';
|
||||
export function parseApplication(
|
||||
parser = CategoryCollectionParser,
|
||||
processEnv: NodeJS.ProcessEnv = process.env,
|
||||
collectionData = LoadedCollectionData): IApplication {
|
||||
collectionsData = PreParsedCollections): IApplication {
|
||||
validateCollectionsData(collectionsData);
|
||||
const information = parseProjectInformation(processEnv);
|
||||
const collection = parser(collectionData, information);
|
||||
const app = new Application(information, [ collection ]);
|
||||
const collections = collectionsData.map((collection) => parser(collection, information));
|
||||
const app = new Application(information, collections);
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -23,5 +25,14 @@ export type CategoryCollectionParserType
|
||||
const CategoryCollectionParser: CategoryCollectionParserType
|
||||
= (file, info) => parseCategoryCollection(file, info);
|
||||
|
||||
const LoadedCollectionData: CollectionData
|
||||
= WindowsData;
|
||||
const PreParsedCollections: readonly CollectionData []
|
||||
= [ WindowsData, MacOsData ];
|
||||
|
||||
function validateCollectionsData(collections: readonly CollectionData[]) {
|
||||
if (!collections.length) {
|
||||
throw new Error('no collection provided');
|
||||
}
|
||||
if (collections.some((collection) => !collection)) {
|
||||
throw new Error('undefined collection provided');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { Category } from '@/domain/Category';
|
||||
import { CollectionData } from 'js-yaml-loader!@/*';
|
||||
import { parseCategory } from './CategoryParser';
|
||||
import { ScriptCompiler } from './Compiler/ScriptCompiler';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { parseScriptingDefinition } from './ScriptingDefinitionParser';
|
||||
import { createEnumParser } from '../Common/Enum';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { CategoryCollection } from '@/domain/CategoryCollection';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
|
||||
|
||||
export function parseCategoryCollection(
|
||||
content: CollectionData,
|
||||
info: IProjectInformation,
|
||||
osParser = createEnumParser(OperatingSystem)): ICategoryCollection {
|
||||
validate(content);
|
||||
const compiler = new ScriptCompiler(content.functions);
|
||||
const scripting = parseScriptingDefinition(content.scripting, info);
|
||||
const context = new CategoryCollectionParseContext(content.functions, scripting);
|
||||
const categories = new Array<Category>();
|
||||
for (const action of content.actions) {
|
||||
const category = parseCategory(action, compiler);
|
||||
const category = parseCategory(action, context);
|
||||
categories.push(category);
|
||||
}
|
||||
const os = osParser.parseEnum(content.os, 'os');
|
||||
const scripting = parseScriptingDefinition(content.scripting, info);
|
||||
const collection = new CategoryCollection(
|
||||
os,
|
||||
categories,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { CategoryData, ScriptData, CategoryOrScriptData } from 'js-yaml-loader!@
|
||||
import { Script } from '@/domain/Script';
|
||||
import { Category } from '@/domain/Category';
|
||||
import { parseDocUrls } from './DocumentationParser';
|
||||
import { parseScript } from './ScriptParser';
|
||||
import { IScriptCompiler } from './Compiler/IScriptCompiler';
|
||||
import { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
|
||||
import { parseScript } from './Script/ScriptParser';
|
||||
|
||||
let categoryIdCounter: number = 0;
|
||||
|
||||
@@ -12,17 +12,15 @@ interface ICategoryChildren {
|
||||
subScripts: Script[];
|
||||
}
|
||||
|
||||
export function parseCategory(category: CategoryData, compiler: IScriptCompiler): Category {
|
||||
if (!compiler) {
|
||||
throw new Error('undefined compiler');
|
||||
}
|
||||
export function parseCategory(category: CategoryData, context: ICategoryCollectionParseContext): Category {
|
||||
if (!context) { throw new Error('undefined context'); }
|
||||
ensureValid(category);
|
||||
const children: ICategoryChildren = {
|
||||
subCategories: new Array<Category>(),
|
||||
subScripts: new Array<Script>(),
|
||||
};
|
||||
for (const data of category.children) {
|
||||
parseCategoryChild(data, children, category, compiler);
|
||||
parseCategoryChild(data, children, category, context);
|
||||
}
|
||||
return new Category(
|
||||
/*id*/ categoryIdCounter++,
|
||||
@@ -49,13 +47,13 @@ function parseCategoryChild(
|
||||
data: CategoryOrScriptData,
|
||||
children: ICategoryChildren,
|
||||
parent: CategoryData,
|
||||
compiler: IScriptCompiler) {
|
||||
context: ICategoryCollectionParseContext) {
|
||||
if (isCategory(data)) {
|
||||
const subCategory = parseCategory(data as CategoryData, compiler);
|
||||
const subCategory = parseCategory(data as CategoryData, context);
|
||||
children.subCategories.push(subCategory);
|
||||
} else if (isScript(data)) {
|
||||
const scriptData = data as ScriptData;
|
||||
const script = parseScript(scriptData, compiler);
|
||||
const script = parseScript(scriptData, context);
|
||||
children.subScripts.push(script);
|
||||
} else {
|
||||
throw new Error(`Child element is neither a category or a script.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { FunctionData } from 'js-yaml-loader!*';
|
||||
import { IScriptCompiler } from './Compiler/IScriptCompiler';
|
||||
import { ScriptCompiler } from './Compiler/ScriptCompiler';
|
||||
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
|
||||
import { SyntaxFactory } from './Syntax/SyntaxFactory';
|
||||
import { ISyntaxFactory } from './Syntax/ISyntaxFactory';
|
||||
|
||||
export class CategoryCollectionParseContext implements ICategoryCollectionParseContext {
|
||||
public readonly compiler: IScriptCompiler;
|
||||
public readonly syntax: ILanguageSyntax;
|
||||
|
||||
constructor(
|
||||
functionsData: ReadonlyArray<FunctionData> | undefined,
|
||||
scripting: IScriptingDefinition,
|
||||
syntaxFactory: ISyntaxFactory = new SyntaxFactory()) {
|
||||
if (!scripting) { throw new Error('undefined scripting'); }
|
||||
this.syntax = syntaxFactory.create(scripting.language);
|
||||
this.compiler = new ScriptCompiler(functionsData, this.syntax);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { IScriptCode } from '@/domain/IScriptCode';
|
||||
import { ScriptCode } from '@/domain/ScriptCode';
|
||||
import { ScriptData, FunctionData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
|
||||
import { IScriptCompiler } from './IScriptCompiler';
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
|
||||
interface ICompiledCode {
|
||||
readonly code: string;
|
||||
@@ -10,8 +11,11 @@ interface ICompiledCode {
|
||||
}
|
||||
|
||||
export class ScriptCompiler implements IScriptCompiler {
|
||||
constructor(private readonly functions: readonly FunctionData[]) {
|
||||
constructor(
|
||||
private readonly functions: readonly FunctionData[] | undefined,
|
||||
private syntax: ILanguageSyntax) {
|
||||
ensureValidFunctions(functions);
|
||||
if (!syntax) { throw new Error('undefined syntax'); }
|
||||
}
|
||||
public canCompile(script: ScriptData): boolean {
|
||||
if (!script.call) {
|
||||
@@ -33,7 +37,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
||||
compiledCodes.push(functionCode);
|
||||
});
|
||||
const scriptCode = merge(compiledCodes);
|
||||
return new ScriptCode(script.name, scriptCode.code, scriptCode.revertCode);
|
||||
return new ScriptCode(scriptCode.code, scriptCode.revertCode, script.name, this.syntax);
|
||||
}
|
||||
|
||||
private getFunctionByName(name: string): FunctionData {
|
||||
@@ -43,7 +47,6 @@ export class ScriptCompiler implements IScriptCompiler {
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
private ensureCompilable(call: ScriptFunctionCallData) {
|
||||
if (!this.functions || this.functions.length === 0) {
|
||||
throw new Error('cannot compile without shared functions');
|
||||
@@ -69,7 +72,11 @@ function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
|
||||
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
|
||||
if (functions.some((func) => !func)) {
|
||||
throw new Error(`some functions are undefined`);
|
||||
}
|
||||
}
|
||||
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
|
||||
const functionsWithParameters = functions
|
||||
.filter((func) => func.parameters && func.parameters.length > 0);
|
||||
@@ -95,9 +102,10 @@ function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
|
||||
}
|
||||
|
||||
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
||||
if (!functions) {
|
||||
if (!functions || functions.length === 0) {
|
||||
return;
|
||||
}
|
||||
ensureNoUndefinedItem(functions);
|
||||
ensureNoDuplicatesInFunctionNames(functions);
|
||||
ensureNoDuplicatesInParameterNames(functions);
|
||||
ensureNoDuplicateCode(functions);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { IScriptCompiler } from './Compiler/IScriptCompiler';
|
||||
|
||||
export interface ICategoryCollectionParseContext {
|
||||
readonly compiler: IScriptCompiler;
|
||||
readonly syntax: ILanguageSyntax;
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
import { Script } from '@/domain/Script';
|
||||
import { ScriptData } from 'js-yaml-loader!@/*';
|
||||
import { parseDocUrls } from './DocumentationParser';
|
||||
import { parseDocUrls } from '../DocumentationParser';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { IScriptCompiler } from './Compiler/IScriptCompiler';
|
||||
import { IScriptCode } from '@/domain/IScriptCode';
|
||||
import { ScriptCode } from '@/domain/ScriptCode';
|
||||
import { createEnumParser, IEnumParser } from '../Common/Enum';
|
||||
import { createEnumParser, IEnumParser } from '../../Common/Enum';
|
||||
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
|
||||
|
||||
export function parseScript(
|
||||
data: ScriptData, compiler: IScriptCompiler,
|
||||
data: ScriptData, context: ICategoryCollectionParseContext,
|
||||
levelParser = createEnumParser(RecommendationLevel)): Script {
|
||||
validateScript(data);
|
||||
if (!compiler) {
|
||||
throw new Error('undefined compiler');
|
||||
}
|
||||
if (!context) { throw new Error('undefined context'); }
|
||||
const script = new Script(
|
||||
/* name */ data.name,
|
||||
/* code */ parseCode(data, compiler),
|
||||
/* code */ parseCode(data, context),
|
||||
/* docs */ parseDocUrls(data),
|
||||
/* level */ parseLevel(data.recommend, levelParser));
|
||||
return script;
|
||||
@@ -29,11 +27,11 @@ function parseLevel(level: string, parser: IEnumParser<RecommendationLevel>): Re
|
||||
return parser.parseEnum(level, 'level');
|
||||
}
|
||||
|
||||
function parseCode(script: ScriptData, compiler: IScriptCompiler): IScriptCode {
|
||||
if (compiler.canCompile(script)) {
|
||||
return compiler.compile(script);
|
||||
function parseCode(script: ScriptData, context: ICategoryCollectionParseContext): IScriptCode {
|
||||
if (context.compiler.canCompile(script)) {
|
||||
return context.compiler.compile(script);
|
||||
}
|
||||
return new ScriptCode(script.name, script.code, script.revertCode);
|
||||
return new ScriptCode(script.code, script.revertCode, script.name, context.syntax);
|
||||
}
|
||||
|
||||
function ensureNotBothCallAndCode(script: ScriptData) {
|
||||
6
src/application/Parser/Script/Syntax/BatchFileSyntax.ts
Normal file
6
src/application/Parser/Script/Syntax/BatchFileSyntax.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
|
||||
export class BatchFileSyntax implements ILanguageSyntax {
|
||||
public readonly commentDelimiters = [ 'REM', '::' ];
|
||||
public readonly commonCodeParts = [ '(', ')', 'else' ];
|
||||
}
|
||||
6
src/application/Parser/Script/Syntax/ISyntaxFactory.ts
Normal file
6
src/application/Parser/Script/Syntax/ISyntaxFactory.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
|
||||
export interface ISyntaxFactory {
|
||||
create(language: ScriptingLanguage): ILanguageSyntax;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
|
||||
export class ShellScriptSyntax implements ILanguageSyntax {
|
||||
public readonly commentDelimiters = [ '#' ];
|
||||
public readonly commonCodeParts = [ '(', ')', 'else' ];
|
||||
}
|
||||
15
src/application/Parser/Script/Syntax/SyntaxFactory.ts
Normal file
15
src/application/Parser/Script/Syntax/SyntaxFactory.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ISyntaxFactory } from './ISyntaxFactory';
|
||||
import { BatchFileSyntax } from './BatchFileSyntax';
|
||||
import { ShellScriptSyntax } from './ShellScriptSyntax';
|
||||
|
||||
export class SyntaxFactory implements ISyntaxFactory {
|
||||
public create(language: ScriptingLanguage): ILanguageSyntax {
|
||||
switch (language) {
|
||||
case ScriptingLanguage.batchfile: return new BatchFileSyntax();
|
||||
case ScriptingLanguage.shellscript: return new ShellScriptSyntax();
|
||||
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { createEnumParser } from '../Common/Enum';
|
||||
import { generateIlCode } from './Compiler/ILCode';
|
||||
import { generateIlCode } from './Script/Compiler/ILCode';
|
||||
|
||||
export function parseScriptingDefinition(
|
||||
definition: ScriptingDefinitionData,
|
||||
|
||||
@@ -38,10 +38,10 @@ declare module 'js-yaml-loader!*' {
|
||||
|
||||
export interface ScriptData extends DocumentableData {
|
||||
name: string;
|
||||
code: string | undefined;
|
||||
revertCode: string | undefined;
|
||||
code?: string;
|
||||
revertCode?: string;
|
||||
call: ScriptFunctionCallData;
|
||||
recommend: string | undefined;
|
||||
recommend?: string;
|
||||
}
|
||||
|
||||
export interface ScriptingDefinitionData {
|
||||
|
||||
225
src/application/collections/macos.yaml
Normal file
225
src/application/collections/macos.yaml
Normal file
@@ -0,0 +1,225 @@
|
||||
# Structure documented in "docs/collections.md"
|
||||
os: macos
|
||||
scripting:
|
||||
language: shellscript
|
||||
startCode: |-
|
||||
#!/usr/bin/env bash
|
||||
# {{ $homepage }} — v{{ $version }} — {{ $date }}
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
script_path=$([[ "$0" = /* ]] && echo "$0" || echo "$PWD/${0#./}")
|
||||
sudo "$script_path" || (
|
||||
echo 'Administrator privileges are required.'
|
||||
exit 1
|
||||
)
|
||||
exit 0
|
||||
fi
|
||||
endCode: |-
|
||||
echo 'Your privacy and security is now hardened 🎉💪'
|
||||
echo 'Press any key to exit.'
|
||||
read -n 1 -s
|
||||
actions:
|
||||
-
|
||||
category: Privacy cleanup
|
||||
children:
|
||||
-
|
||||
category: Clear terminal history
|
||||
children:
|
||||
-
|
||||
name: Clear bash history
|
||||
recommend: standard
|
||||
code: rm -f ~/.bash_history
|
||||
-
|
||||
name: Clear zsh history
|
||||
recommend: standard
|
||||
code: rm -f ~/.zsh_history
|
||||
-
|
||||
name: Clear CUPS printer job cache
|
||||
recommend: strict
|
||||
code: |-
|
||||
sudo rm -rfv /var/spool/cups/c0*
|
||||
sudo rm -rfv /var/spool/cups/tmp/*
|
||||
sudo rm -rfv /var/spool/cups/cache/job.cache*
|
||||
-
|
||||
name: Clear the list of iOS devices connected
|
||||
recommend: strict
|
||||
code: |-
|
||||
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
|
||||
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist Devices
|
||||
sudo defaults delete /Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
|
||||
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
|
||||
sudo rm -rfv /var/db/lockdown/*
|
||||
-
|
||||
name: Reset privacy database (remove all permissions)
|
||||
code: sudo tccutil reset All
|
||||
-
|
||||
category: Configure programs
|
||||
children:
|
||||
-
|
||||
name: Disable Firefox telemetry
|
||||
recommend: standard
|
||||
docs: https://github.com/mozilla/policy-templates/blob/master/README.md
|
||||
code: |-
|
||||
# Enable Firefox policies so the telemetry can be configured.
|
||||
sudo defaults write /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled -bool TRUE
|
||||
# Disable sending usage data
|
||||
sudo defaults write /Library/Preferences/org.mozilla.firefox DisableTelemetry -bool TRUE
|
||||
revertCode: |-
|
||||
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
|
||||
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
|
||||
-
|
||||
name: Disable Microsoft Office diagnostics data sending
|
||||
recommend: standard
|
||||
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
|
||||
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
|
||||
-
|
||||
name: Uninstall Google update
|
||||
recommend: strict
|
||||
code: |-
|
||||
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
|
||||
if [ -f "$googleUpdateFile" ]; then
|
||||
$googleUpdateFile --nuke
|
||||
echo Uninstalled google update
|
||||
else
|
||||
echo Google update file does not exist
|
||||
fi
|
||||
-
|
||||
name: Disable Homebrew user behavior analytics
|
||||
recommend: standard
|
||||
docs: https://docs.brew.sh/Analytics
|
||||
call:
|
||||
-
|
||||
function: PersistUserEnvironmentConfiguration
|
||||
parameters:
|
||||
configuration: export HOMEBREW_NO_ANALYTICS=1
|
||||
-
|
||||
name: Disable NET Core CLI telemetry
|
||||
recommend: standard
|
||||
call:
|
||||
-
|
||||
function: PersistUserEnvironmentConfiguration
|
||||
parameters:
|
||||
configuration: export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
-
|
||||
name: Disable PowerShell Core telemetry
|
||||
recommend: standard
|
||||
docs: https://github.com/PowerShell/PowerShell/tree/release/v7.1.1#telemetry
|
||||
call:
|
||||
-
|
||||
function: PersistUserEnvironmentConfiguration
|
||||
parameters:
|
||||
configuration: export POWERSHELL_TELEMETRY_OPTOUT=1
|
||||
-
|
||||
category: Configure OS
|
||||
children:
|
||||
-
|
||||
category: Configure Apple Remote Desktop
|
||||
children:
|
||||
-
|
||||
name: Deactivate the Remote Management Service
|
||||
recommend: strict
|
||||
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
|
||||
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
|
||||
-
|
||||
name: Remove Apple Remote Desktop Settings
|
||||
recommend: strict
|
||||
code: |-
|
||||
sudo rm -rf /var/db/RemoteManagement
|
||||
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
|
||||
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
|
||||
sudo rm -r /Library/Application\ Support/Apple/Remote\ Desktop/
|
||||
rm -r ~/Library/Application\ Support/Remote\ Desktop/
|
||||
rm -r ~/Library/Containers/com.apple.RemoteDesktop
|
||||
-
|
||||
name: Disable Internet based spell correction
|
||||
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
|
||||
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
|
||||
-
|
||||
name: Disable Remote Apple Events
|
||||
recommend: strict
|
||||
code: sudo systemsetup -setremoteappleevents off
|
||||
revertCode: sudo systemsetup -setremoteappleevents on
|
||||
-
|
||||
name: Do not store documents to iCloud Drive by default
|
||||
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
|
||||
recommend: standard
|
||||
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
|
||||
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
|
||||
-
|
||||
category: Security improvements
|
||||
children:
|
||||
-
|
||||
category: Configure macOS Application Firewall
|
||||
children:
|
||||
-
|
||||
name: Enable firewall
|
||||
recommend: standard
|
||||
docs: https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81681
|
||||
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
|
||||
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off
|
||||
-
|
||||
name: Turn on firewall logging
|
||||
recommend: standard
|
||||
docs: https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
|
||||
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode on
|
||||
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
|
||||
-
|
||||
name: Turn on stealth mode
|
||||
recommend: standard
|
||||
docs: https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
|
||||
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on
|
||||
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off
|
||||
-
|
||||
name: Disable Spotlight indexing
|
||||
code: sudo mdutil -i off -d /
|
||||
revertCode: sudo mdutil -i on /
|
||||
-
|
||||
name: Disable Captive portal
|
||||
docs:
|
||||
- https://web.archive.org/web/20171008071031if_/http://blog.erratasec.com/2010/09/apples-secret-wispr-request.html#.WdnPa5OyL6Y
|
||||
- https://web.archive.org/web/20130407200745/http://www.divertednetworks.net/apple-captiveportal.html
|
||||
- https://web.archive.org/web/20170622064304/https://grpugh.wordpress.com/2014/10/29/an-undocumented-change-to-captive-network-assistant-settings-in-os-x-10-10-yosemite/
|
||||
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false
|
||||
revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active
|
||||
-
|
||||
name: Require a password to wake the computer from sleep or screen saver
|
||||
code: defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
|
||||
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
|
||||
-
|
||||
name: Do not show recent items on dock
|
||||
docs: https://developer.apple.com/documentation/devicemanagement/dock
|
||||
code: defaults write com.apple.dock show-recents -bool false
|
||||
revertCode: defaults delete com.apple.dock show-recents
|
||||
-
|
||||
name: Disable AirDrop file sharing
|
||||
recommend: strict
|
||||
code: defaults write com.apple.NetworkBrowser DisableAirDrop -bool true
|
||||
revertCode: defaults write com.apple.NetworkBrowser DisableAirDrop -bool false
|
||||
functions:
|
||||
-
|
||||
name: PersistUserEnvironmentConfiguration
|
||||
parameters: [ configuration ]
|
||||
code: |-
|
||||
command='{{ $configuration }}'
|
||||
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
|
||||
for profile_file in "${profile_files[@]}"
|
||||
do
|
||||
touch "$profile_file"
|
||||
if ! grep -q "$command" "${profile_file}"; then
|
||||
echo "$command" >> "$profile_file"
|
||||
echo "[$profile_file] Configured"
|
||||
else
|
||||
echo "[$profile_file] No need for any action, already configured"
|
||||
fi
|
||||
done
|
||||
revertCode: |-
|
||||
command='{{ $configuration }}'
|
||||
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
|
||||
for profile_file in "${profile_files[@]}"
|
||||
do
|
||||
if grep -q "$command" "${profile_file}" 2>/dev/null; then
|
||||
sed -i '' "/$command/d" "$profile_file"
|
||||
echo "[$profile_file] Reverted configuration"
|
||||
else
|
||||
echo "[$profile_file] No need for any action, configuration does not exist"
|
||||
fi
|
||||
done
|
||||
Reference in New Issue
Block a user