adotenv/dialect.rs
1//! Explicit literal syntax configuration. Presets perform no expansion or I/O.
2
3/// The statement grammar used to recognize bindings.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum Syntax {
6 /// The crate's conservative, lossless default grammar.
7 Portable,
8 /// Node dotenv's default regular-expression grammar.
9 Node,
10 /// Python dotenv's stream grammar.
11 Python,
12 /// Compose's dotenv grammar, without interpolation.
13 Compose,
14 /// Docker's raw key/value-file grammar.
15 Docker,
16}
17
18/// Recognition of inline comments (whole-statement comments are always enabled).
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum InlineComments {
21 /// A hash preceded by configured whitespace.
22 Whitespace,
23 /// A hash preceded by a literal ASCII space within the value/header tail.
24 Space,
25 /// Any hash starts a comment.
26 AnyHash,
27 /// Whitespace-delimited values, but any hash after a bare key (Python).
28 Python,
29 /// Hashes in values are literal.
30 None,
31}
32
33/// Independently enabled double-quoted escape sequences.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct DoubleEscapes(u16);
36impl DoubleEscapes {
37 /// No recognized escapes.
38 pub const NONE: Self = Self(0);
39 /// `\\n`.
40 pub const LF: Self = Self(1);
41 /// `\\r`.
42 pub const CR: Self = Self(1 << 1);
43 /// `\\t`.
44 pub const TAB: Self = Self(1 << 2);
45 /// `\\\\`.
46 pub const BACKSLASH: Self = Self(1 << 3);
47 /// `\\"`.
48 pub const DOUBLE_QUOTE: Self = Self(1 << 4);
49 /// `\\$` (literal dollar, not interpolation).
50 pub const DOLLAR: Self = Self(1 << 5);
51 /// `\\'`.
52 pub const SINGLE_QUOTE: Self = Self(1 << 6);
53 /// `\\a`.
54 pub const BELL: Self = Self(1 << 7);
55 /// `\\b`.
56 pub const BACKSPACE: Self = Self(1 << 8);
57 /// `\\f`.
58 pub const FORM_FEED: Self = Self(1 << 9);
59 /// `\\v`.
60 pub const VERTICAL_TAB: Self = Self(1 << 10);
61 /// Compose's `\\0` followed by up to three octal digits.
62 pub const OCTAL: Self = Self(1 << 11);
63 /// Portable's newline, carriage return, tab, slash, quote and dollar escapes.
64 pub const PORTABLE: Self = Self(0x3f);
65 /// Node's newline and carriage return substitutions.
66 pub const NODE: Self = Self(3);
67 /// Python dotenv's recognized double-quoted escapes.
68 pub const PYTHON: Self = Self(0x7df);
69 /// Compose's recognized escapes, including its octal-prefix behavior.
70 pub const COMPOSE: Self = Self(0xfbf);
71 /// Enable every sequence enabled by either set.
72 #[must_use]
73 pub const fn union(self, other: Self) -> Self {
74 Self(self.0 | other.0)
75 }
76 /// Whether every sequence in `other` is enabled.
77 #[must_use]
78 pub const fn contains(self, other: Self) -> bool {
79 self.0 & other.0 == other.0
80 }
81}
82
83/// Handling of unrecognized double-quoted escapes.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum UnknownEscapes {
86 /// Keep both characters literally.
87 Preserve,
88 /// Keep both characters and issue a warning.
89 Warn,
90 /// Remove the backslash only.
91 DropBackslash,
92 /// Retain the binding but report an error.
93 Error,
94}
95/// Escapes recognized inside single quotes.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub enum SingleEscapes {
98 /// No single-quoted escapes.
99 None,
100 /// Decode escaped apostrophes.
101 Quote,
102 /// Decode escaped apostrophes and backslashes.
103 QuoteAndBackslash,
104}
105/// Colon assignment separators.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub enum ColonSeparator {
108 /// Only equals signs separate keys and values.
109 None,
110 /// A colon separates only when followed by configured whitespace.
111 Whitespace,
112 /// Any colon separates.
113 Any,
114}
115/// Validation of unquoted key bodies.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub enum KeySyntax {
118 /// ASCII letters/underscore followed by letters, digits or underscore.
119 Posix,
120 /// Nonempty ASCII letters, digits, underscore, dot or hyphen.
121 Portable,
122 /// Unicode 17 letters/numbers, underscore, dot or hyphen.
123 Unicode,
124 /// Nonempty, excluding Unicode whitespace and equals signs.
125 NonWhitespace,
126 /// Nonempty, excluding Python whitespace, equals signs and hashes.
127 Python,
128 /// Compose's Unicode letters/numbers, punctuation and horizontal whitespace.
129 Compose,
130 /// Nonempty, excluding literal ASCII space, tab and equals signs.
131 Docker,
132}
133/// Effective treatment of declarations with no assignment separator.
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135pub enum BareKeys {
136 /// Retain a participating declaration whose value is absent.
137 Preserve,
138 /// Retain syntax but exclude it from lookup.
139 Ignore,
140 /// Look up the declaration as an empty string without changing its syntax.
141 Empty,
142 /// Retain syntax and report an error.
143 Error,
144}
145/// Physical line recognition.
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub enum LineBreaks {
148 /// LF, CRLF and lone CR terminate lines.
149 Universal,
150 /// LF terminates lines; one final CR belongs to the line ending.
151 Lf,
152}
153/// Treatment of an initial UTF-8 byte-order mark.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
155pub enum BomPolicy {
156 /// Retain it as a separate prelude and diagnose it informationally.
157 Strip,
158 /// Treat it as ordinary statement text.
159 Keep,
160 /// Retain it as an invalid item and report an error.
161 Error,
162}
163/// Which participating duplicate declaration wins lookup.
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165pub enum Duplicates {
166 /// The first declaration wins.
167 First,
168 /// The last declaration wins.
169 Last,
170}
171/// Handling of noncomment text following a closing quote.
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
173pub enum QuotedTail {
174 /// Reject the statement.
175 Error,
176 /// Keep the tail token but omit it from the decoded value.
177 Ignore,
178 /// Append the literal tail to the decoded quoted body.
179 Append,
180 /// Reinterpret the opening physical line as an unquoted value.
181 LiteralFallback,
182 /// Ignore the complete statement with a warning.
183 Skip,
184 /// End the item at the quote and parse the remainder as another statement.
185 NextStatement,
186}
187/// Independently enabled multiline quote forms.
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
189pub struct Multiline {
190 /// Permit line endings inside single quotes.
191 pub single: bool,
192 /// Permit line endings inside double quotes.
193 pub double: bool,
194 /// Permit line endings inside backticks.
195 pub backtick: bool,
196}
197impl Multiline {
198 /// Permit multiline content in every quote form.
199 pub const ALL: Self = Self {
200 single: true,
201 double: true,
202 backtick: true,
203 };
204 /// Forbid multiline content in every quote form.
205 pub const NONE: Self = Self {
206 single: false,
207 double: false,
208 backtick: false,
209 };
210}
211/// Whitespace classification used by syntax policies.
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
213pub enum Whitespace {
214 /// ASCII space and tab.
215 Ascii,
216 /// Rust's Unicode `White_Space` classification.
217 Unicode,
218 /// ECMAScript whitespace, including BOM but excluding NEL.
219 EcmaScript,
220 /// Unicode `White_Space` plus U+001C through U+001F.
221 Python,
222 /// Compose's horizontal whitespace classification.
223 Compose,
224}
225/// Handling of malformed statements, excluding explicit policy violations.
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
227pub enum MalformedLines {
228 /// Retain invalid syntax and report an error.
229 Error,
230 /// Retain ignored syntax and report a warning.
231 Ignore,
232}
233
234/// Complete, explicit literal parsing and lookup configuration.
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
236// These orthogonal syntax switches are independently overrideable, not states.
237#[allow(clippy::struct_excessive_bools)]
238pub struct Dialect {
239 /// Statement grammar.
240 pub syntax: Syntax,
241 /// Unquoted inline comment recognition.
242 pub inline_comments: InlineComments,
243 /// Recognized double-quoted escapes.
244 pub double_escapes: DoubleEscapes,
245 /// Unrecognized double-quoted escape policy.
246 pub unknown_escapes: UnknownEscapes,
247 /// Single-quoted escape policy.
248 pub single_escapes: SingleEscapes,
249 /// Recognize backtick-quoted values.
250 pub backticks: bool,
251 /// Colon assignment separator policy.
252 pub colon: ColonSeparator,
253 /// Unquoted key validation.
254 pub keys: KeySyntax,
255 /// Bare declaration lookup/error policy.
256 pub bare_keys: BareKeys,
257 /// Keep all bytes after the separator as a literal value.
258 pub raw_values: bool,
259 /// Physical line recognition.
260 pub line_breaks: LineBreaks,
261 /// Leading byte-order mark policy.
262 pub bom: BomPolicy,
263 /// Winning duplicate declaration.
264 pub duplicates: Duplicates,
265 /// Noncomment text following a quote.
266 pub quoted_tail: QuotedTail,
267 /// Multiline quote permissions.
268 pub multiline: Multiline,
269 /// Whitespace classification.
270 pub whitespace: Whitespace,
271 /// Recognize the export prefix.
272 pub export: bool,
273 /// Permit single-quoted key tokens.
274 pub quoted_keys: bool,
275 /// Permit comments immediately after closing quotes.
276 pub quoted_comments: bool,
277 /// Normalize decoded CRLF and CR to LF, preserving original source maps.
278 pub normalize_newlines: bool,
279 /// Malformed statement policy.
280 pub malformed_lines: MalformedLines,
281 /// Exclude Node's special `__proto__` property from lookup.
282 pub node_object_keys: bool,
283 /// Maximum bytes per physical line before its final LF, including CR.
284 pub line_byte_limit: Option<usize>,
285}
286impl Dialect {
287 /// Conservative Portable syntax; the default configuration.
288 pub const PORTABLE: Self = Self {
289 syntax: Syntax::Portable,
290 inline_comments: InlineComments::Whitespace,
291 double_escapes: DoubleEscapes::PORTABLE,
292 unknown_escapes: UnknownEscapes::Warn,
293 single_escapes: SingleEscapes::None,
294 backticks: false,
295 colon: ColonSeparator::None,
296 keys: KeySyntax::Portable,
297 bare_keys: BareKeys::Preserve,
298 raw_values: false,
299 line_breaks: LineBreaks::Universal,
300 bom: BomPolicy::Strip,
301 duplicates: Duplicates::Last,
302 quoted_tail: QuotedTail::Error,
303 multiline: Multiline::ALL,
304 whitespace: Whitespace::Ascii,
305 export: true,
306 quoted_keys: false,
307 quoted_comments: false,
308 normalize_newlines: false,
309 malformed_lines: MalformedLines::Error,
310 node_object_keys: false,
311 line_byte_limit: None,
312 };
313 /// Node dotenv 18's default grammar and literal substitutions.
314 pub const NODE_DOTENV: Self = Self {
315 syntax: Syntax::Node,
316 whitespace: Whitespace::EcmaScript,
317 inline_comments: InlineComments::AnyHash,
318 double_escapes: DoubleEscapes::NODE,
319 unknown_escapes: UnknownEscapes::Preserve,
320 backticks: true,
321 colon: ColonSeparator::Whitespace,
322 bare_keys: BareKeys::Ignore,
323 quoted_tail: QuotedTail::LiteralFallback,
324 quoted_comments: true,
325 normalize_newlines: true,
326 malformed_lines: MalformedLines::Ignore,
327 node_object_keys: true,
328 ..Self::PORTABLE
329 };
330 /// Python dotenv 1.2's stream grammar, without interpolation.
331 pub const PYTHON_DOTENV: Self = Self {
332 syntax: Syntax::Python,
333 whitespace: Whitespace::Python,
334 inline_comments: InlineComments::Python,
335 double_escapes: DoubleEscapes::PYTHON,
336 unknown_escapes: UnknownEscapes::Preserve,
337 single_escapes: SingleEscapes::QuoteAndBackslash,
338 keys: KeySyntax::Python,
339 quoted_keys: true,
340 quoted_comments: true,
341 ..Self::PORTABLE
342 };
343 /// Compose dotenv's grammar and escapes, without interpolation or inheritance.
344 pub const COMPOSE: Self = Self {
345 syntax: Syntax::Compose,
346 whitespace: Whitespace::Compose,
347 inline_comments: InlineComments::Space,
348 double_escapes: DoubleEscapes::COMPOSE,
349 unknown_escapes: UnknownEscapes::Preserve,
350 single_escapes: SingleEscapes::Quote,
351 colon: ColonSeparator::Any,
352 keys: KeySyntax::Compose,
353 bare_keys: BareKeys::Ignore,
354 line_breaks: LineBreaks::Lf,
355 quoted_tail: QuotedTail::NextStatement,
356 quoted_comments: true,
357 ..Self::PORTABLE
358 };
359 /// Docker CLI's raw kvfile grammar, with no environment inheritance.
360 pub const DOCKER_RAW: Self = Self {
361 syntax: Syntax::Docker,
362 whitespace: Whitespace::Unicode,
363 inline_comments: InlineComments::None,
364 double_escapes: DoubleEscapes::NONE,
365 unknown_escapes: UnknownEscapes::Preserve,
366 keys: KeySyntax::Docker,
367 bare_keys: BareKeys::Ignore,
368 raw_values: true,
369 line_breaks: LineBreaks::Lf,
370 multiline: Multiline::NONE,
371 export: false,
372 line_byte_limit: Some(65_535),
373 ..Self::PORTABLE
374 };
375}
376impl Default for Dialect {
377 fn default() -> Self {
378 Self::PORTABLE
379 }
380}