Skip to content

Commit 300aa38

Browse files
expose Argu metadata to public API
1 parent e86aa53 commit 300aa38

6 files changed

Lines changed: 127 additions & 30 deletions

File tree

src/Argu/ArgumentParser.fs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open FSharp.Reflection
1010
/// The Argu type generates an argument parser given a type argument
1111
/// that is an F# discriminated union. It can then be used to parse command line arguments
1212
/// or XML configuration.
13-
[<NoEquality; NoComparison; Sealed; AutoSerializable(false)>]
13+
[<NoEquality; NoComparison; AutoSerializable(false)>]
1414
type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (argInfo : UnionArgInfo, ?programName : string, ?description : string, ?errorHandler : IExiter) =
1515
// memoize parser generation for given template type
1616
static let argInfoLazy = lazy(preComputeUnionArgInfo<'Template> ())
@@ -39,11 +39,16 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
3939
new ArgumentParser<'Template>(argInfoLazy.Value, ?programName = programName,
4040
?description = description, ?errorHandler = errorHandler)
4141

42-
43-
/// Returns true if top-level parser
44-
/// and false if parser defined for a subcommand
45-
member __.IsTopLevelParser = argInfo.TryGetParent() |> Option.isNone
46-
42+
/// Gets the help flags specified for the CLI parser
43+
member __.HelpFlags = argInfo.HelpParam.Flags
44+
/// Gets the help description specified for the CLI parser
45+
member __.HelpDescription = argInfo.HelpParam.Description
46+
/// Gets metadata for all union cases used by parser
47+
member __.GetArgumentCases() = argInfo.Cases |> Array.map (fun p -> p.ToArgumentCaseInfo())
48+
/// Returns true if parser corresponds to a subcommand
49+
member __.IsSubCommandParser = argInfo.TryGetParent() |> Option.isSome
50+
/// If subcommand parsers, gets parent argument metadata
51+
member __.ParentInfo = argInfo.TryGetParent() |> Option.map (fun p -> p.ToArgumentCaseInfo())
4752
/// Gets the default error handler used by the instance
4853
member __.ErrorHandler = errorHandler
4954

@@ -150,16 +155,24 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
150155
/// Gets the F# union tag representation for given argument
151156
/// </summary>
152157
/// <param name="value">Argument instance.</param>
153-
member __.GetTag(value : 'Template) : UnionCaseInfo =
158+
member __.GetTag(value : 'Template) : int =
159+
argInfo.TagReader.Value (value :> obj)
160+
161+
/// <summary>
162+
/// Gets argument metadata for given argument instance.
163+
/// </summary>
164+
/// <param name="value">Argument instance.</param>
165+
member __.GetArgumentCaseInfo(value : 'Template) : ArgumentCaseInfo =
154166
let tag = argInfo.TagReader.Value (value :> obj)
155-
argInfo.Cases.[tag].UnionCaseInfo
167+
argInfo.Cases.[tag].ToArgumentCaseInfo()
156168

157169
/// <summary>
158-
/// Gets the F# union tag representation for given union case constructor
170+
/// Gets argument metadata for given union case constructor
159171
/// </summary>
160172
/// <param name="ctorExpr">Quoted union case constructor.</param>
161-
member __.GetTag(ctorExpr : Expr<'Fields -> 'Template>) : UnionCaseInfo =
162-
expr2Uci ctorExpr
173+
member __.GetArgumentCaseInfo(ctorExpr : Expr<'Fields -> 'Template>) : ArgumentCaseInfo =
174+
let uci = expr2Uci ctorExpr
175+
argInfo.Cases.[uci.Tag].ToArgumentCaseInfo()
163176

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

214227
/// gets the F# union tag representation of given argument instance
215-
let tagOf (input : 'Template) : UnionCaseInfo =
228+
let tagOf (input : 'Template) : int =
216229
ArgumentParser.Create<'Template>().GetTag input

src/Argu/PreCompute.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ let rec private preComputeUnionCaseArgInfo (stack : Type list) (helpParam : Help
273273
AppSettingsName = appSettingsName
274274
AppSettingsSeparators = appSettingsSeparators
275275
AppSettingsSplitOptions = appSettingsSplitOptions
276-
Usage = usageString
276+
Description = usageString
277277
FieldParsers = parsers
278278
AppSettingsCSV = isAppSettingsCSV
279279
IsMandatory = isMandatory

src/Argu/Types.fs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace Argu
22

33
open System
4+
open FSharp.Reflection
45

56
// Attribute declarations
67

@@ -82,13 +83,13 @@ module ArguAttributes =
8283
inherit Attribute ()
8384
member __.Names = names
8485

85-
/// Sets a custom AppSettings key name.
86+
/// Sets a custom Configuration parsing key name.
8687
[<AttributeUsage(AttributeTargets.Property, AllowMultiple = false)>]
8788
type CustomAppSettingsAttribute (name : string) =
8889
inherit Attribute ()
8990
member __.Name = name
9091

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

115116
/// Source from which to parse arguments
117+
[<Flags>]
116118
type ParseSource =
117119
| None = 0
118120
| AppSettings = 1
@@ -167,4 +169,59 @@ type IConfigurationReader =
167169
/// Configuration reader identifier
168170
abstract Name : string
169171
/// Gets value corresponding to supplied key
170-
abstract GetValue : key:string -> string
172+
abstract GetValue : key:string -> string
173+
174+
/// Argument parameter type identifier
175+
type ArgumentType =
176+
/// Argument specifies primitive parameters like strings or integers
177+
| Primitive = 1
178+
/// Argument specifies an optional parameter which is primitive
179+
| Optional = 2
180+
/// Argument specifies a list of parameters of specific primitive type
181+
| List = 3
182+
/// Argument specifies a subcommand
183+
| SubCommand = 4
184+
185+
/// Union argument metadata
186+
[<NoEquality; NoComparison>]
187+
type ArgumentCaseInfo =
188+
{
189+
/// Human readable name identifier
190+
Name : string
191+
/// Union case reflection identifier
192+
UnionCaseInfo : UnionCaseInfo
193+
/// Type of argument parser
194+
ArgumentType : ArgumentType
195+
196+
/// head element denotes primary command line arg
197+
CommandLineNames : string list
198+
/// name used in AppSettings
199+
AppSettingsName : string option
200+
201+
/// Description of the parameter
202+
Description : string
203+
204+
/// AppSettings parameter separator
205+
AppSettingsSeparators : string []
206+
/// AppSettings parameter split options
207+
AppSettingsSplitOptions : StringSplitOptions
208+
209+
/// If specified, should consume remaining tokens from the CLI
210+
IsRest : bool
211+
/// If specified, parameter can only be at start of CLI parameters
212+
IsFirst : bool
213+
/// If specified, use '--param=arg' CLI parsing syntax
214+
IsEquals1Assignment : bool
215+
/// If specified, use '--param key=value' CLI parsing syntax
216+
IsEquals2Assignment : bool
217+
/// If specified, multiple parameters can be added in AppSettings in CSV form.
218+
AppSettingsCSV : bool
219+
/// Fails if no argument of this type is specified
220+
IsMandatory : bool
221+
/// Specifies that argument should be specified at most once in CLI
222+
IsUnique : bool
223+
/// Hide from Usage
224+
IsHidden : bool
225+
/// Combine AppSettings with CLI inputs
226+
GatherAllSources : bool
227+
}

src/Argu/UnParsers.fs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let printCommandLineSyntax (argInfo : UnionArgInfo) (programName : string) = str
2121
let sorted =
2222
argInfo.Cases
2323
|> Seq.filter (fun aI -> not aI.IsHidden)
24-
|> Seq.sortBy (fun aI -> not aI.IsFirst, aI.IsRest || aI.IsNested, aI.Tag)
24+
|> Seq.sortBy (fun aI -> not aI.IsFirst, aI.IsRest || aI.Type = ArgumentType.SubCommand, aI.Tag)
2525
|> Seq.toArray
2626

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

112112
yield ": "
113-
yield aI.Usage
113+
yield aI.Description
114114
yield Environment.NewLine
115115
}
116116

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

160160
if options.Length > 0 || argInfo.UsesHelpParam then
161161
yield Environment.NewLine; yield Environment.NewLine
@@ -273,7 +273,7 @@ let printAppSettings (argInfo : UnionArgInfo) printComments (args : 'Template li
273273
let mkComment () =
274274
stringExpr {
275275
yield ' '
276-
yield aI.Usage
276+
yield aI.Description
277277

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

301-
let mkComment () = sprintf " %s : %s ..." aI.Usage fp.Description
301+
let mkComment () = sprintf " %s : %s ..." aI.Description fp.Description
302302

303303
mkElem mkComment key values }
304304

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

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

315315
mkElem mkComment key value }
316316

src/Argu/UnionArgInfo.fs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ type UnionCaseArgInfo =
7575
AppSettingsName : string option
7676

7777
/// Description of the parameter
78-
Usage : string
78+
Description : string
7979

80-
/// AppSettings parameter separator
80+
/// Configuration parsing parameter separator
8181
AppSettingsSeparators : string []
82-
/// AppSettings parameter split options
82+
/// Configuration parsing split options
8383
AppSettingsSplitOptions : StringSplitOptions
8484

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

110114
and ParameterType =
111115
| Primitives of FieldParserInfo []
@@ -167,4 +171,27 @@ type UnionParseResults =
167171
UnrecognizedCliParams : string list
168172
/// Usage string requested by the caller
169173
IsUsageRequested : bool
170-
}
174+
}
175+
176+
177+
type UnionCaseArgInfo with
178+
member ucai.ToArgumentCaseInfo() : ArgumentCaseInfo =
179+
{
180+
Name = ucai.Name
181+
ArgumentType = ucai.Type
182+
UnionCaseInfo = ucai.UnionCaseInfo
183+
CommandLineNames = ucai.CommandLineNames
184+
AppSettingsName = ucai.AppSettingsName
185+
Description = ucai.Description
186+
AppSettingsSeparators = ucai.AppSettingsSeparators
187+
AppSettingsSplitOptions = ucai.AppSettingsSplitOptions
188+
IsRest = ucai.IsRest
189+
IsFirst = ucai.IsFirst
190+
IsEquals1Assignment = ucai.IsEquals1Assignment
191+
IsEquals2Assignment = ucai.IsEquals2Assignment
192+
AppSettingsCSV = ucai.AppSettingsCSV
193+
IsMandatory = ucai.IsMandatory
194+
IsUnique = ucai.IsUnique
195+
IsHidden = ucai.IsHidden
196+
GatherAllSources = ucai.GatherAllSources
197+
}

tests/Argu.Tests/Tests.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ module ``Argu Tests`` =
9393

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

0 commit comments

Comments
 (0)