commit 51431ec4cf3d53148831f7b49391c2dafacab363 Author: Fenris Wolf Date: Mon Mar 3 06:15:30 2025 +0000 [ini] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a02bcd0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/temp/ +/build/ diff --git a/source/base.ts b/source/base.ts new file mode 100644 index 0000000..1d9aa97 --- /dev/null +++ b/source/base.ts @@ -0,0 +1,6 @@ +declare var process; + +function show(message : string): void { + process.stdout.write("## " + message + "\n"); +} + diff --git a/source/composition.ts b/source/composition.ts new file mode 100644 index 0000000..5813cac --- /dev/null +++ b/source/composition.ts @@ -0,0 +1,57 @@ +/** + * creates an object, which has all the methods of a class instance, but as + * plain functions (required for composition) + */ +function dullCopy(instance: X): X { + const methodBlacklist: Set = new Set([ + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + "__proto__", + "constructor", + "isPrototypeOf", + "hasOwnProperty", + "propertyIsEnumerable", + "valueOf", + "toString", + "toLocaleString", + ]); + const result = {}; + // attributes + { + for (const [name, value] of Object.entries(instance)) { + result[name] = value; + } + } + // methods + { + const methodNames: Array = Object.getOwnPropertyNames(instance["__proto__"]); + for (const methodName of methodNames) { + if (! methodBlacklist.has(methodName)) { + result[methodName] = function (...methodArgs) { + return instance[methodName].apply(instance, methodArgs); + }; + } + } + } + return (result as X); +} + +/** + * creates a combination of two class instances, forming a new object with the + * methods of both operands (as plain functions) + */ +function compose(x: X, y: Y): (X & Y) { + const result = {}; + for (const object of [x, y]) { + for (const [name, implementation] of Object.entries(dullCopy(object))) { + if (name in result) { + process.stderr.write("[warn] overwriting method '" + name + "'\n"); + } + result[name] = implementation; + } + } + return (result as (X & Y)); +} + diff --git a/source/logic/bookkeeper.ts b/source/logic/bookkeeper.ts new file mode 100644 index 0000000..b5c18a8 --- /dev/null +++ b/source/logic/bookkeeper.ts @@ -0,0 +1,49 @@ +class Bookkeeper implements Employee { + private identity: Identity; + private occupation: Occupation; + + public constructor(name: string) { + this.identity = new Identity("bookkeeper", name); + this.occupation = new Calculating(); + + + + } + + public introduce(): void { + this.identity.introduce(); + } + public work(): void { + this.occupation.work(); + + + } +} + + + + + + + +function makeBookkeeper(name: string): Employee { + return compose( + new Identity("smartly composed bookkeeper", name), + new Calculating() + ); +} + + + + + + + + + + + + + + + diff --git a/source/logic/calculating.ts b/source/logic/calculating.ts new file mode 100644 index 0000000..735e2a7 --- /dev/null +++ b/source/logic/calculating.ts @@ -0,0 +1,9 @@ +class Calculating implements Occupation { + public constructor() { + } + + public work(): void { + show("*type* *type* *type*"); + } +} + diff --git a/source/logic/cleaning.ts b/source/logic/cleaning.ts new file mode 100644 index 0000000..0de3b26 --- /dev/null +++ b/source/logic/cleaning.ts @@ -0,0 +1,9 @@ +class Cleaning implements Occupation { + public constructor() { + } + + public work(): void { + show("*sweep* *sweep* *sweep*"); + } +} + diff --git a/source/logic/employee.ts b/source/logic/employee.ts new file mode 100644 index 0000000..8cf1a31 --- /dev/null +++ b/source/logic/employee.ts @@ -0,0 +1,5 @@ +interface Employee { + introduce(): void; + work(): void; +} + diff --git a/source/logic/identity.ts b/source/logic/identity.ts new file mode 100644 index 0000000..5d2c0aa --- /dev/null +++ b/source/logic/identity.ts @@ -0,0 +1,14 @@ +class Identity { + private title: string; + private name: string; + + public constructor(title: string, name: string) { + this.title = title; + this.name = name; + } + + public introduce(): void { + show(`Hi! My name is ${this.name} and i'm the ${this.title} here.`); + } +} + diff --git a/source/logic/janitor.ts b/source/logic/janitor.ts new file mode 100644 index 0000000..d4fc1c9 --- /dev/null +++ b/source/logic/janitor.ts @@ -0,0 +1,49 @@ +class Janitor implements Employee { + private identity: Identity; + private occupations: Array; + + public constructor(name: string) { + this.identity = new Identity("janitor", name); + this.occupations = [ + new Repairing(), + new Cleaning(), + ]; + } + + public introduce(): void { + this.identity.introduce(); + } + public work(): void { + for (const occupation of this.occupations) { + occupation.work(); + } + } +} + + + + + + + +function makeJanitor(name: string): Employee { + return compose( + compose( + new Identity("smartly composed janitor", name), + new Repairing() + ), + new Cleaning() + ); +} + + + + + + + + + + + + diff --git a/source/logic/occupation.ts b/source/logic/occupation.ts new file mode 100644 index 0000000..30c5a27 --- /dev/null +++ b/source/logic/occupation.ts @@ -0,0 +1,4 @@ +interface Occupation { + work(): void; +} + diff --git a/source/logic/repairing.ts b/source/logic/repairing.ts new file mode 100644 index 0000000..87c09bb --- /dev/null +++ b/source/logic/repairing.ts @@ -0,0 +1,9 @@ +class Repairing implements Occupation { + public constructor() { + } + + public work(): void { + show("*fix* *fix* *fix*"); + } +} + diff --git a/source/main.ts b/source/main.ts new file mode 100644 index 0000000..2eb9638 --- /dev/null +++ b/source/main.ts @@ -0,0 +1,10 @@ +const employees: Array = [ + new Bookkeeper("Alice"), + new Janitor("Bob"), +]; + +for (const employee of employees) { + employee.introduce(); + employee.work(); +} + diff --git a/tools/build b/tools/build new file mode 100755 index 0000000..773ec81 --- /dev/null +++ b/tools/build @@ -0,0 +1,4 @@ +#!/usr/bin/env sh + +make --file=tools/makefile + diff --git a/tools/makefile b/tools/makefile new file mode 100644 index 0000000..c7f7706 --- /dev/null +++ b/tools/makefile @@ -0,0 +1,41 @@ +## consts + +dir_source := source +dir_temp := temp +dir_build := build +cmd_mkdir := mkdir --parents +cmd_tsc := tsc --lib es2020,dom +cmd_log := echo "--" +cmd_chmod := chmod +cmd_cat := cat +cmd_echo := echo + + +## rules + +.PHONY: _default +_default: ${dir_build}/mixin + +${dir_temp}/mixin.js: \ + ${dir_source}/base.ts \ + ${dir_source}/composition.ts \ + ${dir_source}/logic/identity.ts \ + ${dir_source}/logic/occupation.ts \ + ${dir_source}/logic/calculating.ts \ + ${dir_source}/logic/repairing.ts \ + ${dir_source}/logic/cleaning.ts \ + ${dir_source}/logic/employee.ts \ + ${dir_source}/logic/bookkeeper.ts \ + ${dir_source}/logic/janitor.ts \ + ${dir_source}/main.ts + @ ${cmd_log} "compiling …" + @ ${cmd_mkdir} $(dir $@) + @ ${cmd_tsc} --outFile $@ $^ + +${dir_build}/mixin: ${dir_temp}/mixin.js + @ ${cmd_log} "finishing …" + @ ${cmd_mkdir} $(dir $@) + @ ${cmd_echo} "#!/usr/bin/env node\n" > $@ + @ ${cmd_cat} $^ >> $@ + @ ${cmd_chmod} +x $@ +