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

@@ -41,5 +41,5 @@ Application layer compiles templating syntax during parsing to create the end sc
The steps to extend the templating syntax: The steps to extend the templating syntax:
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more. 1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts). 2. Register your in [CompositeExpressionParser](./../src/application/Parser/Executable/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts).

View File

@@ -28,11 +28,20 @@ Related documentation:
- `scripting:` ***[`ScriptingDefinition`](#scriptingdefinition)*** **(required)** - `scripting:` ***[`ScriptingDefinition`](#scriptingdefinition)*** **(required)**
- Sets the scripting language for all inline code used within the collection. - Sets the scripting language for all inline code used within the collection.
### `Category` ### Executables
An Executable is a logical entity that can
- execute once compiled,
- include a `docs` property for documentation.
It's either [Category](#category) or a [Script](#script).
#### `Category`
Represents a logical group of scripts and subcategories. Represents a logical group of scripts and subcategories.
#### `Category` syntax ##### `Category` syntax
- `category:` *`string`* **(required)** - `category:` *`string`* **(required)**
- Name of the category. - Name of the category.
@@ -43,7 +52,7 @@ Represents a logical group of scripts and subcategories.
- `docs`: *`string`* | `[`*`string`*`, ... ]` - `docs`: *`string`* | `[`*`string`*`, ... ]`
- Markdown-formatted documentation related to the category. - Markdown-formatted documentation related to the category.
### `Script` #### `Script`
Represents an individual tweak. Represents an individual tweak.
@@ -58,7 +67,7 @@ Types (like [functions](#function)):
📖 For detailed guidelines, see [Script Guidelines](./script-guidelines.md). 📖 For detailed guidelines, see [Script Guidelines](./script-guidelines.md).
#### `Script` syntax ##### `Script` syntax
- `name`: *`string`* **(required)** - `name`: *`string`* **(required)**
- Script name. - Script name.

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

View File

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

View File

@@ -1,7 +1,8 @@
import type { ICategory, IScript } from '@/domain/ICategory'; import type { Category } from '@/domain/Executables/Category/Category';
import type { IScriptCode } from '@/domain/IScriptCode'; import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
import type { IDocumentable } from '@/domain/IDocumentable'; import type { Documentable } from '@/domain/Executables/Documentable';
import type { ICategoryCollection } from '@/domain/ICategoryCollection'; import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { Script } from '@/domain/Executables/Script/Script';
import { AppliedFilterResult } from '../Result/AppliedFilterResult'; import { AppliedFilterResult } from '../Result/AppliedFilterResult';
import type { FilterStrategy } from './FilterStrategy'; import type { FilterStrategy } from './FilterStrategy';
import type { FilterResult } from '../Result/FilterResult'; import type { FilterResult } from '../Result/FilterResult';
@@ -24,7 +25,7 @@ export class LinearFilterStrategy implements FilterStrategy {
} }
function matchesCategory( function matchesCategory(
category: ICategory, category: Category,
filterLowercase: string, filterLowercase: string,
): boolean { ): boolean {
return matchesAny( return matchesAny(
@@ -34,7 +35,7 @@ function matchesCategory(
} }
function matchesScript( function matchesScript(
script: IScript, script: Script,
filterLowercase: string, filterLowercase: string,
): boolean { ): boolean {
return matchesAny( return matchesAny(
@@ -58,7 +59,7 @@ function matchName(
} }
function matchCode( function matchCode(
code: IScriptCode, code: ScriptCode,
filterLowercase: string, filterLowercase: string,
): boolean { ): boolean {
if (code.execute.toLowerCase().includes(filterLowercase)) { if (code.execute.toLowerCase().includes(filterLowercase)) {
@@ -71,7 +72,7 @@ function matchCode(
} }
function matchDocumentation( function matchDocumentation(
documentable: IDocumentable, documentable: Documentable,
filterLowercase: string, filterLowercase: string,
): boolean { ): boolean {
return documentable.docs.some( 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'; import type { CategorySelectionChangeCommand } from './CategorySelectionChange';
export interface ReadonlyCategorySelection { export interface ReadonlyCategorySelection {
areAllScriptsSelected(category: ICategory): boolean; areAllScriptsSelected(category: Category): boolean;
isAnyScriptSelected(category: ICategory): boolean; isAnyScriptSelected(category: Category): boolean;
} }
export interface CategorySelection extends ReadonlyCategorySelection { 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 { ICategoryCollection } from '@/domain/ICategoryCollection';
import type { CategorySelectionChange, CategorySelectionChangeCommand } from './CategorySelectionChange'; import type { CategorySelectionChange, CategorySelectionChangeCommand } from './CategorySelectionChange';
import type { CategorySelection } from './CategorySelection'; 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; const { selectedScripts } = this.scriptSelection;
if (selectedScripts.length === 0) { if (selectedScripts.length === 0) {
return false; 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; const { selectedScripts } = this.scriptSelection;
if (selectedScripts.length === 0) { if (selectedScripts.length === 0) {
return false; return false;

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import type { IEntity } from '@/infrastructure/Entity/IEntity'; 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> { export interface SelectedScript extends IEntity<ScriptId> {
readonly script: IScript; readonly script: Script;
readonly revert: boolean; readonly revert: boolean;
} }

View File

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

View File

@@ -4,20 +4,21 @@ import type { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection'; import { CategoryCollection } from '@/domain/CategoryCollection';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { createEnumParser } from '../Common/Enum'; import { createEnumParser } from '../Common/Enum';
import { parseCategory } from './CategoryParser'; import { parseCategory } from './Executable/CategoryParser';
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser'; import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
import { createCollectionUtilities, type CategoryCollectionSpecificUtilitiesFactory } from './Executable/CategoryCollectionSpecificUtilities';
export function parseCategoryCollection( export function parseCategoryCollection(
content: CollectionData, content: CollectionData,
projectDetails: ProjectDetails, projectDetails: ProjectDetails,
osParser = createEnumParser(OperatingSystem), osParser = createEnumParser(OperatingSystem),
createUtilities: CategoryCollectionSpecificUtilitiesFactory = createCollectionUtilities,
): ICategoryCollection { ): ICategoryCollection {
validate(content); validate(content);
const scripting = new ScriptingDefinitionParser() const scripting = new ScriptingDefinitionParser()
.parse(content.scripting, projectDetails); .parse(content.scripting, projectDetails);
const context = new CategoryCollectionParseContext(content.functions, scripting); const utilities = createUtilities(content.functions, scripting);
const categories = content.actions.map((action) => parseCategory(action, context)); const categories = content.actions.map((action) => parseCategory(action, utilities));
const os = osParser.parseEnum(content.os, 'os'); const os = osParser.parseEnum(content.os, 'os');
const collection = new CategoryCollection( const collection = new CategoryCollection(
os, 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 { import type {
CategoryData, ScriptData, CategoryOrScriptData, CategoryData, ScriptData, ExecutableData,
} from '@/application/collections/'; } from '@/application/collections/';
import { Script } from '@/domain/Script';
import { Category } from '@/domain/Category';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/ContextualError'; 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 { parseDocs, type DocsParser } from './DocumentationParser';
import { parseScript, type ScriptParser } from './Script/ScriptParser'; import { parseScript, type ScriptParser } from './Script/ScriptParser';
import { createNodeDataValidator, type NodeDataValidator, type NodeDataValidatorFactory } from './NodeValidation/NodeDataValidator'; import { createExecutableDataValidator, type ExecutableValidator, type ExecutableValidatorFactory } from './Validation/ExecutableValidator';
import { NodeDataType } from './NodeValidation/NodeDataType'; import { ExecutableType } from './Validation/ExecutableType';
import type { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext'; import type { CategoryCollectionSpecificUtilities } from './CategoryCollectionSpecificUtilities';
let categoryIdCounter = 0; let categoryIdCounter = 0;
export function parseCategory( export function parseCategory(
category: CategoryData, category: CategoryData,
context: ICategoryCollectionParseContext, collectionUtilities: CategoryCollectionSpecificUtilities,
utilities: CategoryParserUtilities = DefaultCategoryParserUtilities, categoryUtilities: CategoryParserUtilities = DefaultCategoryParserUtilities,
): Category { ): Category {
return parseCategoryRecursively({ return parseCategoryRecursively({
categoryData: category, categoryData: category,
context, collectionUtilities,
utilities, categoryUtilities,
}); });
} }
interface CategoryParseContext { interface CategoryParseContext {
readonly categoryData: CategoryData; readonly categoryData: CategoryData;
readonly context: ICategoryCollectionParseContext; readonly collectionUtilities: CategoryCollectionSpecificUtilities;
readonly parentCategory?: CategoryData; readonly parentCategory?: CategoryData;
readonly utilities: CategoryParserUtilities; readonly categoryUtilities: CategoryParserUtilities;
} }
function parseCategoryRecursively( function parseCategoryRecursively(
@@ -41,24 +41,24 @@ function parseCategoryRecursively(
subscripts: new Array<Script>(), subscripts: new Array<Script>(),
}; };
for (const data of context.categoryData.children) { for (const data of context.categoryData.children) {
parseNode({ parseExecutable({
nodeData: data, data,
children, children,
parent: context.categoryData, parent: context.categoryData,
utilities: context.utilities, categoryUtilities: context.categoryUtilities,
context: context.context, collectionUtilities: context.collectionUtilities,
}); });
} }
try { try {
return context.utilities.createCategory({ return context.categoryUtilities.createCategory({
id: categoryIdCounter++, id: categoryIdCounter++,
name: context.categoryData.category, name: context.categoryData.category,
docs: context.utilities.parseDocs(context.categoryData), docs: context.categoryUtilities.parseDocs(context.categoryData),
subcategories: children.subcategories, subcategories: children.subcategories,
scripts: children.subscripts, scripts: children.subscripts,
}); });
} catch (error) { } catch (error) {
throw context.utilities.wrapError( throw context.categoryUtilities.wrapError(
error, error,
validator.createContextualErrorMessage('Failed to parse category.'), validator.createContextualErrorMessage('Failed to parse category.'),
); );
@@ -67,12 +67,12 @@ function parseCategoryRecursively(
function ensureValidCategory( function ensureValidCategory(
context: CategoryParseContext, context: CategoryParseContext,
): NodeDataValidator { ): ExecutableValidator {
const category = context.categoryData; const category = context.categoryData;
const validator: NodeDataValidator = context.utilities.createValidator({ const validator: ExecutableValidator = context.categoryUtilities.createValidator({
type: NodeDataType.Category, type: ExecutableType.Category,
selfNode: context.categoryData, self: context.categoryData,
parentNode: context.parentCategory, parentCategory: context.parentCategory,
}); });
validator.assertDefined(category); validator.assertDefined(category);
validator.assertValidName(category.category); validator.assertValidName(category.category);
@@ -88,44 +88,43 @@ interface CategoryChildren {
readonly subscripts: Script[]; readonly subscripts: Script[];
} }
interface NodeParseContext { interface ExecutableParseContext {
readonly nodeData: CategoryOrScriptData; readonly data: ExecutableData;
readonly children: CategoryChildren; readonly children: CategoryChildren;
readonly parent: CategoryData; readonly parent: CategoryData;
readonly context: ICategoryCollectionParseContext; readonly collectionUtilities: CategoryCollectionSpecificUtilities;
readonly categoryUtilities: CategoryParserUtilities;
readonly utilities: CategoryParserUtilities;
} }
function parseNode(context: NodeParseContext) { function parseExecutable(context: ExecutableParseContext) {
const validator: NodeDataValidator = context.utilities.createValidator({ const validator: ExecutableValidator = context.categoryUtilities.createValidator({
selfNode: context.nodeData, self: context.data,
parentNode: context.parent, parentCategory: context.parent,
}); });
validator.assertDefined(context.nodeData); validator.assertDefined(context.data);
validator.assert( validator.assert(
() => isCategory(context.nodeData) || isScript(context.nodeData), () => isCategory(context.data) || isScript(context.data),
'Node is neither a category or a script.', 'Executable is neither a category or a script.',
); );
if (isCategory(context.nodeData)) { if (isCategory(context.data)) {
const subCategory = parseCategoryRecursively({ const subCategory = parseCategoryRecursively({
categoryData: context.nodeData, categoryData: context.data,
context: context.context, collectionUtilities: context.collectionUtilities,
parentCategory: context.parent, parentCategory: context.parent,
utilities: context.utilities, categoryUtilities: context.categoryUtilities,
}); });
context.children.subcategories.push(subCategory); context.children.subcategories.push(subCategory);
} else { // A script } 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); context.children.subscripts.push(script);
} }
} }
function isScript(data: CategoryOrScriptData): data is ScriptData { function isScript(data: ExecutableData): data is ScriptData {
return hasCode(data) || hasCall(data); return hasCode(data) || hasCall(data);
} }
function isCategory(data: CategoryOrScriptData): data is CategoryData { function isCategory(data: ExecutableData): data is CategoryData {
return hasProperty(data, 'category'); return hasProperty(data, 'category');
} }
@@ -151,21 +150,21 @@ function hasProperty(
} }
export type CategoryFactory = ( export type CategoryFactory = (
...parameters: ConstructorParameters<typeof Category> ...parameters: ConstructorParameters<typeof CollectionCategory>
) => ICategory; ) => Category;
interface CategoryParserUtilities { interface CategoryParserUtilities {
readonly createCategory: CategoryFactory; readonly createCategory: CategoryFactory;
readonly wrapError: ErrorWithContextWrapper; readonly wrapError: ErrorWithContextWrapper;
readonly createValidator: NodeDataValidatorFactory; readonly createValidator: ExecutableValidatorFactory;
readonly parseScript: ScriptParser; readonly parseScript: ScriptParser;
readonly parseDocs: DocsParser; readonly parseDocs: DocsParser;
} }
const DefaultCategoryParserUtilities: CategoryParserUtilities = { const DefaultCategoryParserUtilities: CategoryParserUtilities = {
createCategory: (...parameters) => new Category(...parameters), createCategory: (...parameters) => new CollectionCategory(...parameters),
wrapError: wrapErrorWithAdditionalContext, wrapError: wrapErrorWithAdditionalContext,
createValidator: createNodeDataValidator, createValidator: createExecutableDataValidator,
parseScript, parseScript,
parseDocs, 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 { FunctionCallArgumentCollection } from '../../Function/Call/Argument/FunctionCallArgumentCollection';
import { ExpressionEvaluationContext, type IExpressionEvaluationContext } from './ExpressionEvaluationContext'; import { ExpressionEvaluationContext, type IExpressionEvaluationContext } from './ExpressionEvaluationContext';
import { ExpressionPosition } from './ExpressionPosition'; 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 { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
import type { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection'; import type { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
import type { IExpressionsCompiler } from './IExpressionsCompiler'; 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 { RegexParser, type PrimitiveExpression } from '../Parser/Regex/RegexParser';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder'; import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';

View File

@@ -1,7 +1,7 @@
// eslint-disable-next-line max-classes-per-file // eslint-disable-next-line max-classes-per-file
import type { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser'; import type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection'; import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter'; import { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
import { ExpressionPosition } from '../Expression/ExpressionPosition'; import { ExpressionPosition } from '../Expression/ExpressionPosition';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder'; import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
import { createPositionFromRegexFullMatch } from '../Expression/ExpressionPositionFactory'; 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 { FunctionCall } from '../FunctionCall';
import type { SingleCallCompiler } from './SingleCall/SingleCallCompiler'; 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 { CompiledCode } from './CompiledCode';
import type { FunctionCall } from '../FunctionCall'; 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 { NewlineCodeSegmentMerger } from './CodeSegmentJoin/NewlineCodeSegmentMerger';
import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler'; import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler';
import type { ISharedFunctionCollection } from '../../ISharedFunctionCollection'; import type { ISharedFunctionCollection } from '../../ISharedFunctionCollection';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import type { FunctionData } from '@/application/collections/'; 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'; import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
export interface ISharedFunctionsParser { export interface ISharedFunctionsParser {

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory'; 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 { BatchFileSyntax } from './BatchFileSyntax';
import { ShellScriptSyntax } from './ShellScriptSyntax'; import { ShellScriptSyntax } from './ShellScriptSyntax';
import type { ISyntaxFactory } from './ISyntaxFactory'; 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 { isString } from '@/TypeHelpers';
import { type NodeDataErrorContext } from './NodeDataErrorContext'; import type { ExecutableData } from '@/application/collections/';
import { createNodeContextErrorMessage, type NodeContextErrorMessageCreator } from './NodeDataErrorContextMessage'; import { type ExecutableErrorContext } from './ExecutableErrorContext';
import type { NodeData } from './NodeData'; import { createExecutableContextErrorMessage, type ExecutableContextErrorMessageCreator } from './ExecutableErrorContextMessage';
export interface NodeDataValidatorFactory { export interface ExecutableValidatorFactory {
(context: NodeDataErrorContext): NodeDataValidator; (context: ExecutableErrorContext): ExecutableValidator;
} }
export interface NodeDataValidator { export interface ExecutableValidator {
assertValidName(nameValue: string): void; assertValidName(nameValue: string): void;
assertDefined( assertDefined(
node: NodeData | undefined, data: ExecutableData | undefined,
): asserts node is NonNullable<NodeData> & void; ): asserts data is NonNullable<ExecutableData> & void;
assert( assert(
validationPredicate: () => boolean, validationPredicate: () => boolean,
errorMessage: string, errorMessage: string,
@@ -19,14 +19,14 @@ export interface NodeDataValidator {
createContextualErrorMessage(errorMessage: string): string; createContextualErrorMessage(errorMessage: string): string;
} }
export const createNodeDataValidator export const createExecutableDataValidator
: NodeDataValidatorFactory = (context) => new ContextualNodeDataValidator(context); : ExecutableValidatorFactory = (context) => new ContextualExecutableValidator(context);
export class ContextualNodeDataValidator implements NodeDataValidator { export class ContextualExecutableValidator implements ExecutableValidator {
constructor( constructor(
private readonly context: NodeDataErrorContext, private readonly context: ExecutableErrorContext,
private readonly createErrorMessage private readonly createErrorMessage
: NodeContextErrorMessageCreator = createNodeContextErrorMessage, : ExecutableContextErrorMessageCreator = createExecutableContextErrorMessage,
) { ) {
} }
@@ -40,11 +40,11 @@ export class ContextualNodeDataValidator implements NodeDataValidator {
} }
public assertDefined( public assertDefined(
node: NodeData, data: ExecutableData,
): asserts node is NonNullable<NodeData> { ): asserts data is NonNullable<ExecutableData> {
this.assert( this.assert(
() => node !== undefined && node !== null && Object.keys(node).length > 0, () => data !== undefined && data !== null && Object.keys(data).length > 0,
'missing node data', '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 type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser'; import { ParameterSubstitutionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser'; import { CompositeExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler'; import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection'; import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument'; import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import type { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser'; import type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
import type { ICodeSubstituter } from './ICodeSubstituter'; import type { ICodeSubstituter } from './ICodeSubstituter';
export class CodeSubstituter implements ICodeSubstituter { export class CodeSubstituter implements ICodeSubstituter {

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
export interface IDocumentable { export interface Documentable {
readonly docs: ReadonlyArray<string>; 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( constructor(
public readonly execute: string, public readonly execute: string,
public readonly revert: string | undefined, public readonly revert: string | undefined,

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