Cake: Support converting enums from string values

Created on 16 Oct 2017  路  27Comments  路  Source: cake-build/cake

Currently, when setting an Enum value on say a setting class, it is necessary to be explicit about it, i.e. if setting this: https://cakebuild.net/api/Cake.Common.Tools.SignTool/SignToolSignSettings/BE7882E3 you would need to do:

DigestAlgorithm = SignToolDigestAlgorithm.Sha256

If might be nice to be able to do something like this:

DigestAlgorithm = "sha256"

Where Cake would attempt to "find" an enum value that matched the string value, and if it can, set it appropriately.

@cake-build/cake-team what are your thoughts?

Feature

Most helpful comment

@ferventcoder Then you'd have to type DigestAlgorithm = "sha256".ToEnum<SignToolDigestAlgorithm>(), and the primary motivation for this issue was to not have to type SignToolDigestAlgorithm..

All 27 comments

Is it okay if I give my thoughts too?

I don't like this because it's not C#. I would prefer the effort be redirected into providing intellisense so you can type s2Tab after the = to autocomplete SignToolDigestAlgorithm.Sha256.

See also the csharplang discussion on permitting the target-typed DigestAlgorithm = Sha256: https://github.com/dotnet/csharplang/issues/354.

How would this implicit cast work?

Only way i could see this work was if we didn't use an enum but SignToolDigestAlgorithm was a class and SignToolDigestAlgorithm.Sha256 was a readonly property of that class and it had implicit validated conversion from string. would be binary breaking, but work on recompile.

Wonder if it's that beneficiary though.

So I want to be able to specify from an env var or value "Sha256" and have it autoconverted. This is how DropkicK worked with the env settings files and it was c# ;)

It's either a wrapper struct or class on every property, or it's preprocessing illegal C# syntax to make it legal before compilation and hoping there are no edge cases which subtly alter behavior of things like overload resolution in corners of C# we didn't think of.

@gep13 It would be nice, but I think I agree with @jnm2 here. Since we don't have access to the string class, we can't really add an implicit conversion and we would need to add some kind of wrapper class for this (not even sure how that would work).

I think it would be better to wait with something like this until it's supported in the language itself or rethink how we do enums (which would be a massive breaking change and open us up to new bugs etc).

Enum.Parse isn't an option here?

PowerShell also does this well. I'm pretty sure it's one line of code. But maybe I don't understand what you have going on that is different than C# when it comes to Enums.

I also do this in Chocolatey and other places, it's pretty standard.

@ferventcoder Then you'd have to type DigestAlgorithm = "sha256".ToEnum<SignToolDigestAlgorithm>(), and the primary motivation for this issue was to not have to type SignToolDigestAlgorithm..

It's either a wrapper struct or class on every property, or it's preprocessing illegal C# syntax to make it legal before compilation and hoping there are no edge cases which subtly alter behavior of things like overload resolution in corners of C# we didn't think of.

None of this. Pretty standard C#, here is an example in Choco's code base -
https://github.com/chocolatey/choco/blob/9c0a77d0cec04a38c2be7c8a74ab337dc1353ceb/src/chocolatey/infrastructure/licensing/LicenseValidation.cs#L66-L74

I still think intellisense is the way to go on the question of making typing easier.

On the question of converting strings to enums, which is a separate topic: sure, there are tons of ways that don't involve making this compile: new SignToolSettings { DigestAlgorithm = "sha256" }.
In C#, if you want to make this compile without changing the type of DigestAlgorithm to string and breaking lots of people, you have to write a wrapper struct and define custom implicit operators. Or, you could preprocess so that we aren't using C# anymore but a metalanguage which compiles down to C#.

@jnm2 I am not that great at explaining primary motivation here. There is a motivation well beyond not having to type out the enumeration type.

  1. Strings are simpler than enums.
  2. Converting to the enum automatically is a helper that allows for keeping things simple
  3. Other languages do this (PowerShell supports this, and C# supports enum parsing).
  4. Having to remember the type name (especially in non-intellisense settings) is a barrier to entry.

I know there are other reasons, but the idea of simplicity is really large and IMHO shouldn't be ignored. However, that said, there is apprehension to doing this, and I must admit I don't know what would make it difficult for Cake just yet (could be the compilation aspect).

Plus I'm pretty sure I can just do the enumeration parsing in my cake scripts, which is even better.

we would need to add some kind of wrapper class for this

@patriksvensson minus access to the string class, I think narrowing conversion operators could be a way. Although I don't think it would work for actual enum types.

~~~csharp
public static implicit operator SignToolDigestAlgorithm(string value)
{
var actualEnum = SignToolDigestAlgorithm.Unknown;
try
{
Enum.TryParse(value, true, out actualEnum);
}
catch (Exception)
{
actualEnum = SignToolDigestAlgorithm.Unknown;
}

           return actualEnum;
}

~~~

So I'm guessing this is likely a pipe dream currently as the build scripts get compiled.

Other languages do this

But that's the PowerShell way. PowerShell is a dynamic interpreted language. People use Cake because it's the C# way: strongly typed (and prone to needing intellisense 馃槂). If Cake stopped doing things the C# way, I would lose interest.

@jnm2 Actually your earlier answer was perfect and pointed me to exactly what I needed to do here:

~~~csharp

var CERT_ALGORITHM = EnvironmentVariable("CERT_ALGORITHM") ?? "Sha256";
// [..snip..]

Task("SignMSI")
.Does(() =>
{
var msiPath = BuildParameters.Paths.Directories.Build + "/ChocolateyGUI.msi";

  Sign(msiPath, new SignToolSignSettings {
      TimeStampUri = new Uri(CERT_TIMESTAMP_URL),
      CertPath = CERT_PATH,
      Password = CERT_PASSWORD,
      TimeStampDigestAlgorithm = (SignToolDigestAlgorithm)Enum.Parse(typeof(SignToolDigestAlgorithm), CERT_ALGORITHM, true)
  });

});
~~~

I can't close this issue myself. I think I was overthinking things a bit here, not realizing I already had exactly what I needed.

If you wanted you could also write a helper method and have EnvironmentVariableAs<SignToolDigestAlgorithm>("CERT_ALGORITHM", "Sha256"). :D

@ferventcoder @gep13 started the issue, so I'd expect him to be the one to decide to close it. 馃槃

I asked Gary to open the issue (on my behalf). 馃憤

Oh, my bad. That makes more sense now!

I had to run to a meeting and wanted to capture it as an issue while it was still fresh in our minds.

I wonder if one could write a cake add in - ToEnum(value, EnumType)

I think something like that or value.ToEnum<EnumType>() would make a great out-of-the-box Cake API.

@jnm2 we accept PR's </nudge>

@jnm2 @ferventcoder I am going to leave this issue open, as I think there is some value to be had from an additional alias/extension to cover this.

Was this page helpful?
0 / 5 - 0 ratings