Refactor to unify scripts/categories as Executable

This commit consolidates scripts and categories under a unified
'Executable' concept. This simplifies the architecture and improves code
readability.

- Introduce subfolders within `src/domain` to segregate domain elements.
- Update class and interface names by removing the 'I' prefix in
  alignment with new coding standards.
- Replace 'Node' with 'Executable' to clarify usage; reserve 'Node'
  exclusively for the UI's tree component.
This commit is contained in:
undergroundwires
2024-06-12 12:36:40 +02:00
parent 8becc7dbc4
commit c138f74460
230 changed files with 1120 additions and 1039 deletions

View File

@@ -1,4 +1,4 @@
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
import type { ICodeChangedEvent } from './ICodeChangedEvent';
@@ -6,13 +6,13 @@ import type { ICodeChangedEvent } from './ICodeChangedEvent';
export class CodeChangedEvent implements ICodeChangedEvent {
public readonly code: string;
public readonly addedScripts: ReadonlyArray<IScript>;
public readonly addedScripts: ReadonlyArray<Script>;
public readonly removedScripts: ReadonlyArray<IScript>;
public readonly removedScripts: ReadonlyArray<Script>;
public readonly changedScripts: ReadonlyArray<IScript>;
public readonly changedScripts: ReadonlyArray<Script>;
private readonly scripts: Map<IScript, ICodePosition>;
private readonly scripts: Map<Script, ICodePosition>;
constructor(
code: string,
@@ -25,7 +25,7 @@ export class CodeChangedEvent implements ICodeChangedEvent {
this.addedScripts = selectIfNotExists(newScripts, oldScripts);
this.removedScripts = selectIfNotExists(oldScripts, newScripts);
this.changedScripts = getChangedScripts(oldScripts, newScripts);
this.scripts = new Map<IScript, ICodePosition>();
this.scripts = new Map<Script, ICodePosition>();
scripts.forEach((position, selection) => {
this.scripts.set(selection.script, position);
});
@@ -35,7 +35,7 @@ export class CodeChangedEvent implements ICodeChangedEvent {
return this.scripts.size === 0;
}
public getScriptPositionInCode(script: IScript): ICodePosition {
public getScriptPositionInCode(script: Script): ICodePosition {
return this.getPositionById(script.id);
}
@@ -65,7 +65,7 @@ function ensureAllPositionsExist(script: string, positions: ReadonlyArray<ICodeP
function getChangedScripts(
oldScripts: ReadonlyArray<SelectedScript>,
newScripts: ReadonlyArray<SelectedScript>,
): ReadonlyArray<IScript> {
): ReadonlyArray<Script> {
return newScripts
.filter((newScript) => oldScripts.find((oldScript) => oldScript.id === newScript.id
&& oldScript.revert !== newScript.revert))

View File

@@ -1,11 +1,11 @@
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
export interface ICodeChangedEvent {
readonly code: string;
readonly addedScripts: ReadonlyArray<IScript>;
readonly removedScripts: ReadonlyArray<IScript>;
readonly changedScripts: ReadonlyArray<IScript>;
readonly addedScripts: ReadonlyArray<Script>;
readonly removedScripts: ReadonlyArray<Script>;
readonly changedScripts: ReadonlyArray<Script>;
isEmpty(): boolean;
getScriptPositionInCode(script: IScript): ICodePosition;
getScriptPositionInCode(script: Script): ICodePosition;
}

View File

@@ -1,11 +1,11 @@
import type { IScript } from '@/domain/IScript';
import type { ICategory } from '@/domain/ICategory';
import type { Script } from '@/domain/Executables/Script/Script';
import type { Category } from '@/domain/Executables/Category/Category';
import type { FilterResult } from './FilterResult';
export class AppliedFilterResult implements FilterResult {
constructor(
public readonly scriptMatches: ReadonlyArray<IScript>,
public readonly categoryMatches: ReadonlyArray<ICategory>,
public readonly scriptMatches: ReadonlyArray<Script>,
public readonly categoryMatches: ReadonlyArray<Category>,
public readonly query: string,
) {
if (!query) { throw new Error('Query is empty or undefined'); }

View File

@@ -1,8 +1,9 @@
import type { IScript, ICategory } from '@/domain/ICategory';
import type { Category } from '@/domain/Executables/Category/Category';
import type { Script } from '@/domain/Executables/Script/Script';
export interface FilterResult {
readonly categoryMatches: ReadonlyArray<ICategory>;
readonly scriptMatches: ReadonlyArray<IScript>;
readonly categoryMatches: ReadonlyArray<Category>;
readonly scriptMatches: ReadonlyArray<Script>;
readonly query: string;
hasAnyMatches(): boolean;
}

View File

@@ -1,7 +1,8 @@
import type { ICategory, IScript } from '@/domain/ICategory';
import type { IScriptCode } from '@/domain/IScriptCode';
import type { IDocumentable } from '@/domain/IDocumentable';
import type { Category } from '@/domain/Executables/Category/Category';
import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
import type { Documentable } from '@/domain/Executables/Documentable';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { Script } from '@/domain/Executables/Script/Script';
import { AppliedFilterResult } from '../Result/AppliedFilterResult';
import type { FilterStrategy } from './FilterStrategy';
import type { FilterResult } from '../Result/FilterResult';
@@ -24,7 +25,7 @@ export class LinearFilterStrategy implements FilterStrategy {
}
function matchesCategory(
category: ICategory,
category: Category,
filterLowercase: string,
): boolean {
return matchesAny(
@@ -34,7 +35,7 @@ function matchesCategory(
}
function matchesScript(
script: IScript,
script: Script,
filterLowercase: string,
): boolean {
return matchesAny(
@@ -58,7 +59,7 @@ function matchName(
}
function matchCode(
code: IScriptCode,
code: ScriptCode,
filterLowercase: string,
): boolean {
if (code.execute.toLowerCase().includes(filterLowercase)) {
@@ -71,7 +72,7 @@ function matchCode(
}
function matchDocumentation(
documentable: IDocumentable,
documentable: Documentable,
filterLowercase: string,
): boolean {
return documentable.docs.some(

View File

@@ -1,9 +1,9 @@
import type { ICategory } from '@/domain/ICategory';
import type { Category } from '@/domain/Executables/Category/Category';
import type { CategorySelectionChangeCommand } from './CategorySelectionChange';
export interface ReadonlyCategorySelection {
areAllScriptsSelected(category: ICategory): boolean;
isAnyScriptSelected(category: ICategory): boolean;
areAllScriptsSelected(category: Category): boolean;
isAnyScriptSelected(category: Category): boolean;
}
export interface CategorySelection extends ReadonlyCategorySelection {

View File

@@ -1,4 +1,4 @@
import type { ICategory } from '@/domain/ICategory';
import type { Category } from '@/domain/Executables/Category/Category';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { CategorySelectionChange, CategorySelectionChangeCommand } from './CategorySelectionChange';
import type { CategorySelection } from './CategorySelection';
@@ -13,7 +13,7 @@ export class ScriptToCategorySelectionMapper implements CategorySelection {
}
public areAllScriptsSelected(category: ICategory): boolean {
public areAllScriptsSelected(category: Category): boolean {
const { selectedScripts } = this.scriptSelection;
if (selectedScripts.length === 0) {
return false;
@@ -27,7 +27,7 @@ export class ScriptToCategorySelectionMapper implements CategorySelection {
);
}
public isAnyScriptSelected(category: ICategory): boolean {
public isAnyScriptSelected(category: Category): boolean {
const { selectedScripts } = this.scriptSelection;
if (selectedScripts.length === 0) {
return false;

View File

@@ -1,5 +1,5 @@
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import { EventSource } from '@/infrastructure/Events/EventSource';
import type { ReadonlyRepository, Repository } from '@/application/Repository/Repository';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
@@ -80,7 +80,7 @@ export class DebouncedScriptSelection implements ScriptSelection {
});
}
public selectOnly(scripts: readonly IScript[]): void {
public selectOnly(scripts: readonly Script[]): void {
assertNonEmptyScriptSelection(scripts);
this.processChanges({
changes: [
@@ -145,7 +145,7 @@ export class DebouncedScriptSelection implements ScriptSelection {
}
}
function assertNonEmptyScriptSelection(selectedItems: readonly IScript[]) {
function assertNonEmptyScriptSelection(selectedItems: readonly Script[]) {
if (selectedItems.length === 0) {
throw new Error('Provided script array is empty. To deselect all scripts, please use the deselectAll() method instead.');
}
@@ -153,7 +153,7 @@ function assertNonEmptyScriptSelection(selectedItems: readonly IScript[]) {
function getScriptIdsToBeSelected(
existingItems: ReadonlyRepository<string, SelectedScript>,
desiredScripts: readonly IScript[],
desiredScripts: readonly Script[],
): string[] {
return desiredScripts
.filter((script) => !existingItems.exists(script.id))
@@ -162,7 +162,7 @@ function getScriptIdsToBeSelected(
function getScriptIdsToBeDeselected(
existingItems: ReadonlyRepository<string, SelectedScript>,
desiredScripts: readonly IScript[],
desiredScripts: readonly Script[],
): string[] {
return existingItems
.getItems()

View File

@@ -1,5 +1,5 @@
import type { IEventSource } from '@/infrastructure/Events/IEventSource';
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { SelectedScript } from './SelectedScript';
import type { ScriptSelectionChangeCommand } from './ScriptSelectionChange';
@@ -10,7 +10,7 @@ export interface ReadonlyScriptSelection {
}
export interface ScriptSelection extends ReadonlyScriptSelection {
selectOnly(scripts: readonly IScript[]): void;
selectOnly(scripts: readonly Script[]): void;
selectAll(): void;
deselectAll(): void;
processChanges(action: ScriptSelectionChangeCommand): void;

View File

@@ -1,9 +1,9 @@
import type { IEntity } from '@/infrastructure/Entity/IEntity';
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
type ScriptId = IScript['id'];
type ScriptId = Script['id'];
export interface SelectedScript extends IEntity<ScriptId> {
readonly script: IScript;
readonly script: Script;
readonly revert: boolean;
}

View File

@@ -1,12 +1,12 @@
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import type { IScript } from '@/domain/IScript';
import type { Script } from '@/domain/Executables/Script/Script';
import type { SelectedScript } from './SelectedScript';
type SelectedScriptId = SelectedScript['id'];
export class UserSelectedScript extends BaseEntity<SelectedScriptId> {
constructor(
public readonly script: IScript,
public readonly script: Script,
public readonly revert: boolean,
) {
super(script.id);

View File

@@ -4,20 +4,21 @@ import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { createEnumParser } from '../Common/Enum';
import { parseCategory } from './CategoryParser';
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
import { parseCategory } from './Executable/CategoryParser';
import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
import { createCollectionUtilities, type CategoryCollectionSpecificUtilitiesFactory } from './Executable/CategoryCollectionSpecificUtilities';
export function parseCategoryCollection(
content: CollectionData,
projectDetails: ProjectDetails,
osParser = createEnumParser(OperatingSystem),
createUtilities: CategoryCollectionSpecificUtilitiesFactory = createCollectionUtilities,
): ICategoryCollection {
validate(content);
const scripting = new ScriptingDefinitionParser()
.parse(content.scripting, projectDetails);
const context = new CategoryCollectionParseContext(content.functions, scripting);
const categories = content.actions.map((action) => parseCategory(action, context));
const utilities = createUtilities(content.functions, scripting);
const categories = content.actions.map((action) => parseCategory(action, utilities));
const os = osParser.parseEnum(content.os, 'os');
const collection = new CategoryCollection(
os,

View File

@@ -0,0 +1,32 @@
import type { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import type { FunctionData } from '@/application/collections/';
import { ScriptCompiler } from './Script/Compiler/ScriptCompiler';
import { SyntaxFactory } from './Script/Validation/Syntax/SyntaxFactory';
import type { IScriptCompiler } from './Script/Compiler/IScriptCompiler';
import type { ILanguageSyntax } from './Script/Validation/Syntax/ILanguageSyntax';
import type { ISyntaxFactory } from './Script/Validation/Syntax/ISyntaxFactory';
export interface CategoryCollectionSpecificUtilities {
readonly compiler: IScriptCompiler;
readonly syntax: ILanguageSyntax;
}
export const createCollectionUtilities: CategoryCollectionSpecificUtilitiesFactory = (
functionsData: ReadonlyArray<FunctionData> | undefined,
scripting: IScriptingDefinition,
syntaxFactory: ISyntaxFactory = new SyntaxFactory(),
) => {
const syntax = syntaxFactory.create(scripting.language);
return {
compiler: new ScriptCompiler(functionsData ?? [], syntax),
syntax,
};
};
export interface CategoryCollectionSpecificUtilitiesFactory {
(
functionsData: ReadonlyArray<FunctionData> | undefined,
scripting: IScriptingDefinition,
syntaxFactory?: ISyntaxFactory,
): CategoryCollectionSpecificUtilities;
}

View File

@@ -1,35 +1,35 @@
import type {
CategoryData, ScriptData, CategoryOrScriptData,
CategoryData, ScriptData, ExecutableData,
} from '@/application/collections/';
import { Script } from '@/domain/Script';
import { Category } from '@/domain/Category';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
import type { ICategory } from '@/domain/ICategory';
import type { Category } from '@/domain/Executables/Category/Category';
import { CollectionCategory } from '@/domain/Executables/Category/CollectionCategory';
import type { Script } from '@/domain/Executables/Script/Script';
import { parseDocs, type DocsParser } from './DocumentationParser';
import { parseScript, type ScriptParser } from './Script/ScriptParser';
import { createNodeDataValidator, type NodeDataValidator, type NodeDataValidatorFactory } from './NodeValidation/NodeDataValidator';
import { NodeDataType } from './NodeValidation/NodeDataType';
import type { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
import { createExecutableDataValidator, type ExecutableValidator, type ExecutableValidatorFactory } from './Validation/ExecutableValidator';
import { ExecutableType } from './Validation/ExecutableType';
import type { CategoryCollectionSpecificUtilities } from './CategoryCollectionSpecificUtilities';
let categoryIdCounter = 0;
export function parseCategory(
category: CategoryData,
context: ICategoryCollectionParseContext,
utilities: CategoryParserUtilities = DefaultCategoryParserUtilities,
collectionUtilities: CategoryCollectionSpecificUtilities,
categoryUtilities: CategoryParserUtilities = DefaultCategoryParserUtilities,
): Category {
return parseCategoryRecursively({
categoryData: category,
context,
utilities,
collectionUtilities,
categoryUtilities,
});
}
interface CategoryParseContext {
readonly categoryData: CategoryData;
readonly context: ICategoryCollectionParseContext;
readonly collectionUtilities: CategoryCollectionSpecificUtilities;
readonly parentCategory?: CategoryData;
readonly utilities: CategoryParserUtilities;
readonly categoryUtilities: CategoryParserUtilities;
}
function parseCategoryRecursively(
@@ -41,24 +41,24 @@ function parseCategoryRecursively(
subscripts: new Array<Script>(),
};
for (const data of context.categoryData.children) {
parseNode({
nodeData: data,
parseExecutable({
data,
children,
parent: context.categoryData,
utilities: context.utilities,
context: context.context,
categoryUtilities: context.categoryUtilities,
collectionUtilities: context.collectionUtilities,
});
}
try {
return context.utilities.createCategory({
return context.categoryUtilities.createCategory({
id: categoryIdCounter++,
name: context.categoryData.category,
docs: context.utilities.parseDocs(context.categoryData),
docs: context.categoryUtilities.parseDocs(context.categoryData),
subcategories: children.subcategories,
scripts: children.subscripts,
});
} catch (error) {
throw context.utilities.wrapError(
throw context.categoryUtilities.wrapError(
error,
validator.createContextualErrorMessage('Failed to parse category.'),
);
@@ -67,12 +67,12 @@ function parseCategoryRecursively(
function ensureValidCategory(
context: CategoryParseContext,
): NodeDataValidator {
): ExecutableValidator {
const category = context.categoryData;
const validator: NodeDataValidator = context.utilities.createValidator({
type: NodeDataType.Category,
selfNode: context.categoryData,
parentNode: context.parentCategory,
const validator: ExecutableValidator = context.categoryUtilities.createValidator({
type: ExecutableType.Category,
self: context.categoryData,
parentCategory: context.parentCategory,
});
validator.assertDefined(category);
validator.assertValidName(category.category);
@@ -88,44 +88,43 @@ interface CategoryChildren {
readonly subscripts: Script[];
}
interface NodeParseContext {
readonly nodeData: CategoryOrScriptData;
interface ExecutableParseContext {
readonly data: ExecutableData;
readonly children: CategoryChildren;
readonly parent: CategoryData;
readonly context: ICategoryCollectionParseContext;
readonly utilities: CategoryParserUtilities;
readonly collectionUtilities: CategoryCollectionSpecificUtilities;
readonly categoryUtilities: CategoryParserUtilities;
}
function parseNode(context: NodeParseContext) {
const validator: NodeDataValidator = context.utilities.createValidator({
selfNode: context.nodeData,
parentNode: context.parent,
function parseExecutable(context: ExecutableParseContext) {
const validator: ExecutableValidator = context.categoryUtilities.createValidator({
self: context.data,
parentCategory: context.parent,
});
validator.assertDefined(context.nodeData);
validator.assertDefined(context.data);
validator.assert(
() => isCategory(context.nodeData) || isScript(context.nodeData),
'Node is neither a category or a script.',
() => isCategory(context.data) || isScript(context.data),
'Executable is neither a category or a script.',
);
if (isCategory(context.nodeData)) {
if (isCategory(context.data)) {
const subCategory = parseCategoryRecursively({
categoryData: context.nodeData,
context: context.context,
categoryData: context.data,
collectionUtilities: context.collectionUtilities,
parentCategory: context.parent,
utilities: context.utilities,
categoryUtilities: context.categoryUtilities,
});
context.children.subcategories.push(subCategory);
} else { // A script
const script = context.utilities.parseScript(context.nodeData, context.context);
const script = context.categoryUtilities.parseScript(context.data, context.collectionUtilities);
context.children.subscripts.push(script);
}
}
function isScript(data: CategoryOrScriptData): data is ScriptData {
function isScript(data: ExecutableData): data is ScriptData {
return hasCode(data) || hasCall(data);
}
function isCategory(data: CategoryOrScriptData): data is CategoryData {
function isCategory(data: ExecutableData): data is CategoryData {
return hasProperty(data, 'category');
}
@@ -151,21 +150,21 @@ function hasProperty(
}
export type CategoryFactory = (
...parameters: ConstructorParameters<typeof Category>
) => ICategory;
...parameters: ConstructorParameters<typeof CollectionCategory>
) => Category;
interface CategoryParserUtilities {
readonly createCategory: CategoryFactory;
readonly wrapError: ErrorWithContextWrapper;
readonly createValidator: NodeDataValidatorFactory;
readonly createValidator: ExecutableValidatorFactory;
readonly parseScript: ScriptParser;
readonly parseDocs: DocsParser;
}
const DefaultCategoryParserUtilities: CategoryParserUtilities = {
createCategory: (...parameters) => new Category(...parameters),
createCategory: (...parameters) => new CollectionCategory(...parameters),
wrapError: wrapErrorWithAdditionalContext,
createValidator: createNodeDataValidator,
createValidator: createExecutableDataValidator,
parseScript,
parseDocs,
};

View File

@@ -1,4 +1,4 @@
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionCallArgumentCollection } from '../../Function/Call/Argument/FunctionCallArgumentCollection';
import { ExpressionEvaluationContext, type IExpressionEvaluationContext } from './ExpressionEvaluationContext';
import { ExpressionPosition } from './ExpressionPosition';

View File

@@ -1,4 +1,4 @@
import { type IExpressionEvaluationContext, ExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
import { type IExpressionEvaluationContext, ExpressionEvaluationContext } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
import type { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
import type { IExpressionsCompiler } from './IExpressionsCompiler';

View File

@@ -1,4 +1,4 @@
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
import { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
import { RegexParser, type PrimitiveExpression } from '../Parser/Regex/RegexParser';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';

View File

@@ -1,7 +1,7 @@
// eslint-disable-next-line max-classes-per-file
import type { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
import type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
import { ExpressionPosition } from '../Expression/ExpressionPosition';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
import { createPositionFromRegexFullMatch } from '../Expression/ExpressionPositionFactory';

View File

@@ -1,4 +1,4 @@
import type { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
import type { ISharedFunctionCollection } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionCollection';
import type { FunctionCall } from '../FunctionCall';
import type { SingleCallCompiler } from './SingleCall/SingleCallCompiler';

View File

@@ -1,4 +1,4 @@
import type { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
import type { ISharedFunctionCollection } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionCollection';
import type { CompiledCode } from './CompiledCode';
import type { FunctionCall } from '../FunctionCall';

View File

@@ -1,4 +1,4 @@
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
import { NewlineCodeSegmentMerger } from './CodeSegmentJoin/NewlineCodeSegmentMerger';
import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler';
import type { ISharedFunctionCollection } from '../../ISharedFunctionCollection';

View File

@@ -1,5 +1,5 @@
import type { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import type { ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
import type { CompiledCode } from '../CompiledCode';
import type { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';

View File

@@ -1,5 +1,5 @@
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
import type { FunctionCallCompilationContext } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
export interface ArgumentCompiler {
createCompiledNestedCall(

View File

@@ -1,11 +1,11 @@
import type { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import type { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import { ParsedFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/ParsedFunctionCall';
import type { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
import type { FunctionCallCompilationContext } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import { ParsedFunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/ParsedFunctionCall';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
import type { ArgumentCompiler } from './ArgumentCompiler';

View File

@@ -1,8 +1,8 @@
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import type { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { FunctionBodyType, type ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
import { FunctionBodyType, type ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
import type { CompiledCode } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/CompiledCode';
import type { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
export class InlineFunctionCallCompiler implements SingleCallCompilerStrategy {

View File

@@ -1,10 +1,10 @@
import {
type CallFunctionBody, FunctionBodyType,
type ISharedFunction,
} from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
} from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
import type { FunctionCallCompilationContext } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import type { CompiledCode } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/CompiledCode';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
import { NestedFunctionArgumentCompiler } from './Argument/NestedFunctionArgumentCompiler';
import type { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';

View File

@@ -1,5 +1,5 @@
import type { FunctionData } from '@/application/collections/';
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
export interface ISharedFunctionsParser {

View File

@@ -2,11 +2,11 @@ import type {
FunctionData, CodeInstruction, CodeFunctionData, CallFunctionData,
CallInstruction, ParameterDefinitionData,
} from '@/application/collections/';
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import { CodeValidator } from '@/application/Parser/Script/Validation/CodeValidator';
import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
import { NoDuplicatedLines } from '@/application/Parser/Script/Validation/Rules/NoDuplicatedLines';
import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
import { CodeValidator } from '@/application/Parser/Executable/Script/Validation/CodeValidator';
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
import { NoDuplicatedLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoDuplicatedLines';
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
import { isArray, isNullOrUndefined, isPlainObject } from '@/TypeHelpers';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';

View File

@@ -1,7 +1,7 @@
import type { ScriptData } from '@/application/collections/';
import type { IScriptCode } from '@/domain/IScriptCode';
import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
export interface IScriptCompiler {
canCompile(script: ScriptData): boolean;
compile(script: ScriptData): IScriptCode;
compile(script: ScriptData): ScriptCode;
}

View File

@@ -1,11 +1,11 @@
import type { FunctionData, ScriptData, CallInstruction } from '@/application/collections/';
import type { IScriptCode } from '@/domain/IScriptCode';
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import { CodeValidator } from '@/application/Parser/Script/Validation/CodeValidator';
import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
import { CodeValidator } from '@/application/Parser/Executable/Script/Validation/CodeValidator';
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
import { createScriptCode, type ScriptCodeFactory } from '@/domain/ScriptCodeFactory';
import { createScriptCode, type ScriptCodeFactory } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
import { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
@@ -34,7 +34,7 @@ export class ScriptCompiler implements IScriptCompiler {
return hasCall(script);
}
public compile(script: ScriptData): IScriptCode {
public compile(script: ScriptData): ScriptCode {
try {
if (!hasCall(script)) {
throw new Error('Script does include any calls.');

View File

@@ -1,44 +1,44 @@
import type { ScriptData, CodeScriptData, CallScriptData } from '@/application/collections/';
import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import { Script } from '@/domain/Script';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import type { IScriptCode } from '@/domain/IScriptCode';
import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
import { CollectionScript } from '@/domain/Executables/Script/CollectionScript';
import { RecommendationLevel } from '@/domain/Executables/Script/RecommendationLevel';
import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError';
import type { ScriptCodeFactory } from '@/domain/ScriptCodeFactory';
import { createScriptCode } from '@/domain/ScriptCodeFactory';
import type { IScript } from '@/domain/IScript';
import type { ScriptCodeFactory } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
import { createScriptCode } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
import type { Script } from '@/domain/Executables/Script/Script';
import { createEnumParser, type IEnumParser } from '@/application/Common/Enum';
import { parseDocs, type DocsParser } from '../DocumentationParser';
import { createEnumParser, type IEnumParser } from '../../Common/Enum';
import { NodeDataType } from '../NodeValidation/NodeDataType';
import { createNodeDataValidator, type NodeDataValidator, type NodeDataValidatorFactory } from '../NodeValidation/NodeDataValidator';
import { ExecutableType } from '../Validation/ExecutableType';
import { createExecutableDataValidator, type ExecutableValidator, type ExecutableValidatorFactory } from '../Validation/ExecutableValidator';
import { CodeValidator } from './Validation/CodeValidator';
import { NoDuplicatedLines } from './Validation/Rules/NoDuplicatedLines';
import type { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
import type { CategoryCollectionSpecificUtilities } from '../CategoryCollectionSpecificUtilities';
export interface ScriptParser {
(
data: ScriptData,
context: ICategoryCollectionParseContext,
utilities?: ScriptParserUtilities,
): IScript;
collectionUtilities: CategoryCollectionSpecificUtilities,
scriptUtilities?: ScriptParserUtilities,
): Script;
}
export const parseScript: ScriptParser = (
data,
context,
collectionUtilities,
utilities = DefaultScriptParserUtilities,
) => {
const validator = utilities.createValidator({
type: NodeDataType.Script,
selfNode: data,
type: ExecutableType.Script,
self: data,
});
validateScript(data, validator);
try {
const script = utilities.createScript({
name: data.name,
code: parseCode(data, context, utilities.codeValidator, utilities.createCode),
code: parseCode(data, collectionUtilities, utilities.codeValidator, utilities.createCode),
docs: utilities.parseDocs(data),
level: parseLevel(data.recommend, utilities.levelParser),
});
@@ -63,21 +63,21 @@ function parseLevel(
function parseCode(
script: ScriptData,
context: ICategoryCollectionParseContext,
collectionUtilities: CategoryCollectionSpecificUtilities,
codeValidator: ICodeValidator,
createCode: ScriptCodeFactory,
): IScriptCode {
if (context.compiler.canCompile(script)) {
return context.compiler.compile(script);
): ScriptCode {
if (collectionUtilities.compiler.canCompile(script)) {
return collectionUtilities.compiler.compile(script);
}
const codeScript = script as CodeScriptData; // Must be inline code if it cannot be compiled
const code = createCode(codeScript.code, codeScript.revertCode);
validateHardcodedCodeWithoutCalls(code, codeValidator, context.syntax);
validateHardcodedCodeWithoutCalls(code, codeValidator, collectionUtilities.syntax);
return code;
}
function validateHardcodedCodeWithoutCalls(
scriptCode: IScriptCode,
scriptCode: ScriptCode,
validator: ICodeValidator,
syntax: ILanguageSyntax,
) {
@@ -93,7 +93,7 @@ function validateHardcodedCodeWithoutCalls(
function validateScript(
script: ScriptData,
validator: NodeDataValidator,
validator: ExecutableValidator,
): asserts script is NonNullable<ScriptData> {
validator.assertDefined(script);
validator.assertValidName(script.name);
@@ -116,17 +116,17 @@ interface ScriptParserUtilities {
readonly createScript: ScriptFactory;
readonly codeValidator: ICodeValidator;
readonly wrapError: ErrorWithContextWrapper;
readonly createValidator: NodeDataValidatorFactory;
readonly createValidator: ExecutableValidatorFactory;
readonly createCode: ScriptCodeFactory;
readonly parseDocs: DocsParser;
}
export type ScriptFactory = (
...parameters: ConstructorParameters<typeof Script>
) => IScript;
...parameters: ConstructorParameters<typeof CollectionScript>
) => Script;
const createScript: ScriptFactory = (...parameters) => {
return new Script(...parameters);
return new CollectionScript(...parameters);
};
const DefaultScriptParserUtilities: ScriptParserUtilities = {
@@ -134,7 +134,7 @@ const DefaultScriptParserUtilities: ScriptParserUtilities = {
createScript,
codeValidator: CodeValidator.instance,
wrapError: wrapErrorWithAdditionalContext,
createValidator: createNodeDataValidator,
createValidator: createExecutableDataValidator,
createCode: createScriptCode,
parseDocs,
};

View File

@@ -1,4 +1,4 @@
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
import type { ICodeLine } from '../ICodeLine';
import type { ICodeValidationRule, IInvalidCodeLine } from '../ICodeValidationRule';

View File

@@ -1,4 +1,4 @@
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
const BatchFileCommonCodeParts = ['(', ')', 'else', '||'];
const PowerShellCommonCodeParts = ['{', '}'];

View File

@@ -1,4 +1,4 @@
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
export class ShellScriptSyntax implements ILanguageSyntax {
public readonly commentDelimiters = ['#'];

View File

@@ -1,6 +1,6 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
import { BatchFileSyntax } from './BatchFileSyntax';
import { ShellScriptSyntax } from './ShellScriptSyntax';
import type { ISyntaxFactory } from './ISyntaxFactory';

View File

@@ -0,0 +1,24 @@
import type { CategoryData, ScriptData, ExecutableData } from '@/application/collections/';
import { ExecutableType } from './ExecutableType';
export type ExecutableErrorContext = {
readonly parentCategory?: CategoryData;
} & (CategoryErrorContext | ScriptErrorContext | UnknownExecutableErrorContext);
export type CategoryErrorContext = {
readonly type: ExecutableType.Category;
readonly self: CategoryData;
readonly parentCategory?: CategoryData;
};
export type ScriptErrorContext = {
readonly type: ExecutableType.Script;
readonly self: ScriptData;
readonly parentCategory?: CategoryData;
};
export type UnknownExecutableErrorContext = {
readonly type?: undefined;
readonly self: ExecutableData;
readonly parentCategory?: CategoryData;
};

View File

@@ -0,0 +1,35 @@
import type { ExecutableData } from '@/application/collections/';
import { ExecutableType } from './ExecutableType';
import type { ExecutableErrorContext } from './ExecutableErrorContext';
export interface ExecutableContextErrorMessageCreator {
(
errorMessage: string,
context: ExecutableErrorContext,
): string;
}
export const createExecutableContextErrorMessage: ExecutableContextErrorMessageCreator = (
errorMessage,
context,
) => {
let message = '';
if (context.type !== undefined) {
message += `${ExecutableType[context.type]}: `;
}
message += errorMessage;
message += `\n${getErrorContextDetails(context)}`;
return message;
};
function getErrorContextDetails(context: ExecutableErrorContext): string {
let output = `Self: ${printExecutable(context.self)}`;
if (context.parentCategory) {
output += `\nParent: ${printExecutable(context.parentCategory)}`;
}
return output;
}
function printExecutable(executable: ExecutableData): string {
return JSON.stringify(executable, undefined, 2);
}

View File

@@ -0,0 +1,4 @@
export enum ExecutableType {
Script,
Category,
}

View File

@@ -1,17 +1,17 @@
import { isString } from '@/TypeHelpers';
import { type NodeDataErrorContext } from './NodeDataErrorContext';
import { createNodeContextErrorMessage, type NodeContextErrorMessageCreator } from './NodeDataErrorContextMessage';
import type { NodeData } from './NodeData';
import type { ExecutableData } from '@/application/collections/';
import { type ExecutableErrorContext } from './ExecutableErrorContext';
import { createExecutableContextErrorMessage, type ExecutableContextErrorMessageCreator } from './ExecutableErrorContextMessage';
export interface NodeDataValidatorFactory {
(context: NodeDataErrorContext): NodeDataValidator;
export interface ExecutableValidatorFactory {
(context: ExecutableErrorContext): ExecutableValidator;
}
export interface NodeDataValidator {
export interface ExecutableValidator {
assertValidName(nameValue: string): void;
assertDefined(
node: NodeData | undefined,
): asserts node is NonNullable<NodeData> & void;
data: ExecutableData | undefined,
): asserts data is NonNullable<ExecutableData> & void;
assert(
validationPredicate: () => boolean,
errorMessage: string,
@@ -19,14 +19,14 @@ export interface NodeDataValidator {
createContextualErrorMessage(errorMessage: string): string;
}
export const createNodeDataValidator
: NodeDataValidatorFactory = (context) => new ContextualNodeDataValidator(context);
export const createExecutableDataValidator
: ExecutableValidatorFactory = (context) => new ContextualExecutableValidator(context);
export class ContextualNodeDataValidator implements NodeDataValidator {
export class ContextualExecutableValidator implements ExecutableValidator {
constructor(
private readonly context: NodeDataErrorContext,
private readonly context: ExecutableErrorContext,
private readonly createErrorMessage
: NodeContextErrorMessageCreator = createNodeContextErrorMessage,
: ExecutableContextErrorMessageCreator = createExecutableContextErrorMessage,
) {
}
@@ -40,11 +40,11 @@ export class ContextualNodeDataValidator implements NodeDataValidator {
}
public assertDefined(
node: NodeData,
): asserts node is NonNullable<NodeData> {
data: ExecutableData,
): asserts data is NonNullable<ExecutableData> {
this.assert(
() => node !== undefined && node !== null && Object.keys(node).length > 0,
'missing node data',
() => data !== undefined && data !== null && Object.keys(data).length > 0,
'missing executable data',
);
}

View File

@@ -1,3 +0,0 @@
import type { ScriptData, CategoryData } from '@/application/collections/';
export type NodeData = CategoryData | ScriptData;

View File

@@ -1,25 +0,0 @@
import type { CategoryData, ScriptData } from '@/application/collections/';
import { NodeDataType } from './NodeDataType';
import type { NodeData } from './NodeData';
export type NodeDataErrorContext = {
readonly parentNode?: CategoryData;
} & (CategoryNodeErrorContext | ScriptNodeErrorContext | UnknownNodeErrorContext);
export type CategoryNodeErrorContext = {
readonly type: NodeDataType.Category;
readonly selfNode: CategoryData;
readonly parentNode?: CategoryData;
};
export type ScriptNodeErrorContext = {
readonly type: NodeDataType.Script;
readonly selfNode: ScriptData;
readonly parentNode?: CategoryData;
};
export type UnknownNodeErrorContext = {
readonly type?: undefined;
readonly selfNode: NodeData;
readonly parentNode?: CategoryData;
};

View File

@@ -1,35 +0,0 @@
import { NodeDataType } from './NodeDataType';
import type { NodeDataErrorContext } from './NodeDataErrorContext';
import type { NodeData } from './NodeData';
export interface NodeContextErrorMessageCreator {
(
errorMessage: string,
context: NodeDataErrorContext,
): string;
}
export const createNodeContextErrorMessage: NodeContextErrorMessageCreator = (
errorMessage,
context,
) => {
let message = '';
if (context.type !== undefined) {
message += `${NodeDataType[context.type]}: `;
}
message += errorMessage;
message += `\n${getErrorContextDetails(context)}`;
return message;
};
function getErrorContextDetails(context: NodeDataErrorContext): string {
let output = `Self: ${printNodeDataAsJson(context.selfNode)}`;
if (context.parentNode) {
output += `\nParent: ${printNodeDataAsJson(context.parentNode)}`;
}
return output;
}
function printNodeDataAsJson(node: NodeData): string {
return JSON.stringify(node, undefined, 2);
}

View File

@@ -1,4 +0,0 @@
export enum NodeDataType {
Script,
Category,
}

View File

@@ -1,23 +0,0 @@
import type { FunctionData } from '@/application/collections/';
import type { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { SyntaxFactory } from './Validation/Syntax/SyntaxFactory';
import type { ILanguageSyntax } from './Validation/Syntax/ILanguageSyntax';
import type { IScriptCompiler } from './Compiler/IScriptCompiler';
import type { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
import type { ISyntaxFactory } from './Validation/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(),
) {
this.syntax = syntaxFactory.create(scripting.language);
this.compiler = new ScriptCompiler(functionsData ?? [], this.syntax);
}
}

View File

@@ -1,7 +0,0 @@
import type { IScriptCompiler } from './Compiler/IScriptCompiler';
import type { ILanguageSyntax } from './Validation/Syntax/ILanguageSyntax';
export interface ICategoryCollectionParseContext {
readonly compiler: IScriptCompiler;
readonly syntax: ILanguageSyntax;
}

View File

@@ -1,11 +1,11 @@
import type { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
import { ParameterSubstitutionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
import { CompositeExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import type { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
import type { ICodeSubstituter } from './ICodeSubstituter';
export class CodeSubstituter implements ICodeSubstituter {

View File

@@ -1,4 +1,8 @@
declare module '@/application/collections/*' {
export interface ExecutableDefinition extends DocumentableData {
}
export interface CollectionData {
readonly os: string;
readonly scripting: ScriptingDefinitionData;
@@ -6,12 +10,12 @@ declare module '@/application/collections/*' {
readonly functions?: ReadonlyArray<FunctionData>;
}
export interface CategoryData extends DocumentableData {
readonly children: ReadonlyArray<CategoryOrScriptData>;
export interface CategoryData extends ExecutableDefinition {
readonly children: ReadonlyArray<ExecutableData>;
readonly category: string;
}
export type CategoryOrScriptData = CategoryData | ScriptData;
export type ExecutableData = CategoryData | ScriptData;
export type DocumentationData = ReadonlyArray<string> | string | undefined;
export interface DocumentableData {
@@ -56,7 +60,7 @@ declare module '@/application/collections/*' {
export type FunctionCallsData = readonly FunctionCallData[] | FunctionCallData | undefined;
export type ScriptDefinition = DocumentableData & {
export type ScriptDefinition = ExecutableDefinition & {
readonly name: string;
readonly recommend?: string;
};

View File

@@ -1,9 +1,9 @@
import { getEnumValues, assertInRange } from '@/application/Common/Enum';
import { RecommendationLevel } from './RecommendationLevel';
import { RecommendationLevel } from './Executables/Script/RecommendationLevel';
import { OperatingSystem } from './OperatingSystem';
import type { IEntity } from '../infrastructure/Entity/IEntity';
import type { ICategory } from './ICategory';
import type { IScript } from './IScript';
import type { Category } from './Executables/Category/Category';
import type { Script } from './Executables/Script/Script';
import type { IScriptingDefinition } from './IScriptingDefinition';
import type { ICategoryCollection } from './ICategoryCollection';
@@ -16,7 +16,7 @@ export class CategoryCollection implements ICategoryCollection {
constructor(
public readonly os: OperatingSystem,
public readonly actions: ReadonlyArray<ICategory>,
public readonly actions: ReadonlyArray<Category>,
public readonly scripting: IScriptingDefinition,
) {
this.queryable = makeQueryable(actions);
@@ -26,7 +26,7 @@ export class CategoryCollection implements ICategoryCollection {
ensureNoDuplicates(this.queryable.allScripts);
}
public getCategory(categoryId: number): ICategory {
public getCategory(categoryId: number): Category {
const category = this.queryable.allCategories.find((c) => c.id === categoryId);
if (!category) {
throw new Error(`Missing category with ID: "${categoryId}"`);
@@ -34,13 +34,13 @@ export class CategoryCollection implements ICategoryCollection {
return category;
}
public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] {
public getScriptsByLevel(level: RecommendationLevel): readonly Script[] {
assertInRange(level, RecommendationLevel);
const scripts = this.queryable.scriptsByLevel.get(level);
return scripts ?? [];
}
public getScript(scriptId: string): IScript {
public getScript(scriptId: string): Script {
const script = this.queryable.allScripts.find((s) => s.id === scriptId);
if (!script) {
throw new Error(`missing script: ${scriptId}`);
@@ -48,11 +48,11 @@ export class CategoryCollection implements ICategoryCollection {
return script;
}
public getAllScripts(): IScript[] {
public getAllScripts(): Script[] {
return this.queryable.allScripts;
}
public getAllCategories(): ICategory[] {
public getAllCategories(): Category[] {
return this.queryable.allCategories;
}
}
@@ -73,9 +73,9 @@ function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
}
interface IQueryableCollection {
allCategories: ICategory[];
allScripts: IScript[];
scriptsByLevel: Map<RecommendationLevel, readonly IScript[]>;
allCategories: Category[];
allScripts: Script[];
scriptsByLevel: Map<RecommendationLevel, readonly Script[]>;
}
function ensureValid(application: IQueryableCollection) {
@@ -83,13 +83,13 @@ function ensureValid(application: IQueryableCollection) {
ensureValidScripts(application.allScripts);
}
function ensureValidCategories(allCategories: readonly ICategory[]) {
function ensureValidCategories(allCategories: readonly Category[]) {
if (!allCategories.length) {
throw new Error('must consist of at least one category');
}
}
function ensureValidScripts(allScripts: readonly IScript[]) {
function ensureValidScripts(allScripts: readonly Script[]) {
if (!allScripts.length) {
throw new Error('must consist of at least one script');
}
@@ -102,8 +102,8 @@ function ensureValidScripts(allScripts: readonly IScript[]) {
}
function flattenApplication(
categories: ReadonlyArray<ICategory>,
): [ICategory[], IScript[]] {
categories: ReadonlyArray<Category>,
): [Category[], Script[]] {
const [subCategories, subScripts] = (categories || [])
// Parse children
.map((category) => flattenApplication(category.subCategories))
@@ -113,7 +113,7 @@ function flattenApplication(
[...previousCategories, ...currentCategories],
[...previousScripts, ...currentScripts],
];
}, [new Array<ICategory>(), new Array<IScript>()]);
}, [new Array<Category>(), new Array<Script>()]);
return [
[
...(categories || []),
@@ -127,7 +127,7 @@ function flattenApplication(
}
function makeQueryable(
actions: ReadonlyArray<ICategory>,
actions: ReadonlyArray<Category>,
): IQueryableCollection {
const flattened = flattenApplication(actions);
return {
@@ -138,8 +138,8 @@ function makeQueryable(
}
function groupByLevel(
allScripts: readonly IScript[],
): Map<RecommendationLevel, readonly IScript[]> {
allScripts: readonly Script[],
): Map<RecommendationLevel, readonly Script[]> {
return getEnumValues(RecommendationLevel)
.map((level) => ({
level,
@@ -150,5 +150,5 @@ function groupByLevel(
.reduce((map, group) => {
map.set(group.level, group.scripts);
return map;
}, new Map<RecommendationLevel, readonly IScript[]>());
}, new Map<RecommendationLevel, readonly Script[]>());
}

View File

@@ -0,0 +1,11 @@
import type { Script } from '../Script/Script';
import type { Executable } from '../Executable';
export interface Category extends Executable<number> {
readonly id: number;
readonly name: string;
readonly subCategories: ReadonlyArray<Category>;
readonly scripts: ReadonlyArray<Script>;
includes(script: Script): boolean;
getAllScriptsRecursively(): ReadonlyArray<Script>;
}

View File

@@ -1,17 +1,17 @@
import { BaseEntity } from '../infrastructure/Entity/BaseEntity';
import type { ICategory } from './ICategory';
import type { IScript } from './IScript';
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import type { Category } from './Category';
import type { Script } from '../Script/Script';
export class Category extends BaseEntity<number> implements ICategory {
private allSubScripts?: ReadonlyArray<IScript> = undefined;
export class CollectionCategory extends BaseEntity<number> implements Category {
private allSubScripts?: ReadonlyArray<Script> = undefined;
public readonly name: string;
public readonly docs: ReadonlyArray<string>;
public readonly subCategories: ReadonlyArray<ICategory>;
public readonly subCategories: ReadonlyArray<Category>;
public readonly scripts: ReadonlyArray<IScript>;
public readonly scripts: ReadonlyArray<Script>;
constructor(parameters: CategoryInitParameters) {
super(parameters.id);
@@ -22,11 +22,11 @@ export class Category extends BaseEntity<number> implements ICategory {
this.scripts = parameters.scripts;
}
public includes(script: IScript): boolean {
public includes(script: Script): boolean {
return this.getAllScriptsRecursively().some((childScript) => childScript.id === script.id);
}
public getAllScriptsRecursively(): readonly IScript[] {
public getAllScriptsRecursively(): readonly Script[] {
if (!this.allSubScripts) {
this.allSubScripts = parseScriptsRecursively(this);
}
@@ -38,11 +38,11 @@ export interface CategoryInitParameters {
readonly id: number;
readonly name: string;
readonly docs: ReadonlyArray<string>;
readonly subcategories: ReadonlyArray<ICategory>;
readonly scripts: ReadonlyArray<IScript>;
readonly subcategories: ReadonlyArray<Category>;
readonly scripts: ReadonlyArray<Script>;
}
function parseScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
function parseScriptsRecursively(category: Category): ReadonlyArray<Script> {
return [
...category.scripts,
...category.subCategories.flatMap((c) => c.getAllScriptsRecursively()),

View File

@@ -1,3 +1,3 @@
export interface IDocumentable {
export interface Documentable {
readonly docs: ReadonlyArray<string>;
}

View File

@@ -0,0 +1,6 @@
import type { IEntity } from '@/infrastructure/Entity/IEntity';
import type { Documentable } from './Documentable';
export interface Executable<TExecutableKey>
extends Documentable, IEntity<TExecutableKey> {
}

View File

@@ -1,6 +1,6 @@
import type { IScriptCode } from './IScriptCode';
import type { ScriptCode } from './ScriptCode';
export class ScriptCode implements IScriptCode {
export class DistinctReversibleScriptCode implements ScriptCode {
constructor(
public readonly execute: string,
public readonly revert: string | undefined,

View File

@@ -1,4 +1,4 @@
export interface IScriptCode {
export interface ScriptCode {
readonly execute: string;
readonly revert?: string;
}

View File

@@ -0,0 +1,12 @@
import { DistinctReversibleScriptCode } from './DistinctReversibleScriptCode';
import type { ScriptCode } from './ScriptCode';
export interface ScriptCodeFactory {
(
...args: ConstructorParameters<typeof DistinctReversibleScriptCode>
): ScriptCode;
}
export const createScriptCode: ScriptCodeFactory = (
...args
) => new DistinctReversibleScriptCode(...args);

Some files were not shown because too many files have changed in this diff Show More