Commit fedf9d7b57297765c18b95aecc9625f0104a0fde

Authored by Jarryd Beck
1 parent fd5cdfd5

Refactor parser

Major refactor of the parsing code organisation to improve encapsulation
and not modify the input arguments. The returned result no longer has
pointers into the original option specification.
include/cxxopts.hpp
... ... @@ -30,6 +30,7 @@ THE SOFTWARE.
30 30 #include <exception>
31 31 #include <iostream>
32 32 #include <limits>
  33 +#include <list>
33 34 #include <map>
34 35 #include <memory>
35 36 #include <regex>
... ... @@ -1013,6 +1014,7 @@ namespace cxxopts
1013 1014 , m_value(std::move(val))
1014 1015 , m_count(0)
1015 1016 {
  1017 + m_hash = std::hash<std::string>{}(m_long + m_short);
1016 1018 }
1017 1019  
1018 1020 OptionDetails(const OptionDetails& rhs)
... ... @@ -1058,12 +1060,20 @@ namespace cxxopts
1058 1060 return m_long;
1059 1061 }
1060 1062  
  1063 + size_t
  1064 + hash() const
  1065 + {
  1066 + return m_hash;
  1067 + }
  1068 +
1061 1069 private:
1062 1070 std::string m_short;
1063 1071 std::string m_long;
1064 1072 String m_desc;
1065 1073 std::shared_ptr<const Value> m_value;
1066 1074 int m_count;
  1075 +
  1076 + size_t m_hash;
1067 1077 };
1068 1078  
1069 1079 struct HelpOptionDetails
... ... @@ -1198,45 +1208,65 @@ namespace cxxopts
1198 1208 std::string m_value;
1199 1209 };
1200 1210  
  1211 + using ParsedHashMap = std::unordered_map<size_t, OptionValue>;
  1212 + using NameHashMap = std::unordered_map<std::string, size_t>;
  1213 +
1201 1214 class ParseResult
1202 1215 {
1203 1216 public:
1204 1217  
1205   - ParseResult(
1206   - std::shared_ptr<
1207   - std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
1208   - >,
1209   - std::vector<std::string>,
1210   - bool allow_unrecognised,
1211   - int&, const char**&);
  1218 + ParseResult() {}
  1219 +
  1220 + ParseResult(const ParseResult&) = default;
  1221 +
  1222 + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args)
  1223 + : m_keys(std::move(keys))
  1224 + , m_values(std::move(values))
  1225 + , m_sequential(std::move(sequential))
  1226 + , m_unmatched(std::move(unmatched_args))
  1227 + {
  1228 + }
  1229 +
  1230 + ParseResult& operator=(ParseResult&&) = default;
  1231 + ParseResult& operator=(const ParseResult&) = default;
1212 1232  
1213 1233 size_t
1214 1234 count(const std::string& o) const
1215 1235 {
1216   - auto iter = m_options->find(o);
1217   - if (iter == m_options->end())
  1236 + auto iter = m_keys.find(o);
  1237 + if (iter == m_keys.end())
1218 1238 {
1219 1239 return 0;
1220 1240 }
1221 1241  
1222   - auto riter = m_results.find(iter->second);
  1242 + auto viter = m_values.find(iter->second);
  1243 +
  1244 + if (viter == m_values.end())
  1245 + {
  1246 + return 0;
  1247 + }
1223 1248  
1224   - return riter->second.count();
  1249 + return viter->second.count();
1225 1250 }
1226 1251  
1227 1252 const OptionValue&
1228 1253 operator[](const std::string& option) const
1229 1254 {
1230   - auto iter = m_options->find(option);
  1255 + auto iter = m_keys.find(option);
1231 1256  
1232   - if (iter == m_options->end())
  1257 + if (iter == m_keys.end())
1233 1258 {
1234 1259 throw_or_mimic<option_not_present_exception>(option);
1235 1260 }
1236 1261  
1237   - auto riter = m_results.find(iter->second);
  1262 + auto viter = m_values.find(iter->second);
  1263 +
  1264 + if (viter == m_values.end())
  1265 + {
  1266 + throw_or_mimic<option_not_present_exception>(option);
  1267 + }
1238 1268  
1239   - return riter->second;
  1269 + return viter->second;
1240 1270 }
1241 1271  
1242 1272 const std::vector<KeyValue>&
... ... @@ -1245,49 +1275,17 @@ namespace cxxopts
1245 1275 return m_sequential;
1246 1276 }
1247 1277  
1248   - private:
1249   -
1250   - void
1251   - parse(int& argc, const char**& argv);
1252   -
1253   - void
1254   - add_to_option(const std::string& option, const std::string& arg);
1255   -
1256   - bool
1257   - consume_positional(const std::string& a);
1258   -
1259   - void
1260   - parse_option
1261   - (
1262   - const std::shared_ptr<OptionDetails>& value,
1263   - const std::string& name,
1264   - const std::string& arg = ""
1265   - );
1266   -
1267   - void
1268   - parse_default(const std::shared_ptr<OptionDetails>& details);
1269   -
1270   - void
1271   - checked_parse_arg
1272   - (
1273   - int argc,
1274   - const char* argv[],
1275   - int& current,
1276   - const std::shared_ptr<OptionDetails>& value,
1277   - const std::string& name
1278   - );
1279   -
1280   - const std::shared_ptr<
1281   - std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
1282   - > m_options;
1283   - std::vector<std::string> m_positional;
1284   - std::vector<std::string>::iterator m_next_positional;
1285   - std::unordered_set<std::string> m_positional_set;
1286   - std::unordered_map<std::shared_ptr<OptionDetails>, OptionValue> m_results;
1287   -
1288   - bool m_allow_unrecognised;
  1278 + const std::vector<std::string>&
  1279 + unmatched() const
  1280 + {
  1281 + return m_unmatched;
  1282 + }
1289 1283  
  1284 + private:
  1285 + NameHashMap m_keys;
  1286 + ParsedHashMap m_values;
1290 1287 std::vector<KeyValue> m_sequential;
  1288 + std::vector<std::string> m_unmatched;
1291 1289 };
1292 1290  
1293 1291 struct Option
... ... @@ -1312,9 +1310,66 @@ namespace cxxopts
1312 1310 std::string arg_help_;
1313 1311 };
1314 1312  
  1313 + using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
  1314 + using PositionalList = std::vector<std::string>;
  1315 + using PositionalListIterator = PositionalList::const_iterator;
  1316 +
  1317 + class OptionParser
  1318 + {
  1319 + public:
  1320 + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
  1321 + : m_options(options)
  1322 + , m_positional(positional)
  1323 + , m_allow_unrecognised(allow_unrecognised)
  1324 + {
  1325 + }
  1326 +
  1327 + ParseResult
  1328 + parse(int argc, const char** argv);
  1329 +
  1330 + bool
  1331 + consume_positional(const std::string& a, PositionalListIterator& next);
  1332 +
  1333 + void
  1334 + checked_parse_arg
  1335 + (
  1336 + int argc,
  1337 + const char* argv[],
  1338 + int& current,
  1339 + const std::shared_ptr<OptionDetails>& value,
  1340 + const std::string& name
  1341 + );
  1342 +
  1343 + void
  1344 + add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
  1345 +
  1346 + void
  1347 + parse_option
  1348 + (
  1349 + const std::shared_ptr<OptionDetails>& value,
  1350 + const std::string& name,
  1351 + const std::string& arg = ""
  1352 + );
  1353 +
  1354 + void
  1355 + parse_default(const std::shared_ptr<OptionDetails>& details);
  1356 +
  1357 + private:
  1358 +
  1359 + void finalise_aliases();
  1360 +
  1361 + const OptionMap& m_options;
  1362 + const PositionalList& m_positional;
  1363 +
  1364 + std::vector<KeyValue> m_sequential;
  1365 + bool m_allow_unrecognised;
  1366 +
  1367 + ParsedHashMap m_parsed;
  1368 + NameHashMap m_keys;
  1369 + };
  1370 +
1315 1371 class Options
1316 1372 {
1317   - using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
1318 1373 public:
1319 1374  
1320 1375 explicit Options(std::string program, std::string help_string = "")
... ... @@ -1325,7 +1380,6 @@ namespace cxxopts
1325 1380 , m_show_positional(false)
1326 1381 , m_allow_unrecognised(false)
1327 1382 , m_options(std::make_shared<OptionMap>())
1328   - , m_next_positional(m_positional.end())
1329 1383 {
1330 1384 }
1331 1385  
... ... @@ -1358,7 +1412,7 @@ namespace cxxopts
1358 1412 }
1359 1413  
1360 1414 ParseResult
1361   - parse(int& argc, const char**& argv);
  1415 + parse(int argc, const char** argv);
1362 1416  
1363 1417 OptionAdder
1364 1418 add_options(std::string group = "");
... ... @@ -1444,11 +1498,13 @@ namespace cxxopts
1444 1498  
1445 1499 std::shared_ptr<OptionMap> m_options;
1446 1500 std::vector<std::string> m_positional;
1447   - std::vector<std::string>::iterator m_next_positional;
1448 1501 std::unordered_set<std::string> m_positional_set;
1449 1502  
1450 1503 //mapping from groups to help options
1451 1504 std::map<std::string, HelpGroupDetails> m_help;
  1505 +
  1506 + std::list<OptionDetails> m_option_list;
  1507 + std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map;
1452 1508 };
1453 1509  
1454 1510 class OptionAdder
... ... @@ -1610,24 +1666,6 @@ namespace cxxopts
1610 1666 } // namespace
1611 1667  
1612 1668 inline
1613   -ParseResult::ParseResult
1614   -(
1615   - std::shared_ptr<
1616   - std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
1617   - > options,
1618   - std::vector<std::string> positional,
1619   - bool allow_unrecognised,
1620   - int& argc, const char**& argv
1621   -)
1622   -: m_options(std::move(options))
1623   -, m_positional(std::move(positional))
1624   -, m_next_positional(m_positional.begin())
1625   -, m_allow_unrecognised(allow_unrecognised)
1626   -{
1627   - parse(argc, argv);
1628   -}
1629   -
1630   -inline
1631 1669 void
1632 1670 Options::add_options
1633 1671 (
... ... @@ -1706,21 +1744,24 @@ OptionAdder::operator()
1706 1744  
1707 1745 inline
1708 1746 void
1709   -ParseResult::parse_default(const std::shared_ptr<OptionDetails>& details)
  1747 +OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)
1710 1748 {
1711   - m_results[details].parse_default(details);
  1749 + // TODO: remove the duplicate code here
  1750 + auto& store = m_parsed[details->hash()];
  1751 + store.parse_default(details);
1712 1752 }
1713 1753  
1714 1754 inline
1715 1755 void
1716   -ParseResult::parse_option
  1756 +OptionParser::parse_option
1717 1757 (
1718 1758 const std::shared_ptr<OptionDetails>& value,
1719 1759 const std::string& /*name*/,
1720 1760 const std::string& arg
1721 1761 )
1722 1762 {
1723   - auto& result = m_results[value];
  1763 + auto hash = value->hash();
  1764 + auto& result = m_parsed[hash];
1724 1765 result.parse(value, arg);
1725 1766  
1726 1767 m_sequential.emplace_back(value->long_name(), arg);
... ... @@ -1728,7 +1769,7 @@ ParseResult::parse_option
1728 1769  
1729 1770 inline
1730 1771 void
1731   -ParseResult::checked_parse_arg
  1772 +OptionParser::checked_parse_arg
1732 1773 (
1733 1774 int argc,
1734 1775 const char* argv[],
... ... @@ -1764,43 +1805,36 @@ ParseResult::checked_parse_arg
1764 1805  
1765 1806 inline
1766 1807 void
1767   -ParseResult::add_to_option(const std::string& option, const std::string& arg)
  1808 +OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)
1768 1809 {
1769   - auto iter = m_options->find(option);
1770   -
1771   - if (iter == m_options->end())
1772   - {
1773   - throw_or_mimic<option_not_exists_exception>(option);
1774   - }
1775   -
1776 1810 parse_option(iter->second, option, arg);
1777 1811 }
1778 1812  
1779 1813 inline
1780 1814 bool
1781   -ParseResult::consume_positional(const std::string& a)
  1815 +OptionParser::consume_positional(const std::string& a, PositionalListIterator& next)
1782 1816 {
1783   - while (m_next_positional != m_positional.end())
  1817 + while (next != m_positional.end())
1784 1818 {
1785   - auto iter = m_options->find(*m_next_positional);
1786   - if (iter != m_options->end())
  1819 + auto iter = m_options.find(*next);
  1820 + if (iter != m_options.end())
1787 1821 {
1788   - auto& result = m_results[iter->second];
  1822 + auto& result = m_parsed[iter->second->hash()];
1789 1823 if (!iter->second->value().is_container())
1790 1824 {
1791 1825 if (result.count() == 0)
1792 1826 {
1793   - add_to_option(*m_next_positional, a);
1794   - ++m_next_positional;
  1827 + add_to_option(iter, *next, a);
  1828 + ++next;
1795 1829 return true;
1796 1830 }
1797   - ++m_next_positional;
  1831 + ++next;
1798 1832 continue;
1799 1833 }
1800   - add_to_option(*m_next_positional, a);
  1834 + add_to_option(iter, *next, a);
1801 1835 return true;
1802 1836 }
1803   - throw_or_mimic<option_not_exists_exception>(*m_next_positional);
  1837 + throw_or_mimic<option_not_exists_exception>(*next);
1804 1838 }
1805 1839  
1806 1840 return false;
... ... @@ -1818,7 +1852,6 @@ void
1818 1852 Options::parse_positional(std::vector<std::string> options)
1819 1853 {
1820 1854 m_positional = std::move(options);
1821   - m_next_positional = m_positional.begin();
1822 1855  
1823 1856 m_positional_set.insert(m_positional.begin(), m_positional.end());
1824 1857 }
... ... @@ -1832,21 +1865,21 @@ Options::parse_positional(std::initializer_list&lt;std::string&gt; options)
1832 1865  
1833 1866 inline
1834 1867 ParseResult
1835   -Options::parse(int& argc, const char**& argv)
  1868 +Options::parse(int argc, const char** argv)
1836 1869 {
1837   - ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv);
1838   - return result;
  1870 + OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
  1871 +
  1872 + return parser.parse(argc, argv);
1839 1873 }
1840 1874  
1841   -inline
1842   -void
1843   -ParseResult::parse(int& argc, const char**& argv)
  1875 +inline ParseResult
  1876 +OptionParser::parse(int argc, const char** argv)
1844 1877 {
1845 1878 int current = 1;
1846   -
1847   - int nextKeep = 1;
1848   -
1849 1879 bool consume_remaining = false;
  1880 + PositionalListIterator next_positional = m_positional.begin();
  1881 +
  1882 + std::vector<std::string> unmatched;
1850 1883  
1851 1884 while (current != argc)
1852 1885 {
... ... @@ -1873,13 +1906,12 @@ ParseResult::parse(int&amp; argc, const char**&amp; argv)
1873 1906  
1874 1907 //if true is returned here then it was consumed, otherwise it is
1875 1908 //ignored
1876   - if (consume_positional(argv[current]))
  1909 + if (consume_positional(argv[current], next_positional))
1877 1910 {
1878 1911 }
1879 1912 else
1880 1913 {
1881   - argv[nextKeep] = argv[current];
1882   - ++nextKeep;
  1914 + unmatched.push_back(argv[current]);
1883 1915 }
1884 1916 //if we return from here then it was parsed successfully, so continue
1885 1917 }
... ... @@ -1893,9 +1925,9 @@ ParseResult::parse(int&amp; argc, const char**&amp; argv)
1893 1925 for (std::size_t i = 0; i != s.size(); ++i)
1894 1926 {
1895 1927 std::string name(1, s[i]);
1896   - auto iter = m_options->find(name);
  1928 + auto iter = m_options.find(name);
1897 1929  
1898   - if (iter == m_options->end())
  1930 + if (iter == m_options.end())
1899 1931 {
1900 1932 if (m_allow_unrecognised)
1901 1933 {
... ... @@ -1927,15 +1959,14 @@ ParseResult::parse(int&amp; argc, const char**&amp; argv)
1927 1959 {
1928 1960 const std::string& name = result[1];
1929 1961  
1930   - auto iter = m_options->find(name);
  1962 + auto iter = m_options.find(name);
1931 1963  
1932   - if (iter == m_options->end())
  1964 + if (iter == m_options.end())
1933 1965 {
1934 1966 if (m_allow_unrecognised)
1935 1967 {
1936 1968 // keep unrecognised options in argument list, skip to next argument
1937   - argv[nextKeep] = argv[current];
1938   - ++nextKeep;
  1969 + unmatched.push_back(argv[current]);
1939 1970 ++current;
1940 1971 continue;
1941 1972 }
... ... @@ -1964,12 +1995,13 @@ ParseResult::parse(int&amp; argc, const char**&amp; argv)
1964 1995 ++current;
1965 1996 }
1966 1997  
1967   - for (auto& opt : *m_options)
  1998 + for (auto& opt : m_options)
1968 1999 {
1969 2000 auto& detail = opt.second;
1970 2001 const auto& value = detail->value();
1971 2002  
1972   - auto& store = m_results[detail];
  2003 + //auto& store = m_results[detail];
  2004 + auto& store = m_parsed[detail->hash()];
1973 2005  
1974 2006 if(value.has_default() && !store.count() && !store.has_default()){
1975 2007 parse_default(detail);
... ... @@ -1980,7 +2012,7 @@ ParseResult::parse(int&amp; argc, const char**&amp; argv)
1980 2012 {
1981 2013 while (current < argc)
1982 2014 {
1983   - if (!consume_positional(argv[current])) {
  2015 + if (!consume_positional(argv[current], next_positional)) {
1984 2016 break;
1985 2017 }
1986 2018 ++current;
... ... @@ -1988,14 +2020,33 @@ ParseResult::parse(int&amp; argc, const char**&amp; argv)
1988 2020  
1989 2021 //adjust argv for any that couldn't be swallowed
1990 2022 while (current != argc) {
1991   - argv[nextKeep] = argv[current];
1992   - ++nextKeep;
  2023 + unmatched.push_back(argv[current]);
1993 2024 ++current;
1994 2025 }
1995 2026 }
1996 2027  
1997   - argc = nextKeep;
  2028 + finalise_aliases();
1998 2029  
  2030 + ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched));
  2031 + return parsed;
  2032 +}
  2033 +
  2034 +inline
  2035 +void
  2036 +OptionParser::finalise_aliases()
  2037 +{
  2038 + for (auto& option: m_options)
  2039 + {
  2040 + auto& detail = *option.second;
  2041 + auto hash = detail.hash();
  2042 + //if (m_parsed.find(hash) != m_parsed.end())
  2043 + {
  2044 + m_keys[detail.short_name()] = hash;
  2045 + m_keys[detail.long_name()] = hash;
  2046 + }
  2047 +
  2048 + m_parsed.emplace(hash, OptionValue());
  2049 + }
1999 2050 }
2000 2051  
2001 2052 inline
... ... @@ -2034,6 +2085,11 @@ Options::add_option
2034 2085 add_one_option(l, option);
2035 2086 }
2036 2087  
  2088 + m_option_list.push_front(*option.get());
  2089 + auto iter = m_option_list.begin();
  2090 + m_option_map[s] = iter;
  2091 + m_option_map[l] = iter;
  2092 +
2037 2093 //add the help details
2038 2094 auto& options = m_help[group];
2039 2095  
... ...
test/options.cpp
... ... @@ -154,7 +154,7 @@ TEST_CASE(&quot;All positional&quot;, &quot;[positional]&quot;)
154 154  
155 155 auto result = options.parse(argc, argv);
156 156  
157   - REQUIRE(argc == 1);
  157 + CHECK(result.unmatched().size() == 0);
158 158 REQUIRE(positional.size() == 3);
159 159  
160 160 CHECK(positional[0] == "a");
... ... @@ -182,7 +182,7 @@ TEST_CASE(&quot;Some positional explicit&quot;, &quot;[positional]&quot;)
182 182  
183 183 auto result = options.parse(argc, argv);
184 184  
185   - CHECK(argc == 1);
  185 + CHECK(result.unmatched().size() == 0);
186 186 CHECK(result.count("output"));
187 187 CHECK(result["input"].as<std::string>() == "b");
188 188 CHECK(result["output"].as<std::string>() == "a");
... ... @@ -209,11 +209,10 @@ TEST_CASE(&quot;No positional with extras&quot;, &quot;[positional]&quot;)
209 209 auto old_argv = argv;
210 210 auto old_argc = argc;
211 211  
212   - options.parse(argc, argv);
  212 + auto result = options.parse(argc, argv);
213 213  
214   - REQUIRE(argc == old_argc - 1);
215   - CHECK(argv[0] == std::string("extras"));
216   - CHECK(argv[1] == std::string("a"));
  214 + auto& unmatched = result.unmatched();
  215 + CHECK((unmatched == std::vector<std::string>{"a", "b", "c", "d"}));
217 216 }
218 217  
219 218 TEST_CASE("Positional not valid", "[positional]") {
... ... @@ -643,9 +642,9 @@ TEST_CASE(&quot;Unrecognised options&quot;, &quot;[options]&quot;) {
643 642  
644 643 SECTION("After allowing unrecognised options") {
645 644 options.allow_unrecognised_options();
646   - CHECK_NOTHROW(options.parse(argc, argv));
647   - REQUIRE(argc == 3);
648   - CHECK_THAT(argv[1], Catch::Equals("--unknown"));
  645 + auto result = options.parse(argc, argv);
  646 + auto& unmatched = result.unmatched();
  647 + CHECK((unmatched == std::vector<std::string>{"--unknown", "--another_unknown"}));
649 648 }
650 649 }
651 650  
... ...