Skip to content

Commit 9fa882c

Browse files
committed
add input parser and update dependencies, and fix codestyle
1 parent b8c66cd commit 9fa882c

File tree

7 files changed

+330
-29
lines changed

7 files changed

+330
-29
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
commit b8c66cd7b406e1bdea693022cfb21ae5c18cbfb7
2+
Author: Alexeev Bronislav <alexeev.dev@mail.ru>
3+
Date: Mon Aug 4 06:48:18 2025 +0700
4+
5+
update readme
6+
7+
commit 76065d9d21bf2b41542a181bf12b5dfeec49f0fa
8+
Author: Alexeev Bronislav <alexeev.dev@mail.ru>
9+
Date: Mon Aug 4 06:46:45 2025 +0700
10+
11+
update readme
12+
113
commit 8f50b181fc220a9c42843a3573c15d3dbe1dd715
214
Author: Alexeev Bronislav <alexeev.dev@mail.ru>
315
Date: Mon Aug 4 06:37:10 2025 +0700

shell.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ let
2121
gdb
2222
catch2
2323
nodejs
24+
abseil-cpp
2425
catch2_3
2526
boehmgc
2627
valgrind

source/input_parser.cpp

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#include <iomanip>
2+
#include <sstream>
3+
#include <stdexcept>
4+
5+
#include "input_parser.hpp"
6+
7+
#include "absl/strings/match.h"
8+
9+
InputParser::InputParser(std::string program_name, std::string description)
10+
: m_PROGRAM_NAME(std::move(program_name))
11+
, m_DESCRIPTION(std::move(description)) {}
12+
13+
auto InputParser::add_option(const Option& opt) -> void {
14+
if (!opt.short_name.empty() && is_option_registered(opt.short_name)) {
15+
throw std::invalid_argument("Duplicate short option: " + opt.short_name);
16+
}
17+
18+
if (!opt.long_name.empty() && is_option_registered(opt.long_name)) {
19+
throw std::invalid_argument("Duplicate long option: " + opt.long_name);
20+
}
21+
22+
const size_t IDX = m_OPTIONS.size();
23+
m_OPTIONS.push_back(opt);
24+
25+
if (!opt.short_name.empty()) {
26+
m_SHORT_MAP[opt.short_name] = IDX;
27+
}
28+
if (!opt.long_name.empty()) {
29+
m_LONG_MAP[opt.long_name] = IDX;
30+
}
31+
}
32+
33+
auto InputParser::parse(int argc, char** argv) -> bool {
34+
reset_state();
35+
36+
for (int i = 1; i < argc;) {
37+
const std::string TOKEN = argv[i];
38+
bool advance_index = true;
39+
40+
if (is_equals_syntax_option(TOKEN)) {
41+
handle_equals_syntax(TOKEN);
42+
} else if (is_regular_option(TOKEN)) {
43+
advance_index = handle_regular_option(TOKEN, i, argc, argv);
44+
} else {
45+
handle_positional_arg(TOKEN);
46+
}
47+
48+
if (advance_index) {
49+
++i;
50+
}
51+
}
52+
53+
return m_ERRORS.empty();
54+
}
55+
56+
auto InputParser::has_option(const std::string& name) const -> bool {
57+
const auto IDX = get_option_index(name);
58+
return IDX && m_PARSED_VALUES.find(*IDX) != m_PARSED_VALUES.end();
59+
}
60+
61+
auto InputParser::get_argument(const std::string& name) const -> std::optional<std::string> {
62+
const auto IDX = get_option_index(name);
63+
if (!IDX) {
64+
return std::nullopt;
65+
}
66+
67+
const auto ITERATOR = m_PARSED_VALUES.find(*IDX);
68+
if (ITERATOR == m_PARSED_VALUES.end()) {
69+
return std::nullopt;
70+
}
71+
72+
return ITERATOR->second;
73+
}
74+
75+
auto InputParser::get_positional_args() const -> const std::vector<std::string>& {
76+
return m_POSITIONAL_ARGS;
77+
}
78+
79+
auto InputParser::get_errors() const -> const std::vector<std::string>& {
80+
return m_ERRORS;
81+
}
82+
83+
auto InputParser::generate_help() const -> std::string {
84+
std::ostringstream oss;
85+
oss << "Usage: " << m_PROGRAM_NAME << " [options]\n\n";
86+
oss << m_DESCRIPTION << "\n\n";
87+
oss << "Options:\n";
88+
89+
constexpr size_t NAME_WIDTH = 30;
90+
91+
for (const auto& opt : m_OPTIONS) {
92+
std::string name_display;
93+
if (!opt.short_name.empty() && !opt.long_name.empty()) {
94+
name_display = opt.short_name + ", " + opt.long_name;
95+
} else {
96+
name_display = !opt.short_name.empty() ? opt.short_name : opt.long_name;
97+
}
98+
99+
if (opt.requires_argument) {
100+
name_display += " " + opt.arg_placeholder;
101+
}
102+
103+
oss << " " << std::left << std::setw(NAME_WIDTH) << name_display << " " << opt.description << "\n";
104+
}
105+
106+
return oss.str();
107+
}
108+
109+
auto InputParser::get_option_index(const std::string& name) const -> std::optional<size_t> {
110+
// Check exact matches first
111+
if (auto iter = m_SHORT_MAP.find(name); iter != m_SHORT_MAP.end()) {
112+
return iter->second;
113+
}
114+
if (auto iter = m_LONG_MAP.find(name); iter != m_LONG_MAP.end()) {
115+
return iter->second;
116+
}
117+
118+
// Handle automatic conversion between short and long forms
119+
if (name.size() > 2 && name.substr(0, 2) == "--") {
120+
const std::string SHORT_FORM = "-" + name.substr(2);
121+
if (auto iter = m_SHORT_MAP.find(SHORT_FORM); iter != m_SHORT_MAP.end()) {
122+
return iter->second;
123+
}
124+
} else if (name.size() == 2 && name[0] == '-') {
125+
const std::string LONGFORM = "--" + name.substr(1);
126+
if (auto iter = m_LONG_MAP.find(LONGFORM); iter != m_LONG_MAP.end()) {
127+
return iter->second;
128+
}
129+
}
130+
131+
return std::nullopt;
132+
}
133+
134+
auto InputParser::is_option_registered(const std::string& name) const -> bool {
135+
return m_SHORT_MAP.find(name) != m_SHORT_MAP.end() || m_LONG_MAP.find(name) != m_LONG_MAP.end();
136+
}
137+
138+
auto InputParser::reset_state() -> void {
139+
m_PARSED_VALUES.clear();
140+
m_POSITIONAL_ARGS.clear();
141+
m_ERRORS.clear();
142+
}
143+
144+
auto InputParser::is_equals_syntax_option(const std::string& token) const -> bool {
145+
return token.size() >= 2 && token.substr(0, 2) == "--" && absl::StrContains(token, '=');
146+
}
147+
148+
auto InputParser::is_regular_option(const std::string& token) const -> bool {
149+
return !token.empty() && token[0] == '-';
150+
}
151+
152+
auto InputParser::handle_equals_syntax(const std::string& token) -> void {
153+
const size_t POS = token.find('=');
154+
const std::string KEY = token.substr(0, POS);
155+
const std::string VALUE = token.substr(POS + 1);
156+
157+
if (const auto ITER = m_LONG_MAP.find(KEY); ITER != m_LONG_MAP.end()) {
158+
const auto& option = m_OPTIONS[ITER->second];
159+
if (option.requires_argument) {
160+
m_PARSED_VALUES[ITER->second] = VALUE;
161+
} else {
162+
m_ERRORS.push_back("Option " + KEY + " doesn't accept arguments");
163+
}
164+
} else {
165+
m_ERRORS.push_back("Unknown option: " + KEY);
166+
}
167+
}
168+
169+
auto InputParser::handle_regular_option(const std::string& token, int& index, int argc, char** argv) -> bool {
170+
std::optional<size_t> idx;
171+
bool advance_index = true;
172+
173+
if (token.size() >= 2 && token.substr(0, 2) == "--") {
174+
if (auto iter = m_LONG_MAP.find(token); iter != m_LONG_MAP.end()) {
175+
idx = iter->second;
176+
}
177+
} else {
178+
if (auto iter = m_SHORT_MAP.find(token); iter != m_SHORT_MAP.end()) {
179+
idx = iter->second;
180+
}
181+
}
182+
183+
if (!idx) {
184+
m_ERRORS.push_back("Unknown option: " + token);
185+
return advance_index;
186+
}
187+
188+
const auto& option = m_OPTIONS[*idx];
189+
if (option.requires_argument) {
190+
if (index + 1 >= argc) {
191+
m_ERRORS.push_back("Missing argument for: " + token);
192+
} else {
193+
m_PARSED_VALUES[*idx] = argv[++index];
194+
advance_index = false; // Already advanced index
195+
}
196+
} else {
197+
m_PARSED_VALUES[*idx] = "";
198+
}
199+
200+
return advance_index;
201+
}
202+
203+
auto InputParser::handle_positional_arg(const std::string& token) -> void {
204+
m_POSITIONAL_ARGS.push_back(token);
205+
}

source/input_parser.hpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#pragma once
2+
3+
#include <map>
4+
#include <optional>
5+
#include <string>
6+
#include <vector>
7+
8+
/**
9+
* @brief Command line option definition structure
10+
*/
11+
struct Option {
12+
std::string short_name; ///< Short option name (e.g., "-h")
13+
std::string long_name; ///< Long option name (e.g., "--help")
14+
std::string description; ///< Option description for help
15+
bool requires_argument; ///< Whether option requires an argument
16+
std::string arg_placeholder; ///< Argument placeholder for help
17+
};
18+
19+
/**
20+
* @brief Advanced command line arguments parser
21+
*
22+
* Supports short and long options with unified handling,
23+
* automatic help generation, and strict validation.
24+
*
25+
* Designed following SOLID, ACID, KISS, and DRY principles.
26+
*/
27+
class InputParser {
28+
public:
29+
/**
30+
* @brief Construct a new Input Parser object
31+
*
32+
* @param program_name Name of the program for help display
33+
* @param description Program description for help display
34+
*/
35+
InputParser(std::string program_name, std::string description);
36+
37+
/**
38+
* @brief Add a new command line option
39+
*
40+
* @param opt Option definition to add
41+
* @throws std::invalid_argument if option name is duplicated
42+
*/
43+
auto add_option(const Option& opt) -> void;
44+
45+
/**
46+
* @brief Parse command line arguments
47+
*
48+
* @param argc Argument count
49+
* @param argv Argument values
50+
* @return true if parsing succeeded
51+
* @return false if parsing failed (invalid arguments)
52+
*/
53+
auto parse(int argc, char** argv) -> bool;
54+
55+
/**
56+
* @brief Check if option was provided
57+
*
58+
* @param name Short or long option name
59+
* @return true if option exists in command line
60+
* @return false otherwise
61+
*/
62+
auto has_option(const std::string& name) const -> bool;
63+
64+
/**
65+
* @brief Get argument value for option
66+
*
67+
* @param name Short or long option name
68+
* @return std::optional<std::string> Argument value if exists
69+
*/
70+
auto get_argument(const std::string& name) const -> std::optional<std::string>;
71+
72+
/**
73+
* @brief Get positional arguments
74+
*
75+
* @return const std::vector<std::string>& List of positional arguments
76+
*/
77+
auto get_positional_args() const -> const std::vector<std::string>&;
78+
79+
/**
80+
* @brief Get parsing errors
81+
*
82+
* @return const std::vector<std::string>& List of parsing errors
83+
*/
84+
auto get_errors() const -> const std::vector<std::string>&;
85+
86+
/**
87+
* @brief Generate help message
88+
*
89+
* @return std::string Formatted help message
90+
*/
91+
auto generate_help() const -> std::string;
92+
93+
private:
94+
auto get_option_index(const std::string& name) const -> std::optional<size_t>;
95+
auto is_option_registered(const std::string& name) const -> bool;
96+
auto reset_state() -> void;
97+
auto is_equals_syntax_option(const std::string& token) const -> bool;
98+
auto is_regular_option(const std::string& token) const -> bool;
99+
auto handle_equals_syntax(const std::string& token) -> void;
100+
auto handle_regular_option(const std::string& token, int& index, int argc, char** argv) -> bool;
101+
auto handle_positional_arg(const std::string& token) -> void;
102+
103+
std::string m_PROGRAM_NAME; ///< Program name
104+
std::string m_DESCRIPTION; ///< Program description
105+
std::vector<Option> m_OPTIONS; ///< Registered options
106+
std::map<std::string, size_t> m_SHORT_MAP; ///< Short name to index mapping
107+
std::map<std::string, size_t> m_LONG_MAP; ///< Long name to index mapping
108+
std::map<size_t, std::string> m_PARSED_VALUES; ///< Parsed option values
109+
std::vector<std::string> m_POSITIONAL_ARGS; ///< Positional arguments
110+
std::vector<std::string> m_ERRORS; ///< Parsing errors
111+
};

source/lib.cpp

Lines changed: 0 additions & 4 deletions
This file was deleted.

source/lib.hpp

Lines changed: 0 additions & 20 deletions
This file was deleted.

source/main.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
#include <iostream>
22
#include <string>
33

4-
#include "lib.hpp"
5-
64
auto main() -> int {
7-
auto const lib = library {};
8-
auto const message = "Hello from " + lib.name + "!";
9-
std::cout << message << '\n';
5+
std::cout << "hello" << '\n';
106
return 0;
117
}

0 commit comments

Comments
 (0)