3838#include < optional>
3939#include < regex>
4040#include < sstream>
41+ #include < tuple>
4142
4243namespace openPMD
4344{
4445
4546namespace
4647{
48+ template <typename Iterator>
4749 std::string
48- concatWithSep (std::vector<std::string> const &v , std::string const &sep)
50+ concatWithSep (Iterator &&begin, Iterator const &end , std::string const &sep)
4951 {
50- switch (v. size () )
52+ if (begin == end )
5153 {
52- case 0 :
5354 return " " ;
54- case 1 :
55- return *v.begin ();
56- default :
57- break ;
5855 }
5956 std::stringstream res;
60- auto it = v.begin ();
61- res << *it++;
62- for (; it != v.end (); ++it)
57+ res << *(begin++);
58+ for (; begin != end; ++begin)
6359 {
64- res << sep << *it ;
60+ res << sep << *begin ;
6561 }
6662 return res.str ();
6763 }
6864
65+ std::string
66+ concatWithSep (std::vector<std::string> const &v, std::string const &sep)
67+ {
68+ return concatWithSep (v.begin (), v.end (), sep);
69+ }
70+
6971 // Not specifying std::regex_constants::optimize here, only using it where
7072 // it makes sense to.
7173 constexpr std::regex_constants::syntax_option_type regex_flags =
7274 std::regex_constants::egrep;
75+
76+ template <typename OutParam>
77+ void setDefaultMeshesParticlesPath (
78+ std::vector<std::string> const &meshes,
79+ std::vector<std::string> const &particles,
80+ OutParam &writeTarget)
81+ {
82+ std::regex is_default_path_specification (" [[:alnum:]_]+/" , regex_flags);
83+ constexpr char const *default_default_mesh = " meshes" ;
84+ constexpr char const *default_default_particle = " particles" ;
85+ for (auto [vec, defaultPath, default_default] :
86+ {std::make_tuple (
87+ &meshes,
88+ &writeTarget.m_defaultMeshesPath ,
89+ default_default_mesh),
90+ std::make_tuple (
91+ &particles,
92+ &writeTarget.m_defaultParticlesPath ,
93+ default_default_particle)})
94+ {
95+ bool set_default = true ;
96+ /*
97+ * The first eligible path in meshesPath/particlesPath is used as
98+ * the default, "meshes"/"particles" otherwise.
99+ */
100+ for (auto const &path : *vec)
101+ {
102+ if (std::regex_match (path, is_default_path_specification))
103+ {
104+ *defaultPath = auxiliary::replace_last (path, " /" , " " );
105+ set_default = false ;
106+ break ;
107+ }
108+ }
109+ if (set_default)
110+ {
111+ *defaultPath = default_default;
112+ }
113+ }
114+ }
73115} // namespace
74116
75117namespace internal
@@ -81,15 +123,43 @@ namespace internal
81123 std::vector<std::string> const &path,
82124 std::string const &name)
83125 {
84- std::string parentPath =
85- (path.empty () ? " " : concatWithSep (path, " /" )) + " /" ;
86- std::string fullPath = path.empty () ? name : parentPath + name;
126+ /*
127+ * /group/meshes/E is a mesh if the meshes path contains:
128+ *
129+ * 1) '/group/meshes/' (absolute path to mesh container)
130+ * 2) '/group/meshes/E' (absolute path to mesh itself)
131+ * 3) 'meshes/' (relative path to mesh container)
132+ *
133+ * The potential fourth option 'E' (relative path to mesh itself)
134+ * is not supported. ("Anything that is named 'E' is a mesh" is not
135+ * really a semantic that we want to explicitly support.)
136+ * '/' is never a valid meshes path.
137+ *
138+ * All this analogously for particles path.
139+ */
140+ std::vector<std::string> pathsToMatch = {
141+ /* option 2) from above */
142+ " /" + (path.empty () ? " " : concatWithSep (path, " /" ) + " /" ) +
143+ name};
144+ if (!path.empty ())
145+ {
146+ // option 1) from above
147+ pathsToMatch.emplace_back (" /" + concatWithSep (path, " /" ) + " /" );
148+
149+ // option 3 from above
150+ pathsToMatch.emplace_back (*path.rbegin () + " /" );
151+ }
87152 return std::any_of (
88153 regexes.begin (),
89154 regexes.end (),
90- [&parentPath, &fullPath](auto const ®ex) {
91- return std::regex_match (parentPath, regex.second ) ||
92- std::regex_match (fullPath, regex.second );
155+ [&pathsToMatch](auto const ®ex) {
156+ return std::any_of (
157+ pathsToMatch.begin (),
158+ pathsToMatch.end (),
159+ [®ex](std::string const &candidate_path) {
160+ return std::regex_match (
161+ candidate_path, regex.second );
162+ });
93163 });
94164 }
95165 } // namespace
@@ -98,9 +168,10 @@ namespace internal
98168 std::vector<std::string> const &meshes,
99169 std::vector<std::string> const &particles)
100170 {
171+ std::regex is_default_path_specification (" [[:alnum:]_]+/" , regex_flags);
101172 for (auto [deque, vec] :
102- {std::make_pair (&this ->meshesPath , &meshes),
103- std::make_pair (&this ->particlesPath , &particles)})
173+ {std::make_tuple (&this ->meshesPath , &meshes),
174+ std::make_tuple (&this ->particlesPath , &particles)})
104175 {
105176 std::transform (
106177 vec->begin (),
@@ -113,6 +184,7 @@ namespace internal
113184 str, regex_flags | std::regex_constants::optimize));
114185 });
115186 }
187+ setDefaultMeshesParticlesPath (meshes, particles, *this );
116188 }
117189
118190 ContainedType MeshesParticlesPath::determineType (
@@ -262,10 +334,6 @@ void CustomHierarchy::readParticleSpecies(
262334 }
263335}
264336
265- // @todo make this flexible again
266- constexpr char const *defaultMeshesPath = " meshes" ;
267- constexpr char const *defaultParticlesPath = " particles" ;
268-
269337void CustomHierarchy::read (internal::MeshesParticlesPath const &mpp)
270338{
271339 std::vector<std::string> currentPath;
@@ -292,6 +360,8 @@ void CustomHierarchy::read(
292360
293361 std::deque<std::string> constantComponentsPushback;
294362 auto &data = get ();
363+ data.m_defaultMeshesPath = mpp.m_defaultMeshesPath ;
364+ data.m_defaultParticlesPath = mpp.m_defaultParticlesPath ;
295365 EraseStaleMeshes meshesMap (data.m_embeddedMeshes );
296366 EraseStaleParticles particlesMap (data.m_embeddedParticles );
297367 for (auto const &path : *pList.paths )
@@ -405,16 +475,19 @@ void CustomHierarchy::flush_internal(
405475
406476 // No need to do anything in access::readOnly since meshes and particles
407477 // are initialized as aliases for subgroups at parsing time
478+ auto &data = get ();
479+ data.m_defaultMeshesPath = mpp.m_defaultMeshesPath ;
480+ data.m_defaultParticlesPath = mpp.m_defaultParticlesPath ;
408481 if (access::write (IOHandler ()->m_frontendAccess ))
409482 {
410483 if (!meshes.empty ())
411484 {
412- (*this )[defaultMeshesPath ];
485+ (*this )[mpp. m_defaultMeshesPath ];
413486 }
414487
415488 if (!particles.empty ())
416489 {
417- (*this )[defaultParticlesPath ];
490+ (*this )[mpp. m_defaultParticlesPath ];
418491 }
419492
420493 flushAttributes (flushParams);
@@ -432,27 +505,53 @@ void CustomHierarchy::flush_internal(
432505 subpath.flush_internal (flushParams, mpp, currentPath);
433506 currentPath.pop_back ();
434507 }
435- auto &data = get ();
436508 for (auto &[name, mesh] : data.m_embeddedMeshes )
437509 {
438510 if (!mpp.isMesh (currentPath, name))
439511 {
440- std::string fullPath = currentPath.empty ()
441- ? name
442- : concatWithSep (currentPath, " /" ) + " /" + name;
443- mpp.meshesPath .emplace (fullPath, std::regex (fullPath, regex_flags));
512+ std::string extend_meshes_path;
513+ if (!currentPath.empty () &&
514+ *currentPath.rbegin () == mpp.m_defaultMeshesPath )
515+ {
516+ extend_meshes_path = *currentPath.rbegin () + " /" ;
517+ }
518+ else
519+ {
520+
521+ extend_meshes_path = " /" +
522+ (currentPath.empty ()
523+ ? " "
524+ : concatWithSep (currentPath, " /" ) + " /" ) +
525+ name;
526+ }
527+ mpp.meshesPath .emplace (
528+ extend_meshes_path,
529+ std::regex (extend_meshes_path, regex_flags));
444530 }
445531 mesh.flush (name, flushParams);
446532 }
447533 for (auto &[name, particleSpecies] : data.m_embeddedParticles )
448534 {
449535 if (!mpp.isParticle (currentPath, name))
450536 {
451- std::string fullPath = currentPath.empty ()
452- ? name
453- : concatWithSep (currentPath, " /" ) + " /" + name;
537+ std::string extend_particles_path;
538+ if (!currentPath.empty () &&
539+ *currentPath.rbegin () == mpp.m_defaultParticlesPath )
540+ {
541+ extend_particles_path = *currentPath.rbegin () + " /" ;
542+ }
543+ else
544+ {
545+
546+ extend_particles_path = " /" +
547+ (currentPath.empty ()
548+ ? " "
549+ : concatWithSep (currentPath, " /" ) + " /" ) +
550+ name;
551+ }
454552 mpp.particlesPath .emplace (
455- fullPath, std::regex (fullPath, regex_flags));
553+ extend_particles_path,
554+ std::regex (extend_particles_path, regex_flags));
456555 }
457556 particleSpecies.flush (name, flushParams);
458557 }
@@ -596,10 +695,11 @@ template <typename KeyType>
596695auto CustomHierarchy::bracketOperatorImpl (KeyType &&provided_key)
597696 -> mapped_type &
598697{
599- auto &cont = container ();
698+ auto &data = get ();
699+ auto &cont = data.m_container ;
600700 auto find_special_key =
601701 [&cont, &provided_key, this ](
602- char const * special_key,
702+ std::string const & special_key,
603703 auto &alias,
604704 auto &&embeddedAccessor) -> std::optional<mapped_type *> {
605705 if (provided_key == special_key)
@@ -656,8 +756,15 @@ auto CustomHierarchy::bracketOperatorImpl(KeyType &&provided_key)
656756 return std::nullopt ;
657757 }
658758 };
759+ if (data.m_defaultMeshesPath .empty () || data.m_defaultParticlesPath .empty ())
760+ {
761+ auto const &series = retrieveSeries ();
762+ auto meshes_paths = series.meshesPaths ();
763+ auto particles_paths = series.particlesPaths ();
764+ setDefaultMeshesParticlesPath (meshes_paths, particles_paths, data);
765+ }
659766 if (auto res = find_special_key (
660- defaultMeshesPath ,
767+ data. m_defaultMeshesPath ,
661768 meshes,
662769 [](auto &group) {
663770 return &group.m_customHierarchyData ->m_embeddedMeshes ;
@@ -667,7 +774,7 @@ auto CustomHierarchy::bracketOperatorImpl(KeyType &&provided_key)
667774 return **res;
668775 }
669776 if (auto res = find_special_key (
670- defaultParticlesPath ,
777+ data. m_defaultParticlesPath ,
671778 particles,
672779 [](auto &group) {
673780 return &group.m_customHierarchyData ->m_embeddedParticles ;
0 commit comments