diff --git a/.editorconfig b/.editorconfig index f3f646b..8ca637d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,11 @@ +# DO NOT EDIT: root convention root = true [*] charset = utf-8 end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true trim_trailing_whitespace = true +# END DO NOT EDIT [*.json] resharper_comment_typo_highlighting = none @@ -18,7 +17,46 @@ insert_final_newline = false [*.cs] indent_size = 4 +csharp_style_pattern_local_over_anonymous_function = true : suggestion +csharp_style_unused_value_assignment_preference = discard_variable : suggestion + +# DO NOT EDIT: json convention +[*.json] +indent_size = 2 +indent_style = space +# END DO NOT EDIT + +# DO NOT EDIT: md convention +[*.md] +indent_size = 2 +indent_style = space +insert_final_newline = true +# END DO NOT EDIT + +# DO NOT EDIT: ps1 convention +[*.ps1] +indent_size = tab indent_style = tab +insert_final_newline = true +# END DO NOT EDIT + +# DO NOT EDIT: yaml convention +[*.{yml,yaml}] +indent_size = 2 +indent_style = space +# END DO NOT EDIT + +# DO NOT EDIT: csharp convention +# generated from https://github.com/Faithlife/CodingGuidelines/blob/master/sections/csharp/editorconfig.md +[*.{csproj,props,slnx,targets}] +indent_size = 2 +indent_style = space + +[*.{cs,cshtml,razor}] +indent_size = tab +indent_style = tab +tab_width = 4 +insert_final_newline = true csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true @@ -71,22 +109,31 @@ csharp_style_expression_bodied_local_functions = true : suggestion csharp_style_expression_bodied_methods = true : suggestion csharp_style_expression_bodied_operators = true : suggestion csharp_style_expression_bodied_properties = true : suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true : suggestion csharp_style_inlined_variable_declaration = true : suggestion csharp_style_namespace_declarations = file_scoped : suggestion -csharp_style_pattern_local_over_anonymous_function = true : suggestion 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_extended_property_pattern = true : suggestion csharp_style_prefer_index_operator = true : suggestion +csharp_style_prefer_local_over_anonymous_function = true : suggestion +csharp_style_prefer_not_pattern = true : suggestion +csharp_style_prefer_null_check_over_type_check = true : suggestion +csharp_style_prefer_pattern_matching = true : suggestion +csharp_style_prefer_primary_constructors = true : suggestion csharp_style_prefer_range_operator = true : suggestion csharp_style_prefer_switch_expression = true : suggestion +csharp_style_prefer_tuple_swap = true : warning csharp_style_throw_expression = true : suggestion -csharp_style_unused_value_assignment_preference = discard_variable : suggestion +csharp_style_unused_value_assignment_preference = discard_variable : warning csharp_style_unused_value_expression_statement_preference = discard_variable : none csharp_style_var_elsewhere = true : suggestion csharp_style_var_for_built_in_types = true : suggestion csharp_style_var_when_type_is_apparent = true : suggestion csharp_using_directive_placement = outside_namespace : warning +dotnet_analyzer_diagnostic.severity = warning dotnet_code_quality_unused_parameters = all : suggestion +dotnet_diagnostic.CA1014.severity = none dotnet_diagnostic.CA1030.severity = none dotnet_diagnostic.CA1031.severity = suggestion dotnet_diagnostic.CA1032.severity = suggestion @@ -97,6 +144,8 @@ dotnet_diagnostic.CA1062.severity = suggestion dotnet_diagnostic.CA1063.severity = none dotnet_diagnostic.CA1303.severity = none dotnet_diagnostic.CA1308.severity = suggestion +dotnet_diagnostic.CA1309.severity = suggestion +dotnet_diagnostic.CA1508.severity = suggestion dotnet_diagnostic.CA1707.severity = none dotnet_diagnostic.CA1716.severity = none dotnet_diagnostic.CA1720.severity = suggestion @@ -106,10 +155,100 @@ dotnet_diagnostic.CA1816.severity = none dotnet_diagnostic.CA1819.severity = suggestion dotnet_diagnostic.CA1822.severity = suggestion dotnet_diagnostic.CA1826.severity = suggestion +dotnet_diagnostic.CA1848.severity = suggestion +dotnet_diagnostic.CA1861.severity = suggestion +dotnet_diagnostic.CA1873.severity = suggestion dotnet_diagnostic.CA2000.severity = none dotnet_diagnostic.CA2227.severity = none dotnet_diagnostic.CA2234.severity = none dotnet_diagnostic.CA2237.severity = none +dotnet_diagnostic.CA2254.severity = none +dotnet_diagnostic.CA5351.severity = none +dotnet_diagnostic.IDE0001.severity = warning +dotnet_diagnostic.IDE0002.severity = warning +dotnet_diagnostic.IDE0003.severity = warning +dotnet_diagnostic.IDE0004.severity = warning +dotnet_diagnostic.IDE0005.severity = warning +dotnet_diagnostic.IDE0007.severity = suggestion +dotnet_diagnostic.IDE0008.severity = suggestion +dotnet_diagnostic.IDE0009.severity = warning +dotnet_diagnostic.IDE0010.severity = suggestion +dotnet_diagnostic.IDE0011.severity = suggestion +dotnet_diagnostic.IDE0016.severity = suggestion +dotnet_diagnostic.IDE0017.severity = suggestion +dotnet_diagnostic.IDE0018.severity = suggestion +dotnet_diagnostic.IDE0019.severity = suggestion +dotnet_diagnostic.IDE0020.severity = suggestion +dotnet_diagnostic.IDE0021.severity = suggestion +dotnet_diagnostic.IDE0022.severity = suggestion +dotnet_diagnostic.IDE0023.severity = suggestion +dotnet_diagnostic.IDE0024.severity = suggestion +dotnet_diagnostic.IDE0025.severity = suggestion +dotnet_diagnostic.IDE0026.severity = suggestion +dotnet_diagnostic.IDE0027.severity = suggestion +dotnet_diagnostic.IDE0028.severity = suggestion +dotnet_diagnostic.IDE0029.severity = warning +dotnet_diagnostic.IDE0030.severity = warning +dotnet_diagnostic.IDE0031.severity = suggestion +dotnet_diagnostic.IDE0032.severity = suggestion +dotnet_diagnostic.IDE0033.severity = warning +dotnet_diagnostic.IDE0034.severity = suggestion +dotnet_diagnostic.IDE0035.severity = warning +dotnet_diagnostic.IDE0036.severity = warning +dotnet_diagnostic.IDE0037.severity = suggestion +dotnet_diagnostic.IDE0038.severity = suggestion +dotnet_diagnostic.IDE0039.severity = suggestion +dotnet_diagnostic.IDE0040.severity = warning +dotnet_diagnostic.IDE0041.severity = suggestion +dotnet_diagnostic.IDE0042.severity = suggestion +dotnet_diagnostic.IDE0044.severity = warning +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_diagnostic.IDE0046.severity = none +dotnet_diagnostic.IDE0047.severity = suggestion +dotnet_diagnostic.IDE0048.severity = suggestion +dotnet_diagnostic.IDE0049.severity = warning +dotnet_diagnostic.IDE0051.severity = warning +dotnet_diagnostic.IDE0052.severity = warning +dotnet_diagnostic.IDE0053.severity = suggestion +dotnet_diagnostic.IDE0054.severity = suggestion +dotnet_diagnostic.IDE0056.severity = suggestion +dotnet_diagnostic.IDE0057.severity = suggestion +dotnet_diagnostic.IDE0058.severity = none +dotnet_diagnostic.IDE0059.severity = warning +dotnet_diagnostic.IDE0060.severity = suggestion +dotnet_diagnostic.IDE0061.severity = suggestion +dotnet_diagnostic.IDE0062.severity = suggestion +dotnet_diagnostic.IDE0063.severity = suggestion +dotnet_diagnostic.IDE0065.severity = warning +dotnet_diagnostic.IDE0066.severity = suggestion +dotnet_diagnostic.IDE0070.severity = suggestion +dotnet_diagnostic.IDE0071.severity = suggestion +dotnet_diagnostic.IDE0072.severity = suggestion +dotnet_diagnostic.IDE0074.severity = suggestion +dotnet_diagnostic.IDE0075.severity = warning +dotnet_diagnostic.IDE0078.severity = suggestion +dotnet_diagnostic.IDE0080.severity = suggestion +dotnet_diagnostic.IDE0082.severity = warning +dotnet_diagnostic.IDE0083.severity = suggestion +dotnet_diagnostic.IDE0090.severity = suggestion +dotnet_diagnostic.IDE0100.severity = warning +dotnet_diagnostic.IDE0110.severity = warning +dotnet_diagnostic.IDE0130.severity = warning +dotnet_diagnostic.IDE0150.severity = suggestion +dotnet_diagnostic.IDE0160.severity = suggestion +dotnet_diagnostic.IDE0161.severity = suggestion +dotnet_diagnostic.IDE0170.severity = suggestion +dotnet_diagnostic.IDE0180.severity = warning +dotnet_diagnostic.IDE0290.severity = suggestion +dotnet_diagnostic.IDE0300.severity = suggestion +dotnet_diagnostic.IDE0301.severity = suggestion +dotnet_diagnostic.IDE0305.severity = suggestion +dotnet_diagnostic.IDE0306.severity = suggestion +dotnet_diagnostic.IDE0370.severity = suggestion +dotnet_diagnostic.IDE0390.severity = suggestion +dotnet_diagnostic.IDE0391.severity = suggestion +dotnet_diagnostic.IDE1005.severity = suggestion +dotnet_diagnostic.NUnit2045.severity = suggestion dotnet_diagnostic.SA0001.severity = none dotnet_diagnostic.SA1003.severity = none dotnet_diagnostic.SA1008.severity = none @@ -157,6 +296,8 @@ dotnet_diagnostic.SA1633.severity = none dotnet_diagnostic.SA1642.severity = none dotnet_diagnostic.SA1643.severity = none dotnet_diagnostic.SX1101.severity = warning +dotnet_diagnostic.SYSLIB1045.severity = suggestion +dotnet_diagnostic.SYSLIB1054.severity = none dotnet_naming_rule.local_functions_rule.severity = warning dotnet_naming_rule.local_functions_rule.style = upper_camel_case_style dotnet_naming_rule.local_functions_rule.symbols = local_functions_symbols @@ -197,6 +338,7 @@ dotnet_sort_system_directives_first = true dotnet_style_coalesce_expression = true : warning dotnet_style_collection_initializer = true : suggestion dotnet_style_explicit_tuple_names = true : warning +dotnet_style_namespace_match_folder = true : warning dotnet_style_null_propagation = true : suggestion dotnet_style_object_initializer = true : suggestion dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity : none @@ -212,6 +354,8 @@ dotnet_style_prefer_conditional_expression_over_return = true : none 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 : warning +dotnet_style_prefer_simplified_interpolation = true : suggestion dotnet_style_qualification_for_event = false : warning dotnet_style_qualification_for_field = false : warning dotnet_style_qualification_for_method = false : warning @@ -227,6 +371,7 @@ resharper_arrange_missing_parentheses_highlighting = hint resharper_arrange_trailing_comma_in_multiline_lists_highlighting = warning resharper_comment_typo_highlighting = none resharper_compare_of_floats_by_equality_operator_highlighting = suggestion +resharper_convert_to_primary_constructor_highlighting = hint resharper_csharp_align_first_arg_by_paren = false resharper_csharp_align_linq_query = false resharper_csharp_align_multiline_argument = false @@ -248,6 +393,7 @@ resharper_csharp_indent_anonymous_method_block = false resharper_csharp_indent_nested_for_stmt = true resharper_csharp_indent_nested_foreach_stmt = true resharper_csharp_indent_nested_while_stmt = true +resharper_csharp_indent_raw_literal_string = indent resharper_csharp_int_align = false resharper_csharp_keep_existing_arrangement = true resharper_csharp_nested_ternary_style = simple_wrap @@ -270,3 +416,4 @@ resharper_unused_auto_property_accessor_global_highlighting = none resharper_unused_auto_property_accessor_local_highlighting = suggestion resharper_unused_member_global_highlighting = none resharper_unused_member_local_highlighting = suggestion +# END DO NOT EDIT diff --git a/.gitattributes b/.gitattributes index 4092f8f..205ec87 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ * text=auto eol=lf +# DO NOT EDIT: csharp convention *.cs text diff=csharp -*.csproj text merge=union -*.sln text merge=union +# END DO NOT EDIT diff --git a/.github/conventions.yml b/.github/conventions.yml new file mode 100644 index 0000000..f50d402 --- /dev/null +++ b/.github/conventions.yml @@ -0,0 +1,11 @@ +conventions: + - path: Faithlife/CodingGuidelines/conventions/dotnet-common + - path: Faithlife/CodingGuidelines/conventions/nuget-config + - path: Faithlife/CodingGuidelines/conventions/gitignore-common + - path: Faithlife/CodingGuidelines/conventions/gitignore-dotnet + - path: Faithlife/CodingGuidelines/conventions/gitignore-ide + - path: Faithlife/CodingGuidelines/conventions/dotnet-common-contributing + - path: Faithlife/CodingGuidelines/conventions/dotnet-common-props + - path: Faithlife/CodingGuidelines/conventions/license-mit + settings: + copyright-holder: Ed Ball \ No newline at end of file diff --git a/.github/lsp.json b/.github/lsp.json new file mode 100644 index 0000000..ff432d6 --- /dev/null +++ b/.github/lsp.json @@ -0,0 +1,19 @@ +{ + "lspServers": { + "csharp": { + "command": "dnx", + "args": [ + "--yes", + "--prerelease", + "roslyn-language-server", + "--", + "--stdio", + "--autoLoadProjects" + ], + "fileExtensions": { + ".cs": "csharp", + ".cshtml": "csharp" + } + } + } +} diff --git a/.github/workflows/apply-repo-conventions.yml b/.github/workflows/apply-repo-conventions.yml new file mode 100644 index 0000000..f2e9f66 --- /dev/null +++ b/.github/workflows/apply-repo-conventions.yml @@ -0,0 +1,40 @@ +name: Apply Repository Conventions + +on: + schedule: + - cron: '31 9 * * 1-5' + workflow_dispatch: + +permissions: {} + +jobs: + apply: + name: Apply + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + token: ${{ secrets.BOT_GITHUB_TOKEN }} + # full history needed for git merge-base in repo-conventions tool + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "ejball-bot" + git config user.email "ejball-bot@users.noreply.github.com" + + - name: Install .NET + uses: actions/setup-dotnet@v5 + + - name: Install APM + uses: microsoft/apm-action@v1 + with: + setup-only: true + + - name: Apply repository conventions + env: + GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} + run: | + dnx -y repo-conventions apply --open-pr \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index ee72496..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: Build -on: - workflow_dispatch: - push: - paths-ignore: - - '*.md' - - 'docs/**' - branches: - - 'master' - tags-ignore: - - '**' - pull_request: - paths-ignore: - - '*.md' - - 'docs/**' -env: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: 1 -defaults: - run: - shell: pwsh -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - steps: - - name: Check out code - uses: actions/checkout@v1 - - name: Set up .NET 5 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 5.0.x - - name: Set up .NET 6 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 6.0.x - - name: Restore - run: .\build.ps1 restore - - name: Build - run: .\build.ps1 build --skip restore - - name: Test - run: .\build.ps1 test --skip build - - name: Publish - if: runner.os == 'Windows' && github.repository_owner == 'ejball' && github.ref == 'refs/heads/master' - env: - BUILD_BOT_PASSWORD: ${{ secrets.BUILD_BOT_PASSWORD }} - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - run: .\build.ps1 publish --skip test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7b3458c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,82 @@ +name: Continuous Integration + +on: + push: + branches: [master] + tags-ignore: ['**'] + pull_request: + workflow_dispatch: + +env: + DOTNET_NOLOGO: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + +defaults: + run: + shell: pwsh + +permissions: + contents: write + +jobs: + build-matrix: + name: Build ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - name: Check out code + uses: actions/checkout@v6 + - name: Install .NET + uses: actions/setup-dotnet@v5 + - name: Restore + run: ./build.ps1 restore + - name: Build + run: ./build.ps1 build --skip restore + - name: Test + run: ./build.ps1 test --skip build + - name: Package + run: ./build.ps1 package --skip test + - name: Upload packages + if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v7 + with: + name: nuget-packages + path: release/*.nupkg + if-no-files-found: error + + build: + name: Build + runs-on: ubuntu-latest + needs: build-matrix + if: ${{ always() }} + steps: + - name: Build failed + if: ${{ needs.build-matrix.result != 'success' }} + run: Write-Host "Build failed."; exit 1 + - name: Build succeeded + if: ${{ needs.build-matrix.result == 'success' }} + run: Write-Host "Build succeeded." + + publish: + name: Publish + runs-on: windows-latest + needs: build + if: github.repository_owner == 'ejball' && github.ref == 'refs/heads/master' + steps: + - name: Check out code + uses: actions/checkout@v6 + - name: Install .NET + uses: actions/setup-dotnet@v5 + - name: Download packages + uses: actions/download-artifact@v8 + with: + name: nuget-packages + path: release + - name: Publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: ./build.ps1 publish --skip package --trigger publish-nuget-output \ No newline at end of file diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000..5fb318f --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,33 @@ +name: Copilot Setup Steps + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +permissions: + contents: read + +env: + DOTNET_NOLOGO: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + +defaults: + run: + shell: pwsh + +jobs: + copilot-setup-steps: + name: Copilot Setup Steps + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v6 + - name: Install .NET + uses: actions/setup-dotnet@v5 + - name: Restore + run: ./build.ps1 restore \ No newline at end of file diff --git a/.gitignore b/.gitignore index e3008f1..7a6120d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,25 @@ +*.ncrunchproject +*.ncrunchsolution +nCrunchTemp* + +# DO NOT EDIT: gitignore-common convention +.DS_Store +Thumbs.db +*.log +# END DO NOT EDIT + +# DO NOT EDIT: gitignore-dotnet convention +artifacts/ bin/ obj/ release/ +# END DO NOT EDIT + +# DO NOT EDIT: gitignore-ide convention .vs/ .idea/ -Thumbs.db *.cache *.user *.userprefs -*.ncrunchproject -*.ncrunchsolution -nCrunchTemp* _ReSharper* -.DS_Store +# END DO NOT EDIT diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ab1646..2bf2bb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,19 @@ + + # Contributing ## Publishing -* To publish the library, update the `` in [`Directory.Build.props`](Directory.Build.props), add a corresponding section to the top of [`ReleaseNotes.md`](ReleaseNotes.md), commit, and push. +To publish an official release: + +* Update the `` in [`Directory.Build.props`](Directory.Build.props). +* Add a corresponding section to the top of [`ReleaseNotes.md`](ReleaseNotes.md). +* Push or create a PR for review. + +### Prereleases + +Certain changes can be hard to unit test and are better tested in a real consumer project. In this case, you can publish a beta version of the library for testing. -## Template +To publish a beta, add a `` below `` in [`Directory.Build.props`](Directory.Build.props), e.g., `beta.1`. Publish as above. -* This repository uses the [`faithlife-build`](https://github.com/Faithlife/CSharpTemplate/tree/faithlife-build) template of [`Faithlife/CSharpTemplate`](https://github.com/Faithlife/CSharpTemplate). +When beta testing is done, delete the `` and publish again. diff --git a/Directory.Build.props b/Directory.Build.props index fba8e6b..f2cb310 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,37 +2,42 @@ 1.2.2 - 10.0 + 1.2.2 + $(NoWarn);1591;1998;NU1507;NU5105;CA1014;CA1508 + ejball + RegexMatchValues + MIT + Ed Ball + + + + + 14.0 enable enable true - $(NoWarn);1591;1998;NU5105;CA1014;CA1508 + en-US embedded - ejball - RegexMatchValues - MIT - https://ejball.com/$(RepositoryName) - https://github.com/$(GitHubOrganization)/$(RepositoryName)/blob/master/ReleaseNotes.md + https://github.com/$(GitHubOrganization)/$(RepositoryName) + https://github.com/$(GitHubOrganization)/$(RepositoryName)/blob/master/ReleaseNotes.md https://github.com/$(GitHubOrganization)/$(RepositoryName).git - Ed Ball + $(GitHubOrganization) Copyright $(Authors) - true - true true - AllEnabledByDefault + latest-all + true + true false false + false + true + true + true + true + true + all + low - - - $(VersionPrefix).$(BuildNumber) - true - - - - - - - + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..b6b91e8 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,23 @@ + + + + true + true + true + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE index 2fd22c4..f1491c9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright 2022 Ed Ball +Copyright 2026 Ed Ball Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index af549a7..83a14ad 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,40 @@ # RegexMatchValues -Converts regular expression matches to strong types. +**RegexMatchValues** converts regular expression matches to strong types. -[![Build](https://github.com/ejball/RegexMatchValues/workflows/Build/badge.svg)](https://github.com/ejball/RegexMatchValues/actions?query=workflow%3ABuild) [![NuGet](https://img.shields.io/nuget/v/RegexMatchValues.svg)](https://www.nuget.org/packages/RegexMatchValues) +[![NuGet](https://img.shields.io/nuget/v/RegexMatchValues.svg)](https://www.nuget.org/packages/RegexMatchValues) -[Documentation](https://ejball.com/RegexMatchValues/) | [Release Notes](ReleaseNotes.md) | [Contributing](CONTRIBUTING.md) +## Usage + +The [RegexMatchExtensions](./src/RegexMatchValues/RegexMatchExtensions.cs) static class provides a few simple extension methods on [`Match`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.match) that convert the capturing groups of the regular expression into a tuple. [(Try it!)](https://dotnetfiddle.net/e7dSIl) + +```csharp +var text = "Use 22/7 for pi."; +var (numerator, denominator) = + Regex.Match(text, @"(\d+)/(\d+)").Get<(int, int)>(); +Console.WriteLine((double) numerator / denominator); +// output: 3.142857142857143 +``` + +[`Get()`](./src/RegexMatchValues/RegexMatchExtensions.cs) throws `InvalidOperationException` if the match fails, so use one of the [`TryGet()`](./src/RegexMatchValues/RegexMatchExtensions.cs) overloads if that is a possibility. [(Try it!)](https://dotnetfiddle.net/t2vtM0) + +```csharp +var text = "Use 3.14 for pi."; +if (Regex.Match(text, @"(\d+)/(\d+)").TryGet(out (int N, int D) fraction)) + Console.WriteLine((double) fraction.N / fraction.D); +else + Console.WriteLine("No fraction found."); +// output: No fraction found. +``` + +To use named capturing groups, specify each group name in the order they should appear in the tuple. [(Try it!)](https://dotnetfiddle.net/78heXi) + +```csharp +var text = "Use 22/7 for pi."; +var (denominator, numerator) = + Regex.Match(text, @"(?\d+)/(?\d+)").Get<(int, int)>("de", "nu"); +Console.WriteLine((double) numerator / denominator); +// output: 3.142857142857143 +``` + +Other types besides tuples are also supported. See the [remarks](./src/RegexMatchValues/RegexMatchExtensions.cs) in the documentation for the full details. diff --git a/RegexMatchValues.sln b/RegexMatchValues.sln deleted file mode 100644 index d8d3d6a..0000000 --- a/RegexMatchValues.sln +++ /dev/null @@ -1,58 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegexMatchValues", "src\RegexMatchValues\RegexMatchValues.csproj", "{E1BF8BA1-9E9D-4E27-83FC-777B8D14F806}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegexMatchValues.Tests", "tests\RegexMatchValues.Tests\RegexMatchValues.Tests.csproj", "{DF559AD1-5287-43B1-AB47-61B6F342B0FE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{028A6C6D-8F8F-4A91-A063-45FFCF892109}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitattributes = .gitattributes - .gitignore = .gitignore - build.ps1 = build.ps1 - .github\workflows\build.yaml = .github\workflows\build.yaml - Directory.Build.props = Directory.Build.props - dotnet-tools.json = dotnet-tools.json - LICENSE = LICENSE - nuget.config = nuget.config - README.md = README.md - ReleaseNotes.md = ReleaseNotes.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Build", "tools\Build\Build.csproj", "{CBD807B4-F49F-4DD1-BC9A-359E90EA913F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XmlDocGen", "tools\XmlDocGen\XmlDocGen.csproj", "{EC2B2484-B209-41A2-94E5-4203A9740983}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E1BF8BA1-9E9D-4E27-83FC-777B8D14F806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E1BF8BA1-9E9D-4E27-83FC-777B8D14F806}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E1BF8BA1-9E9D-4E27-83FC-777B8D14F806}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E1BF8BA1-9E9D-4E27-83FC-777B8D14F806}.Release|Any CPU.Build.0 = Release|Any CPU - {DF559AD1-5287-43B1-AB47-61B6F342B0FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF559AD1-5287-43B1-AB47-61B6F342B0FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF559AD1-5287-43B1-AB47-61B6F342B0FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF559AD1-5287-43B1-AB47-61B6F342B0FE}.Release|Any CPU.Build.0 = Release|Any CPU - {CBD807B4-F49F-4DD1-BC9A-359E90EA913F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CBD807B4-F49F-4DD1-BC9A-359E90EA913F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CBD807B4-F49F-4DD1-BC9A-359E90EA913F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CBD807B4-F49F-4DD1-BC9A-359E90EA913F}.Release|Any CPU.Build.0 = Release|Any CPU - {EC2B2484-B209-41A2-94E5-4203A9740983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EC2B2484-B209-41A2-94E5-4203A9740983}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EC2B2484-B209-41A2-94E5-4203A9740983}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EC2B2484-B209-41A2-94E5-4203A9740983}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {6D621D28-8AB2-41C2-AA99-AE2225542E17} - EndGlobalSection -EndGlobal diff --git a/RegexMatchValues.slnx b/RegexMatchValues.slnx new file mode 100644 index 0000000..564bec8 --- /dev/null +++ b/RegexMatchValues.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/RegexMatchValues.sln.DotSettings b/RegexMatchValues.slnx.DotSettings similarity index 100% rename from RegexMatchValues.sln.DotSettings rename to RegexMatchValues.slnx.DotSettings diff --git a/build.ps1 b/build.ps1 index 21a0bb6..b11b95c 100755 --- a/build.ps1 +++ b/build.ps1 @@ -1,17 +1,13 @@ #!/usr/bin/env pwsh +# DO NOT EDIT: generated by https://github.com/Faithlife/CodingGuidelines/tree/master/conventions/build-script $ErrorActionPreference = 'Stop' Push-Location $PSScriptRoot try { - if (-not (Test-Path ./tools/bin/Build) -or - (Get-ChildItem ./tools/Build/* | Measure-Object LastWriteTime -Maximum).Maximum -gt - (Get-ChildItem ./tools/bin/Build/* | Measure-Object LastWriteTime -Maximum).Maximum) { - if (Test-Path ./tools/bin/Build) { Remove-Item ./tools/bin/Build -Recurse } - dotnet publish ./tools/Build/Build.csproj --output ./tools/bin/Build --nologo --verbosity quiet - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - } - dotnet ./tools/bin/Build/Build.dll $args - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + dotnet publish ./tools/Build/Build.csproj --artifacts-path ./artifacts --nologo --verbosity quiet -tl:off + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + dotnet ./artifacts/publish/Build/release/Build.dll $args + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } finally { - Pop-Location + Pop-Location } diff --git a/global.json b/global.json new file mode 100644 index 0000000..512142d --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestFeature" + } +} diff --git a/nuget.config b/nuget.config index 325a275..da2f86d 100644 --- a/nuget.config +++ b/nuget.config @@ -1,5 +1,6 @@ + - + diff --git a/src/RegexMatchValues/NullableAttributes.cs b/src/RegexMatchValues/NullableAttributes.cs index a4aca6a..0bb74c1 100644 --- a/src/RegexMatchValues/NullableAttributes.cs +++ b/src/RegexMatchValues/NullableAttributes.cs @@ -1,4 +1,5 @@ #if NETSTANDARD2_0 +#pragma warning disable IDE0130 // Namespace does not match folder structure #pragma warning disable SA1402 // File may only contain a single type #pragma warning disable SA1649 // File name should match first type name diff --git a/src/RegexMatchValues/RegexMatchExtensions.cs b/src/RegexMatchValues/RegexMatchExtensions.cs index 48ae34f..9200f82 100644 --- a/src/RegexMatchValues/RegexMatchExtensions.cs +++ b/src/RegexMatchValues/RegexMatchExtensions.cs @@ -97,8 +97,12 @@ public static class RegexMatchExtensions /// The specified type is not supported. public static bool TryGet(this Match match, string[] groupNames, [MaybeNullWhen(returnValue: false)] out T value) { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(match); +#else if (match is null) throw new ArgumentNullException(nameof(match)); +#endif if (!match.Success) { @@ -167,7 +171,7 @@ public static bool TryGet(this Match match, string[] groupNames, [MaybeNullWh return ConvertCapture(group, type); } - private static object ConvertCaptures(CaptureCollection captures, Type type) + private static Array ConvertCaptures(CaptureCollection captures, Type type) { var count = captures.Count; var array = Array.CreateInstance(type, count); diff --git a/src/RegexMatchValues/RegexMatchValues.csproj b/src/RegexMatchValues/RegexMatchValues.csproj index d1f15a2..bb015a5 100644 --- a/src/RegexMatchValues/RegexMatchValues.csproj +++ b/src/RegexMatchValues/RegexMatchValues.csproj @@ -1,15 +1,22 @@ - net6.0;netstandard2.1;netstandard2.0 + net10.0;netstandard2.1;netstandard2.0 Converts regular expression matches to strong types. regex regular expression match group capture convert true + true + README.md + true true - + + + + + diff --git a/tests/RegexMatchValues.Tests/RegexMatchExtensionsTests.cs b/tests/RegexMatchValues.Tests/RegexMatchExtensionsTests.cs index 3f76739..1c230d7 100644 --- a/tests/RegexMatchValues.Tests/RegexMatchExtensionsTests.cs +++ b/tests/RegexMatchValues.Tests/RegexMatchExtensionsTests.cs @@ -1,12 +1,10 @@ using System.Text.RegularExpressions; -using FluentAssertions; -using NUnit.Framework; using static FluentAssertions.FluentActions; namespace RegexMatchValues.Tests; [TestFixture] -public class RegexMatchExtensionsTests +internal sealed class RegexMatchExtensionsTests { [Test] public void StringFailedMatch() @@ -50,7 +48,7 @@ public void StringWrongNameGroupMatch() [Test] public void StringEmptyMatch() { - Regex.Match("hello there", @"l()l").Get().Should().Be(""); + Regex.Match("hello there", "l()l").Get().Should().Be(""); } [Test] @@ -105,38 +103,38 @@ public void IntegerOverflowException() [Test] public void IntegerFormatException() { - Invoking(() => Regex.Match("x 0xDEAD x", @"x(.*)x").Get()).Should().Throw(); + Invoking(() => Regex.Match("x 0xDEAD x", "x(.*)x").Get()).Should().Throw(); } [Test] public void IntegerEmpty() { - Invoking(() => Regex.Match("xx", @"x(.*)x").Get()).Should().Throw(); - Regex.Match("xx", @"x(.*)x").Get().Should().Be(null); - Invoking(() => Regex.Match("xxx", @"x(.*)x(.*)x").Get<(int, int)>()).Should().Throw(); - Regex.Match("xxx", @"x(.*)x(.*)x").Get<(int?, int?)>().Should().Be((default, default)); + Invoking(() => Regex.Match("xx", "x(.*)x").Get()).Should().Throw(); + Regex.Match("xx", "x(.*)x").Get().Should().Be(null); + Invoking(() => Regex.Match("xxx", "x(.*)x(.*)x").Get<(int, int)>()).Should().Throw(); + Regex.Match("xxx", "x(.*)x(.*)x").Get<(int?, int?)>().Should().Be((default, default)); } [Test] public void IntegerOnlyWhitespace() { - Invoking(() => Regex.Match("x x", @"x(.*)x").Get()).Should().Throw(); - Regex.Match("x x", @"x(.*)x").Get().Should().Be(null); - Invoking(() => Regex.Match("x x x", @"x(.*)x(.*)x").Get<(int, int)>()).Should().Throw(); - Regex.Match("x x x", @"x(.*)x(.*)x").Get<(int?, int?)>().Should().Be((default, default)); + Invoking(() => Regex.Match("x x", "x(.*)x").Get()).Should().Throw(); + Regex.Match("x x", "x(.*)x").Get().Should().Be(null); + Invoking(() => Regex.Match("x x x", "x(.*)x(.*)x").Get<(int, int)>()).Should().Throw(); + Regex.Match("x x x", "x(.*)x(.*)x").Get<(int?, int?)>().Should().Be((default, default)); } [Test] public void IntegerWhitespaceTrimmed() { - Regex.Match("x 42 x", @"x(.*)x").Get().Should().Be(42); - Regex.Match("x 42 x", @"x(.*)x").Get().Should().Be(42); + Regex.Match("x 42 x", "x(.*)x").Get().Should().Be(42); + Regex.Match("x 42 x", "x(.*)x").Get().Should().Be(42); } [Test] public void EnumParse() { - Regex.Match("x righttoleft x", @"x(.*)x").Get().Should().Be(RegexOptions.RightToLeft); + Regex.Match("x righttoleft x", "x(.*)x").Get().Should().Be(RegexOptions.RightToLeft); } [Test] @@ -219,15 +217,15 @@ public void GetGroup() [Test] public void OptionalGroups() { - Regex.Match("ac", @"(a)(b)?(c)").Get<(string, string?, string)>().Should().Be(("a", null, "c")); - Regex.Match("ac", @"(a)(b?)(c)").Get<(string, string, string)>().Should().Be(("a", "", "c")); + Regex.Match("ac", "(a)(b)?(c)").Get<(string, string?, string)>().Should().Be(("a", null, "c")); + Regex.Match("ac", "(a)(b?)(c)").Get<(string, string, string)>().Should().Be(("a", "", "c")); } [Test] public void BooleanValues() { - Regex.Match("ac", @"(a)(b)?(c)").Get<(string, bool, bool)>().Should().Be(("a", false, true)); - Regex.Match("ac", @"(a)(b?)(c)").Get<(string, bool, bool)>().Should().Be(("a", true, true)); + Regex.Match("ac", "(a)(b)?(c)").Get<(string, bool, bool)>().Should().Be(("a", false, true)); + Regex.Match("ac", "(a)(b?)(c)").Get<(string, bool, bool)>().Should().Be(("a", true, true)); } [Test] @@ -268,6 +266,6 @@ public void CapturesAsCaptures() Regex.Match("find 1 2 3 5 8", @"(?:([0-9]+)\s*)+").Get().Select(x => x.Value).Should().Equal("1", "2", "3", "5", "8"); } - private static readonly Regex s_signedIntegerRegex = new Regex(@"[-0-9]+"); - private static readonly Regex s_unsignedIntegerRegex = new Regex(@"[0-9]+"); + private static readonly Regex s_signedIntegerRegex = new Regex("[-0-9]+"); + private static readonly Regex s_unsignedIntegerRegex = new Regex("[0-9]+"); } diff --git a/tests/RegexMatchValues.Tests/RegexMatchValues.Tests.csproj b/tests/RegexMatchValues.Tests/RegexMatchValues.Tests.csproj index 0ae24cf..a82106b 100644 --- a/tests/RegexMatchValues.Tests/RegexMatchValues.Tests.csproj +++ b/tests/RegexMatchValues.Tests/RegexMatchValues.Tests.csproj @@ -1,18 +1,23 @@ - net6.0;net5.0 + net8.0 - - - - + + + + + + + + + diff --git a/tools/Build/Build.cs b/tools/Build/Build.cs index e1d275a..7bb52c2 100644 --- a/tools/Build/Build.cs +++ b/tools/Build/Build.cs @@ -1,21 +1,7 @@ -return BuildRunner.Execute(args, build => -{ - var gitLogin = new GitLoginInfo("ejball", Environment.GetEnvironmentVariable("BUILD_BOT_PASSWORD") ?? ""); - - build.AddDotNetTargets( +return BuildRunner.Execute(args, + build => build.AddDotNetTargets( new DotNetBuildSettings { NuGetApiKey = Environment.GetEnvironmentVariable("NUGET_API_KEY"), - DocsSettings = new DotNetDocsSettings - { - GitLogin = gitLogin, - GitAuthor = new GitAuthorInfo("ejball", "ejball@gmail.com"), - SourceCodeUrl = "https://github.com/ejball/RegexMatchValues/tree/master/src", - }, - PackageSettings = new DotNetPackageSettings - { - GitLogin = gitLogin, - PushTagOnPublish = x => $"v{x.Version}", - }, - }); -}); + PackageSettings = new DotNetPackageSettings { PushTagOnPublish = x => $"v{x.Version}" }, + })); diff --git a/tools/Build/Build.csproj b/tools/Build/Build.csproj index b072db3..a13b574 100644 --- a/tools/Build/Build.csproj +++ b/tools/Build/Build.csproj @@ -2,11 +2,11 @@ Exe - net6.0 + net10.0 - + diff --git a/tools/XmlDocGen/XmlDocGen.csproj b/tools/XmlDocGen/XmlDocGen.csproj deleted file mode 100644 index 1376b96..0000000 --- a/tools/XmlDocGen/XmlDocGen.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net6.0 - - - - - - - - - - - diff --git a/tools/XmlDocGen/XmlDocGenApp.cs b/tools/XmlDocGen/XmlDocGenApp.cs deleted file mode 100644 index 893a7df..0000000 --- a/tools/XmlDocGen/XmlDocGenApp.cs +++ /dev/null @@ -1,3 +0,0 @@ -using XmlDocMarkdown.Core; - -return XmlDocMarkdownApp.Run(args);