WIP CLI
This commit is contained in:
@@ -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>;
|
||||||
|
@@ -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)]
|
||||||
|
|
||||||
)
|
)
|
@@ -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;
|
||||||
|
}
|
||||||
|
@@ -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)
|
Reference in New Issue
Block a user