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