@@ -26,6 +26,31 @@ std::vector<Dotenv::env_file_data> Dotenv::GetDataFromArgs(
2626 arg.starts_with (" --env-file-if-exists=" );
2727 };
2828
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+
2954 std::vector<Dotenv::env_file_data> env_files;
3055 // This will be an iterator, pointing to args.end() if no matches are found
3156 auto matched_arg = std::find_if (args.begin (), args.end (), find_match);
@@ -42,19 +67,35 @@ std::vector<Dotenv::env_file_data> Dotenv::GetDataFromArgs(
4267 auto flag = matched_arg->substr (0 , equal_char_index);
4368 auto file_path = matched_arg->substr (equal_char_index + 1 );
4469
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+
4577 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 };
4779 env_files.push_back (env_file_data);
4880 } else {
4981 // `--env-file path`
50- auto file_path = std::next (matched_arg);
82+ auto file_path_ptr = std::next (matched_arg);
5183
52- if (file_path == args.end ()) {
84+ if (file_path_ptr == args.end ()) {
5385 return env_files;
5486 }
5587
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+
5697 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 };
5899 env_files.push_back (env_file_data);
59100 }
60101
@@ -124,9 +165,19 @@ std::string_view trim_spaces(std::string_view input) {
124165 return input.substr (pos_start, pos_end - pos_start + 1 );
125166}
126167
127- void Dotenv::ParseContent (const std::string_view input) {
168+ void Dotenv::ParseContent (const std::string_view input, const std::set<std::string> sections ) {
128169 std::string lines (input);
129170
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+
130181 // Handle windows newlines "\r\n": remove "\r" and keep only "\n"
131182 lines.erase (std::remove (lines.begin (), lines.end (), ' \r ' ), lines.end ());
132183
@@ -154,6 +205,18 @@ void Dotenv::ParseContent(const std::string_view input) {
154205 continue ;
155206 }
156207
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+
157220 // Find the next equals sign or newline in a single pass.
158221 // This optimizes the search by avoiding multiple iterations.
159222 auto equal_or_newline = content.find_first_of (" =\n " );
@@ -176,7 +239,7 @@ void Dotenv::ParseContent(const std::string_view input) {
176239
177240 // If the value is not present (e.g. KEY=) set it to an empty string
178241 if (content.empty () || content.front () == ' \n ' ) {
179- store_. insert_or_assign (std::string (key), " " );
242+ maybe_insert_or_assign_to_store (std::string (key), " " );
180243 continue ;
181244 }
182245
@@ -201,7 +264,7 @@ void Dotenv::ParseContent(const std::string_view input) {
201264 if (content.empty ()) {
202265 // In case the last line is a single key without value
203266 // 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), " " );
205268 break ;
206269 }
207270
@@ -221,7 +284,7 @@ void Dotenv::ParseContent(const std::string_view input) {
221284 pos += 1 ;
222285 }
223286
224- store_. insert_or_assign (std::string (key), multi_line_value);
287+ maybe_insert_or_assign_to_store (std::string (key), multi_line_value);
225288 auto newline = content.find (' \n ' , closing_quote + 1 );
226289 if (newline != std::string_view::npos) {
227290 content.remove_prefix (newline + 1 );
@@ -248,18 +311,18 @@ void Dotenv::ParseContent(const std::string_view input) {
248311 auto newline = content.find (' \n ' );
249312 if (newline != std::string_view::npos) {
250313 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);
252315 content.remove_prefix (newline + 1 );
253316 } else {
254317 // No newline - take rest of content
255318 value = content;
256- store_. insert_or_assign (std::string (key), value);
319+ maybe_insert_or_assign_to_store (std::string (key), value);
257320 break ;
258321 }
259322 } else {
260323 // Found closing quote - take content between quotes
261324 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);
263326 auto newline = content.find (' \n ' , closing_quote + 1 );
264327 if (newline != std::string_view::npos) {
265328 // Use +1 to discard the '\n' itself => next line
@@ -285,7 +348,7 @@ void Dotenv::ParseContent(const std::string_view input) {
285348 value = value.substr (0 , hash_character);
286349 }
287350 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));
289352 content.remove_prefix (newline + 1 );
290353 } else {
291354 // Last line without newline
@@ -294,7 +357,7 @@ void Dotenv::ParseContent(const std::string_view input) {
294357 if (hash_char != std::string_view::npos) {
295358 value = content.substr (0 , hash_char);
296359 }
297- store_. insert_or_assign (std::string (key), trim_spaces (value));
360+ maybe_insert_or_assign_to_store (std::string (key), trim_spaces (value));
298361 content = {};
299362 }
300363 }
@@ -303,7 +366,7 @@ void Dotenv::ParseContent(const std::string_view input) {
303366 }
304367}
305368
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 ) {
307370 uv_fs_t req;
308371 auto defer_req_cleanup = OnScopeLeave ([&req]() { uv_fs_req_cleanup (&req); });
309372
@@ -337,7 +400,7 @@ Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
337400 result.append (buf.base , r);
338401 }
339402
340- ParseContent (result);
403+ ParseContent (result, sections );
341404 return ParseResult::Valid;
342405}
343406
0 commit comments