From 340b7afc4d384d3133c31169f7409578e9497cdc Mon Sep 17 00:00:00 2001 From: Pawel Jankowski Date: Sat, 30 Mar 2024 07:14:37 +0100 Subject: [PATCH] New argument added for customizing end-of-line characters in output files (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit (#1) * Initial commit * Names refactoring * Readme.md and license provided, fixed pipelines, usage examples, minor fixes, cleaning code * Update README.md * Transient vulnerabilities removed. Cleaning code. * Tool version updated in examples * Another enhancement to control end-of-line in created file * fix: End-of-lines in YAML serialization * Readme file updated --------- Co-authored-by: Paweł Jankowski --- README.md | 1 + .../.config/dotnet-tools.json | 2 +- .../ExampleWebAppMultipleEnvironments.csproj | 4 +- .../.config/dotnet-tools.json | 2 +- .../ExampleWebAppSimple.csproj | 2 +- .../AppServiceConfigurationGenerator.cs | 9 ++--- .../ConfigurationCollector.cs | 2 +- .../Repository/ConfigurationRepository.cs | 29 +++++++++++++-- .../Repository/JsonConfigurationRepository.cs | 4 ++ .../Repository/YamlConfigurationRepository.cs | 8 +++- src/Antyrama.Tools.Scribe.Core/ToolOptions.cs | 15 ++++++++ .../JsonConfigurationRepositoryTests.cs | 37 +++++++++++++++++++ 12 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 tests/UnitTests/Repository/JsonConfigurationRepositoryTests.cs diff --git a/README.md b/README.md index 84820c7..50c4fc6 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Tool is customisable, take a look at list of all arguments that can be passed. E | -y | --to-yaml | :heavy_check_mark: | Indicates whether configuration wrapped in YAML Azure DevOps variables file | `false` | | -f | --file-path-template | :heavy_check_mark: | File name template for output. Template may contain a placeholder for environment name. Example: `configuration.{0}.json` | `./configuration.json` | | -e | --environments | :heavy_check_mark: | A list of environment names. Separate configuration file will be created per each environment. Required when file name template contains placeholder | | +| -l | -eol | :heavy_check_mark: | Customize end-of-line characters, possible values: `Cr`, `CrLf` or `Lf`, when skipped, operating system default end-of-line characters will be used | | ### Configuration providers If you'd like to include all the settings collected by different configuration provider, add them all by `--providers` option. More information about [configuration providers](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers). diff --git a/examples/src/ExampleWebAppMultipleEnvironments/.config/dotnet-tools.json b/examples/src/ExampleWebAppMultipleEnvironments/.config/dotnet-tools.json index d51d993..16b8af1 100644 --- a/examples/src/ExampleWebAppMultipleEnvironments/.config/dotnet-tools.json +++ b/examples/src/ExampleWebAppMultipleEnvironments/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "antyrama.tools.scribe.cli": { - "version": "0.0.3-rc", + "version": "0.0.5", "commands": [ "app-settings-to-file" ] diff --git a/examples/src/ExampleWebAppMultipleEnvironments/ExampleWebAppMultipleEnvironments.csproj b/examples/src/ExampleWebAppMultipleEnvironments/ExampleWebAppMultipleEnvironments.csproj index ff33445..7274a18 100644 --- a/examples/src/ExampleWebAppMultipleEnvironments/ExampleWebAppMultipleEnvironments.csproj +++ b/examples/src/ExampleWebAppMultipleEnvironments/ExampleWebAppMultipleEnvironments.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -7,7 +7,7 @@ - + diff --git a/examples/src/ExampleWebAppSimple/.config/dotnet-tools.json b/examples/src/ExampleWebAppSimple/.config/dotnet-tools.json index d51d993..16b8af1 100644 --- a/examples/src/ExampleWebAppSimple/.config/dotnet-tools.json +++ b/examples/src/ExampleWebAppSimple/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "antyrama.tools.scribe.cli": { - "version": "0.0.3-rc", + "version": "0.0.5", "commands": [ "app-settings-to-file" ] diff --git a/examples/src/ExampleWebAppSimple/ExampleWebAppSimple.csproj b/examples/src/ExampleWebAppSimple/ExampleWebAppSimple.csproj index 897ed7a..e79672e 100644 --- a/examples/src/ExampleWebAppSimple/ExampleWebAppSimple.csproj +++ b/examples/src/ExampleWebAppSimple/ExampleWebAppSimple.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Antyrama.Tools.Scribe.Core/AppServiceConfigurationGenerator.cs b/src/Antyrama.Tools.Scribe.Core/AppServiceConfigurationGenerator.cs index 0003ff7..e6e1d5e 100644 --- a/src/Antyrama.Tools.Scribe.Core/AppServiceConfigurationGenerator.cs +++ b/src/Antyrama.Tools.Scribe.Core/AppServiceConfigurationGenerator.cs @@ -35,8 +35,8 @@ public void Generate() .ToDictionary(s => s.Key, s => s.Value); var repository = _options.WrapInYaml - ? (ConfigurationRepository)new YamlConfigurationRepository(_options.YamlVariableName) - : new JsonConfigurationRepository(); + ? (ConfigurationRepository)new YamlConfigurationRepository(_options.YamlVariableName, _options) + : new JsonConfigurationRepository(_options); foreach (var filename in GetConfigurationFiles(_options)) { @@ -48,7 +48,7 @@ public void Generate() } } - private IReadOnlyDictionary> Load(IConfigurationRepository repository, string filename) + private static IReadOnlyDictionary> Load(IConfigurationRepository repository, string filename) { try { @@ -62,7 +62,7 @@ private IReadOnlyDictionary> Load(IC } } - private void Save(IConfigurationRepository repository, string filename, IEnumerable> settings) + private static void Save(IConfigurationRepository repository, string filename, IEnumerable> settings) { using var stream = new FileStream(filename, FileMode.Create, FileAccess.Write); @@ -119,6 +119,5 @@ private IEnumerable GetConfigurationFiles(ToolInternalOptions options) } throw new InvalidOperationException("File path template must contain '{0}' as environment placeholder when environments specified."); - } } diff --git a/src/Antyrama.Tools.Scribe.Core/ConfigurationCollector.cs b/src/Antyrama.Tools.Scribe.Core/ConfigurationCollector.cs index c5fa388..2af8a1d 100644 --- a/src/Antyrama.Tools.Scribe.Core/ConfigurationCollector.cs +++ b/src/Antyrama.Tools.Scribe.Core/ConfigurationCollector.cs @@ -25,7 +25,7 @@ public IReadOnlyList> Collect(string pathSeparator) return settings; } - private void Collect(IConfigurationSection section, ICollection> settings, + private static void Collect(IConfigurationSection section, ICollection> settings, string pathSeparator) { if (section == null || string.IsNullOrEmpty(section.Key)) diff --git a/src/Antyrama.Tools.Scribe.Core/Repository/ConfigurationRepository.cs b/src/Antyrama.Tools.Scribe.Core/Repository/ConfigurationRepository.cs index 4fc1842..5781ce8 100644 --- a/src/Antyrama.Tools.Scribe.Core/Repository/ConfigurationRepository.cs +++ b/src/Antyrama.Tools.Scribe.Core/Repository/ConfigurationRepository.cs @@ -15,19 +15,26 @@ internal interface IConfigurationRepository internal abstract class ConfigurationRepository : IConfigurationRepository { + protected readonly string Eol; + + protected ConfigurationRepository(ToolInternalOptions options) + { + Eol = ResolveEndOfLine(options); + } + public abstract IReadOnlyDictionary[] Load(Stream stream); public abstract void Save(Stream stream, IEnumerable> settings); - protected static string Serialize(IEnumerable> settings) + protected string Serialize(IEnumerable> settings) { var serialized = settings.Select(setting => $" {JsonConvert.SerializeObject(setting)}"); - var separator = $",{Environment.NewLine}"; + var separator = $",{Eol}"; var formatted = string.Join(separator, serialized) .BeautifyJson(); - return $"[{Environment.NewLine}{formatted}{Environment.NewLine}]"; + return $"[{Eol}{formatted}{Eol}]"; } protected static IReadOnlyDictionary[] Deserialize(string settings) @@ -43,4 +50,20 @@ protected static IReadOnlyDictionary[] Deserialize(string settin return Array.Empty>(); } } + + private static string ResolveEndOfLine(ToolInternalOptions options) + { + return options.Eol switch + { + EndOfLine.Cr => Cr, + EndOfLine.CrLf => CrLf, + EndOfLine.Lf => Lf, + null => Environment.NewLine, + _ => throw new ArgumentOutOfRangeException(nameof(options)) + }; + } + + private const string Cr = "\r"; + private const string CrLf = "\r\n"; + private const string Lf = "\n"; } diff --git a/src/Antyrama.Tools.Scribe.Core/Repository/JsonConfigurationRepository.cs b/src/Antyrama.Tools.Scribe.Core/Repository/JsonConfigurationRepository.cs index 1b13ce5..194dcae 100644 --- a/src/Antyrama.Tools.Scribe.Core/Repository/JsonConfigurationRepository.cs +++ b/src/Antyrama.Tools.Scribe.Core/Repository/JsonConfigurationRepository.cs @@ -6,6 +6,10 @@ namespace Antyrama.Tools.Scribe.Core.Repository; internal class JsonConfigurationRepository : ConfigurationRepository { + public JsonConfigurationRepository(ToolInternalOptions options) : base(options) + { + } + public override IReadOnlyDictionary[] Load(Stream stream) { try diff --git a/src/Antyrama.Tools.Scribe.Core/Repository/YamlConfigurationRepository.cs b/src/Antyrama.Tools.Scribe.Core/Repository/YamlConfigurationRepository.cs index 896b7db..0dc4dbb 100644 --- a/src/Antyrama.Tools.Scribe.Core/Repository/YamlConfigurationRepository.cs +++ b/src/Antyrama.Tools.Scribe.Core/Repository/YamlConfigurationRepository.cs @@ -12,7 +12,8 @@ internal class YamlConfigurationRepository : ConfigurationRepository private readonly Serializer _serializer; private readonly Deserializer _deserializer; - public YamlConfigurationRepository(string variableName) + public YamlConfigurationRepository(string variableName, ToolInternalOptions options) + : base(options) { _serializer = new SerializerBuilder() .WithAttributeOverride(variables => variables.AppConfig, @@ -55,8 +56,11 @@ public override void Save(Stream stream, IEnumerable EnvironmentsList { get; set; } + + [Option('l', "eol", Required = false, + HelpText = "End-of-line delimiter, possible values: Cr, CrLf or Lf, by default operating system end-of-line will be used.")] + public EndOfLine? Eol { get; set; } } [Verb("_generate", Hidden = true)] @@ -75,4 +79,15 @@ public class ToolInternalOptions [Option('e', "environments", Required = false, Default = new string[0], HelpText = "A list of environment names. Separate configuration file will be created per each environment. Required when file name template contains placeholder.")] public IEnumerable Environments { get; set; } + + [Option('l', "eol", Required = false, + HelpText = "End-of-line delimiter, possible values: Cr, CrLf or Lf, by default operating system end-of-line will be used.")] + public EndOfLine? Eol { get; set; } +} + +public enum EndOfLine +{ + Cr, + CrLf, + Lf } diff --git a/tests/UnitTests/Repository/JsonConfigurationRepositoryTests.cs b/tests/UnitTests/Repository/JsonConfigurationRepositoryTests.cs new file mode 100644 index 0000000..281b017 --- /dev/null +++ b/tests/UnitTests/Repository/JsonConfigurationRepositoryTests.cs @@ -0,0 +1,37 @@ +using System.Text; +using Antyrama.Tools.Scribe.Core; +using Antyrama.Tools.Scribe.Core.Repository; +using FluentAssertions; + +namespace UnitTests.Repository; + +public sealed class JsonConfigurationRepositoryTests +{ + [Theory] + [InlineData(EndOfLine.CrLf, "\r\n")] + [InlineData(EndOfLine.Cr, "\r")] + [InlineData(EndOfLine.Lf, "\n")] + public void ShouldWriteAndReadConfigFileWithAllPossibleEndOfLines(EndOfLine? eol, string eolChars) + { + // arrange + var options = new ToolInternalOptions { Eol = eol }; + var sut = new JsonConfigurationRepository(options); + + using var stream = new MemoryStream(); + + // act + sut.Save(stream, + new IReadOnlyDictionary[] + { + new Dictionary { { "key1", "value1" }, { "key2", "value2" } } + }); + + // assert + stream.Seek(0, SeekOrigin.Begin); + + using var reader = new StreamReader(stream, Encoding.UTF8); + var result = reader.ReadToEnd(); + + result.Should().Contain(eolChars); + } +}