|
3 | 3 | namespace Dotenv;
|
4 | 4 |
|
5 | 5 | use Dotenv\Exception\InvalidFileException;
|
| 6 | +use Dotenv\Result\Error; |
| 7 | +use Dotenv\Result\Success; |
6 | 8 |
|
7 | 9 | class Parser
|
8 | 10 | {
|
9 | 11 | const INITIAL_STATE = 0;
|
10 | 12 | const UNQUOTED_STATE = 1;
|
11 |
| - const QUOTED_STATE = 2; |
12 |
| - const ESCAPE_STATE = 3; |
13 |
| - const WHITESPACE_STATE = 4; |
14 |
| - const COMMENT_STATE = 5; |
| 13 | + const SINGLE_QUOTED_STATE = 2; |
| 14 | + const DOUBLE_QUOTED_STATE = 3; |
| 15 | + const ESCAPE_SEQUENCE_STATE = 4; |
| 16 | + const WHITESPACE_STATE = 5; |
| 17 | + const COMMENT_STATE = 6; |
15 | 18 |
|
16 | 19 | /**
|
17 | 20 | * Parse the given environment variable entry into a name and value.
|
@@ -97,64 +100,99 @@ private static function isValidName($name)
|
97 | 100 | *
|
98 | 101 | * @throws \Dotenv\Exception\InvalidFileException
|
99 | 102 | *
|
100 |
| - * @return string|null |
| 103 | + * @return \Dotenv\Value|null |
101 | 104 | */
|
102 | 105 | private static function parseValue($value)
|
103 | 106 | {
|
104 |
| - if ($value === null || trim($value) === '') { |
105 |
| - return $value; |
| 107 | + if ($value === null) { |
| 108 | + return null; |
| 109 | + } |
| 110 | + |
| 111 | + if (trim($value) === '') { |
| 112 | + return Value::blank(); |
106 | 113 | }
|
107 | 114 |
|
108 | 115 | return array_reduce(str_split($value), function ($data, $char) use ($value) {
|
109 |
| - switch ($data[1]) { |
110 |
| - case self::INITIAL_STATE: |
111 |
| - if ($char === '"' || $char === '\'') { |
112 |
| - return [$data[0], self::QUOTED_STATE]; |
113 |
| - } elseif ($char === '#') { |
114 |
| - return [$data[0], self::COMMENT_STATE]; |
115 |
| - } else { |
116 |
| - return [$data[0].$char, self::UNQUOTED_STATE]; |
117 |
| - } |
118 |
| - case self::UNQUOTED_STATE: |
119 |
| - if ($char === '#') { |
120 |
| - return [$data[0], self::COMMENT_STATE]; |
121 |
| - } elseif (ctype_space($char)) { |
122 |
| - return [$data[0], self::WHITESPACE_STATE]; |
123 |
| - } else { |
124 |
| - return [$data[0].$char, self::UNQUOTED_STATE]; |
125 |
| - } |
126 |
| - case self::QUOTED_STATE: |
127 |
| - if ($char === $value[0]) { |
128 |
| - return [$data[0], self::WHITESPACE_STATE]; |
129 |
| - } elseif ($char === '\\') { |
130 |
| - return [$data[0], self::ESCAPE_STATE]; |
131 |
| - } else { |
132 |
| - return [$data[0].$char, self::QUOTED_STATE]; |
133 |
| - } |
134 |
| - case self::ESCAPE_STATE: |
135 |
| - if ($char === $value[0] || $char === '\\') { |
136 |
| - return [$data[0].$char, self::QUOTED_STATE]; |
137 |
| - } elseif (in_array($char, ['f', 'n', 'r', 't', 'v'], true)) { |
138 |
| - return [$data[0].stripcslashes('\\' . $char), self::QUOTED_STATE]; |
139 |
| - } else { |
140 |
| - throw new InvalidFileException( |
141 |
| - self::getErrorMessage('an unexpected escape sequence', $value) |
142 |
| - ); |
143 |
| - } |
144 |
| - case self::WHITESPACE_STATE: |
145 |
| - if ($char === '#') { |
146 |
| - return [$data[0], self::COMMENT_STATE]; |
147 |
| - } elseif (!ctype_space($char)) { |
148 |
| - throw new InvalidFileException( |
149 |
| - self::getErrorMessage('unexpected whitespace', $value) |
150 |
| - ); |
151 |
| - } else { |
152 |
| - return [$data[0], self::WHITESPACE_STATE]; |
153 |
| - } |
154 |
| - case self::COMMENT_STATE: |
155 |
| - return [$data[0], self::COMMENT_STATE]; |
156 |
| - } |
157 |
| - }, ['', self::INITIAL_STATE])[0]; |
| 116 | + return self::processChar($data[1], $char)->mapError(function ($err) use ($value) { |
| 117 | + throw new InvalidFileException( |
| 118 | + self::getErrorMessage($err, $value) |
| 119 | + ); |
| 120 | + })->mapSuccess(function ($val) use ($data) { |
| 121 | + return [$data[0]->append($val[0], $val[1]), $val[2]]; |
| 122 | + })->getSuccess(); |
| 123 | + }, [Value::blank(), self::INITIAL_STATE])[0]; |
| 124 | + } |
| 125 | + |
| 126 | + /** |
| 127 | + * Process the given character. |
| 128 | + * |
| 129 | + * @param int $state |
| 130 | + * @param string $char |
| 131 | + * |
| 132 | + * @return array |
| 133 | + */ |
| 134 | + private static function processChar($state, $char) |
| 135 | + { |
| 136 | + switch ($state) { |
| 137 | + case self::INITIAL_STATE: |
| 138 | + if ($char === '\'') { |
| 139 | + return Success::create(['', false, self::SINGLE_QUOTED_STATE]); |
| 140 | + } elseif ($char === '"') { |
| 141 | + return Success::create(['', false, self::DOUBLE_QUOTED_STATE]); |
| 142 | + } elseif ($char === '#') { |
| 143 | + return Success::create(['', false, self::COMMENT_STATE]); |
| 144 | + } elseif ($char === '$') { |
| 145 | + return Success::create([$char, true, self::UNQUOTED_STATE]); |
| 146 | + } else { |
| 147 | + return Success::create([$char, false, self::UNQUOTED_STATE]); |
| 148 | + } |
| 149 | + case self::UNQUOTED_STATE: |
| 150 | + if ($char === '#') { |
| 151 | + return Success::create(['', false, self::COMMENT_STATE]); |
| 152 | + } elseif (ctype_space($char)) { |
| 153 | + return Success::create(['', false, self::WHITESPACE_STATE]); |
| 154 | + } elseif ($char === '$') { |
| 155 | + return Success::create([$char, true, self::UNQUOTED_STATE]); |
| 156 | + } else { |
| 157 | + return Success::create([$char, false, self::UNQUOTED_STATE]); |
| 158 | + } |
| 159 | + case self::SINGLE_QUOTED_STATE: |
| 160 | + if ($char === '\'') { |
| 161 | + return Success::create(['', false, self::WHITESPACE_STATE]); |
| 162 | + } else { |
| 163 | + return Success::create([$char, false, self::SINGLE_QUOTED_STATE]); |
| 164 | + } |
| 165 | + case self::DOUBLE_QUOTED_STATE: |
| 166 | + if ($char === '"') { |
| 167 | + return Success::create(['', false, self::WHITESPACE_STATE]); |
| 168 | + } elseif ($char === '\\') { |
| 169 | + return Success::create(['', false, self::ESCAPE_SEQUENCE_STATE]); |
| 170 | + } elseif ($char === '$') { |
| 171 | + return Success::create([$char, true, self::DOUBLE_QUOTED_STATE]); |
| 172 | + } else { |
| 173 | + return Success::create([$char, false, self::DOUBLE_QUOTED_STATE]); |
| 174 | + } |
| 175 | + case self::ESCAPE_SEQUENCE_STATE: |
| 176 | + if ($char === '"' || $char === '\\') { |
| 177 | + return Success::create([$char, false, self::DOUBLE_QUOTED_STATE]); |
| 178 | + } elseif ($char === '$') { |
| 179 | + return Success::create([$char, false, self::DOUBLE_QUOTED_STATE]); |
| 180 | + } elseif (in_array($char, ['f', 'n', 'r', 't', 'v'], true)) { |
| 181 | + return Success::create([stripcslashes('\\' . $char), false, self::DOUBLE_QUOTED_STATE]); |
| 182 | + } else { |
| 183 | + return Error::create('an unexpected escape sequence'); |
| 184 | + } |
| 185 | + case self::WHITESPACE_STATE: |
| 186 | + if ($char === '#') { |
| 187 | + return Success::create(['', false, self::COMMENT_STATE]); |
| 188 | + } elseif (!ctype_space($char)) { |
| 189 | + return Error::create('unexpected whitespace'); |
| 190 | + } else { |
| 191 | + return Success::create(['', false, self::WHITESPACE_STATE]); |
| 192 | + } |
| 193 | + case self::COMMENT_STATE: |
| 194 | + return Success::create(['', false, self::COMMENT_STATE]); |
| 195 | + } |
158 | 196 | }
|
159 | 197 |
|
160 | 198 | /**
|
|
0 commit comments