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,11 @@ namespace internal
98168 std::vector<std::string> const &meshes,
99169 std::vector<std::string> const &particles)
100170 {
101- for (auto [deque, vec] :
102- {std::make_pair (&this ->meshesPath , &meshes),
103- std::make_pair (&this ->particlesPath , &particles)})
171+ std::regex is_default_path_specification (" [[:alnum:]_]+/" , regex_flags);
172+ for (auto [deque, vec, defaultPath] :
173+ {std::make_tuple (&this ->meshesPath , &meshes, &m_defaultMeshesPath),
174+ std::make_tuple (
175+ &this ->particlesPath , &particles, &m_defaultParticlesPath)})
104176 {
105177 std::transform (
106178 vec->begin (),
@@ -113,6 +185,7 @@ namespace internal
113185 str, regex_flags | std::regex_constants::optimize));
114186 });
115187 }
188+ setDefaultMeshesParticlesPath (meshes, particles, *this );
116189 }
117190
118191 ContainedType MeshesParticlesPath::determineType (
@@ -262,10 +335,6 @@ void CustomHierarchy::readParticleSpecies(
262335 }
263336}
264337
265- // @todo make this flexible again
266- constexpr char const *defaultMeshesPath = " meshes" ;
267- constexpr char const *defaultParticlesPath = " particles" ;
268-
269338void CustomHierarchy::read (internal::MeshesParticlesPath const &mpp)
270339{
271340 std::vector<std::string> currentPath;
@@ -292,6 +361,8 @@ void CustomHierarchy::read(
292361
293362 std::deque<std::string> constantComponentsPushback;
294363 auto &data = get ();
364+ data.m_defaultMeshesPath = mpp.m_defaultMeshesPath ;
365+ data.m_defaultParticlesPath = mpp.m_defaultParticlesPath ;
295366 EraseStaleMeshes meshesMap (data.m_embeddedMeshes );
296367 EraseStaleParticles particlesMap (data.m_embeddedParticles );
297368 for (auto const &path : *pList.paths )
@@ -405,16 +476,19 @@ void CustomHierarchy::flush_internal(
405476
406477 // No need to do anything in access::readOnly since meshes and particles
407478 // are initialized as aliases for subgroups at parsing time
479+ auto &data = get ();
480+ data.m_defaultMeshesPath = mpp.m_defaultMeshesPath ;
481+ data.m_defaultParticlesPath = mpp.m_defaultParticlesPath ;
408482 if (access::write (IOHandler ()->m_frontendAccess ))
409483 {
410484 if (!meshes.empty ())
411485 {
412- (*this )[defaultMeshesPath ];
486+ (*this )[mpp. m_defaultMeshesPath ];
413487 }
414488
415489 if (!particles.empty ())
416490 {
417- (*this )[defaultParticlesPath ];
491+ (*this )[mpp. m_defaultParticlesPath ];
418492 }
419493
420494 flushAttributes (flushParams);
@@ -432,27 +506,53 @@ void CustomHierarchy::flush_internal(
432506 subpath.flush_internal (flushParams, mpp, currentPath);
433507 currentPath.pop_back ();
434508 }
435- auto &data = get ();
436509 for (auto &[name, mesh] : data.m_embeddedMeshes )
437510 {
438511 if (!mpp.isMesh (currentPath, name))
439512 {
440- std::string fullPath = currentPath.empty ()
441- ? name
442- : concatWithSep (currentPath, " /" ) + " /" + name;
443- mpp.meshesPath .emplace (fullPath, std::regex (fullPath, regex_flags));
513+ std::string extend_meshes_path;
514+ if (!currentPath.empty () &&
515+ *currentPath.rbegin () == mpp.m_defaultMeshesPath )
516+ {
517+ extend_meshes_path = *currentPath.rbegin () + " /" ;
518+ }
519+ else
520+ {
521+
522+ extend_meshes_path = " /" +
523+ (currentPath.empty ()
524+ ? " "
525+ : concatWithSep (currentPath, " /" ) + " /" ) +
526+ name;
527+ }
528+ mpp.meshesPath .emplace (
529+ extend_meshes_path,
530+ std::regex (extend_meshes_path, regex_flags));
444531 }
445532 mesh.flush (name, flushParams);
446533 }
447534 for (auto &[name, particleSpecies] : data.m_embeddedParticles )
448535 {
449536 if (!mpp.isParticle (currentPath, name))
450537 {
451- std::string fullPath = currentPath.empty ()
452- ? name
453- : concatWithSep (currentPath, " /" ) + " /" + name;
538+ std::string extend_particles_path;
539+ if (!currentPath.empty () &&
540+ *currentPath.rbegin () == mpp.m_defaultParticlesPath )
541+ {
542+ extend_particles_path = *currentPath.rbegin () + " /" ;
543+ }
544+ else
545+ {
546+
547+ extend_particles_path = " /" +
548+ (currentPath.empty ()
549+ ? " "
550+ : concatWithSep (currentPath, " /" ) + " /" ) +
551+ name;
552+ }
454553 mpp.particlesPath .emplace (
455- fullPath, std::regex (fullPath, regex_flags));
554+ extend_particles_path,
555+ std::regex (extend_particles_path, regex_flags));
456556 }
457557 particleSpecies.flush (name, flushParams);
458558 }
@@ -596,10 +696,11 @@ template <typename KeyType>
596696auto CustomHierarchy::bracketOperatorImpl (KeyType &&provided_key)
597697 -> mapped_type &
598698{
599- auto &cont = container ();
699+ auto &data = get ();
700+ auto &cont = data.m_container ;
600701 auto find_special_key =
601702 [&cont, &provided_key, this ](
602- char const * special_key,
703+ std::string const & special_key,
603704 auto &alias,
604705 auto &&embeddedAccessor) -> std::optional<mapped_type *> {
605706 if (provided_key == special_key)
@@ -656,8 +757,15 @@ auto CustomHierarchy::bracketOperatorImpl(KeyType &&provided_key)
656757 return std::nullopt ;
657758 }
658759 };
760+ if (data.m_defaultMeshesPath .empty () || data.m_defaultParticlesPath .empty ())
761+ {
762+ auto const &series = retrieveSeries ();
763+ auto meshes_paths = series.meshesPaths ();
764+ auto particles_paths = series.particlesPaths ();
765+ setDefaultMeshesParticlesPath (meshes_paths, particles_paths, data);
766+ }
659767 if (auto res = find_special_key (
660- defaultMeshesPath ,
768+ data. m_defaultMeshesPath ,
661769 meshes,
662770 [](auto &group) {
663771 return &group.m_customHierarchyData ->m_embeddedMeshes ;
@@ -667,7 +775,7 @@ auto CustomHierarchy::bracketOperatorImpl(KeyType &&provided_key)
667775 return **res;
668776 }
669777 if (auto res = find_special_key (
670- defaultParticlesPath ,
778+ data. m_defaultParticlesPath ,
671779 particles,
672780 [](auto &group) {
673781 return &group.m_customHierarchyData ->m_embeddedParticles ;
0 commit comments