diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..727dfd7 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "0.38.5", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..35791e9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,38 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[.cs] + + +# This may not be needed, but kept for compatibility with VS +[*.{sln,csproj}] +end_of_line = crlf + +# Markdown files allows the use of trailing spaces to denote +# a line break +[*.md] +trim_trailing_whitespace = false + +# Batch and powershell files requires crlf to be +# used as the line ending. +# Powershell also requires UTF-8 with BOM encoding +# to function if utf8 characters is used (maibe batch files as well) +[*.{bat,ps1}] +charset = utf-8-bom +end_of_line = crlf + +# Shell scripts requires the use of lf line endings +# to be able to run. +[*.sh] +end_of_line = lf + +# The visual studio code file +# requires the use of crlf line endings +[tasks.json] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3dde06e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,21 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +*.cs text diff=csharp + +*.ps1 text eol=crlf +*.bat text eol=crlf +*.sln text eol=crlf +*.csproj text eol=crlf +tasks.json text eol=crlf + +*.sh text eol=lf + +*.md text whitespace=-trailing-space + +# Exclude files from exporting + +.gitattributes export-ignore +.gitignore export-ignore diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..be64973 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ "github>nils-a/renovate-config" ], + "packageRules": [ + { + "matchPackageNames": ["cake.tool", "Spectre.Console", "Microsoft.Extensions.DependencyInjection"], + "enabled": false + } + ] +} \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..84cc07e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,87 @@ +name: Build + +on: + push: + branches: + - main + - develop + - "feature/**" + - "release/**" + - "hotfix/**" + tags: + - "*" + paths-ignore: + - "README.md" + pull_request: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ windows-2019, ubuntu-18.04, macos-10.15 ] + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + GITHUB_PAT: ${{ secrets.GH_TOKEN }} + AZURE_USER: ${{ secrets.AZURE_USER }} + AZURE_PASSWORD: ${{ secrets.AZURE_PASSWORD }} + AZURE_SOURCE: ${{ secrets.AZURE_SOURCE }} + GPR_USER: ${{ secrets.GPR_USER }} + GPR_PASSWORD: ${{ secrets.GPR_PASSWORD }} + GPR_SOURCE: ${{ secrets.GPR_SOURCE }} + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + NUGET_SOURCE: "https://api.nuget.org/v3/index.json" + TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} + TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} + TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} + #WYAM_ACCESS_TOKEN: ${{ secrets.WYAM_ACCESS_TOKEN }} + #WYAM_DEPLOY_BRANCH: "gh-pages" + #WYAM_DEPLOY_REMOTE: ${{ github.event.repository.html_url }} + + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + - name: Fetch all tags and branches + run: git fetch --prune --unshallow + - uses: actions/setup-dotnet@v2.0.0 + with: + # codecov needs 2.1 + dotnet-version: '2.1.818' + - uses: actions/setup-dotnet@v2.0.0 + with: + dotnet-version: '3.1.x' + - uses: actions/setup-dotnet@v2.0.0 + with: + dotnet-version: '5.0.x' + - uses: actions/setup-dotnet@v2.0.0 + with: + dotnet-version: '6.0.x' + - name: Cache Tools + uses: actions/cache@v3 + with: + path: tools + key: ${{ runner.os }}-tools-${{ hashFiles('recipe.cake') }} + - name: Build project + uses: cake-build/cake-action@v1 + with: + script-path: recipe.cake + target: CI + verbosity: Diagnostic + cake-version: 0.38.5 + cake-bootstrap: true + - name: Upload Issues + uses: actions/upload-artifact@v3 + with: + if-no-files-found: warn + name: ${{ matrix.os }} Issues + path: | + BuildArtifacts/report.html + BuildArtifacts/**/coverlet/*.xml + - name: Upload Packages + uses: actions/upload-artifact@v3 + if: runner.os == 'Windows' + with: + if-no-files-found: warn + name: package + path: BuildArtifacts/Packages/**/* \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..5070718 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,76 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [develop] + pull_request: + # The branches below must be a subset of the branches above + branches: [develop] + schedule: + - cron: '0 15 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-18.04 + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['csharp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-dotnet@v2.0.0 + with: + # codecov needs 2.1 + dotnet-version: '2.1.818' + - uses: actions/setup-dotnet@v2.0.0 + with: + dotnet-version: '3.1.x' + - uses: actions/setup-dotnet@v2.0.0 + with: + dotnet-version: '5.0.x' + - uses: actions/setup-dotnet@v2.0.0 + with: + dotnet-version: '6.0.x' + + - name: Cache Tools + uses: actions/cache@v3 + with: + path: tools + key: ${{ runner.os }}-tools-${{ hashFiles('recipe.cake') }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Build project + uses: cake-build/cake-action@v1 + with: + script-path: recipe.cake + target: DotNetCore-Build + cake-version: 0.38.5 + cake-bootstrap: true + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1589fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Ignore everything in vscode folder +# except what we want users to +# contribute if there is any +# improvement +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +!.vscode/tasks.json + +# User-specific files in Visual Studio +*.suo +*.user +*.userosscache +*.sln.docstates +.vs/ + +# GhostDoc plugin settings file +*.GhostDoc.xml + +# Ignore cake tools directory +tools/* + +# Ignore build artifacts +BuildArtifacts/ +[Oo]bj/ +[Bb]in/ +[Tt]estresults/ + +# OS Specific files +*~ +.DS_Store +.AppleDouble +.LSOverride + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Windows shortcuts +*.lnk + +# Thumbnails +._* + +# Cake.Graph related +docs/input/tasks/* + +# Wyam related +config.wyam.* +/src/packages/ +.idea/ +.cake/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..592bce7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +nils@nils-andresen.de. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at +. diff --git a/GitReleaseManager.yaml b/GitReleaseManager.yaml new file mode 100644 index 0000000..0856835 --- /dev/null +++ b/GitReleaseManager.yaml @@ -0,0 +1,43 @@ +create: + include-footer: true + footer-heading: Where to get it + footer-content: > + You can download this release from + [nuget](https://nuget.org/packages/Spectre.Console.Registrars.Microsoft-Di/{milestone}). + footer-includes-milestone: true + milestone-replace-text: "{milestone}" + include-sha-section: true + sha-section-heading: "SHA256 Hashes of the release artifacts" + sha-section-line-format: "- `{1}\t{0}`" +export: + include-created-date-in-title: true + created-date-string-format: yyyy-MM-dd + perform-regex-removal: true + regex-text: '[\r\n]*### Where to get it[\r\n]*You can .*`\.[\r\n]*' + multiline-regex: true +issue-labels-include: + - Breaking change + - Bug + - Feature + - Enhancement + - Improvement + - Documentation + - security +issue-labels-exclude: + - Build + - Internal / Refactoring +issue-labels-alias: + - name: Documentation + header: Documentation + plural: Documentation + - name: security + header: Security + plural: Security +close: + use-issue-comments: true + issue-comment: |- + :tada: This issue has been resolved in version {milestone} :tada: + The release is available on: + - [GitHub Release](https://github.com/{owner}/{repository}/releases/tag/{milestone}) + - [NuGet Package](https://www.nuget.org/packages/Spectre.Console.Registrars.Microsoft-Di/{milestone}) + Your **[GitReleaseManager](https://github.com/GitTools/GitReleaseManager)** bot :package::rocket: \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..f6da9ff --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,3 @@ +branches: + master: + regex: ^main$ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c10fa1a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Nils Andresen + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ab3afc --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Spectre.Console.Registrars.Microsoft-DI + +[![standard-readme compliant][]][standard-readme] +[![Contributor Covenant][contrib-covenantimg]][contrib-covenant] +[![Build][githubimage]][githubbuild] +[![Codecov Report][codecovimage]][codecov] +[![NuGet package][nugetimage]][nuget] + +An implementation of an ITypeRegistrar for Spectre.Console using Microsoft.Extensions.DependencyInjection + +## Table of Contents + +- [Install](#install) +- [Usage](#usage) +- [Maintainer](#maintainer) +- [Contributing](#contributing) +- [License](#license) + +## Install + +```ps +Install-Package Spectre.Console.Registrars.Microsoft-Di +``` + +## Usage + +``` +var collection = new ServiceCollection(); +// setup your end of the ServiceCollection... +var registrar = new ServiceCollectionRegistrar(collection); +var app = new CommandApp(registrar); +``` + +## Maintainer + +[Nils Andresen @nils-a][maintainer] + +## Contributing + +Spectre.Console.Registrars.Microsoft-Di follows the [Contributor Covenant][contrib-covenant] Code of Conduct. + +We accept Pull Requests. + +Small note: If editing the Readme, please conform to the [standard-readme][] specification. + +## License + +[MIT License © Nils Andresen][license] + +[githubbuild]: https://github.com/nils-org/Spectre.Console.Registrars.Microsoft-Di/actions/workflows/build.yml?query=branch%3Adevelop +[githubimage]: https://github.com/nils-org/Spectre.Console.Registrars.Microsoft-Di/actions/workflows/build.yml/badge.svg?branch=develop +[codecov]: https://codecov.io/gh/nils-org/Spectre.Console.Registrars.Microsoft-Di +[codecovimage]: https://img.shields.io/codecov/c/github/nils-org/Spectre.Console.Registrars.Microsoft-Di.svg?logo=codecov&style=flat-square +[contrib-covenant]: https://www.contributor-covenant.org/version/2/0/code_of_conduct/ +[contrib-covenantimg]: https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg +[maintainer]: https://github.com/nils-a +[nuget]: https://nuget.org/packages/Spectre.Console.Registrars.Microsoft-Di +[nugetimage]: https://img.shields.io/nuget/v/Spectre.Console.Registrars.Microsoft-Di.svg?logo=nuget&style=flat-square +[license]: LICENSE.txt +[standard-readme]: https://github.com/RichardLitt/standard-readme +[standard-readme compliant]: https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square +[documentation]: https://nils-org.github.io/Spectre.Console.Registrars.Microsoft-Di/ +[api]: https://cakebuild.net/api/Cake.SevenZip/ \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..be8dfa8 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,14 @@ +$ErrorActionPreference = 'Stop' + +Set-Location -LiteralPath $PSScriptRoot + +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' +$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1' +$env:DOTNET_NOLOGO = '1' + +dotnet tool restore +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +dotnet cake recipe.cake --bootstrap +dotnet cake recipe.cake @args +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..9728521 --- /dev/null +++ b/build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euox pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" + +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_NOLOGO=1 + +dotnet tool restore + +dotnet cake recipe.cake --bootstrap +dotnet cake recipe.cake "$@" diff --git a/global.json b/global.json new file mode 100644 index 0000000..9ff1a3d --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "version": "6.0.300", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/recipe.cake b/recipe.cake new file mode 100644 index 0000000..a699e7c --- /dev/null +++ b/recipe.cake @@ -0,0 +1,57 @@ +#load nuget:?package=Cake.Recipe&version=2.2.1 + +var standardNotificationMessage = "Version {0} of {1} has just been released, it will be available here https://www.nuget.org/packages/{1}, once package indexing is complete."; + +Environment.SetVariableNames(); + +BuildParameters.SetParameters( + context: Context, + buildSystem: BuildSystem, + sourceDirectoryPath: "./src", + title: "Spectre.Console.Registrars.Microsoft-Di", + masterBranchName: "main", + repositoryOwner: "nils-org", + twitterMessage: standardNotificationMessage, + preferredBuildProviderType: BuildProviderType.GitHubActions, + shouldRunDotNetCorePack: true, + shouldUseDeterministicBuilds: true, + shouldRunDupFinder: false, // dupFinder is missing since 2021.3.0-eap + shouldRunInspectCode: false // we're shipping a custom version of it below + ); + +BuildParameters.PrintParameters(Context); + +// workaround for https://github.com/cake-contrib/Cake.Recipe/issues/862 +ToolSettings.SetToolPreprocessorDirectives( + reSharperTools: "#tool nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2022.2.0"); + +ToolSettings.SetToolSettings(context: Context); + +// additional workaround for https://github.com/cake-contrib/Cake.Recipe/issues/862 +// to suppress the --build/--no-build warning that is generated in the default +BuildParameters.Tasks.InspectCodeTask = Task("InspectCode2021") + .WithCriteria(() => BuildParameters.BuildAgentOperatingSystem == PlatformFamily.Windows, "Skipping due to not running on Windows") + .Does(data => RequireTool(ToolSettings.ReSharperTools, () => { + var inspectCodeLogFilePath = BuildParameters.Paths.Directories.InspectCodeTestResults.CombineWithFilePath("inspectcode.xml"); + + var settings = new InspectCodeSettings() { + SolutionWideAnalysis = true, + OutputFile = inspectCodeLogFilePath, + ArgumentCustomization = x => x.Append("--no-build") + }; + + if (FileExists(BuildParameters.SourceDirectoryPath.CombineWithFilePath(BuildParameters.ResharperSettingsFileName))) + { + settings.Profile = BuildParameters.SourceDirectoryPath.CombineWithFilePath(BuildParameters.ResharperSettingsFileName); + } + + InspectCode(BuildParameters.SolutionFilePath, settings); + + // Pass path to InspectCode log file to Cake.Issues.Recipe + IssuesParameters.InputFiles.InspectCodeLogFilePath = inspectCodeLogFilePath; + }) +); +BuildParameters.Tasks.AnalyzeTask.IsDependentOn("InspectCode2021"); +IssuesBuildTasks.ReadIssuesTask.IsDependentOn("InspectCode2021"); + +Build.RunDotNetCore(); diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..061b57d --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,236 @@ +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = true +dotnet_sort_system_directives_first = true +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:warning +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_property = false:warning + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:warning + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = flush_left +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.method_should_be_pascal_case.severity = warning +dotnet_naming_rule.method_should_be_pascal_case.symbols = method +dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.public_or_protected_field_should_be_pascal_case.severity = warning +dotnet_naming_rule.public_or_protected_field_should_be_pascal_case.symbols = public_or_protected_field +dotnet_naming_rule.public_or_protected_field_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.severity = warning +dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.symbols = private_or_internal_static_field +dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_field_should_be_camelcase.severity = warning +dotnet_naming_rule.private_or_internal_field_should_be_camelcase.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_camelcase.style = camelcase + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.method.applicable_kinds = method +dotnet_naming_symbols.method.applicable_accessibilities = public +dotnet_naming_symbols.method.required_modifiers = + +dotnet_naming_symbols.public_or_protected_field.applicable_kinds = field +dotnet_naming_symbols.public_or_protected_field.applicable_accessibilities = public, protected +dotnet_naming_symbols.public_or_protected_field.required_modifiers = + +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.required_modifiers = + +dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.capitalization = camel_case + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000..cc9cf53 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,27 @@ + + + + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) + + + + + + + + + + + <_LocalTopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''"/> + + + diff --git a/src/Spectre.Console.Registrars.Microsoft-Di.Tests/RegistrarFixture.cs b/src/Spectre.Console.Registrars.Microsoft-Di.Tests/RegistrarFixture.cs new file mode 100644 index 0000000..73b5a9f --- /dev/null +++ b/src/Spectre.Console.Registrars.Microsoft-Di.Tests/RegistrarFixture.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Extensions.DependencyInjection; + +using Spectre.Console.Cli; + +namespace Spectre.Console.Registrars.MicrosoftDi.Tests; + +internal class RegistrarFixture +{ + private readonly List> serviceCollectionActions = new List>(); + private readonly List> registrarActions = new List>(); + + + internal void GivenOnServiceCollection(Action action) + { + serviceCollectionActions.Add(action); + } + + internal void GivenOnRegistrar(Action action) + { + registrarActions.Add(action); + } + + internal ITypeResolver GetResolver() + { + var serviceCollection = new ServiceCollection(); + foreach (var action in serviceCollectionActions) + { + action(serviceCollection); + } + + var registrar = new ServiceCollectionRegistrar(serviceCollection); + foreach (var action in registrarActions) + { + action(registrar); + } + + return registrar.Build(); + } +} diff --git a/src/Spectre.Console.Registrars.Microsoft-Di.Tests/RegistrarTests.cs b/src/Spectre.Console.Registrars.Microsoft-Di.Tests/RegistrarTests.cs new file mode 100644 index 0000000..2a6ea02 --- /dev/null +++ b/src/Spectre.Console.Registrars.Microsoft-Di.Tests/RegistrarTests.cs @@ -0,0 +1,111 @@ +using System; + +using Microsoft.Extensions.DependencyInjection; + +using Shouldly; + +using Spectre.Console.Testing; + +using Xunit; + +namespace Spectre.Console.Registrars.MicrosoftDi.Tests; + +public class RegistrarTests +{ + [Fact] + public void Should_Pass_Base_Tests() + { + var baseTests = new TypeRegistrarBaseTests(() => + { + var container = new ServiceCollection(); + return new ServiceCollectionRegistrar(container); + }); + + baseTests.RunAllTests(); + } + + [Fact] + public void Resolver_Should_Return_Registration_From_Container() + { + var fixture = new RegistrarFixture(); + fixture.GivenOnServiceCollection(c => c.AddScoped()); + + var actual = fixture.GetResolver().Resolve(typeof(ISomeInterface)); + + actual.ShouldNotBeNull(); + actual.ShouldBeOfType(); + } + + [Fact] + public void Resolver_Should_Return_Registration_From_Registrar() + { + var fixture = new RegistrarFixture(); + fixture.GivenOnRegistrar(r => r.Register(typeof(ISomeInterface), typeof(SomeDependency))); + + var actual = fixture.GetResolver().Resolve(typeof(ISomeInterface)); + + actual.ShouldNotBeNull(); + actual.ShouldBeOfType(); + } + + [Fact] + public void Resolver_Should_Return_Instance_From_Registrar() + { + var fixture = new RegistrarFixture(); + var expected = new SomeDependency(); + fixture.GivenOnRegistrar(r => r.RegisterInstance(typeof(ISomeInterface), expected)); + + var actual = fixture.GetResolver().Resolve(typeof(ISomeInterface)); + + actual.ShouldNotBeNull(); + ReferenceEquals(expected, actual).ShouldBeTrue(); + } + + [Fact] + public void Resolver_Should_Return_Lazy_From_Registrar() + { + var fixture = new RegistrarFixture(); + var expected = new SomeDependency(); + fixture.GivenOnRegistrar(r => r.RegisterLazy(typeof(ISomeInterface), () => expected)); + + var actual = fixture.GetResolver().Resolve(typeof(ISomeInterface)); + + actual.ShouldNotBeNull(); + ReferenceEquals(expected, actual).ShouldBeTrue(); + } + + [Fact] + public void Resolver_Should_Throw_If_Lazy_Is_Null() + { + var fixture = new RegistrarFixture(); + fixture.GivenOnRegistrar(r => r.RegisterLazy(typeof(ISomeInterface), null!)); + + var action = () => fixture.GetResolver(); + + action.ShouldThrow(); + } + + [Fact] + public void Resolver_Should_Not_Call_Lazy_Factory_If_Not_Needed() + { + var fixture = new RegistrarFixture(); + var factoryCalled = false; + fixture.GivenOnRegistrar(r => r.RegisterLazy(typeof(ISomeInterface), () => + { + factoryCalled = true; + return new SomeDependency(); + })); + + fixture.GetResolver(); + + factoryCalled.ShouldBeFalse(); + } + + private interface ISomeInterface + { + } + + private class SomeDependency : ISomeInterface + { + } +} diff --git a/src/Spectre.Console.Registrars.Microsoft-Di.Tests/Spectre.Console.Registrars.Microsoft-Di.Tests.csproj b/src/Spectre.Console.Registrars.Microsoft-Di.Tests/Spectre.Console.Registrars.Microsoft-Di.Tests.csproj new file mode 100644 index 0000000..2076c59 --- /dev/null +++ b/src/Spectre.Console.Registrars.Microsoft-Di.Tests/Spectre.Console.Registrars.Microsoft-Di.Tests.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp3.1;net5.0;net6.0 + Spectre.Console.Registrars.MicrosoftDi.Tests + 10 + enable + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers + all + + + + + + + + diff --git a/src/Spectre.Console.Registrars.Microsoft-Di.sln b/src/Spectre.Console.Registrars.Microsoft-Di.sln new file mode 100644 index 0000000..70f46af --- /dev/null +++ b/src/Spectre.Console.Registrars.Microsoft-Di.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectre.Console.Registrars.Microsoft-Di", "Spectre.Console.Registrars.Microsoft-Di\Spectre.Console.Registrars.Microsoft-Di.csproj", "{3C1439A7-B1E2-4321-9878-68376D7048FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectre.Console.Registrars.Microsoft-Di.Tests", "Spectre.Console.Registrars.Microsoft-Di.Tests\Spectre.Console.Registrars.Microsoft-Di.Tests.csproj", "{A9545423-59A6-43C7-BC0B-A2F775E5AD0F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3C1439A7-B1E2-4321-9878-68376D7048FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C1439A7-B1E2-4321-9878-68376D7048FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C1439A7-B1E2-4321-9878-68376D7048FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C1439A7-B1E2-4321-9878-68376D7048FC}.Release|Any CPU.Build.0 = Release|Any CPU + {A9545423-59A6-43C7-BC0B-A2F775E5AD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9545423-59A6-43C7-BC0B-A2F775E5AD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9545423-59A6-43C7-BC0B-A2F775E5AD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9545423-59A6-43C7-BC0B-A2F775E5AD0F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Spectre.Console.Registrars.Microsoft-Di/ServiceCollectionRegistrar.cs b/src/Spectre.Console.Registrars.Microsoft-Di/ServiceCollectionRegistrar.cs new file mode 100644 index 0000000..57de7f7 --- /dev/null +++ b/src/Spectre.Console.Registrars.Microsoft-Di/ServiceCollectionRegistrar.cs @@ -0,0 +1,63 @@ +using System; + +using JetBrains.Annotations; + +using Microsoft.Extensions.DependencyInjection; + +using Spectre.Console.Cli; + +[UsedImplicitly] +public sealed class ServiceCollectionRegistrar : ITypeRegistrar +{ + private readonly IServiceCollection builder; + + public ServiceCollectionRegistrar(IServiceCollection builder) + { + this.builder = builder; + } + + public ITypeResolver Build() + { + return new TypeResolver(builder.BuildServiceProvider()); + } + + public void Register(Type service, Type implementation) + { + builder.AddSingleton(service, implementation); + } + + public void RegisterInstance(Type service, object implementation) + { + builder.AddSingleton(service, implementation); + } + + public void RegisterLazy(Type service, Func func) + { + if (func is null) + { + throw new ArgumentNullException(nameof(func)); + } + + builder.AddSingleton(service, _ => func()); + } + + private sealed class TypeResolver : ITypeResolver + { + private readonly IServiceProvider provider; + + public TypeResolver(IServiceProvider provider) + { + this.provider = provider ?? throw new ArgumentNullException(nameof(provider)); + } + + public object? Resolve(Type? type) + { + if (type == null) + { + return null; + } + + return provider.GetService(type); + } + } +} diff --git a/src/Spectre.Console.Registrars.Microsoft-Di/Spectre.Console.Registrars.Microsoft-Di.csproj b/src/Spectre.Console.Registrars.Microsoft-Di/Spectre.Console.Registrars.Microsoft-Di.csproj new file mode 100644 index 0000000..307a0bc --- /dev/null +++ b/src/Spectre.Console.Registrars.Microsoft-Di/Spectre.Console.Registrars.Microsoft-Di.csproj @@ -0,0 +1,43 @@ + + + + netstandard2.0;net5.0;net6.0 + Spectre.Console + 10 + enable + + + + + + + + + 2022 + $([System.DateTime]::Today.Year) + $(StartYear) + $(StartYear)-$(EndYear) + + + Nils Andresen + Copyright © $(FullYear) — Nils Andresen + An implementation of an ITypeRegistrar for Spectre.Console using Microsoft.Extensions.DependencyInjection + MIT + https://github.com/nils-org/Spectre.Console.Registrars.Microsoft-Di + spectre;commandline;console;dependency;injection;di;registrar + https://github.com/nils-org/Spectre.Console.Registrars.Microsoft-Di.git + https://github.com/nils-org/Spectre.Console.Registrars.Microsoft-Di/releases + 0.0.1 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/src/stylecop.json b/src/stylecop.json new file mode 100644 index 0000000..78e2de8 --- /dev/null +++ b/src/stylecop.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "indentation": { + "indentationSize": 4, + "tabSize": 4, + "useTabs": false + }, + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace", + "blankLinesBetweenUsingGroups": "allow", + "systemUsingDirectivesFirst": true + }, + "documentationRules": { + "xmlHeader": false + } + } +}