@@ -26,6 +26,31 @@ std::vector<Dotenv::env_file_data> Dotenv::GetDataFromArgs(
26
26
arg.starts_with (" --env-file-if-exists=" );
27
27
};
28
28
29
+ const auto get_sections = [](const std::string& path) {
30
+ std::set<std::string> sections = {};
31
+ std::int8_t start_index = 0 ;
32
+
33
+ while (true ) {
34
+ auto hash_char_index = path.find (' #' , start_index);
35
+ if (hash_char_index == std::string::npos) {
36
+ return sections;
37
+ }
38
+ auto next_hash_char_index = path.find (' #' , hash_char_index + 1 );
39
+ if (next_hash_char_index == std::string::npos) {
40
+ // We've arrived to the last section
41
+ auto section = path.substr (hash_char_index + 1 );
42
+ sections.insert (section);
43
+ return sections;
44
+ }
45
+ // There are more sections, so let's save the current one and update the index
46
+ auto section = path.substr (hash_char_index+1 , next_hash_char_index - 1 - hash_char_index);
47
+ sections.insert (section);
48
+ start_index = next_hash_char_index;
49
+ }
50
+
51
+ return sections;
52
+ };
53
+
29
54
std::vector<Dotenv::env_file_data> env_files;
30
55
// This will be an iterator, pointing to args.end() if no matches are found
31
56
auto matched_arg = std::find_if (args.begin (), args.end (), find_match);
@@ -42,19 +67,35 @@ std::vector<Dotenv::env_file_data> Dotenv::GetDataFromArgs(
42
67
auto flag = matched_arg->substr (0 , equal_char_index);
43
68
auto file_path = matched_arg->substr (equal_char_index + 1 );
44
69
70
+ auto sections = get_sections (file_path);
71
+
72
+ auto hash_char_index = file_path.find (' #' );
73
+ if (hash_char_index != std::string::npos) {
74
+ file_path = file_path.substr (0 , hash_char_index);
75
+ }
76
+
45
77
struct env_file_data env_file_data = {
46
- file_path, flag.starts_with (optional_env_file_flag)};
78
+ file_path, flag.starts_with (optional_env_file_flag), sections };
47
79
env_files.push_back (env_file_data);
48
80
} else {
49
81
// `--env-file path`
50
- auto file_path = std::next (matched_arg);
82
+ auto file_path_ptr = std::next (matched_arg);
51
83
52
- if (file_path == args.end ()) {
84
+ if (file_path_ptr == args.end ()) {
53
85
return env_files;
54
86
}
55
87
88
+ std::string file_path = file_path_ptr->c_str ();
89
+
90
+ auto sections = get_sections (file_path);
91
+
92
+ auto hash_char_index = file_path.find (' #' );
93
+ if (hash_char_index != std::string::npos) {
94
+ file_path = file_path.substr (0 , hash_char_index);
95
+ }
96
+
56
97
struct env_file_data env_file_data = {
57
- * file_path, matched_arg->starts_with (optional_env_file_flag)};
98
+ file_path, matched_arg->starts_with (optional_env_file_flag), sections };
58
99
env_files.push_back (env_file_data);
59
100
}
60
101
@@ -124,9 +165,19 @@ std::string_view trim_spaces(std::string_view input) {
124
165
return input.substr (pos_start, pos_end - pos_start + 1 );
125
166
}
126
167
127
- void Dotenv::ParseContent (const std::string_view input) {
168
+ void Dotenv::ParseContent (const std::string_view input, const std::set<std::string> sections ) {
128
169
std::string lines (input);
129
170
171
+ // Variable to track the current section ("" indicates that we're in the global/top-level section)
172
+ std::string current_section = " " ;
173
+
174
+ // Insert/Assign a value in the store, but only if it's in the global section or in an included section
175
+ auto maybe_insert_or_assign_to_store = [&](const std::string& key, const std::string_view& value) {
176
+ if (current_section.empty () || (sections.find (current_section.c_str ()) != sections.end ())) {
177
+ store_.insert_or_assign (key, value);
178
+ }
179
+ };
180
+
130
181
// Handle windows newlines "\r\n": remove "\r" and keep only "\n"
131
182
lines.erase (std::remove (lines.begin (), lines.end (), ' \r ' ), lines.end ());
132
183
@@ -154,6 +205,18 @@ void Dotenv::ParseContent(const std::string_view input) {
154
205
continue ;
155
206
}
156
207
208
+ if (content.front () == ' [' ) {
209
+ auto closing_bracket_idx = content.find_first_of (' ]' );
210
+ if (closing_bracket_idx != std::string_view::npos) {
211
+ if (content.at (closing_bracket_idx + 1 ) == ' \n ' ) {
212
+ // We've enterer a new section of the file
213
+ current_section = content.substr (1 , closing_bracket_idx - 1 );
214
+ content.remove_prefix (closing_bracket_idx + 1 );
215
+ continue ;
216
+ }
217
+ }
218
+ }
219
+
157
220
// Find the next equals sign or newline in a single pass.
158
221
// This optimizes the search by avoiding multiple iterations.
159
222
auto equal_or_newline = content.find_first_of (" =\n " );
@@ -176,7 +239,7 @@ void Dotenv::ParseContent(const std::string_view input) {
176
239
177
240
// If the value is not present (e.g. KEY=) set it to an empty string
178
241
if (content.empty () || content.front () == ' \n ' ) {
179
- store_. insert_or_assign (std::string (key), " " );
242
+ maybe_insert_or_assign_to_store (std::string (key), " " );
180
243
continue ;
181
244
}
182
245
@@ -201,7 +264,7 @@ void Dotenv::ParseContent(const std::string_view input) {
201
264
if (content.empty ()) {
202
265
// In case the last line is a single key without value
203
266
// Example: KEY= (without a newline at the EOF)
204
- store_. insert_or_assign (std::string (key), " " );
267
+ maybe_insert_or_assign_to_store (std::string (key), " " );
205
268
break ;
206
269
}
207
270
@@ -221,7 +284,7 @@ void Dotenv::ParseContent(const std::string_view input) {
221
284
pos += 1 ;
222
285
}
223
286
224
- store_. insert_or_assign (std::string (key), multi_line_value);
287
+ maybe_insert_or_assign_to_store (std::string (key), multi_line_value);
225
288
auto newline = content.find (' \n ' , closing_quote + 1 );
226
289
if (newline != std::string_view::npos) {
227
290
content.remove_prefix (newline + 1 );
@@ -248,18 +311,18 @@ void Dotenv::ParseContent(const std::string_view input) {
248
311
auto newline = content.find (' \n ' );
249
312
if (newline != std::string_view::npos) {
250
313
value = content.substr (0 , newline);
251
- store_. insert_or_assign (std::string (key), value);
314
+ maybe_insert_or_assign_to_store (std::string (key), value);
252
315
content.remove_prefix (newline + 1 );
253
316
} else {
254
317
// No newline - take rest of content
255
318
value = content;
256
- store_. insert_or_assign (std::string (key), value);
319
+ maybe_insert_or_assign_to_store (std::string (key), value);
257
320
break ;
258
321
}
259
322
} else {
260
323
// Found closing quote - take content between quotes
261
324
value = content.substr (1 , closing_quote - 1 );
262
- store_. insert_or_assign (std::string (key), value);
325
+ maybe_insert_or_assign_to_store (std::string (key), value);
263
326
auto newline = content.find (' \n ' , closing_quote + 1 );
264
327
if (newline != std::string_view::npos) {
265
328
// Use +1 to discard the '\n' itself => next line
@@ -285,7 +348,7 @@ void Dotenv::ParseContent(const std::string_view input) {
285
348
value = value.substr (0 , hash_character);
286
349
}
287
350
value = trim_spaces (value);
288
- store_. insert_or_assign (std::string (key), std::string (value));
351
+ maybe_insert_or_assign_to_store (std::string (key), std::string (value));
289
352
content.remove_prefix (newline + 1 );
290
353
} else {
291
354
// Last line without newline
@@ -294,7 +357,7 @@ void Dotenv::ParseContent(const std::string_view input) {
294
357
if (hash_char != std::string_view::npos) {
295
358
value = content.substr (0 , hash_char);
296
359
}
297
- store_. insert_or_assign (std::string (key), trim_spaces (value));
360
+ maybe_insert_or_assign_to_store (std::string (key), trim_spaces (value));
298
361
content = {};
299
362
}
300
363
}
@@ -303,7 +366,7 @@ void Dotenv::ParseContent(const std::string_view input) {
303
366
}
304
367
}
305
368
306
- Dotenv::ParseResult Dotenv::ParsePath (const std::string_view path) {
369
+ Dotenv::ParseResult Dotenv::ParsePath (const std::string_view path, const std::set<std::string> sections ) {
307
370
uv_fs_t req;
308
371
auto defer_req_cleanup = OnScopeLeave ([&req]() { uv_fs_req_cleanup (&req); });
309
372
@@ -337,7 +400,7 @@ Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
337
400
result.append (buf.base , r);
338
401
}
339
402
340
- ParseContent (result);
403
+ ParseContent (result, sections );
341
404
return ParseResult::Valid;
342
405
}
343
406
0 commit comments