diff --git a/.editorconfig b/.editorconfig
index 6064c63..b5f39e6 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,15 +1,364 @@
-# EditorConfig is awesome: https://EditorConfig.org
-
-# top-most EditorConfig file
root = true
+# All files
[*]
indent_style = space
+
+# Xml files
+[*.xml]
+indent_size = 2
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
indent_size = 4
+tab_width = 4
+
+# New line preferences
end_of_line = crlf
-charset = utf-8
-trim_trailing_whitespace = true
insert_final_newline = false
-[*.{html,js,css,xml}]
-indent_size = 2
\ No newline at end of file
+#### .NET Coding Conventions ####
+[*.{cs,vb}]
+
+# 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:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# 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:silent
+
+# 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:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+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:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+[*.cs]
+
+# 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:suggestion
+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_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+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:warning
+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:silent
+
+#### 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 = true
+csharp_indent_labels = one_less_than_current
+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 = true
+
+#### Naming styles ####
+[*.{cs,vb}]
+
+# Naming rules
+
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
+dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
+dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
+
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
+dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
+
+# Symbol specifications
+
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interfaces.required_modifiers =
+
+dotnet_naming_symbols.enums.applicable_kinds = enum
+dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enums.required_modifiers =
+
+dotnet_naming_symbols.events.applicable_kinds = event
+dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.events.required_modifiers =
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.methods.required_modifiers =
+
+dotnet_naming_symbols.properties.applicable_kinds = property
+dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.properties.required_modifiers =
+
+dotnet_naming_symbols.public_fields.applicable_kinds = field
+dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_fields.required_modifiers =
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_fields.required_modifiers =
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
+dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types_and_namespaces.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 =
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers =
+
+dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_variables.applicable_kinds = local
+dotnet_naming_symbols.local_variables.applicable_accessibilities = local
+dotnet_naming_symbols.local_variables.required_modifiers =
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.applicable_accessibilities = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+dotnet_naming_symbols.parameters.applicable_accessibilities = *
+dotnet_naming_symbols.parameters.required_modifiers =
+
+dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+dotnet_naming_symbols.local_functions.applicable_accessibilities = *
+dotnet_naming_symbols.local_functions.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascalcase.required_prefix =
+dotnet_naming_style.pascalcase.required_suffix =
+dotnet_naming_style.pascalcase.word_separator =
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix =
+dotnet_naming_style.ipascalcase.word_separator =
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix =
+dotnet_naming_style.tpascalcase.word_separator =
+dotnet_naming_style.tpascalcase.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
+
+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
+
+dotnet_naming_style.s_camelcase.required_prefix = s_
+dotnet_naming_style.s_camelcase.required_suffix =
+dotnet_naming_style.s_camelcase.word_separator =
+dotnet_naming_style.s_camelcase.capitalization = camel_case
+
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..ced73c3
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,7 @@
+# For more information on how to configure a CODEOWNERS file, see
+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
+
+# These owners will be the default owners for everything in the repo. Unless a
+# later match takes precedence, these users will be requested for review when
+# someone opens a pull request.
+* @scottoffen @sdoffen
\ No newline at end of file
diff --git a/.github/workflows/main-pr-build-test.yaml b/.github/workflows/main-pr-build-test.yaml
new file mode 100644
index 0000000..83c12ea
--- /dev/null
+++ b/.github/workflows/main-pr-build-test.yaml
@@ -0,0 +1,163 @@
+# GitHub Workflow to build and test the code on a pull request
+name: PR Build and Test
+
+# The workflow_dispatch event allows you to run the workflow manually
+# The pull_request event triggers the workflow on pull requests to the main branch
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ # This job will build and run the tests for the project
+ build:
+ name: Build and Test
+ runs-on: ubuntu-latest
+
+ # These permissions are required to restore from GitHub Packages
+ permissions:
+ contents: read
+ packages: read
+
+ # The matrix strategy allows you to run the same steps on multiple operating systems
+ # The library should be compatible with all the operating systems in the matrix
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+
+ # The steps below will run in order for each of strategies defined in the matrix
+ steps:
+
+ # Installs the most recent versions of the .NET SDKs
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 3.1.x
+ 5.0.x
+ 6.0.x
+ 7.0.x
+ 8.0.x
+
+ # Displays the available .NET SDKs for verification
+ - name: Display Available .NET SDKs
+ run: dotnet --list-sdks
+
+ # Checks out the code from the repository using a deep clone
+ # The deep close in necessary to access the full history of the repository
+ # so that the NerdBank.GitVersioning tool can determine the version number
+ - name: Checkout code (Deep Clone)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ # Configure NuGet to use GitHub Packages as a source
+ - name: Configure NuGet for GitHub Packages
+ run: |
+ dotnet nuget add source https://nuget.pkg.github.com/scottoffen/index.json \
+ --name GitHub \
+ --username ${{ github.actor }} \
+ --password ${{ secrets.GITHUB_TOKEN }} \
+ --store-password-in-clear-text
+
+ # Restores the dependencies for the project
+ - name: Restore dependencies
+ run: dotnet restore ./src/Grapevine
+
+ # Builds the project without restoring the dependencies
+ - name: Build
+ run: dotnet build ./src/Grapevine --no-restore
+
+ # Runs all the tests in the project except the integration tests
+ # without rebuilding the project
+ - name: Test
+ run: dotnet test ./src/Grapevine --no-build --verbosity normal --filter "Category!=Integration"
+
+ # This job will determine if a pre-release package should be published
+ check:
+ name: Check for Source Code Changes
+ needs: build
+ runs-on: ubuntu-latest
+
+ outputs:
+ has_changes: ${{ steps.changed_files.outputs.any_changed }}
+
+ # The steps below will only run once
+ steps:
+
+ # Checks out the code from the repository using a deep clone
+ # The deep close in necessary to access the full history of the repository
+ # so that the NerdBank.GitVersioning tool can determine the version number
+ - name: Checkout code (Deep Clone)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ # Checks for changes in the ./src directory that are not markdown files
+ # this step outpus a boolean value named any_changed that can be used in
+ # conditional steps.
+ - name: Check for source code changes
+ id: changed_files
+ uses: tj-actions/changed-files@v45
+ with:
+ files: 'src/**'
+ files_ignore: '**/*.md'
+
+ # This job will publish the package to GitHub Packages
+ # While this job is dependent on the build job, do not combine the jobs as
+ # the build job will run multiple times
+ publish:
+ name: Publish PreRelease Package
+ runs-on: ubuntu-latest
+ needs: check
+ if: ${{ needs.check.outputs.has_changes == 'true' }}
+
+ # These permissions are required to publish the package to GitHub Packages
+ permissions:
+ contents: write
+ packages: write
+
+ # The steps below will only run once
+ steps:
+
+ # Installs the most recent versions of the .NET SDKs
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ # Displays the available .NET SDKs for verification
+ - name: Display Available .NET SDKs
+ run: dotnet --list-sdks
+
+ # Checks out the code from the repository using a deep clone
+ # The deep close in necessary to access the full history of the repository
+ # so that the NerdBank.GitVersioning tool can determine the version number
+ - name: Checkout code (Deep Clone)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ # Configure NuGet to use GitHub Packages as a source
+ - name: Configure NuGet for GitHub Packages
+ run: |
+ dotnet nuget add source https://nuget.pkg.github.com/scottoffen/index.json \
+ --name GitHub \
+ --username ${{ github.actor }} \
+ --password ${{ secrets.GITHUB_TOKEN }} \
+ --store-password-in-clear-text
+
+ # Restores the dependencies for the project
+ - name: Restore dependencies
+ run: dotnet restore ./src/Grapevine
+
+ # Builds the project using the Release configuration without restoring dependencies
+ - name: Build
+ run: dotnet build ./src/Grapevine --no-restore --configuration Release --output ./publish
+
+ # Publishes the package to GitHub Packages
+ - name: Publish to GitHub Packages
+ run: |
+ dotnet nuget push ./publish/*.nupkg --source https://nuget.pkg.github.com/scottoffen/index.json --api-key ${{ secrets.GITHUB_TOKEN }}
+ dotnet nuget push ./publish/*.snupkg --source https://nuget.pkg.github.com/scottoffen/index.json --api-key ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/main-publish-release.yaml b/.github/workflows/main-publish-release.yaml
new file mode 100644
index 0000000..d3dcdb0
--- /dev/null
+++ b/.github/workflows/main-publish-release.yaml
@@ -0,0 +1,179 @@
+# This workflow will automatically build and test the project when code is merged
+# into the main branch, then publish a release of the library to GitHub Packages.
+# It can also be manually triggered to optionally publish to Nuget.org.
+name: Publish Release
+
+# The workflow_dispatch event allows you to run the workflow manually
+# The push event triggers the workflow on pushes to the main branch
+on:
+ workflow_dispatch:
+ inputs:
+ pushNuget:
+ description: 'Push to Nuget'
+ required: false
+ default: false
+ type: boolean
+ pushGitHub:
+ description: 'Push to GitHub'
+ required: false
+ default: true
+ type: boolean
+ push:
+ branches:
+ - main
+ paths:
+ - 'src/**'
+ - '!src/**/*.md'
+
+jobs:
+ # This job will build and run the tests for the project
+ build:
+ name: Build and Test
+ runs-on: ubuntu-latest
+
+ # These permissions are required to restore from GitHub Packages
+ permissions:
+ contents: read
+ packages: read
+
+ # The matrix strategy allows you to run the same steps on multiple operating systems
+ # The library should be compatible with all the operating systems in the matrix
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+
+ # The steps below will run in order for each of strageies defined in the matrix
+ steps:
+
+ # Installs the most recent versions of the .NET SDKs
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ # Displays the available .NET SDKs for verification
+ - name: Display Available .NET SDKs
+ run: dotnet --list-sdks
+
+ # Checks out the code from the repository using a deep clone
+ # The deep close in necessary to access the full history of the repository
+ # so that the NerdBank.GitVersioning tool can determine the version number
+ - name: Checkout Code (Deep Clone)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ # Configure NuGet to use GitHub Packages as a source
+ - name: Configure NuGet for GitHub Packages
+ run: |
+ dotnet nuget add source https://nuget.pkg.github.com/scottoffen/index.json \
+ --name GitHub \
+ --username ${{ github.actor }} \
+ --password ${{ secrets.GITHUB_TOKEN }} \
+ --store-password-in-clear-text
+
+ # Restores the dependencies for the project
+ - name: Restore Dependencies
+ run: dotnet restore ./src/Grapevine
+
+ # Builds the project without restoring the dependencies
+ - name: Build Project
+ run: dotnet build ./src/Grapevine --no-restore
+
+ # Runs all the tests in the project except the integration tests
+ # without rebuilding the project
+ - name: Execute Test
+ run: dotnet test ./src/Grapevine --no-build --verbosity normal --filter "Category!=Integration"
+
+ # This job will publish the package to GitHub Packages
+ # While this job is dependent on the build job, do not combine the jobs as
+ # the build job will run multiple times
+ publish:
+ name: Publish Package
+ runs-on: ubuntu-latest
+ needs: build
+
+ # These permissions are required to publish the package to GitHub Packages
+ permissions:
+ contents: write
+ packages: write
+
+ # Get default values for the workflow_dispatch event inputs
+ env:
+ PUSH_GITHUB: ${{ github.event.inputs.pushGitHub || 'true' }}
+ PUSH_NUGET: ${{ github.event.inputs.pushNuget || 'false' }}
+
+ # The steps below will only run once
+ steps:
+
+ # Installs the most recent versions of the .NET SDKs
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ # Displays the available .NET SDKs for verification
+ - name: Display Available .NET SDKs
+ run: dotnet --list-sdks
+
+ # Checks out the code from the repository using a deep clone
+ # The deep close in necessary to access the full history of the repository
+ # so that the NerdBank.GitVersioning tool can determine the version number
+ - name: Checkout Code (Deep Clone)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ # Configure NuGet to use GitHub Packages as a source
+ - name: Configure NuGet for GitHub Packages
+ run: |
+ dotnet nuget add source https://nuget.pkg.github.com/scottoffen/index.json \
+ --name GitHub \
+ --username ${{ github.actor }} \
+ --password ${{ secrets.GITHUB_TOKEN }} \
+ --store-password-in-clear-text
+
+ # Restores the dependencies for the project
+ - name: Restore Dependencies
+ run: dotnet restore ./src/Grapevine
+
+ # Builds the project using the Release configuration without restoring dependencies
+ - name: Build Release
+ run: dotnet build ./src/Grapevine --no-restore --configuration Release --output ./publish
+
+ # Output the github.event.inputs values
+ - name: Display Inputs
+ run: |
+ echo "pushGitHub: $PUSH_GITHUB"
+ echo "pushNuget: $PUSH_NUGET"
+
+ # Publishes the package to GitHub Packages
+ # This step is run by default, but can be disabled
+ - name: Publish to GitHub Packages
+ if: env.PUSH_GITHUB == 'true'
+ run: |
+ dotnet nuget push ./publish/*.nupkg --source https://nuget.pkg.github.com/scottoffen/index.json --api-key ${{ secrets.GITHUB_TOKEN }}
+ dotnet nuget push ./publish/*.snupkg --source https://nuget.pkg.github.com/scottoffen/index.json --api-key ${{ secrets.GITHUB_TOKEN }}
+
+ # Publishes the package to Nuget.org
+ # This step is optional and can be triggered manually
+ # The NUGET_API_KEY secret must be set in the repository settings
+ - name: Publish to Nuget.org
+ if: env.PUSH_NUGET == 'true'
+ run: |
+ dotnet nuget push ./publish/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
+
+ # Outputs the version number for the release
+ # See https://www.jameskerr.blog/posts/how-to-set-output-in-github-actions/
+ - name: Get Version Info
+ id: get_version
+ run: |
+ cd src/Grapevine
+ echo "version='$(nbgv get-version -v NugetPackageVersion)'" >> "$GITHUB_OUTPUT"
+ cd ../..
+
+ # Create a release tag based on the version number
+ - name: Create Release
+ run: gh release create ${{ steps.get_version.outputs.version }} --title ${{ steps.get_version.outputs.version }} --notes "Release ${{ steps.get_version.outputs.version }}" --target main
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 229c21a..5be365a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,10 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
# User-specific files
*.rsuser
@@ -57,11 +60,14 @@ dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
-# .NET Core
+# .NET
project.lock.json
project.fragment.lock.json
artifacts/
+# Tye
+.tye/
+
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
@@ -206,9 +212,6 @@ PublishScripts/
*.nuget.props
*.nuget.targets
-# Nuget personal access tokens and Credentials
-# nuget.config
-
# Microsoft Azure Build Output
csx/
*.build.csdef
@@ -364,6 +367,9 @@ ASALocalRun/
# Local History for Visual Studio
.localhistory/
+# Visual Studio History (VSHistory) files
+.vshistory/
+
# BeatPulse healthcheck temp database
healthchecksdb
@@ -395,4 +401,84 @@ FodyWeavers.xsd
*.msp
# JetBrains Rider
-*.sln.iml
\ No newline at end of file
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
diff --git a/src/Grapeseed/Grapeseed.csproj b/src/Grapeseed/Grapeseed.csproj
index e6d81f4..6300036 100644
--- a/src/Grapeseed/Grapeseed.csproj
+++ b/src/Grapeseed/Grapeseed.csproj
@@ -1,44 +1,65 @@
- netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0
- NETSDK1138
+ net5.0;net6.0;net7.0;net8.0
+ enable
+ $(NoWarn);NETSDK1138;CS1591;CS1573;CS1711;CS1574;CS1572
+ true
Grapeseed
- $(Version)
Scott Offen
- © 2014 - $([System.DateTime]::Now.ToString('yyyy')) Scott Offen
- Scott Offen
+ © 2014 - $([System.DateTime]::Now.ToString('yyyy')) $(Authors)
Grapevine abstractions
- false
$(Version)
$(Version)
- 6.0.0-beta
- true
- LICENSE
- https://github.com/scottoffen/grapevine
+ $(Version)
+ 0.0.1
+ README.md
+ MIT
+ false
git
+ true
+ https://github.com/scottoffen/grapevine
https://scottoffen.github.io/grapevine/
grapevine.png
- true
+ true
true
snupkg
$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
-
-
-
+
+ latestmajor
+ true
+
+
+
+ latestmajor
+ true
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- <_Parameter1>Grapevine.Tests
-
+
+
diff --git a/src/Grapeseed/IContentFolder.cs b/src/Grapeseed/IContentFolder.cs
index 31dd015..dddb19c 100644
--- a/src/Grapeseed/IContentFolder.cs
+++ b/src/Grapeseed/IContentFolder.cs
@@ -43,9 +43,8 @@ public interface IContentFolder
Task SendFileAsync(IHttpContext context, string filename);
///
- /// The action to take if the file is not found but should be. This occures when prefix is found at the begining of the path info, but the file name specified isn't found in the content folder.
+ /// The action to take if the file is not found but should be. This occurs when prefix is found at the beginning of the path info, but the file name specified isn't found in the content folder.
///
- /// Action
Func FileNotFoundHandler { get; set; }
}
diff --git a/src/Grapeseed/README.md b/src/Grapeseed/README.md
new file mode 100644
index 0000000..60defef
--- /dev/null
+++ b/src/Grapeseed/README.md
@@ -0,0 +1 @@
+# Grapeseed
\ No newline at end of file
diff --git a/src/Grapevine.Tests/RouteConstraintTests.cs b/src/Grapevine.Tests/RouteConstraintTests.cs
index 3da3816..1f961bf 100644
--- a/src/Grapevine.Tests/RouteConstraintTests.cs
+++ b/src/Grapevine.Tests/RouteConstraintTests.cs
@@ -9,348 +9,348 @@ namespace Grapevine.Tests
{
public class RouteConstraintTests
{
- public class AlphaResolver
- {
- [Fact]
- public void returns_alpha_pattern_with_repetition_quantifier()
- {
- var constraints = "alpha".Split(':').ToList();
- var expected = "([a-zA-Z]+)";
+ // public class AlphaResolver
+ // {
+ // [Fact]
+ // public void returns_alpha_pattern_with_repetition_quantifier()
+ // {
+ // var constraints = "alpha".Split(':').ToList();
+ // var expected = "([a-zA-Z]+)";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_alpha_pattern_with_minimum_length_quantifier()
- {
- var constraints = "alpha:minlength(2)".Split(':').ToList();
- var expected = "([a-zA-Z]{2,})";
+ // [Fact]
+ // public void returns_alpha_pattern_with_minimum_length_quantifier()
+ // {
+ // var constraints = "alpha:minlength(2)".Split(':').ToList();
+ // var expected = "([a-zA-Z]{2,})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_alpha_pattern_with_maximum_length_quantifier()
- {
- var constraints = "alpha:maxlength(2)".Split(':').ToList();
- var expected = "([a-zA-Z]{1,2})";
+ // [Fact]
+ // public void returns_alpha_pattern_with_maximum_length_quantifier()
+ // {
+ // var constraints = "alpha:maxlength(2)".Split(':').ToList();
+ // var expected = "([a-zA-Z]{1,2})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_alpha_pattern_with_exact_length_quantifier()
- {
- var constraints = "alpha:length(2)".Split(':').ToList();
- var expected = "([a-zA-Z]{2})";
+ // [Fact]
+ // public void returns_alpha_pattern_with_exact_length_quantifier()
+ // {
+ // var constraints = "alpha:length(2)".Split(':').ToList();
+ // var expected = "([a-zA-Z]{2})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_alpha_pattern_with_range_length_quantifier()
- {
- var constraints = "alpha:length(2,5)".Split(':').ToList();
- var expected = "([a-zA-Z]{2,5})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
- }
-
- public class AlphaNumericResolver
- {
- [Fact]
- public void returns_alphanum_pattern_with_repetition_quantifier()
- {
- var constraints = "alphanum".Split(':').ToList();
- var expected = @"(\w+)";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_alphanum_pattern_with_minimum_length_quantifier()
- {
- var constraints = "alphanum:minlength(2)".Split(':').ToList();
- var expected = @"(\w{2,})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_alphanum_pattern_with_maximum_length_quantifier()
- {
- var constraints = "alphanum:maxlength(2)".Split(':').ToList();
- var expected = @"(\w{1,2})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_alphanum_pattern_with_exact_length_quantifier()
- {
- var constraints = "alphanum:length(2)".Split(':').ToList();
- var expected = @"(\w{2})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_alphanum_pattern_with_range_length_quantifier()
- {
- var constraints = "alphanum:length(2,5)".Split(':').ToList();
- var expected = @"(\w{2,5})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
- }
-
- public class CustomResolver
- {
- private static string expectedResult = "RESOLVED";
-
- private static RouteConstraintResolver customResolver = (value) => { return expectedResult; };
-
- [Fact]
- public void throws_exception_when_attempting_to_override_protected_resolver()
- {
- Should.Throw(() =>
- {
- RouteConstraints.AddResolver("num", customResolver);
- });
- }
-
- [Fact]
- public void can_add_custom_resovler()
- {
- var customKey = "custom";
- RouteConstraints.AddResolver(customKey, customResolver);
-
- var actual = RouteConstraints.Resolve(new List() { customKey });
-
- actual.ShouldBe(expectedResult);
- }
- }
-
- public class GuidResolver
- {
- [Fact]
- public void returns_guid_pattern_that_matches_guids()
- {
- var guidBase = Guid.NewGuid().ToString();
- var guids = new List()
- {
- guidBase.Replace("-", ""),
- guidBase.ToLower(),
- guidBase.ToUpper(),
- "{" + guidBase + "}",
- $"({guidBase})"
- };
-
- var notguid = guidBase.Replace("-", "=");
- var constraints = new List() { "guid" };
- var pattern = new Regex($"^{RouteConstraints.Resolve(constraints)}$");
-
- foreach (var guid in guids)
- {
- pattern.IsMatch(guid).ShouldBeTrue();
- }
-
- pattern.IsMatch(notguid).ShouldBeFalse();
- }
- }
-
- public class NumericResolver
- {
- [Fact]
- public void returns_numeric_pattern_with_repetition_quantifier()
- {
- var constraints = "num".Split(':').ToList();
- var expected = @"(\d+)";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_numeric_pattern_with_minimum_length_quantifier()
- {
- var constraints = "num:minlength(2)".Split(':').ToList();
- var expected = @"(\d{2,})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_numeric_pattern_with_maximum_length_quantifier()
- {
- var constraints = "num:maxlength(2)".Split(':').ToList();
- var expected = @"(\d{1,2})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_numeric_pattern_with_exact_length_quantifier()
- {
- var constraints = "num:length(2)".Split(':').ToList();
- var expected = @"(\d{2})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_numeric_pattern_with_range_length_quantifier()
- {
- var constraints = "num:length(2,5)".Split(':').ToList();
- var expected = @"(\d{2,5})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
- }
-
- public class StringResolver
- {
- [Fact]
- public void returns_string_pattern_with_repetition_quantifier()
- {
- var constraints = "string".Split(':').ToList();
- var expected = @"([^/]+)";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
-
- [Fact]
- public void returns_string_pattern_with_minimum_length_quantifier()
- {
- var constraints = "string:minlength(2)".Split(':').ToList();
- var expected = @"([^/]{2,})";
-
- var pattern = RouteConstraints.Resolve(constraints);
-
- pattern.ShouldBe(expected);
- }
+ // [Fact]
+ // public void returns_alpha_pattern_with_range_length_quantifier()
+ // {
+ // var constraints = "alpha:length(2,5)".Split(':').ToList();
+ // var expected = "([a-zA-Z]{2,5})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+ // }
+
+ // public class AlphaNumericResolver
+ // {
+ // [Fact]
+ // public void returns_alphanum_pattern_with_repetition_quantifier()
+ // {
+ // var constraints = "alphanum".Split(':').ToList();
+ // var expected = @"(\w+)";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_alphanum_pattern_with_minimum_length_quantifier()
+ // {
+ // var constraints = "alphanum:minlength(2)".Split(':').ToList();
+ // var expected = @"(\w{2,})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_alphanum_pattern_with_maximum_length_quantifier()
+ // {
+ // var constraints = "alphanum:maxlength(2)".Split(':').ToList();
+ // var expected = @"(\w{1,2})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_alphanum_pattern_with_exact_length_quantifier()
+ // {
+ // var constraints = "alphanum:length(2)".Split(':').ToList();
+ // var expected = @"(\w{2})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_alphanum_pattern_with_range_length_quantifier()
+ // {
+ // var constraints = "alphanum:length(2,5)".Split(':').ToList();
+ // var expected = @"(\w{2,5})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+ // }
+
+ // public class CustomResolver
+ // {
+ // private static string expectedResult = "RESOLVED";
+
+ // private static RouteConstraintResolver customResolver = (value) => { return expectedResult; };
+
+ // [Fact]
+ // public void throws_exception_when_attempting_to_override_protected_resolver()
+ // {
+ // Should.Throw(() =>
+ // {
+ // RouteConstraints.AddResolver("num", customResolver);
+ // });
+ // }
+
+ // [Fact]
+ // public void can_add_custom_resovler()
+ // {
+ // var customKey = "custom";
+ // RouteConstraints.AddResolver(customKey, customResolver);
+
+ // var actual = RouteConstraints.Resolve(new List() { customKey });
+
+ // actual.ShouldBe(expectedResult);
+ // }
+ // }
+
+ // public class GuidResolver
+ // {
+ // [Fact]
+ // public void returns_guid_pattern_that_matches_guids()
+ // {
+ // var guidBase = Guid.NewGuid().ToString();
+ // var guids = new List()
+ // {
+ // guidBase.Replace("-", ""),
+ // guidBase.ToLower(),
+ // guidBase.ToUpper(),
+ // "{" + guidBase + "}",
+ // $"({guidBase})"
+ // };
+
+ // var notguid = guidBase.Replace("-", "=");
+ // var constraints = new List() { "guid" };
+ // var pattern = new Regex($"^{RouteConstraints.Resolve(constraints)}$");
+
+ // foreach (var guid in guids)
+ // {
+ // pattern.IsMatch(guid).ShouldBeTrue();
+ // }
+
+ // pattern.IsMatch(notguid).ShouldBeFalse();
+ // }
+ // }
+
+ // public class NumericResolver
+ // {
+ // [Fact]
+ // public void returns_numeric_pattern_with_repetition_quantifier()
+ // {
+ // var constraints = "num".Split(':').ToList();
+ // var expected = @"(\d+)";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_numeric_pattern_with_minimum_length_quantifier()
+ // {
+ // var constraints = "num:minlength(2)".Split(':').ToList();
+ // var expected = @"(\d{2,})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_numeric_pattern_with_maximum_length_quantifier()
+ // {
+ // var constraints = "num:maxlength(2)".Split(':').ToList();
+ // var expected = @"(\d{1,2})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_numeric_pattern_with_exact_length_quantifier()
+ // {
+ // var constraints = "num:length(2)".Split(':').ToList();
+ // var expected = @"(\d{2})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_numeric_pattern_with_range_length_quantifier()
+ // {
+ // var constraints = "num:length(2,5)".Split(':').ToList();
+ // var expected = @"(\d{2,5})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+ // }
+
+ // public class StringResolver
+ // {
+ // [Fact]
+ // public void returns_string_pattern_with_repetition_quantifier()
+ // {
+ // var constraints = "string".Split(':').ToList();
+ // var expected = @"([^/]+)";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
+
+ // [Fact]
+ // public void returns_string_pattern_with_minimum_length_quantifier()
+ // {
+ // var constraints = "string:minlength(2)".Split(':').ToList();
+ // var expected = @"([^/]{2,})";
+
+ // var pattern = RouteConstraints.Resolve(constraints);
+
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_string_pattern_with_maximum_length_quantifier()
- {
- var constraints = "string:maxlength(2)".Split(':').ToList();
- var expected = @"([^/]{1,2})";
+ // [Fact]
+ // public void returns_string_pattern_with_maximum_length_quantifier()
+ // {
+ // var constraints = "string:maxlength(2)".Split(':').ToList();
+ // var expected = @"([^/]{1,2})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_string_pattern_with_exact_length_quantifier()
- {
- var constraints = "string:length(2)".Split(':').ToList();
- var expected = @"([^/]{2})";
+ // [Fact]
+ // public void returns_string_pattern_with_exact_length_quantifier()
+ // {
+ // var constraints = "string:length(2)".Split(':').ToList();
+ // var expected = @"([^/]{2})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_string_pattern_with_range_length_quantifier()
- {
- var constraints = "string:length(2,5)".Split(':').ToList();
- var expected = @"([^/]{2,5})";
+ // [Fact]
+ // public void returns_string_pattern_with_range_length_quantifier()
+ // {
+ // var constraints = "string:length(2,5)".Split(':').ToList();
+ // var expected = @"([^/]{2,5})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
- }
+ // pattern.ShouldBe(expected);
+ // }
+ // }
- public class UnspecifiedResolver
- {
- [Fact]
- public void returns_default_pattern_when_no_constraint_is_provided()
- {
- List constraints = new List();
- var pattern = RouteConstraints.Resolve(constraints);
+ // public class UnspecifiedResolver
+ // {
+ // [Fact]
+ // public void returns_default_pattern_when_no_constraint_is_provided()
+ // {
+ // List constraints = new List();
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(RouteConstraints.DefaultPattern);
- }
+ // pattern.ShouldBe(RouteConstraints.DefaultPattern);
+ // }
- [Fact]
- public void returns_string_pattern_with_minimum_length_quantifier()
- {
- var constraints = "minlength(2)".Split(':').ToList();
- var expected = @"([^/]{2,})";
+ // [Fact]
+ // public void returns_string_pattern_with_minimum_length_quantifier()
+ // {
+ // var constraints = "minlength(2)".Split(':').ToList();
+ // var expected = @"([^/]{2,})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_string_pattern_with_maximum_length_quantifier()
- {
- var constraints = "maxlength(2)".Split(':').ToList();
- var expected = @"([^/]{1,2})";
+ // [Fact]
+ // public void returns_string_pattern_with_maximum_length_quantifier()
+ // {
+ // var constraints = "maxlength(2)".Split(':').ToList();
+ // var expected = @"([^/]{1,2})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_string_pattern_with_exact_length_quantifier()
- {
- var constraints = "length(2)".Split(':').ToList();
- var expected = @"([^/]{2})";
+ // [Fact]
+ // public void returns_string_pattern_with_exact_length_quantifier()
+ // {
+ // var constraints = "length(2)".Split(':').ToList();
+ // var expected = @"([^/]{2})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
+ // pattern.ShouldBe(expected);
+ // }
- [Fact]
- public void returns_string_pattern_with_range_length_quantifier()
- {
- var constraints = "length(2,5)".Split(':').ToList();
- var expected = @"([^/]{2,5})";
+ // [Fact]
+ // public void returns_string_pattern_with_range_length_quantifier()
+ // {
+ // var constraints = "length(2,5)".Split(':').ToList();
+ // var expected = @"([^/]{2,5})";
- var pattern = RouteConstraints.Resolve(constraints);
+ // var pattern = RouteConstraints.Resolve(constraints);
- pattern.ShouldBe(expected);
- }
- }
+ // pattern.ShouldBe(expected);
+ // }
+ // }
}
}
\ No newline at end of file
diff --git a/src/Grapevine/AssemblyInfo.cs b/src/Grapevine/AssemblyInfo.cs
new file mode 100644
index 0000000..a73116d
--- /dev/null
+++ b/src/Grapevine/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Runtime.InteropServices;
+
+// In SDK-style projects such as this one, several assembly attributes that were historically
+// defined in this file are now automatically added during build and populated with
+// values defined in project properties. For details of which attributes are included
+// and how to customize this process see: https://aka.ms/assembly-info-properties
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM
+// components. If you need to access a type in this assembly from COM, set the ComVisible
+// attribute to true on that type. See https://aka.ms/assembly-visibility
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM.
+[assembly: Guid("7DF0613D-6191-48DE-BDE7-C138BD2BFC6D")]
+
+// CLSCompliant attribute is used to indicate whether the assembly is compliant with
+// the Common Language Specification (CLS). See https://aka.ms/assembly-cls
+[assembly: CLSCompliant(false)]
diff --git a/src/Grapevine/Directory.Build.props b/src/Grapevine/Directory.Build.props
new file mode 100644
index 0000000..26f6094
--- /dev/null
+++ b/src/Grapevine/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+
+
+ all
+ 3.6.143
+
+
+
\ No newline at end of file
diff --git a/src/Grapevine/Grapevine.csproj b/src/Grapevine/Grapevine.csproj
index cc92417..e99ffaf 100644
--- a/src/Grapevine/Grapevine.csproj
+++ b/src/Grapevine/Grapevine.csproj
@@ -1,63 +1,80 @@
- netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0
- NETSDK1138
+ net5.0;net6.0;net7.0;net8.0
+ enable
+ $(NoWarn);NETSDK1138;CS1591;CS1572
+ true
Grapevine
- $(Version)
Scott Offen
- © 2014 - $([System.DateTime]::Now.ToString('yyyy')) Scott Offen
- Scott Offen
+ © 2014 - $([System.DateTime]::Now.ToString('yyyy')) $(Authors)
Fast, unopinionated, embeddable, minimalist REST framework for .NET
- false
rest http api web router client server express json xml embedded
$(Version)
$(Version)
- 6.0.0-beta
- true
- LICENSE
- https://github.com/scottoffen/grapevine
+ $(Version)
+ 0.0.1
+ README.md
+ MIT
+ false
git
+ true
+ https://github.com/scottoffen/grapevine
https://scottoffen.github.io/grapevine/
grapevine.png
- true
+ true
true
snupkg
+
+
$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+
+ latestmajor
+ true
+
+
+
+ latestmajor
+ true
+
+
-
-
-
-
-
+
+
-
-
- True
- True
- Messages.resx
-
+
+
+
+
+
+
-
-
- ResXFileCodeGenerator
- Messages.Designer.cs
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
-
-
- <_Parameter1>Grapevine.Tests
-
+
+
+
+
+
+
diff --git a/src/Grapevine/Middleware/CorsPolicy.cs b/src/Grapevine/Middleware/CorsPolicy.cs
index 7806b13..09fe54c 100644
--- a/src/Grapevine/Middleware/CorsPolicy.cs
+++ b/src/Grapevine/Middleware/CorsPolicy.cs
@@ -28,7 +28,7 @@ public CorsPolicy(Uri allowOrigin)
public CorsPolicy(IEnumerable allowOrigins)
{
- _allowedOrigins = allowOrigins.Cast();
+ _allowedOrigins = _allowedOrigins.Select(x => x.ToString());
}
public async Task ApplyAsync(IHttpContext context, IRestServer server)
diff --git a/src/Grapevine/MiddlewareExtensions.cs b/src/Grapevine/MiddlewareExtensions.cs
index 764cc11..18f458a 100644
--- a/src/Grapevine/MiddlewareExtensions.cs
+++ b/src/Grapevine/MiddlewareExtensions.cs
@@ -63,14 +63,14 @@ public static IRestServer UseCorrelationId(this IRestServer server, string corre
return server.UseCorrelationId(correlationIdFieldName, null);
}
- public static IRestServer UseCorrelationId(this IRestServer server, Func correlactionIdGenerator)
+ public static IRestServer UseCorrelationId(this IRestServer server, Func correlationIdGenerator)
{
- return server.UseCorrelationId(null, correlactionIdGenerator);
+ return server.UseCorrelationId(null, correlationIdGenerator);
}
- public static IRestServer UseCorrelationId(this IRestServer server, string correlationIdFieldName, Func correlactionIdGenerator)
+ public static IRestServer UseCorrelationId(this IRestServer server, string correlationIdFieldName, Func correlationIdGenerator)
{
- var mw = new CorrelationId(correlationIdFieldName, correlactionIdGenerator);
+ var mw = new CorrelationId(correlationIdFieldName, correlationIdGenerator);
server.OnRequestAsync += mw.EnsureCorrelationIdAsync;
return server;
}
@@ -93,7 +93,7 @@ public static IRestServer UseCorsPolicy(this IRestServer server, Uri allowOrigin
public static IRestServer UseCorsPolicy(this IRestServer server, IEnumerable allowOrigins)
{
- return server.UseCorsPolicy(allowOrigins.Cast().ToList());
+ return server.UseCorsPolicy(allowOrigins.Select(x => new Uri(x)).ToList());
}
public static IRestServer UseCorsPolicy(this IRestServer server, IEnumerable allowOrigins)
diff --git a/src/Grapevine/README.md b/src/Grapevine/README.md
new file mode 100644
index 0000000..cf11032
--- /dev/null
+++ b/src/Grapevine/README.md
@@ -0,0 +1 @@
+# Grapevine
\ No newline at end of file
diff --git a/src/Grapevine/version.json b/src/Grapevine/version.json
new file mode 100644
index 0000000..4dccd3c
--- /dev/null
+++ b/src/Grapevine/version.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
+ "version": "5.0-rc",
+ "publicReleaseRefSpec": [
+ "^refs/heads/main$",
+ "^refs/heads/v\\d+(?:\\.\\d+)?$"
+ ],
+ "cloudBuild": {
+ "buildNumber": {
+ "enabled": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Samples/Samples.csproj b/src/Samples/Samples.csproj
index c0b3af9..e11cd0d 100644
--- a/src/Samples/Samples.csproj
+++ b/src/Samples/Samples.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
NETSDK1138
@@ -11,12 +11,12 @@
-
-
-
-
-
-
+
+
+
+
+
+