PostCloseFileRenamer.cc
Go to the documentation of this file.
2 
4 #include "boost/date_time/posix_time/posix_time.hpp"
5 #include "boost/filesystem.hpp"
7 
8 #include <iomanip>
9 #include <sstream>
10 #include <string>
11 
12 namespace bfs = boost::filesystem;
13 
14 // For sanity. To avoid unintentionally introducing std overloads, we
15 // do not make a using declaration for boost::regex.
16 using namespace boost::regex_constants;
17 using boost::regex_match;
18 using boost::regex_search;
19 using boost::smatch;
20 
21 namespace {
22  boost::regex const rename_re{
23  "%[lp]|%(\\d+)?([#rRsS])|%t([ocrRsS])|%if([bnedp])|%"
24  "ifs%([^%]*)%([^%]*)%([ig]*)%|%.",
25  ECMAScript};
26 
28  to_string(boost::posix_time::ptime const& pt)
29  {
30  if (pt == boost::posix_time::ptime{}) {
31  return "-";
32  }
33  return boost::posix_time::to_iso_string(pt);
34  }
35 }
36 
38  : stats_{stats}
39 {}
40 
41 // Do not substitute for the "%(\\d+)?#" pattern here.
44  std::string const& filePattern) const
45 {
46  std::string result; // Empty
47  smatch match;
48  auto sb = cbegin(filePattern), sid = sb, se = cend(filePattern);
49  while (regex_search(sid, se, match, rename_re)) {
50  // Precondition: that the regex creates the sub-matches we think it
51  // should.
52  assert(match.size() == 8);
53  // Subexpressions:
54  // 0: Entire matched expression.
55  // 1. Possible fill format digits for numeric substitution.
56  // 2. Numeric substitution specifier.
57  // 3. Timestamp substitution specifier.
58  // 4. Input file name substitution specifier.
59  // 5. Input file name regex match clause.
60  // 6. Input file name regex substitution clause.
61  // 7. Input file name regex flags.
62  //
63  // Note that we're not using named capture groups because that is
64  // not supported by C++11 to which we will eventually migrate when
65  // GCC supports the full C++11 regex functionality (reportedly
66  // 4.9.0).
67  //
68  // Add the bit before the next substitution pattern to the result.
69  result += match.prefix();
70  // Decide what we're going to add to the result instead of the
71  // substitution pattern:
72  switch (*(match[0].first + 1)) {
73  case 'l':
74  result += stats_.moduleLabel();
75  break;
76  case 'p':
77  result += stats_.processName();
78  break;
79  case 'i':
80  result += subInputFileName_(match);
81  break;
82  case 't':
83  result += subTimestamp_(match);
84  break;
85  default:
86  if (match[2].matched) { // [#rRsS]
87  result += subFilledNumericNoIndex_(match);
88  } else {
90  << "Unrecognized substitution %" << *(match[0].first + 1)
91  << " in pattern " << filePattern
92  << " -- typo or misconstructed regex?\n";
93  }
94  break;
95  }
96  sid = match[0].second; // Set position for next match start.
97  }
98  result.append(sid, se); // Append unmatched text at end.
99  return result;
100 }
101 
103 art::PostCloseFileRenamer::subInputFileName_(boost::smatch const& match) const
104 {
106  // If the filename is empty, substitute "-". If it is merely the
107  // required substitution that is empty, substitute "".
108  if (!stats_.lastOpenedInputFile().empty()) {
109  bfs::path const ifp{stats_.lastOpenedInputFile()};
110  if (match[4].matched) { // %if[bdenp]
111  switch (*(match[4].first)) {
112  case 'b': {
113  // Base name without extension.
114  result = ifp.stem().native();
115  } break;
116  case 'd': {
117  // Fully resolved path without filename.
118  result = canonical(ifp.parent_path()).native();
119  } break;
120  case 'e': {
121  // Extension.
122  result = ifp.extension().native();
123  } break;
124  case 'n': {
125  // Base name with extension.
126  result = ifp.filename().native();
127  } break;
128  case 'p': {
129  // Fully-resolved path with filename.
130  result = (canonical(ifp.parent_path()) / ifp.filename()).native();
131  } break;
132  default: // INTERNAL_ERROR.
134  << "Internal error: subInputFileName_() did not recognize "
135  "substitution code %if"
136  << *(match[4].first) << ".\n";
137  break;
138  }
139  } else if (match[5].matched) { // Regex substitution.
140  // Decompose the regex;
141  syntax_option_type sflags{ECMAScript};
142  match_flag_type mflags{match_default};
143  bool global{false};
144  if (match[7].matched) { // Options.
145  for (auto c : match[7].str()) {
146  switch (c) {
147  case 'i':
148  sflags |= icase;
149  break;
150  case 'g':
151  global = true;
152  break;
153  default: // INTERNAL ERROR.
155  << "Internal error: subInputFileName_() did not recognize "
156  "regex flag '"
157  << c << "'.\n";
158  break;
159  }
160  }
161  }
162  if (!global) {
163  mflags |= format_first_only;
164  }
165  boost::regex const dsub{match[5].str(), sflags};
166  result = regex_replace(
167  stats_.lastOpenedInputFile(), dsub, match[6].str(), mflags);
168  } else { // INTERNAL ERROR.
170  << "Internal error: subInputFileName_() called for unknown reasons "
171  "with pattern "
172  << match[0].str() << ".\n";
173  }
174  } else {
175  result = "-";
176  }
177  return result;
178 }
179 
181 art::PostCloseFileRenamer::subTimestamp_(boost::smatch const& match) const
182 {
184  switch (*(match[3].first)) {
185  case 'o': // Open.
186  result = to_string(stats_.outputFileOpenTime());
187  break;
188  case 'c': // Close.
190  break;
191  case 'r': // Start of Run with lowest number.
192  result = to_string(stats_.lowestRunStartTime());
193  break;
194  case 'R': // Start of Run with highest number.
196  break;
197  case 's': // Start of SubRun with lowest number.
199  break;
200  case 'S': // Start of SubRun with highest number.
202  break;
203  default: // INTERNAL ERROR.
205  << "Internal error: subTimestamp_() did not recognize substitution "
206  "code %t"
207  << *(match[3].first) << ".\n";
208  break;
209  }
210  return result;
211 }
212 
215  boost::smatch const& match) const
216 {
217  bool const did_match = match[1].matched;
218  std::string const format_string = match[1].str();
219  auto zero_fill = [did_match, &format_string](std::ostringstream& os,
220  auto const& num) {
221  if (did_match) {
222  os << std::setfill('0') << std::setw(std::stoul(format_string));
223  }
224  os << num;
225  };
226 
228  std::ostringstream num_str;
229 
230  switch (*(match[2].first)) {
231  case '#':
232  // In order to get the indexing correct, we cannot yet
233  // substitute the index. We must wait until the entire filename
234  // as been assembled, with all other substitutions evaluated.
235  // At this point, we need to restore the original pattern.
236  num_str << "%" << match[1].str() << "#";
237  break;
238  case 'r':
239  if (stats_.lowestRunID().isValid()) {
240  zero_fill(num_str, stats_.lowestRunID().run());
241  }
242  break;
243  case 'R':
244  if (stats_.highestRunID().isValid()) {
245  zero_fill(num_str, stats_.highestRunID().run());
246  }
247  break;
248  case 's':
249  if (stats_.lowestSubRunID().isValid()) {
250  zero_fill(num_str, stats_.lowestSubRunID().subRun());
251  }
252  break;
253  case 'S':
254  if (stats_.highestSubRunID().isValid()) {
255  zero_fill(num_str, stats_.highestSubRunID().subRun());
256  }
257  break;
258  default: // INTERNAL ERROR.
259  break;
260  }
261  result = num_str.str();
262  if (result.empty()) {
263  result = "-";
264  }
265  return result;
266 }
267 
270 {
271  std::string const finalPattern = applySubstitutionsNoIndex_(toPattern);
272  auto const components = detail::componentsFromPattern(finalPattern);
273 
274  // Get index for the processed pattern, incrementing if a file-close
275  // has been recorded.
276  auto& index = indexForProcessedPattern_[components];
277  if (stats_.fileCloseRecorded()) {
278  ++index;
279  }
280 
281  return components.fileNameWithIndex(index);
282 }
283 
286  std::string const& toPattern)
287 {
288  std::string const& newFile = applySubstitutions(toPattern);
289  boost::system::error_code ec;
290  bfs::rename(inPath, newFile, ec);
291  if (ec) {
292  // Fail (different filesystems? Try copy / delete instead).
293  // This attempt will throw on failure.
294  bfs::copy_file(inPath, newFile, bfs::copy_option::overwrite_if_exists);
295  bfs::remove(inPath);
296  }
297  return newFile;
298 }
std::string subFilledNumericNoIndex_(boost::smatch const &match) const
std::map< detail::FileNameComponents, std::size_t > indexForProcessedPattern_
std::string const & processName() const
decltype(auto) constexpr cend(T &&obj)
ADL-aware version of std::cend.
Definition: StdUtils.h:87
static QCString result
SubRunID const & lowestSubRunID() const
std::string string
Definition: nybbler.cc:12
std::string const & moduleLabel() const
boost::posix_time::ptime lowestRunStartTime() const
RunNumber_t run() const
Definition: RunID.h:64
bool isValid() const
Definition: SubRunID.h:97
std::string applySubstitutionsNoIndex_(std::string const &filePattern) const
PostCloseFileRenamer(FileStatsCollector const &stats)
boost::posix_time::ptime outputFileOpenTime() const
FileStatsCollector const & stats_
SubRunID const & highestSubRunID() const
FileNameComponents componentsFromPattern(std::string const &pattern)
Q_EXPORT QTSManip setw(int w)
Definition: qtextstream.h:331
boost::posix_time::ptime highestRunStartTime() const
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
boost::posix_time::ptime highestSubRunStartTime() const
std::string const & lastOpenedInputFile() const
std::string applySubstitutions(std::string const &filePattern)
std::string maybeRenameFile(std::string const &inPath, std::string const &toPattern)
decltype(auto) constexpr cbegin(T &&obj)
ADL-aware version of std::cbegin.
Definition: StdUtils.h:82
bool isValid() const
Definition: RunID.h:70
boost::posix_time::ptime lowestSubRunStartTime() const
SubRunNumber_t subRun() const
Definition: SubRunID.h:91
std::string subTimestamp_(boost::smatch const &match) const
Q_EXPORT QTSManip setfill(int f)
Definition: qtextstream.h:337
std::string to_string(ModuleType const mt)
Definition: ModuleType.h:34
static QCString str
Definition: se.py:1
std::string subInputFileName_(boost::smatch const &match) const
boost::posix_time::ptime outputFileCloseTime() const
def rename(src, dest)