Skip to content
Merged
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
126 changes: 70 additions & 56 deletions src/dotenv.net/Parser.cs
Original file line number Diff line number Diff line change
@@ -1,75 +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 = {'"'};
internal static ReadOnlySpan<KeyValuePair<string, string>> Parse(ReadOnlySpan<string> dotEnvRows,
bool shouldTrimValue)
private const string SingleQuote = "'";
private const string DoubleQuotes = "\"";

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("=\""))
{
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.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(new[] { '=' }, 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 HasNoKey(this ReadOnlySpan<char> row, out int index)
{
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 bool IsQuoted(string value) => (value.StartsWith(SingleQuote) && value.EndsWith(SingleQuote))
|| (value.StartsWith(DoubleQuotes) && value.EndsWith(DoubleQuotes));

private static string Key(this ReadOnlySpan<char> row, int index)
private static string StripQuotes(string value)
{
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);
}
}
}
28 changes: 24 additions & 4 deletions 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 @@ -52,7 +53,7 @@ public void ConfigShouldLoadEnvWithTrimOptions()
EnvReader.GetStringValue("DB_DATABASE")
.Should()
.Be("laravel");

DotEnv.Fluent()
.WithEnvFiles(WhitespacesEnvFileName)
.WithoutTrimValues()
Expand All @@ -67,7 +68,7 @@ public void ConfigShouldLoadEnvWithTrimOptions()
public void ConfigShouldLoadEnvWithExistingVarOverwriteOptions()
{
Environment.SetEnvironmentVariable("Generic", "Existing");

DotEnv.Fluent()
.WithEnvFiles(GenericEnvFileName)
.WithoutOverwriteExistingVars()
Expand All @@ -76,7 +77,7 @@ public void ConfigShouldLoadEnvWithExistingVarOverwriteOptions()
EnvReader.GetStringValue("Generic")
.Should()
.Be("Existing");

DotEnv.Fluent()
.WithEnvFiles(GenericEnvFileName)
.WithOverwriteExistingVars()
Expand All @@ -97,7 +98,7 @@ public void ConfigShouldLoadDefaultEnvWithProbeOptions()

action.Should()
.ThrowExactly<FileNotFoundException>();

action = () => DotEnv.Fluent()
.WithProbeForEnv(5)
.WithExceptions()
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("doubler");
EnvReader.GetStringValue("DOUBLE_QUOTE_EVEN_MORE_LINES")
.Should()
.Be("doublest");
}

[Fact]
public void ConfigShouldLoadEnvWithInvalidEnvEntries()
{
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"