Skip to content

Commit

Permalink
Refactored EnvFile & EnvLexer to support multi-line entries
Browse files Browse the repository at this point in the history
  • Loading branch information
jaxwilko committed Mar 1, 2023
1 parent 888053d commit 5d6a30d
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 76 deletions.
8 changes: 4 additions & 4 deletions src/Contracts/DataFileLexerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
interface DataFileLexerInterface
{
public const T_ENV = 'T_ENV';
public const T_QUOTED_ENV = 'T_QUOTED_ENV';
public const T_ENV_NO_VALUE = 'T_ENV_NO_VALUE';
public const T_VALUE = 'T_VALUE';
public const T_QUOTED_VALUE = 'T_QUOTED_VALUE';
public const T_WHITESPACE = 'T_WHITESPACE';
public const T_COMMENT = 'T_COMMENT';

/**
* Get the ast from array of src lines
*
* @param array<int, string> $src
* @param string $string
* @return array<int, array>
*/
public function parse(array $src): array;
public function parse(string $string): array;
}
75 changes: 45 additions & 30 deletions src/EnvFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* Class EnvFile
*/
class EnvFile implements DataFileInterface
class EnvFile extends DataFile implements DataFileInterface
{
/**
* Lines of env data
Expand Down Expand Up @@ -88,37 +88,50 @@ public function set($key, $value = null)
}

foreach ($this->ast as $index => $item) {
if (
!in_array($item['token'], [
$this->lexer::T_ENV,
$this->lexer::T_QUOTED_ENV,
$this->lexer::T_ENV_NO_VALUE
])
) {
// Skip all but keys
if ($item['token'] !== $this->lexer::T_ENV) {
continue;
}

if ($item['env']['key'] === $key) {
$this->ast[$index]['env']['value'] = $this->castValue($value);
if ($item['value'] === $key) {
if (
!isset($this->ast[$index + 1])
|| !in_array($this->ast[$index + 1]['token'], [$this->lexer::T_VALUE, $this->lexer::T_QUOTED_VALUE])
) {
throw new \Exception('jack go fix');
}

$this->ast[$index + 1]['value'] = $this->castValue($value);

// Reprocess the token type to ensure old casting rules are still applied
$this->ast[$index]['token'] = (
is_numeric($value)
|| is_bool($value)
|| is_null($value)
|| (is_string($value) && strpos($value, ' ') === false && $item['token'] === $this->lexer::T_ENV)
) ? $this->lexer::T_ENV : $this->lexer::T_QUOTED_ENV;
switch ($this->ast[$index + 1]['token']) {
case $this->lexer::T_VALUE:
if (
str_contains($this->ast[$index + 1]['value'], '"')
|| str_contains($this->ast[$index + 1]['value'], '\'')
) {
$this->ast[$index + 1]['token'] = $this->lexer::T_QUOTED_VALUE;
}
break;
case $this->lexer::T_QUOTED_VALUE:
if (is_null($value) || $value === true || $value === false) {
$this->ast[$index + 1]['token'] = $this->lexer::T_VALUE;
}
break;
}

return $this;
}
}

// We did not find the key in the AST, therefore we must create it
$this->ast[] = [
'token' => (is_numeric($value) || is_bool($value)) ? $this->lexer::T_ENV : $this->lexer::T_QUOTED_ENV,
'env' => [
'key' => $key,
'value' => $this->castValue($value)
]
'token' => $this->lexer::T_ENV,
'value' => $key
];
$this->ast[] = [
'token' => (is_numeric($value) || is_bool($value)) ? $this->lexer::T_VALUE : $this->lexer::T_QUOTED_VALUE,
'value' => $this->castValue($value)
];

// Add a new line
Expand Down Expand Up @@ -171,7 +184,7 @@ protected function parse(string $filePath): array
return [];
}

$contents = file($filePath);
$contents = file_get_contents($filePath);

return $this->lexer->parse($contents);
}
Expand All @@ -185,17 +198,19 @@ public function getVariables(): array
{
$env = [];

foreach ($this->ast as $item) {
if (
!in_array($item['token'], [
$this->lexer::T_ENV,
$this->lexer::T_QUOTED_ENV
])
) {
foreach ($this->ast as $index => $item) {
if ($item['token'] !== $this->lexer::T_ENV) {
continue;
}

if (!(
isset($this->ast[$index + 1])
&& in_array($this->ast[$index + 1]['token'], [$this->lexer::T_VALUE, $this->lexer::T_QUOTED_VALUE])
)) {
continue;
}

$env[$item['env']['key']] = trim($item['env']['value']);
$env[$item['value']] = trim($this->ast[$index + 1]['value']);
}

return $env;
Expand Down
71 changes: 34 additions & 37 deletions src/Parser/EnvLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,49 @@
class EnvLexer implements DataFileLexerInterface
{
protected $tokenMap = [
'/^([\w]*)="?(.+)["|$]/' => self::T_QUOTED_ENV,
'/^([\w]*)="?(.*)(?:"|$)/' => self::T_ENV,
'/^(\w+)/' => self::T_ENV_NO_VALUE,
'/^(\s+)/' => self::T_WHITESPACE,
'/(#[\w\s]).*/' => self::T_COMMENT,
'/^(\s+)/' => self::T_WHITESPACE,
'/^(#.*)/' => self::T_COMMENT,
'/^(\w+)/s' => self::T_ENV,
'/^="([^"\\\]*(?:\\\.[^"\\\]*)*)"/s' => self::T_QUOTED_VALUE,
'/^\=(.*)/' => self::T_VALUE,
];

public function parse(array $src): array
/**
* Parses an array of lines into an AST
*
* @param string $string
* @return array|array[]
* @throws EnvParserException
*/
public function parse(string $string): array
{
$tokens = [];
$offset = 0;
do {
$result = $this->match($string, $offset);

foreach ($src as $line => $str) {
$read = 0;
do {
$result = $this->match($str, $line, $read);

if (is_null($result)) {
throw new EnvParserException("Unable to parse line " . ($line + 1) . ".");
}
if (is_null($result)) {
throw new EnvParserException("Unable to parse file, failed at: " . $offset . ".");
}

$tokens[] = $result;
$tokens[] = $result;

$read += strlen($result['match']);
} while ($read < strlen($str));
}
$offset += strlen($result['match']);
} while ($offset < strlen($string));

return $tokens;
}

public function match(string $str, int $line, int $offset): ?array
/**
* Parse a string against our token map and return a node
*
* @param string $str
* @param int $offset
* @return array|null
*/
public function match(string $str, int $offset): ?array
{
$source = $str;
$str = substr($str, $offset);

foreach ($this->tokenMap as $pattern => $name) {
Expand All @@ -48,37 +60,22 @@ public function match(string $str, int $line, int $offset): ?array

switch ($name) {
case static::T_ENV:
case static::T_QUOTED_ENV:
return [
'match' => $matches[0],
'env' => [
'key' => $matches[1],
'value' => $matches[2],
],
'token' => $name,
'line' => $line + 1
];
case static::T_ENV_NO_VALUE:
case static::T_VALUE:
case static::T_QUOTED_VALUE:
return [
'match' => $matches[0],
'env' => [
'key' => $matches[1],
'value' => '',
],
'value' => $matches[1] ?? '',
'token' => $name,
'line' => $line + 1
];
case static::T_COMMENT:
return [
'match' => $matches[0],
'token' => $name,
'line' => $line + 1
];
case static::T_WHITESPACE:
return [
'match' => $matches[1],
'token' => $name,
'line' => $line + 1
];
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/Printer/EnvPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public function render(array $ast): string
foreach ($ast as $item) {
switch ($item['token']) {
case EnvLexer::T_ENV:
$output .= sprintf('%s=%s', $item['env']['key'], $item['env']['value']);
$output .= $item['value'];
break;
case EnvLexer::T_QUOTED_ENV:
$output .= sprintf('%s="%s"', $item['env']['key'], $item['env']['value']);
case EnvLexer::T_VALUE:
$output .= sprintf('=%s', $item['value']);
break;
case EnvLexer::T_ENV_NO_VALUE:
$output .= $item['env']['key'];
case EnvLexer::T_QUOTED_VALUE:
$output .= sprintf('="%s"', $item['value']);
break;
case EnvLexer::T_COMMENT:
case EnvLexer::T_WHITESPACE:
Expand Down

0 comments on commit 5d6a30d

Please sign in to comment.