Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions src/Command.fs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,23 @@ module internal Commands =
|> format
|> output.GroupedOptions CommandName.NamespaceSeparator "Available commands:"

let private showUsage (output: Output) =
output.SimpleOptions "Usage:" [
[ "command [options] [--] [arguments]"; "" ]
]

let private showOptions (output: Output) applicationOptions =
applicationOptions
|> OptionsDefinitions.format
|> output.SimpleOptions "Options:"

let showApplicationHelp (output: Output) applicationOptions commands =
showUsage output
showOptions output applicationOptions

commands
|> showAvailable output

let add name command (commands: Commands) =
commands.Add (CommandName (Name name), command)

Expand Down Expand Up @@ -348,37 +365,23 @@ module internal Commands =
Initialize = None
Interact = None
Execute = Execute <| fun (input, output) ->
let showUsage () =
output.SimpleOptions "Usage:" [
[ "command [options] [--] [arguments]"; "" ]
]

let showOptions () =
applicationOptions
|> OptionsDefinitions.format
|> output.SimpleOptions "Options:"

match input with
| Input.Argument.OptionalValue "namespace" namespaceToList ->
match commands |> byNamespace namespaceToList with
| commandsByNamespace when commandsByNamespace |> Map.isEmpty ->
output.Error <| sprintf "There are no commands defined in the \"%s\" namespace.\n" namespaceToList
ExitCode.Error
| commandsByNamespace ->
showUsage()
showOptions()
showUsage output
showOptions output applicationOptions

commandsByNamespace
|> format
|> output.SimpleOptions "Available commands:"

ExitCode.Success
| _ ->
showUsage()
showOptions()

commands
|> showAvailable output
showApplicationHelp output applicationOptions commands

ExitCode.Success
}
Expand Down
32 changes: 19 additions & 13 deletions src/ConsoleApplication.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ module MFConsoleApplication =
let (|ContainsOption|_|) option (args: Args) =
args |> containsOption option |> Bool.toOption

let getCommandName (args: Args) =
let tryGetCommandName (args: Args) =
args
|> Array.pick (function
|> Array.tryPick (function
| CommandName.IsCommandName commandName -> Some commandName
| _ -> None
)
Expand Down Expand Up @@ -148,6 +148,8 @@ module MFConsoleApplication =

let args =
match args with
| Args.ContainsOption OptionsDefinitions.help
| Args.ContainsOption OptionsDefinitions.version -> args
| Args.Empty
| Args.ContainsOnlyOptions ->
[|
Expand All @@ -160,19 +162,23 @@ module MFConsoleApplication =
| Args.ContainsOption OptionsDefinitions.help ->
parts |> showApplicationInfo

let rawCommandName = args |> Args.getCommandName
match args |> Args.tryGetCommandName with
| Some rawCommandName ->
return!
match parts.Commands |> Commands.find rawCommandName with
| ExactlyOne (commandName, command) ->
command
|> Help.showForCommand output parts.OptionDecorationLevel parts.ApplicationOptions commandName

return!
match parts.Commands |> Commands.find rawCommandName with
| ExactlyOne (commandName, command) ->
command
|> Help.showForCommand output parts.OptionDecorationLevel parts.ApplicationOptions commandName
Ok ExitCode.Success
| MoreThanOne (givenName, names) -> Error (ArgsError.AmbigousCommandFound (givenName, names))
| NoCommand unknownName -> Error (ArgsError.CommandNotFound unknownName)
<@> ConsoleApplicationError.ArgsError
<!!*> currentCommand
| None ->
Commands.showApplicationHelp output parts.ApplicationOptions parts.Commands

Ok ExitCode.Success
| MoreThanOne (givenName, names) -> Error (ArgsError.AmbigousCommandFound (givenName, names))
| NoCommand unknownName -> Error (ArgsError.CommandNotFound unknownName)
<@> ConsoleApplicationError.ArgsError
<!!*> currentCommand
return ExitCode.Success
| Args.ContainsOption OptionsDefinitions.version ->
{ parts with ApplicationInfo = ApplicationInfo.OnlyNameAndVersion }
|> showApplicationInfo
Expand Down
165 changes: 165 additions & 0 deletions tests/ApplicationHelpTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
module Feather.ConsoleApplication.Tests.ApplicationHelp

open Expecto
open Feather.ConsoleApplication

type TestCase = {
Description: string
Command: string
DefaultCommand: string option
ExpectedOutput: string list
}

let private greetCommand: CommandDefinition = {
Description = "Greets a person"
Help = None
Arguments = []
Options = []
Initialize = None
Interact = None
Execute = Execute <| fun (_, output) ->
output.Message "greeting"
ExitCode.Success
}

let private header = [
"Application help test <1.0.0>"
"============================="
""
]

let private applicationHelp = [
yield! header

"Usage:"
" command [options] [--] [arguments] "
""
"Options:"
" -h, --help Display this help message "
" -q, --quiet Do not output any message "
" -V, --version Display this application version "
" -n, --no-interaction Do not ask any interactive question "
" --no-progress Whether to disable all progress bars "
" --no-ansi Whether to disable all markup with ansi formatting"
" -v|vv|vvv, --verbose Increase the verbosity of messages "
""
"Available commands:"
" about Displays information about the current project"
" greet Greets a person "
" help Displays help for a command "
" list Lists commands "
""
""
]

let provideApplicationHelp = seq {
{
Description = "should show application help when --help given without command"
Command = "--help"
DefaultCommand = None
ExpectedOutput = applicationHelp
}

{
Description = "should show application help when -h given without command"
Command = "-h"
DefaultCommand = None
ExpectedOutput = applicationHelp
}

{
Description = "should show application help when --help given with other options only"
Command = "--no-interaction --help"
DefaultCommand = None
ExpectedOutput = applicationHelp
}

{
Description = "should show application help when --help given and custom default command configured"
Command = "--help"
DefaultCommand = Some "greet"
ExpectedOutput = applicationHelp
}

{
Description = "should run default command when no args given and custom default command configured"
Command = ""
DefaultCommand = Some "greet"
ExpectedOutput = [
yield! header

"greeting"
""
]
}

{
Description = "should run default command when only options without help given and custom default command configured"
Command = "--no-interaction"
DefaultCommand = Some "greet"
ExpectedOutput = [
yield! header

"greeting"
""
]
}

{
Description = "should show name and version when --version given"
Command = "--version"
DefaultCommand = None
ExpectedOutput = [
"Application help test <1.0.0>"
""
]
}
}

[<Tests>]
let applicationHelpTests =
testList "ConsoleApplication - application help" [
yield!
provideApplicationHelp
|> Seq.map (fun { Description = desc; Command = command; DefaultCommand = customDefault; ExpectedOutput = expected } ->
testCase desc <| fun _ ->
use buffer = new Feather.ConsoleStyle.Output.BufferOutput(Feather.ConsoleStyle.Verbosity.Normal)
let console = Feather.ConsoleStyle.ConsoleStyle(buffer)
let args =
match command with
| "" -> [||]
| command -> command.Split " "
let application =
match customDefault with
| Some defaultCommandName ->
consoleApplication {
title "Feather.ConsoleApplication.Test"
name "Application help test"
version "1.0.0"
info ApplicationInfo.NameAndVersion
command "greet" greetCommand
defaultCommand defaultCommandName
useOutput console
}
| None ->
consoleApplication {
title "Feather.ConsoleApplication.Test"
name "Application help test"
version "1.0.0"
info ApplicationInfo.NameAndVersion
command "greet" greetCommand
useOutput console
}

let result = application |> runResult args

Expect.equal result (Ok ExitCode.Success) $"Command \"{command}\" should be successful"
let output =
(buffer.Fetch() |> console.RemoveMarkup).Split "\n"
|> List.ofArray
expected
|> List.iteri (fun i line ->
Expect.equal output.[i] line $"Line {i + 1} should match."
)
)
]
1 change: 1 addition & 0 deletions tests/tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="ArgsTests.fs" />
<Compile Include="CommandDefinitionTests.fs" />
<Compile Include="DefaultCommandsTests.fs" />
<Compile Include="ApplicationHelpTests.fs" />
<Compile Include="Tests.fs" />
</ItemGroup>

Expand Down