Crombie Tools
Parse.h
Go to the documentation of this file.
1 #ifndef CROMBIE_PARSE_H
2 #define CROMBIE_PARSE_H
3 
4 /**
5  This header file includes common functions that are used to interpret configuration files
6  */
7 
8 #include <istream>
9 #include <regex>
10 #include <string>
11 #include <list>
12 #include <vector>
13 
14 #include "crombie/Types.h"
15 #include "crombie/Debug.h"
16 #include "crombie/Misc.h"
17 
18 namespace crombie {
19  namespace Parse {
20 
21  namespace {
22  /// Extract shell commands from lines and replace them in the line
23  std::string do_shell (const std::string& line) {
24  std::regex expr {"`([^`]+)`"};
25  std::smatch match;
26  std::string output = line;
27  while (std::regex_search(output, match, expr))
28  output = output.replace(match.position(), match.position() + match.length(), Misc::shell(match[1]));
29 
30  return output;
31  }
32 
33  /// Remove comments from lines
34  std::string remove_comments (const std::string& line) {
35  return line.substr(0, line.find("! "));
36  }
37 
38  /// Substitute environment variables when needed
39  std::string envsubstitution(const std::string& line) {
40  auto startpos = line.find("env{");
41  if (startpos != std::string::npos) {
42  startpos += 4; // Don't pick out "env{"
43  auto splitpos = line.find(':', startpos);
44  auto endpos = line.find('}', splitpos);
45  if (splitpos != std::string::npos and endpos != std::string::npos) {
46  auto envvar = line.substr(startpos, splitpos - startpos);
47  splitpos += 1; // Skip over ':'
48  auto envdefault = line.substr(splitpos, endpos - splitpos);
49  Debug::Debug(__PRETTY_FUNCTION__, envvar, envdefault);
50  startpos -= 4; // Now replace "env{"
51  endpos += 1; // And '}'
52  auto output = line;
53  return output.replace(startpos, endpos - startpos, Misc::env(envvar, envdefault));
54  }
55  }
56  return line;
57  }
58 
59  /// Filter lines according to Parser expectations
60  std::string parse (const std::string& line) {
61  auto output = do_shell(envsubstitution(line));
62  Debug::Debug(__PRETTY_FUNCTION__, "IN:", line, "\nOUT:", output);
63  return output;
64  }
65 
66  /// Do the expansion of tokens
67  std::string expand(const std::string &line) {
68  std::string output = line;
69  // Match the outer <> and the first {} pair you come to
70  std::regex expand_op {"<(.*?)\\{([^\\}]*)\\}(.*?)>(?!>)"};
71  std::smatch matches;
72  while(std::regex_search(output, matches, expand_op)) {
73  Debug::Debug(__PRETTY_FUNCTION__, output);
74  std::string begin = matches[1];
75  auto tokens = Misc::tokenize(matches[2]);
76  std::string end = matches[3];
77  std::string replace {};
78  for (auto& token : tokens) {
79  if (replace.size())
80  replace += ' ';
81  replace += begin + token + end;
82  }
83  Debug::Debug(__PRETTY_FUNCTION__, begin, end, replace);
84 
85  output.replace(matches.position(), matches.length(), replace);
86  }
87  return output;
88  }
89 
90  /// Do the multi-line expansion
91  std::vector<std::string> multiline(const std::string& line) {
92  std::list<std::string> output;
93  output.push_back(line);
94  bool need_checked = true;
95  // 1st group is the main line to keep
96  // 2nd group is the character to replace
97  // 3rd group is the different replacements for each line
98  std::regex multi_op {"(.*)\\s+;(\\S?)\\s+(.*)"};
99  std::smatch matches;
100  while(need_checked) {
101  need_checked = false;
102  for (auto check = output.begin(); check != output.end();) {
103  if (std::regex_match(*check, matches, multi_op)) {
104  need_checked = true;
105  auto remove = check;
106  std::string toreplace = matches[2];
107  if (not toreplace.size())
108  toreplace += '$';
109  auto replacements = Misc::tokenize(matches[3]);
110 
111  for (auto& sub : replacements) {
112  std::string newline = matches[1];
113  for(auto pos = newline.find(toreplace); pos != std::string::npos; pos = newline.find(toreplace))
114  newline.replace(pos, 1, sub);
115 
116  output.insert(remove, std::move(newline));
117  }
118 
119  output.erase(remove, ++check);
120  }
121  else // Just move along
122  ++check;
123  }
124  }
125  return Misc::comprehension<std::string>(output, parse);
126  }
127  }
128 
129  /// Parse an input stream and return a list of parsed lines, with empty lines removed
130  Types::strings parse (std::istream& is) {
131  Types::strings output{};
132  for (std::string raw; std::getline(is, raw); ) {
133  Debug::Debug(__PRETTY_FUNCTION__, "Raw line:", raw);
134  auto&& line = remove_comments(raw);
135  if (line.size())
136  for (auto&& expanded : multiline(expand(line))) {
137  Debug::Debug(__PRETTY_FUNCTION__, "Expanded:", expanded);
138  output.push_back(expanded);
139  }
140  }
141  return output;
142  }
143 
144  }
145 }
146 
147 #endif