-
Notifications
You must be signed in to change notification settings - Fork 656
Support C# format strings in config #4605
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
9swampy
wants to merge
10
commits into
GitTools:main
Choose a base branch
from
9swampy:SupportFormatStringsInConfig
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
96d8ba6
Support C# format strings in config
9swampy 532fdbc
Address PR comments.
9swampy 11a9737
Address PR comments II. Apply SOLI>>D<< to ValueFormatter.
9swampy 146e229
Draft documentation proposal.
9swampy 61163ef
Add ZeroFormatting tests to cover zero-suppression syntax
9swampy 9e97bfe
Align tests with moved InputSanitizer.cs and StringFormatWithExtensio…
9swampy a147b37
Renames and updates PascalCase extension method
arturcic 7d8bc62
Fix table alignment and remove trailing whitespaces
arturcic 1e890a3
Moves formatting tests to correct namespace
arturcic cc37e2b
address PR comments
arturcic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
--- | ||
title: Format Strings | ||
description: Using C# format strings in GitVersion configuration | ||
--- | ||
|
||
GitVersion supports C# format strings in configuration, allowing you to apply standard .NET formatting and custom transformations to version properties. This enhancement provides more flexibility and control over how version information is displayed and used throughout your build process. | ||
|
||
## Overview | ||
|
||
The custom formatter functionality introduces several new formatters that can be used in GitVersion configuration files and templates: | ||
|
||
- **FormattableFormatter**: Supports standard .NET format strings for numeric values, dates, and implements `IFormattable` | ||
- **NumericFormatter**: Handles numeric formatting with culture-aware output | ||
- **DateTimeFormatter**: Provides date and time formatting with standard and custom format specifiers | ||
- **String Case Formatters**: Provides text case transformations with custom format specifiers | ||
|
||
## Standard .NET Format Strings | ||
|
||
### Numeric Formatting | ||
|
||
You can now use standard .NET numeric format strings with version components: | ||
|
||
```yaml | ||
# GitVersion.yml | ||
template: "{Major}.{Minor}.{Patch:F2}-{PreReleaseLabel}" | ||
``` | ||
|
||
**Supported Numeric Formats:** | ||
|
||
- `F` or `f` (Fixed-point): `{Patch:F2}` → `"1.23"` | ||
- `N` or `n` (Number): `{BuildMetadata:N0}` → `"1,234"` | ||
- `C` or `c` (Currency): `{Major:C}` → `"¤1.00"` | ||
- `P` or `p` (Percent): `{CommitsSinceVersionSource:P}` → `"12,345.60 %"` | ||
- `D` or `d` (Decimal): `{Major:D4}` → `"0001"` | ||
- `X` or `x` (Hexadecimal): `{Patch:X}` → `"FF"` | ||
|
||
### Date and Time Formatting | ||
|
||
When working with date-related properties like `CommitDate`: | ||
|
||
```yaml | ||
template: "Build-{SemVer}-{CommitDate:yyyy-MM-dd}" | ||
``` | ||
|
||
**Common Date Format Specifiers:** | ||
|
||
- `yyyy-MM-dd` → `"2024-03-15"` | ||
- `HH:mm:ss` → `"14:30:22"` | ||
- `MMM dd, yyyy` → `"Mar 15, 2024"` | ||
- `yyyy-MM-dd'T'HH:mm:ss'Z'` → `"2024-03-15T14:30:22Z"` | ||
|
||
## Custom String Case Formatters | ||
|
||
GitVersion introduces custom format specifiers for string case transformations that can be used in templates: | ||
|
||
### Available Case Formats | ||
|
||
| Format | Description | Example Input | Example Output | | ||
|--------|---------------------------------------------------------------|------------------|------------------| | ||
| `u` | **Uppercase** - Converts entire string to uppercase | `feature-branch` | `FEATURE-BRANCH` | | ||
| `l` | **Lowercase** - Converts entire string to lowercase | `Feature-Branch` | `feature-branch` | | ||
| `t` | **Title Case** - Capitalizes first letter of each word | `feature-branch` | `Feature-Branch` | | ||
| `s` | **Sentence Case** - Capitalizes only the first letter | `feature-branch` | `Feature-branch` | | ||
| `c` | **PascalCase** - Removes separators and capitalizes each word | `feature-branch` | `FeatureBranch` | | ||
|
||
### Usage Examples | ||
|
||
```yaml | ||
# GitVersion.yml configuration | ||
branches: | ||
feature: | ||
label: "{BranchName:c}" # Converts to PascalCase | ||
|
||
template: "{Major}.{Minor}.{Patch}-{PreReleaseLabel:l}.{CommitsSinceVersionSource:0000}" | ||
``` | ||
|
||
**Template Usage:** | ||
|
||
```yaml | ||
# Using format strings in templates | ||
assembly-informational-format: "{Major}.{Minor}.{Patch}-{CommitsSinceVersionSource:0000}" | ||
template: "{SemVer}-{BranchName:l}" | ||
``` | ||
|
||
## Examples | ||
|
||
Based on actual test cases from the implementation: | ||
|
||
### Zero-Padded Numeric Formatting | ||
|
||
```yaml | ||
# Zero-padded commit count | ||
assembly-informational-format: "{Major}.{Minor}.{Patch}-{CommitsSinceVersionSource:0000}" | ||
# Result: "1.2.3-0042" | ||
``` | ||
|
||
### String Case Transformations | ||
|
||
```yaml | ||
branches: | ||
feature: | ||
label: "{BranchName:c}" # PascalCase: "feature-branch" → "FeatureBranch" | ||
hotfix: | ||
label: "hotfix-{BranchName:l}" # Lowercase: "HOTFIX-BRANCH" → "hotfix-branch" | ||
``` | ||
|
||
### Date and Time Formatting | ||
|
||
```yaml | ||
template: "{SemVer}-build-{CommitDate:yyyy-MM-dd}" | ||
# Result: "1.2.3-build-2021-01-01" | ||
``` | ||
|
||
### Numeric Formatting | ||
|
||
```yaml | ||
# Currency format (uses InvariantCulture) | ||
template: "Cost-{Major:C}" # Result: "Cost-¤1.00" | ||
|
||
# Percentage format | ||
template: "Progress-{Minor:P}" # Result: "Progress-200.00 %" | ||
|
||
# Thousands separator | ||
template: "Build-{CommitsSinceVersionSource:N0}" # Result: "Build-1,234" | ||
``` | ||
|
||
## Configuration Integration | ||
|
||
The format strings are used in GitVersion configuration files through various formatting properties: | ||
|
||
### Assembly Version Formatting | ||
|
||
```yaml | ||
# GitVersion.yml | ||
assembly-informational-format: "{Major}.{Minor}.{Patch}-{CommitsSinceVersionSource:0000}" | ||
assembly-versioning-format: "{Major}.{Minor}.{Patch}.{env:BUILD_NUMBER}" | ||
assembly-file-versioning-format: "{MajorMinorPatch}.{CommitsSinceVersionSource}" | ||
``` | ||
|
||
### Template-Based Configuration | ||
|
||
```yaml | ||
# Global template for consistent formatting across all variables | ||
template: "{SemVer}-{BranchName:l}-{ShortSha}" | ||
|
||
branches: | ||
main: | ||
label: "" | ||
feature: | ||
label: "{BranchName:c}.{CommitsSinceVersionSource}" | ||
increment: Minor | ||
release: | ||
label: "rc-{CommitsSinceVersionSource:000}" | ||
increment: None | ||
``` | ||
|
||
### Environment Variable Integration | ||
|
||
```yaml | ||
# Using environment variables with fallbacks | ||
template: "{Major}.{Minor}.{Patch}-{env:RELEASE_STAGE ?? 'dev'}" | ||
assembly-informational-format: "{SemVer}+{env:BUILD_ID ?? 'local'}" | ||
``` | ||
|
||
### Real-World Integration Examples | ||
|
||
Based on the actual test implementation: | ||
|
||
```yaml | ||
# Example from VariableProviderTests.cs | ||
assembly-informational-format: "{Major}.{Minor}.{Patch}-{CommitsSinceVersionSource:0000}" | ||
# Result: "1.2.3-0042" when CommitsSinceVersionSource = 42 | ||
|
||
# Branch-specific formatting | ||
branches: | ||
feature: | ||
label: "{BranchName:c}" # PascalCase conversion | ||
hotfix: | ||
label: "hotfix.{CommitsSinceVersionSource:00}" | ||
``` | ||
|
||
## Invariant Culture Formatting | ||
|
||
The formatting system uses `CultureInfo.InvariantCulture` by default through the chained `TryFormat` overload implementation. This provides: | ||
|
||
- **Consistent results** across all environments and systems | ||
- **Predictable numeric formatting** with period (.) as decimal separator and comma (,) as thousands separator | ||
- **Standard date formatting** using English month names and formats | ||
- **No localization variations** regardless of system locale | ||
|
||
```csharp | ||
// All environments produce the same output: | ||
// {CommitsSinceVersionSource:N0} → "1,234" | ||
// {CommitDate:MMM dd, yyyy} → "Mar 15, 2024" | ||
// {Major:C} → "¤1.00" (generic currency symbol) | ||
``` | ||
|
||
This ensures that version strings generated by GitVersion are consistent across different build environments, developer machines, and CI/CD systems. | ||
|
||
## Verified Examples | ||
|
||
The following examples are verified by actual unit tests in the GitVersion codebase: | ||
|
||
### Zero-Padded Numeric Formatting | ||
|
||
```yaml | ||
assembly-informational-format: "{Major}.{Minor}.{Patch}-{CommitsSinceVersionSource:0000}" | ||
``` | ||
|
||
**Test**: `VariableProviderTests.Format_Allows_CSharp_FormatStrings()` | ||
**Input**: `CommitsSinceVersionSource = 42` | ||
**Output**: `"1.2.3-0042"` | ||
|
||
### String Case Transformations | ||
|
||
```csharp | ||
// From StringFormatterTests.cs | ||
[TestCase("hello world", "c", "HelloWorld")] // PascalCase | ||
[TestCase("hello", "u", "HELLO")] // Uppercase | ||
[TestCase("HELLO", "l", "hello")] // Lowercase | ||
[TestCase("hello world", "t", "Hello World")] // Title Case | ||
[TestCase("hELLO", "s", "Hello")] // Sentence Case | ||
``` | ||
|
||
### Numeric Format Specifiers | ||
|
||
```csharp | ||
// From NumericFormatterTests.cs | ||
[TestCase("1234.5678", "n", "1,234.57")] // Number format | ||
[TestCase("1234.5678", "f2", "1234.57")] // Fixed-point format | ||
[TestCase("1234.5678", "f0", "1235")] // No decimals | ||
``` | ||
|
||
### Date Formatting | ||
|
||
```csharp | ||
// From DateFormatterTests.cs | ||
[TestCase("2021-01-01", "yyyy-MM-dd", "2021-01-01")] | ||
[TestCase("2021-01-01T12:00:00Z", "yyyy-MM-ddTHH:mm:ssZ", "2021-01-01T12:00:00Z")] | ||
``` | ||
|
||
### Currency and Percentage (InvariantCulture) | ||
|
||
```csharp | ||
// From FormattableFormatterTests.cs | ||
[TestCase(123.456, "C", "¤123.46")] // Generic currency symbol | ||
[TestCase(123.456, "P", "12,345.60 %")] // Percentage format | ||
[TestCase(1234567890, "N0", "1,234,567,890")] // Thousands separators | ||
``` | ||
|
||
[reference-configuration]: /docs/reference/configuration | ||
[variables]: /docs/reference/variables |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
src/GitVersion.Core.Tests/Extensions/ShouldlyExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
namespace GitVersion.Core.Tests.Extensions; | ||
|
||
public static class ShouldlyExtensions | ||
{ | ||
/// <summary> | ||
/// Asserts that the action throws an exception of type TException | ||
/// with the expected message. | ||
/// </summary> | ||
public static void ShouldThrowWithMessage<TException>(this Action action, string expectedMessage) where TException : Exception | ||
{ | ||
var ex = Should.Throw<TException>(action); | ||
ex.Message.ShouldBe(expectedMessage); | ||
} | ||
|
||
/// <summary> | ||
/// Asserts that the action throws an exception of type TException, | ||
/// and allows further assertion on the exception instance. | ||
/// </summary> | ||
public static void ShouldThrow<TException>(this Action action, Action<TException> additionalAssertions) where TException : Exception | ||
{ | ||
var ex = Should.Throw<TException>(action); | ||
additionalAssertions(ex); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to advertise code coverage in our documentation. If the test cases contain essential information, we should provide them in a more human-friendly format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@9swampy I left only this change on you, all the other requested changes were addressed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's the worst criticism you had on "my" AI generated ramblings, that's a win. Did you check the links are good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@9swampy please fix the docs and then I guess we can merge