WIP CLI
This commit is contained in:
@@ -3,21 +3,26 @@ package nat;
|
||||
import kiss.Prelude;
|
||||
import kiss.List;
|
||||
import kiss.Operand;
|
||||
|
||||
import haxe.Constraints;
|
||||
|
||||
enum CommandArgument {
|
||||
enum CommandArgType {
|
||||
SelectedEntry;
|
||||
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>);
|
||||
Entry;
|
||||
OneEntry; // This constructor must be disambiguated from the typedef "Entry"
|
||||
Entries(min:Null<Int>, max:Null<Int>);
|
||||
}
|
||||
|
||||
typedef CommandArg = {
|
||||
name:String,
|
||||
type:CommandArgType
|
||||
};
|
||||
|
||||
typedef Command = {
|
||||
args:Array<CommandArgument>,
|
||||
args:Array<CommandArg>,
|
||||
handler:Function
|
||||
// Command handlers need to return a ChangeSet
|
||||
};
|
||||
|
||||
typedef ChangeSet = Array<Entry>;
|
||||
|
@@ -4,9 +4,115 @@
|
||||
(defmethod selectEntries [:Array<Entry> 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
|
||||
&prop :ArchiveUI ui]
|
||||
[&mut :Array<Entry> selectedEntries []
|
||||
&mut :ChangeSet lastChangeSet []
|
||||
:Map<String,Command> commands (new Map)]
|
||||
|
||||
)
|
@@ -6,25 +6,30 @@ interface ArchiveUI {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
function chooseEntry(archive:Archive):Entry;
|
||||
function chooseEntry(prompt:String, archive:Archive, resolve:(Entry) -> Void):Void;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function handleChanges(changeSet:ChangeSet):Void;
|
||||
|
||||
/**
|
||||
* Tell the user that something is wrong
|
||||
*/
|
||||
function reportError(error:String):Void;
|
||||
}
|
@@ -4,18 +4,38 @@
|
||||
(new ArchiveController
|
||||
(new Archive archiveDir)
|
||||
(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]
|
||||
0)
|
||||
(defmethod :Void enterText [prompt resolve &opt minLength maxLength]
|
||||
(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]
|
||||
null)
|
||||
(defmethod :Void enterNumber [prompt resolve &opt min max inStepsOf]
|
||||
(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