]> git.stg.codes - stg.git/blob - projects/sgconf/options.cpp
Verbose test reports on failures.
[stg.git] / projects / sgconf / options.cpp
1 /*
2  *    This program is free software; you can redistribute it and/or modify
3  *    it under the terms of the GNU General Public License as published by
4  *    the Free Software Foundation; either version 2 of the License, or
5  *    (at your option) any later version.
6  *
7  *    This program is distributed in the hope that it will be useful,
8  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *    GNU General Public License for more details.
11  *
12  *    You should have received a copy of the GNU General Public License
13  *    along with this program; if not, write to the Free Software
14  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
15  */
16
17 /*
18  *    Author : Maxim Mamontov <faust@stargazer.dp.ua>
19  */
20
21 #include "options.h"
22
23 #include "action.h"
24 #include "parser_state.h"
25
26 #include "stg/common.h"
27
28 #include <fstream>
29 #include <sstream>
30 #include <iostream>
31 #include <functional>
32 #include <algorithm>
33
34 #include <unistd.h>
35
36 namespace
37 {
38
39 template <class C>
40 void ReadConfigFile(const std::string & filePath, void (C::* callback)(const std::string&, const std::string&), C * obj)
41 {
42     std::ifstream stream(filePath.c_str());
43     std::string line;
44     size_t num = 0;
45     while (std::getline(stream, line))
46     {
47         ++num;
48         line = Trim(line);
49         std::string::size_type pos = line.find_first_of('#');
50         if (pos != std::string::npos)
51             line = line.substr(0, pos);
52         if (line.empty())
53             continue;
54         pos = line.find_first_of('=');
55         if (pos == std::string::npos)
56         {
57             std::ostringstream error;
58             error << "Bad file format, missing '=' in '" << filePath << ":" << num << "'.";
59             throw std::runtime_error(error.str().c_str());
60         }
61         (obj->*callback)(Trim(line.substr(0, pos)), Trim(line.substr(pos + 1, line.length() - pos - 1)));
62     }
63 }
64
65 } // namespace anonymous
66
67 using SGCONF::OPTION;
68 using SGCONF::OPTION_BLOCK;
69 using SGCONF::OPTION_BLOCKS;
70 using SGCONF::ACTION;
71 using SGCONF::PARSER_STATE;
72
73 OPTION::OPTION(const std::string & shortName,
74                const std::string & longName,
75                std::unique_ptr<ACTION> action,
76                const std::string & description)
77     : m_shortName(shortName),
78       m_longName(longName),
79       m_action(std::move(action)),
80       m_description(description)
81 {
82 }
83
84 OPTION::OPTION(const std::string & longName,
85                std::unique_ptr<ACTION> action,
86                const std::string & description)
87     : m_longName(longName),
88       m_action(std::move(action)),
89       m_description(description)
90 {
91 }
92
93 void OPTION::Help(size_t level) const
94 {
95     if (!m_action)
96         throw ERROR("Option is not defined.");
97     std::string indent(level, '\t');
98     std::cout << indent;
99     if (!m_shortName.empty())
100         std::cout << "-" << m_shortName << ", ";
101     std::cout << "--" << m_longName << " " << m_action->ParamDescription()
102               << "\t" << m_description << m_action->DefaultDescription() << "\n";
103     m_action->Suboptions().Help(level);
104 }
105
106 bool OPTION::Check(const char * arg) const
107 {
108     if (arg == NULL)
109         return false;
110
111     if (*arg++ != '-')
112         return false;
113
114     if (*arg == '-')
115         return m_longName == arg + 1;
116
117     return m_shortName == arg;
118 }
119
120 PARSER_STATE OPTION::Parse(int argc, char ** argv, void * data)
121 {
122     if (!m_action)
123         throw ERROR("Option is not defined.");
124     try
125     {
126         return m_action->Parse(argc, argv, data);
127     }
128     catch (const ACTION::ERROR & ex)
129     {
130         if (m_longName.empty())
131             throw ERROR("-" + m_shortName + ": " + ex.what());
132         else
133             throw m_shortName.empty() ? ERROR("--" + m_longName + ": " + ex.what())
134                                       : ERROR("--" + m_longName + ", -" + m_shortName + ": " + ex.what());
135     }
136 }
137
138 void OPTION::ParseValue(const std::string & value)
139 {
140     if (!m_action)
141         throw ERROR("Option is not defined.");
142     try
143     {
144         return m_action->ParseValue(value);
145     }
146     catch (const ACTION::ERROR & ex)
147     {
148         throw ERROR(m_longName + ": " + ex.what());
149     }
150 }
151
152 OPTION_BLOCK & OPTION_BLOCK::Add(const std::string & shortName,
153                                  const std::string & longName,
154                                  std::unique_ptr<ACTION> action,
155                                  const std::string & description)
156 {
157     m_options.emplace_back(shortName, longName, std::move(action), description);
158     return *this;
159 }
160
161 OPTION_BLOCK & OPTION_BLOCK::Add(const std::string & longName,
162                                  std::unique_ptr<ACTION> action,
163                                  const std::string & description)
164 {
165     m_options.emplace_back(longName, std::move(action), description);
166     return *this;
167 }
168
169 void OPTION_BLOCK::Help(size_t level) const
170 {
171     if (m_options.empty())
172         return;
173     if (!m_description.empty())
174         std::cout << m_description << ":\n";
175     for (const auto& option : m_options)
176         option.Help(level + 1);
177 }
178
179 PARSER_STATE OPTION_BLOCK::Parse(int argc, char ** argv, void * data)
180 {
181     PARSER_STATE state(false, argc, argv);
182     if (state.argc == 0)
183         return state;
184     while (state.argc > 0 && !state.stop)
185     {
186         const auto it = std::find_if(m_options.begin(), m_options.end(), [&state](const auto& opt){ return opt.Check(*state.argv); });
187         if (it != m_options.end())
188             state = it->Parse(--state.argc, ++state.argv, data);
189         else
190             break;
191     }
192     return state;
193 }
194
195 void OPTION_BLOCK::ParseFile(const std::string & filePath)
196 {
197     if (access(filePath.c_str(), R_OK))
198         throw ERROR("File '" + filePath + "' does not exists.");
199     ReadConfigFile(filePath, &OPTION_BLOCK::OptionCallback, this);
200 }
201
202 void OPTION_BLOCK::OptionCallback(const std::string & key, const std::string & value)
203 {
204     for (auto& option : m_options)
205         if (option.Name() == key)
206             option.ParseValue(value);
207 }
208
209 void OPTION_BLOCKS::Help(size_t level) const
210 {
211     for (const auto& block : m_blocks)
212     {
213         block.Help(level);
214         std::cout << "\n";
215     }
216 }
217
218 PARSER_STATE OPTION_BLOCKS::Parse(int argc, char ** argv)
219 {
220     PARSER_STATE state(false, argc, argv);
221     auto it = m_blocks.begin();
222     while (state.argc > 0 && !state.stop && it != m_blocks.end())
223     {
224         state = it->Parse(state.argc, state.argv);
225         ++it;
226     }
227     return state;
228 }