Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type Support Beyond Primitives #43

Closed
cloudRoutine opened this issue Dec 6, 2015 · 6 comments
Closed

Type Support Beyond Primitives #43

cloudRoutine opened this issue Dec 6, 2015 · 6 comments
Labels

Comments

@cloudRoutine
Copy link
Member

I hacked together a workaround of the restriction that only primitive types can be used in the argument parser so that any type 'T with the static member Parse:string->'T would be viable.

It lets you write arg parsers like :

type BOOM (str) =
    override __.ToString() = str
    static member Parse str =
        match str:string with
        | "boom" | "Boom" -> BOOM str
        | "BOOM" -> BOOM "GOES THE DYNAMITE"
        | _      -> failwith "can't go BOOM"

type POW (str) =
    override __.ToString() = str
    static member Parse str =
        match str:string with
        | "pow" | "Pow" | "POW" -> POW str
        | "explode" -> POW "MUSIC MAKES YOU LOSE CONTROL"
        | _     -> failwith "got no POW"


let trickyDict = 
    parserDict [ 
        mkComplexParser<BOOM> () 
        mkComplexParser<POW> () 
    ]

type CLIArguments =
    | Listener of host:string * port:int
    | Detach | Boom of BOOM | Pow of POW 
with 
    interface IArgParserTemplate with
        member s.Usage =
            match s with
            | Listener _ -> "specify a listener (hostname : port)."
            | Detach _   -> "detach daemon from console."
            | Boom _     -> "when I say boom"
            | Pow _      -> "you say pow"

let parser  = ArgumentParser.Create<CLIArguments>(parserDict=trickyDict)
let usage   = parser.Usage() |> printfn "%A"
let results = parser.Parse([| "--detach" ; "--listener" ; "localhost" ; "8080"; "--pow";"explode";"--boom";"BOOM"|]);;

results.GetAllResults() |> List.iter (printfn "%A")
printfn "%A" <| results.Contains <@ Boom @>;;
(*
    > Listener ("localhost",8080)
    Detach
    Boom GOES THE DYNAMITE
    Pow MUSIC MAKES YOU LOSE CONTROL
    true
*)

let results' = parser.Parse([| "--detach" ; "--listener" ; "localhost" ; "8080"; "--pow";"pow";"--boom";"boom"|])
results'.GetAllResults() |> List.iter (printfn "%A");;
(*
    >
    Listener ("localhost",8080)
    Detach
    Boom boom
    Pow pow
*)

Here's a gist with a rough overview of what I changed to implement this

I was hoping for some feedback about whether you're open to adding this functionality before I submitted a PR.

If you're open to this addition, is there a different type of implementation you'd prefer? I had to change a bunch of access restrictions to make ParserInfo available and I wasn't sure if there was some important reason why it shouldn't be exposed that I overlooked.

( the actual PR won't be this silly 😉 )

@eiriktsarpalis
Copy link
Member

I'd be in favor of implementing something like that, but the design certainly needs polish.
Why not just use interfaces, i.e.

type ICustomParser = interface end

type ICustomParser<'T> =
    inherit ICustomPrimitive
    abstract Parse : string -> 'T
    abstract UnParse : 'T -> string

Then just have the argparser take a list of the base interface. The UnParse implementation is needed in order to fully support the PrintCommandLine method. Thoughts?

@cloudRoutine
Copy link
Member Author

I used string for the unparse in

let inline mkComplexParser< ^a when ^a:(static member Parse:string -> ^a)>() = 
    (typeof< ^a>, ParserInfo.Create< ^a> (typeof< ^a>.Name) (fun str -> 
        (^a:(static member Parse:string -> ^a) str)) string)

I used the static method for parse as it seemed like that was the common contract you were already using, and there are a bunch of other types (Guids, Uids, TimeSpans, IP Addresses, enums, etc) that share that signature.

If you've going to deviate from that I think you should go even further and make parser combinators. At the very least to support verbs and subparsers, they're the most important feature Argu is missing. Ideally the functionality of the combinators could extend into formatting transformations, different modes of execution, more nuanced help displays, color modulation, minor animation, etc. (or maybe I love FParsec a bit too much)

Are you open to supporting file formats besides XML for config files? It'd to be able to use Argu with Toml configs..

Just out of curiosity, do you think it'd be feasible to modify Argu so that it could generate a CLI for cmd as well as a posh (powershell) cmdlet with similar usage conventions? I'm not in love with posh, but I don't mind writing out cmdlets in F#, and it might be fun to use on some Nano Servers.

I'm down to help you take the implementation/functionality as far as you want to run with it.

@eiriktsarpalis
Copy link
Member

There are many features missing from Argu and the codebase is arguably stale. I'm currently not very motivated in contributing improvements to the library, so I'd be positive to someone else taking over development. Perhaps this would be facilitated by a move to fsprojects.

@cloudRoutine
Copy link
Member Author

probably, I've talked to a bunch of people who were interested in it, but wished it had a fuller feature set.

@cloudRoutine
Copy link
Member Author

@eiriktsarpalis have you decided whether you want to move it to fsprojects?

@eiriktsarpalis
Copy link
Member

Yeah, we're up for it. We just need to find a couple of maintainer outside of nessos and make the move.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants