ifw-daq 3.1.0
IFW Data Acquisition modules
Loading...
Searching...
No Matches
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/fileappender.h>
14#include <log4cplus/logger.h>
15#include <log4cplus/loggingmacros.h>
16#include <nlohmann/json.hpp>
17
18#include <daq/error/report.hpp>
19#include <daq/json/dpSpec.hpp>
20
21#include "entrypoint.hpp"
22#include "sources.hpp"
23
24namespace error {
25/**
26 * Error codes
27 */
28enum {
29 /** Invalid program arguments */
31
32 /** @name DpSpec errors */
33 ///@{
34 /** Data product specification file not found */
36 /** Invalid Data Product Specification JSON */
38 /** Invalid Data Product Specification */
40 ///@}
41
42 /** @name Source errors */
43 ///@{
45 ///@}
46
47 Unknown = 255,
48};
49} // namespace error
50
52 ExitWithErrorCode(int code) : m_code(code) {
53 }
54 int GetCode() const {
55 return m_code;
56 }
57
58private:
59 int m_code;
60};
61
62static std::pair<std::string, std::string> CustomParser(std::string const& element) {
63 if (element == "-") {
64 return std::make_pair("specification-file", "-");
65 }
66 return {};
67}
68
69static auto Resolve(std::filesystem::path const& path, std::filesystem::path const& root)
70 -> std::filesystem::path {
71 if (path.is_relative()) {
72 return root / path;
73 } else {
74 return path;
75 }
76}
77
78static void LogArgs(int argc, char** argv) {
79 std::stringstream ss;
80 std::for_each(argv, argv + argc, [&](char* arg) {
81 if (arg > argv[0]) {
82 ss << " " << arg;
83 } else {
84 ss << arg;
85 }
86 });
87
88 LOG4CPLUS_INFO("daq.dpmmerge",
89 "Started in " << std::filesystem::current_path() << " as: " << ss.str());
90}
91
92static void InitFileAppender(std::filesystem::path const& path) {
93 // Note that PropertyConfigurator::configure() will reset configuration so we avoid doing that.
94 using log4cplus::FileAppender;
95 using log4cplus::PatternLayout;
96 auto appender = log4cplus::SharedAppenderPtr(
97 new FileAppender(path.string(), std::ios_base::out | std::ios_base::app));
98 appender->setLayout(std::make_unique<PatternLayout>("%D{%Y-%m-%dT%H:%M:%S.%q+0000} %-5p %m%n"));
99 log4cplus::Logger::getRoot().addAppender(appender);
100}
101
102int main(int argc, char** argv) {
103 log4cplus::initialize();
104 // note: last arg configures so default console appender logs to stderr
105 log4cplus::BasicConfigurator config(log4cplus::Logger::getDefaultHierarchy(), true);
106 config.configure();
107
108 namespace po = boost::program_options;
109
110 auto logger = log4cplus::Logger::getInstance("daq.dpmmerge");
111
112 const std::string synopsis("daqDpmMerge [options] <specification-file>");
113
114 po::positional_options_description positional;
115 positional.add("specification-file", 1);
116
117 std::string spec_file;
118 po::options_description options(synopsis);
119 // clang-format off
120 options.add_options()
121 ("help,h",
122 "produce help message (also use this option for each command to get relevant help)")
123 ("version",
124 "print version")
125 ("root",
126 po::value<std::string>(),
127 "specifies root directory from which relative source file paths in specification-file will"
128 " be resolved as well as the default output directory. If not specified it will use the "
129 "root of the specification-file file or if "
130 "specification-file is not a regular file it will use current working directory.")
131 ("outfile,o",
132 po::value<std::string>(),
133 "FITS output file name, e.g. `-o myfits.fits`. By default the output name will be "
134 "derived using the specification-file `fileId` property: `<fileId>.fits`. "
135 "Relative paths are relative the root directory.")
136 ("resolver",
137 po::value<std::string>(),
138 "specifies optional FITS source resolver which resolves location of input FITS files "
139 "to their location on this host. If specification references FITS files this must be "
140 "provided.")
141 ("json",
142 "output status messages to standard out in JSON format")
143 ("logfile,l",
144 po::value<std::string>(),
145 "Write log output, in addition to the console, to the specified file.")
146 ("dry-run",
147 "skips operations with visible side-effects, useful for validating inputs")
148 ;
149 po::options_description hidden("Hidden options");
150 hidden.add_options()
151 ("specification-file", po::value<std::string>(&spec_file), "input file")
152 ;
153 // clang-format on
154
155 po::options_description all("All");
156 all.add(options).add(hidden);
157
158 po::variables_map vm;
159 try {
160 auto parsed = po::command_line_parser(argc, argv)
161 .options(all)
162 .positional(positional)
163 .extra_parser(CustomParser)
164 .run();
165 po::store(parsed, vm);
166 if (vm.count("help")) {
167 std::cerr << options << '\n';
168 return 0;
169 }
170 if (vm.count("version")) {
171 std::cerr << "daqDpmMerge " << VERSION
172#if defined(DEBUG)
173 << " (debug build)"
174#endif
175 << '\n';
176 return 0;
177 }
178 // Both positional and hidden option --specification-file ends up in "specification-file"
179 // in vm variables_map.
180 if (!vm.count("specification-file")) {
181 std::cerr << "argument error: specification-file not provided\n";
183 } else {
184 spec_file = vm["specification-file"].as<std::string>();
185 }
186
187 // Determine root path
188 auto root_arg = vm.count("root") ? vm["root"].as<std::string>() : std::string();
189 auto root_path = std::filesystem::path();
190 if (!root_arg.empty()) {
191 // If explicitly specified, use that argument.
192 root_path =
193 std::filesystem::path(root_arg, std::filesystem::path::format::native_format);
194 if (!std::filesystem::is_directory(root_path)) {
195 std::cerr << "argument error: --root argument \"" << root_arg
196 << "\" is not a valid directory\n";
198 }
199 } else if (std::filesystem::is_regular_file(spec_file)) {
200 // For regular files we fall back to using the directory containing the input file
201 root_path = std::filesystem::path(spec_file).parent_path();
202 } else {
203 // As a last fallback we use current working directory.
204 root_path = std::filesystem::current_path();
205 }
206
207 auto log_file = vm.count("logfile") ? vm["logfile"].as<std::string>() : std::string();
208 if (!log_file.empty()) {
209 // Configure logger to file
210 auto log_path = Resolve(std::filesystem::path(log_file), root_path);
211 InitFileAppender(log_path);
212 }
213
214 // Optional output filename
215 std::optional<std::filesystem::path> out_path;
216 if (vm.count("outfile")) {
217 auto out = Resolve(std::filesystem::path(vm["outfile"].as<std::string>()), root_path);
218 ;
219 if (std::filesystem::exists(out)) {
220 std::cerr << "argument error: --outfile argument " << out << " exists\n";
222 }
223 out_path = out;
224 }
225
226 // Optional source resolver
228 if (vm.count("resolver")) {
229 auto resolver_path =
230 Resolve(std::filesystem::path(vm["resolver"].as<std::string>()), root_path);
231 if (!std::filesystem::exists(resolver_path)) {
232 std::cerr << "argument error: --resolver argument " << resolver_path
233 << " not found\n";
235 }
236 std::ifstream file(resolver_path);
237 if (file.fail()) {
238 std::cerr << "error: failed to open specification with path '" << resolver_path
239 << '\n';
241 }
242 auto json = nlohmann::json::parse(file);
243 resolver.SetMapping(json.get<daq::dpm::SourceResolver::Mapping>());
244 }
245
246 // Load JSON
247 nlohmann::json json_spec;
248 if (spec_file == "-") {
249 // Read from stdin
250 json_spec = nlohmann::json::parse(std::cin);
251 } else {
252 // Read from file
253 std::ifstream file(spec_file);
254 if (file.fail()) {
255 std::cerr << "error: failed to open specification with path '" << spec_file << '\n';
257 }
258 json_spec = nlohmann::json::parse(file);
259 }
260
261 auto use_json = vm.count("json");
262 auto dry_run = vm.count("dry-run");
263 // At this point we have the parsed specification and all arguments.
264 // So let's merge...
265 try {
266 auto dp_spec = daq::json::ParseDpSpec(json_spec);
267 LogArgs(argc, argv);
269 root_path, out_path, dp_spec, resolver, dry_run, use_json);
270 } catch (daq::dpm::SourceNotFound const& e) {
271 std::throw_with_nested(ExitWithErrorCode(error::SourceNotFound));
272 } catch (daq::dpm::merge::SourceNotFoundError const& e) {
273 std::throw_with_nested(ExitWithErrorCode(error::SourceNotFound));
274 } catch (daq::json::DpSpecError const&) {
275 std::throw_with_nested(daq::json::DpSpecError(
276 fmt::format("Failed to parse Data Product Specification '{}'", spec_file)));
277 }
278 } catch (ExitWithErrorCode const& e) {
280 return e.GetCode();
281 } catch (po::error const& e) {
284 } catch (nlohmann::json::parse_error const& e) {
285 std::cerr << "error: json parsing failed: " << e.what() << '\n';
287 } catch (nlohmann::json::exception const& e) {
288 std::cerr << "error: json error: " << e.what() << '\n';
290 } catch (daq::json::DpSpecError const& e) {
293 } catch (std::exception const& e) {
295 }
296 return error::Unknown;
297}
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:102
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:24
@ Unknown
Definition: main.cpp:47
@ InvalidProgramArgument
Invalid program arguments.
Definition: main.cpp:30
@ DpSpecNotFound
Data product specification file not found.
Definition: main.cpp:35
@ DpSpecInvalid
Invalid Data Product Specification.
Definition: main.cpp:39
@ DpSpecInvalidJson
Invalid Data Product Specification JSON
Definition: main.cpp:37
@ SourceNotFound
Definition: main.cpp:44
ExitWithErrorCode(int code)
Definition: main.cpp:52
int GetCode() const
Definition: main.cpp:54
Source file not found.
Definition: sources.hpp:108