Skip to content

Commit 588bfee

Browse files
usage string improvements
1 parent 300aa38 commit 588bfee

13 files changed

Lines changed: 180 additions & 144 deletions

File tree

docs/content/tutorial.fsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ let parser = ArgumentParser.Create<CLIArguments>(programName = "gadget.exe")
8787

8888
(** We can get the automatically generated usage string by typing *)
8989

90-
let usage = parser.Usage()
90+
let usage = parser.PrintUsage()
9191

9292
(** giving
9393
@@ -204,15 +204,15 @@ Argu is convenient when it comes to automated process spawning:
204204

205205
open System.Diagnostics
206206

207-
let arguments = parser.PrintCommandLineFlat [ Port 42 ; Working_Directory "temp" ]
207+
let arguments = parser.PrintCommandLineArgumentsFlat [ Port 42 ; Working_Directory "temp" ]
208208

209209
Process.Start("foo.exe", arguments)
210210

211211
(**
212212
It can also be used to auto-generate a suitable `AppSettings` configuration file:
213213
*)
214214

215-
let xml = parser.PrintAppSettings [ Port 42 ; Working_Directory "/tmp" ]
215+
let xml = parser.PrintAppSettingsArguments [ Port 42 ; Working_Directory "/tmp" ]
216216

217217
(**
218218
which would yield the following:

src/Argu/Argu.fsproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@
6565
<Compile Include="AssemblyInfo.fs" />
6666
<Compile Include="Types.fs" />
6767
<Compile Include="Utils.fs" />
68+
<Compile Include="ConfigReaders.fs" />
6869
<Compile Include="UnionArgInfo.fs" />
69-
<Compile Include="ParseResult.fs" />
7070
<Compile Include="PreCompute.fs" />
7171
<Compile Include="UnParsers.fs" />
72+
<Compile Include="ParseResult.fs" />
7273
<Compile Include="Parsers.fs" />
73-
<Compile Include="ConfigReaders.fs" />
7474
<Compile Include="ArgumentParser.fs" />
7575
<None Include="paket.template" />
7676
</ItemGroup>

src/Argu/ArgumentParser.fs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,37 @@ open FSharp.Reflection
1111
/// that is an F# discriminated union. It can then be used to parse command line arguments
1212
/// or XML configuration.
1313
[<NoEquality; NoComparison; AutoSerializable(false)>]
14-
type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (argInfo : UnionArgInfo, ?programName : string, ?description : string, ?errorHandler : IExiter) =
14+
type ArgumentParser<'Template when 'Template :> IArgParserTemplate>
15+
internal (argInfo : UnionArgInfo, ?programName : string, ?description : string,
16+
?usageStringCharacterWidth : int, ?errorHandler : IExiter) =
17+
1518
// memoize parser generation for given template type
1619
static let argInfoLazy = lazy(preComputeUnionArgInfo<'Template> ())
1720

21+
let _usageStringCharacterWidth = defaultArg usageStringCharacterWidth 80
1822
let _programName = match programName with Some pn -> pn | None -> currentProgramName.Value
1923
let errorHandler = match errorHandler with Some e -> e | None -> new ExceptionExiter() :> _
2024

21-
let mkUsageString argInfo msgOpt = printUsage argInfo _programName description msgOpt |> String.build
22-
25+
let mkUsageString argInfo msgOpt = printUsage argInfo _programName _usageStringCharacterWidth msgOpt |> StringExpr.build
2326

2427
let (|ParserExn|_|) (e : exn) =
2528
match e with
2629
// do not display usage for App.Config parameter errors
27-
| ParseError (msg, id, _) when id <> ErrorCode.CommandLine -> Some(id, msg)
30+
| ParseError (msg, ErrorCode.AppSettings, _) -> Some(ErrorCode.AppSettings, msg)
2831
| ParseError (msg, id, aI) -> Some (id, mkUsageString aI (Some msg))
29-
| HelpText aI -> Some (ErrorCode.HelpText, mkUsageString aI None)
32+
| HelpText aI -> Some (ErrorCode.HelpText, mkUsageString aI description)
3033
| _ -> None
3134

3235
/// <summary>
3336
/// Creates a new parser instance based on supplied F# union template.
3437
/// </summary>
3538
/// <param name="programName">Program identifier, e.g. 'cat'. Defaults to the current executable name.</param>
3639
/// <param name="description">Program description placed at the top of the usage string.</param>
40+
/// <param name="usageStringCharacterWidth">Text width used when formatting the usage string. Defaults to 80 chars.</param>
3741
/// <param name="errorHandler">The implementation of IExiter used for error handling. Exception is default.</param>
38-
new (?programName : string, ?description : string, ?errorHandler : IExiter) =
42+
new (?programName : string, ?description : string, ?usageStringCharacterWidth : int, ?errorHandler : IExiter) =
3943
new ArgumentParser<'Template>(argInfoLazy.Value, ?programName = programName,
40-
?description = description, ?errorHandler = errorHandler)
44+
?usageStringCharacterWidth = usageStringCharacterWidth, ?description = description, ?errorHandler = errorHandler)
4145

4246
/// Gets the help flags specified for the CLI parser
4347
member __.HelpFlags = argInfo.HelpParam.Flags
@@ -64,11 +68,11 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
6468
let inputs = match inputs with None -> getEnvironmentCommandLineArgs () | Some args -> args
6569

6670
try
67-
let cliResults = parseCommandLine argInfo _programName description errorHandler raiseOnUsage ignoreUnrecognized inputs
71+
let cliResults = parseCommandLine argInfo _programName description _usageStringCharacterWidth errorHandler raiseOnUsage ignoreUnrecognized inputs
6872
let ignoreMissing = (cliResults.IsUsageRequested && not raiseOnUsage) || ignoreMissing
6973
let results = postProcessResults argInfo ignoreMissing None (Some cliResults)
7074

71-
new ParseResult<'Template>(argInfo, results, mkUsageString argInfo, errorHandler)
75+
new ParseResult<'Template>(argInfo, results, _programName, description, _usageStringCharacterWidth, errorHandler)
7276

7377
with ParserExn (errorCode, msg) -> errorHandler.Exit (msg, errorCode)
7478

@@ -82,7 +86,7 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
8286
let appSettingsResults = parseKeyValueConfig configurationReader argInfo
8387
let results = postProcessResults argInfo ignoreMissing (Some appSettingsResults) None
8488

85-
new ParseResult<'Template>(argInfo, results, mkUsageString argInfo, errorHandler)
89+
new ParseResult<'Template>(argInfo, results, _programName, description, _usageStringCharacterWidth, errorHandler)
8690

8791
with ParserExn (errorCode, msg) -> errorHandler.Exit (msg, errorCode)
8892

@@ -102,10 +106,10 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
102106

103107
try
104108
let appSettingsResults = parseKeyValueConfig configurationReader argInfo
105-
let cliResults = parseCommandLine argInfo _programName description errorHandler raiseOnUsage ignoreUnrecognized inputs
109+
let cliResults = parseCommandLine argInfo _programName description _usageStringCharacterWidth errorHandler raiseOnUsage ignoreUnrecognized inputs
106110
let results = postProcessResults argInfo ignoreMissing (Some appSettingsResults) (Some cliResults)
107111

108-
new ParseResult<'Template>(argInfo, results, mkUsageString argInfo, errorHandler)
112+
new ParseResult<'Template>(argInfo, results, _programName, description, _usageStringCharacterWidth, errorHandler)
109113

110114
with ParserExn (errorCode, msg) -> errorHandler.Exit (msg, errorCode)
111115

@@ -134,11 +138,7 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
134138
/// </summary>
135139
/// <param name="inputs">Argument input sequence.</param>
136140
member __.ToParseResult (inputs : seq<'Template>) : ParseResult<'Template> =
137-
mkParseResultFromValues argInfo errorHandler (mkUsageString argInfo) inputs
138-
139-
/// <summary>Returns the usage string.</summary>
140-
/// <param name="message">The message to be displayed on top of the usage string.</param>
141-
member __.Usage (?message : string) : string = mkUsageString argInfo message
141+
mkParseResultFromValues argInfo errorHandler _usageStringCharacterWidth _programName description inputs
142142

143143
/// <summary>
144144
/// Gets a subparser associated with specific subcommand instance
@@ -174,26 +174,37 @@ type ArgumentParser<'Template when 'Template :> IArgParserTemplate> private (arg
174174
let uci = expr2Uci ctorExpr
175175
argInfo.Cases.[uci.Tag].ToArgumentCaseInfo()
176176

177+
/// <summary>Formats a usage string for the argument parser.</summary>
178+
/// <param name="message">The message to be displayed on top of the usage string.</param>
179+
/// <param name="programName">Override the default program name settings.</param>
180+
/// <param name="usageStringCharacterWidth">Text width used when formatting the usage string.</param>
181+
member __.PrintUsage (?message : string, ?programName : string, ?usageStringCharacterWidth : int) : string =
182+
let programName = defaultArg programName _programName
183+
let usageStringCharacterWidth = defaultArg usageStringCharacterWidth _usageStringCharacterWidth
184+
printUsage argInfo programName usageStringCharacterWidth message |> StringExpr.build
185+
177186
/// <summary>
178187
/// Prints command line syntax. Useful for generating documentation.
179188
/// </summary>
180189
/// <param name="programName">Program name identifier placed at start of syntax string</param>
181-
member __.PrintCommandLineSyntax (?programName : string) : string =
190+
/// <param name="usageStringCharacterWidth">Text width used when formatting the usage string.</param>
191+
member __.PrintCommandLineSyntax (?programName : string, ?usageStringCharacterWidth : int) : string =
182192
let programName = defaultArg programName _programName
183-
printCommandLineSyntax argInfo programName |> String.build
193+
let usageStringCharacterWidth = defaultArg usageStringCharacterWidth _usageStringCharacterWidth
194+
printCommandLineSyntax argInfo "" usageStringCharacterWidth programName |> StringExpr.build
184195

185196
/// <summary>Prints parameters in command line format. Useful for argument string generation.</summary>
186-
member __.PrintCommandLine (args : 'Template list) : string [] =
197+
member __.PrintCommandLineArguments (args : 'Template list) : string [] =
187198
printCommandLineArgs argInfo (Seq.cast args) |> Seq.toArray
188199

189200
/// <summary>Prints parameters in command line format. Useful for argument string generation.</summary>
190-
member __.PrintCommandLineFlat (args : 'Template list) : string =
191-
__.PrintCommandLine args |> flattenCliTokens
201+
member __.PrintCommandLineArgumentsFlat (args : 'Template list) : string =
202+
__.PrintCommandLineArguments args |> flattenCliTokens
192203

193204
/// <summary>Prints parameters in App.Config format.</summary>
194205
/// <param name="args">The parameters that fill out the XML document.</param>
195206
/// <param name="printComments">Print XML comments over every configuration entry.</param>
196-
member __.PrintAppSettings (args : 'Template list, ?printComments : bool) : string =
207+
member __.PrintAppSettingsArguments (args : 'Template list, ?printComments : bool) : string =
197208
let printComments = defaultArg printComments true
198209
let xmlDoc = printAppSettings argInfo printComments args
199210
use writer = { new System.IO.StringWriter() with member __.Encoding = System.Text.Encoding.UTF8 }
@@ -212,14 +223,21 @@ type ArgumentParser =
212223
/// </summary>
213224
/// <param name="programName">Program identifier, e.g. 'cat'. Defaults to the current executable name.</param>
214225
/// <param name="description">Program description placed at the top of the usage string.</param>
226+
/// <param name="usageStringCharacterWidth">Text width used when formatting the usage string. Defaults to 80 chars.</param>
215227
/// <param name="errorHandler">The implementation of IExiter used for error handling. Exception is default.</param>
216-
static member Create<'Template when 'Template :> IArgParserTemplate>(?programName : string, ?description : string, ?errorHandler : IExiter) =
217-
new ArgumentParser<'Template>(?programName = programName, ?description = description, ?errorHandler = errorHandler)
228+
static member Create<'Template when 'Template :> IArgParserTemplate>(?programName : string, ?description : string, ?usageStringCharacterWidth : int, ?errorHandler : IExiter) =
229+
new ArgumentParser<'Template>(?programName = programName, ?description = description, ?errorHandler = errorHandler, ?usageStringCharacterWidth = usageStringCharacterWidth)
218230

219231

220232
[<AutoOpen>]
221233
module ArgumentParserUtils =
222-
234+
235+
type ParseResult<'Template when 'Template :> IArgParserTemplate> with
236+
member r.Parser =
237+
new ArgumentParser<'Template>(r.ArgInfo, r.ProgramName, ?description = r.Description,
238+
usageStringCharacterWidth = r.CharacterWidth,
239+
errorHandler = r.ErrorHandler)
240+
223241
/// converts a sequence of inputs to a ParseResult instance
224242
let toParseResults (inputs : seq<'Template>) : ParseResult<'Template> =
225243
ArgumentParser.Create<'Template>().ToParseResult(inputs)

src/Argu/ParseResult.fs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
open FSharp.Quotations
44

5-
type private IParseResults =
6-
abstract GetAllResults : unit -> seq<obj>
7-
85
/// Argument parsing result holder.
96
[<Sealed; AutoSerializable(false); StructuredFormatDisplay("{StructuredFormatDisplay}")>]
107
type ParseResult<'Template when 'Template :> IArgParserTemplate>
11-
internal (argInfo : UnionArgInfo, results : UnionParseResults, mkUsageString : string option -> string, exiter : IExiter) =
8+
internal (argInfo : UnionArgInfo, results : UnionParseResults, programName : string, description : string option, usageStringCharWidth : int, exiter : IExiter) =
9+
10+
let mkUsageString message = printUsage argInfo programName usageStringCharWidth message |> StringExpr.build
11+
12+
// error handler functions
13+
let error hideUsage code msg =
14+
if hideUsage then exiter.Exit(msg, code)
15+
else exiter.Exit(mkUsageString (Some msg), code)
1216

13-
// exiter wrapper
14-
let exit hideUsage msg id =
15-
if hideUsage then exiter.Exit(msg, id)
16-
else exiter.Exit(mkUsageString (Some msg), id)
17+
let errorf hideusage code fmt = Printf.ksprintf (error hideusage code) fmt
1718

1819
// restriction predicate based on optional parse source
1920
let restrictF flags : UnionCaseParseResult -> bool =
@@ -26,32 +27,29 @@ type ParseResult<'Template when 'Template :> IArgParserTemplate>
2627
let getResult rs (e : Expr) =
2728
let id = expr2Uci e
2829
let results = results.Cases.[id.Tag]
29-
match Seq.tryLast results with
30+
match Array.tryLast results with
3031
| None ->
3132
let aI = argInfo.Cases.[id.Tag]
32-
exit aI.NoCommandLine (sprintf "missing argument '%s'." aI.Name) ErrorCode.PostProcess
33-
| Some r ->
34-
if restrictF rs r then r
35-
else
36-
let aI = r.ArgInfo
37-
exit aI.NoCommandLine (sprintf "missing argument '%s'." aI.Name) ErrorCode.PostProcess
33+
errorf aI.NoCommandLine ErrorCode.PostProcess "ERROR: missing argument '%s'." aI.Name
34+
| Some r when restrictF rs r -> r
35+
| Some r -> errorf r.ArgInfo.NoCommandLine ErrorCode.PostProcess "ERROR: missing argument '%s'." r.ArgInfo.Name
3836

3937
let parseResult (f : 'F -> 'S) (r : UnionCaseParseResult) =
4038
try f (r.FieldContents :?> 'F)
41-
with e ->
42-
exit r.ArgInfo.NoCommandLine (sprintf "Error parsing '%s': %s" r.ParseContext e.Message) ErrorCode.PostProcess
39+
with e -> errorf r.ArgInfo.NoCommandLine ErrorCode.PostProcess "ERROR parsing '%s': %s" r.ParseContext e.Message
4340

44-
interface IParseResults with
45-
member __.GetAllResults () =
46-
__.GetAllResults() |> Seq.map box
41+
interface IParseResult with
42+
member __.GetAllResults () = __.GetAllResults() |> Seq.map box
43+
44+
member __.ErrorHandler = exiter
45+
member internal __.ProgramName = programName
46+
member internal __.Description = description
47+
member internal __.ArgInfo = argInfo
48+
member internal __.CharacterWidth = usageStringCharWidth
4749

4850
/// Returns true if '--help' parameter has been specified in the command line.
4951
member __.IsUsageRequested = results.IsUsageRequested
5052

51-
/// <summary>Returns the usage string.</summary>
52-
/// <param name="message">The message to be displayed on top of the usage string.</param>
53-
member __.Usage (?message : string) : string = mkUsageString message
54-
5553
/// Gets all unrecognized CLI parameters which
5654
/// accumulates if parsed with 'ignoreUnrecognized = true'
5755
member __.UnrecognizedCliParams = results.UnrecognizedCliParams
@@ -126,8 +124,9 @@ type ParseResult<'Template when 'Template :> IArgParserTemplate>
126124
/// <param name="errorCode">The error code to be returned.</param>
127125
/// <param name="showUsage">Print usage together with error message.</param>
128126
member __.Raise (msg : string, ?errorCode : ErrorCode, ?showUsage : bool) : 'T =
127+
let errorCode = defaultArg errorCode ErrorCode.PostProcess
129128
let showUsage = defaultArg showUsage true
130-
exit (not showUsage) msg (defaultArg errorCode ErrorCode.PostProcess)
129+
error (not showUsage) errorCode msg
131130

132131
/// <summary>Raise an error through the argument parser's exiter mechanism. Display usage optionally.</summary>
133132
/// <param name="error">The error to be displayed.</param>

0 commit comments

Comments
 (0)