Skip to content

Commit

Permalink
expose Argu metadata to public API
Browse files Browse the repository at this point in the history
  • Loading branch information
eiriktsarpalis committed Jun 28, 2016
1 parent e86aa53 commit 300aa38
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 30 deletions.
37 changes: 25 additions & 12 deletions src/Argu/ArgumentParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ open FSharp.Reflection
/// The Argu type generates an argument parser given a type argument
/// that is an F# discriminated union. It can then be used to parse command line arguments
/// or XML configuration.
[<NoEquality; NoComparison; Sealed; AutoSerializable(false)>]
[<NoEquality; NoComparison; AutoSerializable(false)>]
type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (argInfo : UnionArgInfo, ?programName : string, ?description : string, ?errorHandler : IExiter) =
// memoize parser generation for given template type
static let argInfoLazy = lazy(preComputeUnionArgInfo<'Template> ())
Expand Down Expand Up @@ -39,11 +39,16 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
new ArgumentParser<'Template>(argInfoLazy.Value, ?programName = programName,
?description = description, ?errorHandler = errorHandler)


/// Returns true if top-level parser
/// and false if parser defined for a subcommand
member __.IsTopLevelParser = argInfo.TryGetParent() |> Option.isNone

/// Gets the help flags specified for the CLI parser
member __.HelpFlags = argInfo.HelpParam.Flags
/// Gets the help description specified for the CLI parser
member __.HelpDescription = argInfo.HelpParam.Description
/// Gets metadata for all union cases used by parser
member __.GetArgumentCases() = argInfo.Cases |> Array.map (fun p -> p.ToArgumentCaseInfo())
/// Returns true if parser corresponds to a subcommand
member __.IsSubCommandParser = argInfo.TryGetParent() |> Option.isSome
/// If subcommand parsers, gets parent argument metadata
member __.ParentInfo = argInfo.TryGetParent() |> Option.map (fun p -> p.ToArgumentCaseInfo())
/// Gets the default error handler used by the instance
member __.ErrorHandler = errorHandler

Expand Down Expand Up @@ -150,16 +155,24 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
/// Gets the F# union tag representation for given argument
/// </summary>
/// <param name="value">Argument instance.</param>
member __.GetTag(value : 'Template) : UnionCaseInfo =
member __.GetTag(value : 'Template) : int =
argInfo.TagReader.Value (value :> obj)

/// <summary>
/// Gets argument metadata for given argument instance.
/// </summary>
/// <param name="value">Argument instance.</param>
member __.GetArgumentCaseInfo(value : 'Template) : ArgumentCaseInfo =
let tag = argInfo.TagReader.Value (value :> obj)
argInfo.Cases.[tag].UnionCaseInfo
argInfo.Cases.[tag].ToArgumentCaseInfo()

/// <summary>
/// Gets the F# union tag representation for given union case constructor
/// Gets argument metadata for given union case constructor
/// </summary>
/// <param name="ctorExpr">Quoted union case constructor.</param>
member __.GetTag(ctorExpr : Expr<'Fields -> 'Template>) : UnionCaseInfo =
expr2Uci ctorExpr
member __.GetArgumentCaseInfo(ctorExpr : Expr<'Fields -> 'Template>) : ArgumentCaseInfo =
let uci = expr2Uci ctorExpr
argInfo.Cases.[uci.Tag].ToArgumentCaseInfo()

/// <summary>
/// Prints command line syntax. Useful for generating documentation.
Expand Down Expand Up @@ -212,5 +225,5 @@ module ArgumentParserUtils =
ArgumentParser.Create<'Template>().ToParseResult(inputs)

/// gets the F# union tag representation of given argument instance
let tagOf (input : 'Template) : UnionCaseInfo =
let tagOf (input : 'Template) : int =
ArgumentParser.Create<'Template>().GetTag input
2 changes: 1 addition & 1 deletion src/Argu/PreCompute.fs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ let rec private preComputeUnionCaseArgInfo (stack : Type list) (helpParam : Help
AppSettingsName = appSettingsName
AppSettingsSeparators = appSettingsSeparators
AppSettingsSplitOptions = appSettingsSplitOptions
Usage = usageString
Description = usageString
FieldParsers = parsers
AppSettingsCSV = isAppSettingsCSV
IsMandatory = isMandatory
Expand Down
63 changes: 60 additions & 3 deletions src/Argu/Types.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Argu

open System
open FSharp.Reflection

// Attribute declarations

Expand Down Expand Up @@ -82,13 +83,13 @@ module ArguAttributes =
inherit Attribute ()
member __.Names = names

/// Sets a custom AppSettings key name.
/// Sets a custom Configuration parsing key name.
[<AttributeUsage(AttributeTargets.Property, AllowMultiple = false)>]
type CustomAppSettingsAttribute (name : string) =
inherit Attribute ()
member __.Name = name

/// Specify a custom value separator in AppSettings parameters
/// Specify a custom value separator in Configuration parsing parameters
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Property, AllowMultiple = false)>]
type AppSettingsSeparatorAttribute ([<ParamArray>] separators : string [], splitOptions : StringSplitOptions) =
inherit Attribute()
Expand All @@ -113,6 +114,7 @@ module CliPrefix =
let [<Literal>] DoubleDash = "--"

/// Source from which to parse arguments
[<Flags>]
type ParseSource =
| None = 0
| AppSettings = 1
Expand Down Expand Up @@ -167,4 +169,59 @@ type IConfigurationReader =
/// Configuration reader identifier
abstract Name : string
/// Gets value corresponding to supplied key
abstract GetValue : key:string -> string
abstract GetValue : key:string -> string

/// Argument parameter type identifier
type ArgumentType =
/// Argument specifies primitive parameters like strings or integers
| Primitive = 1
/// Argument specifies an optional parameter which is primitive
| Optional = 2
/// Argument specifies a list of parameters of specific primitive type
| List = 3
/// Argument specifies a subcommand
| SubCommand = 4

/// Union argument metadata
[<NoEquality; NoComparison>]
type ArgumentCaseInfo =
{
/// Human readable name identifier
Name : string
/// Union case reflection identifier
UnionCaseInfo : UnionCaseInfo
/// Type of argument parser
ArgumentType : ArgumentType

/// head element denotes primary command line arg
CommandLineNames : string list
/// name used in AppSettings
AppSettingsName : string option

/// Description of the parameter
Description : string

/// AppSettings parameter separator
AppSettingsSeparators : string []
/// AppSettings parameter split options
AppSettingsSplitOptions : StringSplitOptions

/// If specified, should consume remaining tokens from the CLI
IsRest : bool
/// If specified, parameter can only be at start of CLI parameters
IsFirst : bool
/// If specified, use '--param=arg' CLI parsing syntax
IsEquals1Assignment : bool
/// If specified, use '--param key=value' CLI parsing syntax
IsEquals2Assignment : bool
/// If specified, multiple parameters can be added in AppSettings in CSV form.
AppSettingsCSV : bool
/// Fails if no argument of this type is specified
IsMandatory : bool
/// Specifies that argument should be specified at most once in CLI
IsUnique : bool
/// Hide from Usage
IsHidden : bool
/// Combine AppSettings with CLI inputs
GatherAllSources : bool
}
12 changes: 6 additions & 6 deletions src/Argu/UnParsers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let printCommandLineSyntax (argInfo : UnionArgInfo) (programName : string) = str
let sorted =
argInfo.Cases
|> Seq.filter (fun aI -> not aI.IsHidden)
|> Seq.sortBy (fun aI -> not aI.IsFirst, aI.IsRest || aI.IsNested, aI.Tag)
|> Seq.sortBy (fun aI -> not aI.IsFirst, aI.IsRest || aI.Type = ArgumentType.SubCommand, aI.Tag)
|> Seq.toArray

for aI in sorted do
Expand Down Expand Up @@ -110,7 +110,7 @@ let printArgUsage (aI : UnionCaseArgInfo) = stringExpr {
yield " <options>"

yield ": "
yield aI.Usage
yield aI.Description
yield Environment.NewLine
}

Expand Down Expand Up @@ -155,7 +155,7 @@ let printUsage (argInfo : UnionArgInfo) programName (description : string option
let options, subcommands =
argInfo.Cases
|> Seq.filter (fun aI -> not aI.IsHidden)
|> Seq.partition (fun aI -> not aI.IsNested)
|> Seq.partition (fun aI -> aI.Type <> ArgumentType.SubCommand)

if options.Length > 0 || argInfo.UsesHelpParam then
yield Environment.NewLine; yield Environment.NewLine
Expand Down Expand Up @@ -273,7 +273,7 @@ let printAppSettings (argInfo : UnionArgInfo) printComments (args : 'Template li
let mkComment () =
stringExpr {
yield ' '
yield aI.Usage
yield aI.Description

match parsers |> Array.toList with
| [] -> ()
Expand All @@ -298,7 +298,7 @@ let printAppSettings (argInfo : UnionArgInfo) printComments (args : 'Template li
|> Seq.map (fun t -> fp.UnParser (t :> _))
|> String.concat aI.AppSettingsSeparators.[0]

let mkComment () = sprintf " %s : %s ..." aI.Usage fp.Description
let mkComment () = sprintf " %s : %s ..." aI.Description fp.Description

mkElem mkComment key values }

Expand All @@ -310,7 +310,7 @@ let printAppSettings (argInfo : UnionArgInfo) printComments (args : 'Template li
| None -> ""
| Some t -> fp.UnParser (t :> _)

let mkComment () = sprintf " %s : ?%s" aI.Usage fp.Description
let mkComment () = sprintf " %s : ?%s" aI.Description fp.Description

mkElem mkComment key value }

Expand Down
41 changes: 34 additions & 7 deletions src/Argu/UnionArgInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ type UnionCaseArgInfo =
AppSettingsName : string option

/// Description of the parameter
Usage : string
Description : string

/// AppSettings parameter separator
/// Configuration parsing parameter separator
AppSettingsSeparators : string []
/// AppSettings parameter split options
/// Configuration parsing split options
AppSettingsSplitOptions : StringSplitOptions

/// If specified, should consume remaining tokens from the CLI
Expand All @@ -90,7 +90,7 @@ type UnionCaseArgInfo =
IsEquals1Assignment : bool
/// If specified, use '--param key=value' CLI parsing syntax
IsEquals2Assignment : bool
/// If specified, multiple parameters can be added in AppSettings in CSV form.
/// If specified, multiple parameters can be added in Configuration in CSV form.
AppSettingsCSV : bool
/// Fails if no argument of this type is specified
IsMandatory : bool
Expand All @@ -104,8 +104,12 @@ type UnionCaseArgInfo =
with
member inline __.Tag = __.UnionCaseInfo.Tag
member inline __.NoCommandLine = __.CommandLineNames.Length = 0
member inline __.IsNested = match __.FieldParsers with NestedUnion _ -> true | _ -> false
member inline __.IsOptional = match __.FieldParsers with OptionalParam _ -> true | _ -> false
member inline __.Type =
match __.FieldParsers with
| Primitives _ -> ArgumentType.Primitive
| OptionalParam _ -> ArgumentType.Optional
| ListParam _ -> ArgumentType.List
| NestedUnion _ -> ArgumentType.SubCommand

and ParameterType =
| Primitives of FieldParserInfo []
Expand Down Expand Up @@ -167,4 +171,27 @@ type UnionParseResults =
UnrecognizedCliParams : string list
/// Usage string requested by the caller
IsUsageRequested : bool
}
}


type UnionCaseArgInfo with
member ucai.ToArgumentCaseInfo() : ArgumentCaseInfo =
{
Name = ucai.Name
ArgumentType = ucai.Type
UnionCaseInfo = ucai.UnionCaseInfo
CommandLineNames = ucai.CommandLineNames
AppSettingsName = ucai.AppSettingsName
Description = ucai.Description
AppSettingsSeparators = ucai.AppSettingsSeparators
AppSettingsSplitOptions = ucai.AppSettingsSplitOptions
IsRest = ucai.IsRest
IsFirst = ucai.IsFirst
IsEquals1Assignment = ucai.IsEquals1Assignment
IsEquals2Assignment = ucai.IsEquals2Assignment
AppSettingsCSV = ucai.AppSettingsCSV
IsMandatory = ucai.IsMandatory
IsUnique = ucai.IsUnique
IsHidden = ucai.IsHidden
GatherAllSources = ucai.GatherAllSources
}
2 changes: 1 addition & 1 deletion tests/Argu.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ module ``Argu Tests`` =

[<Fact>]
let ``Simple AppSettings parsing`` () =
let args = [ Mandatory_Arg true ; Detach ; Listener ("localhost", 8080) ; Log_Level 2 ] |> List.sortBy (fun u -> tagOf(u).Tag)
let args = [ Mandatory_Arg true ; Detach ; Listener ("localhost", 8080) ; Log_Level 2 ] |> List.sortBy tagOf
let xmlSource = parser.PrintAppSettings args
let xmlFile = Path.GetTempFileName()
do File.WriteAllText(xmlFile, xmlSource)
Expand Down

0 comments on commit 300aa38

Please sign in to comment.