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

Feature: Added multi-line parsing via double quotes & Fix: Pipelines #52

Merged
merged 37 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c2246df
Enabled multi-line parsing of env files
VijoPlays Mar 22, 2024
c294fcf
Merged master
VijoPlays May 31, 2024
14f7b26
.net 8 changes
VijoPlays May 31, 2024
5d8d36a
Expanded README with multi-line usage
VijoPlays May 31, 2024
7e0d967
Testing pipelines fix
VijoPlays Jun 5, 2024
56e7868
Testing pipelines fix
VijoPlays Jun 5, 2024
208a08e
Dotnet versions
VijoPlays Jun 5, 2024
06a8687
Pipeline: Test fix
VijoPlays Jun 5, 2024
4d69544
Pipeline: Test fix
VijoPlays Jun 5, 2024
5b2bad9
Pipeline: Test fix
VijoPlays Jun 5, 2024
dfab9ad
Pipeline: Test fix
VijoPlays Jun 5, 2024
8460529
Pipeline: Test fix
VijoPlays Jun 5, 2024
56eb55e
Pipeline: Test fix
VijoPlays Jun 5, 2024
0d8e2dc
Pipeline: Test fix
VijoPlays Jun 5, 2024
c6622ca
Pipeline: Test fix
VijoPlays Jun 5, 2024
e6f3a50
Pipeline: Test fix
VijoPlays Jun 5, 2024
ed6028e
Pipeline: Test fix
VijoPlays Jun 5, 2024
7be0f92
Pipeline: Debugging
VijoPlays Jun 5, 2024
96014a2
Pipeline: Debugging
VijoPlays Jun 5, 2024
9dfb2fd
Pipeline: Debugging
VijoPlays Jun 5, 2024
e5624d4
Pipeline: Debugging
VijoPlays Jun 5, 2024
1acb1a2
Pipeline: Debugging
VijoPlays Jun 5, 2024
0e6e2e3
Pipeline: Debugging
VijoPlays Jun 5, 2024
9b508bf
Pipeline: Debugging
VijoPlays Jun 5, 2024
7fabb73
Pipeline: Debugging
VijoPlays Jun 5, 2024
8de254a
Pipeline: Debugging
VijoPlays Jun 5, 2024
8e70bca
Pipeline: Debugging
VijoPlays Jun 5, 2024
c8c5611
Pipeline: Debugging
VijoPlays Jun 5, 2024
bd45389
Pipeline: Debugging
VijoPlays Jun 5, 2024
6d7b23f
Pipeline: Debugging
VijoPlays Jun 5, 2024
183379a
Pipeline: Debugging
VijoPlays Jun 5, 2024
d7fa8ad
Pipeline: Debugging
VijoPlays Jun 5, 2024
81a4ccb
Pipeline: Fixed
VijoPlays Jun 5, 2024
c0a756d
Merge pull request #2 from SamhammerAG/pipelines
VijoPlays Jun 5, 2024
d60016b
Review adjustments
VijoPlays Jun 13, 2024
d43a3a2
Merge branch 'master' of github.com:SamhammerAG/dotenv.net
VijoPlays Jun 13, 2024
3dc340c
Pipeline test fix
VijoPlays Jun 13, 2024
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
14 changes: 6 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
- uses: actions/checkout@v3

- name: Use .NET 8 SDK
uses: actions/setup-dotnet@v2
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0
dotnet-version: 8.0.x

- name: Restore dependencies
run: dotnet restore ./src/**/*.csproj
Expand All @@ -24,12 +24,10 @@ jobs:
run: dotnet build ./src/**/*.csproj -c Release --no-restore

- name: Run tests with code coverage
run: dotnet test ./src/**/*.csproj -c Release --no-restore \
--collect-coverage true --coverage-directory ./coverage \
--coverage-format lcov
run: dotnet test dotenv.net.sln /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=./coverage/

- name: Upload coverage to Coveralls
uses: coveralls-actions/coveralls-action@v1.3.1
uses: coverallsapp/github-action@v1.1.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
coveralls-file: ./coverage/coverage.info
github-token: ${{ github.token }}
path-to-lcov: ${{ github.workspace }}/tests/dotenv.net.Tests/coverage/coverage.info
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ var envVars = DotEnv.Read();
Console.WriteLine(envVars["KEY"]); // would print out whatever value was associated with the 'KEY'
```

### Defining env variables

Environment variables can be defined in various ways:

* \# I am comment
* hello=world
* SINGLE_QUOTES='single'
* DOUBLE_QUOTES="double"

Any variable starting with # will be ignored by the parser.

Any variable containing no quotes or single quotes will be read as a variable.

Any variable containing double quotes will be considered a multi-line variable, and can thus either be finished in the same line - or another line.

<br>

### Fluent API
Expand Down
122 changes: 70 additions & 52 deletions src/dotenv.net/Parser.cs
Original file line number Diff line number Diff line change
@@ -1,71 +1,89 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace dotenv.net;

internal static class Parser
{
private static readonly char[] SingleQuote = { '\'' };
private static readonly char[] DoubleQuotes = { '"' };
private const string SingleQuote = "'";
private const string DoubleQuotes = "\"";

internal static ReadOnlySpan<KeyValuePair<string, string>> Parse(ReadOnlySpan<string> dotEnvRows,
bool shouldTrimValue)
internal static ReadOnlySpan<KeyValuePair<string, string>> Parse(ReadOnlySpan<string> rawEnvRows,
bool trimValues)
{
var validEntries = new List<KeyValuePair<string, string>>();
var keyValuePairs = new List<KeyValuePair<string, string>>();

foreach (var dotEnvRow in dotEnvRows)
for (var i = 0; i < rawEnvRows.Length; i++)
{
var row = new ReadOnlySpan<char>(dotEnvRow.TrimStart().ToCharArray());

if (row.IsEmpty)
continue;

if (row.IsComment())
continue;

if (row.HasNoKey(out var index))
continue;

var key = row.Key(index);
var value = row.Value(index, shouldTrimValue);
validEntries.Add(new KeyValuePair<string, string>(key, value));
var rawEnvRow = rawEnvRows[i];

if(rawEnvRow.StartsWith("#")) continue;

if (rawEnvRow.Contains("=\""))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line will fail if the env is

DOUBLE_QUOTE_MULTI_LINE= "dou
bler"

I have merged the PR in but will have to introduce a fix

{
var key = rawEnvRow.Substring(0, rawEnvRow.IndexOf("=\"", StringComparison.Ordinal));
var valueStringBuilder = new StringBuilder();
valueStringBuilder.Append(rawEnvRow.Substring(rawEnvRow.IndexOf("=\"", StringComparison.Ordinal) + 2));

while (!rawEnvRow.EndsWith("\""))
{
i++;
if (i >= rawEnvRows.Length)
{
break;
}
rawEnvRow = rawEnvRows[i];
valueStringBuilder.AppendLine();
valueStringBuilder.Append(rawEnvRow);
}
//Remove last "
valueStringBuilder.Remove(valueStringBuilder.Length - 1, 1);

var value = valueStringBuilder.ToString();
if (trimValues)
{
value = value.Trim();
}

keyValuePairs.Add(new KeyValuePair<string, string>(key, value));
}
else
{
//Check that line is not empty
var rawEnvEmpty = rawEnvRow.Trim();
if(string.IsNullOrEmpty(rawEnvEmpty)) continue;

// Regular key-value pair
var keyValue = rawEnvRow.Split(['='], 2);

var key = keyValue[0].Trim();
var value = keyValue[1];

if(string.IsNullOrEmpty(key)) continue;

if (IsQuoted(value))
{
value = StripQuotes(value);
}

if (trimValues)
{
value = value.Trim();
}

keyValuePairs.Add(new KeyValuePair<string, string>(key, value));
}
}

return new ReadOnlySpan<KeyValuePair<string, string>>(validEntries.ToArray());
return keyValuePairs.ToArray();
}

private static bool IsComment(this ReadOnlySpan<char> row) => row[0] == '#';
private static bool IsQuoted(string value) => (value.StartsWith(SingleQuote) && value.EndsWith(SingleQuote))
|| (value.StartsWith(DoubleQuotes) && value.EndsWith(DoubleQuotes));

private static bool HasNoKey(this ReadOnlySpan<char> row, out int index)
private static string StripQuotes(string value)
{
index = row.IndexOf('=');
return index <= 0;
}

private static bool IsQuoted(this ReadOnlySpan<char> row) =>
(row.StartsWith(SingleQuote) && row.EndsWith(SingleQuote))
|| (row.StartsWith(DoubleQuotes) && row.EndsWith(DoubleQuotes));

private static ReadOnlySpan<char> StripQuotes(this ReadOnlySpan<char> row) => row.Trim('\'').Trim('\"');

private static string Key(this ReadOnlySpan<char> row, int index)
{
var untrimmedKey = row.Slice(0, index);
return untrimmedKey.Trim().ToString();
}

private static string Value(this ReadOnlySpan<char> row, int index, bool trimValue)
{
var value = row.Slice(index + 1);

// handle quoted values
if (value.IsQuoted())
value = value.StripQuotes();

// trim output if requested
if (trimValue)
value = value.Trim();

return value.ToString();
return value.Substring(1, value.Length - 2);
}
}
22 changes: 21 additions & 1 deletion tests/dotenv.net.Tests/DotEnv.Fluent.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class DotEnvFluentTests
private const string WhitespacesEnvFileName = "whitespaces.env";
private const string NonExistentEnvFileName = "non-existent.env";
private const string QuotationsEnvFileName = "quotations.env";
private const string MultiLinesEnvFileName = "multi-lines.env";
private const string AsciiEnvFileName = "ascii.env";
private const string GenericEnvFileName = "generic.env";
private const string IncompleteEnvFileName = "incomplete.env";
Expand Down Expand Up @@ -127,6 +128,25 @@ public void ConfigShouldLoadEnvWithQuotedValues()
.Be("single");
}

[Fact]
public void ConfigLoadsMultilineEnvs()
{
DotEnv.Fluent()
.WithEnvFiles(MultiLinesEnvFileName)
.WithTrimValues()
.Load();

EnvReader.GetStringValue("DOUBLE_QUOTE")
.Should()
.Be("double");
EnvReader.GetStringValue("DOUBLE_QUOTE_MULTI_LINE")
.Should()
.Be($"dou{Environment.NewLine}bler");
EnvReader.GetStringValue("DOUBLE_QUOTE_EVEN_MORE_LINES")
.Should()
.Be($"dou{Environment.NewLine}{Environment.NewLine}b{Environment.NewLine}{Environment.NewLine}lest");
}

[Fact]
public void ConfigShouldLoadEnvWithInvalidEnvEntries()
{
Expand All @@ -139,4 +159,4 @@ public void ConfigShouldLoadEnvWithInvalidEnvEntries()
.Should()
.BeFalse();
}
}
}
11 changes: 9 additions & 2 deletions tests/dotenv.net.Tests/dotenv.net.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<PackageVersion></PackageVersion>
<AssemblyVersion>3.1.0</AssemblyVersion>
Expand All @@ -9,7 +9,11 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="3.0.3">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down Expand Up @@ -40,6 +44,9 @@
<None Update="incomplete.env">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="multi-lines.env">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\dotenv.net\dotenv.net.csproj" />
Expand Down
8 changes: 8 additions & 0 deletions tests/dotenv.net.Tests/multi-lines.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DOUBLE_QUOTE="double"
DOUBLE_QUOTE_MULTI_LINE="dou
bler"
DOUBLE_QUOTE_EVEN_MORE_LINES="dou

b

lest"
Loading