Inject promptForYaml instead of having the callers inject it at the call site

main
Mari 3 years ago
parent d3d820e60a
commit a9cbd24e0c
  1. 6
      gulpfile.js
  2. 4121
      package-lock.json
  3. 20
      package.json
  4. 13
      src/commands/AddEntry.ts
  5. 10
      src/commands/UpdateEmpathyGuide.ts
  6. 16
      src/prompts/implementations/EmpathyGroupPrompt.ts
  7. 14
      src/prompts/implementations/EmpathyGuidePrompt.ts
  8. 14
      src/prompts/implementations/EntryMainMenuPrompt.ts
  9. 9
      src/schemata/SchemaData.ts
  10. 33
      src/schemata/YAMLPrompt.ts

@ -0,0 +1,6 @@
const gulp = require("gulp");
const ts = require("gulp-typescript");
const tsProject = ts.createProject("tsconfig.json");
gulp.task("default", function () {
return tsProject.src().pipe(tsProject()).js.pipe(gulp.dest("dist"));
});

4121
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -4,20 +4,16 @@
"description": "The guided journaling software used to track Mari's status.",
"main": "index.js",
"scripts": {
"start": "npm run app",
"start": "npm run --silent app",
"app": "node dist/index.js",
"update-empathy-guide": "npm run app -- update-empathy-guide",
"add-entry": "npm run app -- add-entry",
"build": "npm run build-ts",
"prestart": "npm run build",
"debug": "npm run start --inspect",
"watch-node": "nodemon dist/index.js",
"watch": "tsc && concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold,green.bold\" \"npm run watch-ts\" \"npm run watch-node\"",
"build-ts": "tsc",
"watch-ts": "tsc -w"
"update-empathy-guide": "npm run --silent app -- update-empathy-guide",
"add-entry": "npm run --silent app -- add-entry",
"build": "gulp",
"prestart": "npm run --silent build",
"debug": "npm run --silent start --inspect"
},
"keywords": [],
"author": "",
"author": "Mari",
"license": "ISC",
"dependencies": {
"ajv": "^8.6.2",
@ -46,6 +42,8 @@
"@types/pluralize": "^0.0.29",
"@types/yargs": "^17.0.0",
"concurrently": "^5.0.0",
"gulp": "^4.0.2",
"gulp-typescript": "^6.0.0-alpha.1",
"nodemon": "^2.0.4",
"typescript": "^4.0.3"
}

@ -11,6 +11,7 @@ import {conditionPrompt} from "../prompts/implementations/ConditionPrompt";
import {entryPrompt} from "../prompts/implementations/EntryPrompt";
import {guidedEmpathyPrompt} from "../prompts/implementations/GuidedEmpathyPrompt";
import {suicidalityPrompt} from "../prompts/implementations/SuicidalityPrompt";
import {yamlPrompt} from "../schemata/YAMLPrompt";
export function addEntryCommand(): CommandModule {
return {
@ -34,6 +35,11 @@ export function addEntryCommand(): CommandModule {
const mainMenuItems = [
// TODO: add ability to amend previous entry
// TODO: add To-Do list items, which can be in four states (Pending, Done, Suspended, Canceled)
// A list item with the same ID as a previous one overrides it, allowing items to be edited or
// marked as done - the current state of the list is written to a separate part of the journal
// file (or a separate file) on save, so that we can easily edit it on future visits, but the
// entry also contains a changelog of entries added, changed, and removed
condition.mainMenu,
summary.mainMenu,
new Separator(),
@ -83,10 +89,15 @@ export function addEntryCommand(): CommandModule {
*/
]
const promptYaml = yamlPrompt({
inquire,
showError: async (text: string) => console.log(text)
})
const mainMenu = entryMainMenuPrompt({
inquire,
choices: mainMenuItems,
showError: async (value) => console.log(value),
promptYaml
})
const entry = entryPrompt({

@ -5,6 +5,7 @@ import {LocalRepository} from "../repository/LocalRepository";
import {empathyGroupPrompt} from "../prompts/implementations/EmpathyGroupPrompt";
import {empathyGroupListPrompt} from "../prompts/implementations/EmpathyGroupListPrompt";
import {empathyGuidePrompt} from "../prompts/implementations/EmpathyGuidePrompt";
import {yamlPrompt} from "../schemata/YAMLPrompt";
export function updateEmpathyGuideCommand(): CommandModule {
return {
@ -15,11 +16,16 @@ export function updateEmpathyGuideCommand(): CommandModule {
const storage = new LocalRepository()
const empathyGuide = await storage.loadEmpathyGuide()
const empathyGroup = empathyGroupPrompt({
const promptYaml = yamlPrompt({
inquire,
showError: async (text) => console.error(text),
})
const empathyGroup = empathyGroupPrompt({
inquire,
promptYaml,
})
const empathyList = empathyGroupListPrompt({
inquire,
promptForEmpathyGroup: empathyGroup
@ -27,7 +33,7 @@ export function updateEmpathyGuideCommand(): CommandModule {
const newGuide = await empathyGuidePrompt({
inquire,
showError: async (text) => console.error(text),
promptYaml,
promptForEmpathyGroupList: empathyList,
})({default: empathyGuide})
await storage.saveEmpathyGuide(newGuide)

@ -1,9 +1,9 @@
import capitalize from "capitalize";
import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt";
import {YamlPromptFunction} from "../../schemata/YAMLPrompt";
import chalk from "chalk";
import {InquireFunction, ShowFunction} from "../Inquire";
import {InquireFunction} from "../Inquire";
import {EmpathyGroup, EmpathyGroupJTD} from "../../datatypes/EmpathyGroup";
import {ExpandQuestion, InputQuestion, MultiTextInputQuestion} from "inquirer";
import {EditorQuestion, ExpandQuestion, InputQuestion, MultiTextInputQuestion} from "inquirer";
export interface EmpathyGroupPromptOptions {
default?: EmpathyGroup
@ -11,8 +11,8 @@ export interface EmpathyGroupPromptOptions {
}
export interface EmpathyGroupPromptDependencies {
inquire: InquireFunction<ExpandQuestion, typeof PROMPT|typeof AUTO|typeof YAML> & EditYamlInquire & InquireFunction<InputQuestion, string> & InquireFunction<MultiTextInputQuestion, string[]>
showError: ShowFunction
inquire: InquireFunction<ExpandQuestion, typeof PROMPT|typeof AUTO|typeof YAML> & InquireFunction<InputQuestion, string> & InquireFunction<MultiTextInputQuestion, string[]> & InquireFunction<EditorQuestion, string>
promptYaml: YamlPromptFunction
}
export function empathyGroupPrompt(deps: EmpathyGroupPromptDependencies): (opts: EmpathyGroupPromptOptions) => Promise<EmpathyGroup | null> {
@ -25,7 +25,7 @@ const YAML = "YAML"
export async function promptForEmpathyGroup(opts: EmpathyGroupPromptOptions, deps: EmpathyGroupPromptDependencies): Promise<EmpathyGroup | null> {
const {listName, default: {header = undefined, items = []} = {}} = opts
const {inquire, showError} = deps
const {inquire, promptYaml} = deps
const mode = await inquire({
type: "expand",
message: `How would you like to ${header ? "edit" : "create"} this ${listName} group?`,
@ -52,12 +52,10 @@ export async function promptForEmpathyGroup(opts: EmpathyGroupPromptOptions, dep
items: parsedItems.map((item) => item.toLocaleLowerCase())
}
case YAML:
return await editYaml({
return await promptYaml({
schema: EmpathyGroupJTD,
currentValue: opts.default,
name: `this ${listName} group`,
inquire,
showError,
}) || null
case PROMPT:
default:

@ -3,8 +3,8 @@ import chalk from "chalk";
import pluralize from "pluralize";
import {totalItems} from "../../datatypes/EmpathyGroupList";
import {ExpandQuestion, Separator} from "inquirer";
import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt";
import {InquireFunction, ShowFunction} from "../Inquire";
import {YamlPromptFunction} from "../../schemata/YAMLPrompt";
import {InquireFunction} from "../Inquire";
import {EmpathyGroupListPromptOptions} from "./EmpathyGroupListPrompt";
import {EmpathyGroup} from "../../datatypes/EmpathyGroup";
import {EmpathyGuide, EmpathyGuideJTD} from "../../datatypes/EmpathyGuide";
@ -14,9 +14,9 @@ export interface EmpathyGuidePromptOptions {
}
export interface EmpathyGuidePromptDependencies {
readonly inquire: InquireFunction<ExpandQuestion, typeof PLEASANT | typeof UNPLEASANT | typeof NEEDS | typeof INSPECT | typeof SAVE> & EditYamlInquire
readonly showError: ShowFunction
readonly inquire: InquireFunction<ExpandQuestion, typeof PLEASANT | typeof UNPLEASANT | typeof NEEDS | typeof INSPECT | typeof SAVE>
readonly promptForEmpathyGroupList: (opts: EmpathyGroupListPromptOptions) => Promise<readonly EmpathyGroup[]>
readonly promptYaml: YamlPromptFunction
}
export function empathyGuidePrompt(deps: EmpathyGuidePromptDependencies): (opts: EmpathyGuidePromptOptions) => Promise<EmpathyGuide> {
@ -31,7 +31,7 @@ const SAVE = "Save"
export async function promptForEmpathyGuide(opts: EmpathyGuidePromptOptions, deps: EmpathyGuidePromptDependencies): Promise<EmpathyGuide> {
const {default: value} = opts
const {inquire, showError, promptForEmpathyGroupList} = deps
const {inquire, promptForEmpathyGroupList, promptYaml} = deps
const result = await inquire({
type: "expand",
message: "What would you like to modify?",
@ -108,12 +108,10 @@ export async function promptForEmpathyGuide(opts: EmpathyGuidePromptOptions, dep
default:
case INSPECT:
return promptForEmpathyGuide({
default: await editYaml({
default: await promptYaml({
schema: EmpathyGuideJTD,
currentValue: value,
name: "the current empathy guide",
inquire,
showError
})
}, deps)
}

@ -1,8 +1,8 @@
import {InquireFunction, ShowFunction} from "../Inquire";
import {InquireFunction} from "../Inquire";
import {ExpandChoiceOptions, ExpandQuestion, Separator, SeparatorOptions} from "inquirer";
import {merge} from "../../utils/Merge";
import {Entry, EntryJTD} from "../../datatypes/Entry";
import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt";
import {YamlPromptFunction} from "../../schemata/YAMLPrompt";
const VIEW = ".View"
const DONE = ".Done"
@ -59,9 +59,9 @@ export function makeEntryMainMenuChoice<PropertyT extends string & keyof EntryMa
export interface EntryMainMenuOptions extends Omit<Entry, "finishedAt"> {}
export interface EntryMainMenuDependencies {
readonly inquire: InquireFunction<ExpandQuestion, typeof DONE | typeof VIEW | EntryMainMenuChoiceKey> & EditYamlInquire
readonly inquire: InquireFunction<ExpandQuestion, typeof DONE | typeof VIEW | EntryMainMenuChoiceKey>
readonly choices: readonly (EntryMainMenuChoice<EntryMainMenuChoiceKey>|SeparatorOptions)[]
readonly showError: ShowFunction
readonly promptYaml: YamlPromptFunction
}
export function entryMainMenuPrompt(deps: EntryMainMenuDependencies): (options: EntryMainMenuOptions) => Promise<Entry> {
@ -72,7 +72,7 @@ export async function promptForEntryMainMenu(entry: EntryMainMenuOptions, deps:
const {
inquire,
choices,
showError,
promptYaml,
} = deps
const result = await inquire({
type: "expand",
@ -98,12 +98,10 @@ export async function promptForEntryMainMenu(entry: EntryMainMenuOptions, deps:
finishedAt: new Date(),
}
} else if (result === VIEW) {
const updated = await editYaml({
const updated = await promptYaml({
schema: EntryJTD,
currentValue: {...entry, finishedAt: new Date()},
name: "the current entry",
inquire,
showError,
})
const withoutFinishedAt = {...updated, finishedAt: undefined}
return continueWith(withoutFinishedAt)

@ -8,7 +8,6 @@ interface BaseSchemaData<RepresentedT, ReferenceT extends string> {
schema: unknown,
key: ReferenceT,
definition: { [key in ReferenceT]: unknown }
referenced?: { [key in ReferenceT]: RepresentedT }
reference: { "ref": ReferenceT },
validate: ValidateFunction<RepresentedT>
requiredReferences: AnyReferenceList
@ -19,14 +18,13 @@ export interface SchemaData<RepresentedT, ReferenceT extends string, ReferencesT
schema: JTDSchemaType<RepresentedT, DefinitionsT>,
key: ReferenceT,
definition: { [key in ReferenceT]: JTDSchemaType<RepresentedT, DefinitionsT> }
referenced?: { [key in ReferenceT]: RepresentedT }
reference: { "ref": ReferenceT },
validate: ValidateFunction<RepresentedT>
requiredReferences: ReferencesT
}
export type AnySchemaData = BaseSchemaData<unknown, string>
export type AnySchemaDataFor<RepresentedT> = BaseSchemaData<RepresentedT, string>
export type AnySchemaData = AnySchemaDataFor<unknown>
export type AnyReferenceList = AnySchemaData[]
export type AnyDefinitions = Record<string, unknown>
@ -35,19 +33,16 @@ export type Value<DataT extends AnySchemaData> = Exclude<DataT["value"], undefin
export type Definition<DataT extends AnySchemaData> = Exclude<DataT["definition"], undefined>
export type Reference<DataT extends AnySchemaData> = Exclude<DataT["reference"], undefined>
export type ReferenceKey<DataT extends AnySchemaData> = Exclude<DataT["key"], undefined>
export type Referenced<DataT extends AnySchemaData> = Exclude<DataT["referenced"], undefined>
export type ReferencedSchemaMap<ReferencesT extends AnyReferenceList> = {
[Property in keyof ReferencesT as ReferencesT[Property] extends AnySchemaData ? ReferenceKey<ReferencesT[Property]> : never]: ReferencesT[Property] extends AnySchemaData ? ReferencesT[Property] : never
}
export type ReferencedSchema<ReferencesT extends AnyReferenceList> = ReferencedSchemaMap<ReferencesT>[keyof ReferencedSchemaMap<ReferencesT>]
export type ReferencedDefinitions<ReferencesT extends AnyReferenceList> = {
[Property in keyof ReferencedSchemaMap<ReferencesT>]?: Definition<ReferencedSchemaMap<ReferencesT>[Property]>
}
export type ReferencedTypes<ReferencesT extends AnyReferenceList> = {
[Property in keyof ReferencedSchemaMap<ReferencesT>]?: Value<ReferencedSchemaMap<ReferencesT>[Property]>
}
// TODO: Add a test for a circular definition (i.e., an object that contains a reference to itself, for recursive objects like trees) in both one- and two-object loops
export function schema<
RepresentedT,
KeyT extends string,

@ -6,12 +6,35 @@ import {EditorQuestion, ExpandQuestion} from "inquirer";
const RETRY = "Retry"
const ABORT = "Abort"
export type EditYamlInquire = InquireFunction<EditorQuestion, string> & InquireFunction<ExpandQuestion, typeof RETRY|typeof ABORT>
export interface YamlPromptOptions<ObjectT> {
schema: AnySchemaDataFor<ObjectT>
currentValue: ObjectT|undefined
name: string
}
// TODO: Inject this one's dependencies (inquire, showError) as well, instead of making callers do it, and give it to said callers.
export async function editYaml<ObjectT extends object>({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor<ObjectT>, currentValue: ObjectT, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise<ObjectT>
export async function editYaml<ObjectT extends object>({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor<ObjectT>, currentValue: ObjectT|undefined, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise<ObjectT|undefined>
export async function editYaml<ObjectT extends object>({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor<ObjectT>, currentValue: ObjectT|undefined, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise<ObjectT|undefined> {
export interface YamlPromptSetOptions<ObjectT> extends YamlPromptOptions<ObjectT> {
currentValue: ObjectT
}
export interface YamlPromptDependencies {
inquire: InquireFunction<EditorQuestion, string> & InquireFunction<ExpandQuestion, typeof RETRY|typeof ABORT>
showError: ShowFunction
}
export type YamlPromptFunction = (<ObjectT>(opts: YamlPromptSetOptions<ObjectT>) => Promise<ObjectT>) & (<ObjectT>(opts: YamlPromptOptions<ObjectT>) => Promise<ObjectT|undefined>)
export function yamlPrompt(deps: YamlPromptDependencies): YamlPromptFunction {
function injectedYamlPrompt<ObjectT>(opts: YamlPromptSetOptions<ObjectT>): Promise<ObjectT>
function injectedYamlPrompt<ObjectT>(opts: YamlPromptOptions<ObjectT>): Promise<ObjectT|undefined>
function injectedYamlPrompt<ObjectT>(opts: YamlPromptOptions<ObjectT>): Promise<ObjectT|undefined> {
return promptForYaml<ObjectT>(opts, deps)
}
return injectedYamlPrompt
}
export async function promptForYaml<ObjectT>({schema, currentValue, name}: YamlPromptSetOptions<ObjectT>, {inquire, showError}: YamlPromptDependencies): Promise<ObjectT>
export async function promptForYaml<ObjectT>({schema, currentValue, name}: YamlPromptOptions<ObjectT>, {inquire, showError}: YamlPromptDependencies): Promise<ObjectT|undefined>
export async function promptForYaml<ObjectT>({schema, currentValue, name}: YamlPromptOptions<ObjectT>, {inquire, showError}: YamlPromptDependencies): Promise<ObjectT|undefined> {
const original = dump(currentValue)
let text = original
while (true) {

Loading…
Cancel
Save