Support for .env (WIP)

This commit is contained in:
Daniele Teti 2023-05-24 10:55:43 +02:00
parent bb30db152d
commit d892c21cc4
11 changed files with 871 additions and 5 deletions

View File

@ -0,0 +1,308 @@
// *************************************************************************** }
//
// Delphi MVC Framework
//
// Copyright (c) 2010-2023 Daniele Teti and the DMVCFramework Team
//
// https://github.com/danieleteti/delphimvcframework
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ***************************************************************************
unit MVCFramework.DotEnv.Parser;
interface
uses System.Generics.Collections, System.SysUtils;
type
{$SCOPEDENUMS ON}
TMVCDotEnvParserState = (FileThenEnv, EnvThenFile, OnlyFile, OnlyEnv);
TMVCDotEnvDictionary = class(TDictionary<String, String>)
public
constructor Create; virtual;
end;
EMVCDotEnvParser = class(Exception)
end;
TLineBreakStyle = (MSWindows, Linux { MacOS too } );
TStringQuotedStyle = (SingleQuoted, DoublyQuoted, UnQuoted);
{
For Windows, it is CRLF
For UNIX, it is LF
For MAC (up through version 9) it was CR
For MAC OS X, it is LF
https://en.wikipedia.org/wiki/Newline
}
{
https://pypi.org/project/python-dotenv/
}
TMVCDotEnvParser = class
private
fCode: string;
fCurrChar: Char;
fIndex: Integer;
fCurLine: Integer;
fLineBreakStyle: TLineBreakStyle;
fLineBreaksStyle: TLineBreakStyle;
fSavedIndex: Integer;
fCodeLength: Integer;
function MatchIdentifier(out Value: String): Boolean;
function MatchKey(out Token: String): Boolean;
function MatchValue(out Token: String): Boolean;
function MatchSymbol(const Symbol: Char): Boolean;
procedure Check(Value: Boolean; Error: String = '');
function MatchString(out Value: String): Boolean;
procedure EatLineBreaks;
procedure EatUpToLineBreak;
function NextChar: Char;
procedure EatSpaces;
function DetectLineBreakStyle(Code: String): TLineBreakStyle;
procedure MatchInLineComment;
public
constructor Create; virtual;
destructor Destroy; override;
procedure Parse(const EnvDictionay: TMVCDotEnvDictionary; const DotEnvCode: String);
end;
implementation
uses
System.IOUtils,
System.TypInfo,
System.Classes;
const
LINE_BREAKS: array [TLineBreakStyle.MSWindows .. TLineBreakStyle.Linux] of AnsiString = (#13#10, #10);
{ TMVCDotEnvParser }
procedure TMVCDotEnvParser.Check(Value: Boolean; Error: String);
begin
if not Value then
begin
raise EMVCDotEnvParser.CreateFmt('Error: %s - got "%s" at line: %d - index: %d',
[Error, fCurrChar, fCurLine, fIndex - fSavedIndex]);
end;
end;
constructor TMVCDotEnvParser.Create;
begin
inherited;
end;
destructor TMVCDotEnvParser.Destroy;
begin
inherited;
end;
function TMVCDotEnvParser.DetectLineBreakStyle(Code: String): TLineBreakStyle;
begin
if Code.Contains(String(LINE_BREAKS[TLineBreakStyle.MSWindows])) then
Exit(TLineBreakStyle.MSWindows);
if Code.Contains(String(LINE_BREAKS[TLineBreakStyle.Linux])) then
Exit(TLineBreakStyle.Linux);
Result := TLineBreakStyle.MSWindows; // just one line or empty file
end;
procedure TMVCDotEnvParser.EatLineBreaks;
begin
while CharInSet(fCode.Chars[fIndex], [#13, #10]) do
begin
NextChar;
if (fCurrChar = String(LINE_BREAKS[fLineBreakStyle])[1]) then
begin
Inc(fCurLine);
fSavedIndex := fIndex;
end;
end;
end;
procedure TMVCDotEnvParser.EatSpaces;
begin
while CharInSet(fCode.Chars[fIndex], [#32, #9]) do
begin
NextChar;
end;
end;
procedure TMVCDotEnvParser.EatUpToLineBreak;
begin
while not CharInSet(fCode.Chars[fIndex], [#13, #10]) do
begin
NextChar;
end;
end;
procedure TMVCDotEnvParser.MatchInLineComment;
begin
EatSpaces;
if MatchSymbol('#') then
begin
EatUpToLineBreak;
end;
end;
procedure TMVCDotEnvParser.Parse(const EnvDictionay: TMVCDotEnvDictionary; const DotEnvCode: String);
var
lKey: string;
lValue: string;
begin
fCode := DotEnvCode;
fCodeLength := Length(fCode);
fLineBreaksStyle := DetectLineBreakStyle(fCode);
fIndex := -1;
fCurLine := 0;
fSavedIndex := 0;
NextChar;
while fIndex < Length(DotEnvCode) do
begin
EatLineBreaks;
EatSpaces;
if MatchKey(lKey) then
begin
EatSpaces;
Check(MatchSymbol('='), 'Expected "="');
EatSpaces;
Check(MatchValue(lValue), 'Expected "Value"');
EnvDictionay.AddOrSetValue(lKey, lValue);
end
else if fCurrChar = #0 then
begin
Break;
end
else if CharInSet(fCurrChar, [';', '#']) then
begin
EatUpToLineBreak;
EatLineBreaks;
end
else
begin
raise EMVCDotEnvParser.CreateFmt('Unexpected char "%s" at %d', [fCurrChar, fIndex - fSavedIndex]);
end;
end;
end;
function TMVCDotEnvParser.MatchKey(out Token: String): Boolean;
var
lTmp: String;
begin
lTmp := '';
if MatchSymbol('''') then
begin
Check(MatchIdentifier(Token));
Check(MatchSymbol(''''));
Result := True;
end
else
begin
Result := MatchIdentifier(Token);
end;
end;
function TMVCDotEnvParser.MatchSymbol(const Symbol: Char): Boolean;
begin
Result := fCode.Chars[fIndex] = Symbol;
if Result then
begin
NextChar;
end;
end;
function TMVCDotEnvParser.MatchIdentifier(out Value: String): Boolean;
begin
Value := '';
while CharInSet(fCode.Chars[fIndex], ['0' .. '9', 'a' .. 'z', 'A' .. 'Z', '_', '.', ':', '$', '%']) do
begin
Value := Value + fCode.Chars[fIndex];
NextChar;
end;
Result := not Value.IsEmpty;
end;
function TMVCDotEnvParser.MatchString(out Value: String): Boolean;
procedure MatchUpToCharacterSingleLine(out Value: String; const Delimiter1: Char);
begin
while (fIndex < fCodeLength) and (fCode.Chars[fIndex] <> Delimiter1) and
(not CharInSet(fCode.Chars[fIndex], [#13, #10])) do
begin
Value := Value + fCode.Chars[fIndex];
NextChar;
end;
end;
procedure MatchUpToCharacterMultiLine(out Value: String; const Delimiter1: Char);
begin
while (fIndex < fCodeLength) and (fCode.Chars[fIndex] <> Delimiter1) and
(not CharInSet(fCode.Chars[fIndex], [#13, #10])) do
begin
Value := Value + fCode.Chars[fIndex];
NextChar;
end;
end;
begin
Value := '';
EatSpaces;
if MatchSymbol('"') then
begin
MatchUpToCharacterMultiLine(Value, '"');
Check(MatchSymbol('"'), 'Expected ''"''');
EatSpaces;
MatchInLineComment;
end
else if MatchSymbol('''') then
begin
MatchUpToCharacterMultiLine(Value, '''');
Check(MatchSymbol(''''), 'Expected ''''');
EatSpaces;
MatchInLineComment;
end
else
begin
MatchUpToCharacterSingleLine(Value, '#');
Value := Value.Trim;
end;
Result := not Value.IsEmpty;
end;
function TMVCDotEnvParser.MatchValue(out Token: String): Boolean;
begin
Result := MatchString(Token);
end;
function TMVCDotEnvParser.NextChar: Char;
begin
Inc(fIndex);
Result := fCode.Chars[fIndex];
fCurrChar := Result;
end;
{ TMVCDotEnvDictionary }
constructor TMVCDotEnvDictionary.Create;
begin
inherited Create;
end;
end.

View File

@ -0,0 +1,309 @@
// *************************************************************************** }
//
// Delphi MVC Framework
//
// Copyright (c) 2010-2023 Daniele Teti and the DMVCFramework Team
//
// https://github.com/danieleteti/delphimvcframework
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ***************************************************************************
unit MVCFramework.DotEnv;
interface
uses System.SysUtils, System.Generics.Collections, MVCFramework.DotEnv.Parser;
type
{$SCOPEDENUMS ON}
TMVCDotEnvPriority = (FileThenEnv, EnvThenFile, OnlyFile, OnlyEnv);
EMVCDotEnv = class(Exception)
end;
IMVCDotEnv = interface
['{5FD2C3CB-0895-4CCD-985F-27394798E4A8}']
function WithStrategy(const Strategy: TMVCDotEnvPriority = TMVCDotEnvPriority.EnvThenFile): IMVCDotEnv;
function UseProfile(const ProfileName: String): IMVCDotEnv;
function ClearProfiles: IMVCDotEnv;
function Build(const DotEnvPath: string = ''): IMVCDotEnv; overload;
function Env(const Name: string): string; overload;
function SaveToFile(const FileName: String): IMVCDotEnv;
function IsFrozen: Boolean;
end;
function GlobalDotEnv: IMVCDotEnv;
function NewDotEnv: IMVCDotEnv;
implementation
uses
System.IOUtils,
System.TypInfo,
System.Classes;
var
gDotEnv: IMVCDotEnv = nil;
{ TDotEnv }
type
TMVCDotEnv = class(TInterfacedObject, IMVCDotEnv)
strict private
fFrozen: Boolean;
fPriority: TMVCDotEnvPriority;
fEnvPath: string;
fEnvDict: TMVCDotEnvDictionary;
fProfiles: TList<String>;
procedure ReadEnvFile;
function GetDotEnvVar(const key: string): string;
function ExplodePlaceholders(const Value: string): string;
procedure PopulateDictionary(const EnvDict: TDictionary<string, string>; const EnvFilePath: String);
procedure CheckAlreadyBuilt;
procedure CheckNotBuilt;
procedure ExplodeReferences;
strict protected
function WithStrategy(const Priority: TMVCDotEnvPriority = TMVCDotEnvPriority.EnvThenFile): IMVCDotEnv; overload;
function UseProfile(const ProfileName: String): IMVCDotEnv;
function ClearProfiles: IMVCDotEnv;
function Build(const DotEnvDirectory: string = ''): IMVCDotEnv; overload;
function IsFrozen: Boolean;
function Env(const Name: string): string; overload;
function SaveToFile(const FileName: String): IMVCDotEnv;
public
constructor Create;
destructor Destroy; override;
end;
function TMVCDotEnv.GetDotEnvVar(const key: string): string;
begin
fEnvDict.TryGetValue(key, Result);
end;
function TMVCDotEnv.Env(const Name: string): string;
begin
CheckNotBuilt;
if fPriority in [TMVCDotEnvPriority.FileThenEnv, TMVCDotEnvPriority.OnlyFile] then
begin
Result := GetDotEnvVar(name);
if fPriority = TMVCDotEnvPriority.OnlyFile then
begin
// OnlyFile
Exit;
end;
// FileThenEnv
if Result.IsEmpty then
begin
Exit(ExplodePlaceholders(GetEnvironmentVariable(Name)));
end;
end
else if fPriority in [TMVCDotEnvPriority.EnvThenFile, TMVCDotEnvPriority.OnlyEnv] then
begin
Result := ExplodePlaceholders(GetEnvironmentVariable(Name));
if fPriority = TMVCDotEnvPriority.OnlyEnv then
begin
// OnlyEnv
Exit;
end;
// EnvThenFile
if Result.IsEmpty then
begin
Exit(GetDotEnvVar(Name));
end;
end
else
begin
raise Exception.Create('Unknown Strategy');
end;
end;
function TMVCDotEnv.UseProfile(const ProfileName: String): IMVCDotEnv;
begin
CheckAlreadyBuilt;
fProfiles.Add(ProfileName);
Result := Self;
end;
function TMVCDotEnv.WithStrategy(const Priority: TMVCDotEnvPriority): IMVCDotEnv;
begin
CheckAlreadyBuilt;
Result := Self;
fPriority := Priority;
end;
function TMVCDotEnv.Build(const DotEnvDirectory: string): IMVCDotEnv;
begin
CheckNotBuilt;
Result := Self;
fEnvPath := TDirectory.GetParent(GetModuleName(HInstance));
if not DotEnvDirectory.IsEmpty then
begin
fEnvPath := TPath.Combine(fEnvPath, DotEnvDirectory);
end;
fEnvDict.Clear;
ReadEnvFile;
ExplodeReferences;
fFrozen := True;
end;
procedure TMVCDotEnv.CheckAlreadyBuilt;
begin
if fFrozen then
begin
raise Exception.Create('DotEnv Engine Already Built');
end;
end;
procedure TMVCDotEnv.CheckNotBuilt;
begin
if fFrozen then
begin
raise EMVCDotEnv.Create('Build must be called before use the engine');
end;
end;
function TMVCDotEnv.ClearProfiles: IMVCDotEnv;
begin
CheckAlreadyBuilt;
fProfiles.Clear;
Result := Self;
end;
constructor TMVCDotEnv.Create;
begin
inherited;
fFrozen := False;
fProfiles := TList<String>.Create;
fEnvDict := TMVCDotEnvDictionary.Create;
fEnvPath := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
fPriority := TMVCDotEnvPriority.EnvThenFile;
end;
destructor TMVCDotEnv.Destroy;
begin
FreeAndNil(fEnvDict);
fProfiles.Free;
inherited;
end;
function TMVCDotEnv.IsFrozen: Boolean;
begin
Result := fFrozen;
end;
function TMVCDotEnv.ExplodePlaceholders(const Value: string): string;
var
lStartPos, lEndPos: Integer;
lKey, lValue: string;
begin
Result := Value;
while Result.IndexOf('${') > -1 do
begin
lStartPos := Result.IndexOf('${');
lEndPos := Result.IndexOf('}');
if (lEndPos = -1) or (lEndPos < lStartPos) then
begin
raise EMVCDotEnv.Create('Unclosed expansion (${...}) at: ' + Value);
end;
lKey := Result.Substring(lStartPos + 2, lEndPos - (lStartPos + 2));
lValue := Env(lKey);
Result := StringReplace(Result, '${' + lKey + '}', lValue, [rfReplaceAll]);
end;
end;
procedure TMVCDotEnv.ExplodeReferences;
var
lKey: String;
begin
for lKey in fEnvDict.Keys do
begin
fEnvDict.AddOrSetValue(lKey, ExplodePlaceholders(fEnvDict[lKey]));
end;
end;
function TMVCDotEnv.SaveToFile(const FileName: String): IMVCDotEnv;
var
lKeys: TArray<String>;
lKey: String;
lSL: TStringList;
begin
lKeys := fEnvDict.Keys.ToArray;
TArray.Sort<String>(lKeys);
lSL := TStringList.Create;
try
for lKey in lKeys do
begin
lSL.Values[lKey] := GetDotEnvVar(lKey);
end;
lSL.SaveToFile(FileName);
finally
lSL.Free;
end;
Result := Self;
end;
procedure TMVCDotEnv.PopulateDictionary(const EnvDict: TDictionary<string, string>; const EnvFilePath: String);
var
lDotEnvCode: string;
lParser: TMVCDotEnvParser;
begin
if not TFile.Exists(EnvFilePath) then
begin
Exit;
end;
lDotEnvCode := TFile.ReadAllText(EnvFilePath);
lParser := TMVCDotEnvParser.Create;
try
lParser.Parse(fEnvDict, lDotEnvCode);
finally
lParser.Free;
end;
end;
procedure TMVCDotEnv.ReadEnvFile;
var
lProfileEnvPath: string;
I: Integer;
begin
PopulateDictionary(fEnvDict, IncludeTrailingPathDelimiter(fEnvPath) + '.env');
for I := 0 to fProfiles.Count - 1 do
begin
lProfileEnvPath := TPath.Combine(fEnvPath, '.env') + '.' + fProfiles[I];
PopulateDictionary(fEnvDict, lProfileEnvPath);
end;
end;
function GlobalDotEnv: IMVCDotEnv;
begin
Result := gDotEnv;
end;
function NewDotEnv: IMVCDotEnv;
begin
Result := TMVCDotEnv.Create;
end;
initialization
gDotEnv := NewDotEnv;
end.

View File

@ -12,7 +12,6 @@ uses
DUnitX.TestFramework,
{$IFDEF CONSOLE_TESTRUNNER}
DUnitX.Loggers.Console,
DUnitX.Loggers.XML.NUnit,
{$ENDIF }
{$IFDEF TESTINSIGHT}
TestInsight.DUnitX,
@ -73,7 +72,9 @@ uses
EntitiesProcessors in 'EntitiesProcessors.pas',
MVCFramework.Nullables in '..\..\..\sources\MVCFramework.Nullables.pas',
IntfObjectPoolTestU in 'IntfObjectPoolTestU.pas',
ObjectPoolTestU in 'ObjectPoolTestU.pas';
ObjectPoolTestU in 'ObjectPoolTestU.pas',
MVCFramework.DotEnv.Parser in '..\..\..\sources\MVCFramework.DotEnv.Parser.pas',
MVCFramework.DotEnv in '..\..\..\sources\MVCFramework.DotEnv.pas';
{$R *.RES}

View File

@ -183,8 +183,7 @@
<PropertyGroup Condition="'$(Cfg_2_Win64)'!=''">
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
<Manifest_File>(None)</Manifest_File>
<AppDPIAwarenessMode>none</AppDPIAwarenessMode>
<DCC_DebugDCUs>false</DCC_DebugDCUs>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
@ -259,6 +258,8 @@
<DCCReference Include="..\..\..\sources\MVCFramework.Nullables.pas"/>
<DCCReference Include="IntfObjectPoolTestU.pas"/>
<DCCReference Include="ObjectPoolTestU.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.DotEnv.Parser.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.DotEnv.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
@ -342,6 +343,8 @@
<Source Name="MainSource">DMVCFrameworkTests.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k280.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp280.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k280.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp280.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>

View File

@ -290,6 +290,32 @@ type
procedure TestPutGet_Check_No_AV;
end;
[TestFixture]
TTestDotEnv = class(TObject)
public
[Test]
procedure TestWithoutProfiles;
[Test]
procedure TestWithDevProfile;
[Test]
procedure TestWithDevAndTestProfile;
end;
[TestFixture]
TTestDotEnvParser = class(TObject)
public
[Test]
procedure TestKeyValue;
[Test]
procedure TestKeyValueWithQuotedValues;
[Test]
procedure TestVarPlaceHolders;
[Test]
procedure TestInLineComments;
end;
implementation
{$WARN SYMBOL_DEPRECATED OFF}
@ -312,7 +338,8 @@ uses
{$ENDIF}
TestServerControllerU, System.Classes,
MVCFramework.DuckTyping, System.IOUtils, MVCFramework.SystemJSONUtils,
IdGlobal, System.TypInfo, System.Types, Winapi.Windows;
IdGlobal, System.TypInfo, System.Types, Winapi.Windows, MVCFramework.DotEnv,
MVCFramework.DotEnv.Parser;
var
JWT_SECRET_KEY_TEST: string = 'myk3y';
@ -2150,6 +2177,166 @@ begin
end;
end;
{ TTestDotEnv }
function Are2FilesEqual(const File1, File2: TFileName): Boolean;
var
ms1, ms2: TMemoryStream;
begin
Result := False;
ms1 := TMemoryStream.Create;
try
ms1.LoadFromFile(File1);
ms2 := TMemoryStream.Create;
try
ms2.LoadFromFile(File2);
if ms1.Size = ms2.Size then
begin
Result := CompareMem(ms1.Memory, ms2.memory, ms1.Size);
end;
finally
ms2.Free;
end;
finally
ms1.Free;
end
end;
procedure TTestDotEnv.TestWithDevAndTestProfile;
var
lDotEnv: IMVCDotEnv;
begin
lDotEnv := NewDotEnv.UseProfile('dev').UseProfile('test').Build('..\dotEnv');
lDotEnv.SaveToFile('..\dotEnv\dotEnvDump-profile-dev-and-test.test.txt');
Assert.IsTrue(Are2FilesEqual('..\dotEnv\dotEnvDump-profile-dev-and-test.correct.txt','..\dotEnv\dotEnvDump-profile-dev-and-test.test.txt'), 'Files are different');
end;
procedure TTestDotEnv.TestWithDevProfile;
var
lDotEnv: IMVCDotEnv;
begin
lDotEnv := NewDotEnv.UseProfile('dev').Build('..\dotEnv');
lDotEnv.SaveToFile('..\dotEnv\dotEnvDump-profile-dev.test.txt');
Assert.IsTrue(Are2FilesEqual('..\dotEnv\dotEnvDump-profile-dev.correct.txt','..\dotEnv\dotEnvDump-profile-dev.test.txt'), 'Files are different');
end;
procedure TTestDotEnv.TestWithoutProfiles;
var
lDotEnv: IMVCDotEnv;
begin
lDotEnv := NewDotEnv.Build('..\dotEnv');
lDotEnv.SaveToFile('..\dotEnv\dotEnvDump-noprofile.test.txt');
Assert.IsTrue(Are2FilesEqual('..\dotEnv\dotEnvDump-noprofile.correct.txt','..\dotEnv\dotEnvDump-noprofile.test.txt'), 'Files are different');
end;
{ TTestDotEnvParser }
procedure TTestDotEnvParser.TestInLineComments;
const
DOTENVCODE =
'#comment1' + sLineBreak +
'#comment2' + sLineBreak +
'key1= "value1" #inline comment' + sLineBreak +
';comment3' + sLineBreak +
'key2 = ''value2'' #inline comment' + sLineBreak +
';comment' + sLineBreak +
'key3 = value3 #inline comment' + sLineBreak +
'key4 = " value4 " #inline comment' + sLineBreak +
';commentX';
begin
var lParser := TMVCDotEnvParser.Create;
try
var lDict := TMVCDotEnvDictionary.Create();
try
lParser.Parse(lDict, DOTENVCODE);
Assert.AreEqual('value1', lDict['key1']);
Assert.AreEqual('value2', lDict['key2']);
Assert.AreEqual('value3', lDict['key3']);
Assert.AreEqual(' value4 ', lDict['key4']);
finally
lDict.Free;
end;
finally
lParser.Free;
end;
end;
procedure TTestDotEnvParser.TestKeyValue;
const
DOTENVCODE = 'key1=value1' + sLineBreak + 'key2 = value2 with another value' + sLineBreak;
begin
var lParser := TMVCDotEnvParser.Create;
try
var lDict := TMVCDotEnvDictionary.Create();
try
lParser.Parse(lDict, DOTENVCODE);
Assert.AreEqual('value1', lDict['key1']);
Assert.AreEqual('value2 with another value', lDict['key2']);
finally
lDict.Free;
end;
finally
lParser.Free;
end;
end;
procedure TTestDotEnvParser.TestKeyValueWithQuotedValues;
const
DOTENVCODE =
'key1= "value1"' + sLineBreak +
'key2 = ''value2''' + sLineBreak +
'key3 = "uno''due''"' + sLineBreak +
'key4 = ''uno"due"''' + sLineBreak;
begin
var lParser := TMVCDotEnvParser.Create;
try
var lDict := TMVCDotEnvDictionary.Create();
try
lParser.Parse(lDict, DOTENVCODE);
Assert.AreEqual('value1', lDict['key1']);
Assert.AreEqual('value2', lDict['key2']);
Assert.AreEqual('uno''due''', lDict['key3']);
Assert.AreEqual('uno"due"', lDict['key4']);
finally
lDict.Free;
end;
finally
lParser.Free;
end;
end;
procedure TTestDotEnvParser.TestVarPlaceHolders;
const
DOTENVCODE =
'#comment1' + sLineBreak +
'#comment2' + sLineBreak +
'key1= "value1"' + sLineBreak +
';comment3' + sLineBreak +
'key2 = ''value2''' + sLineBreak +
';comment' + sLineBreak +
'key3 = |${key1}|${key2}|' + sLineBreak +
'key4 = value4' + sLineBreak +
';commentX';
begin
var lParser := TMVCDotEnvParser.Create;
try
var lDict := TMVCDotEnvDictionary.Create();
try
lParser.Parse(lDict, DOTENVCODE);
Assert.AreEqual('value1', lDict['key1']);
Assert.AreEqual('value2', lDict['key2']);
Assert.AreEqual('|${key1}|${key2}|', lDict['key3']);
Assert.AreEqual('value4', lDict['key4']);
finally
lDict.Free;
end;
finally
lParser.Free;
end;
end;
initialization
TDUnitX.RegisterTestFixture(TTestRouting);
@ -2159,6 +2346,8 @@ TDUnitX.RegisterTestFixture(TTestMultiMap);
TDUnitX.RegisterTestFixture(TTestNameCase);
TDUnitX.RegisterTestFixture(TTestCryptUtils);
TDUnitX.RegisterTestFixture(TTestLRUCache);
TDUnitX.RegisterTestFixture(TTestDotEnv);
TDUnitX.RegisterTestFixture(TTestDotEnvParser);
finalization

View File

@ -0,0 +1,14 @@
key1=value1
key2= value2
key3 = value3 #incline comment
key4 = value4
;comment1
key2.1= "value2.1"
key2.2= 'value2.2'
key2.3 = 'value2.3'
key2.4 = '${key2.1}|${key2.2}|${key2.3}|${key7}'
#comment2
key5=${key6}
key6=${key3}
key7=${key6}

View File

@ -0,0 +1,5 @@
key1=devvalue1
key2= devvalue2
key3 = devvalue3 #incline comment
key7=${key2.1}
mode=dev

View File

@ -0,0 +1,2 @@
key1=testvalue1
mode=test

View File

@ -0,0 +1,11 @@
key1=value1
key2=value2
key2.1=value2.1
key2.2=value2.2
key2.3=value2.3
key2.4=value2.1|value2.2|value2.3|value3
key3=value3
key4=value4
key5=value3
key6=value3
key7=value3

View File

@ -0,0 +1,12 @@
key1=testvalue1
key2=devvalue2
key2.1=value2.1
key2.2=value2.2
key2.3=value2.3
key2.4=value2.1|value2.2|value2.3|value2.1
key3=devvalue3
key4=value4
key5=devvalue3
key6=devvalue3
key7=value2.1
mode=test

View File

@ -0,0 +1,12 @@
key1=devvalue1
key2=devvalue2
key2.1=value2.1
key2.2=value2.2
key2.3=value2.3
key2.4=value2.1|value2.2|value2.3|value2.1
key3=devvalue3
key4=value4
key5=devvalue3
key6=devvalue3
key7=value2.1
mode=dev