From 99f6b46a2347bf7ada51c1b617a1dea87b79ac8e Mon Sep 17 00:00:00 2001 From: Offirmo Date: Tue, 1 Oct 2024 16:16:06 +1000 Subject: [PATCH] +++ --- .../3-advanced/normalize-string/README.md | 23 ++----- .../active/view--chat/package.json | 1 + .../src/__fixtures/primitives--console.ts | 56 +++++++++++++--- .../active/view--chat/src/__fixtures/tour.ts | 31 +++++---- .../view--chat/src/implementation/types.ts | 6 +- .../active/view--chat/src/loop/demo.ts | 2 +- .../active/view--chat/src/loop/index.ts | 36 +++++----- .../active/view--chat/src/loop/types.ts | 2 +- .../active/view--chat/src/steps/bases.ts | 65 +++++++++++++++++++ .../view--chat/src/{types => steps}/index.ts | 1 + .../src/{types => steps}/type-guards.ts.txt | 0 .../view--chat/src/{types => steps}/types.ts | 9 ++- 12 files changed, 167 insertions(+), 65 deletions(-) create mode 100644 stack--current/5-incubator/active/view--chat/src/steps/bases.ts rename stack--current/5-incubator/active/view--chat/src/{types => steps}/index.ts (50%) rename stack--current/5-incubator/active/view--chat/src/{types => steps}/type-guards.ts.txt (100%) rename stack--current/5-incubator/active/view--chat/src/{types => steps}/types.ts (85%) diff --git a/stack--current/3-advanced/normalize-string/README.md b/stack--current/3-advanced/normalize-string/README.md index e53d636a..a380b738 100644 --- a/stack--current/3-advanced/normalize-string/README.md +++ b/stack--current/3-advanced/normalize-string/README.md @@ -4,24 +4,13 @@ TODO publish on npm? ```ts -import { NORMALIZERS } from '@offirmo-private/normalize-string' +import { + combineꓽnormalizers, + ensure_string, + normalize_unicode, + trim, +} from '@offirmo-private/normalize-string' -NORMALIZERS.coerce_to_redeemable_code(s) -NORMALIZERS.ensure_string(s) -NORMALIZERS.capitalize(s) -NORMALIZERS.to_lower_case(s) -NORMALIZERS.to_upper_case(s) -NORMALIZERS.trim(s) -NORMALIZERS.coerce_to_ascii(s) -NORMALIZERS.normalize_unicode(s) -NORMALIZERS.coerce_blanks_to_single_spaces(s) -NORMALIZERS.coerce_delimiters_to_space(s) -NORMALIZERS.convert_spaces_to_camel_case(s) -NORMALIZERS.coerce_to_safe_nickname(s) -NORMALIZERS.coerce_to_redeemable_code(s) -NORMALIZERS.normalizeꓽemailⵧsafe(s) -NORMALIZERS.normalizeꓽemailⵧreasonable(s) -NORMALIZERS.normalizeꓽemailⵧfull(s) ``` https://thread.engineering/2018-08-29-searching-and-sorting-text-with-diacritical-marks-in-javascript/ diff --git a/stack--current/5-incubator/active/view--chat/package.json b/stack--current/5-incubator/active/view--chat/package.json index 66cf573d..caa3ae6b 100644 --- a/stack--current/5-incubator/active/view--chat/package.json +++ b/stack--current/5-incubator/active/view--chat/package.json @@ -16,6 +16,7 @@ "tslib": "^2" }, "dependencies": { + "@offirmo-private/normalize-string": "*", "@offirmo-private/ts-types": "*", "is-promise": "^4", "p-progress": "^1", diff --git a/stack--current/5-incubator/active/view--chat/src/__fixtures/primitives--console.ts b/stack--current/5-incubator/active/view--chat/src/__fixtures/primitives--console.ts index 71299c86..5d068cb9 100644 --- a/stack--current/5-incubator/active/view--chat/src/__fixtures/primitives--console.ts +++ b/stack--current/5-incubator/active/view--chat/src/__fixtures/primitives--console.ts @@ -1,43 +1,79 @@ +/** trivial console-based chat primitives for testing + * no need to be fancy. + */ import { type ChatPrimitives } from '../implementation/types.js' +const DEBUG = false + const CHAT_CONSOLE: ChatPrimitives = { setup: async () => { - console.log('[ChatPrimitives.setup()]') + DEBUG && console.log('[ChatPrimitives.setup()]') }, display_message: async ({msg, choices}) => { - console.log('[ChatPrimitives.display_message()]') + DEBUG && console.log('[ChatPrimitives.display_message()]') console.log(msg) - if (choices) console.log('Choices:', choices) + if (choices) { + console.log('Choices:', choices) + } }, - pretend_to_think: async ({duration_ms}) => { throw new Error('NO UI pretend_to_think') }, + pretend_to_think: async ({duration_ms}) => { + DEBUG && console.log('[ChatPrimitives.pretend_to_think(${duration_ms})]') - pretend_to_work: async({ + console.log('…') + await new Promise(resolve => setTimeout(resolve, duration_ms)) + console.log('↳ ✔') + }, + + pretend_to_work: async ({ msg_before, duration_ms, msg_after, }) => { + DEBUG && console.log(`[ChatPrimitives.pretend_to_work(${duration_ms})]`) + console.log(msg_before) await new Promise(resolve => setTimeout(resolve, duration_ms)) - console.log(msg_after) + console.log('↳ ' + msg_after) }, //read_answer: async () => { throw new Error('NO UI read_answer') }, - display_task: ({ + display_task: async ({ msg_before, promise, msg_after, }) => { - throw new Error(`NO UI display_task!`) + DEBUG && console.log('[ChatPrimitives.display_task(...)]') + + console.log(msg_before) + let result: any = undefined + let error: Error | undefined = undefined + const success = await promise.then( + (_res) => { + result = _res + return true + }, + (_err) => { + error = _err as any // TODO one day coerce to error using error utils + return false + }) + console.log('↳ ' + msg_after(success, result || error)) }, - spin_until_resolution: async ({promise}) => promise, + spin_until_resolution: async ({promise}) => { + DEBUG && console.log('[ChatPrimitives.spin_until_resolution(...)]') + + console.log('[ChatPrimitives.spin_until_resolution()] begin…') + await promise + console.log('↳ end.') + return promise + }, teardown: async () => { - console.log('[ChatPrimitives.teardown()]') + DEBUG && console.log('[ChatPrimitives.teardown()]') }, } diff --git a/stack--current/5-incubator/active/view--chat/src/__fixtures/tour.ts b/stack--current/5-incubator/active/view--chat/src/__fixtures/tour.ts index f9c2cadb..2491e184 100755 --- a/stack--current/5-incubator/active/view--chat/src/__fixtures/tour.ts +++ b/stack--current/5-incubator/active/view--chat/src/__fixtures/tour.ts @@ -1,14 +1,17 @@ -import { type Step, StepType } from '../types/types.js' -import { type StepsGenerator } from '../loop/types.js' import Deferred from '@offirmo/deferred' +import { type Step, StepType } from '../steps/types.js' +import { getꓽInputStepⵧnonEmptyString } from '../steps/bases.js' + +import { type StepsGenerator } from '../loop/types.js' + export default function* get_next_step(skip_to_index: number = 0) { console.log('get_next_step()', { skip_to_index }) const state = { mode: 'main', - name: undefined, - city: undefined, + name: undefined as string | undefined, + city: undefined as string | undefined, } const warmup_promise = new Deferred() @@ -20,7 +23,7 @@ export default function* get_next_step(skip_to_index: number = 0) { type: StepType.perceived_labor, msg_before: 'Waking up...', - duration_ms: 2000, + duration_ms: 1000, msg_after: 'Awoken!', }, @@ -39,15 +42,17 @@ export default function* get_next_step(skip_to_index: number = 0) { msg: 'Welcome. I’ll have a few questions…', }, + getꓽInputStepⵧnonEmptyString({ + prompt: 'What’s your name?', + msg_as_user: (value: string) => `My name is "${value}".`, + msg_acknowledge: (value: string) => `Thanks, ${value}!`, + + callback: (value: string) => { + console.log(`[callback called: ${value}]`) + state.name = value + }, + }), /* - { - type: 'ask_for_string', - msg_main: 'What’s your name?', - //validator: null, // TODO - msgg_as_user: value => `My name is "${value}".`, - msgg_acknowledge: name => `Thanks for the answer, ${name}!`, - callback: value => { state.name = value }, - }, { type: 'ask_for_string', msg_main: 'What city do you live in?', diff --git a/stack--current/5-incubator/active/view--chat/src/implementation/types.ts b/stack--current/5-incubator/active/view--chat/src/implementation/types.ts index 643b8881..19857975 100644 --- a/stack--current/5-incubator/active/view--chat/src/implementation/types.ts +++ b/stack--current/5-incubator/active/view--chat/src/implementation/types.ts @@ -2,7 +2,7 @@ import { Enum } from 'typescript-string-enums' import { PProgress as PromiseWithProgress } from 'p-progress' import { type Immutable } from '@offirmo-private/ts-types' -import { type TaskProgressStep } from '../types/types.js' +import { type TaskProgressStep } from '../steps/types.js' ///////////////////////////////////////////////// @@ -30,9 +30,9 @@ interface ChatPrimitives { //read_answer(step) TODO clarify display_task(p: { - msg_before: TaskProgressStep['msg_before'], + msg_before: ContentType | string, promise: TaskProgressStep['promise'], - msg_after: TaskProgressStep['msg_after'], + msg_after: NonNullable['msg_after']>, }): Promise // while we wait for the next step. diff --git a/stack--current/5-incubator/active/view--chat/src/loop/demo.ts b/stack--current/5-incubator/active/view--chat/src/loop/demo.ts index c24c4918..402a7c31 100755 --- a/stack--current/5-incubator/active/view--chat/src/loop/demo.ts +++ b/stack--current/5-incubator/active/view--chat/src/loop/demo.ts @@ -4,7 +4,7 @@ import primitives from '../__fixtures/primitives--console.js' import { create } from './index.js' const chat = create({ - DEBUG: true, + DEBUG: false, gen_next_step: generator_func() as any, primitives, }) diff --git a/stack--current/5-incubator/active/view--chat/src/loop/index.ts b/stack--current/5-incubator/active/view--chat/src/loop/index.ts index 34cee362..b22a50e9 100644 --- a/stack--current/5-incubator/active/view--chat/src/loop/index.ts +++ b/stack--current/5-incubator/active/view--chat/src/loop/index.ts @@ -1,8 +1,9 @@ import is_promise from 'is-promise' import { type Immutable } from '@offirmo-private/ts-types' +import {} from '@offirmo-private/normalize-string' import { type ChatPrimitives } from '../implementation/types.js' -import { type Step, StepType } from '../types/index.js' +import { type Step, StepType } from '../steps/index.js' import { create_dummy_progress_promise } from '../utils/index.js' import { StepsGenerator } from './types.js' @@ -89,7 +90,7 @@ function create({ } async function execute_step(step: Step) { - if (DEBUG) console.log('↘ ${LIB}.execute_step(\n', DEBUG_to_prettified_str(step), '\n)') + if (DEBUG) console.log('↘ ${LIB}.execute_step(', DEBUG_to_prettified_str(step), ')') //const step = normalize_step(raw_step) @@ -110,30 +111,30 @@ function create({ case StepType.progress: { let result: any = undefined let error: Error | undefined = undefined + let success = false + step.promise.then( + (_res) => { + success = true + result = _res + }, + (_err) => { + success = false + error = _err as any // TODO one day coerce to error using error utils + }) await primitives.display_task({ msg_before: step.msg_before || 'Processing…', promise: step.promise, msg_after: step.msg_after || ((success: boolean, result: any) => { if (success) - return 'Done!' + return '✔ Success' else - return `Failed! ("${result?.message}")` + return `✖ Failed ("${result?.message}")` }), }) - .then( - (_res) => { - result = _res - return true - }, - (_err) => { - error = _err as any // TODO one day coerce to error using error utils - return false - }) - .then(success => { - if (step.callback) - step.callback(success, result || error) - }) + + if (step.callback) + step.callback(success, result || error) break } @@ -165,6 +166,7 @@ function create({ return answer }*/ default: + console.error(`Unsupported step type: "${step.type}"!`, step) throw new Error(`Unsupported step type: "${step.type}"!`) } } diff --git a/stack--current/5-incubator/active/view--chat/src/loop/types.ts b/stack--current/5-incubator/active/view--chat/src/loop/types.ts index 6d51ae4a..de2323ef 100644 --- a/stack--current/5-incubator/active/view--chat/src/loop/types.ts +++ b/stack--current/5-incubator/active/view--chat/src/loop/types.ts @@ -1,5 +1,5 @@ -import { type Step } from '../types/index.js' +import { type Step } from '../steps/index.js' ///////////////////////////////////////////////// diff --git a/stack--current/5-incubator/active/view--chat/src/steps/bases.ts b/stack--current/5-incubator/active/view--chat/src/steps/bases.ts new file mode 100644 index 00000000..a4cba750 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/steps/bases.ts @@ -0,0 +1,65 @@ +import { StepType, type InputStep } from './types.js' +import { + combineꓽnormalizers, + ensure_string, + normalize_unicode, + trim, +} from '@offirmo-private/normalize-string' + +function getꓽInputStepⵧnonEmptyString( + parts: Omit, 'type' | 'normalizer' | 'validators'> +): InputStep { + const step: InputStep = { + type: StepType.input, + + normalizer: (raw: any): string => { + let val = ensure_string(raw) + val = normalize_unicode(val) + val = trim(val) + return val + }, + validators: [ + (value: string) => [value.length > 0, 'Should have at least 1 letter.'], + ], + ...parts + } + return step +} + +/* +function getꓽInputStepⵧconfirmation(): InputStep { + return { + type: typeof StepType.input, + + normalizer: (raw: any): number => { + let val = Boolean(raw) + return val + }, + validators: [ + ], + } +} + +function getꓽInputStepⵧinteger(): Omit, 'prompt' | 'msg_as_user' | 'msg_acknowledge'> { + return { + type: typeof StepType.input, + + normalizer: (raw: any): number => { + raw = ensure_string(raw) + let val = Number(raw) + + val = normalize_unicode(val) + val = trim(val) + return val + }, + validators: [ + + ], + } +}*/ + +///////////////////////////////////////////////// + +export { + getꓽInputStepⵧnonEmptyString, +} diff --git a/stack--current/5-incubator/active/view--chat/src/types/index.ts b/stack--current/5-incubator/active/view--chat/src/steps/index.ts similarity index 50% rename from stack--current/5-incubator/active/view--chat/src/types/index.ts rename to stack--current/5-incubator/active/view--chat/src/steps/index.ts index fdc63323..5c73e288 100644 --- a/stack--current/5-incubator/active/view--chat/src/types/index.ts +++ b/stack--current/5-incubator/active/view--chat/src/steps/index.ts @@ -1 +1,2 @@ export * from './types.js' +export * from './bases.js' diff --git a/stack--current/5-incubator/active/view--chat/src/types/type-guards.ts.txt b/stack--current/5-incubator/active/view--chat/src/steps/type-guards.ts.txt similarity index 100% rename from stack--current/5-incubator/active/view--chat/src/types/type-guards.ts.txt rename to stack--current/5-incubator/active/view--chat/src/steps/type-guards.ts.txt diff --git a/stack--current/5-incubator/active/view--chat/src/types/types.ts b/stack--current/5-incubator/active/view--chat/src/steps/types.ts similarity index 85% rename from stack--current/5-incubator/active/view--chat/src/types/types.ts rename to stack--current/5-incubator/active/view--chat/src/steps/types.ts index 5fc08a03..8870a2a5 100644 --- a/stack--current/5-incubator/active/view--chat/src/types/types.ts +++ b/stack--current/5-incubator/active/view--chat/src/steps/types.ts @@ -15,7 +15,7 @@ export type StepType = Enum // eslint-disable-line no-redeclare ///////////////////////////////////////////////// -// TODO more async?? everything? +// TODO more async?? callbacks? everything? interface BaseStep { // TODO needed? @@ -57,6 +57,7 @@ interface AskForConfirmationStep extends BaseStep { callback?: (confirmation: boolean) => void } +// inspired by https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input // TODO refine // TODO select between choices // TODO types @@ -64,9 +65,11 @@ interface InputStep extends BaseStep { type: typeof StepType.input prompt: ContentType | string - normalizer?: (raw: T) => T - msg_as_user: (value: T) => ContentType | string + placeholder?: ContentType | string // may be useful in input, but primitive is free to ignore it https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#placeholder + default?: T + normalizer?: (raw: any) => T // raw is most likely string validators: Array<(value: T) => [ boolean, ContentType | string ]> + msg_as_user: (value: T) => ContentType | string msg_acknowledge: (value: T) => ContentType | string callback?: (value: T) => void