Skip to content

Commit

Permalink
Amendments made (suggested refactoring). Change in the base class of …
Browse files Browse the repository at this point in the history
…commands: now BaseCommand does not contain the "showHelpText" option, it was replaced with "noLogo" because the library can automatically handle the "help" option. All requests for help are translated into an error stream
  • Loading branch information
evgTSV authored and ForNeVeR committed Mar 9, 2024
1 parent a78deaf commit 0488f65
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 69 deletions.
10 changes: 10 additions & 0 deletions O21.CommandLine.Tests/MockConsole.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace O21.CommandLine.Tests

open O21.CommandLine

type MockConsole() =
member val InfoItems = ResizeArray<string>()
member val ErrorItems = ResizeArray<string>()
interface IConsole with
member this.ReportInfo(text:string) = this.InfoItems.Add(text)
member this.ReportError(text:string) = this.ErrorItems.Add(text)
15 changes: 0 additions & 15 deletions O21.CommandLine.Tests/MockReporter.fs

This file was deleted.

4 changes: 1 addition & 3 deletions O21.CommandLine.Tests/O21.CommandLine.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
<TargetFramework>net7.0</TargetFramework>

<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<Compile Include="MockReporter.fs" />
<Compile Include="MockConsole.fs" />
<Compile Include="Tests.fs"/>
<Compile Include="Program.fs"/>
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions O21.CommandLine.Tests/Program.fs

This file was deleted.

42 changes: 26 additions & 16 deletions O21.CommandLine.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ open O21.CommandLine.Tests
open O21.CommandLine.Arguments
open Xunit

let private AssertHasOnlyBannerMessage (reporter:MockConsole) =
Assert.Single(reporter.InfoItems, fun m -> m = CommandLineParser.bannerMessage)

[<Theory>]
[<InlineData("start C:\\O21 --screenSizes 1920 1080")>]
[<InlineData("export C:\\O21\\file.bin -o C:\\O21\\ReserveFolder")>]
[<InlineData("helpFile C:\\O21\\file.bin --out C:\\O21\\ReserveFolder")>]
let ValidVerbFullArgsTest (argsString:string) =
let args = argsString.Split()
let reporter = MockReporter()
let reporter = MockConsole()
CommandLineParser.parseArguments args reporter (fun args ->
match args with
| :? StartGame as startCommand ->
Expand All @@ -26,44 +29,51 @@ let ValidVerbFullArgsTest (argsString:string) =
Assert.Equal("C:\\O21\\ReserveFolder", helpCommand.outputDirectory)
| _ -> Assert.Fail($"The type ({args.GetType().FullName}) must match {typeof<StartGame>}")
)
Assert.Empty(reporter.ErrorList)
Assert.NotEmpty(reporter.InfoList)
Assert.Empty(reporter.ErrorItems)
Assert.NotEmpty(reporter.InfoItems)

[<Fact>]
let StartVerbWithoutScreenSizesTest () =
let args = [|"start"; "C:\\O21";|]
let reporter = MockReporter()
let reporter = MockConsole()
CommandLineParser.parseArguments args reporter (fun args ->
match args with
| :? StartGame as startCommand ->
Assert.Equal("C:\\O21", startCommand.gameDirectory)
Assert.Null(startCommand.screenSizes)
| _ -> Assert.Fail($"The type ({args.GetType().FullName}) must match {typeof<StartGame>}")
)
Assert.Empty(reporter.ErrorList)
Assert.NotEmpty(reporter.InfoList)
Assert.Empty(reporter.ErrorItems)
Assert.NotEmpty(reporter.InfoItems)

[<Fact>]
let StartVerbWithoutArgsTest () =
let args = [|"start"|]
let reporter = MockReporter()
let reporter = MockConsole()
CommandLineParser.parseArguments args reporter (fun _ -> Assert.Fail("Parsing process was failed, but function was called"))
Assert.Equivalent([|CommandLineParser.directoryPathNotDefined|], reporter.ErrorList)
Assert.Empty(reporter.InfoList)
Assert.Equivalent([|CommandLineParser.directoryPathNotDefined|], reporter.ErrorItems)
AssertHasOnlyBannerMessage reporter

[<Fact>]
let ExportVerbWithoutArgsTest () =
let args = [|"export"|]
let reporter = MockReporter()
let reporter = MockConsole()
CommandLineParser.parseArguments args reporter (fun _ -> Assert.Fail("Parsing process was failed, but function was called"))
Assert.Equivalent([|CommandLineParser.inputFileNotDefined; CommandLineParser.directoryPathNotDefined|], reporter.ErrorList)
Assert.Empty(reporter.InfoList)
Assert.Equivalent([|CommandLineParser.inputFileNotDefined; CommandLineParser.directoryPathNotDefined|], reporter.ErrorItems)
AssertHasOnlyBannerMessage reporter

[<Fact>]
let HelpFileVerbWithoutArgsTest () =
let args = [|"helpFile"|]
let reporter = MockConsole()
CommandLineParser.parseArguments args reporter (fun _ -> Assert.Fail("Parsing process was failed, but function was called"))
Assert.Equivalent([|CommandLineParser.inputFileNotDefined; CommandLineParser.directoryPathNotDefined|], reporter.ErrorItems)
AssertHasOnlyBannerMessage reporter

[<Fact>]
let UndefinedVerbTest () =
let args = [|"helloWorld"|]
let reporter = MockReporter()
let reporter = MockConsole()
CommandLineParser.parseArguments args reporter (fun _ -> Assert.Fail("Parsing process was failed, but function was called"))
Assert.NotEmpty(reporter.ErrorList)
Assert.Empty(reporter.InfoList)

Assert.NotEmpty(reporter.ErrorItems)
Assert.Empty(reporter.InfoItems)
18 changes: 9 additions & 9 deletions O21.CommandLine/Arguments.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@ open CommandLine

module Arguments =
[<AbstractClass>]
type BaseCommand(showHelpInfo:bool) =
type BaseCommand(noLogo:bool) =
class
[<Option('h', "help", HelpText = "Help info about command")>]
member this.showHelpInfo: bool = showHelpInfo
[<Option("no-logo", HelpText = "Suppress banner message")>]
member this.noLogo: bool = noLogo
end

[<Verb("start", HelpText = "Start the game")>]
type StartGame(gameDirectory:string, screenSizes:IList<int>, showHelpInfo:bool) =
inherit BaseCommand(showHelpInfo)
type StartGame(gameDirectory:string, screenSizes:IList<int>, noLogo:bool) =
inherit BaseCommand(noLogo)
[<Value(0, HelpText = "The directory where the game will be loaded")>]
member this.gameDirectory : string = gameDirectory
[<Option("screenSizes", HelpText = "Set up the sizes of window (width and height)")>]
member this.screenSizes : IList<int> = screenSizes

[<Verb("export", HelpText = "Export resources")>]
type ExportResources(inputFilePath:string, outputDirectory:string, showHelpInfo:bool) =
inherit BaseCommand(showHelpInfo)
type ExportResources(inputFilePath:string, outputDirectory:string, noLogo:bool) =
inherit BaseCommand(noLogo)
[<Value(0, HelpText = "The file from which the resources will be exported")>]
member this.inputFilePath: string = inputFilePath
[<Option('o', "out", HelpText = "Directory where resources will be stored")>]
member this.outputDirectory: string = outputDirectory

[<Verb("helpFile", HelpText = "Parse a WinHelp file and extract all the information from it.")>]
type HelpFile(inputFilePath:string, outputDirectory:string, showHelpInfo:bool) =
inherit BaseCommand(showHelpInfo)
type HelpFile(inputFilePath:string, outputDirectory:string, noLogo:bool) =
inherit BaseCommand(noLogo)
[<Value(0, HelpText = "Path to the input .hlp file.")>]
member this.inputFilePath: string = inputFilePath
[<Option('o', "out", HelpText = "Path to the output resource directory.")>]
Expand Down
35 changes: 22 additions & 13 deletions O21.CommandLine/CommandLineParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,48 @@ namespace O21.CommandLine

open System
open System.Reflection
open System.Linq
open CommandLine
open CommandLine.Text
open O21.CommandLine.Arguments

module CommandLineParser =
let notParserMessage = "invalid command"
let notParsedMessage = "invalid command"
let parsingSuccessMessage = "success"
let directoryPathNotDefined = "Directory path should be defined"
let invalidScreenSizesOption = "The screen sizes can only accept 2 values (width and height)"
let inputFileNotDefined = "File should be defined"

let prepareHelpText (parserResult:ParserResult<obj>) : string =
let bannerMessage = $"O21 v{Assembly.GetExecutingAssembly().GetName().Version}"

let private prepareHelpText (parserResult:ParserResult<obj>) : string =
let helpText = HelpText.AutoBuild(parserResult, fun h ->
h.AdditionalNewLineAfterOption <- false
h.Heading <- $"O21 v{Assembly.GetExecutingAssembly().GetName().Version}"
h.Heading <- bannerMessage
h)
helpText.ToString()

let private isHelpRequest(parserResult:NotParsed<obj>) =
parserResult.Errors.Any(fun e -> e.GetType() = typeof<HelpVerbRequestedError>
|| e.GetType() = typeof<HelpRequestedError>)

let parseArguments (args:string[]) (reporter:IReporter) (worker:BaseCommand -> unit) =
use parser = new Parser(fun cfg -> cfg.HelpWriter <- null)
let parseArguments (args:string[]) (reporter:IConsole) (worker:BaseCommand -> unit) =
use parser = new Parser(fun cfg ->
cfg.HelpWriter <- null
cfg.CaseSensitive <- false)
let parserResult = parser.ParseArguments<StartGame, ExportResources, HelpFile> args

match parserResult with
| :? NotParsed<obj> as notParsed ->
reporter.ReportError(notParserMessage)
let helpText = prepareHelpText notParsed
if not (isHelpRequest notParsed) then
reporter.ReportError(notParsedMessage)
reporter.ReportError(helpText)
| command ->
let mutable success = true
if (command.Value :?> BaseCommand).showHelpInfo then
reporter.ReportInfo("help info") // TODO[#141]: implement help info for command
else
match command.Value with
if not (command.Value :?> BaseCommand).noLogo then
reporter.ReportInfo(bannerMessage)
match command.Value with
| :? StartGame as startCommand ->
if String.IsNullOrWhiteSpace startCommand.gameDirectory then
reporter.ReportError(directoryPathNotDefined)
Expand All @@ -57,6 +66,6 @@ module CommandLineParser =
reporter.ReportError(directoryPathNotDefined)
success <- false
| _ -> raise(ArgumentException("Undefined command", command.GetType().FullName))
if success then
reporter.ReportInfo(parsingSuccessMessage)
worker (command.Value :?> BaseCommand)
if success then
reporter.ReportInfo(parsingSuccessMessage)
worker (command.Value :?> BaseCommand)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace O21.CommandLine

[<Interface>]
type IReporter =
type IConsole =
abstract member ReportInfo: string -> unit
abstract member ReportError: string -> unit
2 changes: 1 addition & 1 deletion O21.CommandLine/O21.CommandLine.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<ItemGroup>
<Compile Include="Arguments.fs" />
<Compile Include="IReporter.fs" />
<Compile Include="IConsole.fs" />
<Compile Include="CommandLineParser.fs" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion O21.Game/O21.Game.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@

<Compile Include="RaylibEnvironment.fs" />

<Compile Include="CommandLineReporter.fs" />
<Compile Include="SystemConsole.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Expand Down
6 changes: 2 additions & 4 deletions O21.Game/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ let private runGame screenSize u95DataDirectory =
let main(args: string[]): int =
Encoding.RegisterProvider CodePagesEncodingProvider.Instance

let reporter:CommandLineReporter = new CommandLineReporter()
let reporter:SystemConsole = new SystemConsole()

let matchArgs (command:BaseCommand) =
match command with
Expand All @@ -50,9 +50,7 @@ let main(args: string[]): int =
let! resources = Async.AwaitTask(NeExeFile.LoadResources exportCommand.inputFilePath)
exportImagesAsBmp exportCommand.outputDirectory resources
})
| :? HelpFile as helpCommand ->
Console.OutputEncoding <- Encoding.UTF8

| :? HelpFile as helpCommand ->
use input = new FileStream(helpCommand.inputFilePath, FileMode.Open, FileAccess.Read)
use reader = new BinaryReader(input, Encoding.UTF8, leaveOpen = true)
let file = WinHelpFile.Load reader
Expand Down
8 changes: 4 additions & 4 deletions O21.Game/CommandLineReporter.fs → O21.Game/SystemConsole.fs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
namespace O21.Game

open System
open System.Text
open O21.CommandLine

type CommandLineReporter() =
interface IReporter with
type SystemConsole() =
do Console.OutputEncoding <- Encoding.UTF8
interface IConsole with
member this.ReportInfo(text:string) = printfn $"%s{text}"
member this.ReportError(text:string) =
Console.ForegroundColor <- ConsoleColor.Red
printfn $"%s{text}"
Console.ForegroundColor <- ConsoleColor.White
member public this.ReportInfo(text:string) = (this :> IReporter).ReportInfo text
member public this.ReportError(text:string) = (this :> IReporter).ReportError text

0 comments on commit 0488f65

Please sign in to comment.