@@ -26,6 +26,33 @@ 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
46
+ // index
47
+ auto section = path.substr (hash_char_index + 1 ,
48
+ next_hash_char_index - 1 - hash_char_index);
49
+ sections.insert (section);
50
+ start_index = next_hash_char_index;
51
+ }
52
+
53
+ return sections;
54
+ };
55
+
29
56
std::vector<Dotenv::env_file_data> env_files;
30
57
// This will be an iterator, pointing to args.end() if no matches are found
31
58
auto matched_arg = std::find_if (args.begin (), args.end (), find_match);
@@ -42,19 +69,37 @@ std::vector<Dotenv::env_file_data> Dotenv::GetDataFromArgs(
42
69
auto flag = matched_arg->substr (0 , equal_char_index);
43
70
auto file_path = matched_arg->substr (equal_char_index + 1 );
44
71
72
+ auto sections = get_sections (file_path);
73
+
74
+ auto hash_char_index = file_path.find (' #' );
75
+ if (hash_char_index != std::string::npos) {
76
+ file_path = file_path.substr (0 , hash_char_index);
77
+ }
78
+
45
79
struct env_file_data env_file_data = {
46
- file_path, flag.starts_with (optional_env_file_flag)};
80
+ file_path, flag.starts_with (optional_env_file_flag), sections };
47
81
env_files.push_back (env_file_data);
48
82
} else {
49
83
// `--env-file path`
50
- auto file_path = std::next (matched_arg);
84
+ auto file_path_ptr = std::next (matched_arg);
51
85
52
- if (file_path == args.end ()) {
86
+ if (file_path_ptr == args.end ()) {
53
87
return env_files;
54
88
}
55
89
90
+ std::string file_path = file_path_ptr->c_str ();
91
+
92
+ auto sections = get_sections (file_path);
93
+
94
+ auto hash_char_index = file_path.find (' #' );
95
+ if (hash_char_index != std::string::npos) {
96
+ file_path = file_path.substr (0 , hash_char_index);
97
+ }
98
+
56
99
struct env_file_data env_file_data = {
57
- *file_path, matched_arg->starts_with (optional_env_file_flag)};
100
+ file_path,
101
+ matched_arg->starts_with (optional_env_file_flag),
102
+ sections};
58
103
env_files.push_back (env_file_data);
59
104
}
60
105
@@ -124,9 +169,24 @@ std::string_view trim_spaces(std::string_view input) {
124
169
return input.substr (pos_start, pos_end - pos_start + 1 );
125
170
}
126
171
127
- void Dotenv::ParseContent (const std::string_view input) {
172
+ void Dotenv::ParseContent (const std::string_view input,
173
+ const std::set<std::string> sections) {
128
174
std::string lines (input);
129
175
176
+ // Variable to track the current section ("" indicates that we're in the
177
+ // global/top-level section)
178
+ std::string current_section = " " ;
179
+
180
+ // Insert/Assign a value in the store, but only if it's in the global section
181
+ // or in an included section
182
+ auto maybe_insert_or_assign_to_store = [&](const std::string& key,
183
+ const std::string_view& value) {
184
+ if (current_section.empty () ||
185
+ (sections.find (current_section.c_str ()) != sections.end ())) {
186
+ store_.insert_or_assign (key, value);
187
+ }
188
+ };
189
+
130
190
// Handle windows newlines "\r\n": remove "\r" and keep only "\n"
131
191
lines.erase (std::remove (lines.begin (), lines.end (), ' \r ' ), lines.end ());
132
192
@@ -154,6 +214,18 @@ void Dotenv::ParseContent(const std::string_view input) {
154
214
continue ;
155
215
}
156
216
217
+ if (content.front () == ' [' ) {
218
+ auto closing_bracket_idx = content.find_first_of (' ]' );
219
+ if (closing_bracket_idx != std::string_view::npos) {
220
+ if (content.at (closing_bracket_idx + 1 ) == ' \n ' ) {
221
+ // We've enterer a new section of the file
222
+ current_section = content.substr (1 , closing_bracket_idx - 1 );
223
+ content.remove_prefix (closing_bracket_idx + 1 );
224
+ continue ;
225
+ }
226
+ }
227
+ }
228
+
157
229
// Find the next equals sign or newline in a single pass.
158
230
// This optimizes the search by avoiding multiple iterations.
159
231
auto equal_or_newline = content.find_first_of (" =\n " );
@@ -176,7 +248,7 @@ void Dotenv::ParseContent(const std::string_view input) {
176
248
177
249
// If the value is not present (e.g. KEY=) set it to an empty string
178
250
if (content.empty () || content.front () == ' \n ' ) {
179
- store_. insert_or_assign (std::string (key), " " );
251
+ maybe_insert_or_assign_to_store (std::string (key), " " );
180
252
continue ;
181
253
}
182
254
@@ -201,7 +273,7 @@ void Dotenv::ParseContent(const std::string_view input) {
201
273
if (content.empty ()) {
202
274
// In case the last line is a single key without value
203
275
// Example: KEY= (without a newline at the EOF)
204
- store_. insert_or_assign (std::string (key), " " );
276
+ maybe_insert_or_assign_to_store (std::string (key), " " );
205
277
break ;
206
278
}
207
279
@@ -221,7 +293,7 @@ void Dotenv::ParseContent(const std::string_view input) {
221
293
pos += 1 ;
222
294
}
223
295
224
- store_. insert_or_assign (std::string (key), multi_line_value);
296
+ maybe_insert_or_assign_to_store (std::string (key), multi_line_value);
225
297
auto newline = content.find (' \n ' , closing_quote + 1 );
226
298
if (newline != std::string_view::npos) {
227
299
content.remove_prefix (newline + 1 );
@@ -248,18 +320,18 @@ void Dotenv::ParseContent(const std::string_view input) {
248
320
auto newline = content.find (' \n ' );
249
321
if (newline != std::string_view::npos) {
250
322
value = content.substr (0 , newline);
251
- store_. insert_or_assign (std::string (key), value);
323
+ maybe_insert_or_assign_to_store (std::string (key), value);
252
324
content.remove_prefix (newline + 1 );
253
325
} else {
254
326
// No newline - take rest of content
255
327
value = content;
256
- store_. insert_or_assign (std::string (key), value);
328
+ maybe_insert_or_assign_to_store (std::string (key), value);
257
329
break ;
258
330
}
259
331
} else {
260
332
// Found closing quote - take content between quotes
261
333
value = content.substr (1 , closing_quote - 1 );
262
- store_. insert_or_assign (std::string (key), value);
334
+ maybe_insert_or_assign_to_store (std::string (key), value);
263
335
auto newline = content.find (' \n ' , closing_quote + 1 );
264
336
if (newline != std::string_view::npos) {
265
337
// Use +1 to discard the '\n' itself => next line
@@ -285,7 +357,7 @@ void Dotenv::ParseContent(const std::string_view input) {
285
357
value = value.substr (0 , hash_character);
286
358
}
287
359
value = trim_spaces (value);
288
- store_. insert_or_assign (std::string (key), std::string (value));
360
+ maybe_insert_or_assign_to_store (std::string (key), std::string (value));
289
361
content.remove_prefix (newline + 1 );
290
362
} else {
291
363
// Last line without newline
@@ -294,7 +366,7 @@ void Dotenv::ParseContent(const std::string_view input) {
294
366
if (hash_char != std::string_view::npos) {
295
367
value = content.substr (0 , hash_char);
296
368
}
297
- store_. insert_or_assign (std::string (key), trim_spaces (value));
369
+ maybe_insert_or_assign_to_store (std::string (key), trim_spaces (value));
298
370
content = {};
299
371
}
300
372
}
@@ -303,7 +375,8 @@ void Dotenv::ParseContent(const std::string_view input) {
303
375
}
304
376
}
305
377
306
- Dotenv::ParseResult Dotenv::ParsePath (const std::string_view path) {
378
+ Dotenv::ParseResult Dotenv::ParsePath (const std::string_view path,
379
+ const std::set<std::string> sections) {
307
380
uv_fs_t req;
308
381
auto defer_req_cleanup = OnScopeLeave ([&req]() { uv_fs_req_cleanup (&req); });
309
382
@@ -337,7 +410,7 @@ Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
337
410
result.append (buf.base , r);
338
411
}
339
412
340
- ParseContent (result);
413
+ ParseContent (result, sections );
341
414
return ParseResult::Valid;
342
415
}
343
416
0 commit comments