From c4691352d515938345d6b57ccb7f78c3f3e14dc3 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Fri, 3 Jul 2026 22:35:43 +0200 Subject: [PATCH 1/5] Scaffolded the New Project This is a complete rewrite of the command line parser in modern C# and .NET 10. This will not only be a command line parser, but a CLI application framework that mirrors how ASP.NET Core works. For this reason the project was renamed to CLI.NET Core. This commit only scaffolds the rough repository layout, consisting of a read me, a license, a list of contributors, a changelog, docs, design materials, the source code, and tests. All old files that are no longer needed were deleted. Since the permissive MIT license has recently been used more and more by large corporations to take from the open source community and not give anything back, the license of this project was changed from MIT to LGPL 3.0. The LGPL still allows people to freely use the library in their own software, even for commercial projects, but making any changes to it forces the derivative work to be released under the same license, thus forcing companies to give back to the community. --- .editorconfig | 19 + .gitattributes | 62 ++ .gitignore | 27 +- .vscode/extensions.json | 16 + .vscode/settings.json | 84 +- .vscode/tasks.json | 31 - CHANGELOG.md | 29 + CONTRIBUTORS.md | 5 + CommandLineParser.sln | 56 -- LICENSE | 186 +++- README.md | 40 +- design/.gitkeep | 0 docs/README.md | 3 + docs/images/Banner.png | Bin 68474 -> 0 bytes docs/images/Logo.fw.png | Bin 74034 -> 0 bytes docs/images/Logo.ico | Bin 370070 -> 0 bytes docs/images/Logo1024x1024.png | Bin 15905 -> 0 bytes docs/images/Logo128x128.png | Bin 1706 -> 0 bytes docs/images/Logo256x256.png | Bin 3378 -> 0 bytes docs/images/Logo32x32.png | Bin 504 -> 0 bytes docs/images/Logo512x512.png | Bin 7043 -> 0 bytes docs/images/Logo64x64.png | Bin 934 -> 0 bytes source/.gitkeep | 0 source/Arguments/Argument.cs | 47 - source/Arguments/FlagArgument.cs | 63 -- source/Arguments/NamedArgument.cs | 81 -- source/Arguments/PositionalArgument.cs | 40 - source/Arguments/StringExtensions.cs | 69 -- source/Command.cs | 119 --- source/Parser.cs | 777 --------------- source/ParserOptions.cs | 148 --- source/ParsingResults.cs | 174 ---- source/Properties/AssemblyInformation.cs | 12 - source/System.CommandLine.Parser.csproj | 8 - source/ValueConverters/BooleanConverter.cs | 94 -- source/ValueConverters/CollectionHelper.cs | 330 ------- .../ValueConverters/EnumerationConverter.cs | 65 -- source/ValueConverters/FloatConverter.cs | 80 -- source/ValueConverters/IValueConverter.cs | 37 - source/ValueConverters/IntegerCoverter.cs | 123 --- source/ValueConverters/StringConverter.cs | 67 -- source/ValueConverters/ValueConverter.cs | 120 --- test/CamelCasingTests.cs | 37 - test/CollectionHelperTests.cs | 435 --------- test/ParserTests.cs | 905 ------------------ test/System.CommandLine.Parser.Tests.csproj | 18 - test/ValueConverterTests.cs | 155 --- test/ValueConvertersTests.cs | 278 ------ tests/linters/.gitkeep | 0 tests/unit-tests/.gitkeep | 0 50 files changed, 410 insertions(+), 4430 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .vscode/extensions.json delete mode 100644 .vscode/tasks.json create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTORS.md delete mode 100755 CommandLineParser.sln create mode 100644 design/.gitkeep create mode 100644 docs/README.md delete mode 100644 docs/images/Banner.png delete mode 100644 docs/images/Logo.fw.png delete mode 100644 docs/images/Logo.ico delete mode 100644 docs/images/Logo1024x1024.png delete mode 100644 docs/images/Logo128x128.png delete mode 100644 docs/images/Logo256x256.png delete mode 100644 docs/images/Logo32x32.png delete mode 100644 docs/images/Logo512x512.png delete mode 100644 docs/images/Logo64x64.png create mode 100644 source/.gitkeep delete mode 100644 source/Arguments/Argument.cs delete mode 100644 source/Arguments/FlagArgument.cs delete mode 100644 source/Arguments/NamedArgument.cs delete mode 100644 source/Arguments/PositionalArgument.cs delete mode 100644 source/Arguments/StringExtensions.cs delete mode 100644 source/Command.cs delete mode 100644 source/Parser.cs delete mode 100644 source/ParserOptions.cs delete mode 100644 source/ParsingResults.cs delete mode 100644 source/Properties/AssemblyInformation.cs delete mode 100644 source/System.CommandLine.Parser.csproj delete mode 100644 source/ValueConverters/BooleanConverter.cs delete mode 100644 source/ValueConverters/CollectionHelper.cs delete mode 100644 source/ValueConverters/EnumerationConverter.cs delete mode 100644 source/ValueConverters/FloatConverter.cs delete mode 100644 source/ValueConverters/IValueConverter.cs delete mode 100644 source/ValueConverters/IntegerCoverter.cs delete mode 100644 source/ValueConverters/StringConverter.cs delete mode 100644 source/ValueConverters/ValueConverter.cs delete mode 100644 test/CamelCasingTests.cs delete mode 100644 test/CollectionHelperTests.cs delete mode 100644 test/ParserTests.cs delete mode 100644 test/System.CommandLine.Parser.Tests.csproj delete mode 100644 test/ValueConverterTests.cs delete mode 100644 test/ValueConvertersTests.cs create mode 100644 tests/linters/.gitkeep create mode 100644 tests/unit-tests/.gitkeep diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e304982 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ + +# This file is the top-most editor configuration file +root = true + +# Default settings for all files +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# Turns the maximum line length off for Markdown files, as we use the automatic word wrap of the code editor +[*.md] +max_line_length = off + +# C# project files use 2 spaces for indentation +[*.csproj] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5293555 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,62 @@ + +# Normalize line endings for all text files to Unix-style (LF) on commit; Git auto-detects whether a file is text and, if so, stores it with LF in the +# repository regardless of the line endings used in the working tree +* text=auto eol=lf + +# Source and configuration files that should always be treated as text and normalized to LF +*.cs text +*.csproj text +*.sln text eol=crlf +*.props text +*.targets text +*.json text +*.jsonc text +*.md text +*.yml text +*.yaml text +*.toml text +*.xml text +*.svg text +*.txt text +*.editorconfig text +*.gitattributes text +*.gitignore text +Dockerfile text +Containerfile text +compose.yaml text + +# Shell scripts must always use LF, otherwise they break on Unix systems +*.sh text eol=lf +*.bash text eol=lf + +# Windows-only scripts must keep CRLF, otherwise they break on Windows +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# Binary files that must never be normalized or line-ending converted +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.avif binary +*.woff binary +*.woff2 binary +*.ttf binary +*.eot binary +*.otf binary +*.pdf binary +*.zip binary +*.gz binary +*.7z binary +*.mp4 binary +*.webm binary +*.mov binary +*.mp3 binary +*.wav binary +*.dll binary +*.exe binary +*.node binary +*.snap binary -diff diff --git a/.gitignore b/.gitignore index f62d8bd..f49b6f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,29 @@ -# Ignores the build outputs +# Operating system thumbnail databases +.DS_Store +Thumbs.db + +# IDEs and code editors (Visual Studio Code settings, launch configuration, tasks, and extensions are excluded) +.c9/ +.classpath +.history/* +.project +.settings/ +.vscode/* +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json +*.launch +*.sublime-workspace +/.idea + +# Build output bin/ obj/ + +# To Do Lists +TODO.md + +# Miscellaneous tool logs, outputs, and caches +.VSCodeCounter/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..02b8a94 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,16 @@ +{ + // List of extensions which should be recommended for users of this workspace + "recommendations": [ + // Language support for .gitignore files + "codezombiech.gitignore", + + // Adds useful features for editing Markdown tables + "TakumiI.markdowntable", + + // Highlights TODOs, FIXMEs, HACKs, DEBUGs, REVIEWs, and NOTEs inside of code files + "wayou.vscode-todo-highlight", + + // Integrates many useful features for editing Markdown + "yzhang.markdown-all-in-one", + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 7b68778..9d75c76 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,80 @@ { + // A vertical ruler in the code editor at 150 characters, this is a visual aide to help users keep the 150 character line limit file files that + // warrant a line limit + "editor.rulers": [150], + + // Maps file names to file types; this is required when Visual Studio Code does not correctly auto-detect file types based on file extension + "files.associations": { + "**/.vscode/*.json": "jsonc", + "LICENSE": "plaintext", + }, + + // Excludes certain files from being displayed in the file explorer "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, + ".VSCodeCounter": true, "**/.DS_Store": true, + "**/.git": true, + "**/bin": true, + "**/obj": true, + "**/Thumbs.db": true, + }, + + // Excludes certain files from being watched by the file watcher, this is required, because if the data directory is inside of the workspace, then + // Visual Studio Code will watch the entire directory, which can not only cause performance issues, but will also cause the file watcher to run + // out of file handles, which are limited by the operating system, that are necessary to watch the files + "files.watcherExclude": { + ".VSCodeCounter": true, + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, "**/bin": true, - "**/obj": true + "**/obj": true, }, - "cSpell.words": [ - "Neumann", - "Xunit", - "paket", - "vvvcccaaa" - ] + + // Configures the nesting of files that belong together in the explorer (the nested files are not expanded by default); this configuration nests + // all Node.js-related configuration files under the "package.json" file, and all linter and code formatter configuration files under the + // ".editorconfig" file + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.expand": false, + "explorer.fileNesting.patterns": { + ".editorconfig": ".gitattributes, .gitignore", + "README.md": "CHANGELOG.md, CONTRIBUTORS.md, LICENSE, TODO.md", + }, + + // Specifies which Git branches are protected in this repository, Visual Studio will prevent users from committing to these branches (this only + // works for the commit feature that is built into Visual Studio Code and not in the terminal) + "git.branchProtection": [ + "main", + "staging", + ], + + // Specifies that Visual Studio Code should always open the merge editor when a file is opened that has conflicts, instead of the code editor + "git.mergeEditor": true, + + // Allows users to force push to a remote Git repository, as we regularly make use of "git commit --amend" in this project + "git.allowForcePush": true, + + // Tells Visual Studio Code to fetch all branches instead of only the current one when pulling from a remote repository and to prune references + // to remote objects that no longer exist + "git.fetchOnPull": true, + "git.pruneOnFetch": true, + + // Specifies that Visual Studio Code should push tags and perform a rebase instead of a merge when synching with a remote repository + "git.followTagsWhenSync": true, + "git.rebaseWhenSync": true, + + // Configures the icon theme to be VSCode Icons and adapts the icons of certain files and directories for the VSCode Icons plugin + "workbench.iconTheme": "vscode-icons", + "vsicons.associations.files": [ + {"icon": "dal", "extensions": ["CHANGELOG.md"], "filename": true, "format": "svg"}, + {"icon": "docz", "extensions": ["README.md"], "filename": true, "format": "svg"}, + {"icon": "janet", "extensions": ["CONTRIBUTORS.md"], "filename": true, "format": "svg"}, + {"icon": "todo", "extensions": ["TODO.md"], "filename": true, "format": "svg"}, + {"icon": "vscode", "extensions": [".vscode/extensions.json", ".vscode/settings.json"], "filename": true, "format": "svg"}, + ], + "vsicons.associations.folders": [ + {"icon": "coverage", "extensions": ["linters"], "format": "svg"}, + {"icon": "template", "extensions": ["design"], "format": "svg"}, + {"icon": "test", "extensions": ["unit-tests"], "format": "svg"}, + ], } diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index ec84cf9..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,31 +0,0 @@ - -{ - "version": "2.0.0", - "command": "dotnet", - "type": "shell", - "args": [], - "tasks": [ - { - "label": "build", - "args": [ - "${workspaceRoot}/CommandLineParser.sln" - ], - "group": "build", - "problemMatcher": "$msCompile" - }, - { - "label": "test", - "args": [ - "${workspaceRoot}/test/System.CommandLine.Parser.Tests.csproj" - ], - "group": "test", - "problemMatcher": "$msCompile", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - } - } - ] -} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..70eefe9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +## v0.3.0 + +*To be determined.* + +Complete rewrite as a .NET 10 library using the modern application hosting model and an API that mirrors ASP.NET Core. + +In addition to the rewrite, the following changes were made: + +- Since the permissive MIT license has recently been used more and more by large corporations to take from the open source community and not give anything back, the license of this project was changed from MIT to LGPL 3.0. The LGPL still allows people to freely use the library in their own software, even for commercial projects, but making any changes to it forces the derivative work to be released under the same license, thus forcing companies to give back to the community. + +## v0.2.0 + +*Never released.* + +Complete rewrite as a .NET Core .NET Standard library with cross-platform support. Was never released and skipped. + +## v0.1.1 + +*Released on July 1, 2016.* + +Improved the error handling of the command line parser. Lexical and syntactical errors can now be handled. + +## v0.1.0 + +*Released on June 15, 2016.* + +The initial release of the command line parser. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..6039bcf --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +# CLI.NET Core Contributors + +This is a list of all people who have contributed to the CLI.NET Core project in alphabetical order. If you have made any contributions to this project, please add yourself to the list. + +- [David Neumann](https://github.com/lecode-official) diff --git a/CommandLineParser.sln b/CommandLineParser.sln deleted file mode 100755 index 7d33d01..0000000 --- a/CommandLineParser.sln +++ /dev/null @@ -1,56 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "source", "source", "{23B68DFF-3D21-42B9-8E37-FB5D6B51FE11}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.CommandLine.Parser", "source\System.CommandLine.Parser.csproj", "{2FF67E01-D4AD-4378-8D99-58A39CE98587}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{27E652C6-5165-4804-AE19-7D6C777B7C75}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.CommandLine.Parser.Tests", "test\System.CommandLine.Parser.Tests.csproj", "{32A32DAC-A488-4915-B103-B8CDC4243E91}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Debug|x64.ActiveCfg = Debug|x64 - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Debug|x64.Build.0 = Debug|x64 - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Debug|x86.ActiveCfg = Debug|x86 - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Debug|x86.Build.0 = Debug|x86 - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Release|Any CPU.Build.0 = Release|Any CPU - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Release|x64.ActiveCfg = Release|x64 - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Release|x64.Build.0 = Release|x64 - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Release|x86.ActiveCfg = Release|x86 - {2FF67E01-D4AD-4378-8D99-58A39CE98587}.Release|x86.Build.0 = Release|x86 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Debug|x64.ActiveCfg = Debug|x64 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Debug|x64.Build.0 = Debug|x64 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Debug|x86.ActiveCfg = Debug|x86 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Debug|x86.Build.0 = Debug|x86 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Release|Any CPU.Build.0 = Release|Any CPU - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Release|x64.ActiveCfg = Release|x64 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Release|x64.Build.0 = Release|x64 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Release|x86.ActiveCfg = Release|x86 - {32A32DAC-A488-4915-B103-B8CDC4243E91}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {2FF67E01-D4AD-4378-8D99-58A39CE98587} = {23B68DFF-3D21-42B9-8E37-FB5D6B51FE11} - {32A32DAC-A488-4915-B103-B8CDC4243E91} = {27E652C6-5165-4804-AE19-7D6C777B7C75} - EndGlobalSection -EndGlobal diff --git a/LICENSE b/LICENSE index a5ae042..0a04128 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,165 @@ -The MIT License (MIT) - -Copyright (c) 2016 David Neumann - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md index 91c75eb..a1f758c 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,20 @@ -# Command Line Parser +# CLI.NET Core -![Command Line Parser Logo](docs/images/Banner.png "Command Line Parser Logo") +CLI.NET Core is a command line application framework in the style of ASP.NET Core. It allows you to define commands and command line arguments in much the same way you would define actions and arguments for a Web API in ASP.NET Core. -A simple and light-weight parser for command line arguments, which makes it possible to access parameters -from the command line via well-defined .NET data structures. +## Project Structure -## Using the Project +The repository is organized into a few top-level directories: -You can always get the latest stable version from NuGet: +- **`source/`** — The source code of the CLI.NET Core framework itself. +- **`tests/`** — The unit tests and integration tests that verify the behavior of the framework, as well as the linters and code formatters that enforce a consistent and high-quality codebase. +- **`docs/`** — The project documentation. Start with [`docs/README.md`](docs/README.md). +- **`design/`** — Design documents, notes, and assets related to the project. -```bash -Install-Package System.CommandLine.Parser -Version 0.1.1 # Using the Visual Studio Package Manager -dotnet add package System.CommandLine.Parser --version 0.1.1 # Using the .NET Command Line Interface -paket add System.CommandLine.Parser --version 0.1.1 # Using the packet Command Line Interface -``` +## Contributing -**__But please be aware that the current 0.1.1 version on NuGet is outdated and should only be used for -legacy projects. The next release is a complete rewrite of the original code for .NET Core and will work -on all supported .NET Core platforms (e.g. Windows, macOS, and Linux).__** +If you'd like to contribute, there are multiple ways you can help out. If you find a bug or have a feature request, please feel free to open an issue on GitHub. If you want to contribute code, please fork the repository and use a feature branch. Pull requests are always welcome. Before forking, please open an issue where you describe what you want to do. This helps to align your ideas with mine and may prevent you from doing work, that I am already planning on doing. If you have contributed to the project, please add yourself to the [contributors list](CONTRIBUTORS.md) and document your changes in the [changelog](CHANGELOG.md). Also, if necessary, update the [documentation](docs/README.md). To help speed up the merging of your pull request, please comment and document your code extensively and try to emulate the coding style of the project. -If you want to go ahead and use the new version, you can download and manually build the solution. The -project was built using Visual Studio Code and .NET Core 2.0. To build the solution, clone the repository -and build it using the command line tools of .NET Core: +## License -```bash -git clone https://github.com/lecode-official/command-line-parser -cd command-line-parser -dotnet build -dotnet test ./test/System.CommandLine.Parser.Tests/System.CommandLine.Parser.Tests.csproj -``` - -## Contributions - -I always greatly appreciate feedback and bug reports. To file a bug, please use GitHub's issue system. -Alternatively, you can clone the repository and send me a pull request. +The code in this project is licensed under the GNU Lesser General Public License v3.0 license. This generally means that you may use, distribute, and modify this work freely given that you make the complete source code of your modifications available under the same license. If your project uses this software only through the provided interfaces (i.e., you link against this library), your work may be distributed under different terms without having to publish the source code of your work, even if your project is closed source and commercial. If you make contributions you explicitly grant patent rights if your contribution includes anything patented by you. **This is no legal advice and you should read the [license file](LICENSE) for more information.** diff --git a/design/.gitkeep b/design/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..57fa1de --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# Documentation + +Welcome to the documentation of CLI.NET Core, a .NET command line application framework in the style of ASP.NET Core. It allows you to define commands and command line arguments in much the same way you would define actions and arguments for a Web API in ASP.NET Core. diff --git a/docs/images/Banner.png b/docs/images/Banner.png deleted file mode 100644 index af3a778c630bba99016989fbf883da6af75ed1f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68474 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

H=?|bpPnx(pQ#oWpYSP8h0;)VF}>WT%m*^8 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�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 CVa=HU diff --git a/docs/images/Logo.ico b/docs/images/Logo.ico deleted file mode 100644 index feaab61ef725a64f19ed7f2ff8d8ac8e8069c406..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 370070 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 R+8|0_(J|02{`KB({|D*T)^z{? diff --git a/docs/images/Logo128x128.png b/docs/images/Logo128x128.png deleted file mode 100644 index c897295bbf18fc014e21d22e916baf0be4d6032a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1706 zcmd5-X;cz;5dR|uc%W8fhUd1rQksdPp(TZ(t7&RwWub*v$4cRPVOompt|gL|cP}-{ zvSe4wz}6DA^zYS`m(PtyPtR7oB7TB=F7Z!^L~@;?fE@Y4Xp+M zK)Sh-4k?lG#St)NtRn<8DPc{7lba7hX_pYeSCzGDnCtNf0MyriaY$QVyh2IpM>_jP z9;Tg+qz8nD0AesLFvQs9TzCkb79JX5yzj8xnBWN;0GNpziRcrPH_=O@Zivzs%390& zx?-UY9I}<@d8hW9Fi!W;ogPn}Lz|uMs;4_@qYQ6ezpfjSzDR)@>dFgcf3V{yuCUw+ zJTF%R=b?f&=yow->JfmQPUdfOTH+}G-U%^0tJb$^U{Yk<-}>R3h06IH#oOh+az~W^ zYhrl!NSq3;Mwbz$u@?_B)`x1RLJ&gma}cP-0_j(@IACPRrm89w;2<$-?D6<^khk{n zgXYoY?y(e%OM)h$y_XCbKryI=G|y}V^x@W7`4T&iI`SxbPsO4ZlUo0}R@)|r5%ryT zV*t*MO`)!_!%vSfvwVY3*@d#d4M)y*-IjdZ7I@qoG!Oe*61k2n^% zwKAWB73iE+$+cBywcn9mDbr+=EKOD?s1NnLMgiW~ef=kkO!dh>>6{F*JHMM=v`)-8Aw4ZQl zy|QVVI!wpUBXG-3V^5nigNkgjiRAa57yVjANd-Jfhdzx0&Q0Irp`%0QSLU`XABy3< zQeh!8QnsZR=rwJ7(ZL*+WLq?3(nDsjA+3NF_?O@2z33r_87cid@uUj`g$cuKp0@D4 zHtJ6v@P3ydmo{V}DhsMY%)GUaq+loGx5^w|z8}>llVOg#R-;lzM9%!iwBjSC*)5&T zI};T~rbi{VT}47pZysG>(R{yo)P`@GyR@BQ!Rg}I`_0bJ3)Udc#K7^ArQ)nv;|#)r zTqFMsp79|l$i?eE9I*rP&ly$lFKG>I|HR?n)8%rvpw#X>cw|(VXbip(Z=JLpDpT(d zmW&%7uZ9Y$F6$8pk2kIMpuyfF&h`RBuXizL1663$%Lej`#R(rd%fdDT%HcYf#Zaz{ z*km?FxTprghMr<`l9JXTUVpq{RgaNahRPp9lgJqh2BgS*oiTvoWBVRgE% zf-DaWl?Zt2?71v)Y0yJ1#!FvM>z9GK7|E+1<^iPMjY2L@{NU4L9Ak?YBz9>j!*Etd zhZ7lv_32D5sE!|c)|ZTMff8|c6OEG#3qc3mustWp-Zj-?QSm{w?0)FI=TCn(q}C<1 zkg;e)AWpkpo=Egr5}je&@3hKA1Sp5cO;P!=v5|W2Q2ZXNt6bJhWlwF|ZH48yZ^C(G zG_^meXa9OhUjwn|dJ_S>h z+u}QVm@UXh17UG+^sVT@H!nC@?S&>5D=10(>omtWEl&JpgZYVbFtCP6)W0y5`=geX zp<2Lhl+!}-qNNoYVWLCmMw;ADlbSJ4zL8Ltl8tG1;8`1o@olTlBCP!4g`vuf{$pWR zD#mZ^=ryV~V_G7SgKd&&1L06*AI3jlu^A7eyoG*t?Ru6-w2VZ{gvMWs$iHaI|C82$ zKdv8Uov>eLEp9%ctI0Xr5Cp$g;Qq>*r^71!kkr4}=O3rJ5-Xeeb%WGuhw1E#oB1Ur zv4Sz)LbPqw=})#EYn9SNP+)BAY|A}g^LHQKL0TY{b*pUF6sAoH=8B5l%f`W&w6TQz epTetL&Vo^5OQxx;uZX1lE5Oa!lT_+NVg3!2n(aIQ diff --git a/docs/images/Logo256x256.png b/docs/images/Logo256x256.png deleted file mode 100644 index e3f5b33c0f3cd71dc0ccbba478865d57e9ee0e7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3378 zcmeHKdpy(c9{*16LQ?dj(M1f^Dn3T*li z=GeaUfzVE8z2el~N5O5m=gZz*dpY3WGo&HArm^q$`-&Zng{W)Kj_}^|PInEih<^AU zU~mh#6Mz0~-ZbcQWK!n1Ea42E9uE-WWdZ+PAYki$fN)U~I3gD9h6>PpL;~1l4*;$I z^`gR+hAoA2b4xl%==45#Sz^68K~w4UdKu-6Vn#+(;+y@JbfIZ*5Dw?jL)T#&bZ|Qm zz(}-?h5odjm;eKfYbFD+uWDV$eFoDuOc|NepEXJO8XB6)Z!DgBF#$YO7j|UQCZkJ^ z`35i9Jvf4YOo1`XAg)@CtnROCqz7BBU?dk#DZiKeK%9+fO$U4@Tkcz#UQUio;2V!l zYu!$R<|%J)m;vLF8)O_-$EchDEw3Pkc4mI+t#?<0@XXOREIFm7rweyQ`geZaV;W*g zEGOL^JZ;PE;Jbut5?2NKYo1w~+W0*jS6_MQoIGRXv}sIEehRU`vCUR=kjcHF&IIZc zIVyhgazW?Km7)f4X`)G_?nHXfg4?|c*NR_sB0=xEPJC0}k;LlV zNM76rKr{D8>+5}Ffe&xTmO(2@O{i|OU-YQ7gjr11x!v=3U@IZkEYok~rjPeb zNk7=4j&}LRe)n$UY=q%*mf-CG1_ZM5{%X>L?7Q+^S6KRzY_|TO^p8>6)!MO-Iy*8` z;$*eI{)lti*gni~I9-;0A--xGkbRvPC|p|MQwk_bk5`>Y5*=F>%hDO&ERWYiH>y)a zH|-)$RZuo%ly}t5>+A&}3i^6(%XRP|ThfG!^3H~gfYvc5-Sm-6sP`u4=r3Z?ZMxMD zUt!;Y?t@1s5ld<@ygCHLV>8aCO#~(rf#QQ6H!rEzpt{w2UN$?!&zc=S)rMdE6)faw z6@L0;bZh9w)xBuM1GnKi@-;DWfog?eXv5$b-C$I+VXU9;1@Xqjxv}5{(I^3Tc{nD0 z$UYR2u>&H61RndTi$3v}O6r=u_3TbNoRS$4fJ@k0(2! z;rodM5-0p%0GiXbHCr4`lI}zQV-X{vt*pc`d|>3EOH5_&^OR z3R#@Ng|d|hTt5Y+gigLg_N{{HmB@svVgDSAm{19!Kv|QmwH>0Vm@z+cXzB_esCOUc z)`1F=3MBr0#Cl$}X>(^N|9(kM4vE4}#+}%uE#g@8Gc)k1VWMWj`l_<}n4VQQx zh#}LwUwOm?#2^w5TOOxGuzXhDkzKgH@%>bju*mStAv3cYVarWjoENdmZ4p#XO$%YO z!;V+inQeTf&1*G$VV8OvJ;h#RZ;n6wh+fL2*3D-2UwBv+hW$2l zSyaax!!NxLTG`B{>N$@N<$ryBQxE|*KS0gZIpkgM_@FktkYXYI!3pjs>5KjNfj5yd z@uj_gxJsY0UNNzDXJR|nLx!-1LE$=R!=fI3 zpKGRGI2Z?Nv%b7=-;l9)QO$+=O33U+M3t?SmK zlG&R)b1AXmG9NUXo7rbc9QMA9>B5>s4OZqnop%*$`j1_=!NTu{{TH1|SEb(FyrGlv zJ|Lbm!ixz|7m3}INSE6BempdC&{o#p2HvFLdb=gT7uFa?y>!m&X^Q{o>-5wg8!|5$ zQS{08lT#!q^(-9n;z7yJVkX}{=Fa4!ovQbK9az7W?_-Q{SNVYI&;s2~voDpR_cooIRJ4ia^t4&Y0Yj@&~szB5aXcEL!r0k`xCTPjr6c*4I z)d0=6NQzcbetrTje}n<;d$B6%OXji(0q5*W>o9FASJu-Uu;YqM{R`d|5mb8cw;6N! zx|4F*C8P%F=cUPIxqcRA)=?KQg+pd>4`2Euu!iD8;l2AhpV2&j`oorX%f6Z`3^3VW zgN>a@<5;x4fi)gKEs!mK%Fx4U9I39mFguSbt&~G4zYc6^`^hm;ncvRP+ru;ziV;U` zh-w&st!*P~`x#iaV|>92I414>0^!`3a^vBq!#MCBF^ng3&&;rU%V?$A(v=x)FU2U2 zi{78a()q_9Ah;he-A=a*@86WWml9ulTHo zA%hfJ>0Sw4mKY*y!@KE$E+ER6rInghDs!%u)<=3`cgxdrM&RQ)iNv!61FD(ZD@)u~ z3|ASTk91UigPuQ2;EAjn{gY>Csj96ez6q2svcE3Qw)YKJ!B?=@!7LyvaeRuEjAXM( z_OM;cn?BWnj+tl>w%1oER4f2&T-qC>%jG!uqiNEG4iHt|EBA_C}>5T64$ zZGh0>@K>Ze|D!DG|HuBv8S4F6(85ND(w%hmnAR1QAm^gEcXLOH(W7m{mo0!Gk0N0{ zD?s8~bNZXH7M12@mcc%i_BjmktfxxaNuwt&O`UTXq;j)G*gTI4OBrd6%u>j%T3_N? zBz?{|$Fa4T+>;~qyEqIXv0c>9t~SU);Eq|k%3mcC8c`;qG?@W5VQ}e@Oacm|z+awI8pj4Kpnw(HvKK85j54eU}60$9Ldz z4zVl?PD*mL`!oQ!22ClSUjVT4_kDsg034?^?ZXxTz%GEp|8{xr)$rMCAni`Uqd%bI zPQg{agAq-@3@Tm#`{Mv6GLm|yjJIwTYsM^O{T8M!L#kF_d3`X0if6EvOVpZg8;Q9r zTr)xFas*%UBBZTlm!kHzgl!7saT%pJXU?hQ;<||Z6q0$g?g(6fMLW?mj%>kPPT8!c9`ljzs z&dve+B*WTm)EiB{0ONFDd75bBUpf923jhHi00e*l5CGE!KnS?sX%ZpbK|iz)1pNMk u=t|_svMes!G9koJ<^y{}Z{j$08sHld?qW534VLl%00004J!W5HLU>Ata7UFE#{`qA(z3 z6qPCviXuU(p%;@NNQVSWLLec@J(+vo?#q3;4|8wUI{D8z>o5D;<=cDhlVEFYwqu*j zHUPj5^Ghc700=|3!eHwbXkbJRu%ST|ao*fvEA)%rdLtfs77w{}4FSOR-GWP~j})^B zy_5|zbq>1%zZr(Se#aM_y8-w1J#^9kjxQ2^ClGPy!j)5V4Ih#Lkl~t}oO6iGUM8VK za+pyQtFv>Lo*fz8a^P^BWpl<~;-@mhyj@RJc)o+kHx?du zhS`rX(x}Q))*NGEsg-3+d`ro0GW{x?A;KchYtb3-4_Mm z@L>QBDF{Jm#R0f<2>|2s|C%X-(gw{pJ`@y8^~F2O!IBicty`vm?LjdmZ=}hRGnvfm z%#VAn2I;48ZvVEbf`S5dMMD^jub@bFUC{*tLupEr*o&?AI>*QLljMpn`HOg&nVBI> zqE9FqO2V|a%ZcFzMfuiay;D3%3EFT*LvJHSg9%v>LWguDd)sp9n^X z#8sNoWNiK{@an7ov8>}WW(fnZ)d%@vz^miZ`f$US5VU z=0E#GR;adWP6%Y)DdLVjQUGt&Q2-QW`!_}0*or%1Dgm0GYA+9`{%#}EPz+78X5RF4 z+MFC|GV!DJPIWiFo!cLIT-7eY{%L+ma74BfMJ8xR#ut4*H`wf8%}~rs3+#aWxb!r6 zxZo|{*2MQvzJs*L^fe+SI6Apbgj3LfbQ<8Y2aC}@*rz*@W)7;bLVJfS zSMQ02{8p>|O=sCpMn>tk8-_R?@f5`M&qv-@ z&X3hrt{OPzy?*6qb5ahaAcKl9E$KVGw5VFho(po3HS=@di@&_R%+PFO+JLz7`JmnK zM|dl3|E(ADcqh#?UT2w6uQ=oYmOA6>hYtY|u(ZcRS0B<;9L06{y$Ey31oyGLq4$_N-T#t6i-8&5m9|LkiDQBgtnN zqot}|(Pq&5T(o(Q3cx9buQkf{%6{KtCWdOmziF#0stxX8-wLdaPIfIeq2KlMnQ2K4 z?Y(ou8ZDcP4l+p&O8FbjOurSqAfF$`Xs`7vIoXq3FpNFAMMDPrG&`V9%sBQ55Zbm{ z=3|tEjiw)Pytb*Nn7WhEYSg%qvOf4eFNKXSw19U}HzORS`0?@2#q_-+W@`!@<_|K_ zolTojU7I~q|17dFOKa_-ot2yROyyoWe^D5`+_ICgoaDDNIPtCp*0Dz4c0Nt;Ac9fi zH=fPkT~Q~O7Mn|Q%qe{3hwNHFomIRT^RgzP4%Fk72!Y!tv&WCzeG zg)`%MH${PQIzDp!d1M(DdzrO*mCuV@UD2M6cIOhEN+U+Fy4n&ECR`%IbM)Iv?|4Dj zN_SR|z25rGlbiIlEUHi4F6jKQ8!z=^zSFtlqr++FMtQR>b-nB?$efqk)n>Gw>^*cw`oXE^_N!_olG`K3n&W?8? zC%j~r@+i`MK1=}!o%I2Kpyjj$oSz+07)R}U}y&lK+z4zwPPB?A_ zxcTBgY`?|C>Gor@j1V_V1!DKv4(mp(xvI|b@fjWWVjQ2_bzw8|*YpL`Ri8VaM)oZs zU%GJ#Q>m@CoHt$j`AW%z{9>;4UdW*=G;Y;K_AS*k{rFQ@c|WR2M7nyfmU-xZ$pXb! z!kOvqDq(qu4(W>s(u7SNMtjDr(81_cg@Xl?c-n_IL8<9Vyh~SEn`>|0Kos=ZciBBG zIS+sX@z3W@ms7dv9mMg<$n$>lK`v*1o&hizHwhbf-tYMw_p&K;ej?d`&b7;ZX+xg2 z$26d4IB{GK@tWiTCBJb`3c5V`sKh}rM<)$=ioD+u07`G&bhm7%nHQ1#P|iAqIdpTV z>UR!{T=alwoQAE!8@qoItc>$Jdm=cAzIdozc zX0NTrAfXCjD8np_MKo5IuRhlO<27CxksSi@14GB-4pn++m_S@K+GFQgP<|-@&-K3u zz(DhN_C_cvn`w8t%RJP_(MgpMju;|*XSeu`OU$l(4=kwhw{x7;eFY5=$t=fv8fno! z>7uxo2aV)kx}_EjQ}&3^mDg00ORS*X+P`R%_ckHOg#7n!^(bbihjhNsvRUtIBirgP zZuHp82H*=9VBf|okM67@?}ioH;mi4bos;b;H713jHyvk+A;MBRP2SDT@VJ`3>}ywQ zMMs~c&CO+!`Nm#-`e#G58u>$0s|)YleaLO5+EN6uo0LC(>hP7E5@tkt+q&)@oEm8h zHJhny;(o=3CGkrvNO9h6+LWYU$BpFar`Vbw)l%JAEm>~#rUpD_iC=rn7yzq>o^o9C zrKPA-9*Y>?R@%pRKOQ@eg$22dt>>0@+hG~$w?_F6Pt(Fv*?6w@SU#G}RO0kt?{{d@ z5@O}4@pU~IrI=ph@y$6rDvIH0xS1|Cbp#aY3IVHA@U`$SnyFmNur=OU^3q~r?%kUF z-RVb7k7X^~Bt$Xa5QjppH6YsdhI(KEyJw(4mQ0EBh>y*6w<~4r;M7@^7@}TC5s25E za+vl_tEKwyj5^RrQ&PESO5N!-v-uF#s*3nC3p@YdguC?FFU|TIiM-#Ee7u5#%0I+s z@`NTF%yHro(B7A&*B&hdxkN^vD`%K--(&2k7=s12#6j#|@Dkbo%(yM9AAoLUA9b8R3_|8$lm{XdHBj7dwhb%ZJJGz6T%(46@^9m~z(Icd7z`2}yH zE+k@d+=(R8!TPnO6#3<7e>cv@Sp+tEj7H`4Lw>UcGI^rL>D>YT3695|T6LhSG`%wp zLvp;ZN!M*dmc?F13eTvR>Hms9EX9CqD*_n^heIzji^nWU>47m*qvc zevpD(isSJN!e;eQE#kAE8J9d;9h4Rxcr|Y`iZrGp3~<-QajMP@A?I$_F7`NAPW%dm z2h#gp+Zc^a*vYT`!d`}mU1hgTRb;gK^Y~<$fOM1@U zmWTNmN_!3G;)$b!dxPG*QNh*(OedZY`wjHJ0Q=&6Ei+m(n!=!5guOm%xk`Fn(?%b! zmf)C2$G8Tnxz?QwcCOcRadGj6cJRrat+#=vj2p2ZJu#x7=$9cW6~u8TTp%gG(>Lnl zHpi)WO@fF724j*p!wQf1GSj2(c&+%f5q`Q72ry()hVOOS(Do3w=@`3gm8q!jVziX1TM!fH9x9Y9U3`Nj3G4uj1^Xq<7w`+A<8=9u+ zl}}lx_Pg3}HXt3hRptn60wJ-9p0;Fd6~g&n0?93qrVlXcNodbhF?m!i&fl7~S-nie ztgn2{8+UOf$O=K$$76Co$J$W#)5l$Uxm!SysF0S$;?UduzrOJyB>19??*fa52M)Ii z?6&x4OmRK>8HWX_$-P4qmpTTSQ zN!F*Hj95I0HbVW`^ImYyVutR?-XSmh>>!J#;$Cg-#jak5JbK+}}ra4k_@d z@t}D?8C<+x`{YLerjztz=SJRg8BW_~i)`m0fOP9Ug=eK56%F>px^@Nl?`Il;i(b}n>zDX)naoaZtDeL>qN-yM6#)l~p zW9eeBCh@zj@Wx|u=*h2WCJ?AGQzp~dz30xigmGUno?S6oPv6IMzFiYpZP zTKLFuX^`nG?6oJxxfv?37B^D+MJ6C;35dAFb5r;>G$wO^${u)#v={aA7RQCUREC@* zDrFBM#;fxpc*d3pdNEC0%jI=!^K%tNVXZS;ad!I_lKIbOX3Vil+1gE8YOgOZT=%$U z0C~>T8nfn$qugOptz+UiyOAm+?O{{-&?vL&*NCJ|?i$_EGGjo^Wu~U8P2g%0*yYA^ zD!*Mc35y8-lIHRK+7pb3)}6P)`yL1wXmn6#uDU_sf|c#o$^x^o$Qd^1`>SByRN`xa_%tCxC(Ee1+I$a zHFQo&XeB@pWT;Pg@>q6{Ip*_=+1{-g)k8y}rkx+9c+}5vQ;*;=0@Iw?Zqy7l051A_ z`Z=9#3L?Cu)Kpcssqj`(g_UKVUm%h0X>_}?T67A?J(CZ%U#wiAtbVPZd~`W2bph52 ztYl}Q&=bLOn9dQ?kDnk%^ske$22YfIJaUSR#12X~UQ<^uXq23Io=Y2v8Oz6XckEsD zvh0k%>0TXX9=)XI$3)5-%BC5t-q_?y+PE-;ihz*T76COtC$y;;8m>$7A>y6lrSQY8 zR2tMhY7a&12rc;mQR#X|2KCJH&(G2kjeC&%`Mk6M4&+WD-ui<7>NDz_p#KTjS3t;( z{6k9JP3!0N6-i_RXM~;B3K^Y%I5_SDsaj0^p@4Zzw@bBtIG!Qkc@s+nBKoHUFXY=I z6g%TDk4wCDsXIIb1>{0|WbpXfI-bJqz9Iy7u1B>8sX_7>ZmjC}pbqk9)Lo}q;^eAV zM6AvUd&!<6qp>Ef?=l*zAa&BX)XT5cYiB>)o_~BUjultm+pIIY&M($py#_4+|&gxf7d@MHlFi)&SK|-qtr83!UC~nsW z{^3)x9NY+$K6~NgFp16OVRbg>w=J^8rspTGMS8#25rVYx%%$;Yehj z+wC~LGa*L@YkyyHU`11@5GQPW%{vVrMg7X&0u&qR$QKr-y|$LAU|=NS0ncnIT8;XO zN-ZVZ!J%Ymy&YGkvhgNas8~bj;efz|P*vf{*aw4!LK!6Ite*XL5{dMLef=^p-39gp z7ave{d;x%NPT4=DhVhTmaES-`)e>5JH@4wEVb*=*HBq9tFj=90e-G^6*VX%fME_U% zxBmw${jE|0S7%=`2LqYjfBb#0-w;4GI6Em<2v>6C^Vo@Hk%`SS6_leWs9t^cd!X5$ zAd`MC-w${Ne0>*jcq?v{C1oH2u2{xa6*!bQB<8-F;q|M~P>aLv?iGnpXZI`Z;F~vb zjl5<|MN)3TP!Sh~i;wTrz(Tc*fP|>9zSXn0+q2jRL`H1HsX(( zCFT0&7yb@Or^z35()>Qm9OJXUQVrzi78j4BWP!3R27_tvBYvXw!tpB<%vzcu-GxZs zqYwnLS28>|%8qa-kyB)+7(9)#gq=#+Z`vzjyxgVF-}K37@Zs@uki3mN-X{{4VGpf3 zh5^|NnoS=IW`k@bIz}(c@4nJ za0`JjiZTPcKxSN-)QPPs)23l}wCRD30H&9g~?>y<>w zY7Hxwmi7hwT%8&xbUN((nr^uk26pUvVIqF>curnkwRMQ{;(If5|81|STfSF*&!2hn z=YQ{c&wW|vbdup>Z*GspOde2T^9$w~W$f`^zP@GkV?HhJrvAK!$uB1(Z{OGD!a2+g za~rgmd;NDfH0xQikyGnVq2OYM1Qm79*QpV|gc!EGNZ%6CQ?iuZWbK^%)W<*_kE#}L>uzoSu`o&tC=bD}V`RJa>>p4$u{o224S=x8g z8{6{#*MD4D@&4IXdq39Ok_NK<&+jcepfStUApT4xuSNAM6^6wN1*Wf$d3xia^PLyF zd@GbDneS{(@MDbHp0jLweZ%g%wlm*u{3xI+J^RI5`2~+1tjl-H8;I1*48K`pusr&} z^REV0a)WEPG-r23=d@?b&gf{WJrhaDu<$s3u#D*+TUPces3>DMO3$#HI*kxw|iC-B13I-qxgFU)|rXt`yMn{U3kPKeZpPqXJ*({QAxy>Ja51 zMIGWcLtCQG6!&{4%-C)-^nNYs1blWW)SZnr-E&EAP2{apMCAGOJ^i?X&k z%vkpG)sKtH@86s|DfOU!dByYcz5vE0tV;qx#80jzu~#frC#il0<|zhGS3j3^P6 - /// Represents the abstract base class that all argument types have to implement. - /// - public abstract class Argument - { - #region Properties - - ///

- /// Gets the name of the argument. - /// - public string Name { get; protected set; } - - /// - /// Gets the alias name of the argument. - /// - public string Alias { get; protected set; } - - /// - /// Contains the name that the argument will have in the result dictionary after parsing. This must adhere to normal C# naming standards. - /// - private string destination; - - /// - /// Gets the name that the argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// - public string Destination - { - get => this.destination; - protected set => this.destination = value.ToCamelCasePropertyName(); - } - - /// - /// Gets the descriptive help text for the argument, which is used in the help string. - /// - public string Help { get; protected set; } - - /// - /// Gets the type of the argument. - /// - public Type Type { get; protected set; } - - #endregion - } -} diff --git a/source/Arguments/FlagArgument.cs b/source/Arguments/FlagArgument.cs deleted file mode 100644 index 4aa85d0..0000000 --- a/source/Arguments/FlagArgument.cs +++ /dev/null @@ -1,63 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.Reflection; - -#endregion - -namespace System.CommandLine.Arguments -{ - /// - /// Represents a single flag argument of a command line parser. In contrast to named arguments, flag arguments, cannot be explicitly assigned a value, but rather get their value from their presence or absence. - /// - /// - /// The type of the argument. May only be a boolean, integer, or enumeration type. When the type is boolean, then only the presence or absence of the flag is determined. If the type is an integer type (i.e. byte, sbyte, - /// short, ushort, int, uint, long, or ulong), then the number of occurrences is determined. But if the type is an enumeration type, then the number of occurrences is interpreted as the enumeration value. For example - /// consider the following enumeration type: enum Severity { None = 0, Low = 1, Medium = 2, High = 3 }, which is the type of the flag argument named "severity" with the alias "s", then the following command line - /// argument "-sss" would parse to an enumeration value of Severity.High. - /// - public class FlagArgument : Argument - { - #region Constructors - - /// - /// Initializes a new instance. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// The name that the argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// A descriptive help text for the argument, which is used in the help string. - /// If either the name or the destination are null, empty, or only consist of white spaces, then an is thrown. - public FlagArgument(string name, string alias, string destination, string help) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(destination)) - throw new ArgumentNullException(nameof(destination)); - - // Stores the arguments for later use - this.Name = name; - this.Alias = alias; - this.Destination = destination; - this.Help = help; - this.Type = typeof(T); - - // Checks if the type of the flag argument is allowed - if (!FlagArgument.allowedTypes.Contains(this.Type) && !this.Type.GetTypeInfo().IsEnum) - throw new ArgumentException("The specified type is not allowed for flag arguments.", nameof(T)); - } - - #endregion - - #region Private Static Fields - - /// - /// Contains a list of all the types that are allowed to be used with . - /// - private static List allowedTypes = new List { typeof(bool), typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong) }; - - #endregion - } -} diff --git a/source/Arguments/NamedArgument.cs b/source/Arguments/NamedArgument.cs deleted file mode 100644 index 0d8c9e1..0000000 --- a/source/Arguments/NamedArgument.cs +++ /dev/null @@ -1,81 +0,0 @@ - -#region Using Directives - -using System.CommandLine.ValueConverters; -using System.Reflection; - -#endregion - -namespace System.CommandLine.Arguments -{ - /// - /// Represents a single named argument of a command line parser. A named argument is a command line argument that is comprised of a name and an explicit value. - /// - /// The type of the argument. - public class NamedArgument : Argument - { - #region Constructors - - /// - /// Initializes a new instance. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// The name that the argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// A descriptive help text for the argument, which is used in the help string. - /// The value that the argument receives if it was not detected by the parser. - /// A callback function, which is invoked when the same argument was specified more than once. - /// If either the name or the destination are null, empty, or only consist of white spaces, then an is thrown. - public NamedArgument(string name, string alias, string destination, string help, T defaultValue, Func duplicateResolutionPolicy) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(destination)) - throw new ArgumentNullException(nameof(destination)); - - // Stores the arguments for later use - this.Name = name; - this.Alias = alias; - this.Destination = destination; - this.Help = help; - this.DefaultValue = defaultValue; - this.DuplicateResolutionPolicy = duplicateResolutionPolicy != null ? new Func((oldValue, newValue) => duplicateResolutionPolicy((T)oldValue, (T)newValue)) : this.ResolveDuplicateValues; - this.Type = typeof(T); - } - - #endregion - - #region Public Properties - - /// - /// Gets the value that the argument receives if it was not detected by the parser. - /// - public T DefaultValue { get; private set; } - - /// - /// Gets a callback function, which is invoked when the same argument was specified more than once. - /// - public Func DuplicateResolutionPolicy { get; private set; } - - #endregion - - #region Private Methods - - /// - /// This the default duplicate resolution strategy. If the type parameter is a supported collection type, then the old and the new value are merged into a single collection, otherwise the new value - /// always wins. - /// - /// The old value. - /// The new value. - /// Returns the resolved value. - private object ResolveDuplicateValues(object oldValue, object newValue) - { - if (CollectionHelper.IsSupportedCollectionType(oldValue.GetType()) && CollectionHelper.IsSupportedCollectionType(newValue.GetType())) - return CollectionHelper.Merge(oldValue.GetType(), oldValue, newValue); - return newValue; - } - - #endregion - } -} diff --git a/source/Arguments/PositionalArgument.cs b/source/Arguments/PositionalArgument.cs deleted file mode 100644 index 5d94428..0000000 --- a/source/Arguments/PositionalArgument.cs +++ /dev/null @@ -1,40 +0,0 @@ - -namespace System.CommandLine.Arguments -{ - /// - /// Represents a single positional argument of a command line parser. A positional argument, in contrast to a named or flag argument, is an argument, which is only comprised of a value. They are not optional and must - /// be at the beginning of the command line arguments in exactly the same order as they were declared. - /// - /// The type of the positional argument. - public class PositionalArgument : Argument - { - #region Constructors - - /// - /// Initializes a new instance. - /// - /// The name of the positional argument, which is used in the help string. - /// - /// The name that the positional argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// - /// A descriptive help text for the argument, which is used in the help string. - /// If either the name or the destination are null, empty, or only consist of white spaces then an is thrown. - public PositionalArgument(string name, string destination, string help) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(destination)) - throw new ArgumentNullException(nameof(destination)); - - // Stores the arguments for later use - this.Name = name; - this.Alias = name; - this.Destination = destination; - this.Help = help; - this.Type = typeof(T); - } - - #endregion - } -} diff --git a/source/Arguments/StringExtensions.cs b/source/Arguments/StringExtensions.cs deleted file mode 100644 index 600b76b..0000000 --- a/source/Arguments/StringExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ - -#region Using Directives - -using System.Text; -using System.Text.RegularExpressions; - -#endregion - -namespace System.CommandLine.Arguments -{ - /// - /// Represents a set of extension methods for the class. - /// - internal static class StringExtensions - { - #region Extension Methods - - /// - /// Converts an arbitrary string into a valid C# property name in camel case. - /// - /// The string that is to be converted into a valid C# property name. - /// If the specified string is either null, empty, or only consists of white spaces, then an is thrown. - /// Returns a valid C# property name in camel case. - public static string ToCamelCasePropertyName(this string name) - { - // Validates the parameters - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - - // Makes sure that the name is a valid C# name by first removing all characters that are not allowed, except for whitespaces, underscores, and dashes (they are needed to camel case the name) - const string firstCharacterRegex = @"\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}"; - const string extendedCharacterRegex = @"\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Cf}"; - Regex invalidCharacters = new Regex(string.Format(@"[^{0}{1} \-_]", firstCharacterRegex, extendedCharacterRegex)); - name = invalidCharacters.Replace(name, string.Empty); - - // Adds spaces at the end of strings of digits, to make sure that the following characters are correctly capitalized - StringBuilder stringBuilder = new StringBuilder(); - for (int index = 0; index < name.Length; index++) - { - char currentCharacter = name[index]; - stringBuilder.Append(currentCharacter); - if (index == name.Length - 1) - break; - char nextCharacter = name[index + 1]; - if (char.IsDigit(currentCharacter) && !char.IsDigit(nextCharacter)) - stringBuilder.Append(" "); - } - name = stringBuilder.ToString(); - - // Camel cases the name - stringBuilder = new StringBuilder(); - foreach (string part in name.Split(' ', '-', '_')) - { - if (part.Length == 0) - continue; - stringBuilder.Append(part.Substring(0, 1).ToUpperInvariant()); - stringBuilder.Append(part.Substring(1)); - } - name = stringBuilder.ToString(); - - // Makes sure that the first character of the name is a letter - if (char.IsDigit(name[0])) - return $"_{name}"; - return name; - } - - #endregion - } -} diff --git a/source/Command.cs b/source/Command.cs deleted file mode 100644 index dc202d1..0000000 --- a/source/Command.cs +++ /dev/null @@ -1,119 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.CommandLine.Arguments; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -#endregion - -namespace System.CommandLine -{ - /// - /// Represents a command. A command is a sub-parser, which allows specialized operations to be performed. - /// - public class Command - { - #region Constructors - - /// - /// Initializes a new instance. - /// - /// The name of the command. - /// The alias of the command, which can be used as an alternative to the name. - /// The sub-parser that is responsible for parsing the arguments of the command. - public Command(string name, string alias, Parser subParser) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (subParser == null) - throw new ArgumentNullException(nameof(subParser)); - - // Stores the arguments for later use - this.Name = name; - this.Alias = alias; - this.SubParser = subParser; - } - - /// - /// Initializes a new instance. - /// - /// The name of the command. - /// The sub-parser that is responsible for parsing the arguments of the command. - public Command(string name, Parser subParser) - : this(name, null, subParser) - {} - - /// - /// Initializes a new instance. - /// - /// The name of the command. - /// The alias of the command, which can be used as an alternative to the name. - /// A descriptive help text for the command, which is used in the help string. - /// The options of the sub-parser that is responsible for parsing the arguments of the command. - public Command(string name, string alias, string description, ParserOptions parserOptions) - : this(name, alias, new Parser(description, parserOptions)) - {} - - /// - /// Initializes a new instance. - /// - /// The name of the command. - /// The alias of the command, which can be used as an alternative to the name. - /// A descriptive help text for the command, which is used in the help string. - public Command(string name, string alias, string description) - : this(name, alias, description, new ParserOptions()) - {} - - /// - /// Initializes a new instance. - /// - /// The name of the command. - /// A descriptive help text for the command, which is used in the help string. - public Command(string name, string description) - : this(name, null, description, new ParserOptions()) - {} - - /// - /// Initializes a new instance. - /// - /// The name of the command. - /// The options of the sub-parser that is responsible for parsing the arguments of the command. - public Command(string name, ParserOptions parserOptions) - : this(name, null, null, parserOptions) - {} - - /// - /// Initializes a new instance. - /// - /// The name of the command. - public Command(string name) - : this(name, null, null, new ParserOptions()) - {} - - #endregion - - #region Public Properties - - /// - /// Gets or sets the name of the command. - /// - public string Name { get; protected set; } - - /// - /// Gets or sets the alias of the command. An alias can be used as an alternative to the name of the command. - /// - public string Alias { get; protected set; } - - /// - /// Gets or sets the sub-parser that is responsible for parsing the arguments of the command. - /// - public Parser SubParser { get; protected set; } - - #endregion - } -} diff --git a/source/Parser.cs b/source/Parser.cs deleted file mode 100644 index 15abf0b..0000000 --- a/source/Parser.cs +++ /dev/null @@ -1,777 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.CommandLine.Arguments; -using System.CommandLine.ValueConverters; -using System.Linq; - -#endregion - -namespace System.CommandLine -{ - /// - /// Represents a parser for command line arguments. - /// - public class Parser - { - #region Constructors - - /// - /// Initializes a new instance. - /// - public Parser() - : this(null, new ParserOptions()) - { - } - - /// - /// Initializes a new instance. - /// - /// The description of the parser. If this is a root parser, then this is the description of the application. Otherwise this it the description for the command. - public Parser(string description) - : this(description, new ParserOptions()) - { - } - - /// - /// Initializes a new instance. - /// - /// The options of the command line parser. - /// If the options are null, then an is thrown. - public Parser(ParserOptions options) - : this(null, options) - { - } - - /// - /// Initializes a new instance. - /// - /// The description of the parser. If this is a root parser, then this is the description of the application. Otherwise this it the description for the command. - /// The options of the command line parser. - /// If the options are null, then an is thrown. - public Parser(string description, ParserOptions options) - { - // Validates the arguments - if (options == null) - throw new ArgumentNullException(nameof(options)); - - // Stores the arguments for later use - this.Description = description; - this.Options = options; - } - - #endregion - - #region Public Properties - - /// - /// Gets the description of the parser. If this is a root parser, then this is the description of the application. Otherwise this it the description for the command. - /// - public string Description { get; private set; } - - /// - /// Gets the positional arguments of the parser. - /// - public IEnumerable PositionalArguments { get; private set; } = new List(); - - /// - /// Gets the named arguments of the parser. - /// - public IEnumerable NamedArguments { get; private set; } = new List(); - - /// - /// Gets the flag arguments of the parser. - /// - public IEnumerable FlagArguments { get; private set; } = new List(); - - /// - /// Gets the commands of the parser (which in turn consist of a sub-parser, which is able to parse the arguments of the command). - /// - public IEnumerable Commands { get; private set; } = new List(); - - /// - /// Gets or sets the options of the command line parser. - /// - public ParserOptions Options { get; private set; } - - #endregion - - #region Private Methods - - /// - /// Checks if the specified argument has a valid name and whether another argument with the same name or alias already exists and throws an exception if so. - /// - /// - /// If the name or alias of the argument is invalid or there already is a named argument, a flag argument, or a positional argument with the same name or alias as the specified argument, then an - /// is thrown. - /// - private void AssertArgumentIsValidAndUnique(Argument argument) - { - // Checks if the name of the argument starts with one of the argument prefixes or contains the key value separator, if so, then an exception is thrown - if (argument.Name.StartsWith(this.Options.ArgumentPrefix)) - throw new InvalidOperationException($"The name of the argument {argument.Name} must not start with the argument prefix {this.Options.ArgumentPrefix}."); - if (!string.IsNullOrWhiteSpace(argument.Alias) && argument.Alias.StartsWith(this.Options.ArgumentPrefix)) - throw new InvalidOperationException($"The alias of the argument {argument.Alias} must not start with the argument prefix {this.Options.ArgumentAliasPrefix}."); - if (argument.Name.Contains(this.Options.KeyValueSeparator)) - throw new InvalidOperationException($"The name of the argument {argument.Name} must not contain the key value separator {this.Options.KeyValueSeparator}."); - if (!string.IsNullOrWhiteSpace(argument.Alias) && argument.Alias.Contains(this.Options.KeyValueSeparator)) - throw new InvalidOperationException($"The alias of the argument {argument.Alias} must not contain the key value separator {this.Options.KeyValueSeparator}."); - - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // Checks if there are any arguments with the same name - if (this.NamedArguments.Any(namedArgument => string.Equals(namedArgument.Name, argument.Name, stringComparison))) - throw new InvalidOperationException($"There already is a named argument with the name {argument.Name}."); - if (this.FlagArguments.Any(flagArgument => string.Equals(flagArgument.Name, argument.Name, stringComparison))) - throw new InvalidOperationException($"There already is a flag argument with the name {argument.Name}."); - if (this.PositionalArguments.Any(positionalArgument => string.Equals(positionalArgument.Name, argument.Name, stringComparison))) - throw new InvalidOperationException($"There already is a positional argument with the name {argument.Name}."); - - // Checks if there are any arguments with the same destination - if (this.NamedArguments.Any(namedArgument => string.Equals(namedArgument.Destination, argument.Destination, stringComparison))) - throw new InvalidOperationException($"There already is a named argument with the destination {argument.Destination}."); - if (this.FlagArguments.Any(flagArgument => string.Equals(flagArgument.Destination, argument.Destination, stringComparison))) - throw new InvalidOperationException($"There already is a flag argument with the destination {argument.Destination}."); - if (this.PositionalArguments.Any(positionalArgument => string.Equals(positionalArgument.Destination, argument.Destination, stringComparison))) - throw new InvalidOperationException($"There already is a positional argument with the destination {argument.Destination}."); - - // Checks if there are any other arguments with the same alias - if (!string.IsNullOrWhiteSpace(argument.Alias)) - { - if (this.NamedArguments.Any(namedArgument => string.Equals(namedArgument.Alias, argument.Alias, stringComparison))) - throw new InvalidOperationException($"There already is a named argument with the alias {argument.Alias}."); - if (this.FlagArguments.Any(flagArgument => string.Equals(flagArgument.Alias, argument.Alias, stringComparison))) - throw new InvalidOperationException($"There already is a flag argument with the alias {argument.Alias}."); - } - } - - /// - /// Parses the token and checks if it references a named argument. - /// - /// The token that is to be parsed. - /// - /// This out parameter will be assigned the named argument which was referenced by the token. If the token does not match any named argument then this out parameter will be set to null. - /// - /// Returns true if the token references a named argument and false otherwise. - private bool ParseNamedArgument(string token, out Argument namedArgument, out string namedArgumentValue) - { - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // If the named argument could not be parsed, then all out parameters are set to null - namedArgument = null; - namedArgumentValue = null; - - // For named arguments the token may contain not only the argument reference, but the value as well (e.g. "--age=18"), so the value has to be separated out - string namedArgumentReference = token; - string value = null; - if (namedArgumentReference.IndexOf(this.Options.KeyValueSeparator) > 0) - { - string[] tokenElements = token.Split(new string[] { this.Options.KeyValueSeparator }, StringSplitOptions.None); - if (tokenElements.Length != 2) - return false; - namedArgumentReference = tokenElements[0]; - value = tokenElements[1]; - } - - // Checks if there is a named argument with the name or alias that is specified in the token - if (this.NamedArguments.Any(argument => namedArgumentReference.Equals(string.Concat(this.Options.ArgumentPrefix, argument.Name), stringComparison)) || - this.NamedArguments.Any(argument => namedArgumentReference.Equals(string.Concat(this.Options.ArgumentAliasPrefix, argument.Alias), stringComparison))) - { - namedArgument = this.NamedArguments.Single(argument => - namedArgumentReference.Equals(string.Concat(this.Options.ArgumentPrefix, argument.Name), stringComparison) || - namedArgumentReference.Equals(string.Concat(this.Options.ArgumentAliasPrefix, argument.Alias), stringComparison) - ); - namedArgumentValue = value; - return true; - } - - // Since the token neither matches the name of a named argument nor an alias of a named argument, the token does not represent a named argument - return false; - } - - /// - /// Parses the specified token and checks if it references a flag argument. - /// - /// The token that is to be parsed. - /// - /// This out parameter will be assigned the flag argument which was referenced by the token. If the token does not match any flag argument then this out parameter will be set to null. - /// - /// Returns true if the token references a flag argument and false otherwise. - private bool ParseFlagArgument(string token, out Argument flagArgument) - { - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // Checks if there is a flag argument with the name or alias that is specified in the token - if (this.FlagArguments.Any(argument => token.Equals(string.Concat(this.Options.ArgumentPrefix, argument.Name), stringComparison)) || - this.FlagArguments.Any(argument => token.Equals(string.Concat(this.Options.ArgumentAliasPrefix, argument.Alias), stringComparison))) - { - flagArgument = this.FlagArguments.Single(argument => - token.Equals(string.Concat(this.Options.ArgumentPrefix, argument.Name), stringComparison) || - token.Equals(string.Concat(this.Options.ArgumentAliasPrefix, argument.Alias), stringComparison) - ); - return true; - } - - // Since the token neither matches the name of a flag argument nor an alias of a flag argument, the token does not represent a flag argument - flagArgument = null; - return false; - } - - /// - /// Parses the specified token and checks if it is a multi-character flag argument and references multiple flag arguments. - /// - /// The token that is to be parsed. - /// - /// This out parameter will be assigned a dictionary with all the flag arguments and the number of their occurrences that were referenced by the token. If the token is not a valid multi-character flag argument, - /// then this out parameter will be set to null. - /// - /// Returns true if the token is a multi-character flag argument and false otherwise. - private bool ParseMultiCharacterFlagArgument(string token, out IDictionary flagArgumentOccurrences) - { - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // Checks if the token starts with the argument alias prefix, if not, then the token cannot reference a multi-character flag - flagArgumentOccurrences = null; - if (!token.StartsWith(this.Options.ArgumentAliasPrefix)) - return false; - - // Cycles through all flags and tries to match them with flag arguments - Dictionary occurrences = new Dictionary(); - foreach (string flag in token.Replace(this.Options.ArgumentAliasPrefix, string.Empty).Select(character => character.ToString())) - { - // Gets the corresponding argument for the flag - Argument flagArgument = this.FlagArguments.FirstOrDefault(argument => argument.Alias.Equals(flag, stringComparison)); - - // If no flag argument could be found, then the multi-character flag argument does not match, at least not entirely, and is therefore invalid - if (flagArgument == null) - return false; - - if (occurrences.ContainsKey(flagArgument)) - occurrences[flagArgument] += 1; - else - occurrences.Add(flagArgument, 1); - } - - // Since all flags in the multi-character flag were matched with a flag argument, the multi-character flag argument is valid - flagArgumentOccurrences = occurrences; - return true; - } - - /// - /// Parses the specified token and checks if it references a command. - /// - /// The token that is to be parsed. - /// This out parameter will be assigned the command which was referenced by the token. If the token does not match any command, then this out parameter will be set to null. - /// Returns true if the token references a command and false otherwise. - private bool ParseCommand(string token, out Command command) - { - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // Checks if there is a command with the name or alias that is specified in the token - if (this.Commands.Any(c => token.Equals(c.Name, stringComparison)) || this.Commands.Any(c => token.Equals(c.Alias, stringComparison))) - { - command = this.Commands.Single(c => token.Equals(c.Name, stringComparison) || token.Equals(c.Alias, stringComparison)); - return true; - } - - // Since the token neither matches the name of a command nor an alias of a command, the token does not represent a command - command = null; - return false; - } - - /// - /// Determines if the specified token references a named argument, a flag argument, a multi-character flag argument, or a command. - /// - /// The token that is to be parsed. - /// Returns true if the token references a named argument, a flag argument, a multi-character flag argument, or a command and false otherwise. - public bool IsArgumentOrCommand(string token) - { - // Checks if the token references a named argument, a flag argument, a multi-character flag argument, or a command, if so, true is returned - if (this.ParseNamedArgument(token, out Argument _, out string _)) - return true; - if (this.ParseFlagArgument(token, out Argument _)) - return true; - if (this.ParseMultiCharacterFlagArgument(token, out IDictionary _)) - return true; - if (this.ParseCommand(token, out Command _)) - return true; - - // Since the neither references a named argument, a flag argument, a multi-character flag argument, nor a command, false is returned - return false; - } - - /// - /// Parses the command line arguments by matching them to the declared arguments and commands. - /// - /// A queue, which contains the tokens. - /// Returns the parsing results, which is a bag of arguments. - private ParsingResults Parse(Queue tokenQueue) - { - // Creates new parsing results, which will hold the parsed argument values - ParsingResults parsingResults = new ParsingResults(); - - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // At first the positional arguments have to be parsed, because they are non-optional and have to be there - foreach (Argument positionalArgument in this.PositionalArguments) - { - // Checks if there are still enough tokens left in the command line arguments - if (!tokenQueue.Any()) - throw new InvalidOperationException($"The non-optional positional argument {positionalArgument.Name} is missing."); - - // Adds the value of the positional argument to the parsing results - parsingResults.Add(positionalArgument, ValueConverter.Convert(positionalArgument.Type, tokenQueue.Dequeue())); - } - - // Named arguments have default values, so a list is needed to keep track of all the named arguments that have not been parsed - List missingNamedArguments = this.NamedArguments.ToList(); - - // Since flag arguments get their value from being present or absent, all flag values get a value (missing flags just get their "absent" value, e.g. false), there number of occurrences have to be recorded - Dictionary flagArgumentOccurrences = new Dictionary(); - foreach (Argument flagArgument in this.FlagArguments) - flagArgumentOccurrences.Add(flagArgument, 0); - - // While there are still token in the queue, the named arguments, flag arguments, and commands have to be parsed - while (tokenQueue.Any()) - { - // Checks if the next token references a named argument, a flag argument, a multi-character flag argument, a command, or is an invalid token - string nextToken = tokenQueue.Dequeue(); - if (this.ParseNamedArgument(nextToken, out Argument namedArgument, out string namedArgumentValue)) - { - // Named arguments may have more than one value (which is helpful when dealing with lists or custom duplicate resolution policies), therefore the next tokens are interpreted as values as long as - // they do not reference any arguments or commands (but at least the first token after the named parameter is used as a value) - if (namedArgumentValue != null) - parsingResults.Add(namedArgument, ValueConverter.Convert(namedArgument.Type, namedArgumentValue)); - else - parsingResults.Add(namedArgument, ValueConverter.Convert(namedArgument.Type, tokenQueue.Dequeue())); - while (tokenQueue.Any() && !this.IsArgumentOrCommand(tokenQueue.Peek())) - parsingResults.Add(namedArgument, ValueConverter.Convert(namedArgument.Type, tokenQueue.Dequeue())); - - // Since the named argument was parsed it is not missing from the command line arguments and the default value does not need to be set - missingNamedArguments.Remove(namedArgument); - } - else if (this.ParseFlagArgument(nextToken, out Argument flagArgument)) - { - flagArgumentOccurrences[flagArgument] += 1; - } - else if (this.ParseMultiCharacterFlagArgument(nextToken, out IDictionary occurrences)) - { - foreach (Argument argument in occurrences.Keys) - flagArgumentOccurrences[argument] += occurrences[argument]; - } - else if (this.ParseCommand(nextToken, out Command command)) - { - ParsingResults subResults = command.SubParser.Parse(tokenQueue); - parsingResults.AddCommand(command, subResults); - break; - } - else - { - // Since the next token in the queue is neither a named argument or flag argument, nor a command, the token is erroneous and an exception is thrown - throw new InvalidOperationException($"Unexpected token {nextToken}. This token is neither an argument nor a command."); - } - } - - // Adds the default values of all named arguments that were not in the command line arguments - foreach (Argument namedArgument in missingNamedArguments) - { - Type namedArgumentType = namedArgument.GetType(); - if (namedArgumentType.GetGenericTypeDefinition() != typeof(NamedArgument<>)) - continue; - object namedArgumentDefaultValue = namedArgumentType.GetProperty(nameof(NamedArgument.DefaultValue)).GetValue(namedArgument); - parsingResults.Add(namedArgument, namedArgumentDefaultValue); - } - - // Adds the value of the flag arguments - foreach (Argument flagArgument in flagArgumentOccurrences.Keys) - parsingResults.Add(flagArgument, ValueConverter.Convert(flagArgument.Type, flagArgumentOccurrences[flagArgument].ToString())); - - // Returns the result of the parsing - return parsingResults; - } - - #endregion - - #region Public Methods - - /// - /// Adds a positional argument to the command line parser. - /// - /// The name of the positional argument, which is used in the help string. - /// The type of the positional argument. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name, then an is thrown. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddPositionalArgument(string name) => this.AddPositionalArgument(name, name, null); - - /// - /// Adds a positional argument to the command line parser. - /// - /// The name of the positional argument, which is used in the help string. - /// A descriptive help text for the argument, which is used in the help string. - /// The type of the positional argument. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name, then an is thrown. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddPositionalArgument(string name, string help) => this.AddPositionalArgument(name, name, help); - - /// - /// Adds a positional argument to the command line parser. - /// - /// The name of the positional argument, which is used in the help string. - /// - /// The name that the positional argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// - /// A descriptive help text for the argument, which is used in the help string. - /// If either the name or the destination is null, empty, only consists of white spaces, then a is thrown. - /// If there already is an argument with the same name, then an is thrown. - /// The type of the positional argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddPositionalArgument(string name, string destination, string help) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(destination)) - throw new ArgumentNullException(nameof(destination)); - - // Checks if there is already an argument with the same name - PositionalArgument newPositionalArgument = new PositionalArgument(name, destination, help); - this.AssertArgumentIsValidAndUnique(newPositionalArgument); - - // Adds the positional argument to the parser - (this.PositionalArguments as List).Add(newPositionalArgument); - - // Returns this parser, so that method invocations can be chained - return this; - } - - /// - /// Adds a named argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddNamedArgument(string name) => this.AddNamedArgument(name, null, name, null, default(T), null); - - /// - /// Adds a named argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddNamedArgument(string name, string alias) => this.AddNamedArgument(name, alias, name, null, default(T), null); - - /// - /// Adds a named argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// A descriptive help text for the argument, which is used in the help string. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddNamedArgument(string name, string alias, string help) => this.AddNamedArgument(name, alias, name, help, default(T), null); - - /// - /// Adds a named argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// The name that the argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// A descriptive help text for the argument, which is used in the help string. - /// If either the name or the destination is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddNamedArgument(string name, string alias, string destination, string help) => this.AddNamedArgument(name, alias, destination, help, default(T), null); - - /// - /// Adds a named argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// The name that the argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// A descriptive help text for the argument, which is used in the help string. - /// The value that the argument receives if it was not detected by the parser. - /// If either the name or the destination are null, empty, or only consist of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddNamedArgument(string name, string alias, string destination, string help, T defaultValue) => this.AddNamedArgument(name, alias, destination, help, defaultValue, null); - - /// - /// Adds a named argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// The name that the argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// A descriptive help text for the argument, which is used in the help string. - /// The value that the argument receives if it was not detected by the parser. - /// A callback function, which is invoked when the same argument was specified more than once. - /// If either the name or the destination are null, empty, or only consist of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddNamedArgument(string name, string alias, string destination, string help, T defaultValue, Func duplicateResolutionPolicy) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(destination)) - throw new ArgumentNullException(nameof(destination)); - - // Checks if there is already an argument with the same name or alias - NamedArgument newNamedArgument = new NamedArgument(name, alias, destination, help, defaultValue, duplicateResolutionPolicy); - this.AssertArgumentIsValidAndUnique(newNamedArgument); - - // Adds the argument to the parser - (this.NamedArguments as List).Add(newNamedArgument); - - // Returns this parser, so that method invocations can be chained - return this; - } - - /// - /// Adds a flag argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddFlagArgument(string name) => this.AddFlagArgument(name, null, name, null); - - /// - /// Adds a flag argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddFlagArgument(string name, string alias) => this.AddFlagArgument(name, alias, name, null); - - /// - /// Adds a flag argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// A descriptive help text for the argument, which is used in the help string. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddFlagArgument(string name, string alias, string help) => this.AddFlagArgument(name, alias, name, help); - - /// - /// Adds a flag argument to the command line parser. - /// - /// The name of the argument, which is used for parsing and in the help string. - /// The alias name of the argument. - /// The name that the argument will have in the result dictionary after parsing. This should adhere to normal C# naming standards. If it does not, it is automatically converted. - /// A descriptive help text for the argument, which is used in the help string. - /// If either the name or the destination are null, empty, or only consist of white spaces, then an is thrown. - /// If there already is an argument with the same name or the same alias, then an is thrown. - /// The type of the argument. - /// Returns this command line parser so that method invocations can be chained. - public Parser AddFlagArgument(string name, string alias, string destination, string help) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(destination)) - throw new ArgumentNullException(nameof(destination)); - - // Checks if there is already an argument with the same name or alias - FlagArgument newFlagArgument = new FlagArgument(name, alias, destination, help); - this.AssertArgumentIsValidAndUnique(newFlagArgument); - - // Adds the argument to the parser - (this.FlagArguments as List).Add(newFlagArgument); - - // Returns this parser, so that method invocations can be chained - return this; - } - - /// - /// Creates a new sub-command for the command line parser. - /// - /// The name of the command. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is a command with the same name, then an is thrown. - /// Returns the argument parser for the command, which can then be configured. - public Parser AddCommand(string name) => this.AddCommand(name, null, null, this.Options); - - /// - /// Creates a new sub-command for the command line parser. - /// - /// The name of the command. - /// The parser options for the command. - /// If the name is null, empty, or only consists or white spaces or the options are null, then an is thrown. - /// If there already is a command with the same name, then an is thrown. - /// Returns the argument parser for the command, which can then be configured. - public Parser AddCommand(string name, ParserOptions parserOptions) => this.AddCommand(name, null, null, parserOptions); - - /// - /// Creates a new sub-command for the command line parser. - /// - /// The name of the command. - /// The description for the command. - /// If the name is null, empty, or only consists of white spaces, then an is thrown. - /// If there already is a command with the same name, then an is thrown. - /// Returns the argument parser for the command, which can then be configured. - public Parser AddCommand(string name, string description) => this.AddCommand(name, null, description, this.Options); - - /// - /// Creates a new sub-command for the command line parser. - /// - /// The name of the command. - /// The alias of the command, which can be used as an alternative to the name. - /// The description for the command. - /// The parser options for the command. - /// If the name is null, empty, or only consists of white spaces or the options are null, then an is thrown. - /// If there already is a command with the same name, then an is thrown. - /// Returns the argument parser for the command, which can then be configured. - public Parser AddCommand(string name, string alias, string description) => this.AddCommand(name, alias, description, this.Options); - - /// - /// Creates a new sub-command for the command line parser. - /// - /// The name of the command. - /// The alias of the command, which can be used as an alternative to the name. - /// The description for the command. - /// The parser options for the command. - /// If the name is null, empty, or only consists of white spaces or the options are null, then an is thrown. - /// If there already is a command with the same name or alias, then an is thrown. - /// Returns the argument parser for the command, which can then be configured. - public Parser AddCommand(string name, string alias, string description, ParserOptions parserOptions) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (parserOptions == null) - throw new ArgumentNullException(nameof(parserOptions)); - - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // Checks if there is already a command with the same name or alias - if (this.Commands.Any(command => string.Equals(command.Name, name, stringComparison))) - throw new InvalidOperationException($"There already is a command with the name {name}."); - if (!string.IsNullOrWhiteSpace(alias) && this.Commands.Any(command => string.Equals(command.Alias, alias, stringComparison))) - throw new InvalidOperationException($"There already is a command with the alias {alias}."); - - // Creates the parser for the new command and adds it to the list of commands - Command newCommand = new Command(name, alias, description, parserOptions); - (this.Commands as List).Add(newCommand); - - // Returns the created parser - return newCommand.SubParser; - } - - /// - /// Creates a new sub-command for the command line parser. - /// - /// The name of the command. - /// The parser that is used to parse the arguments of the command. - /// If the name is null, empty, or only consists of white spaces or the parser is null, then an is thrown. - /// If there already is a command with the same name, then an is thrown. - /// Returns the argument parser for the command, which can then be configured. - public Parser AddCommand(string name, Parser subParser) => this.AddCommand(name, null, subParser); - - /// - /// Creates a new sub-command for the command line parser. - /// - /// The name of the command. - /// The alias of the command, which can be used as an alternative to the name. - /// The parser that is used to parse the arguments of the command. - /// If the name is null, empty, or only consists of white spaces or the parser is null, then an is thrown. - /// If there already is a command with the same name or alias, then an is thrown. - /// Returns the argument parser for the command, which can then be configured. - public Parser AddCommand(string name, string alias, Parser subParser) - { - // Validates the arguments - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - if (subParser == null) - throw new ArgumentNullException(nameof(subParser)); - - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // Checks if there is already a command with the same name or alias - if (this.Commands.Any(command => string.Equals(command.Name, name, stringComparison))) - throw new InvalidOperationException($"There already is a command with the name {name}."); - if (!string.IsNullOrWhiteSpace(alias) && this.Commands.Any(command => string.Equals(command.Alias, alias, stringComparison))) - throw new InvalidOperationException($"There already is a command with the alias {alias}."); - - // Adds the new command to the list of commands - Command newCommand = new Command(name, alias, subParser); - (this.Commands as List).Add(newCommand); - - // Returns the sub-parser - return subParser; - } - - /// - /// Adds the specified command to the command line parser. - /// - /// The command that is to be added. - /// If the command is null, then an is thrown. - /// If there already is a command with the same name or alias, then an is thrown. - /// Returns the parser of the command, which can be used to chain calls. - public Parser AddCommand(Command command) - { - // Validates the arguments - if (command == null) - throw new ArgumentNullException(nameof(command)); - - // Determines the string comparison type based on whether the casing should be ignored or not - StringComparison stringComparison = this.Options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - // Checks if there is already a command with the same name or alias - if (this.Commands.Any(otherCommand => string.Equals(otherCommand.Name, command.Name, stringComparison))) - throw new InvalidOperationException($"There already is a command with the name {command.Name}."); - if (!string.IsNullOrWhiteSpace(command.Alias) && this.Commands.Any(otherCommand => string.Equals(otherCommand.Alias, command.Alias, stringComparison))) - throw new InvalidOperationException($"There already is a command with the alias {command.Alias}."); - - // Adds the command to the list of commands - (this.Commands as List).Add(command); - - // Returns the sub-parser - return command.SubParser; - } - - /// - /// Parses the command line arguments by matching them to the declared arguments and commands. - /// - /// The command line arguments that were retrieved by the application. - /// Returns the parsing results, which is a bag of arguments. - public ParsingResults Parse(string[] commandLineArguments) - { - // Copies the command line arguments into a queue so that they are easier to parse without having to do fancy indexing, the first token is dequeued right away, because it is the file name of the executable - Queue tokenQueue = new Queue(commandLineArguments); - tokenQueue.Dequeue(); - - // Parses the command line arguments and returns the result - return this.Parse(tokenQueue); - } - - #endregion - } -} diff --git a/source/ParserOptions.cs b/source/ParserOptions.cs deleted file mode 100644 index b8f0db5..0000000 --- a/source/ParserOptions.cs +++ /dev/null @@ -1,148 +0,0 @@ - -#region Using Directives - -using System.Runtime.InteropServices; - -#endregion - -namespace System.CommandLine -{ - /// - /// Represents a property bag for the different options for the command line parser. - /// - public class ParserOptions - { - #region Constructors - - /// - /// Initializes a new instance. It uses some sensible default options for the command line parser. The default options are operating-system-aware, so for example - /// is set to "/" when being used under Windows, but set to "--" when used under Linux or macOS. - /// - public ParserOptions() - : this( - false, - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/" : "--", - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/" : "-", - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ":" : "=", - !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ) - { } - - /// - /// Initializes a new instance. It uses some sensible default options for the command line parser. The default options are operating-system-aware, so for example - /// is set to "/" when being used under Windows, but set to "--" when used under Linux or macOS. - /// - /// A value that determines whether the parser ignores case or not when parsing argument names. - public ParserOptions(bool ignoreCase) - : this( - ignoreCase, - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/" : "--", - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/" : "-", - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ":" : "=", - !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ) - { } - - /// - /// Initializes a new instance. - /// - /// The prefix that is used for arguments. - /// The prefix that is used for the aliases of arguments. - /// The separator that separates argument keys and their values. - public ParserOptions(string argumentPrefix, string argumentAliasPrefix, string keyValueSeparator) - : this( - false, - argumentPrefix, - argumentAliasPrefix, - keyValueSeparator, - argumentPrefix != argumentAliasPrefix - ) - { } - - /// - /// Initializes a new instance. - /// - /// A value that determines whether the parser ignores case or not when parsing argument names. - /// The prefix that is used for arguments. - /// The separator that separates argument keys and their values. - /// The prefix that is used for the aliases of arguments. - public ParserOptions(bool ignoreCase, string argumentPrefix, string argumentAliasPrefix, string keyValueSeparator) - : this( - ignoreCase, - argumentPrefix, - argumentAliasPrefix, - keyValueSeparator, - argumentPrefix != argumentAliasPrefix - ) - { } - - /// - /// Initializes a new instance. - /// - /// A value that determines whether the parser ignores case or not when parsing argument names. - /// The prefix that is used for arguments. - /// The prefix that is used for the aliases of arguments. - /// The separator that separates argument keys and their values. - /// A value that determines whether multiple flags arguments can be combined into a single flag. - public ParserOptions(bool ignoreCase, string argumentPrefix, string argumentAliasPrefix, string keyValueSeparator, bool allowMultiCharacterFlags) - { - this.IgnoreCase = ignoreCase; - this.ArgumentPrefix = argumentPrefix; - this.ArgumentAliasPrefix = argumentAliasPrefix; - this.KeyValueSeparator = keyValueSeparator; - this.AllowMultiCharacterFlags = allowMultiCharacterFlags; - } - - #endregion - - #region Public Properties - - /// - /// Gets or sets a value that determines whether the parser ignores case or not when parsing argument names. - /// - public bool IgnoreCase { get; set; } = false; - - /// - /// Gets or sets the prefix that is used for arguments. Under UNIX-like operating systems, this is usually two hyphen-minuses or dash characters: "--". Under Windows this is usually a single forward slash "/". - /// - public string ArgumentPrefix { get; set; } - - /// - /// Gets or sets the prefix that is used for the aliases of arguments. Under UNIX-like operating systems, this is usually a single hyphen-minus or dash character: "-". Under Windows this is usually a single - /// forward slash "/". - /// - public string ArgumentAliasPrefix { get; set; } - - /// - /// Gets or sets the seperator that is used to separate argument keys from the values. Under UNIX-like operating systems, this is usually a single equals sign: "=". Under Windows this is usually a single - /// colon sign: ":". This is an optional way to specify values, e.g. these two syntaxes are equivalent: "--age=18" and "--age 18". - /// - public string KeyValueSeparator { get; set; } - - /// - /// Contains a value that determines whether multiple flags can be combined into a single flag. - /// - private bool allowMultiCharacterFlags; - - /// - /// Gets or sets a value that determines whether multiple flags arguments can be combined into a single flag. For example if there are two flag arguments "flag1" and "flag2" and there aliases are "a" and "b" - /// respectively, then the following notation is valid for setting both flags at once "-ab" (equivalent to "-ba") if is set to true. This is just a short-hand notation - /// for the equivalent "-a -b". This notation is commmon in UNIX-like operating systems. Multi-character flags are only allowed if the prefix for arguments is not the same as the prefix for the aliases of - /// arguments. For example in Windows these prefixes are usually the both a single forward slash "/". In this case multi-character flags may be mistaken for other named or flag arguments and are therefore - /// not allowed. Consider the case where there are three flag arguments "ab", "foo", and "bar", where "foo" and "bar" have the aliases "a" and "b" respectively. In this case the following command line - /// argument "/ab" may either be interpreted as "ab", or "foo" and "bar". - /// - public bool AllowMultiCharacterFlags - { - get => this.allowMultiCharacterFlags; - set - { - if (value && this.ArgumentPrefix == this.ArgumentAliasPrefix) - throw new ArgumentException("If the same prefix is used for arguments and for the aliases of arguments, then multi-character flags may be ambigous and are therefore not allowed.", nameof(value)); - this.allowMultiCharacterFlags = value; - } - } - - #endregion - } -} diff --git a/source/ParsingResults.cs b/source/ParsingResults.cs deleted file mode 100644 index 3224b5b..0000000 --- a/source/ParsingResults.cs +++ /dev/null @@ -1,174 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.CommandLine.Arguments; - -#endregion - -namespace System.CommandLine -{ - /// - /// Represents the result of parsing. It contains all parses argument values and all parsed sub commands. - /// - public class ParsingResults - { - #region Public Properties - - /// - /// Gets the parsed values of all command line arguments. The dictionary key is the destination of the argument and the value is the parsed value. - /// - public IDictionary ParsedValues { get; private set; } = new Dictionary(); - - /// - /// Gets the name of the command of the parsing results. If a parser has a command, then the arguments of the command are parsed into a separate parsing result. For the top-level parser there is no command name - /// and this property is set to null. - /// - public string Command { get; private set; } - - /// - /// Gets the sub-results of the a command. If a parser has a command, then the arguments of the command are parsed into these sub results. If no command was parsed, then this property is set to null. - /// - public ParsingResults SubResults { get; private set; } - - /// - /// Gets a value that determines whether there are sub-results from a command. - /// - public bool HasSubResults { get => this.SubResults != null; } - - #endregion - - #region Internal Methods - - /// - /// Adds a new value to the parsing results. If there already is a value for the specified destination, then the value is overwritten. - /// - /// The name of the argument. - /// The parsed value of the argument. - internal void Add(string destination, object value) - { - // Checks if there is already a value for the specified destination, if so it is overwritten, otherwise it is added - if (this.ParsedValues.ContainsKey(destination)) - this.ParsedValues[destination] = value; - else - this.ParsedValues.Add(destination, value); - } - - /// - /// Adds a new value to the parsing results. If there already is a value for the specified destination, then the duplicate resolution policy is used to determine the new value. - /// - /// The name of the argument. - /// The parsed value of the argument. - /// A callback, which is used when there already is a value for the specified destination. - internal void Add(string destination, object value, Func duplicateResolutionPolicy) - { - // Checks if there is already a value for the specified destination, if so the duplicate resolution policy is used to determine the new value - if (this.ParsedValues.ContainsKey(destination)) - this.ParsedValues[destination] = duplicateResolutionPolicy(this.ParsedValues[destination], value); - else - this.ParsedValues.Add(destination, value); - } - - /// - /// Adds a new value to the parsing results. If there already is a value for the specified destination, then the value is overwritten. - /// - /// The argument that was parsed. - /// The parsed value of the argument. - internal void Add(Argument argument, object value) - { - if (argument.GetType().GetGenericTypeDefinition() == typeof(NamedArgument<>)) - this.Add(argument.Destination, value, argument.GetType().GetProperty(nameof(NamedArgument.DuplicateResolutionPolicy)).GetValue(argument) as Func); - else - this.Add(argument.Destination, value); - } - - /// - /// Adds new sub-results for the specified command. - /// - /// The name of the command for which the sub-results are to be added. - /// The parsing results that were parsed for the command. - /// Returns the created sub-results. - internal ParsingResults AddCommand(string name, ParsingResults subResults) - { - // Checks if there already are sub-results, if so, an exception is thrown - if (this.HasSubResults) - throw new InvalidOperationException("There already is a sub result for a command."); - - // Adds the sub-results for the command and returns them - this.SubResults = subResults; - this.SubResults.Command = name; - return this.SubResults; - } - - /// - /// Adds new sub-results for the specified command. - /// - /// The command for which the sub-results are to be added. - /// The parsing results that were parsed for the command. - /// Returns the created sub-results. - internal ParsingResults AddCommand(Command command, ParsingResults subResults) => this.AddCommand(command.Name, subResults); - - #endregion - - #region Public Methods - - /// - /// Gets the value of the parsed argument. - /// - /// The name of the argument. - /// If the argument with the specified destination could not be found (i.e. was not parsed), then a is thrown. - /// Returns the parsed value of the argument with the specified destination. - public object GetParsedValue(string destination) - { - // Checks if there is a parsed value for the specified destination, if not, an exception is thrown - if (!this.ParsedValues.ContainsKey(destination)) - throw new KeyNotFoundException($"There is no parsed value for the destination {destination}."); - - // Returns the parsed value for the specified destination - return this.ParsedValues[destination]; - } - - /// - /// Gets the value of the parsed argument. The value is cast to the specified type. - /// - /// The name of the argument. - /// The type into which the value is to be converted. - /// If the argument with the specified destination could not be found (i.e. was not parsed), then a is thrown. - /// If the parsed value cannot be cast into the specified type, then a is thrown. - /// Returns the parsed value of the argument with the specified destination. - public T GetParsedValue(string destination) - { - // Checks if there is a parsed value for the specified destination, if not, an exception is thrown - if (!this.ParsedValues.ContainsKey(destination)) - throw new KeyNotFoundException($"There is no parsed value for the destination {destination}."); - - // Tries to cast the parsed value into the destination type, if it cannot be cast, then an exception is thrown - T parsedValue = (T)this.ParsedValues[destination]; - if (parsedValue == null) - throw new InvalidOperationException($"The parsed value could not be cast into the destination type {parsedValue.GetType().Name}."); - - // Returns the parsed value for the specified destination - return parsedValue; - } - - /// - /// Gets the value of the parsed argument. - /// - /// The argument for which the parsed value is to be retrieved. - /// If the argument could not be found (i.e. was not parsed), then a is thrown. - /// Returns the parsed value of the specified argument. - public object GetParsedValue(Argument argument) => this.GetParsedValue(argument.Destination); - - /// - /// Gets the value of the parsed argument. The value is cast to the specified type. - /// - /// The argument for which the parsed value is to be retrieved. - /// The type into which the value is to be converted. - /// If the argument could not be found (i.e. was not parsed), then a is thrown. - /// If the parsed value cannot be cast into the specified type, then a is thrown. - /// Returns the parsed value of the specified argument. - public T GetParsedValue(Argument argument) => this.GetParsedValue(argument.Destination); - - #endregion - } -} diff --git a/source/Properties/AssemblyInformation.cs b/source/Properties/AssemblyInformation.cs deleted file mode 100644 index ee0fda9..0000000 --- a/source/Properties/AssemblyInformation.cs +++ /dev/null @@ -1,12 +0,0 @@ - -#region Using Directives - -using System.Runtime.CompilerServices; - -#endregion - -#region General Information - -[assembly: InternalsVisibleTo("System.CommandLine.Parser.Tests")] - -#endregion diff --git a/source/System.CommandLine.Parser.csproj b/source/System.CommandLine.Parser.csproj deleted file mode 100644 index aac4287..0000000 --- a/source/System.CommandLine.Parser.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - - netstandard2.0 - - - \ No newline at end of file diff --git a/source/ValueConverters/BooleanConverter.cs b/source/ValueConverters/BooleanConverter.cs deleted file mode 100644 index d0de0b7..0000000 --- a/source/ValueConverters/BooleanConverter.cs +++ /dev/null @@ -1,94 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.Globalization; - -#endregion - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents a value converter, which is able to convert strings to booleans. - /// - public class BooleanConverter : IValueConverter - { - #region Private Static Fields - - /// - /// Contains a list of all the types that are supported by this value converter. - /// - private static readonly List supportedTypes = new List { typeof(bool) }; - - /// - /// Contains a conversion dictionary, which maps well-known string values to their respective boolean representation. - /// - private static readonly Dictionary booleanConversionMap = new Dictionary - { - ["TRUE"] = true, - ["FALSE"] = false, - ["YES"] = true, - ["NO"] = false, - ["ON"] = true, - ["OFF"] = false, - }; - - #endregion - - #region IValueConverter Implementation - - /// - /// Determines whether this value converter is able to convert the specified string. - /// - /// The value that is to be tested. - /// Returns true if the value converter is able to convert the specified type and false otherwise. - public bool CanConvertFrom(string value) - { - // Checks if the value is one of the well-known strings that the value converter can convert to boolean - if (BooleanConverter.booleanConversionMap.ContainsKey(value.Trim().ToUpperInvariant())) - return true; - - // Checks if the value is numeric, in that case the value can also be converted - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out _)) - return true; - - // Since the value is neither one of the well-known strings, nor an integer value, the value cannot be converter - return false; - } - - /// - /// Determines whether this value converter is able to convert a string to the specified type. - /// - /// The type which is to be tested. - /// Returns true if the value converter can convert to the specified type and false otherwise. - public bool CanConvertTo(Type resultType) => BooleanConverter.supportedTypes.Contains(resultType); - - /// - /// Converts the specified value to the specified destination type. - /// - /// The value that is to be converted to the specified destination type. - /// The type to which the value is to be converted. - /// If the type or the value is null, empty, or only consists of white spaces, then an is thrown. - /// If the specified type is not supported or the value could not be converted, then an is thrown. - /// Returns a new instance of the specified type, that contains the converted value. - public object Convert(Type resultType, string value) - { - // Validates the arguments - if (resultType == null) - throw new ArgumentNullException(nameof(resultType)); - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullException(nameof(value)); - - // Tries to convert the specified value to the specified type - if (BooleanConverter.booleanConversionMap.ContainsKey(value.Trim().ToUpperInvariant())) - return BooleanConverter.booleanConversionMap[value.Trim().ToUpperInvariant()]; - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out long integerValue)) - return integerValue != 0; - - // If this code is reached it means, that either the specified type is not supported or the value could not be converted, in that case an exception is thrown - throw new InvalidOperationException($"The value \"{value}\" cannot be converted to \"{resultType.Name}\"."); - } - - #endregion - } -} diff --git a/source/ValueConverters/CollectionHelper.cs b/source/ValueConverters/CollectionHelper.cs deleted file mode 100644 index 801b2fa..0000000 --- a/source/ValueConverters/CollectionHelper.cs +++ /dev/null @@ -1,330 +0,0 @@ - -#region Using Directives - -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reflection; - -#endregion - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents a helper, which makes it easy to create collections of many different kinds via one, simple API. - /// - public static class CollectionHelper - { - #region Private Static Fields - - /// - /// Contains a list of all the collection types that are supported by the collection helper. - /// - private static List supportedTypes = new List - { - // System.Collections - typeof(ArrayList), - typeof(Queue), - typeof(Stack), - typeof(ICollection), - typeof(IEnumerable), - typeof(IList), - - // System.Collections.Generic - typeof(HashSet<>), - typeof(LinkedList<>), - typeof(List<>), - typeof(Queue<>), - typeof(SortedSet<>), - typeof(Stack<>), - typeof(ICollection<>), - typeof(IEnumerable<>), - typeof(IList<>), - typeof(IReadOnlyCollection<>), - typeof(IReadOnlyList<>), - typeof(ISet<>), - - // System.Collections.ObjectModel - typeof(Collection<>), - typeof(ObservableCollection<>), - typeof(ReadOnlyCollection<>), - typeof(ReadOnlyObservableCollection<>) - }; - - #endregion - - #region Public Static Methods - - /// - /// Determines if a given type is one of the supported collection types. - /// - /// The type that is to be checked for support. - /// Returns true if the specified type is one of the supported collection types and false otherwise. - public static bool IsSupportedCollectionType(Type type) - { - if (type.IsArray) - { - if (type.GetElementType().IsArray) - return false; - return type.GetArrayRank() == 1; - } - if (type.IsConstructedGenericType) - type = type.GetGenericTypeDefinition(); - return CollectionHelper.supportedTypes.Contains(type); - } - - /// - /// Retrieves the element type of the specified collection. - /// - /// The collection type for which the element type is to be determined. - /// Returns the type of the elements of the specified collection type. If the collection type is not generic typeof(object) is returned. - public static Type GetCollectionElementType(Type type) - { - if (!CollectionHelper.IsSupportedCollectionType(type)) - throw new InvalidOperationException("The specified type is not a supported collection type."); - - if (type.IsArray) - return type.GetElementType(); - if (type.IsConstructedGenericType) - return type.GetGenericArguments()[0]; - return typeof(object); - } - - /// - /// Merges two collections into one collection of the same type. - /// - /// The type of collection into which the result is to be converted. - /// The first collection. - /// The second collection. - /// Returns a collection that contains the elements of both collections, which is of the same type as the input collections. - public static object Merge(Type resultType, object firstCollection, object secondCollection) - { - // Validates that the two collections are of a supported collection type - if (!CollectionHelper.IsSupportedCollectionType(firstCollection.GetType())) - throw new InvalidOperationException("The type of the first collection is not a supported collection type."); - if (!CollectionHelper.IsSupportedCollectionType(secondCollection.GetType())) - throw new InvalidOperationException("The type of the second collection is not a supported collection type."); - if (!CollectionHelper.IsSupportedCollectionType(resultType)) - throw new InvalidOperationException("The result type is not a supported collection type."); - - // Converts the two collections to arrays first - Array firstArray = CollectionHelper.ToArray(firstCollection); - Array secondArray = CollectionHelper.ToArray(secondCollection); - - // Combines the two arrays into a single array - object[] mergedArray = new object[firstArray.Length + secondArray.Length]; - Array.Copy(firstArray, mergedArray, firstArray.Length); - Array.Copy(secondArray, 0, mergedArray, firstArray.Length, secondArray.Length); - - // Converts the merged array back to the collection type and returns it - return CollectionHelper.To(resultType, mergedArray); - } - - /// - /// Converts the specified collection to a collection of another containing the same elements. - /// - /// The type of collection into which the result is to be converted. - /// The collection, that is to be converted to another collection type. - /// Returns the converted collection. - public static object To(Type resultType, object inputCollection) - { - // Checks if both collection types are supported - if (!CollectionHelper.IsSupportedCollectionType(inputCollection.GetType())) - throw new InvalidOperationException("The type of the input collection is not a supported collection type."); - if (!CollectionHelper.IsSupportedCollectionType(resultType)) - throw new InvalidOperationException("The result type is not a supported collection type."); - - // First the input collection is converted to an array and then the array is converted to the result collection type - Array array = CollectionHelper.ToArray(inputCollection); - return CollectionHelper.FromArray(resultType, array); - } - - /// - /// Converts the specified collection into an array with the same elements. - /// - /// The collection that is to be converted into an array. - /// Returns an array that contains the same elements of the specified input collection. - public static Array ToArray(object inputCollection) - { - // Checks if the type of the input collection is supported - Type type = inputCollection.GetType(); - if (!CollectionHelper.IsSupportedCollectionType(type)) - throw new InvalidOperationException("The type of the input collection is not a supported collection type."); - - // Checks if the collection already is an array, then nothing needs to be done - if (type.IsArray) - return inputCollection as Array; - - // Creates a new array - int arraySize = CollectionHelper.GetCount(inputCollection); - object[] array = new object[arraySize]; - - // Checks if the input collection implements ICollection, in that case there is a way to copy its contents to an array - ICollection collection = inputCollection as ICollection; - if (collection != null) - { - collection.CopyTo(array, 0); - return array; - } - - // Checks if the input collection implements the IEnumerable interface, in that case the enumerator can be used to copy its contents to an array - IEnumerable enumerable = inputCollection as IEnumerable; - if (enumerable != null) - { - IEnumerator enumerator = enumerable.GetEnumerator(); - int currentIndex = 0; - while (enumerator.MoveNext()) - { - array[currentIndex] = enumerator.Current; - currentIndex += 1; - } - return array; - } - - // Since some weird error must have occurred (all supported collection types either implement ICollection or IEnumerator one or the other way), an exception is thrown - throw new InvalidOperationException("The input collection type neither implements ICollection nor IEnumerable."); - } - - /// - /// Converts the specified array into another collection type with the same elements. - /// - /// The type of collection into which the result is to be converted. - /// The array that is to be converted into another collection type. - /// Returns a collection that contains the same elements of the specified input array. - public static object FromArray(Type resultType, Array inputArray) - { - // Checks if the type of the input collection is supported - if (!CollectionHelper.IsSupportedCollectionType(resultType)) - throw new InvalidOperationException("The result type is not a supported collection type."); - - // Determines which type the elements will have in the result collection - Type elementType; - if (resultType.IsGenericType) - elementType = resultType.GenericTypeArguments[0]; - else if (resultType.IsArray) - elementType = resultType.GetElementType(); - else - elementType = typeof(object); - if (resultType.IsConstructedGenericType) - resultType = resultType.GetGenericTypeDefinition(); - - // Checks if the result collection is an array and performs the necessary conversions - if (resultType.IsArray) - { - Array resultArray = Array.CreateInstance(elementType, inputArray.Length); - Array.Copy(inputArray, resultArray, inputArray.Length); - return resultArray; - } - - // Checks if the result collection is ICollection, IEnumerable, or IList, in that case nothing needs to be done, because Array already implements all three of them - if (resultType == typeof(ICollection) || resultType == typeof(IEnumerable) || resultType == typeof(IList)) - return inputArray; - - // Checks if the result collection is a non-generic collection type, all non-generic collection types implement a constructor that takes an ICollection as a parameter, - // so reflection can be used to instantiated instances of them - if (resultType == typeof(ArrayList) || resultType == typeof(Queue) || resultType == typeof(Stack)) - { - ConstructorInfo collectionConstructor = resultType.GetConstructor(new Type[] { typeof(ICollection) }); - object newCollection = collectionConstructor.Invoke(new object[] { inputArray }); - return newCollection; - } - - // Since all of the following types are generic types, the input array is first cast into a generic IEnumerable - IEnumerable enumerable = inputArray as IEnumerable; - MethodInfo castMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast)).MakeGenericMethod( new System.Type[]{ elementType } ); - object genericEnumerable = castMethod.Invoke(null, new object[] { enumerable }); - - // Many of the generic collection types have a constructor that takes a generic IEnumerable as a parameter, so reflection can be used to instantiated instances of them - if (resultType == typeof(HashSet<>) || - resultType == typeof(LinkedList<>) || - resultType == typeof(List<>) || - resultType == typeof(Queue<>) || - resultType == typeof(SortedSet<>) || - resultType == typeof(Stack<>) || - resultType == typeof(ObservableCollection<>) || - resultType == typeof(IEnumerable<>) || - resultType == typeof(IList<>) || - resultType == typeof(ISet<>)) - { - if (resultType == typeof(IEnumerable<>) || resultType == typeof(IList<>)) - resultType = typeof(List<>); - if (resultType == typeof(ISet<>)) - resultType = typeof(SortedSet<>); - - Type collectionType = resultType.MakeGenericType(new Type[] { elementType }); - ConstructorInfo collectionConstructor = collectionType.GetConstructor(new Type[] { genericEnumerable.GetType() }); - object newCollection = collectionConstructor.Invoke(new object[] { genericEnumerable }); - return newCollection; - } - - // The ReadOnlyObservableCollection<> is a special case, as it only has one constructor, which takes a ObservableCollection<> as parameter, so reflection can be used to instantiated instances of it - if (resultType == typeof(ReadOnlyObservableCollection<>)) - { - Type observableCollectionType = typeof(ObservableCollection<>).MakeGenericType(new Type[] { elementType }); - ConstructorInfo observableCollectionConstructor = observableCollectionType.GetConstructor(new Type[] { genericEnumerable.GetType() }); - object observableCollection = observableCollectionConstructor.Invoke(new object[] { genericEnumerable }); - Type collectionType = typeof(ReadOnlyObservableCollection<>).MakeGenericType(new Type[] { elementType }); - ConstructorInfo collectionConstructor = collectionType.GetConstructor(new Type[] { observableCollectionType }); - object newCollection = collectionConstructor.Invoke(new object[] { observableCollection }); - return newCollection; - } - - // The Collection<> and ReadOnlyCollection<> are special cases, as they have constructors, which take generic IList values as parameters, so reflection can be used to instantiated instances of them - if (resultType == typeof(Collection<>) || - resultType == typeof(ReadOnlyCollection<>) || - resultType == typeof(ICollection<>) || - resultType == typeof(IReadOnlyCollection<>) || - resultType == typeof(IReadOnlyList<>)) - { - if (resultType == typeof(ICollection<>)) - resultType = typeof(Collection<>); - if (resultType == typeof(IReadOnlyCollection<>) || resultType == typeof(IReadOnlyList<>)) - resultType = typeof(ReadOnlyCollection<>); - - Type listType = typeof(List<>).MakeGenericType(new Type[] { elementType }); - ConstructorInfo listConstructor = listType.GetConstructor(new Type[] { genericEnumerable.GetType() }); - object newList = listConstructor.Invoke(new object[] { genericEnumerable }); - Type collectionType = resultType.MakeGenericType(new Type[] { elementType }); - ConstructorInfo collectionConstructor = collectionType.GetConstructor(new Type[] { listType }); - object newCollection = collectionConstructor.Invoke(new object[] { newList }); - return newCollection; - } - - throw new InvalidOperationException("The array could not be converted."); - } - - /// - /// Gets the number of elements in the specified collection. - /// - /// The collection for which the number of elements is to be determined. - /// Returns the number of elements that are in the specified collection. - public static int GetCount(object inputCollection) - { - // Determines if the specified collection is of a supported type - if (!CollectionHelper.IsSupportedCollectionType(inputCollection.GetType())) - throw new InvalidOperationException("The type of the input collection is not a supported collection type."); - - // Checks if the collection implements ICollection, in that case, the length of the collection can directly be accessed - ICollection collection = inputCollection as ICollection; - if (collection != null) - return collection.Count; - - // Since the collection does not implement ICollection, the other way to retrieve length is via IEnumerable and IEnumerator - IEnumerable enumerable = inputCollection as IEnumerable; - if (enumerable != null) - { - int count = 0; - IEnumerator enumerator = enumerable.GetEnumerator(); - while (enumerator.MoveNext()) - count += 1; - return count; - } - - // Since some weird error must have occurred (all supported collection types either implement ICollection or IEnumerator one or the other way), an exception is thrown - throw new InvalidOperationException("The input collection type neither implements ICollection nor IEnumerable."); - } - - #endregion - } -} diff --git a/source/ValueConverters/EnumerationConverter.cs b/source/ValueConverters/EnumerationConverter.cs deleted file mode 100644 index 0ec7d3a..0000000 --- a/source/ValueConverters/EnumerationConverter.cs +++ /dev/null @@ -1,65 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -#endregion - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents a value converter, which is able to convert strings to enumeration values. - /// - public class EnumerationConverter : IValueConverter - { - #region IValueConverter Implementation - - /// - /// Determines whether this value converter is able to convert the specified string. - /// - /// The value that is to be tested. - /// Returns true if the value converter is able to convert the specified type and false otherwise. - public bool CanConvertFrom(string value) => false; - - /// - /// Determines whether this value converter is able to convert a string to the specified type. - /// - /// The type which is to be tested. - /// Returns true if the value converter can convert to the specified type and false otherwise. - public bool CanConvertTo(Type resultType) => resultType.IsEnum; - - /// - /// Converts the specified value to the specified destination type. - /// - /// The value that is to be converted to the specified destination type. - /// The type to which the value is to be converted. - /// If the type or the value is null, empty, or only consists of white spaces, then an is thrown. - /// If the specified type is not supported or the value could not be converted, then an is thrown. - /// Returns a new instance of the specified type, that contains the converted value. - public object Convert(Type resultType, string value) - { - // Validates the arguments - if (resultType == null) - throw new ArgumentNullException(nameof(resultType)); - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullException(nameof(value)); - - // Checks if the result type is an enumeration, if not, then an exception is thrown - if (!resultType.IsEnum) - throw new InvalidOperationException($"The {resultType.Name} type is not an enumeration type."); - - // Tries to parse the enumeration by integer value - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int integerValue)) - return Enum.ToObject(resultType, integerValue); - - // Tries to parse the enumeration by name - if (!Enum.GetNames(resultType).Any(name => string.Equals(value, name, StringComparison.OrdinalIgnoreCase))) - throw new InvalidOperationException($"The value \"{value}\" cannot be converted to \"{resultType.Name}\"."); - return Enum.Parse(resultType, Enum.GetNames(resultType).First(name => string.Equals(value, name, StringComparison.OrdinalIgnoreCase))); - } - - #endregion - } -} diff --git a/source/ValueConverters/FloatConverter.cs b/source/ValueConverters/FloatConverter.cs deleted file mode 100644 index 3b0fb2f..0000000 --- a/source/ValueConverters/FloatConverter.cs +++ /dev/null @@ -1,80 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.Globalization; - -#endregion - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents a value converter, which is able to convert strings to floats. - /// - public class FloatConverter : IValueConverter - { - #region Private Static Fields - - /// - /// Contains a list of all the types that are supported by this value converter. - /// - private static readonly List supportedTypes = new List { typeof(float), typeof(double), typeof(decimal) }; - - /// - /// Contains the number style that is used to parse the floating point literals. - /// - private static readonly NumberStyles numberStyles = NumberStyles.Float | NumberStyles.AllowThousands | NumberStyles.AllowExponent; - - #endregion - - #region IValueConverter Implementation - - /// - /// Determines whether this value converter is able to convert the specified string. - /// - /// The value that is to be tested. - /// Returns true if the value converter is able to convert the specified type and false otherwise. - public bool CanConvertFrom(string value) => decimal.TryParse(value, numberStyles, CultureInfo.InvariantCulture, out _); - - /// - /// Determines whether this value converter is able to convert a string to the specified type. - /// - /// The type which is to be tested. - /// Returns true if the value converter can convert to the specified type and false otherwise. - public bool CanConvertTo(Type resultType) => FloatConverter.supportedTypes.Contains(resultType); - - /// - /// Converts the specified value to the specified destination type. - /// - /// The value that is to be converted to the specified destination type. - /// The type to which the value is to be converted. - /// If the type or the value is null, empty, or only consists of white spaces, then an is thrown. - /// If the specified type is not supported or the value could not be converted, then an is thrown. - /// Returns a new instance of the specified type, that contains the converted value. - public object Convert(Type resultType, string value) - { - // Validates the arguments - if (resultType == null) - throw new ArgumentNullException(nameof(resultType)); - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullException(nameof(value)); - - // Tries to convert the specified value to the specified type - try - { - if (resultType == typeof(float)) - return float.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(double)) - return double.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(decimal) || resultType == typeof(object)) - return decimal.Parse(value, numberStyles, CultureInfo.InvariantCulture); - } - catch (FormatException) {} - - // If this code is reached it means, that either the specified type is not supported or the value could not be converted, in that case an exception is thrown - throw new InvalidOperationException($"The value \"{value}\" cannot be converted to \"{resultType.Name}\"."); - } - - #endregion - } -} diff --git a/source/ValueConverters/IValueConverter.cs b/source/ValueConverters/IValueConverter.cs deleted file mode 100644 index 8ea75d2..0000000 --- a/source/ValueConverters/IValueConverter.cs +++ /dev/null @@ -1,37 +0,0 @@ - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents an interface that all value converter extensions for the must implement. - /// - public interface IValueConverter - { - #region Methods - - /// - /// Determines whether this value converter is able to convert the specified string. - /// - /// The value that is to be tested. - /// Returns true if the value converter is able to convert the specified type and false otherwise. - bool CanConvertFrom(string value); - - /// - /// Determines whether this value converter is able to convert a string to the specified type. - /// - /// The type which is to be tested. - /// Returns true if the value converter can convert to the specified type and false otherwise. - bool CanConvertTo(Type resultType); - - /// - /// Converts the specified value to the specified destination type. - /// - /// The value that is to be converted to the specified destination type. - /// The type to which the value is to be converted. - /// If the type or the value is null, empty, or only consists of white spaces, then an is thrown. - /// If the specified type is not supported or the value could not be converted, then an is thrown. - /// Returns a new instance of the specified type, that contains the converted value. - object Convert(Type resultType, string value); - - #endregion - } -} diff --git a/source/ValueConverters/IntegerCoverter.cs b/source/ValueConverters/IntegerCoverter.cs deleted file mode 100644 index 49469e1..0000000 --- a/source/ValueConverters/IntegerCoverter.cs +++ /dev/null @@ -1,123 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.Globalization; -using System.Text.RegularExpressions; - -#endregion - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents a value converter, which is able to convert strings to integers. - /// - public class IntegerConverter : IValueConverter - { - #region Private Static Fields - - /// - /// Contains a list of all the types that are supported by this value converter. - /// - private static List supportedTypes = new List { typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong) }; - - /// - /// Contains the number style that is used to parse the integer literals. - /// - private static readonly NumberStyles numberStyles = NumberStyles.Integer | NumberStyles.AllowThousands | NumberStyles.AllowExponent; - - #endregion - - #region IValueConverter Implementation - - /// - /// Determines whether this value converter is able to convert the specified string. - /// - /// The value that is to be tested. - /// Returns true if the value converter is able to convert the specified type and false otherwise. - public bool CanConvertFrom(string value) - { - Regex hexNumberRegex = new Regex(@"^[ ]*[\+\-]?(0x)", RegexOptions.IgnoreCase); - if (hexNumberRegex.IsMatch(value)) - return long.TryParse(hexNumberRegex.Replace(value, string.Empty, 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _); - return long.TryParse(value, numberStyles, CultureInfo.InvariantCulture, out _); - } - - /// - /// Determines whether this value converter is able to convert a string to the specified type. - /// - /// The type which is to be tested. - /// Returns true if the value converter can convert to the specified type and false otherwise. - public bool CanConvertTo(Type resultType) => IntegerConverter.supportedTypes.Contains(resultType); - - /// - /// Converts the specified value to the specified destination type. - /// - /// The value that is to be converted to the specified destination type. - /// The type to which the value is to be converted. - /// If the type or the value is null, empty, or only consists of white spaces, then an is thrown. - /// If the specified type is not supported or the value could not be converted, then an is thrown. - /// Returns a new instance of the specified type, that contains the converted value. - public object Convert(Type resultType, string value) - { - // Validates the arguments - if (resultType == null) - throw new ArgumentNullException(nameof(resultType)); - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullException(nameof(value)); - - // Tries to convert the specified value to the specified type - try - { - Regex hexNumberRegex = new Regex(@"^[ ]*[\+\-]?(0x)", RegexOptions.IgnoreCase); - if (hexNumberRegex.IsMatch(value)) - { - int sign = value.Contains("-") ? -1 : 1; - value = hexNumberRegex.Replace(value, string.Empty, 1); - if (resultType == typeof(byte) && sign == 1) - return byte.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - if (resultType == typeof(sbyte)) - return (sbyte)(sbyte.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture) * sign); - if (resultType == typeof(short)) - return (short)(short.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture) * sign); - if (resultType == typeof(ushort) && sign == 1) - return ushort.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - if (resultType == typeof(int)) - return (int)(int.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture) * sign); - if (resultType == typeof(uint) && sign == 1) - return uint.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - if (resultType == typeof(long) || resultType == typeof(object)) - return (long)(long.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture) * sign); - if (resultType == typeof(ulong) && sign == 1) - return ulong.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - } - else - { - if (resultType == typeof(byte)) - return byte.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(sbyte)) - return sbyte.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(short)) - return short.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(ushort)) - return ushort.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(int)) - return int.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(uint)) - return uint.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(long) || resultType == typeof(object)) - return long.Parse(value, numberStyles, CultureInfo.InvariantCulture); - if (resultType == typeof(ulong)) - return ulong.Parse(value, numberStyles, CultureInfo.InvariantCulture); - } - } - catch (FormatException) {} - catch (OverflowException) {} - - // If this code is reached it means, that either the specified type is not supported or the value could not be converted, in that case an exception is thrown - throw new InvalidOperationException($"The value \"{value}\" cannot be converted to \"{resultType.Name}\"."); - } - - #endregion - } -} diff --git a/source/ValueConverters/StringConverter.cs b/source/ValueConverters/StringConverter.cs deleted file mode 100644 index b153b27..0000000 --- a/source/ValueConverters/StringConverter.cs +++ /dev/null @@ -1,67 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; - -#endregion - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents a value converter, which is able to convert strings to strings. - /// - public class StringConverter : IValueConverter - { - #region Private Static Fields - - /// - /// Contains a list of all the types that are supported by this value converter. - /// - private static readonly List supportedTypes = new List { typeof(string), typeof(StringBuilder) }; - - #endregion - - #region IValueConverter Implementation - - /// - /// Determines whether this value converter is able to convert the specified string. - /// - /// The value that is to be tested. - /// Returns true if the value converter is able to convert the specified type and false otherwise. - public bool CanConvertFrom(string value) => true; - - /// - /// Determines whether this value converter is able to convert a string to the specified type. - /// - /// The type which is to be tested. - /// Returns true if the value converter can convert to the specified type and false otherwise. - public bool CanConvertTo(Type resultType) => StringConverter.supportedTypes.Contains(resultType); - - /// - /// Converts the specified value to the specified destination type. - /// - /// The value that is to be converted to the specified destination type. - /// The type to which the value is to be converted. - /// If the type or the value is null, empty, or only consists of white spaces, then an is thrown. - /// If the specified type is not supported or the value could not be converted, then an is thrown. - /// Returns a new instance of the specified type, that contains the converted value. - public object Convert(Type resultType, string value) - { - // Validates the arguments - if (resultType == null) - throw new ArgumentNullException(nameof(resultType)); - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullException(nameof(value)); - - // Tries to convert the specified value to the specified type - if (resultType == typeof(StringBuilder)) - return new StringBuilder(value); - return value; - } - - #endregion - } -} diff --git a/source/ValueConverters/ValueConverter.cs b/source/ValueConverters/ValueConverter.cs deleted file mode 100644 index 549f800..0000000 --- a/source/ValueConverters/ValueConverter.cs +++ /dev/null @@ -1,120 +0,0 @@ - -#region Using Directives - -using System.Collections.Generic; - -#endregion - -namespace System.CommandLine.ValueConverters -{ - /// - /// Represents a highly-customizable value conversion API, which can virtually convert any string to a meaningful C# object. It can be extended with new value converters. - /// - public static class ValueConverter - { - #region Constructors - - /// - /// Initializes the statically. It initializes the built-in value converters. - /// - static ValueConverter() => ValueConverter.ResetValueConverters(); - - #endregion - - #region Private Static Fields - - /// - /// Contains a list of all the value converters that can be used by . - /// - private static List valueConverters; - - #endregion - - #region Public Static Methods - - /// - /// Converts the specified value to the specified type. - /// - /// The value that is to be converted to the specified type. - /// The type to which the specified value is to be converted. - /// - /// If no value converter was found that is capable of converting to the specified type or none of the value converters that were found could successfully convert the value, then an - /// is thrown. - /// - /// Returns the converted value. - public static object Convert(Type resultType, string value) - { - // Checks if the type is a collection type - if (CollectionHelper.IsSupportedCollectionType(resultType)) - { - Type elementType = CollectionHelper.GetCollectionElementType(resultType); - object[] collectionValue = { ValueConverter.Convert(elementType, value) }; - return CollectionHelper.FromArray(resultType, collectionValue); - } - - // Tries all the registered value converters one after another till a converter is found, that is able to convert the specified value to the specified type (the value converters - // are ordered by priority, therefore they can be tried from first to last, if a value converter throws an error the search for a fitting value converter is continued) - foreach (IValueConverter valueConverter in ValueConverter.valueConverters) - { - try - { - if (resultType == typeof(object)) - { - if (valueConverter.CanConvertFrom(value)) - return valueConverter.Convert(resultType, value); - } - else - { - if (valueConverter.CanConvertTo(resultType)) - return valueConverter.Convert(resultType, value); - } - } - catch (InvalidOperationException) - { - continue; - } - } - - // If this code is reached it means, that either the specified type is not supported by any of the value converters or the value could not be converted by any of them, in that case an exception is thrown - throw new InvalidOperationException($"The value \"{value}\" cannot be converted to \"{resultType.Name}\"."); - } - - /// - /// Adds a new value converter to the list of value converters used by . User-defined value converters have the highest priority and always tried before any built-in value converters - /// are used. To restore the default behavior of , invoke . - /// - /// The value converter that is to be added. - public static void AddValueConverter(IValueConverter valueConverter) - { - if (valueConverter == null) - throw new ArgumentNullException(nameof(valueConverter)); - ValueConverter.valueConverters.Insert(0, valueConverter); - } - - /// - /// Resets to its initial state. All user-defined value converters are removed and the default value-converters are re-initialized. - /// - public static void ResetValueConverters() - { - if (ValueConverter.valueConverters != null) - { - foreach (IValueConverter valueConverter in ValueConverter.valueConverters) - { - IDisposable disposableValueConverter = valueConverter as IDisposable; - if (disposableValueConverter != null) - disposableValueConverter.Dispose(); - } - } - ValueConverter.valueConverters = new List - { - new IntegerConverter(), - new FloatConverter(), - new BooleanConverter(), - new EnumerationConverter(), - new StringConverter() - }; - } - - #endregion - } -} diff --git a/test/CamelCasingTests.cs b/test/CamelCasingTests.cs deleted file mode 100644 index 76e58a5..0000000 --- a/test/CamelCasingTests.cs +++ /dev/null @@ -1,37 +0,0 @@ - -#region Using Directives - -using System; -using System.CommandLine.Arguments; -using Xunit; - -#endregion - -namespace System.CommandLine.Tests -{ - /// - /// Represents a set of unit tests for the camel casing routine. - /// - public class CamelCasingTests - { - #region Unit Tests - - /// - /// Tests the camel-casing methods used for the named arguments. - /// - [Fact] - public void TestCamelCasing() - { - Assert.Equal("ThisIsATest", "This is a Test".ToCamelCasePropertyName()); - Assert.Equal("ThisIsATest", "this_is_a_test".ToCamelCasePropertyName()); - Assert.Equal("ThisIsATest", "ThisIsATest".ToCamelCasePropertyName()); - Assert.Equal("ThisIsATest", "--this-is-a-test".ToCamelCasePropertyName()); - Assert.Equal("ThisIsATest", "/this_is_a_test".ToCamelCasePropertyName()); - Assert.Equal("ThisIsATest", " this is a test ".ToCamelCasePropertyName()); - Assert.Equal("ThisIsATest", "T!h§i%s_i&s_a_t/e(s)t".ToCamelCasePropertyName()); - Assert.Equal("_45ThisIsATest123", "45this is a test123".ToCamelCasePropertyName()); - } - - #endregion - } -} diff --git a/test/CollectionHelperTests.cs b/test/CollectionHelperTests.cs deleted file mode 100644 index 0d5c27b..0000000 --- a/test/CollectionHelperTests.cs +++ /dev/null @@ -1,435 +0,0 @@ - -#region Using Directives - -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.CommandLine.ValueConverters; -using System.Linq; -using Xunit; - -#endregion - -namespace System.CommandLine.Tests -{ - /// - /// Represents a set of unit tests for the . - /// - public class CollectionHelperTests - { - #region Unit Tests - - /// - /// Tests the ability of the to convert any collection type to any other collection type. - /// - [Fact] - public void TestTo() - { - int[] integerArray = new int[] { 1, 2, 3, 4, 5 }; - List genericList = CollectionHelper.To(typeof(List), integerArray) as List; - Assert.Equal(5, genericList.Count); - Assert.Equal(1, genericList[0]); - Assert.Equal(2, genericList[1]); - Assert.Equal(3, genericList[2]); - Assert.Equal(4, genericList[3]); - Assert.Equal(5, genericList[4]); - - ArrayList arrayList = new ArrayList { "foo", "bar" }; - Collection genericCollection = CollectionHelper.To(typeof(Collection), arrayList) as Collection; - Assert.Equal(2, genericCollection.Count); - Assert.Equal("foo", genericCollection[0]); - Assert.Equal("bar", genericCollection[1]); - - ObservableCollection genericObservableCollection = new ObservableCollection(new List { true }); - Queue genericQueue = CollectionHelper.To(typeof(Queue), genericObservableCollection) as Queue; - Assert.Equal(1, genericQueue.Count); - Assert.Equal(true, genericQueue.Dequeue()); - - Stack stack = new Stack(); - stack.Push(3.14159); - stack.Push(2.71828); - double[] doubleArray = CollectionHelper.To(typeof(double[]), stack) as double[]; - Assert.Equal(2, doubleArray.Length); - Assert.Equal(2.71828, doubleArray[0]); - Assert.Equal(3.14159, doubleArray[1]); - - LinkedList genericLinkedList = new LinkedList(); - genericLinkedList.AddFirst(1.0f); - genericLinkedList.AddLast(2.0f); - float[] floatArray = CollectionHelper.To(typeof(float[]), genericLinkedList) as float[]; - Assert.Equal(2, floatArray.Length); - Assert.Equal(1.0f, floatArray[0]); - Assert.Equal(2.0f, floatArray[1]); - } - - /// - /// Tests the ability of the to merge different kinds of collections. - /// - public void TestMerge() - { - int[] firstIntegerArray = new int[] { 1, 2, 3 }; - int[] secondIntegerArray = new int[] { 4, 5 }; - int[] mergedIntegerArray = CollectionHelper.Merge(typeof(int[]), firstIntegerArray, secondIntegerArray) as int[]; - Assert.Equal(5, mergedIntegerArray.Length); - Assert.Equal(1, mergedIntegerArray[0]); - Assert.Equal(2, mergedIntegerArray[1]); - Assert.Equal(3, mergedIntegerArray[2]); - Assert.Equal(4, mergedIntegerArray[3]); - Assert.Equal(5, mergedIntegerArray[4]); - - ArrayList firstArrayList = new ArrayList { "foo" }; - ArrayList secondArrayList = new ArrayList { "bar" }; - ArrayList mergedArrayList = CollectionHelper.Merge(typeof(ArrayList), firstArrayList, secondArrayList) as ArrayList; - Assert.Equal(2, mergedArrayList.Count); - Assert.Equal("foo", mergedArrayList[0]); - Assert.Equal("bar", mergedArrayList[1]); - - List firstGenericList = new List { 2.71828, 6.62607 }; - List secondGenericList = new List { 3.14159 }; - List mergedGenericList = CollectionHelper.Merge(typeof(List), firstGenericList, secondGenericList) as List; - Assert.Equal(3, mergedGenericList.Count); - Assert.Equal(2.71828, mergedGenericList.ElementAt(0)); - Assert.Equal(6.62607, mergedGenericList.ElementAt(1)); - Assert.Equal(3.14159, mergedGenericList.ElementAt(1)); - - Queue firstQueue = new Queue(); - firstQueue.Enqueue(true); - bool[] secondBooleanArray = new bool[] { false, true }; - Collection mergedGenericCollection = CollectionHelper.Merge(typeof(Collection), firstQueue, secondBooleanArray) as Collection; - Assert.Equal(3, mergedGenericCollection.Count); - Assert.Equal(true, mergedGenericCollection.ElementAt(0)); - Assert.Equal(false, mergedGenericCollection.ElementAt(1)); - Assert.Equal(true, mergedGenericCollection.ElementAt(1)); - } - - /// - /// Tests the ability of the to detect supported collection types. - /// - [Fact] - public void TestIsSupportedCollectionType() - { - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(int[]))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(float[]))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(double[]))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(string[]))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(bool[]))); - - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(ArrayList))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(Queue))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(Stack))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(ICollection))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(IEnumerable))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(IList))); - - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(HashSet))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(LinkedList))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(List))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(Queue))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(SortedSet))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(Stack))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(ICollection))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(IEnumerable))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(IList))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(IReadOnlyCollection))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(IReadOnlyList))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(ISet))); - - - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(Collection))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(ObservableCollection))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(ReadOnlyCollection))); - Assert.True(CollectionHelper.IsSupportedCollectionType(typeof(ReadOnlyObservableCollection))); - - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(int))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(float))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(double))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(string))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(bool))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(object))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(Type))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(int[,]))); - Assert.False(CollectionHelper.IsSupportedCollectionType(typeof(float[][]))); - } - - /// - /// Tests the ability of the to create any kind of collection from an array. - /// - [Fact] - public void TestFromArray() - { - int[] integerArray = new int[] { 1, 2, 3, 4, 5 }; - double[] doubleArray = new double[] { 3.14159, 2.71828 }; - string[] stringArray = new string[] { "foo", "bar", "foobar" }; - - ArrayList arrayList = CollectionHelper.FromArray(typeof(ArrayList), integerArray) as ArrayList; - Assert.Equal(5, arrayList.Count); - Assert.Equal(1, arrayList[0]); - Assert.Equal(2, arrayList[1]); - Assert.Equal(3, arrayList[2]); - Assert.Equal(4, arrayList[3]); - Assert.Equal(5, arrayList[4]); - - Queue queue = CollectionHelper.FromArray(typeof(Queue), doubleArray) as Queue; - Assert.Equal(2, queue.Count); - Assert.Equal(3.14159, queue.Dequeue()); - Assert.Equal(2.71828, queue.Dequeue()); - - Stack stack = CollectionHelper.FromArray(typeof(Stack), stringArray) as Stack; - Assert.Equal(3, stack.Count); - Assert.Equal("foobar", stack.Pop()); - Assert.Equal("bar", stack.Pop()); - Assert.Equal("foo", stack.Pop()); - - HashSet genericHashSet = CollectionHelper.FromArray(typeof(HashSet), integerArray) as HashSet; - Assert.Equal(5, genericHashSet.Count); - Assert.Equal(1, genericHashSet.ElementAt(0)); - Assert.Equal(2, genericHashSet.ElementAt(1)); - Assert.Equal(3, genericHashSet.ElementAt(2)); - Assert.Equal(4, genericHashSet.ElementAt(3)); - Assert.Equal(5, genericHashSet.ElementAt(4)); - - LinkedList genericLinkedList = CollectionHelper.FromArray(typeof(LinkedList), doubleArray) as LinkedList; - Assert.Equal(2, genericLinkedList.Count); - Assert.Equal(3.14159, genericLinkedList.ElementAt(0)); - Assert.Equal(2.71828, genericLinkedList.ElementAt(1)); - - List genericList = CollectionHelper.FromArray(typeof(List), stringArray) as List; - Assert.Equal(3, genericList.Count); - Assert.Equal("foo", genericList[0]); - Assert.Equal("bar", genericList[1]); - Assert.Equal("foobar", genericList[2]); - - Queue genericQueue = CollectionHelper.FromArray(typeof(Queue), integerArray) as Queue; - Assert.Equal(5, genericQueue.Count); - Assert.Equal(1, genericQueue.ElementAt(0)); - Assert.Equal(2, genericQueue.ElementAt(1)); - Assert.Equal(3, genericQueue.ElementAt(2)); - Assert.Equal(4, genericQueue.ElementAt(3)); - Assert.Equal(5, genericQueue.ElementAt(4)); - - SortedSet genericSortedSet = CollectionHelper.FromArray(typeof(SortedSet), doubleArray) as SortedSet; - Assert.Equal(2, genericSortedSet.Count); - Assert.Equal(2.71828, genericSortedSet.ElementAt(0)); - Assert.Equal(3.14159, genericSortedSet.ElementAt(1)); - - Stack genericStack = CollectionHelper.FromArray(typeof(Stack), stringArray) as Stack; - Assert.Equal(3, genericStack.Count); - Assert.Equal("foobar", genericStack.Pop()); - Assert.Equal("bar", genericStack.Pop()); - Assert.Equal("foo", genericStack.Pop()); - - Collection genericCollection = CollectionHelper.FromArray(typeof(Collection), integerArray) as Collection; - Assert.Equal(5, genericCollection.Count); - Assert.Equal(1, genericCollection.ElementAt(0)); - Assert.Equal(2, genericCollection.ElementAt(1)); - Assert.Equal(3, genericCollection.ElementAt(2)); - Assert.Equal(4, genericCollection.ElementAt(3)); - Assert.Equal(5, genericCollection.ElementAt(4)); - - ObservableCollection genericObservableCollection = CollectionHelper.FromArray(typeof(ObservableCollection), doubleArray) as ObservableCollection; - Assert.Equal(2, genericObservableCollection.Count); - Assert.Equal(3.14159, genericObservableCollection.ElementAt(0)); - Assert.Equal(2.71828, genericObservableCollection.ElementAt(1)); - - ReadOnlyCollection genericReadOnlyCollection = CollectionHelper.FromArray(typeof(ReadOnlyCollection), stringArray) as ReadOnlyCollection; - Assert.Equal(3, genericReadOnlyCollection.Count); - Assert.Equal("foo", genericReadOnlyCollection.ElementAt(0)); - Assert.Equal("bar", genericReadOnlyCollection.ElementAt(1)); - Assert.Equal("foobar", genericReadOnlyCollection.ElementAt(2)); - - ReadOnlyObservableCollection genericReadOnlyObservableCollection = CollectionHelper.FromArray(typeof(ReadOnlyObservableCollection), integerArray) as ReadOnlyObservableCollection; - Assert.Equal(5, genericReadOnlyObservableCollection.Count); - Assert.Equal(1, genericReadOnlyObservableCollection.ElementAt(0)); - Assert.Equal(2, genericReadOnlyObservableCollection.ElementAt(1)); - Assert.Equal(3, genericReadOnlyObservableCollection.ElementAt(2)); - Assert.Equal(4, genericReadOnlyObservableCollection.ElementAt(3)); - Assert.Equal(5, genericReadOnlyObservableCollection.ElementAt(4)); - } - - /// - /// Tests the ability of the to determine the length of any collection. - /// - [Fact] - public void TestCollectionLength() - { - int[] array = new int[] { 1, 2, 3, 4, 5 }; - Assert.Equal(5, CollectionHelper.GetCount(array)); - - ArrayList arrayList = new ArrayList { "foo", "bar", "foobar" }; - Assert.Equal(3, CollectionHelper.GetCount(arrayList)); - - Queue queue = new Queue(); - queue.Enqueue(1.0); - queue.Enqueue(2.0); - queue.Enqueue(3.0); - Assert.Equal(3, CollectionHelper.GetCount(queue)); - - Stack stack = new Stack(); - stack.Push(true); - Assert.Equal(1, CollectionHelper.GetCount(stack)); - - HashSet genericHashSet = new HashSet { 1, 2, 3, 4, 5, 6 }; - Assert.Equal(6, CollectionHelper.GetCount(genericHashSet)); - - LinkedList genericLinkedList = new LinkedList(); - genericLinkedList.AddFirst("foo"); - genericLinkedList.AddFirst("bar"); - Assert.Equal(2, CollectionHelper.GetCount(genericLinkedList)); - - List genericList = new List { 1.2, 2.3, 3.4, 4.5, 5.6, 6.7 }; - Assert.Equal(6, CollectionHelper.GetCount(genericList)); - - Queue genericQueue = new Queue(); - genericQueue.Enqueue(false); - Assert.Equal(1, CollectionHelper.GetCount(genericQueue)); - - SortedSet genericSortedSet = new SortedSet { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; - Assert.Equal(10, CollectionHelper.GetCount(genericSortedSet)); - - Stack genericStack = new Stack(); - genericStack.Push(3.14159); - genericStack.Push(2.71828); - Assert.Equal(2, CollectionHelper.GetCount(genericStack)); - - Collection genericCollection = new Collection(); - genericCollection.Add("foo"); - genericCollection.Add("bar"); - genericCollection.Add("foobar"); - Assert.Equal(3, CollectionHelper.GetCount(genericCollection)); - - ObservableCollection genericObservableCollection = new ObservableCollection() { false, true }; - Assert.Equal(2, CollectionHelper.GetCount(genericObservableCollection)); - - ReadOnlyCollection genericReadOnlyCollection = new ReadOnlyCollection(new List { 1.0f, 2.0f, 3.0f }); - Assert.Equal(3, CollectionHelper.GetCount(genericReadOnlyCollection)); - - ReadOnlyObservableCollection genericReadOnlyObservableCollection = new ReadOnlyObservableCollection(new ObservableCollection { "bar" }); - Assert.Equal(1, CollectionHelper.GetCount(genericReadOnlyObservableCollection)); - } - - /// - /// Tests the ability of the to convert arrays to any collection type. - /// - [Fact] - public void TestToArray() - { - int[] integerArray = new int[] { 1, 2, 3, 4, 5 }; - Array array = CollectionHelper.ToArray(integerArray); - Assert.Equal(5, array.Length); - Assert.Equal(1, array.GetValue(0)); - Assert.Equal(2, array.GetValue(1)); - Assert.Equal(3, array.GetValue(2)); - Assert.Equal(4, array.GetValue(3)); - Assert.Equal(5, array.GetValue(4)); - - ArrayList arrayList = new ArrayList { "foo", "bar", "foobar" }; - array = CollectionHelper.ToArray(arrayList); - Assert.Equal(3, array.Length); - Assert.Equal("foo", array.GetValue(0)); - Assert.Equal("bar", array.GetValue(1)); - Assert.Equal("foobar", array.GetValue(2)); - - Queue queue = new Queue(); - queue.Enqueue(1.0); - queue.Enqueue(2.0); - queue.Enqueue(3.0); - array = CollectionHelper.ToArray(queue); - Assert.Equal(3, array.Length); - Assert.Equal(1.0, array.GetValue(0)); - Assert.Equal(2.0, array.GetValue(1)); - Assert.Equal(3.0, array.GetValue(2)); - - Stack stack = new Stack(); - stack.Push(true); - array = CollectionHelper.ToArray(stack); - Assert.Equal(1, array.Length); - Assert.Equal(true, array.GetValue(0)); - - HashSet genericHashSet = new HashSet { 1, 2, 3, 4, 5, 6 }; - array = CollectionHelper.ToArray(genericHashSet); - Assert.Equal(6, array.Length); - Assert.Equal(1, array.GetValue(0)); - Assert.Equal(2, array.GetValue(1)); - Assert.Equal(3, array.GetValue(2)); - Assert.Equal(4, array.GetValue(3)); - Assert.Equal(5, array.GetValue(4)); - Assert.Equal(6, array.GetValue(5)); - - LinkedList genericLinkedList = new LinkedList(); - genericLinkedList.AddFirst("foo"); - genericLinkedList.AddFirst("bar"); - array = CollectionHelper.ToArray(genericLinkedList); - Assert.Equal(2, array.Length); - Assert.Equal("bar", array.GetValue(0)); - Assert.Equal("foo", array.GetValue(1)); - - List genericList = new List { 1.2, 2.3, 3.4, 4.5, 5.6, 6.7 }; - array = CollectionHelper.ToArray(genericList); - Assert.Equal(6, array.Length); - Assert.Equal(1.2, array.GetValue(0)); - Assert.Equal(2.3, array.GetValue(1)); - Assert.Equal(3.4, array.GetValue(2)); - Assert.Equal(4.5, array.GetValue(3)); - Assert.Equal(5.6, array.GetValue(4)); - Assert.Equal(6.7, array.GetValue(5)); - - Queue genericQueue = new Queue(); - genericQueue.Enqueue(false); - array = CollectionHelper.ToArray(genericQueue); - Assert.Equal(1, array.Length); - Assert.Equal(false, array.GetValue(0)); - - SortedSet genericSortedSet = new SortedSet { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; - array = CollectionHelper.ToArray(genericSortedSet); - Assert.Equal(10, array.Length); - Assert.Equal(10, array.GetValue(0)); - Assert.Equal(20, array.GetValue(1)); - Assert.Equal(30, array.GetValue(2)); - Assert.Equal(40, array.GetValue(3)); - Assert.Equal(50, array.GetValue(4)); - Assert.Equal(60, array.GetValue(5)); - Assert.Equal(70, array.GetValue(6)); - Assert.Equal(80, array.GetValue(7)); - Assert.Equal(90, array.GetValue(8)); - Assert.Equal(100, array.GetValue(9)); - - Stack genericStack = new Stack(); - genericStack.Push(3.14159); - genericStack.Push(2.71828); - array = CollectionHelper.ToArray(genericStack); - Assert.Equal(2, array.Length); - Assert.Equal(2.71828, array.GetValue(0)); - Assert.Equal(3.14159, array.GetValue(1)); - - Collection genericCollection = new Collection(); - genericCollection.Add("foo"); - genericCollection.Add("bar"); - genericCollection.Add("foobar"); - array = CollectionHelper.ToArray(genericCollection); - Assert.Equal(3, array.Length); - Assert.Equal("foo", array.GetValue(0)); - Assert.Equal("bar", array.GetValue(1)); - Assert.Equal("foobar", array.GetValue(2)); - - ObservableCollection genericObservableCollection = new ObservableCollection() { false, true }; - array = CollectionHelper.ToArray(genericObservableCollection); - Assert.Equal(2, array.Length); - Assert.Equal(false, array.GetValue(0)); - Assert.Equal(true, array.GetValue(1)); - - ReadOnlyCollection genericReadOnlyCollection = new ReadOnlyCollection(new List { 1.0f, 2.0f, 3.0f }); - array = CollectionHelper.ToArray(genericReadOnlyCollection); - Assert.Equal(3, array.Length); - Assert.Equal(1.0f, array.GetValue(0)); - Assert.Equal(2.0f, array.GetValue(1)); - Assert.Equal(3.0f, array.GetValue(2)); - - ReadOnlyObservableCollection genericReadOnlyObservableCollection = new ReadOnlyObservableCollection(new ObservableCollection { "bar" }); - array = CollectionHelper.ToArray(genericReadOnlyObservableCollection); - Assert.Equal(1, array.Length); - Assert.Equal("bar", array.GetValue(0)); - } - - #endregion - } -} diff --git a/test/ParserTests.cs b/test/ParserTests.cs deleted file mode 100644 index a25331d..0000000 --- a/test/ParserTests.cs +++ /dev/null @@ -1,905 +0,0 @@ - -#region Using Directives - -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.Text; -using Xunit; - -#endregion - -namespace System.CommandLine.Tests -{ - /// - /// Represents a set of unit tests for the command line parser. - /// - public class ParserTests - { - #region Unit Tests - - /// - /// Tests whether the command line parser correctly handles situations where multiple arguments are declared with the same name and/or alias. - /// - [Fact] - public void TestDuplicateArgumentReferences() - { - // Adding two arguments with the same name results in an exception - Parser parser = new Parser(new ParserOptions { IgnoreCase = true }); - parser.AddFlagArgument("show-warnings"); - Assert.Throws(() => parser.AddNamedArgument("show-warnings")); - Assert.Throws(() => parser.AddNamedArgument("Show-Warnings")); - - // Adding two arguments with the same alias results in an exception - parser = new Parser(new ParserOptions { IgnoreCase = true }); - parser.AddNamedArgument("file-name", "f"); - Assert.Throws(() => parser.AddNamedArgument("factor", "f")); - Assert.Throws(() => parser.AddNamedArgument("filter", "F")); - - // Adding two arguments with different names that both map to the same destination name results in an exception - parser = new Parser(new ParserOptions { IgnoreCase = true }); - parser.AddPositionalArgument("file-name"); - Assert.Throws(() => parser.AddNamedArgument("fileName")); - Assert.Throws(() => parser.AddNamedArgument("FileName")); - - // When unsetting the ignore case option, then two names that differ in casing should be accepted - parser = new Parser(new ParserOptions { IgnoreCase = false }); - parser.AddFlagArgument("show-warnings"); - parser.AddNamedArgument("Show-Warnings", null, "Warnings", null); - Assert.Throws(() => parser.AddNamedArgument("show-warnings")); - - // When unsetting the ignore case option, then two aliases that differ in casing should be accepted - parser = new Parser(new ParserOptions { IgnoreCase = false }); - parser.AddNamedArgument("file-name", "f"); - parser.AddNamedArgument("factor", "F"); - Assert.Throws(() => parser.AddNamedArgument("filter", "f")); - - // When unsetting the ignore case option, then two destinations that differ in casing should be accepted - parser = new Parser(new ParserOptions { IgnoreCase = false }); - parser.AddNamedArgument("show-warnings", null, "ShowWarnings", null); - parser.AddNamedArgument("Show-Warnings", null, "Showwarnings", null); - Assert.Throws(() => parser.AddNamedArgument("show-warnings")); - } - - /// - /// Tests whether the command line parser correctly handles situations where multiple commands are declared with the same name. - /// - [Fact] - public void TestDuplicateCommandNames() - { - // Adding two commands with the same name results in an exception - Parser parser = new Parser(new ParserOptions { IgnoreCase = true }); - parser.AddCommand("create"); - Assert.Throws(() => parser.AddCommand("Create")); - Assert.Throws(() => parser.AddCommand("create")); - - // Adding two commands with the same alias results in an exception - parser = new Parser(new ParserOptions { IgnoreCase = true }); - parser.AddCommand("create", "c", "Creates a new entity."); - Assert.Throws(() => parser.AddCommand("combine", "c", "Combines two entities.")); - Assert.Throws(() => parser.AddCommand("calibrate", "C", "Calibrates the entity.")); - - // When unsetting the ignore case option, then two names that differ in casing should be accepted - parser = new Parser(new ParserOptions { IgnoreCase = false }); - parser.AddCommand("create"); - parser.AddCommand("Create"); - Assert.Throws(() => parser.AddCommand("create")); - - // When unsetting the ignore case option, then two aliases that differ in casing should be accepted - parser = new Parser(new ParserOptions { IgnoreCase = false }); - parser.AddCommand("create", "c", "Creates a new entity."); - parser.AddCommand("combine", "C", "Combines two entities."); - Assert.Throws(() => parser.AddCommand("calibrate", "C", "Calibrates the entity.")); - } - - /// - /// Tests whether the parser can handle one positional argument. - /// - [Fact] - public void TestOnePositionalArgument() - { - // Sets up the parser - Parser parser = new Parser(); - parser.AddPositionalArgument("file-name"); - - // Parses the command line arguments - ParsingResults parsingResults = parser.Parse(new string[] { "test.exe", @"C:\test" }); - - // Validates that the parsed values are correct - Assert.Equal(@"C:\test", parsingResults.GetParsedValue("FileName")); - } - - /// - /// Tests whether the parser can handle multiple positional arguments. - /// - [Fact] - public void TestMultiplePositionalArgument() - { - // Sets up the parser - Parser parser = new Parser(); - parser - .AddPositionalArgument("file-name") - .AddPositionalArgument("factor") - .AddPositionalArgument("day-of-week"); - - // Parses the command line arguments - ParsingResults parsingResults = parser.Parse(new string[] { "test.exe", @"C:\test", "8.45e9", "friday" }); - - // Validates that the parsed values are correct - Assert.Equal(@"C:\test", parsingResults.GetParsedValue("FileName")); - Assert.Equal(8.45e9, parsingResults.GetParsedValue("Factor")); - Assert.Equal(DayOfWeek.Friday, parsingResults.GetParsedValue("DayOfWeek")); - } - - /// - /// Tests how the parser handles if a position argument is missing. - /// - [Fact] - public void TestMissingPositionalArguments() - { - // Sets up the parser - Parser parser = new Parser(); - parser - .AddPositionalArgument("file-name") - .AddPositionalArgument("factor"); - - // Parses the command line arguments, since there are two positional arguments declared and only one value was supplied, an exception should be raised - Assert.Throws(() => parser.Parse(new string[] { "test.exe", @"C:\test" })); - } - - /// - /// Tests whether the parser can handle one named argument. - /// - [Fact] - public void TestOneNamedArgument() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddNamedArgument("file-name", "f"); - - // Parses the command line arguments - ParsingResults parsingResultsWithName = parser.Parse(new string[] { "test.exe", "--file-name", @"C:\test" }); - ParsingResults parsingResultsWithAlias = parser.Parse(new string[] { "test.exe", "-f", @"C:\test" }); - - // Validates that the parsed values are correct - Assert.Equal(@"C:\test", parsingResultsWithName.GetParsedValue("FileName")); - Assert.Equal(@"C:\test", parsingResultsWithAlias.GetParsedValue("FileName")); - } - - /// - /// Tests whether the parser can handle multiple named arguments. - /// - [Fact] - public void TestMultipleNamedArgument() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "/", - ArgumentAliasPrefix = "/" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i") - .AddNamedArgument("day-of-week", "d"); - - // Parses the command line arguments - ParsingResults parsingResultsWithName = parser.Parse(new string[] { "test.exe", "/feature", "false", "/number-of-iterations", "72", "/day-of-week", "wednesday" }); - ParsingResults parsingResultsWithAlias = parser.Parse(new string[] { "test.exe", "/f", "off", "/i", "72", "/d", "wednesday" }); - ParsingResults parsingResultsMixed = parser.Parse(new string[] { "test.exe", "/f", "no", "/number-of-iterations", "72", "/d", "wednesday" }); - - // Validates that the parsed values are correct - Assert.False(parsingResultsWithName.GetParsedValue("Feature")); - Assert.Equal(72, parsingResultsWithName.GetParsedValue("NumberOfIterations")); - Assert.Equal(DayOfWeek.Wednesday, parsingResultsWithName.GetParsedValue("DayOfWeek")); - Assert.False(parsingResultsWithAlias.GetParsedValue("Feature")); - Assert.Equal(72, parsingResultsWithAlias.GetParsedValue("NumberOfIterations")); - Assert.Equal(DayOfWeek.Wednesday, parsingResultsWithAlias.GetParsedValue("DayOfWeek")); - Assert.False(parsingResultsMixed.GetParsedValue("Feature")); - Assert.Equal(72, parsingResultsMixed.GetParsedValue("NumberOfIterations")); - Assert.Equal(DayOfWeek.Wednesday, parsingResultsMixed.GetParsedValue("DayOfWeek")); - } - - /// - /// Tests whether the parser correctly handles default values. - /// - [Fact] - public void TestNamedArgumentDefaultValues() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser - .AddNamedArgument("feature", "f", "Feature", null, true) - .AddNamedArgument("number-of-iterations", "i", "NumberOfIterations", null, 123) - .AddNamedArgument("day-of-week", "d", "DayOfWeek"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "--number-of-iterations", "72" }); - ParsingResults thirdParsingResults = parser.Parse(new string[] { "test.exe", "--feature", "false", "-i", "72", "-d", "wednesday" }); - - // Validates that the parsed values are correct - Assert.True(firstParsingResults.GetParsedValue("Feature")); - Assert.Equal(123, firstParsingResults.GetParsedValue("NumberOfIterations")); - Assert.Equal(DayOfWeek.Sunday, firstParsingResults.GetParsedValue("DayOfWeek")); - Assert.True(secondParsingResults.GetParsedValue("Feature")); - Assert.Equal(72, secondParsingResults.GetParsedValue("NumberOfIterations")); - Assert.Equal(DayOfWeek.Sunday, secondParsingResults.GetParsedValue("DayOfWeek")); - Assert.False(thirdParsingResults.GetParsedValue("Feature")); - Assert.Equal(72, thirdParsingResults.GetParsedValue("NumberOfIterations")); - Assert.Equal(DayOfWeek.Wednesday, thirdParsingResults.GetParsedValue("DayOfWeek")); - } - - /// - /// Tests whether the parser can handle one flag argument. - /// - [Fact] - public void TestOneFlagArgument() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddFlagArgument("number", "n"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "--number" }); - ParsingResults thirdParsingResults = parser.Parse(new string[] { "test.exe", "--number", "--number" }); - ParsingResults fourthParsingResults = parser.Parse(new string[] { "test.exe", "-n", "-n", "-n" }); - - // Validates that the parsed values are correct - Assert.Equal(0, firstParsingResults.GetParsedValue("Number")); - Assert.Equal(1, secondParsingResults.GetParsedValue("Number")); - Assert.Equal(2, thirdParsingResults.GetParsedValue("Number")); - Assert.Equal(3, fourthParsingResults.GetParsedValue("Number")); - } - - /// - /// Tests whether the parser can handle multiple flag arguments. - /// - [Fact] - public void TestMultipleFlagArgument() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddFlagArgument("day", "d"); - parser.AddFlagArgument("verbose", "v"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "--verbose", "--day" }); - ParsingResults thirdParsingResults = parser.Parse(new string[] { "test.exe", "--day", "--day" }); - ParsingResults fourthParsingResults = parser.Parse(new string[] { "test.exe", "-d", "-v", "-d", "-v", "-d" }); - - // Validates that the parsed values are correct - Assert.Equal(DayOfWeek.Sunday, firstParsingResults.GetParsedValue("Day")); - Assert.False(firstParsingResults.GetParsedValue("Verbose")); - Assert.Equal(DayOfWeek.Monday, secondParsingResults.GetParsedValue("Day")); - Assert.True(secondParsingResults.GetParsedValue("Verbose")); - Assert.Equal(DayOfWeek.Tuesday, thirdParsingResults.GetParsedValue("Day")); - Assert.False(thirdParsingResults.GetParsedValue("Verbose")); - Assert.Equal(DayOfWeek.Wednesday, fourthParsingResults.GetParsedValue("Day")); - Assert.True(fourthParsingResults.GetParsedValue("Verbose")); - } - - /// - /// Represents an enumeration used for testing the multi-character flags. - /// - private enum VerbosityLevel { Quiet, Minimal, Normal, Detailed, Diagnostic } - - /// - /// Tests how the parser handles multi-character flags. - /// - [Fact] - public void TestMultiCharacterFlagArguments() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddFlagArgument("verbosity", "v"); - parser.AddFlagArgument("count", "c"); - parser.AddFlagArgument("activate", "a"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "-vca" }); - ParsingResults thirdParsingResults = parser.Parse(new string[] { "test.exe", "-vvv", "-ccc", "-aaa" }); - ParsingResults fourthParsingResults = parser.Parse(new string[] { "test.exe", "-vvvcccaaa" }); - - // Validates that the parsed values are correct - Assert.Equal(VerbosityLevel.Quiet, firstParsingResults.GetParsedValue("Verbosity")); - Assert.Equal(0, firstParsingResults.GetParsedValue("Count")); - Assert.False(firstParsingResults.GetParsedValue("Activate")); - Assert.Equal(VerbosityLevel.Minimal, secondParsingResults.GetParsedValue("Verbosity")); - Assert.Equal(1, secondParsingResults.GetParsedValue("Count")); - Assert.True(secondParsingResults.GetParsedValue("Activate")); - Assert.Equal(VerbosityLevel.Detailed, thirdParsingResults.GetParsedValue("Verbosity")); - Assert.Equal(3, thirdParsingResults.GetParsedValue("Count")); - Assert.True(thirdParsingResults.GetParsedValue("Activate")); - Assert.Equal(VerbosityLevel.Detailed, fourthParsingResults.GetParsedValue("Verbosity")); - Assert.Equal(3, fourthParsingResults.GetParsedValue("Count")); - Assert.True(fourthParsingResults.GetParsedValue("Activate")); - } - - /// - /// Tests how the parser handles a situation where it parses a named argument that was not declared. - /// - [Fact] - public void TestUnknownNamedArgument() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - - // Parses the command line arguments, since there is are only two named arguments declared but three are supplied, an exception should be raised - Assert.Throws(() => parser.Parse(new string[] { "test.exe", "--feature", "on", "--number-of-iterations", "72", "--day-of-week", "wednesday" })); - } - - /// - /// Tests how the parser handles a situation where it parses a named argument and its value is missing. - /// - [Fact] - public void TestMissingNamedArgumentValue() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "/", - ArgumentAliasPrefix = "/" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i") - .AddNamedArgument("day-of-week", "d"); - - // Parses the command line arguments, there are three declared arguments and only two are supplied, but since named arguments are optional, nothing should happen - ParsingResults parsingResults = parser.Parse(new string[] { "test.exe", "/feature", "yes", "/number-of-iterations", "72" }); - - // Validates that the parsed values are correct - Assert.True(parsingResults.GetParsedValue("Feature")); - Assert.Equal(72, parsingResults.GetParsedValue("NumberOfIterations")); - } - - /// - /// Tests whether the parser can handle multiple positional arguments and named arguments. - /// - [Fact] - public void TestPositionalAndNamedArguments() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser - .AddPositionalArgument("file-name") - .AddPositionalArgument("factor") - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i") - .AddNamedArgument("day-of-week", "d"); - - // Parses the command line arguments - ParsingResults parsingResults = parser.Parse(new string[] { "test.exe", "/home/test.txt", "123.5", "-f", "true", "--number-of-iterations", "123", "-d", "Sunday" }); - - // Validates that the parsed values are correct - Assert.Equal("/home/test.txt", parsingResults.GetParsedValue("FileName")); - Assert.Equal(123.5, parsingResults.GetParsedValue("Factor")); - Assert.True(parsingResults.GetParsedValue("Feature")); - Assert.Equal(123, parsingResults.GetParsedValue("NumberOfIterations")); - Assert.Equal(DayOfWeek.Sunday, parsingResults.GetParsedValue("DayOfWeek")); - } - - /// - /// Tests how the parser handles different prefix styles (e.g. the UNIX "--" style and the Windows "/" style). - /// - [Fact] - public void TestArgumentPrefixStyles() - { - // Sets up the parser with the usual UNIX style argument prefixes - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - - // Parses the command line arguments with the UNIX style argument prefixes - ParsingResults parsingResults = parser.Parse(new string[] { "test.exe", "--feature", "off", "-i", "987" }); - - // Validates that the parsed values with the UNIX style argument prefixes are correct - Assert.False(parsingResults.GetParsedValue("Feature")); - Assert.Equal(987, parsingResults.GetParsedValue("NumberOfIterations")); - - // Sets up the parser with the usual Windows style argument prefixes - parser = new Parser(new ParserOptions - { - ArgumentPrefix = "/", - ArgumentAliasPrefix = "/" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - - // Parses the command line arguments with the Windows style argument prefixes - parsingResults = parser.Parse(new string[] { "test.exe", "/feature", "off", "/i", "987" }); - - // Validates that the parsed values with the Windows style argument prefixes are correct - Assert.False(parsingResults.GetParsedValue("Feature")); - Assert.Equal(987, parsingResults.GetParsedValue("NumberOfIterations")); - - // Sets up the parser with some non-standard argument prefixes, with different prefixes for arguments and their aliases - parser = new Parser(new ParserOptions - { - ArgumentPrefix = "@", - ArgumentAliasPrefix = "&" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - - // Parses the command line arguments with non-standard argument prefixes - parsingResults = parser.Parse(new string[] { "test.exe", "@feature", "off", "&i", "987" }); - - // Validates that the parsed values with non-standard argument prefixes are correct - Assert.False(parsingResults.GetParsedValue("Feature")); - Assert.Equal(987, parsingResults.GetParsedValue("NumberOfIterations")); - - // Sets up the parser with some non-standard argument prefixes, with the same prefixes for arguments and their aliases - parser = new Parser(new ParserOptions - { - ArgumentPrefix = "=", - ArgumentAliasPrefix = "=" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - - // Parses the command line arguments with non-standard argument prefixes - parsingResults = parser.Parse(new string[] { "test.exe", "=feature", "off", "=i", "987" }); - - // Validates that the parsed values with non-standard argument prefixes are correct - Assert.False(parsingResults.GetParsedValue("Feature")); - Assert.Equal(987, parsingResults.GetParsedValue("NumberOfIterations")); - - // Sets up the parser with switched UNIX style argument prefixes - parser = new Parser(new ParserOptions - { - ArgumentPrefix = "-", - ArgumentAliasPrefix = "--" - }); - parser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - - // Parses the command line arguments with the switched UNIX style argument prefixes - parsingResults = parser.Parse(new string[] { "test.exe", "-feature", "off", "--i", "987" }); - - // Validates that the parsed values with the switched UNIX style argument prefixes are correct - Assert.False(parsingResults.GetParsedValue("Feature")); - Assert.Equal(987, parsingResults.GetParsedValue("NumberOfIterations")); - } - - /// - /// Tests how the parser handles named arguments that are using the key value separator syntax (e.g. "--age=18" instead of "--age 18"). - /// - [Fact] - public void TestKeyValueSeparator() - { - // Sets up the parser - Parser unixStyleParser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-", - KeyValueSeparator = "=" - }); - unixStyleParser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - Parser windowsStyleParser = new Parser(new ParserOptions - { - ArgumentPrefix = "/", - ArgumentAliasPrefix = "/", - KeyValueSeparator = ":" - }); - windowsStyleParser - .AddNamedArgument("feature", "f") - .AddNamedArgument("number-of-iterations", "i"); - - // Parses the command line arguments - ParsingResults firstUnixStyleParsingResults = unixStyleParser.Parse(new string[] { "test.exe", "--feature=true", "-i=34" }); - ParsingResults secondUnixStyleParsingResults = unixStyleParser.Parse(new string[] { "test.exe", "-f", "off", "--number-of-iterations", "78" }); - ParsingResults firstWindowStyleParsingResults = windowsStyleParser.Parse(new string[] { "test.exe", "/feature:no", "/i:49" }); - ParsingResults secondWindowsStyleParsingResults = windowsStyleParser.Parse(new string[] { "test.exe", "/f", "yes", "/number-of-iterations", "20" }); - - // Validates that the parsed values are correct - Assert.True(firstUnixStyleParsingResults.GetParsedValue("Feature")); - Assert.Equal(34, firstUnixStyleParsingResults.GetParsedValue("NumberOfIterations")); - Assert.False(secondUnixStyleParsingResults.GetParsedValue("Feature")); - Assert.Equal(78, secondUnixStyleParsingResults.GetParsedValue("NumberOfIterations")); - Assert.False(firstWindowStyleParsingResults.GetParsedValue("Feature")); - Assert.Equal(49, firstWindowStyleParsingResults.GetParsedValue("NumberOfIterations")); - Assert.True(secondWindowsStyleParsingResults.GetParsedValue("Feature")); - Assert.Equal(20, secondWindowsStyleParsingResults.GetParsedValue("NumberOfIterations")); - } - - /// - /// Tests how the parser handles the parsing of arguments of a collection type and especially how it handles a situation where a single named argument is provided multiple times. - /// - [Fact] - public void TestCollectionArguments() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddNamedArgument>("names", "n"); - parser.AddNamedArgument("car", "c"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe", "--names", "Alice", "-n", "Bob", "--car", "blue car", "--names", "Eve" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "-n", "Alice", "Bob", "Eve", "-c", "red car" }); - - // Validates that the parsed values are correct - Assert.Equal(new List { "Alice", "Bob", "Eve" }, firstParsingResults.GetParsedValue>("Names")); - Assert.Equal("blue car", firstParsingResults.GetParsedValue("Car")); - Assert.Equal(new List { "Alice", "Bob", "Eve" }, secondParsingResults.GetParsedValue>("Names")); - Assert.Equal("red car", secondParsingResults.GetParsedValue("Car")); - } - - /// - /// Tests whether the default duplicate resolution policy works, which is used if a single named argument is provided more than once. - /// - [Fact] - public void TestDuplicateResolutionPolicy() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddNamedArgument("age", "a"); - - // Parses the command line arguments - ParsingResults parsingResults = parser.Parse(new string[] { "test.exe", "--age", "18", "-a", "17", "--age", "16" }); - - // Validates that the parsed values are correct - Assert.Equal(16, parsingResults.GetParsedValue("Age")); - } - - /// - /// Tests whether a custom duplicate resolution policy works, which is used if a single named argument is provided more than once. - /// - [Fact] - public void TestCustomDuplicateResolutionPolicy() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddNamedArgument("add", "a", "Add", "Adds numbers together", 0, (a, b) => a + b); - parser.AddNamedArgument("multiply", "m", "Multiply", "Multiplies numbers together", 0, (a, b) => a * b); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe", "--add", "1", "-a", "2", "--add", "3" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "--multiply", "4", "5", "-m", "6" }); - - // Validates that the parsed values are correct - Assert.Equal(6, firstParsingResults.GetParsedValue("Add")); - Assert.Equal(120, secondParsingResults.GetParsedValue("Multiply")); - } - - /// - /// Tests how the parser handles the parsing of a single commands. - /// - [Fact] - public void TestSingleCommand() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - parser.AddNamedArgument("verbose", "v"); - Parser commandParser = parser.AddCommand("restart", "r", string.Empty); - commandParser.AddPositionalArgument("time"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe", "-v", "no" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "restart", "now" }); - ParsingResults thirdParsingResults = parser.Parse(new string[] { "test.exe", "--verbose", "yes", "r", "later" }); - - // Validates that the parsed values are correct - Assert.False(firstParsingResults.GetParsedValue("Verbose")); - Assert.True(secondParsingResults.HasSubResults); - Assert.NotNull(secondParsingResults.SubResults); - Assert.Equal("restart", secondParsingResults.SubResults.Command); - Assert.Equal("now", secondParsingResults.SubResults.GetParsedValue("Time")); - Assert.True(thirdParsingResults.GetParsedValue("Verbose")); - Assert.True(thirdParsingResults.HasSubResults); - Assert.NotNull(thirdParsingResults.SubResults); - Assert.Equal("restart", thirdParsingResults.SubResults.Command); - Assert.Equal("later", thirdParsingResults.SubResults.GetParsedValue("Time")); - } - - /// - /// Tests how the parser handles the parsing of a command when having the a choice between multiple commands. - /// - [Fact] - public void TestMultipleCommands() - { - // Sets up the parser - Parser parser = new Parser(); - parser.AddCommand("test", "t", string.Empty); - parser.AddCommand("run", "r", string.Empty); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe", "test" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "t" }); - ParsingResults thirdParsingResults = parser.Parse(new string[] { "test.exe", "run" }); - ParsingResults fourthParsingResults = parser.Parse(new string[] { "test.exe", "r" }); - - // Validates that the parsed values are correct - Assert.Equal("test", firstParsingResults.SubResults.Command); - Assert.Equal("test", secondParsingResults.SubResults.Command); - Assert.Equal("run", thirdParsingResults.SubResults.Command); - Assert.Equal("run", fourthParsingResults.SubResults.Command); - } - - /// - /// Tests how the parser handles the parsing of multiple nested commands. - /// - [Fact] - public void TestNestedCommands() - { - // Sets up the parser - Parser parser = new Parser(new ParserOptions - { - ArgumentPrefix = "--", - ArgumentAliasPrefix = "-" - }); - Parser subParser = parser.AddCommand("add"); - Parser subSubParser = subParser.AddCommand("package"); - subSubParser.AddCommand("to-project"); - subSubParser.AddCommand("to-solution"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "add" }); - ParsingResults thirdParsingResults = parser.Parse(new string[] { "test.exe", "add", "package" }); - ParsingResults fourthParsingResults = parser.Parse(new string[] { "test.exe", "add", "package", "to-project" }); - ParsingResults fifthParsingResults = parser.Parse(new string[] { "test.exe", "add", "package", "to-solution" }); - - // Validates that the parsed values are correct - Assert.False(firstParsingResults.HasSubResults); - Assert.Equal("add", secondParsingResults.SubResults.Command); - Assert.False(secondParsingResults.SubResults.HasSubResults); - Assert.Equal("add", thirdParsingResults.SubResults.Command); - Assert.Equal("package", thirdParsingResults.SubResults.SubResults.Command); - Assert.False(thirdParsingResults.SubResults.SubResults.HasSubResults); - Assert.Equal("add", fourthParsingResults.SubResults.Command); - Assert.Equal("package", fourthParsingResults.SubResults.SubResults.Command); - Assert.Equal("to-project", fourthParsingResults.SubResults.SubResults.SubResults.Command); - Assert.False(fourthParsingResults.SubResults.SubResults.SubResults.HasSubResults); - Assert.Equal("add", fifthParsingResults.SubResults.Command); - Assert.Equal("package", fifthParsingResults.SubResults.SubResults.Command); - Assert.Equal("to-solution", fifthParsingResults.SubResults.SubResults.SubResults.Command); - Assert.False(fifthParsingResults.SubResults.SubResults.SubResults.HasSubResults); - } - - /// - /// Tests how the parser handles situations where arguments have the same names or aliases as commands. - /// - [Fact] - public void TestArgumentCommandNameCollision() - { - // Sets up the parser - Parser parser = new Parser(); - parser.AddNamedArgument("test", "t"); - parser.AddCommand("test", "t", string.Empty); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe", "--test", "test", "test" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "-t", "t", "t" }); - - // Validates that the parsed values are correct - Assert.Equal("test", firstParsingResults.GetParsedValue("Test")); - Assert.True(firstParsingResults.HasSubResults); - Assert.Equal("test", firstParsingResults.SubResults.Command); - Assert.Equal("t", secondParsingResults.GetParsedValue("Test")); - Assert.True(secondParsingResults.HasSubResults); - Assert.Equal("test", secondParsingResults.SubResults.Command); - } - - /// - /// Tests how the parser handles name collisions in a parser and a sub parser. - /// - [Fact] - public void TestArgumentNameCollisionInCommands() - { - // Sets up the parser - Parser parser = new Parser(); - parser.AddNamedArgument("day-of-week", "d"); - Parser subParser = parser.AddCommand("test", "t", string.Empty); - subParser.AddNamedArgument("day-of-week", "d"); - - // Parses the command line arguments - ParsingResults firstParsingResults = parser.Parse(new string[] { "test.exe", "--day-of-week", "thursday", "test", "--day-of-week", "wednesday" }); - ParsingResults secondParsingResults = parser.Parse(new string[] { "test.exe", "-d", "Tuesday", "t", "-d", "Saturday" }); - - // Validates that the parsed values are correct - Assert.Equal(DayOfWeek.Thursday, firstParsingResults.GetParsedValue("DayOfWeek")); - Assert.True(firstParsingResults.HasSubResults); - Assert.Equal("test", firstParsingResults.SubResults.Command); - Assert.Equal(DayOfWeek.Wednesday, firstParsingResults.SubResults.GetParsedValue("DayOfWeek")); - Assert.Equal(DayOfWeek.Tuesday, secondParsingResults.GetParsedValue("DayOfWeek")); - Assert.True(secondParsingResults.HasSubResults); - Assert.Equal("test", secondParsingResults.SubResults.Command); - Assert.Equal(DayOfWeek.Saturday, secondParsingResults.SubResults.GetParsedValue("DayOfWeek")); - } - - /// - /// Tests how the parser handles a very complex scenario with many different kinds of parameters and multiple commands. - /// - [Fact] - public void TestComplexScenario() - { - // Creates the main parser and adds all the necessary arguments - Parser parser = new Parser("A simple version of the .NET CLI.", new ParserOptions(false, "--", "-", "=", true)); - parser.AddFlagArgument("help", "h", "Show command line help."); - - // Creates the parser for the add command - Parser addCommandParser = parser.AddCommand("add", "Add a new package or reference to a .NET project."); - addCommandParser.AddPositionalArgument("project", "The project file to operate on."); - addCommandParser.AddFlagArgument("help", "h", "Show command line help."); - - // Creates the parser for the add package command - Parser addPackageCommandParser = addCommandParser.AddCommand("package", "Add a NuGet package reference to the project."); - addPackageCommandParser.AddPositionalArgument("package-name", "The package reference to add."); - addPackageCommandParser.AddFlagArgument("help", "h", "Show command line help."); - addPackageCommandParser.AddNamedArgument("version", "v", "The version of the package to add."); - addPackageCommandParser.AddFlagArgument("interactive", null, "Show command line help."); - - // Creates the parser for the add reference command - Parser addReferenceCommandParser = addCommandParser.AddCommand("reference", "Add a project-to-project reference to the project."); - addReferenceCommandParser.AddPositionalArgument("project-path", "The paths to the projects to add as references."); - addReferenceCommandParser.AddFlagArgument("help", "h", "Show command line help."); - addReferenceCommandParser.AddNamedArgument("framework", "f", "Add the reference only when targeting a specific framework."); - - // Creates the parser for the build command - Parser buildCommandParser = parser.AddCommand("build", "Add a new package or reference to a .NET project."); - buildCommandParser.AddPositionalArgument("project", "The project file to operate on."); - buildCommandParser.AddFlagArgument("help", "h", "Show command line help."); - buildCommandParser.AddNamedArgument("verbosity", "v", "Set the MSBuild verbosity level. Allowed values are quiet, minimal, normal, detailed, and diagnostic."); - buildCommandParser.AddNamedArgument("framework", "f", "The target framework to build for. The target framework must also be specified in the project file."); - - // Creates the parser for the new command - Parser newCommandParser = parser.AddCommand("new", "Create a new .NET project or file."); - newCommandParser.AddPositionalArgument("template", "The project template to use."); - newCommandParser.AddFlagArgument("help", "h", "Show command line help."); - newCommandParser.AddNamedArgument("name", "n", "The name for the output being created. If no name is specified, the name of the current directory is used."); - newCommandParser.AddNamedArgument("output", "o", "Location to place the generated output."); - - // Creates the parser for the run command - Parser runCommandParser = parser.AddCommand("run", "Build and run a .NET project output."); - runCommandParser.AddFlagArgument("help", "h", "Show command line help."); - runCommandParser.AddNamedArgument("project", "p", "Project", "The path to the project file to run (defaults to the current directory if there is only one project).", "./"); - runCommandParser.AddNamedArgument("framework", "f", "The target framework to run for. The target framework must also be specified in the project file."); - - // Tests how the parser handles no command line arguments at all - ParsingResults parsingResults = parser.Parse(new string[] { "dotnet" }); - Assert.False(parsingResults.GetParsedValue("Help")); - Assert.False(parsingResults.HasSubResults); - Assert.Null(parsingResults.SubResults); - - // Tests how the parser handles the add command - parsingResults = parser.Parse(new string[] { "dotnet", "add", "./test.csproj", "--help" }); - Assert.False(parsingResults.GetParsedValue("Help")); - Assert.True(parsingResults.HasSubResults); - Assert.Equal("add", parsingResults.SubResults.Command); - Assert.Equal("./test.csproj", parsingResults.SubResults.GetParsedValue("Project")); - Assert.True(parsingResults.SubResults.GetParsedValue("Help")); - Assert.False(parsingResults.SubResults.HasSubResults); - - // Tests how the parser handles the add package command - parsingResults = parser.Parse(new string[] { "dotnet", "add", "./test.csproj", "package", "Newtonsoft.Json", "--version", "11.0.2" }); - Assert.False(parsingResults.GetParsedValue("Help")); - Assert.True(parsingResults.HasSubResults); - Assert.Equal("add", parsingResults.SubResults.Command); - Assert.Equal("./test.csproj", parsingResults.SubResults.GetParsedValue("Project")); - Assert.False(parsingResults.SubResults.GetParsedValue("Help")); - Assert.True(parsingResults.SubResults.HasSubResults); - Assert.Equal("package", parsingResults.SubResults.SubResults.Command); - Assert.Equal("Newtonsoft.Json", parsingResults.SubResults.SubResults.GetParsedValue("PackageName")); - Assert.Equal("11.0.2", parsingResults.SubResults.SubResults.GetParsedValue("Version")); - Assert.False(parsingResults.SubResults.SubResults.GetParsedValue("Help")); - Assert.False(parsingResults.SubResults.SubResults.GetParsedValue("Interactive")); - Assert.False(parsingResults.SubResults.SubResults.HasSubResults); - - // Tests how the parser handles the missing package name in the add package command - Assert.Throws(() => parser.Parse(new string[] { "dotnet", "add", "./test.csproj", "package" })); - - // Tests how the parser handles the add reference command - parsingResults = parser.Parse(new string[] { "dotnet", "add", "./test.csproj", "reference", "../foo/bar.csproj", "--framework", "netcoreapp2.1", "-h" }); - Assert.False(parsingResults.GetParsedValue("Help")); - Assert.True(parsingResults.HasSubResults); - Assert.Equal("add", parsingResults.SubResults.Command); - Assert.Equal("./test.csproj", parsingResults.SubResults.GetParsedValue("Project")); - Assert.False(parsingResults.SubResults.GetParsedValue("Help")); - Assert.True(parsingResults.SubResults.HasSubResults); - Assert.Equal("reference", parsingResults.SubResults.SubResults.Command); - Assert.Equal("../foo/bar.csproj", parsingResults.SubResults.SubResults.GetParsedValue("ProjectPath")); - Assert.Equal("netcoreapp2.1", parsingResults.SubResults.SubResults.GetParsedValue("Framework")); - Assert.True(parsingResults.SubResults.SubResults.GetParsedValue("Help")); - Assert.False(parsingResults.SubResults.SubResults.HasSubResults); - - // Tests how the parser handles the missing project path in the add reference command - Assert.Throws(() => parser.Parse(new string[] { "dotnet", "add", "./test.csproj", "reference" })); - - // Tests how the parser handles the build command - parsingResults = parser.Parse(new string[] { "dotnet", "build", "./test.csproj", "--verbosity", "detailed" }); - Assert.False(parsingResults.GetParsedValue("Help")); - Assert.True(parsingResults.HasSubResults); - Assert.Equal("build", parsingResults.SubResults.Command); - Assert.Equal("./test.csproj", parsingResults.SubResults.GetParsedValue("Project")); - Assert.Equal(VerbosityLevel.Detailed, parsingResults.SubResults.GetParsedValue("Verbosity")); - Assert.False(parsingResults.SubResults.GetParsedValue("Help")); - Assert.False(parsingResults.SubResults.HasSubResults); - - // Tests how the parser handles the new command - parsingResults = parser.Parse(new string[] { "dotnet", "new", "console", "-n", "test", "--output", "./test" }); - Assert.False(parsingResults.GetParsedValue("Help")); - Assert.True(parsingResults.HasSubResults); - Assert.Equal("new", parsingResults.SubResults.Command); - Assert.Equal("console", parsingResults.SubResults.GetParsedValue("Template")); - Assert.Equal("test", parsingResults.SubResults.GetParsedValue("Name")); - Assert.Equal("./test", parsingResults.SubResults.GetParsedValue("Output")); - Assert.False(parsingResults.SubResults.GetParsedValue("Help")); - Assert.False(parsingResults.SubResults.HasSubResults); - - // Tests how the parser handles the run command - parsingResults = parser.Parse(new string[] { "dotnet", "run", "--project", "./test.csproj", "-f", "netcoreapp2.0", "-h" }); - Assert.False(parsingResults.GetParsedValue("Help")); - Assert.True(parsingResults.HasSubResults); - Assert.Equal("run", parsingResults.SubResults.Command); - Assert.Equal("./test.csproj", parsingResults.SubResults.GetParsedValue("Project")); - Assert.Equal("netcoreapp2.0", parsingResults.SubResults.GetParsedValue("Framework")); - Assert.True(parsingResults.SubResults.GetParsedValue("Help")); - Assert.False(parsingResults.SubResults.HasSubResults); - } - - #endregion - } -} diff --git a/test/System.CommandLine.Parser.Tests.csproj b/test/System.CommandLine.Parser.Tests.csproj deleted file mode 100644 index 3d3e045..0000000 --- a/test/System.CommandLine.Parser.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - netcoreapp2.0 - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ValueConverterTests.cs b/test/ValueConverterTests.cs deleted file mode 100644 index 18f6155..0000000 --- a/test/ValueConverterTests.cs +++ /dev/null @@ -1,155 +0,0 @@ - -#region Using Directives - -using System; -using System.Net; -using System.Text; -using System.Collections.Generic; -using System.CommandLine.Arguments; -using System.CommandLine.ValueConverters; -using Xunit; - -#endregion - -namespace System.CommandLine.Tests -{ - /// - /// Represents a set of unit tests for the class. - /// - public class ValueConverterTests - { - #region Unit Tests - - /// - /// Tests how the handles the conversion of boolean values. - /// - [Fact] - public void TestBooleanValueConversion() - { - Assert.Equal(true, ValueConverter.Convert(typeof(bool), "true")); - Assert.Equal(false, ValueConverter.Convert(typeof(bool), "false")); - Assert.Equal(true, ValueConverter.Convert(typeof(bool), "on")); - Assert.Equal(false, ValueConverter.Convert(typeof(bool), "off")); - Assert.Equal(true, ValueConverter.Convert(typeof(bool), "yes")); - Assert.Equal(false, ValueConverter.Convert(typeof(bool), "no")); - Assert.Equal(true, ValueConverter.Convert(typeof(bool), "1")); - Assert.Equal(false, ValueConverter.Convert(typeof(bool), "0")); - } - - /// - /// Represents an enumeration used for testing the conversion of enumeration values. - /// - [Flags] - private enum Color { Red = 1, Green = 2, Blue = 4 } - - /// - /// Tests how the handles the conversion of enumeration values. - /// - [Fact] - public void TestEnumerationValueConversion() - { - Assert.Equal(Color.Red, ValueConverter.Convert(typeof(Color), "Red")); - Assert.Equal(Color.Green, ValueConverter.Convert(typeof(Color), "Green")); - Assert.Equal(Color.Blue, ValueConverter.Convert(typeof(Color), "Blue")); - Assert.Equal(Color.Red | Color.Blue, ValueConverter.Convert(typeof(Color), "5")); - - Assert.Equal(DayOfWeek.Monday, ValueConverter.Convert(typeof(DayOfWeek), "Monday")); - Assert.Equal(DayOfWeek.Tuesday, ValueConverter.Convert(typeof(DayOfWeek), "Tuesday")); - Assert.Equal(DayOfWeek.Wednesday, ValueConverter.Convert(typeof(DayOfWeek), "Wednesday")); - Assert.Equal(DayOfWeek.Thursday, ValueConverter.Convert(typeof(DayOfWeek), "Thursday")); - Assert.Equal(DayOfWeek.Friday, ValueConverter.Convert(typeof(DayOfWeek), "Friday")); - Assert.Equal(DayOfWeek.Saturday, ValueConverter.Convert(typeof(DayOfWeek), "Saturday")); - Assert.Equal(DayOfWeek.Sunday, ValueConverter.Convert(typeof(DayOfWeek), "Sunday")); - } - - /// - /// Tests how the handles the conversion of integer values. - /// - [Fact] - public void TestIntegerValueConversion() - { - Assert.Equal((byte)1, ValueConverter.Convert(typeof(byte), "1")); - Assert.Equal((sbyte)123, ValueConverter.Convert(typeof(sbyte), "123")); - Assert.Equal((short)-23, ValueConverter.Convert(typeof(short), "-23")); - Assert.Equal((ushort)5000, ValueConverter.Convert(typeof(ushort), "5,0,0,0")); - Assert.Equal(-20000, ValueConverter.Convert(typeof(int), "-2e4")); - Assert.Equal(2300000u, ValueConverter.Convert(typeof(uint), "23e5")); - Assert.Equal(1234567890L, ValueConverter.Convert(typeof(long), "1,234,567,890")); - Assert.Equal(123456789UL, ValueConverter.Convert(typeof(ulong), "1,2,3,4,5,6,7,8,9")); - Assert.Equal(123400L, ValueConverter.Convert(typeof(object), "1,234E2")); - - Assert.Equal((byte)3, ValueConverter.Convert(typeof(byte), "+0x3")); - Assert.Equal((sbyte)123, ValueConverter.Convert(typeof(sbyte), "0X7B")); - Assert.Equal((short)-35, ValueConverter.Convert(typeof(short), "-0x23")); - Assert.Equal((ushort)2748, ValueConverter.Convert(typeof(ushort), "+0xABC")); - Assert.Equal(-11259375, ValueConverter.Convert(typeof(int), "-0xAbCdEf")); - Assert.Equal(65535u, ValueConverter.Convert(typeof(uint), "0XFFFF")); - Assert.Equal(-78187493520L, ValueConverter.Convert(typeof(long), "-0x1234567890")); - Assert.Equal(0UL, ValueConverter.Convert(typeof(ulong), "0x0")); - Assert.Equal(3735928559L, ValueConverter.Convert(typeof(object), "0XDEADBEEF")); - } - - /// - /// Tests how the handles the conversion of float values. - /// - [Fact] - public void TestFloatValueConversion() - { - Assert.Equal(1.0f, ValueConverter.Convert(typeof(float), "1")); - Assert.Equal(0.123d, ValueConverter.Convert(typeof(double), ".123")); - Assert.Equal(1234567m, ValueConverter.Convert(typeof(decimal), "1,234,567")); - Assert.Equal(123L, ValueConverter.Convert(typeof(object), "123")); - Assert.Equal(12.3f, ValueConverter.Convert(typeof(float), "1,2.3")); - Assert.Equal(123.456d, ValueConverter.Convert(typeof(double), "123.456")); - Assert.Equal(1.00001m, ValueConverter.Convert(typeof(decimal), "00001.00001")); - Assert.Equal(0.123m, ValueConverter.Convert(typeof(object), ".123")); - Assert.Equal(1234567.89f, ValueConverter.Convert(typeof(float), "1,234,567.89")); - Assert.Equal(123456789.0d, ValueConverter.Convert(typeof(double), "1,2,3,4,5,6,7,8,9")); - Assert.Equal(0.0m, ValueConverter.Convert(typeof(decimal), "00000")); - Assert.Equal(1234L, ValueConverter.Convert(typeof(object), "1,234")); - Assert.Equal(12000.0f, ValueConverter.Convert(typeof(float), "12e3")); - Assert.Equal(0.01d, ValueConverter.Convert(typeof(double), "10e-3")); - Assert.Equal(10000000000000000.0m, ValueConverter.Convert(typeof(decimal), "10E+15")); - Assert.Equal(0.00035m, ValueConverter.Convert(typeof(object), "3.5E-4")); - } - - /// - /// Tests how the handles the conversion of any values to collections. - /// - [Fact] - public void TestCollectionConversion() - { - Assert.Equal(new Color[] { Color.Red }, ValueConverter.Convert(typeof(Color[]), "Red")); - Assert.Equal(new Color[] { Color.Red | Color.Blue }, ValueConverter.Convert(typeof(Color[]), "5")); - Assert.Equal(new int[] { -255 }, ValueConverter.Convert(typeof(int[]), "-0xff")); - - Assert.Equal(new List { 1 }, ValueConverter.Convert(typeof(List), "1")); - Assert.Equal(new List { false }, ValueConverter.Convert(typeof(List), "False")); - } - - /// - /// Represents a very simple value converter for IP addresses, which is used to test custom value converters. - /// - private class IPAddressConverter : IValueConverter - { - public bool CanConvertFrom(string value) => IPAddress.TryParse(value, out _); - public bool CanConvertTo(Type resultType) => resultType == typeof(IPAddress); - public object Convert(Type resultType, string value) => IPAddress.Parse(value); - } - - /// - /// Tests how the handles custom value converters. - /// - [Fact] - public void TestCustomValueConverters() - { - ValueConverter.AddValueConverter(new IPAddressConverter()); - Assert.Equal(IPAddress.Loopback, ValueConverter.Convert(typeof(IPAddress), "127.0.0.1")); - Assert.Equal(IPAddress.IPv6Loopback, ValueConverter.Convert(typeof(IPAddress), "::1")); - Assert.Equal(new IPAddress(new byte[] { 0, 0, 0, 0 }), ValueConverter.Convert(typeof(object), "0.0.0.0")); - ValueConverter.ResetValueConverters(); - } - - #endregion - } -} \ No newline at end of file diff --git a/test/ValueConvertersTests.cs b/test/ValueConvertersTests.cs deleted file mode 100644 index 5fd3465..0000000 --- a/test/ValueConvertersTests.cs +++ /dev/null @@ -1,278 +0,0 @@ - -#region Using Directives - -using System; -using System.Text; -using System.CommandLine.Arguments; -using System.CommandLine.ValueConverters; -using Xunit; - -#endregion - -namespace System.CommandLine.Tests -{ - /// - /// Represents a set of unit tests for the value converters based on . - /// - public class ValueConvertersTests - { - #region Unit Tests - - /// - /// Tests the value converter that can convert strings to boolean values. - /// - [Fact] - public void TestBooleanConverter() - { - BooleanConverter booleanConverter = new BooleanConverter(); - - Assert.True(booleanConverter.CanConvertTo(typeof(bool))); - Assert.True(booleanConverter.CanConvertTo(typeof(Boolean))); - - Assert.False(booleanConverter.CanConvertTo(typeof(string))); - Assert.False(booleanConverter.CanConvertTo(typeof(float))); - Assert.False(booleanConverter.CanConvertTo(typeof(double))); - Assert.False(booleanConverter.CanConvertTo(typeof(int))); - - Assert.True(booleanConverter.CanConvertFrom(" oN ")); - Assert.True(booleanConverter.CanConvertFrom("OFF ")); - Assert.True(booleanConverter.CanConvertFrom(" YeS")); - Assert.True(booleanConverter.CanConvertFrom("No")); - Assert.True(booleanConverter.CanConvertFrom("true")); - Assert.True(booleanConverter.CanConvertFrom("FALSE")); - Assert.True(booleanConverter.CanConvertFrom("10")); - - Assert.False(booleanConverter.CanConvertFrom("abc")); - Assert.False(booleanConverter.CanConvertFrom("No!")); - - Assert.Equal(true, booleanConverter.Convert(typeof(bool), "TRUE")); - Assert.Equal(false, booleanConverter.Convert(typeof(object), "fAlSe")); - Assert.Equal(true, booleanConverter.Convert(typeof(bool), "on")); - Assert.Equal(false, booleanConverter.Convert(typeof(object), "oFF")); - Assert.Equal(true, booleanConverter.Convert(typeof(bool), "yeS")); - Assert.Equal(false, booleanConverter.Convert(typeof(object), "No")); - Assert.Equal(true, booleanConverter.Convert(typeof(bool), "-10")); - Assert.Equal(true, booleanConverter.Convert(typeof(bool), "123")); - Assert.Equal(false, booleanConverter.Convert(typeof(object), "0")); - - Assert.Throws(() => booleanConverter.Convert(typeof(object), "0.0")); - Assert.Throws(() => booleanConverter.Convert(typeof(object), "XYZ")); - } - - /// - /// Represents an enumeration used for testing the enumeration converter. - /// - private enum Animal { Cat, Dog, Bird } - - /// - /// Tests the value converter that can convert strings to enumeration values. - /// - [Fact] - public void TestEnumerationConverter() - { - EnumerationConverter enumerationConverter = new EnumerationConverter(); - - Assert.True(enumerationConverter.CanConvertTo(typeof(DayOfWeek))); - Assert.True(enumerationConverter.CanConvertTo(typeof(Animal))); - Assert.True(enumerationConverter.CanConvertTo(typeof(PlatformID))); - - Assert.False(enumerationConverter.CanConvertTo(typeof(string))); - Assert.False(enumerationConverter.CanConvertTo(typeof(int))); - Assert.False(enumerationConverter.CanConvertTo(typeof(bool))); - Assert.False(enumerationConverter.CanConvertTo(typeof(float))); - Assert.False(enumerationConverter.CanConvertTo(typeof(Enum))); - - Assert.False(enumerationConverter.CanConvertFrom("abc")); - Assert.False(enumerationConverter.CanConvertFrom("123")); - Assert.False(enumerationConverter.CanConvertFrom("")); - - Assert.Equal(Animal.Cat, enumerationConverter.Convert(typeof(Animal), "Cat")); - Assert.Equal(Animal.Dog, enumerationConverter.Convert(typeof(Animal), "Dog")); - Assert.Equal(Animal.Bird, enumerationConverter.Convert(typeof(Animal), "Bird")); - Assert.Equal(Animal.Bird, enumerationConverter.Convert(typeof(Animal), "2")); - - Assert.Throws(() => enumerationConverter.Convert(typeof(object), "Dog")); - Assert.Throws(() => enumerationConverter.Convert(typeof(Animal), "Fish")); - } - - /// - /// Tests the value converter that can convert strings to integer values. - /// - [Fact] - public void TestIntegerConverter() - { - IntegerConverter integerConverter = new IntegerConverter(); - - Assert.True(integerConverter.CanConvertTo(typeof(byte))); - Assert.True(integerConverter.CanConvertTo(typeof(sbyte))); - Assert.True(integerConverter.CanConvertTo(typeof(short))); - Assert.True(integerConverter.CanConvertTo(typeof(ushort))); - Assert.True(integerConverter.CanConvertTo(typeof(int))); - Assert.True(integerConverter.CanConvertTo(typeof(uint))); - Assert.True(integerConverter.CanConvertTo(typeof(long))); - Assert.True(integerConverter.CanConvertTo(typeof(ulong))); - - Assert.False(integerConverter.CanConvertTo(typeof(string))); - Assert.False(integerConverter.CanConvertTo(typeof(float))); - Assert.False(integerConverter.CanConvertTo(typeof(double))); - Assert.False(integerConverter.CanConvertTo(typeof(decimal))); - Assert.False(integerConverter.CanConvertTo(typeof(Enum))); - - Assert.True(integerConverter.CanConvertFrom(" 10 ")); - Assert.True(integerConverter.CanConvertFrom("123")); - Assert.True(integerConverter.CanConvertFrom("1,2,3,4 ")); - Assert.True(integerConverter.CanConvertFrom(" 1,234,567 ")); - Assert.True(integerConverter.CanConvertFrom(" 000,000")); - Assert.True(integerConverter.CanConvertFrom(" 3e7 ")); - Assert.True(integerConverter.CanConvertFrom("500e-2")); - Assert.True(integerConverter.CanConvertFrom(" 6E4 ")); - Assert.True(integerConverter.CanConvertFrom(" 2E+5")); - Assert.True(integerConverter.CanConvertFrom(" 0xFF")); - Assert.True(integerConverter.CanConvertFrom("0X123 ")); - Assert.True(integerConverter.CanConvertFrom(" 0x1F2A ")); - Assert.True(integerConverter.CanConvertFrom("-0X1")); - - Assert.False(integerConverter.CanConvertFrom("abc")); - Assert.False(integerConverter.CanConvertFrom("")); - Assert.False(integerConverter.CanConvertFrom(" ")); - Assert.False(integerConverter.CanConvertFrom(".")); - Assert.False(integerConverter.CanConvertFrom(" abc123")); - Assert.False(integerConverter.CanConvertFrom("0x ")); - Assert.False(integerConverter.CanConvertFrom(" e4 ")); - Assert.False(integerConverter.CanConvertFrom("23e ")); - Assert.False(integerConverter.CanConvertFrom(" 1 2 3")); - Assert.False(integerConverter.CanConvertFrom(" 1E-4 ")); - Assert.False(integerConverter.CanConvertFrom(" --4")); - Assert.False(integerConverter.CanConvertFrom("ABCDEF")); - Assert.False(integerConverter.CanConvertFrom("0x0X1")); - - Assert.Equal((byte)1, integerConverter.Convert(typeof(byte), "1")); - Assert.Equal((sbyte)123, integerConverter.Convert(typeof(sbyte), " 123")); - Assert.Equal((short)-23, integerConverter.Convert(typeof(short), "-23 ")); - Assert.Equal((ushort)5000, integerConverter.Convert(typeof(ushort), " 5,0,0,0 ")); - Assert.Equal(-20000, integerConverter.Convert(typeof(int), " -2e4 ")); - Assert.Equal(2300000u, integerConverter.Convert(typeof(uint), "23e5")); - Assert.Equal(1234567890L, integerConverter.Convert(typeof(long), "1,234,567,890 ")); - Assert.Equal(123456789UL, integerConverter.Convert(typeof(ulong), " 1,2,3,4,5,6,7,8,9")); - Assert.Equal(123400L, integerConverter.Convert(typeof(object), " 1,234E2 ")); - - Assert.Equal((byte)3, integerConverter.Convert(typeof(byte), "+0x3")); - Assert.Equal((sbyte)123, integerConverter.Convert(typeof(sbyte), " 0X7B ")); - Assert.Equal((short)-35, integerConverter.Convert(typeof(short), "-0x23 ")); - Assert.Equal((ushort)2748, integerConverter.Convert(typeof(ushort), " +0xABC ")); - Assert.Equal(-11259375, integerConverter.Convert(typeof(int), " -0xAbCdEf ")); - Assert.Equal(65535u, integerConverter.Convert(typeof(uint), "0XFFFF")); - Assert.Equal(-78187493520L, integerConverter.Convert(typeof(long), "-0x1234567890 ")); - Assert.Equal(0UL, integerConverter.Convert(typeof(ulong), " 0x0 ")); - Assert.Equal(3735928559L, integerConverter.Convert(typeof(object), " 0XDEADBEEF ")); - - Assert.Throws(() => integerConverter.Convert(typeof(byte), ",")); - Assert.Throws(() => integerConverter.Convert(typeof(sbyte), "256")); - Assert.Throws(() => integerConverter.Convert(typeof(short), "1234567890")); - Assert.Throws(() => integerConverter.Convert(typeof(object), "123e-2")); - Assert.Throws(() => integerConverter.Convert(typeof(uint), "-0xF")); - } - - /// - /// Tests the value converter that can convert strings to float values. - /// - [Fact] - public void TestFloatConverter() - { - FloatConverter floatConverter = new FloatConverter(); - - Assert.True(floatConverter.CanConvertTo(typeof(float))); - Assert.True(floatConverter.CanConvertTo(typeof(double))); - Assert.True(floatConverter.CanConvertTo(typeof(decimal))); - - Assert.False(floatConverter.CanConvertTo(typeof(string))); - Assert.False(floatConverter.CanConvertTo(typeof(int))); - Assert.False(floatConverter.CanConvertTo(typeof(Enum))); - - Assert.True(floatConverter.CanConvertFrom(" 10 ")); - Assert.True(floatConverter.CanConvertFrom("1.0")); - Assert.True(floatConverter.CanConvertFrom(" .2")); - Assert.True(floatConverter.CanConvertFrom(" 0 ")); - Assert.True(floatConverter.CanConvertFrom(" 1,000,000")); - Assert.True(floatConverter.CanConvertFrom(" 123.456 ")); - Assert.True(floatConverter.CanConvertFrom(".123")); - Assert.True(floatConverter.CanConvertFrom(" 1,234,567.89 ")); - Assert.True(floatConverter.CanConvertFrom("1e2")); - Assert.True(floatConverter.CanConvertFrom("12E-3")); - Assert.True(floatConverter.CanConvertFrom("1.2E4")); - - Assert.False(floatConverter.CanConvertFrom("abc")); - Assert.False(floatConverter.CanConvertFrom("")); - Assert.False(floatConverter.CanConvertFrom(" ")); - Assert.False(floatConverter.CanConvertFrom(".")); - Assert.False(floatConverter.CanConvertFrom("abc123")); - Assert.False(floatConverter.CanConvertFrom("1 2 3")); - Assert.False(floatConverter.CanConvertFrom("123e")); - Assert.False(floatConverter.CanConvertFrom("e123")); - - Assert.Equal(1.0f, floatConverter.Convert(typeof(float), "1")); - Assert.Equal(0.123d, floatConverter.Convert(typeof(double), " .123")); - Assert.Equal(1234567m, floatConverter.Convert(typeof(decimal), "1,234,567 ")); - Assert.Equal(123.0m, floatConverter.Convert(typeof(object), " 123")); - Assert.Equal(12.3f, floatConverter.Convert(typeof(float), " 1,2.3")); - Assert.Equal(123.456d, floatConverter.Convert(typeof(double), "123.456 ")); - Assert.Equal(1.00001m, floatConverter.Convert(typeof(decimal), "00001.00001")); - Assert.Equal(0.123m, floatConverter.Convert(typeof(object), ".123 ")); - Assert.Equal(1234567.89f, floatConverter.Convert(typeof(float), "1,234,567.89 ")); - Assert.Equal(123456789.0d, floatConverter.Convert(typeof(double), " 1,2,3,4,5,6,7,8,9")); - Assert.Equal(0.0m, floatConverter.Convert(typeof(decimal), " 00000 ")); - Assert.Equal(1234.0m, floatConverter.Convert(typeof(object), "1,234")); - Assert.Equal(12000.0f, floatConverter.Convert(typeof(float), "12e3")); - Assert.Equal(0.01d, floatConverter.Convert(typeof(double), "10e-3")); - Assert.Equal(10000000000000000.0m, floatConverter.Convert(typeof(decimal), " 10E+15 ")); - Assert.Equal(0.00035m, floatConverter.Convert(typeof(object), "3.5E-4")); - - Assert.Throws(() => floatConverter.Convert(typeof(float), ".")); - Assert.Throws(() => floatConverter.Convert(typeof(double), "122abc")); - Assert.Throws(() => floatConverter.Convert(typeof(decimal), " 1 2 3 4 ")); - Assert.Throws(() => floatConverter.Convert(typeof(object), "123..123")); - Assert.Throws(() => floatConverter.Convert(typeof(float), "e")); - Assert.Throws(() => floatConverter.Convert(typeof(double), "1E")); - Assert.Throws(() => floatConverter.Convert(typeof(decimal), " 3E")); - Assert.Throws(() => floatConverter.Convert(typeof(object), " 3e4E-1 ")); - } - - /// - /// Tests the value converter that can convert strings to string values. - /// - [Fact] - public void TestStringConverter() - { - StringConverter stringConverter = new StringConverter(); - - Assert.True(stringConverter.CanConvertTo(typeof(string))); - Assert.True(stringConverter.CanConvertTo(typeof(StringBuilder))); - - Assert.False(stringConverter.CanConvertTo(typeof(float))); - Assert.False(stringConverter.CanConvertTo(typeof(double))); - Assert.False(stringConverter.CanConvertTo(typeof(decimal))); - Assert.False(stringConverter.CanConvertTo(typeof(Enum))); - - Assert.True(stringConverter.CanConvertFrom(" 123 ")); - Assert.True(stringConverter.CanConvertFrom("1.0")); - Assert.True(stringConverter.CanConvertFrom(" Dog")); - Assert.True(stringConverter.CanConvertFrom(" True ")); - Assert.True(stringConverter.CanConvertFrom(" abc")); - Assert.True(stringConverter.CanConvertFrom("1.2E3")); - Assert.True(stringConverter.CanConvertFrom(" 0xFF")); - Assert.True(stringConverter.CanConvertFrom("Hello, World!")); - Assert.True(stringConverter.CanConvertFrom("The quick brown fox jumps over the lazy dog.")); - - Assert.Equal("Hello, World!", stringConverter.Convert(typeof(string), "Hello, World!")); - Assert.Equal("The quick brown fox jumps over the lazy dog.", stringConverter.Convert(typeof(object), "The quick brown fox jumps over the lazy dog.")); - - StringBuilder stringBuilder = new StringBuilder("Hello, World!"); - Assert.True(stringBuilder.Equals(stringConverter.Convert(typeof(StringBuilder), "Hello, World!") as StringBuilder)); - stringBuilder = new StringBuilder("The quick brown fox"); - stringBuilder.Append(" jumps over the lazy dog."); - Assert.True(stringBuilder.Equals(stringConverter.Convert(typeof(StringBuilder), "The quick brown fox jumps over the lazy dog.") as StringBuilder)); - } - - #endregion - } -} diff --git a/tests/linters/.gitkeep b/tests/linters/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit-tests/.gitkeep b/tests/unit-tests/.gitkeep new file mode 100644 index 0000000..e69de29 From a890e2fa0673d29fd0ee4133f6f4f3e47aa687c5 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Sun, 5 Jul 2026 21:45:30 +0200 Subject: [PATCH 2/5] Added Linters & Code Formatters The project now uses a set of linters and a code formatter to (1) find and correct problems in the code and (2) to consistently format all files. If you use Visual Studio Code to work on the project, all linters and the code formatter are configured to directly work in the code editor. A set of recommended extensions is included for the linters and the code formatter. The following tools are being used: - **CSpell**, a spell checker for code, to ensure that there are no misspellings in the code. - **MarkdownLint**, a linter for Markdown files, which ensures that the Markdown files are consistently formatted and standards are enforced. - **dprint**, an unopinionated, configurable code formatter with plugins for many languages. This code formatter is used to format Markdown, JSON, YAML, and TOML files in the project. --- .vscode/extensions.json | 11 +++- .vscode/settings.json | 49 +++++++++++++---- CHANGELOG.md | 4 ++ README.md | 10 ++++ dprint.json | 82 ++++++++++++++++++++++++++++ tests/linters/.cspell.json | 94 +++++++++++++++++++++++++++++++++ tests/linters/.gitkeep | 0 tests/linters/.markdownlint.yml | 11 ++++ 8 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 dprint.json create mode 100644 tests/linters/.cspell.json delete mode 100644 tests/linters/.gitkeep create mode 100644 tests/linters/.markdownlint.yml diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 02b8a94..c1d11f1 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,15 @@ // Language support for .gitignore files "codezombiech.gitignore", + // Perform spell-checking of the documentation, code, and comments with support for warnings/errors in Visual Studio Code + "streetsidesoftware.code-spell-checker", + + // Integrates MarkdownLint into the editor + "DavidAnson.vscode-markdownlint", + + // Integrates the dprint code formatter into the editor + "dprint.dprint", + // Adds useful features for editing Markdown tables "TakumiI.markdowntable", @@ -12,5 +21,5 @@ // Integrates many useful features for editing Markdown "yzhang.markdown-all-in-one", - ] + ], } diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d75c76..f9f2131 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,3 @@ - { // A vertical ruler in the code editor at 150 characters, this is a visual aide to help users keep the 150 character line limit file files that // warrant a line limit @@ -7,6 +6,7 @@ // Maps file names to file types; this is required when Visual Studio Code does not correctly auto-detect file types based on file extension "files.associations": { "**/.vscode/*.json": "jsonc", + "dprint.json": "jsonc", "LICENSE": "plaintext", }, @@ -37,7 +37,7 @@ "explorer.fileNesting.enabled": true, "explorer.fileNesting.expand": false, "explorer.fileNesting.patterns": { - ".editorconfig": ".gitattributes, .gitignore", + ".editorconfig": ".gitattributes, .gitignore, dprint.json", "README.md": "CHANGELOG.md, CONTRIBUTORS.md, LICENSE, TODO.md", }, @@ -63,18 +63,47 @@ "git.followTagsWhenSync": true, "git.rebaseWhenSync": true, + // Imports the external settings for the code spell checker CSPell, furthermore, the "ignorePaths" and the "useGitignore" settings are reset, + // because they overwrite the settings in the imported configuration, also the diagnostic level is increased from "Information" to "Error" to + // indicate that spelling mistakes must be corrected + "cSpell.ignorePaths": [], + "cSpell.useGitignore": false, + "cSpell.diagnosticLevel": "Error", + "cSpell.import": ["${workspaceFolder}/tests/linters/.cspell.json"], + + // Configures the Markdown linter MarkdownLint to use our custom configuration + "markdownlint.config": { "extends": "${workspaceFolder}/tests/linters/.markdownlint.yml" }, + + // Configures the code formatter dprint + "dprint.verbose": true, + "editor.defaultFormatter": "dprint.dprint", + "editor.formatOnType": false, + "editor.formatOnSave": true, + + // A list of domains from which JSON schema files are automatically downloaded + "json.schemaDownload.trustedDomains": { + "https://developer.microsoft.com/json-schemas/": true, + "https://json-schema.org/": true, + "https://json.schemastore.org/": true, + "https://plugins.dprint.dev/": true, + "https://raw.githubusercontent.com/devcontainers/spec/": true, + "https://raw.githubusercontent.com/microsoft/vscode/": true, + "https://schemastore.azurewebsites.net/": true, + "https://www.schemastore.org/": true, + }, + // Configures the icon theme to be VSCode Icons and adapts the icons of certain files and directories for the VSCode Icons plugin "workbench.iconTheme": "vscode-icons", "vsicons.associations.files": [ - {"icon": "dal", "extensions": ["CHANGELOG.md"], "filename": true, "format": "svg"}, - {"icon": "docz", "extensions": ["README.md"], "filename": true, "format": "svg"}, - {"icon": "janet", "extensions": ["CONTRIBUTORS.md"], "filename": true, "format": "svg"}, - {"icon": "todo", "extensions": ["TODO.md"], "filename": true, "format": "svg"}, - {"icon": "vscode", "extensions": [".vscode/extensions.json", ".vscode/settings.json"], "filename": true, "format": "svg"}, + { "icon": "dal", "extensions": ["CHANGELOG.md"], "filename": true, "format": "svg" }, + { "icon": "docz", "extensions": ["README.md"], "filename": true, "format": "svg" }, + { "icon": "janet", "extensions": ["CONTRIBUTORS.md"], "filename": true, "format": "svg" }, + { "icon": "todo", "extensions": ["TODO.md"], "filename": true, "format": "svg" }, + { "icon": "vscode", "extensions": [".vscode/extensions.json", ".vscode/settings.json"], "filename": true, "format": "svg" }, ], "vsicons.associations.folders": [ - {"icon": "coverage", "extensions": ["linters"], "format": "svg"}, - {"icon": "template", "extensions": ["design"], "format": "svg"}, - {"icon": "test", "extensions": ["unit-tests"], "format": "svg"}, + { "icon": "coverage", "extensions": ["linters"], "format": "svg" }, + { "icon": "template", "extensions": ["design"], "format": "svg" }, + { "icon": "test", "extensions": ["unit-tests"], "format": "svg" }, ], } diff --git a/CHANGELOG.md b/CHANGELOG.md index 70eefe9..556b8f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ Complete rewrite as a .NET 10 library using the modern application hosting model In addition to the rewrite, the following changes were made: - Since the permissive MIT license has recently been used more and more by large corporations to take from the open source community and not give anything back, the license of this project was changed from MIT to LGPL 3.0. The LGPL still allows people to freely use the library in their own software, even for commercial projects, but making any changes to it forces the derivative work to be released under the same license, thus forcing companies to give back to the community. +- The project now uses a set of linters and a code formatter to (1) find and correct problems in the code and (2) to consistently format all files. If you use Visual Studio Code to work on the project, all linters and the code formatter are configured to directly work in the code editor. A set of recommended extensions is included for the linters and the code formatter. The following tools are being used: + - **CSpell**, a spell checker for code, to ensure that there are no misspellings in the code. + - **MarkdownLint**, a linter for Markdown files, which ensures that the Markdown files are consistently formatted and standards are enforced. + - **dprint**, an unopinionated, configurable code formatter with plugins for many languages. This code formatter is used to format Markdown, JSON, XML, YAML, and TOML files in the project. ## v0.2.0 diff --git a/README.md b/README.md index a1f758c..0e5cec3 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,16 @@ The repository is organized into a few top-level directories: - **`docs/`** — The project documentation. Start with [`docs/README.md`](docs/README.md). - **`design/`** — Design documents, notes, and assets related to the project. +## Development + +### Code Formatting & Linting + +The project uses a set of linters and a code formatter to (1) find and correct problems in the code and (2) to consistently format all files. If you use Visual Studio Code to work on the project, all linters and the code formatter are configured to directly work in the code editor. A set of recommended extensions is included for the linters and the code formatter. The following tools are being used: + +- **[CSpell](tests/linters/.cspell.json)**, a spell checker for code, to ensure that there are no misspellings in the code. +- **[MarkdownLint](tests/linters/.markdownlint.yml)**, a linter for Markdown files, which ensures that the Markdown files are consistently formatted and standards are enforced. +- **[dprint](dprint.json)**, an unopinionated, configurable code formatter with plugins for many languages. This code formatter is used to format Markdown, JSON, XML, YAML, and TOML files in the project. + ## Contributing If you'd like to contribute, there are multiple ways you can help out. If you find a bug or have a feature request, please feel free to open an issue on GitHub. If you want to contribute code, please fork the repository and use a feature branch. Pull requests are always welcome. Before forking, please open an issue where you describe what you want to do. This helps to align your ideas with mine and may prevent you from doing work, that I am already planning on doing. If you have contributed to the project, please add yourself to the [contributors list](CONTRIBUTORS.md) and document your changes in the [changelog](CHANGELOG.md). Also, if necessary, update the [documentation](docs/README.md). To help speed up the merging of your pull request, please comment and document your code extensively and try to emulate the coding style of the project. diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000..cf5d9a5 --- /dev/null +++ b/dprint.json @@ -0,0 +1,82 @@ +{ + // By default, dprint only formats files that have changed since the last time it was run; setting "incremental" to false forces dprint to always + // format all eligible files; unlike most settings, "incremental" is a global setting that cannot be inherited through "extends" (dprint reports + // it as an unknown property when it appears in an extended configuration), so it has to be declared here in the top-level configuration instead + // of in the extended configuration in "tests/linters" + "incremental": false, + + // By default, everything should be indented with 4 space characters and no tabs should be used (exceptions are configured accordingly) + "indentWidth": 4, + "useTabs": false, + + // No language should ever exceed 150 characters per line (although dprint may sometimes fail to break lines to fullfil this limit); some + // languages, like Markdown have separate settings to disable line breaking, so the character limit does not apply to them + "lineWidth": 150, + + // Configuration for Markdown files + "markdown": { + "textWrap": "never", + "emphasisKind": "asterisks", + }, + + // Configuration for JSON files + "json": { + // The JSON standard does not allow trailing commas, but the JSONC standard does, so we force trailing commas only for JSONC + "trailingCommas": "jsonc", + "jsonTrailingCommaFiles": [ + ".cspell.json", + ".vscode/extensions.json", + ".vscode/settings.json", + ".vscode/tasks.json", + "dprint.json", + ], + }, + + // Configuration for XML files + "markup": { + "formatComments": true, + "preferAttrsSingleLine": true, + + // C# project files (.csproj) and the XML-based solution files (.slnx) are XML, but they do not use a file extension that the markup_fmt + // plugin recognizes by default, so they are explicitly associated with the plugin here; associations are additive, so the plugin keeps + // formatting the file types it already recognizes on its own (e.g. .xml and .html) + "associations": [ + "**/*.csproj", + "**/*.slnx", + ], + + // The closing angle bracket is on the same line as the last attribute when an HTML tag is broken over multiple lines + "closingBracketSameLine": true, + }, + + // Configuration for YAML files + "yaml": { + "indentWidth": 2, + "trimTrailingZero": true, + + // Forces a whitespace at the beginning of a comment + "formatComments": true, + + // Wraps prose text in plain scalars when they exceed the print width + "proseWrap": "always", + }, + + // Configuration for TOML files + "toml": { + "indentWidth": 2, + }, + + // Glob patterns for files that should be excluded + "excludes": [ + "**/*.svg", + ], + + // A list of plugins that should be used by dprint + "plugins": [ + "https://plugins.dprint.dev/g-plane/markup_fmt-v0.27.3.wasm", + "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.6.0.wasm", + "https://plugins.dprint.dev/json-0.22.0.wasm", + "https://plugins.dprint.dev/markdown-0.22.1.wasm", + "https://plugins.dprint.dev/toml-0.7.0.wasm", + ], +} diff --git a/tests/linters/.cspell.json b/tests/linters/.cspell.json new file mode 100644 index 0000000..f6402f6 --- /dev/null +++ b/tests/linters/.cspell.json @@ -0,0 +1,94 @@ +{ + // Version of the settings file, this should always be 0.2 + "version": "0.2", + + // The code in this repository is checked against American English + "language": "en-US", + + // Words do not have to match in case + "caseSensitive": false, + + // A list of file types that should be enabled (not all file types are enabled by default) + "enableFiletypes": [ + "gitignore", + ], + + // A list of paths that are ignored when spell-checking (ignores the .git directory, but excludes the commit message file that is created by Git + // during a commit, and the cSpell configuration file itself) + "ignorePaths": [ + // The CSpell configuration file is ignored, because it contains flag words that are always considered incorrect, which results in the + // configuration file being marked as containing spelling errors + "**/.cspell.json", + + // Operating system file preview databases + "**/.DS_Store", + "**/Thumbs.db", + + // Everything in the Git directory is ignored, except for the commit message file + "**/.git/!(COMMIT_EDITMSG)", + + // Ignores SVG files (the entire file type could be ignored, but CSpell groups SVG files together with all XML-based file types) + "**/*.svg", + + // Build output and caches, which contain binaries and generated code that should not be spell-checked + "**/bin/", + "**/obj/", + ], + + // A list of dictionaries that should be added beyond the American English dictionary + "dictionaries": [ + // Dictionaries that contain common misspellings + "en-us", + "en-us-common-misspellings", + "en-common-misspellings", + "de-DE", + "es-ES", + "fr-FR", + + // Dictionaries that contain the keywords of the programming, markup, styling, and configuration languages used in the project + "bash", + "docker", + "git", + "csharp", + + // Other miscellaneous dictionaries + "companies", + "computing-acronyms", + "filetypes", + "fonts", + "fullstack", + "lorem-ipsum", + "misc", + "networking-terms", + "public-licenses", + "software-terms", + "software-term-suggestions", + "software-tools", + "coding-compound-words", + "web-services", + ], + + // A list of words to be always considered incorrect (by default, only words that have 4 or more characters are spell-checked, so this list + // includes some common typos of words that are too short to be recognizes by CSpell) + "flagWords": [ + "adn", + "fo", + "fro", + "hte", + "ot", + ], + + // A list of words that are not in the included default dictionary for American English + "words": [ + "Anson", + "codezombiech", + "Containerfile", + "docz", + "markdowntable", + "Neumann", + "Takumi", + "vsicons", + "wayou", + "yzhang", + ], +} diff --git a/tests/linters/.gitkeep b/tests/linters/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/linters/.markdownlint.yml b/tests/linters/.markdownlint.yml new file mode 100644 index 0000000..0658b7c --- /dev/null +++ b/tests/linters/.markdownlint.yml @@ -0,0 +1,11 @@ +# Use the default configuration for all rules +default: true + +# The line length rule is not used in this project, as breaking the lines manually takes a lot of work and always has to be redone after updating a +# document, furthermore, many editors allow the auto-breaking of lines to fit the text inside the window, which is optimal for prose +line-length: false + +# There are some inline-HTML tags that are allowed, although in general using HTML should be abstained from +no-inline-html: + allowed_elements: + - kbd From 0cdc9aa76d4bf0b60ad41af9b4043b1e0df51617 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Sun, 5 Jul 2026 22:31:47 +0200 Subject: [PATCH 3/5] Added a GitHub Actions Workflow A new GitHub Actions Workflow was added, which runs the linters and the code formatter on every push to any branch. --- .github/workflows/linters.yml | 58 +++++++++++++++++++++++++++++++++++ .vscode/settings.json | 1 + 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/linters.yml diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..04a66c5 --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,58 @@ +# The display name of the workflow, shown in the "Actions" tab of the GitHub repository and in the checks of a commit or pull request +name: Linters + +# Defines the events that trigger this workflow; this workflow runs on every push to any branch of the repository +on: + push: + +# Defines the jobs that make up the workflow; all jobs run in parallel by default, but here every linter is a separate step of a single job, so that +# they all share the same checked-out working tree and Node.js installation +jobs: + # A single job that runs all linters and code formatters of the project on a virtual machine with the latest Ubuntu image, which is the fastest and + # cheapest of the available runners + lint: + name: Run Linters + runs-on: ubuntu-latest + + # The sequence of tasks that are executed as part of the job; the steps run sequentially in the same runner and share the same file system + steps: + # Checks out the repository into the runner's working directory, so that the linters have access to the source code and their configuration + # files; the "actions/checkout" action is pinned to a major version to receive non-breaking updates automatically + - name: Checkout Repository + uses: actions/checkout@v4 + + # Installs Node.js, which is required to run the npm-based linters CSpell and MarkdownLint, as well as the dprint code formatter, which is + # distributed as an npm package; pins the Node.js version to the current long-term support release, so that the linters run in a stable and + # reproducible environment + - name: Set Up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + # Installs the linters and the code formatter globally, so that they are available as commands in the subsequent steps; installing them in a + # dedicated step keeps the linter steps below focused on just running the tools + - name: Install Linters + run: npm install --global dprint@0.55.1 cspell@10.0.1 markdownlint-cli2@0.23.0 + + # Runs the dprint code formatter in check mode; "check" only verifies that all files are already formatted and exits with a non-zero status code + # if any file is not, but unlike "dprint fmt" it never modifies any file; dprint automatically discovers the "dprint.json" stub in the + # repository root, which extends the real configuration in "tests/linters", so no configuration path has to be passed explicitly, and the + # "**/*" glob makes dprint check the whole repository (relative to the working directory) + - name: Check Formatting (dprint) + run: dprint check "**/*" + + # Runs the CSpell spell checker over every file in the repository; the "lint" command reports spelling mistakes and exits with a non-zero status + # code if any are found, the configuration path is passed explicitly, and "--no-progress" suppresses the per-file progress output to keep the + # log readable; the "if" ensures that the step runs (unless the workflow was cancelled), even if the previous linter reported an error and + # exited with a non-zero exit code, so that the job runs all linters instead of stopping at the first failure + - name: Spell Check (CSpell) + if: ${{ !cancelled() }} + run: cspell lint --config tests/linters/.cspell.json --no-progress "**/*" + + # Runs the MarkdownLint linter over all Markdown files in the repository; the configuration path is passed explicitly via "--config", and the + # build output directories are excluded using markdownlint-cli2's "#"-prefixed negated globs (markdownlint-cli2 has no "--ignore" flag), so that + # only the project's own documentation is linted; the "if" ensures that the step runs (unless the workflow was cancelled), even if the previous + # linter reported an error and exited with a non-zero exit code, so that the job runs all linters instead of stopping at the first failure + - name: Lint Markdown (MarkdownLint) + if: ${{ !cancelled() }} + run: markdownlint-cli2 --config tests/linters/.markdownlint.yml "**/*.md" "#**/bin/**" "#**/obj/**" diff --git a/.vscode/settings.json b/.vscode/settings.json index f9f2131..474d51c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -103,6 +103,7 @@ ], "vsicons.associations.folders": [ { "icon": "coverage", "extensions": ["linters"], "format": "svg" }, + { "icon": "github", "extensions": ["workflows"], "format": "svg" }, { "icon": "template", "extensions": ["design"], "format": "svg" }, { "icon": "test", "extensions": ["unit-tests"], "format": "svg" }, ], From def381f5f4e5e49b69fdf287c2bdba762315cf43 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Mon, 6 Jul 2026 23:48:02 +0200 Subject: [PATCH 4/5] Added Empty C# Library & Sample Projects Added two C# projects, one for the CLI.NET Core library and one for the sample app. Also added a solution in the new SLNX format that contains the two project. The projects are fully configured and have a custom read me. --- .vscode/settings.json | 2 + source/.gitkeep | 0 source/CLI.NET Core.slnx | 8 ++++ source/clinet-core/CLI.NET Core.csproj | 34 ++++++++++++++++ source/clinet-core/README.md | 3 ++ .../sample-app/CLI.NET Core Sample App.csproj | 39 +++++++++++++++++++ source/sample-app/README.md | 3 ++ tests/linters/.cspell.json | 2 + 8 files changed, 91 insertions(+) delete mode 100644 source/.gitkeep create mode 100644 source/CLI.NET Core.slnx create mode 100644 source/clinet-core/CLI.NET Core.csproj create mode 100644 source/clinet-core/README.md create mode 100644 source/sample-app/CLI.NET Core Sample App.csproj create mode 100644 source/sample-app/README.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 474d51c..ace795b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -102,6 +102,8 @@ { "icon": "vscode", "extensions": [".vscode/extensions.json", ".vscode/settings.json"], "filename": true, "format": "svg" }, ], "vsicons.associations.folders": [ + { "icon": "app", "extensions": ["sample-app"], "format": "svg" }, + { "icon": "cli", "extensions": ["clinet-core"], "format": "svg" }, { "icon": "coverage", "extensions": ["linters"], "format": "svg" }, { "icon": "github", "extensions": ["workflows"], "format": "svg" }, { "icon": "template", "extensions": ["design"], "format": "svg" }, diff --git a/source/.gitkeep b/source/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/source/CLI.NET Core.slnx b/source/CLI.NET Core.slnx new file mode 100644 index 0000000..a3f04fa --- /dev/null +++ b/source/CLI.NET Core.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/source/clinet-core/CLI.NET Core.csproj b/source/clinet-core/CLI.NET Core.csproj new file mode 100644 index 0000000..cf35cd4 --- /dev/null +++ b/source/clinet-core/CLI.NET Core.csproj @@ -0,0 +1,34 @@ + + + + net10.0 + enable + enable + CliNetCore + true + + + + + CLI.NET Core + CLI.NET Core + en-US + + + + + CliNetCore + 0.1.0 + CLI.NET Core + David Neumann + A command line application framework in the style of ASP.NET Core. + Copyright © 2026 David Neumann + README.md + https://github.com/lecode-official/command-line-parser/blob/master/source/clinet-core + git + https://github.com/lecode-official/command-line-parser + + + + + diff --git a/source/clinet-core/README.md b/source/clinet-core/README.md new file mode 100644 index 0000000..f89bca0 --- /dev/null +++ b/source/clinet-core/README.md @@ -0,0 +1,3 @@ +# CLI.NET Core + +CLI.NET Core is a command line application framework in the style of ASP.NET Core. It allows you to define commands and command line arguments in much the same way you would define actions and arguments for a Web API in ASP.NET Core. diff --git a/source/sample-app/CLI.NET Core Sample App.csproj b/source/sample-app/CLI.NET Core Sample App.csproj new file mode 100644 index 0000000..5e64938 --- /dev/null +++ b/source/sample-app/CLI.NET Core Sample App.csproj @@ -0,0 +1,39 @@ + + + + net10.0 + Exe + enable + enable + CliNetCore.SampleApp + + + + + CLI.NET Core Sample App + CLI.NET Core + en-US + + + + + CliNetCoreSampleApp + 0.1.0 + CLI.NET Core Sample App + David Neumann + A sample app for demonstrating the CLI.NET Core application framework. + Copyright © 2026 David Neumann + README.md + https://github.com/lecode-official/command-line-parser/blob/master/source/sample-app + git + https://github.com/lecode-official/command-line-parser + + + + + + + + + + diff --git a/source/sample-app/README.md b/source/sample-app/README.md new file mode 100644 index 0000000..dcb180f --- /dev/null +++ b/source/sample-app/README.md @@ -0,0 +1,3 @@ +# CLI.NET Core Sample App + +A sample application demonstrating the use of the CLI.NET Core application framework. diff --git a/tests/linters/.cspell.json b/tests/linters/.cspell.json index f6402f6..b3aff57 100644 --- a/tests/linters/.cspell.json +++ b/tests/linters/.cspell.json @@ -81,11 +81,13 @@ // A list of words that are not in the included default dictionary for American English "words": [ "Anson", + "clinet", "codezombiech", "Containerfile", "docz", "markdowntable", "Neumann", + "slnx", "Takumi", "vsicons", "wayou", From 061f68b8eaabcbed0a50f433f8105f0d18a980be Mon Sep 17 00:00:00 2001 From: David Neumann Date: Tue, 7 Jul 2026 00:18:49 +0200 Subject: [PATCH 5/5] Created a New Logo A completely new, modern logo was designed using Claude Design. It heavily borrows from the ASP.NET Core logo, with the "C" mark and a dot. The logo is available in both light and dark mode variants in the `design/` directory and a design guide for it was created. --- .github/workflows/linters.yml | 4 +- CHANGELOG.md | 1 + README.md | 4 +- design/.gitkeep | 0 design/DESIGN.md | 89 +++++++++++++++++++++++++++++++ design/badge-flat-dark.svg | 1 + design/badge-flat-light.svg | 1 + design/badge-glass-dark.svg | 28 ++++++++++ design/badge-glass-light.svg | 28 ++++++++++ design/mark-dark.svg | 1 + design/mark-light.svg | 1 + design/mini-badge-flat-dark.svg | 1 + design/mini-badge-flat-light.svg | 1 + design/readme-header-dark.png | Bin 0 -> 29373 bytes design/readme-header-dark.svg | 32 +++++++++++ design/readme-header-light.png | Bin 0 -> 26002 bytes design/readme-header-light.svg | 32 +++++++++++ design/wordmark-dark.svg | 2 + design/wordmark-light.svg | 2 + design/wordmark.html | 69 ++++++++++++++++++++++++ tests/linters/.cspell.json | 3 ++ 21 files changed, 297 insertions(+), 3 deletions(-) delete mode 100644 design/.gitkeep create mode 100644 design/DESIGN.md create mode 100644 design/badge-flat-dark.svg create mode 100644 design/badge-flat-light.svg create mode 100644 design/badge-glass-dark.svg create mode 100644 design/badge-glass-light.svg create mode 100644 design/mark-dark.svg create mode 100644 design/mark-light.svg create mode 100644 design/mini-badge-flat-dark.svg create mode 100644 design/mini-badge-flat-light.svg create mode 100644 design/readme-header-dark.png create mode 100644 design/readme-header-dark.svg create mode 100644 design/readme-header-light.png create mode 100644 design/readme-header-light.svg create mode 100644 design/wordmark-dark.svg create mode 100644 design/wordmark-light.svg create mode 100644 design/wordmark.html diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 04a66c5..3b57168 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -19,13 +19,13 @@ jobs: # Checks out the repository into the runner's working directory, so that the linters have access to the source code and their configuration # files; the "actions/checkout" action is pinned to a major version to receive non-breaking updates automatically - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 # Installs Node.js, which is required to run the npm-based linters CSpell and MarkdownLint, as well as the dprint code formatter, which is # distributed as an npm package; pins the Node.js version to the current long-term support release, so that the linters run in a stable and # reproducible environment - name: Set Up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 24 diff --git a/CHANGELOG.md b/CHANGELOG.md index 556b8f4..0d2440b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ In addition to the rewrite, the following changes were made: - **CSpell**, a spell checker for code, to ensure that there are no misspellings in the code. - **MarkdownLint**, a linter for Markdown files, which ensures that the Markdown files are consistently formatted and standards are enforced. - **dprint**, an unopinionated, configurable code formatter with plugins for many languages. This code formatter is used to format Markdown, JSON, XML, YAML, and TOML files in the project. +- A completely new, modern logo was designed, which heavily borrows from the ASP.NET Core logo, with the "C" wordmark and a dot. The logo is available in both light and dark mode variants in the `design/` directory and a design guide for it was created. ## v0.2.0 diff --git a/README.md b/README.md index 0e5cec3..8e789dc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # CLI.NET Core +![CLI.NET Core Logo](design/readme-header-dark.png#gh-dark-mode-only) ![CLI.NET Core Logo](design/readme-header-light.png#gh-light-mode-only) + CLI.NET Core is a command line application framework in the style of ASP.NET Core. It allows you to define commands and command line arguments in much the same way you would define actions and arguments for a Web API in ASP.NET Core. ## Project Structure @@ -9,7 +11,7 @@ The repository is organized into a few top-level directories: - **`source/`** — The source code of the CLI.NET Core framework itself. - **`tests/`** — The unit tests and integration tests that verify the behavior of the framework, as well as the linters and code formatters that enforce a consistent and high-quality codebase. - **`docs/`** — The project documentation. Start with [`docs/README.md`](docs/README.md). -- **`design/`** — Design documents, notes, and assets related to the project. +- **`design/`** — Design assets related to the project and the [design guide](design/DESIGN.md). ## Development diff --git a/design/.gitkeep b/design/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/design/DESIGN.md b/design/DESIGN.md new file mode 100644 index 0000000..0c41dc7 --- /dev/null +++ b/design/DESIGN.md @@ -0,0 +1,89 @@ +# CLI.NET Core — Brand & Logo Guide + +This document describes the CLI.NET Core visual identity and how to use the logo assets in this repository. All assets live in this folder. + +## Concept + +The mark is an **aperture "C"** cradling a **dot**. + +- The **C** stands for CLI, Core, and C#. +- The **dot** is the `.` of `.NET` — the pivot the whole ecosystem turns around. +- The opening of the C reads as an *intake*: arguments going in, structure coming out. It nods to the way ASP.NET Core routes a request to a handler — the same API feel CLI.NET Core mirrors for the command line. + +The style is **restrained and terminal-native**: near-black and a single violet accent, geometric construction, no literal terminal window or blinking prompt. + +## Color + +| Token | Hex | Use | +| --------------- | --------------------- | --------------------------------------- | +| Ink | `#23222B` | Primary text, mark on light | +| Ink (mark) | `#4E4672` | The "C" on light glass badges | +| Accent (violet) | `#6D4AFF` | The dot, the `.` in the wordmark, links | +| Accent (light) | `#A98CFF` / `#8B6BFF` | Dot / accent on dark surfaces | +| Paper | `#F3F2F5` | Mark on dark, light surfaces | +| Near-black | `#16151C` | Dark surfaces, terminal chrome | + +**Dark badge field** (radial, light→dark): `#4A3D82 → #2C2650 → #1C1930` **Light badge field** (linear): `#EFEAFF → #E6DBFF → #F4ECFB` + +The violet accent is the only saturated color. Use it sparingly — one accent per composition. + +## Typography + +**Space Grotesk** — wordmark and headings. The wordmark is weight **600** with `CLI` / `NET` in ink, the `.` in accent violet, and `Core` in weight **400**, slightly muted. + +Wordmark spelling is always **`CLI.NET Core`** — the dot is colored, `Core` is a separate lighter word. + +## The Mark + +- Outer circle radius **39**, inner radius **21**, centered in a 100×100 box. +- Mouth opening on the right, spanning **±34°**. +- The four mouth terminals are filleted with an **equal corner radius of 3** (mathematically tangent to the arcs — no visible kink). +- The dot sits at **(73, 50)** with radius **8**, in the accent violet. + +Do not re-draw the mark by hand; use the vector files below. + +## Variants & Files + +### Core Vector Marks + +- `mark-light.svg` — ink mark + violet dot, for light backgrounds +- `mark-dark.svg` — paper mark + light-violet dot, for dark backgrounds + +### App/Package Badges + +- `badge-glass-light.svg` / `-dark.svg` — the **primary** frosted-glass badge (drop shadow on the inner panel, inner glow, gentle gradient dither). +- `badge-flat-light.svg` / `-dark.svg` — flat, full-bleed badges. Use for small favicons and anywhere filter effects may be stripped. + +### Wordmark Lockups + +- `wordmark-light.svg` / `-dark.svg` — compact bar (mark + text). +- `readme-light.svg` / `-dark.svg` — tall horizontal lockup for READMEs. +- `wordmark-bars.html` — responsive, fill-width HTML version of the bar (`.clinet-core-bar`, add `.clinet-core-bar--dark` on dark surfaces). + +## Using the Logo on GitHub + +GitHub sanitizes SVG `` effects when rendered via ``, so use the **baked PNGs** for the glass look, and let GitHub pick light/dark: + +```markdown +![CLI.NET Core Logo](design/readme-header-dark.png#gh-dark-mode-only) ![CLI.NET Core Logo](design/readme-header-light.png#gh-light-mode-only) +``` + +## Dos/Don'ts + +**Dos:** + +- Keep clear space around the mark equal to the dot's diameter. +- Pair the light mark with light surfaces, the dark mark with dark surfaces. +- Use the flat badge when effects might not render (tiny sizes, sanitized SVG). + +**Don'ts:** + +- Recolor the mark outside the palette, or add a second accent. +- Stretch or skew the mark or wordmark. +- Add a literal terminal window, cursor, or prompt. +- Rasterize the glass SVG very large — an 8-bit gradient will band; prefer the vector master or a super-sampled PNG. + +## Notes on Formats + +- **SVG** is the source of truth (resolution-independent). The glass badges use only widely-supported filter primitives (`feGaussianBlur`, `feOffset`, `feComposite`, `feFlood`, `feTurbulence`) — they open correctly in Inkscape. +- The SVG wordmarks reference the **Space Grotesk** font; install it or request an outlined version if you need them fully self-contained. diff --git a/design/badge-flat-dark.svg b/design/badge-flat-dark.svg new file mode 100644 index 0000000..8029f68 --- /dev/null +++ b/design/badge-flat-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/badge-flat-light.svg b/design/badge-flat-light.svg new file mode 100644 index 0000000..a8e79c4 --- /dev/null +++ b/design/badge-flat-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/badge-glass-dark.svg b/design/badge-glass-dark.svg new file mode 100644 index 0000000..ce1a218 --- /dev/null +++ b/design/badge-glass-dark.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/design/badge-glass-light.svg b/design/badge-glass-light.svg new file mode 100644 index 0000000..7dac1fc --- /dev/null +++ b/design/badge-glass-light.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/design/mark-dark.svg b/design/mark-dark.svg new file mode 100644 index 0000000..fd20084 --- /dev/null +++ b/design/mark-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/mark-light.svg b/design/mark-light.svg new file mode 100644 index 0000000..6a4bfcb --- /dev/null +++ b/design/mark-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/mini-badge-flat-dark.svg b/design/mini-badge-flat-dark.svg new file mode 100644 index 0000000..af4dede --- /dev/null +++ b/design/mini-badge-flat-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/mini-badge-flat-light.svg b/design/mini-badge-flat-light.svg new file mode 100644 index 0000000..6419c4d --- /dev/null +++ b/design/mini-badge-flat-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/readme-header-dark.png b/design/readme-header-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a7b7583c118226994c4dde832bc0743ead0afc6e GIT binary patch literal 29373 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +CLI.NET Core + diff --git a/design/readme-header-light.png b/design/readme-header-light.png new file mode 100644 index 0000000000000000000000000000000000000000..42854c91cce990669395062a111c8b30e193f119 GIT binary patch literal 26002 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 literal 0 HcmV?d00001 diff --git a/design/readme-header-light.svg b/design/readme-header-light.svg new file mode 100644 index 0000000..aa91f9d --- /dev/null +++ b/design/readme-header-light.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +CLI.NET Core + \ No newline at end of file diff --git a/design/wordmark-dark.svg b/design/wordmark-dark.svg new file mode 100644 index 0000000..1ebf4b0 --- /dev/null +++ b/design/wordmark-dark.svg @@ -0,0 +1,2 @@ + +CLI.NET Core \ No newline at end of file diff --git a/design/wordmark-light.svg b/design/wordmark-light.svg new file mode 100644 index 0000000..4bc97a3 --- /dev/null +++ b/design/wordmark-light.svg @@ -0,0 +1,2 @@ + +CLI.NET Core \ No newline at end of file diff --git a/design/wordmark.html b/design/wordmark.html new file mode 100644 index 0000000..6df6e07 --- /dev/null +++ b/design/wordmark.html @@ -0,0 +1,69 @@ + + + + + + CLI.NET Core — wordmark bar + + + + + + +
Fill-width bar — resize the window; the box grows, content stays put
+ +
+ + + + + CLI.NET Core +
+ +
+ + + + + CLI.NET Core +
+ + diff --git a/tests/linters/.cspell.json b/tests/linters/.cspell.json index b3aff57..9be670f 100644 --- a/tests/linters/.cspell.json +++ b/tests/linters/.cspell.json @@ -85,12 +85,15 @@ "codezombiech", "Containerfile", "docz", + "Inkscape", "markdowntable", "Neumann", "slnx", "Takumi", "vsicons", "wayou", + "wordmark", + "wordmarks", "yzhang", ], }