- dotEnv parser allows empty values

- dotEnv names adhere to [a-z,A-Z,_,\.][0-9,a-z,A-Z,_,\.]*
- better dotEnv error reporting
This commit is contained in:
Daniele Teti 2023-10-21 23:46:12 +02:00
parent 83cf604f30
commit 72fd459537
6 changed files with 379 additions and 93 deletions

View File

@ -317,6 +317,7 @@ resourcestring
// 3 - controller class name
// 4 - middlewares
// 5 - jsonrpc registration code
// 6 - jsonrpc class unit
sWebModuleUnit =
'unit %0:s;' + sLineBreak +
'' + sLineBreak +

View File

@ -2,94 +2,270 @@ object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'dotEnv :: ShowCase'
ClientHeight = 442
ClientWidth = 824
ClientHeight = 644
ClientWidth = 1028
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
DesignSize = (
824
442)
OnShow = FormShow
TextHeight = 15
object Shape1: TShape
Left = 8
Top = 181
Width = 169
Height = 4
Brush.Color = clGray
end
object btnSimple: TButton
Left = 8
Top = 16
Width = 169
Height = 49
Caption = 'default ENV'
object PageControl1: TPageControl
AlignWithMargins = True
Left = 3
Top = 3
Width = 1022
Height = 638
ActivePage = TabSheet2
Align = alClient
TabOrder = 0
OnClick = btnSimpleClick
end
object mmVars: TMemo
Left = 183
Top = 16
Width = 633
Height = 418
Anchors = [akLeft, akTop, akRight, akBottom]
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Consolas'
Font.Style = []
ParentFont = False
TabOrder = 1
ExplicitWidth = 629
ExplicitHeight = 417
end
object btnTestEnv: TButton
Left = 8
Top = 71
Width = 169
Height = 49
Caption = 'Test ENV (default + test)'
TabOrder = 2
OnClick = btnTestEnvClick
end
object btnProdEnv: TButton
Left = 8
Top = 126
Width = 169
Height = 49
Caption = 'Prod ENV (default + prod)'
TabOrder = 3
OnClick = btnProdEnvClick
end
object btnSingleEnv: TButton
Left = 8
Top = 191
Width = 169
Height = 49
Caption = 'Single ENV without inheritance (only prod)'
TabOrder = 4
WordWrap = True
OnClick = btnSingleEnvClick
end
object btnRequireKeys: TButton
Left = 8
Top = 264
Width = 169
Height = 49
Caption = 'Require Keys (OK)'
TabOrder = 5
OnClick = btnRequireKeysClick
end
object btnRequireKeys2: TButton
Left = 8
Top = 319
Width = 169
Height = 49
Caption = 'Require Keys (FAIL)'
TabOrder = 6
OnClick = btnRequireKeys2Click
ExplicitWidth = 1018
ExplicitHeight = 637
object TabSheet1: TTabSheet
Caption = 'dotEnv Samples'
DesignSize = (
1014
608)
object btnSingleEnv: TButton
Left = 8
Top = 191
Width = 169
Height = 49
Caption = 'Single ENV without inheritance (only prod)'
TabOrder = 0
WordWrap = True
OnClick = btnSingleEnvClick
end
object btnRequireKeys: TButton
Left = 8
Top = 328
Width = 169
Height = 49
Caption = 'Require Keys (OK)'
TabOrder = 1
OnClick = btnRequireKeysClick
end
object btnRequireKeys2: TButton
Left = 8
Top = 383
Width = 169
Height = 49
Caption = 'Require Keys (FAIL)'
TabOrder = 2
OnClick = btnRequireKeys2Click
end
object btnProdEnv: TButton
Left = 8
Top = 126
Width = 169
Height = 49
Caption = 'Prod ENV (default + prod)'
TabOrder = 3
OnClick = btnProdEnvClick
end
object btnTestEnv: TButton
Left = 8
Top = 71
Width = 169
Height = 49
Caption = 'Test ENV (default + test)'
TabOrder = 4
OnClick = btnTestEnvClick
end
object btnSimple: TButton
Left = 8
Top = 16
Width = 169
Height = 49
Caption = 'default ENV'
TabOrder = 5
OnClick = btnSimpleClick
end
object mmVars: TMemo
Left = 183
Top = 18
Width = 825
Height = 587
Anchors = [akLeft, akTop, akRight, akBottom]
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Consolas'
Font.Style = []
ParentFont = False
TabOrder = 6
end
end
object TabSheet2: TTabSheet
Caption = 'dotEnv Playground'
ImageIndex = 1
object Splitter1: TSplitter
Left = 625
Top = 0
Height = 608
ExplicitLeft = 408
ExplicitTop = 168
ExplicitHeight = 100
end
object Panel1: TPanel
Left = 0
Top = 0
Width = 625
Height = 608
Align = alLeft
Caption = 'Panel1'
TabOrder = 0
object Label1: TLabel
AlignWithMargins = True
Left = 4
Top = 4
Width = 617
Height = 25
Align = alTop
Caption = '.env file contents'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -19
Font.Name = 'Segoe UI'
Font.Style = []
ParentFont = False
ExplicitWidth = 142
end
object memSrc: TMemo
AlignWithMargins = True
Left = 4
Top = 35
Width = 617
Height = 569
Align = alClient
Ctl3D = True
EditMargins.Auto = True
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Consolas'
Font.Style = []
Lines.Strings = (
'#############################################'
'# Sample .env file'
'# This is a sample file just to play around with the .env syntax' +
'.'
'# Feel free to change it and see how the variables are interpola' +
'ted'
'# and provided to your app at run-time'
'#############################################'
''
'# This is a comment'
''
'# Let'#39's configure a database connection'
'db_host = myserver'
'db_port = 5432'
'db_url = pg://${db_host}:${db_port}'
''
'## variable names can start with ". _ a-z" and can contains "0-9' +
' . _ a-z"'
''
'valid.varname = OK # this is valid'
'.also.valid = OK # this is valid'
'__also__valid__ = OK # this is valid'
'.123 = OK # this is valid'
'_.123 = OK # this is valid'
'# 3notvalid = KO # this is not valid (try to uncomment and see' +
' the error)'
''
'#############################################'
'# Logging configuration'
'#############################################'
''
'# loglevel can be: debug, normal (default), warning, error, exce' +
'ption'
'loglevel = error'
''
'dmvc.profiler.enabled = false'
'dmvc.profiler.warning_threshold = 3000'
''
'#############################################'
'# EMail configuration'
'#############################################'
''
'email.sender = peter.parker@bittime.com'
'email.smtpserver = mail.bittime.com'
'email.smtpserverport = 25'
''
'#############################################'
'# FireDAC configuration'
'#############################################'
''
'# enable or disable tracing'
'firedac.trace = true'
''
'# trace file name '
'firedac.trace_filename = firedaclog.log'
''
'# append or replace the trace file contents at each execution?'
'firedac.trace_file_append = false '
''
''
'')
ParentCtl3D = False
ParentFont = False
TabOrder = 0
WordWrap = False
OnChange = memSrcChange
end
end
object Panel2: TPanel
Left = 628
Top = 0
Width = 386
Height = 608
Align = alClient
Caption = 'Panel2'
TabOrder = 1
object Label2: TLabel
AlignWithMargins = True
Left = 4
Top = 4
Width = 378
Height = 25
Align = alTop
Caption = 'What'#39's application "sees"'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -19
Font.Name = 'Segoe UI'
Font.Style = []
ParentFont = False
ExplicitWidth = 211
end
object memDst: TMemo
AlignWithMargins = True
Left = 4
Top = 35
Width = 378
Height = 569
Align = alClient
Ctl3D = True
EditMargins.Auto = True
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Consolas'
Font.Style = []
ParentCtl3D = False
ParentFont = False
ReadOnly = True
TabOrder = 0
WordWrap = False
end
end
end
end
end

View File

@ -5,25 +5,37 @@ interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
MVCFramework.DotEnv;
MVCFramework.DotEnv, Vcl.ComCtrls, System.IOUtils;
type
TMainForm = class(TForm)
btnSimple: TButton;
mmVars: TMemo;
btnTestEnv: TButton;
btnProdEnv: TButton;
Shape1: TShape;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
btnSingleEnv: TButton;
btnRequireKeys: TButton;
btnRequireKeys2: TButton;
btnProdEnv: TButton;
btnTestEnv: TButton;
btnSimple: TButton;
mmVars: TMemo;
memDst: TMemo;
Splitter1: TSplitter;
memSrc: TMemo;
Panel1: TPanel;
Panel2: TPanel;
Label1: TLabel;
Label2: TLabel;
procedure btnSimpleClick(Sender: TObject);
procedure btnTestEnvClick(Sender: TObject);
procedure btnProdEnvClick(Sender: TObject);
procedure btnSingleEnvClick(Sender: TObject);
procedure btnRequireKeysClick(Sender: TObject);
procedure btnRequireKeys2Click(Sender: TObject);
procedure memSrcChange(Sender: TObject);
procedure FormShow(Sender: TObject);
private
procedure UpdatePlayGround;
procedure UpdateUI(dotEnv: IMVCDotEnv);
public
{ Public declarations }
@ -90,6 +102,43 @@ begin
UpdateUI(dotEnv);
end;
procedure TMainForm.FormShow(Sender: TObject);
begin
if DebugHook<>0 then
begin
ShowMessage('Please, run this sample without debugging!' + sLineBreak +
'It will raise exceptions manhy times while using the playground');
end;
UpdatePlayGround;
end;
procedure TMainForm.memSrcChange(Sender: TObject);
begin
UpdatePlayGround;
end;
procedure TMainForm.UpdatePlayGround;
begin
var lFileName := TPath.Combine(TPath.GetHomePath, '.env.playground');
memSrc.Lines.SaveToFile(lFileName);
try
var dotEnv := NewDotEnv
.WithStrategy(TMVCDotEnvPriority.OnlyFile)
.UseProfile('playground')
.Build(TPath.GetHomePath);
memDst.Clear;
memDst.Lines.AddStrings(dotEnv.ToArray);
memDst.Color := clWindow;
except
on E: Exception do
begin
memDst.Lines.Text := E.Message;
memDst.Color := clRed;
end;
end;
end;
procedure TMainForm.UpdateUI(dotEnv: IMVCDotEnv);
begin
Caption := 'dotEnv ShowCase :: MODE = ' + dotEnv.Env('mode');

View File

@ -101,8 +101,8 @@ 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]);
raise EMVCDotEnvParser.CreateFmt('Error: %s - got "%s" at line: %d',
[Error, fCurrChar, fCurLine + 1]);
end;
end;
@ -189,7 +189,7 @@ begin
EatSpaces;
Check(MatchSymbol('='), 'Expected "="');
EatSpaces;
Check(MatchValue(lValue), 'Expected "Value"');
MatchValue(lValue);
EnvDictionay.AddOrSetValue(lKey, lValue);
EatSpaces;
MatchInLineComment;
@ -202,10 +202,11 @@ begin
begin
EatUpToLineBreak;
EatLineBreaks;
Inc(fCurLine);
end
else
begin
raise EMVCDotEnvParser.CreateFmt('Unexpected char "%s" at %d', [fCurrChar, fIndex - fSavedIndex]);
raise EMVCDotEnvParser.CreateFmt('Unexpected char "%s" at line %d', [fCurrChar, fCurLine + 1]);
end;
end;
end;
@ -237,9 +238,22 @@ begin
end;
function TMVCDotEnvParser.MatchIdentifier(out Value: String): Boolean;
const
FirstCharSet = ['a' .. 'z', 'A' .. 'Z', '_', '.'];
CharSet = ['0' .. '9'] + FirstCharSet;
begin
Value := '';
while CharInSet(fCode.Chars[fIndex], ['0' .. '9', 'a' .. 'z', 'A' .. 'Z', '_', '.', ':', '$', '%']) do
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
begin
Value := Value + fCode.Chars[fIndex];
NextChar;

View File

@ -271,6 +271,10 @@ type
[Test]
procedure TestKeyValue;
[Test]
procedure TestWithBadNames;
[Test]
procedure TestWithEmptyValue;
[Test]
procedure TestKeyValueWithQuotedValues;
[Test]
procedure TestValueWithMultiline;
@ -2322,6 +2326,48 @@ begin
end;
end;
procedure TTestDotEnvParser.TestWithBadNames;
const
DOTENVCODE = 'key1=value1' + sLineBreak + '3key2 = 12';
begin
var lParser := TMVCDotEnvParser.Create;
try
var lDict := TMVCDotEnvDictionary.Create();
try
Assert.WillRaise(
procedure
begin
lParser.Parse(lDict, DOTENVCODE);
end,
EMVCDotEnvParser);
finally
lDict.Free;
end;
finally
lParser.Free;
end;
end;
procedure TTestDotEnvParser.TestWithEmptyValue;
const
DOTENVCODE = 'key1=value1' + sLineBreak + 'key2 = ' + sLineBreak + 'key3 = xyz ' + sLineBreak;
begin
var lParser := TMVCDotEnvParser.Create;
try
var lDict := TMVCDotEnvDictionary.Create();
try
lParser.Parse(lDict, DOTENVCODE);
Assert.AreEqual('value1', lDict['key1']);
Assert.AreEqual('', lDict['key2']);
Assert.AreEqual('xyz', lDict['key3']);
finally
lDict.Free;
end;
finally
lParser.Free;
end;
end;
initialization
TDUnitX.RegisterTestFixture(TTestRouting);

View File

@ -11,4 +11,4 @@ key2.4 = '${key2.1}|${key2.2}|${key2.3}|${key7}'
key5=${key6}
key6=${key3}
key7=${key6}
key8=