ifw-daq  3.0.0-pre2
IFW Data Acquisition modules
main.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_dpm_merge
4  * @copyright ESO - European Southern Observatory
5  */
6 #include <filesystem>
7 #include <fstream>
8 #include <iostream>
9 
10 #include <boost/program_options.hpp>
11 #include <fmt/format.h>
12 #include <log4cplus/configurator.h>
13 #include <log4cplus/logger.h>
14 #include <log4cplus/loggingmacros.h>
15 #include <nlohmann/json.hpp>
16 
17 #include <daq/json/dpSpec.hpp>
18 #include <daq/error/report.hpp>
19 
20 #include "entrypoint.hpp"
21 #include "sources.hpp"
22 
23 namespace error {
24 /**
25  * Error codes
26  */
27 enum {
28  /** Invalid program arguments */
30 
31  /** @name DpSpec errors */
32  ///@{
33  /** Data product specification file not found */
35  /** Invalid Data Product Specification JSON */
37  /** Invalid Data Product Specification */
39  ///@}
40 
41  /** @name Source errors */
42  ///@{
44  ///@}
45 
46  Unknown = 255,
47 };
48 } // namespace error
49 
51  ExitWithErrorCode(int code) : m_code(code) {
52  }
53  int GetCode() const {
54  return m_code;
55  }
56 
57 private:
58  int m_code;
59 };
60 
61 std::pair<std::string, std::string> CustomParser(std::string const& element) {
62  if (element == "-") {
63  return std::make_pair("specification-file", "-");
64  }
65  return {};
66 }
67 
68 int main(int argc, char** argv) {
69  log4cplus::initialize();
70  // note: last arg configures so default console appender logs to stderr
71  log4cplus::BasicConfigurator config(log4cplus::Logger::getDefaultHierarchy(), true);
72  config.configure();
73 
74  namespace po = boost::program_options;
75 
76  auto logger = log4cplus::Logger::getInstance("daq.dpmmerge");
77 
78  const std::string synopsis("daqDpmMerge [options] <specification-file>");
79 
80  po::positional_options_description positional;
81  positional.add("specification-file", 1);
82 
83  std::string spec_file;
84  po::options_description options(synopsis);
85  // clang-format off
86  options.add_options()
87  ("help,h",
88  "produce help message (also use this option for each command to get relevant help)")
89  ("version",
90  "print version")
91  ("root",
92  po::value<std::string>(),
93  "specifies root directory from which relative source file paths in specification-file will"
94  " be resolved as well as the default output directory. If not specified it will use the "
95  "root of the specification-file file or if "
96  "specification-file is not a regular file it will use current working directory.")
97  ("outfile,o",
98  po::value<std::string>(),
99  "FITS output file name, e.g. `-o myfits.fits`. By default the output name will be "
100  "derived using the specification-file `fileId` property: `<fileId>.fits`. "
101  "Relative paths are relative the root directory.")
102  ("resolver",
103  po::value<std::string>(),
104  "specifies optional FITS source resolver which resolves location of input FITS files "
105  "to their location on this host. If specification references FITS files this must be "
106  "provided.")
107  ("json",
108  "output status messages to standard out in JSON format")
109  ("dry-run",
110  "skips operations with visible side-effects, useful for validating inputs")
111  ;
112  po::options_description hidden("Hidden options");
113  hidden.add_options()
114  ("specification-file", po::value<std::string>(&spec_file), "input file")
115  ;
116  // clang-format on
117 
118  po::options_description all("All");
119  all.add(options).add(hidden);
120 
121  po::variables_map vm;
122  try {
123  auto parsed = po::command_line_parser(argc, argv)
124  .options(all)
125  .positional(positional)
126  .extra_parser(CustomParser)
127  .run();
128  po::store(parsed, vm);
129  if (vm.count("help")) {
130  std::cerr << options << std::endl;
131  return 0;
132  }
133  if (vm.count("version")) {
134  std::cerr << "daqDpmMerge " << VERSION
135 #if defined(DEBUG)
136  << " (debug build)"
137 #endif
138  << std::endl;
139  return 0;
140  }
141 
142  // Both positional and hidden option --specification-file ends up in "specification-file"
143  // in vm variables_map.
144  if (!vm.count("specification-file")) {
145  std::cerr << "argument error: specification-file not provided" << std::endl;
147  } else {
148  spec_file = vm["specification-file"].as<std::string>();
149  }
150 
151  // Determine root path
152  auto root_arg = vm.count("root") ? vm["root"].as<std::string>() : std::string();
153  auto root_path = std::filesystem::path();
154  if (!root_arg.empty()) {
155  // If explicitly specified, use that argument.
156  root_path =
157  std::filesystem::path(root_arg, std::filesystem::path::format::native_format);
158  if (!std::filesystem::is_directory(root_path)) {
159  std::cerr << "argument error: --root argument \"" << root_arg
160  << "\" is not a valid directory" << std::endl;
162  }
163  } else if (std::filesystem::is_regular_file(spec_file)) {
164  // For regular files we fall back to using the directory containing the input file
165  root_path = std::filesystem::path(spec_file).parent_path();
166  } else {
167  // As a last fallback we use current working directory.
168  root_path = std::filesystem::current_path();
169  }
170 
171  // Optional output filename
172  std::optional<std::filesystem::path> out_path;
173  if (vm.count("outfile")) {
174  auto out = std::filesystem::path(vm["outfile"].as<std::string>());
175  if (out.is_relative()) {
176  out = root_path / out;
177  }
178  if (std::filesystem::exists(out)) {
179  std::cerr << "argument error: --outfile argument " << out << " exists" << std::endl;
181  }
182  out_path = out;
183  }
184 
185  // Optional source resolver
186  daq::dpm::SourceResolver resolver;
187  if (vm.count("resolver")) {
188  auto resolver_path = std::filesystem::path(vm["resolver"].as<std::string>());
189  if (resolver_path.is_relative()) {
190  resolver_path = root_path / resolver_path;
191  }
192  if (!std::filesystem::exists(resolver_path)) {
193  std::cerr << "argument error: --resolver argument " << resolver_path << " not found"
194  << std::endl;
196  }
197  std::ifstream file(resolver_path);
198  if (file.fail()) {
199  std::cerr << "error: failed to open specification with path '" << resolver_path
200  << "'\n";
202  }
203  auto json = nlohmann::json::parse(file);
204  resolver.SetMapping(json.get<daq::dpm::SourceResolver::Mapping>());
205  }
206 
207  // Load JSON
208  nlohmann::json json_spec;
209  if (spec_file == "-") {
210  // Read from stdin
211  json_spec = nlohmann::json::parse(std::cin);
212  } else {
213  // Read from file
214  std::ifstream file(spec_file);
215  if (file.fail()) {
216  std::cerr << "error: failed to open specification with path '" << spec_file
217  << "'\n";
218  return error::DpSpecNotFound;
219  }
220  json_spec = nlohmann::json::parse(file);
221  }
222 
223  auto use_json = vm.count("json");
224  auto dry_run = vm.count("dry-run");
225  // At this point we have the parsed specification and all arguments.
226  // So let's merge...
227  try {
228  auto dp_spec = daq::json::ParseDpSpec(json_spec);
230  root_path, out_path, dp_spec, resolver, dry_run, use_json);
231  } catch (daq::dpm::SourceNotFound const& e) {
232  std::throw_with_nested(ExitWithErrorCode(error::SourceNotFound));
233  } catch (daq::dpm::merge::SourceNotFoundError const& e) {
234  std::throw_with_nested(ExitWithErrorCode(error::SourceNotFound));
235  } catch (daq::json::DpSpecError const&) {
236  std::throw_with_nested(daq::json::DpSpecError(
237  fmt::format("Failed to parse Data Product Specification '{}'", spec_file)));
238  }
239  } catch (ExitWithErrorCode const& e) {
241  return e.GetCode();
242  } catch (po::error const& e) {
245  } catch (nlohmann::json::parse_error const& e) {
246  std::cerr << "error: json parsing failed: " << e.what() << std::endl;
248  } catch (nlohmann::json::exception const& e) {
249  std::cerr << "error: json error: " << e.what() << std::endl;
251  } catch (daq::json::DpSpecError const& e) {
253  return error::DpSpecInvalid;
254  } catch (std::exception const& e) {
256  }
257  return error::Unknown;
258 }
Provides location of fits source file.
void SetMapping(Mapping mapping) noexcept
std::map< SourceFile, std::string > Mapping
int main(int argc, char **argv)
Definition: main.cpp:68
std::pair< std::string, std::string > CustomParser(std::string const &element)
Definition: main.cpp:61
int Entrypoint(fs::path const &root, std::optional< fs::path > const &opt_out_path, json::DpSpec const &spec, SourceResolver const &resolver, bool dry_run, bool use_json)
Definition: entrypoint.cpp:145
void ReportNestedExceptions(std::ostream &os) noexcept
Definition: report.cpp:50
DpSpec ParseDpSpec(Json const &json)
Parse JSON to construct the DpSpec structure.
Definition: dpSpec.cpp:47
Definition: main.cpp:23
@ Unknown
Definition: main.cpp:46
@ InvalidProgramArgument
Invalid program arguments.
Definition: main.cpp:29
@ DpSpecNotFound
Data product specification file not found.
Definition: main.cpp:34
@ DpSpecInvalid
Invalid Data Product Specification.
Definition: main.cpp:38
@ DpSpecInvalidJson
Invalid Data Product Specification JSON
Definition: main.cpp:36
@ SourceNotFound
Definition: main.cpp:43
ExitWithErrorCode(int code)
Definition: main.cpp:51
int GetCode() const
Definition: main.cpp:53
Source file not found.
Definition: sources.hpp:108