Skip to content
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

Adding support for kebab-case #252

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using EFCore.NamingConventions.Internal;
using Xunit;

Expand Down Expand Up @@ -33,6 +32,7 @@ public static IEnumerable<object[]> Convention()
NamingConvention.SnakeCase => new NamingConventionsOptionsExtension().WithSnakeCaseNamingConvention(),
NamingConvention.LowerCase => new NamingConventionsOptionsExtension().WithLowerCaseNamingConvention(),
NamingConvention.CamelCase => new NamingConventionsOptionsExtension().WithCamelCaseNamingConvention(),
NamingConvention.KebabCase => new NamingConventionsOptionsExtension().WithKebabCaseNamingConvention(),
NamingConvention.UpperCase => new NamingConventionsOptionsExtension().WithUpperCaseNamingConvention(),
NamingConvention.UpperSnakeCase => new NamingConventionsOptionsExtension().WithUpperSnakeCaseNamingConvention(),
_ => throw new ArgumentOutOfRangeException($"Unhandled enum value: {convention}, NamingConventionsOptionsExtension not defined for the test")
Expand Down
4 changes: 4 additions & 0 deletions EFCore.NamingConventions.Test/RewriterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ public void Camel_case()
[Fact]
public void Upper_case()
=> Assert.Equal("FULLNAME", new UpperCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName"));

[Fact]
public void Kebab_case()
=> Assert.Equal("full-name", new KebabCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName"));
}
70 changes: 70 additions & 0 deletions EFCore.NamingConventions/Internal/KebabCaseNameRewriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Globalization;
using System.Text;

namespace EFCore.NamingConventions.Internal
{
public class KebabCaseNameRewriter : INameRewriter
{
private readonly CultureInfo _culture;

public KebabCaseNameRewriter(CultureInfo cultureInfo) => _culture = cultureInfo;

public string RewriteName(string name)
{
var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5));
var previousCategory = default(UnicodeCategory?);

for (var currentIndex = 0; currentIndex < name.Length; currentIndex++)
{
var currentChar = name[currentIndex];

if (currentChar == '-')
{
builder.Append('-');
previousCategory = null;
continue;
}

var currentCategory = char.GetUnicodeCategory(currentChar);

switch (currentCategory)
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.TitlecaseLetter:
if (previousCategory == UnicodeCategory.SpaceSeparator ||
previousCategory == UnicodeCategory.LowercaseLetter ||
previousCategory != UnicodeCategory.DecimalDigitNumber &&
previousCategory != null &&
currentIndex > 0 &&
currentIndex + 1 < name.Length &&
char.IsLower(name[currentIndex + 1]))
{
builder.Append('-');
}

currentChar = char.ToLower(currentChar, _culture);
break;
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.DecimalDigitNumber:
if (previousCategory == UnicodeCategory.SpaceSeparator)
{
builder.Append('-');
}
break;
default:
if (previousCategory != null)
{
previousCategory = UnicodeCategory.SpaceSeparator;
}
continue;
}

builder.Append(currentChar);
previousCategory = currentCategory;
}

return builder.ToString();
}
}
}
1 change: 1 addition & 0 deletions EFCore.NamingConventions/Internal/NamingConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public enum NamingConvention
SnakeCase,
LowerCase,
CamelCase,
KebabCase,
UpperCase,
UpperSnakeCase
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using JetBrains.Annotations;

namespace EFCore.NamingConventions.Internal;

Expand All @@ -28,6 +27,7 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet)
NamingConvention.SnakeCase => new SnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
NamingConvention.LowerCase => new LowerCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
NamingConvention.CamelCase => new CamelCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
NamingConvention.KebabCase => new KebabCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
NamingConvention.UpperCase => new UpperCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
NamingConvention.UpperSnakeCase => new UpperSnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture),
_ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + namingStyle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class NamingConventionsOptionsExtension : IDbContextOptionsExtension
private NamingConvention _namingConvention;
private CultureInfo? _culture;

public NamingConventionsOptionsExtension() {}
public NamingConventionsOptionsExtension() { }
protected NamingConventionsOptionsExtension(NamingConventionsOptionsExtension copyFrom)
{
_namingConvention = copyFrom._namingConvention;
Expand Down Expand Up @@ -74,7 +74,15 @@ public virtual NamingConventionsOptionsExtension WithCamelCaseNamingConvention(C
return clone;
}

public void Validate(IDbContextOptions options) {}
public virtual NamingConventionsOptionsExtension WithKebabCaseNamingConvention(CultureInfo? culture = null)
{
var clone = Clone();
clone._namingConvention = NamingConvention.KebabCase;
clone._culture = culture;
return clone;
}

public void Validate(IDbContextOptions options) { }

public void ApplyServices(IServiceCollection services)
=> services.AddEntityFrameworkNamingConventions();
Expand All @@ -83,7 +91,7 @@ private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
private string? _logFragment;

public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension) {}
public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension) { }

private new NamingConventionsOptionsExtension Extension
=> (NamingConventionsOptionsExtension)base.Extension;
Expand All @@ -106,6 +114,7 @@ public override string LogFragment
NamingConvention.UpperCase => "using upper case naming",
NamingConvention.UpperSnakeCase => "using upper snake-case naming",
NamingConvention.CamelCase => "using camel-case naming",
NamingConvention.KebabCase => "using kebab-case naming",
_ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + Extension._namingConvention)
});

Expand Down
28 changes: 24 additions & 4 deletions EFCore.NamingConventions/NamingConventionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Globalization;
using Microsoft.EntityFrameworkCore.Infrastructure;
using JetBrains.Annotations;
using EFCore.NamingConventions.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
Expand All @@ -24,7 +23,7 @@ public static DbContextOptionsBuilder UseSnakeCaseNamingConvention(
}

public static DbContextOptionsBuilder<TContext> UseSnakeCaseNamingConvention<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder , CultureInfo? culture = null)
this DbContextOptionsBuilder<TContext> optionsBuilder, CultureInfo? culture = null)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);

Expand All @@ -47,7 +46,7 @@ public static DbContextOptionsBuilder<TContext> UseLowerCaseNamingConvention<TCo
this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture);
=> (DbContextOptionsBuilder<TContext>)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);

public static DbContextOptionsBuilder UseUpperCaseNamingConvention(
this DbContextOptionsBuilder optionsBuilder,
Expand Down Expand Up @@ -111,4 +110,25 @@ public static DbContextOptionsBuilder<TContext> UseCamelCaseNamingConvention<TCo
CultureInfo? culture = null)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseCamelCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);

public static DbContextOptionsBuilder UseKebabCaseNamingConvention(
this DbContextOptionsBuilder optionsBuilder,
CultureInfo? culture = null)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));

var extension = (optionsBuilder.Options.FindExtension<NamingConventionsOptionsExtension>()
?? new NamingConventionsOptionsExtension())
.WithKebabCaseNamingConvention(culture);

((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> UseKebabCaseNamingConvention<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
CultureInfo? culture = null)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseKebabCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture);
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ SELECT c.id, c.full_name
## Supported naming conventions

* UseSnakeCaseNamingConvention: `FullName` becomes `full_name`
* UseKebabCaseNamingConvention: `FullName` becomes `full-name`
* UseLowerCaseNamingConvention: `FullName` becomes `fullname`
* UseCamelCaseNamingConvention: `FullName` becomes `fullName`
* UseUpperCaseNamingConvention: `FullName` becomes `FULLNAME`
Expand Down