2023-05-24 10:55:43 +02:00
|
|
|
// *************************************************************************** }
|
|
|
|
//
|
|
|
|
// 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
|
2023-10-21 23:46:12 +02:00
|
|
|
raise EMVCDotEnvParser.CreateFmt('Error: %s - got "%s" at line: %d',
|
|
|
|
[Error, fCurrChar, fCurLine + 1]);
|
2023-05-24 10:55:43 +02:00
|
|
|
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
|
2023-06-16 14:58:17 +02:00
|
|
|
fCurrChar := NextChar;
|
2023-05-24 10:55:43 +02:00
|
|
|
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
|
2023-06-16 14:58:17 +02:00
|
|
|
while not CharInSet(fCode.Chars[fIndex], [#13, #10, #0]) do
|
2023-05-24 10:55:43 +02:00
|
|
|
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;
|
2023-06-06 17:34:24 +02:00
|
|
|
if fCodeLength = 0 then { empty .env file }
|
|
|
|
begin
|
|
|
|
Exit;
|
|
|
|
end;
|
2023-05-24 10:55:43 +02:00
|
|
|
NextChar;
|
|
|
|
while fIndex < Length(DotEnvCode) do
|
|
|
|
begin
|
|
|
|
EatLineBreaks;
|
|
|
|
EatSpaces;
|
|
|
|
if MatchKey(lKey) then
|
|
|
|
begin
|
|
|
|
EatSpaces;
|
|
|
|
Check(MatchSymbol('='), 'Expected "="');
|
|
|
|
EatSpaces;
|
2023-10-21 23:46:12 +02:00
|
|
|
MatchValue(lValue);
|
2023-05-24 10:55:43 +02:00
|
|
|
EnvDictionay.AddOrSetValue(lKey, lValue);
|
2023-06-16 14:58:17 +02:00
|
|
|
EatSpaces;
|
|
|
|
MatchInLineComment;
|
2023-05-24 10:55:43 +02:00
|
|
|
end
|
|
|
|
else if fCurrChar = #0 then
|
|
|
|
begin
|
|
|
|
Break;
|
|
|
|
end
|
|
|
|
else if CharInSet(fCurrChar, [';', '#']) then
|
|
|
|
begin
|
|
|
|
EatUpToLineBreak;
|
|
|
|
EatLineBreaks;
|
2023-10-21 23:46:12 +02:00
|
|
|
Inc(fCurLine);
|
2023-05-24 10:55:43 +02:00
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
2023-10-21 23:46:12 +02:00
|
|
|
raise EMVCDotEnvParser.CreateFmt('Unexpected char "%s" at line %d', [fCurrChar, fCurLine + 1]);
|
2023-05-24 10:55:43 +02:00
|
|
|
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;
|
2023-10-21 23:46:12 +02:00
|
|
|
const
|
|
|
|
FirstCharSet = ['a' .. 'z', 'A' .. 'Z', '_', '.'];
|
|
|
|
CharSet = ['0' .. '9'] + FirstCharSet;
|
2023-05-24 10:55:43 +02:00
|
|
|
begin
|
|
|
|
Value := '';
|
2023-10-21 23:46:12 +02:00
|
|
|
if CharInSet(fCode.Chars[fIndex], FirstCharSet) then
|
|
|
|
begin
|
|
|
|
Value := fCode.Chars[fIndex];
|
|
|
|
NextChar;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
Exit(False);
|
|
|
|
end;
|
|
|
|
|
|
|
|
while CharInSet(fCode.Chars[fIndex], CharSet) do
|
2023-05-24 10:55:43 +02:00
|
|
|
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
|
2023-06-16 14:58:17 +02:00
|
|
|
Check(fCode.Chars[fIndex] <> #0, 'Unexpected end of file');
|
2023-05-24 10:55:43 +02:00
|
|
|
Value := Value + fCode.Chars[fIndex];
|
|
|
|
NextChar;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure MatchUpToCharacterMultiLine(out Value: String; const Delimiter1: Char);
|
|
|
|
begin
|
2023-05-25 00:48:03 +02:00
|
|
|
while (fIndex < fCodeLength) and (fCode.Chars[fIndex] <> Delimiter1) do
|
2023-05-24 10:55:43 +02:00
|
|
|
begin
|
2023-06-16 14:58:17 +02:00
|
|
|
Check(fCode.Chars[fIndex] <> #0, 'Unexpected end of file');
|
2023-05-24 10:55:43 +02:00
|
|
|
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
|
2023-06-16 14:58:17 +02:00
|
|
|
if fIndex >= (fCodeLength - 1) then
|
|
|
|
begin
|
|
|
|
fIndex := fCodeLength;
|
|
|
|
Exit(#0);
|
|
|
|
end;
|
2023-05-24 10:55:43 +02:00
|
|
|
Inc(fIndex);
|
|
|
|
Result := fCode.Chars[fIndex];
|
|
|
|
fCurrChar := Result;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ TMVCDotEnvDictionary }
|
|
|
|
|
|
|
|
constructor TMVCDotEnvDictionary.Create;
|
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|