From 0488f65fef5202defa7973618b30a39f883dadb8 Mon Sep 17 00:00:00 2001 From: Evgeny608799 Date: Sat, 9 Mar 2024 13:37:09 +0500 Subject: [PATCH] Amendments made (suggested refactoring). Change in the base class of 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 --- O21.CommandLine.Tests/MockConsole.fs | 10 +++++ O21.CommandLine.Tests/MockReporter.fs | 15 ------- .../O21.CommandLine.Tests.fsproj | 4 +- O21.CommandLine.Tests/Program.fs | 2 - O21.CommandLine.Tests/Tests.fs | 42 ++++++++++++------- O21.CommandLine/Arguments.fs | 18 ++++---- O21.CommandLine/CommandLineParser.fs | 35 ++++++++++------ O21.CommandLine/{IReporter.fs => IConsole.fs} | 2 +- O21.CommandLine/O21.CommandLine.fsproj | 2 +- O21.Game/O21.Game.fsproj | 2 +- O21.Game/Program.fs | 6 +-- ...ommandLineReporter.fs => SystemConsole.fs} | 8 ++-- 12 files changed, 77 insertions(+), 69 deletions(-) create mode 100644 O21.CommandLine.Tests/MockConsole.fs delete mode 100644 O21.CommandLine.Tests/MockReporter.fs delete mode 100644 O21.CommandLine.Tests/Program.fs rename O21.CommandLine/{IReporter.fs => IConsole.fs} (88%) rename O21.Game/{CommandLineReporter.fs => SystemConsole.fs} (57%) diff --git a/O21.CommandLine.Tests/MockConsole.fs b/O21.CommandLine.Tests/MockConsole.fs new file mode 100644 index 0000000..d3b9642 --- /dev/null +++ b/O21.CommandLine.Tests/MockConsole.fs @@ -0,0 +1,10 @@ +namespace O21.CommandLine.Tests + +open O21.CommandLine + +type MockConsole() = + member val InfoItems = ResizeArray() + member val ErrorItems = ResizeArray() + interface IConsole with + member this.ReportInfo(text:string) = this.InfoItems.Add(text) + member this.ReportError(text:string) = this.ErrorItems.Add(text) diff --git a/O21.CommandLine.Tests/MockReporter.fs b/O21.CommandLine.Tests/MockReporter.fs deleted file mode 100644 index 0f44a2e..0000000 --- a/O21.CommandLine.Tests/MockReporter.fs +++ /dev/null @@ -1,15 +0,0 @@ -namespace O21.CommandLine.Tests - -open O21.CommandLine -open System.Collections.Generic - -type MockReporter() = - let infoList:List = List() - let errorList:List = List() - interface IReporter with - member this.ReportInfo(text:string) = infoList.Add(text) - member this.ReportError(text:string) = errorList.Add(text) - member public this.ReportInfo(text:string) = (this :> IReporter).ReportInfo text - member public this.ReportError(text:string) = (this :> IReporter).ReportError text - member public this.InfoList with get() = infoList - member public this.ErrorList with get() = errorList diff --git a/O21.CommandLine.Tests/O21.CommandLine.Tests.fsproj b/O21.CommandLine.Tests/O21.CommandLine.Tests.fsproj index 7c97810..81ad110 100644 --- a/O21.CommandLine.Tests/O21.CommandLine.Tests.fsproj +++ b/O21.CommandLine.Tests/O21.CommandLine.Tests.fsproj @@ -4,14 +4,12 @@ net7.0 false - false true - + - diff --git a/O21.CommandLine.Tests/Program.fs b/O21.CommandLine.Tests/Program.fs deleted file mode 100644 index a2b1c63..0000000 --- a/O21.CommandLine.Tests/Program.fs +++ /dev/null @@ -1,2 +0,0 @@ -module Program = let [] main _ = 0 - diff --git a/O21.CommandLine.Tests/Tests.fs b/O21.CommandLine.Tests/Tests.fs index fe4e1bf..ce384dc 100644 --- a/O21.CommandLine.Tests/Tests.fs +++ b/O21.CommandLine.Tests/Tests.fs @@ -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) + [] [] [] [] 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 -> @@ -26,13 +29,13 @@ let ValidVerbFullArgsTest (argsString:string) = Assert.Equal("C:\\O21\\ReserveFolder", helpCommand.outputDirectory) | _ -> Assert.Fail($"The type ({args.GetType().FullName}) must match {typeof}") ) - Assert.Empty(reporter.ErrorList) - Assert.NotEmpty(reporter.InfoList) + Assert.Empty(reporter.ErrorItems) + Assert.NotEmpty(reporter.InfoItems) [] 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 -> @@ -40,30 +43,37 @@ let StartVerbWithoutScreenSizesTest () = Assert.Null(startCommand.screenSizes) | _ -> Assert.Fail($"The type ({args.GetType().FullName}) must match {typeof}") ) - Assert.Empty(reporter.ErrorList) - Assert.NotEmpty(reporter.InfoList) + Assert.Empty(reporter.ErrorItems) + Assert.NotEmpty(reporter.InfoItems) [] 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 [] 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 + +[] +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 [] 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) diff --git a/O21.CommandLine/Arguments.fs b/O21.CommandLine/Arguments.fs index c2f2eab..3d63f69 100644 --- a/O21.CommandLine/Arguments.fs +++ b/O21.CommandLine/Arguments.fs @@ -5,31 +5,31 @@ open CommandLine module Arguments = [] - type BaseCommand(showHelpInfo:bool) = + type BaseCommand(noLogo:bool) = class - [] - member this.showHelpInfo: bool = showHelpInfo + [] + member this.noLogo: bool = noLogo end [] - type StartGame(gameDirectory:string, screenSizes:IList, showHelpInfo:bool) = - inherit BaseCommand(showHelpInfo) + type StartGame(gameDirectory:string, screenSizes:IList, noLogo:bool) = + inherit BaseCommand(noLogo) [] member this.gameDirectory : string = gameDirectory [] member this.screenSizes : IList = screenSizes [] - type ExportResources(inputFilePath:string, outputDirectory:string, showHelpInfo:bool) = - inherit BaseCommand(showHelpInfo) + type ExportResources(inputFilePath:string, outputDirectory:string, noLogo:bool) = + inherit BaseCommand(noLogo) [] member this.inputFilePath: string = inputFilePath [] member this.outputDirectory: string = outputDirectory [] - type HelpFile(inputFilePath:string, outputDirectory:string, showHelpInfo:bool) = - inherit BaseCommand(showHelpInfo) + type HelpFile(inputFilePath:string, outputDirectory:string, noLogo:bool) = + inherit BaseCommand(noLogo) [] member this.inputFilePath: string = inputFilePath [] diff --git a/O21.CommandLine/CommandLineParser.fs b/O21.CommandLine/CommandLineParser.fs index 82fe98f..16e805c 100644 --- a/O21.CommandLine/CommandLineParser.fs +++ b/O21.CommandLine/CommandLineParser.fs @@ -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) : string = + let bannerMessage = $"O21 v{Assembly.GetExecutingAssembly().GetName().Version}" + + let private prepareHelpText (parserResult:ParserResult) : 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) = + parserResult.Errors.Any(fun e -> e.GetType() = typeof + || e.GetType() = typeof) - 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 args match parserResult with | :? NotParsed 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) @@ -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) diff --git a/O21.CommandLine/IReporter.fs b/O21.CommandLine/IConsole.fs similarity index 88% rename from O21.CommandLine/IReporter.fs rename to O21.CommandLine/IConsole.fs index 28e4d12..c6d6fb2 100644 --- a/O21.CommandLine/IReporter.fs +++ b/O21.CommandLine/IConsole.fs @@ -1,6 +1,6 @@ namespace O21.CommandLine [] -type IReporter = +type IConsole = abstract member ReportInfo: string -> unit abstract member ReportError: string -> unit diff --git a/O21.CommandLine/O21.CommandLine.fsproj b/O21.CommandLine/O21.CommandLine.fsproj index 5415e28..38bd8c6 100644 --- a/O21.CommandLine/O21.CommandLine.fsproj +++ b/O21.CommandLine/O21.CommandLine.fsproj @@ -7,7 +7,7 @@ - + diff --git a/O21.Game/O21.Game.fsproj b/O21.Game/O21.Game.fsproj index eccbc25..415e893 100644 --- a/O21.Game/O21.Game.fsproj +++ b/O21.Game/O21.Game.fsproj @@ -81,7 +81,7 @@ - + diff --git a/O21.Game/Program.fs b/O21.Game/Program.fs index 06ead5a..c23ada4 100644 --- a/O21.Game/Program.fs +++ b/O21.Game/Program.fs @@ -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 @@ -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 diff --git a/O21.Game/CommandLineReporter.fs b/O21.Game/SystemConsole.fs similarity index 57% rename from O21.Game/CommandLineReporter.fs rename to O21.Game/SystemConsole.fs index 515d9f9..7f4a354 100644 --- a/O21.Game/CommandLineReporter.fs +++ b/O21.Game/SystemConsole.fs @@ -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