@@ -75,8 +75,7 @@ class CmdParser {
7575 auto scanner = Scanner (args);
7676 auto const tokens = scanner ();
7777 auto const indices = index_tokens (tokens);
78- // TODO: check we did not receive unknown arguments (throw UnknownArgument)
79- auto map = this ->get_args_map (tokens, indices, 0 , -1 );
78+ auto map = this ->get_args_map (args, tokens, indices, 0 , tokens.size () - 1 );
8079 return map;
8180 }
8281
@@ -103,33 +102,46 @@ class CmdParser {
103102 auto get_cmd_fmt () const noexcept { return CmdFmt (this ->cmd_ref .get (), this ->extra_info ); }
104103
105104 auto get_args_map (
105+ std::span<char const *> const args,
106106 std::span<Token const > const tokens,
107107 TokenIndices const &indices,
108108 std::size_t const recursion_start_idx,
109109 std::size_t recursion_end_idx
110110 ) {
111- constexpr auto args_size = std::tuple_size_v<decltype (this ->cmd_ref .get ().args )>;
112111 auto args_map = ArgsMap<Cmd const >();
113112 args_map.exec_path = *tokens[recursion_start_idx].value ;
114- if (this ->cmd_ref .get ().has_subcmds ())
115- this ->parse_possible_subcmd (args_map, tokens, indices, recursion_start_idx, recursion_end_idx);
113+ if (this ->cmd_ref .get ().has_subcmds ()) {
114+ this ->parse_possible_subcmd (args, args_map, tokens, indices, recursion_start_idx, recursion_end_idx);
115+ }
116116 // further args have to be > recursion_start_idx and <= recursion_end_idx
117+ std::set<std::size_t > consumed_indices;
118+ consumed_indices.insert (recursion_start_idx);
119+ constexpr auto args_size = std::tuple_size_v<decltype (this ->cmd_ref .get ().args )>;
117120 this ->process_tokens (
118- args_map, tokens, indices, recursion_start_idx, recursion_end_idx, std::make_index_sequence<args_size>()
121+ args_map,
122+ tokens,
123+ indices,
124+ recursion_start_idx,
125+ recursion_end_idx,
126+ consumed_indices,
127+ std::make_index_sequence<args_size>()
119128 );
129+ this ->check_unknown_args (args, tokens, recursion_start_idx, recursion_end_idx, consumed_indices);
120130 return args_map;
121131 }
122132
123133 void parse_possible_subcmd (
134+ std::span<char const *> const args,
124135 ArgsMap<Cmd const > &args_map,
125136 std::span<Token const > const tokens,
126137 TokenIndices const &indices,
127138 std::size_t const recursion_start_idx,
128139 std::size_t &recursion_end_idx
129140 ) {
130141 // Note: a command can't have positionals if it has subcommands, so it suffices to check if first positional token
131- // is a subcommand. If it isn't, everything is considered as positionals. Also, the command name is not present in
132- // the indices, so 0 really is the first positional.
142+ // is a subcommand. If it's not, then the user provided an unknown subcommand, since this method is only called
143+ // if the command has any subcommand. Also, the command name is not present in the indices,
144+ // so 0 (or recursion_start_idx) really is the first positional.
133145 auto const tok_idx = indices.first_pos_idx_after (recursion_start_idx);
134146 if (!tok_idx.has_value ()) return ;
135147 auto const &tok = tokens[*tok_idx];
@@ -138,19 +150,21 @@ class CmdParser {
138150 int i = 0 ;
139151 // clang-format off
140152 std::apply (
141- [this , &i, cmd_idx, &args_map, tokens, &indices, recursion_end_idx, tok_idx ](auto &&... cmd) {
153+ [this , &i, cmd_idx, &args_map, &args, tokens, &indices, tok_idx, recursion_end_idx ](auto &&... cmd) {
142154 (void )(( // cast to void to suppress unused warning
143155 i == cmd_idx
144156 ? (args_map.submap = CmdParser<typename std::remove_reference_t <decltype (cmd)>::type>(
145- cmd.get (), this ->extra_info , this ->cmd_ref .get ().name ).get_args_map (tokens, indices, *tok_idx, recursion_end_idx), true )
157+ cmd.get (), this ->extra_info , this ->cmd_ref .get ().name ).get_args_map (args, tokens, indices, *tok_idx, recursion_end_idx), true )
146158 : (++i, false )
147159 ) || ...);
148160 },
149161 this ->cmd_ref .get ().subcmds
150162 );
151163 // clang-format on
152- // when coming back from parsing a subcommand, limit the rest of the tokens to up to where we found the subcommand
164+ // when coming back from parsing a subcommand, limit the rest of the tokens to up to the token before the subcmd
153165 recursion_end_idx = *tok_idx - 1 ;
166+ } else {
167+ throw UnknownSubcommand (this ->cmd_ref .get ().name , *tok.value , this ->get_cmd_fmt ());
154168 }
155169 }
156170
@@ -161,16 +175,17 @@ class CmdParser {
161175 TokenIndices const &indices,
162176 std::size_t const recursion_start_idx,
163177 std::size_t const recursion_end_idx,
178+ std::set<std::size_t > &consumed_indices,
164179 std::index_sequence<Is...>
165180 ) {
166181 try {
167- (this ->process_ith_arg <Is>(args_map, tokens, indices, recursion_start_idx, recursion_end_idx), ...);
182+ // clang-format off
183+ (this ->process_ith_arg <Is>(args_map, tokens, indices, recursion_start_idx, recursion_end_idx, consumed_indices), ...);
168184 if (!args_map.has_submap ()) { // commands can't have both positionals and subcommands
169185 std::size_t cur_pos_idx = 0 ;
170- // clang-format off
171- (this ->process_ith_arg <Is>(args_map, tokens, indices, recursion_start_idx, recursion_end_idx, cur_pos_idx), ...);
172- // clang-format on
186+ (this ->process_ith_arg <Is>(args_map, tokens, indices, recursion_start_idx, recursion_end_idx, consumed_indices, cur_pos_idx), ...);
173187 }
188+ // clang-format on
174189 (this ->check_ith_arg <Is>(args_map), ...);
175190 } catch (std::runtime_error const &e) {
176191 throw UserError (e.what (), get_cmd_fmt ());
@@ -183,19 +198,26 @@ class CmdParser {
183198 std::span<Token const > const tokens,
184199 TokenIndices const &indices,
185200 std::size_t const recursion_start_idx,
186- std::size_t const recursion_end_idx
201+ std::size_t const recursion_end_idx,
202+ std::set<std::size_t > &consumed_indices
187203 ) {
188204 switch (auto const &arg = std::get<I>(this ->cmd_ref .get ().args ); arg.type ) {
189205 case ArgType::FLG: {
190206 std::size_t flg_count = 0 ;
191207 if (auto const it = indices.opts_n_flgs .find (arg.name ); it != indices.opts_n_flgs .end ()) {
192208 for (auto const idx : it->second ) {
193- if (idx > recursion_start_idx && idx <= recursion_end_idx) flg_count += 1 ;
209+ if (idx > recursion_start_idx && idx <= recursion_end_idx) {
210+ flg_count += 1 ;
211+ consumed_indices.insert (idx);
212+ }
194213 }
195214 }
196215 if (auto const it = indices.opts_n_flgs .find (arg.abbrev ); it != indices.opts_n_flgs .end ()) {
197216 for (auto const idx : it->second ) {
198- if (idx > recursion_start_idx && idx <= recursion_end_idx) flg_count += 1 ;
217+ if (idx > recursion_start_idx && idx <= recursion_end_idx) {
218+ flg_count += 1 ;
219+ consumed_indices.insert (idx);
220+ }
199221 }
200222 }
201223
@@ -217,15 +239,19 @@ class CmdParser {
217239 all_idxs.reserve (reserve_size);
218240 }
219241 if (has_name) {
220- // all_idxs.append_range(it_name->second); // TODO(?): use `| filter`
221242 for (auto const idx : it_name->second ) {
222- if (idx > recursion_start_idx && idx <= recursion_end_idx) all_idxs.push_back (idx);
243+ if (idx > recursion_start_idx && idx <= recursion_end_idx) {
244+ all_idxs.push_back (idx);
245+ consumed_indices.insert (idx);
246+ }
223247 }
224248 }
225249 if (has_abbrev) {
226- // all_idxs.append_range(it_abbrev->second); // TODO(?): use `| filter`
227250 for (auto const idx : it_abbrev->second ) {
228- if (idx > recursion_start_idx && idx <= recursion_end_idx) all_idxs.push_back (idx);
251+ if (idx > recursion_start_idx && idx <= recursion_end_idx) {
252+ all_idxs.push_back (idx);
253+ consumed_indices.insert (idx);
254+ }
229255 }
230256 }
231257 std::ranges::sort (all_idxs);
@@ -237,6 +263,7 @@ class CmdParser {
237263 else if (idx + 1 < tokens.size () && tokens[idx + 1 ].type == TokenType::IDENTIFIER) {
238264 opt_values.push_back (*tokens[idx + 1 ].value );
239265 this ->indices_used_as_opt_values .insert (idx + 1 );
266+ consumed_indices.insert (idx + 1 );
240267 }
241268 }
242269
@@ -258,6 +285,7 @@ class CmdParser {
258285 TokenIndices const &indices,
259286 std::size_t const recursion_start_idx,
260287 std::size_t const recursion_end_idx,
288+ std::set<std::size_t > &consumed_indices,
261289 std::size_t &cur_pos_idx
262290 ) {
263291 auto const &arg = std::get<I>(this ->cmd_ref .get ().args );
@@ -276,6 +304,7 @@ class CmdParser {
276304 if (tok_idx >= tokens.size ()) return ;
277305
278306 cur_pos_idx += 1 ;
307+ consumed_indices.insert (tok_idx);
279308 if (auto const &tok = tokens[tok_idx]; tok.value )
280309 consume_arg<I>(args_map, arg, *tok.value , this ->cmd_ref .get (), this ->extra_info );
281310 }
@@ -288,6 +317,23 @@ class CmdParser {
288317 if (arg.has_default ()) std::get<I>(args_map.args ) = *arg.default_value ;
289318 }
290319 }
320+
321+ void check_unknown_args (
322+ std::span<char const *> const args,
323+ std::span<Token const > const tokens,
324+ std::size_t const recursion_start_idx,
325+ std::size_t const recursion_end_idx,
326+ std::set<std::size_t > const &consumed_indices
327+ ) const {
328+ if (std::size_t const recursion_amount_indices = recursion_end_idx - recursion_start_idx + 1 ;
329+ consumed_indices.size () < recursion_amount_indices) {
330+ std::vector<std::string_view> unknown_args;
331+ for (std::size_t idx = recursion_start_idx; idx <= recursion_end_idx; ++idx) {
332+ if (!consumed_indices.contains (idx)) unknown_args.emplace_back (args[tokens[idx].args_idx ]);
333+ }
334+ throw UnknownArguments (this ->cmd_ref .get ().name , unknown_args, this ->get_cmd_fmt ());
335+ }
336+ }
291337};
292338
293339} // namespace opz
0 commit comments