This commit is contained in:
2021-06-23 17:21:03 -06:00
parent 34875a277a
commit 1602b873bc
4 changed files with 156 additions and 20 deletions

View File

@@ -3,21 +3,26 @@ package nat;
import kiss.Prelude; import kiss.Prelude;
import kiss.List; import kiss.List;
import kiss.Operand; import kiss.Operand;
import haxe.Constraints; import haxe.Constraints;
enum CommandArgument { enum CommandArgType {
SelectedEntry; SelectedEntry;
SelectedEntries(min:Null<Int>, max:Null<Int>); SelectedEntries(min:Null<Int>, max:Null<Int>);
Text(minLength:Null<Int>, maxLength:Null<Int>); Text(minLength:Null<Int>, maxLength:Null<Float>); // max length is a float so Math.POSITIVE_INFINITY can be used
Number(min:Null<Float>, max:Null<Float>, inStepsOf:Null<Float>); Number(min:Null<Float>, max:Null<Float>, inStepsOf:Null<Float>);
Entry; OneEntry; // This constructor must be disambiguated from the typedef "Entry"
Entries(min:Null<Int>, max:Null<Int>); Entries(min:Null<Int>, max:Null<Int>);
} }
typedef CommandArg = {
name:String,
type:CommandArgType
};
typedef Command = { typedef Command = {
args:Array<CommandArgument>, args:Array<CommandArg>,
handler:Function handler:Function
// Command handlers need to return a ChangeSet
}; };
typedef ChangeSet = Array<Entry>; typedef ChangeSet = Array<Entry>;

View File

@@ -4,9 +4,115 @@
(defmethod selectEntries [:Array<Entry> e] (defmethod selectEntries [:Array<Entry> e]
(set selectedEntries e)) (set selectedEntries e))
(defmethod selectLastChangeSet []
(set selectedEntries lastChangeSet))
(defmethod :Void collectAndValidateArg [:CommandArg arg :Dynamic->Void continuation]
(case arg.type
(SelectedEntry
(if (= 1 selectedEntries.length)
(continuation (first selectedEntries))
(ui.reportError "The requested command expects 1 entry to be selected. You have selected: $selectedEntries.length")))
((SelectedEntries min max)
(unless min (set min 0))
// TODO might want to optimize this O(n) count operation by pre-calculating it
(unless max (set max (count archive.entries)))
(if !(<= min selectedEntries.length max)
(ui.reportError "The requested command expects between $min and $max entries to be selected. You have selected: $selectedEntries.length")
(continuation selectedEntries)))
((Text minLength maxLength)
(unless minLength (set minLength 0))
(unless maxLength (set maxLength Math.POSITIVE_INFINITY))
(ui.enterText
"${arg.name} (${minLength}-${maxLength} characters):"
(lambda :Void [text]
(if !(<= minLength text.length maxLength)
(ui.reportError "The requested command expected a string between $minLength and $maxLength characters long. You entered: $text.length characters")
(continuation text)))
minLength
maxLength))
((Number min max inStepsOf)
(unless min (set min Math.NEGATIVE_INFINITY))
(unless max (set max Math.POSITIVE_INFINITY))
(let [&mut prompt "${arg.name} (${min}-${max}"]
(when inStepsOf
(+= prompt " in steps of ${inStepsOf}"))
(+= prompt "):")
(ui.enterNumber
prompt
(lambda :Void [number]
(let [minMaxError
"The requested command expected a number between $min and $max"
stepError
"$minMaxError in steps of $inStepsOf"
youEntered
". You entered: $number"]
(if (or
!(<= min number max)
(and inStepsOf !(= 0 (% (- number min) inStepsOf))))
(if inStepsOf
(ui.reportError "${stepError}$youEntered")
(ui.reportError "${minMaxError}$youEntered"))
(continuation number))))
min
max
inStepsOf)))
(OneEntry
(ui.chooseEntry
"${arg.name}:"
archive
continuation))
((Entries min max)
(unless min (set min 0))
// TODO might want to optimize this O(n) count operation by pre-calculating it
(unless max (set max (count archive.entries)))
(ui.chooseEntries
"${arg.name}:"
archive
(lambda :Void [:Array<Entry> entries]
(if (or
(> min entries.length)
(< max entries.length))
(ui.reportError "The requested command expects between $min and $max entries. You chose: $entries.length")
(continuation selectedEntries)))))))
/*(defmethod :Void runCommand [command]
(let [collectedArgs
[]
callHandler
(lambda []
(set lastChangeSet (the ChangeSet (Reflect.callMethod command.handler collectedArgs)))
(ui.handleChanges lastChangeSet))])
// If the command has no arguments, it can run synchronously with no hassle
(if !command.args
(callHandler)
// To facilitate asynchronous arg input via UI, we need to construct an insanely complicated nested callback to give the UI
(let [argsToCollect
(reversed command.args)
composeArgCollector
(lambda :Void->Void [:CommandArg arg :Void->Void lastResolver]
(lambda :Void []
(case arg.type
((SelectedEntry
(ui.chooseEntry arg.prompt )))
(collectedArgs.push arg)
(lastResolver)
))
resolve (lambda [:Dynamic arg]
())
])
(doFor arg (reversed command.args)
)
)
)*/
(defnew [&prop :Archive archive (defnew [&prop :Archive archive
&prop :ArchiveUI ui] &prop :ArchiveUI ui]
[&mut :Array<Entry> selectedEntries [] [&mut :Array<Entry> selectedEntries []
&mut :ChangeSet lastChangeSet []
:Map<String,Command> commands (new Map)] :Map<String,Command> commands (new Map)]
) )

View File

@@ -6,25 +6,30 @@ interface ArchiveUI {
/** /**
* Prompt the user to enter text * Prompt the user to enter text
*/ */
function enterText(?minLength:Int, ?maxLength:Int):String; function enterText(prompt:String, resolve:(String) -> Void, ?minLength:Int, ?maxLength:Float):Void;
/** /**
* Prompt the user to enter a number * Prompt the user to enter a number
*/ */
function enterNumber(?min:Float, ?max:Float, ?inStepsOf:Float):Float; function enterNumber(prompt:String, resolve:(Float) -> Void, ?min:Float, ?max:Float, ?inStepsOf:Float):Void;
/** /**
* Prompt the user to choose a single Entry * Prompt the user to choose a single Entry
*/ */
function chooseEntry(archive:Archive):Entry; function chooseEntry(prompt:String, archive:Archive, resolve:(Entry) -> Void):Void;
/** /**
* Prompt the user to choose multiple Entries * Prompt the user to choose multiple Entries
*/ */
function chooseEntries(archive:Archive, ?min:Int, ?max:Int):Array<Entry>; function chooseEntries(prompt:String, archive:Archive, resolve:(Array<Entry>) -> Void, ?min:Int, ?max:Float):Void;
/** /**
* Update the interface to reflect changes made to Entries through commands * Update the interface to reflect changes made to Entries through commands
*/ */
function handleChanges(changeSet:ChangeSet):Void; function handleChanges(changeSet:ChangeSet):Void;
}
/**
* Tell the user that something is wrong
*/
function reportError(error:String):Void;
}

View File

@@ -4,18 +4,38 @@
(new ArchiveController (new ArchiveController
(new Archive archiveDir) (new Archive archiveDir)
(new CLI))] (new CLI))]
)) (controller.collectAndValidateArg (object name "numerical" type (Number -5 5 1)) ->:Void val ~val)
(controller.collectAndValidateArg (object name "string" type (Text 5 null)) ->:Void val ~val)))
(defmethod enterText [&opt minLength maxLength] (defnew [])
"")
(defmethod enterNumber [&opt min max inStepsOf] (defmethod :Void enterText [prompt resolve &opt minLength maxLength]
0) (Sys.print "$prompt ")
(loop
(let [entered (.toString (.readLine (Sys.stdin)))]
(if !(<= minLength entered.length maxLength)
(Sys.print "Try again? ")
{(resolve entered)
(break)}))))
(defmethod chooseEntry [archive] (defmethod :Void enterNumber [prompt resolve &opt min max inStepsOf]
null) (Sys.print "$prompt ")
(loop
(let [entered (Std.parseFloat (.toString (.readLine (Sys.stdin))))]
(if
(or
!(<= min entered max)
(and inStepsOf !(= 0 (% (- entered min) inStepsOf))))
(Sys.print "Try again? ")
{(resolve entered)
(break)}))))
(defmethod chooseEntries [archive &opt min max] (defmethod :Void chooseEntry [prompt archive resolve]
[]) (resolve null))
(defmethod :Void handleChanges [changeSet] 0) (defmethod :Void chooseEntries [prompt archive resolve &opt min max]
(resolve []))
(defmethod handleChanges [changeSet])
(defmethod :Void reportError [error] ~error)