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/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..3b57168 --- /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@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@v6 + 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/.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..c1d11f1 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,25 @@ +{ + // List of extensions which should be recommended for users of this workspace + "recommendations": [ + // 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", + + // 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..ace795b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,112 @@ - { + // 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", + "dprint.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 + "**/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, + }, + + // 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, dprint.json", + "README.md": "CHANGELOG.md, CONTRIBUTORS.md, LICENSE, TODO.md", }, - "cSpell.words": [ - "Neumann", - "Xunit", - "paket", - "vvvcccaaa" - ] + + // 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, + + // 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" }, + ], + "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" }, + { "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..0d2440b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# 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. +- 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. +- 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 + +*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..8e789dc 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,32 @@ -# Command Line Parser +# CLI.NET Core -![Command Line Parser Logo](docs/images/Banner.png "Command Line Parser Logo") +![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) -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. +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. -## Using the Project +## Project Structure -You can always get the latest stable version from NuGet: +The repository is organized into a few top-level directories: -```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 -``` +- **`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 assets related to the project and the [design guide](design/DESIGN.md). -**__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).__** +## Development -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: +### Code Formatting & Linting -```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 -``` +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: -## Contributions +- **[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. -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. +## 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. + +## License + +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/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 0000000..a7b7583 Binary files /dev/null and b/design/readme-header-dark.png differ diff --git a/design/readme-header-dark.svg b/design/readme-header-dark.svg new file mode 100644 index 0000000..8cf6a6b --- /dev/null +++ b/design/readme-header-dark.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +CLI.NET Core + diff --git a/design/readme-header-light.png b/design/readme-header-light.png new file mode 100644 index 0000000..42854c9 Binary files /dev/null and b/design/readme-header-light.png differ 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/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 af3a778..0000000 Binary files a/docs/images/Banner.png and /dev/null differ diff --git a/docs/images/Logo.fw.png b/docs/images/Logo.fw.png deleted file mode 100644 index 7e94c9d..0000000 Binary files a/docs/images/Logo.fw.png and /dev/null differ diff --git a/docs/images/Logo.ico b/docs/images/Logo.ico deleted file mode 100644 index feaab61..0000000 Binary files a/docs/images/Logo.ico and /dev/null differ diff --git a/docs/images/Logo1024x1024.png b/docs/images/Logo1024x1024.png deleted file mode 100644 index fbabed5..0000000 Binary files a/docs/images/Logo1024x1024.png and /dev/null differ diff --git a/docs/images/Logo128x128.png b/docs/images/Logo128x128.png deleted file mode 100644 index c897295..0000000 Binary files a/docs/images/Logo128x128.png and /dev/null differ diff --git a/docs/images/Logo256x256.png b/docs/images/Logo256x256.png deleted file mode 100644 index e3f5b33..0000000 Binary files a/docs/images/Logo256x256.png and /dev/null differ diff --git a/docs/images/Logo32x32.png b/docs/images/Logo32x32.png deleted file mode 100644 index 59a6221..0000000 Binary files a/docs/images/Logo32x32.png and /dev/null differ diff --git a/docs/images/Logo512x512.png b/docs/images/Logo512x512.png deleted file mode 100644 index b77c809..0000000 Binary files a/docs/images/Logo512x512.png and /dev/null differ diff --git a/docs/images/Logo64x64.png b/docs/images/Logo64x64.png deleted file mode 100644 index 1e6fe26..0000000 Binary files a/docs/images/Logo64x64.png and /dev/null differ 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/source/Arguments/Argument.cs b/source/Arguments/Argument.cs deleted file mode 100644 index 051bba4..0000000 --- a/source/Arguments/Argument.cs +++ /dev/null @@ -1,47 +0,0 @@ - -namespace System.CommandLine.Arguments -{ - /// - /// 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/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/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/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/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/.cspell.json b/tests/linters/.cspell.json new file mode 100644 index 0000000..9be670f --- /dev/null +++ b/tests/linters/.cspell.json @@ -0,0 +1,99 @@ +{ + // 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", + "clinet", + "codezombiech", + "Containerfile", + "docz", + "Inkscape", + "markdowntable", + "Neumann", + "slnx", + "Takumi", + "vsicons", + "wayou", + "wordmark", + "wordmarks", + "yzhang", + ], +} 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 diff --git a/tests/unit-tests/.gitkeep b/tests/unit-tests/.gitkeep new file mode 100644 index 0000000..e69de29