Skip to content

Commit dc5c875

Browse files
committed
feat(search): add pagination with page and limit flags
1 parent e6ebcb2 commit dc5c875

File tree

1 file changed

+152
-14
lines changed

1 file changed

+152
-14
lines changed

src/commands/SearchCommand.cpp

Lines changed: 152 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ namespace vix::commands
9696
{
9797
if (!entry.contains("keywords") || !entry["keywords"].is_array())
9898
return {};
99+
99100
std::string out;
100101
for (const auto &k : entry["keywords"])
101102
{
@@ -135,6 +136,102 @@ namespace vix::commands
135136
int score{};
136137
};
137138

139+
struct SearchOptions
140+
{
141+
std::string query;
142+
std::size_t page = 1;
143+
std::size_t limit = 5;
144+
};
145+
146+
bool parse_positive_size(const std::string &s, std::size_t &out)
147+
{
148+
if (s.empty())
149+
return false;
150+
151+
std::size_t value = 0;
152+
for (char c : s)
153+
{
154+
if (c < '0' || c > '9')
155+
return false;
156+
157+
value = (value * 10u) + static_cast<std::size_t>(c - '0');
158+
}
159+
160+
if (value == 0)
161+
return false;
162+
163+
out = value;
164+
return true;
165+
}
166+
167+
bool parse_search_args(const std::vector<std::string> &args, SearchOptions &opt)
168+
{
169+
bool querySet = false;
170+
171+
for (std::size_t i = 0; i < args.size(); ++i)
172+
{
173+
const std::string &arg = args[i];
174+
175+
if (arg == "--page")
176+
{
177+
if (i + 1 >= args.size())
178+
return false;
179+
180+
std::size_t page = 0;
181+
if (!parse_positive_size(args[++i], page))
182+
return false;
183+
184+
opt.page = page;
185+
continue;
186+
}
187+
188+
if (arg == "--limit")
189+
{
190+
if (i + 1 >= args.size())
191+
return false;
192+
193+
std::size_t limit = 0;
194+
if (!parse_positive_size(args[++i], limit))
195+
return false;
196+
197+
opt.limit = std::clamp<std::size_t>(limit, 1, 100);
198+
continue;
199+
}
200+
201+
if (arg.rfind("--page=", 0) == 0)
202+
{
203+
std::size_t page = 0;
204+
if (!parse_positive_size(arg.substr(7), page))
205+
return false;
206+
207+
opt.page = page;
208+
continue;
209+
}
210+
211+
if (arg.rfind("--limit=", 0) == 0)
212+
{
213+
std::size_t limit = 0;
214+
if (!parse_positive_size(arg.substr(8), limit))
215+
return false;
216+
217+
opt.limit = std::clamp<std::size_t>(limit, 1, 100);
218+
continue;
219+
}
220+
221+
if (!querySet)
222+
{
223+
opt.query = arg;
224+
querySet = true;
225+
continue;
226+
}
227+
228+
opt.query += " ";
229+
opt.query += arg;
230+
}
231+
232+
return !opt.query.empty();
233+
}
234+
138235
int score_entry(const json &e, const std::string &qLower)
139236
{
140237
const std::string ns = e.value("namespace", "");
@@ -173,8 +270,18 @@ namespace vix::commands
173270
if (args.empty())
174271
return help();
175272

176-
const std::string query = args[0];
177-
vix::cli::util::kv(std::cout, "query", vix::cli::util::quote(query));
273+
SearchOptions options;
274+
if (!parse_search_args(args, options))
275+
{
276+
error("invalid search arguments");
277+
hint("Usage: vix search <query> [--page N] [--limit N]");
278+
hint("Example: vix search json --page 2 --limit 5");
279+
return 1;
280+
}
281+
282+
vix::cli::util::kv(std::cout, "query", vix::cli::util::quote(options.query));
283+
vix::cli::util::kv(std::cout, "page", std::to_string(options.page));
284+
vix::cli::util::kv(std::cout, "limit", std::to_string(options.limit));
178285

179286
const fs::path repoDir = registry_repo_dir();
180287
const fs::path idxDir = registry_index_dir();
@@ -187,7 +294,7 @@ namespace vix::commands
187294
}
188295

189296
std::vector<Hit> hits;
190-
const std::string qLower = to_lower(query);
297+
const std::string qLower = to_lower(options.query);
191298

192299
for (const auto &it : fs::directory_iterator(idxDir))
193300
{
@@ -231,28 +338,58 @@ namespace vix::commands
231338

232339
if (hits.empty())
233340
{
234-
error(std::string("no results for ") + vix::cli::util::quote(query));
341+
error(std::string("no results for ") + vix::cli::util::quote(options.query));
235342
hint("Tip: search by namespace, name, description, or keywords");
236343
hint("Example: vix search gaspardkirira");
237344
return 0;
238345
}
239346

240-
vix::cli::util::one_line_spacer(std::cout);
347+
const std::size_t total = hits.size();
348+
const std::size_t totalPages =
349+
(total + options.limit - 1) / options.limit;
241350

242-
const std::size_t limit = 20;
243-
const std::size_t n = std::min<std::size_t>(hits.size(), limit);
351+
if (options.page > totalPages)
352+
{
353+
error("page out of range");
354+
hint("Total pages: " + std::to_string(totalPages));
355+
return 1;
356+
}
357+
358+
const std::size_t start = (options.page - 1) * options.limit;
359+
const std::size_t end = std::min(start + options.limit, total);
360+
361+
vix::cli::util::one_line_spacer(std::cout);
244362

245-
for (std::size_t i = 0; i < n; ++i)
363+
for (std::size_t i = start; i < end; ++i)
246364
{
247365
const auto &h = hits[i];
248366
vix::cli::util::pkg_line(std::cout, h.id, h.latest, h.desc, h.repo);
249367
std::cout << "\n";
250368
}
251369

252-
if (hits.size() > limit)
253-
vix::cli::util::ok_line(std::cout, "Showing " + std::to_string(limit) + " of " + std::to_string(hits.size()) + " result(s).");
254-
else
255-
vix::cli::util::ok_line(std::cout, "Found " + std::to_string(hits.size()) + " result(s).");
370+
vix::cli::util::ok_line(
371+
std::cout,
372+
"Showing " + std::to_string(start + 1) +
373+
"-" + std::to_string(end) +
374+
" of " + std::to_string(total) +
375+
" result(s).");
376+
377+
if (totalPages > 1)
378+
{
379+
std::cout << " " << GRAY
380+
<< "Page " << options.page << "/" << totalPages
381+
<< RESET << "\n";
382+
383+
if (options.page < totalPages)
384+
{
385+
std::cout << " " << GRAY
386+
<< "Next: vix search "
387+
<< vix::cli::util::quote(options.query)
388+
<< " --page " << (options.page + 1)
389+
<< " --limit " << options.limit
390+
<< RESET << "\n";
391+
}
392+
}
256393

257394
return 0;
258395
}
@@ -261,13 +398,14 @@ namespace vix::commands
261398
{
262399
std::cout
263400
<< "Usage:\n"
264-
<< " vix search <query>\n\n"
401+
<< " vix search <query> [--page N] [--limit N]\n\n"
265402
<< "Description:\n"
266403
<< " Search packages in the local registry index (offline).\n\n"
267404
<< "Examples:\n"
268405
<< " vix registry sync\n"
269406
<< " vix search tree\n"
270-
<< " vix search gaspardkirira\n";
407+
<< " vix search json --page 2\n"
408+
<< " vix search gaspardkirira --limit 50\n";
271409
return 0;
272410
}
273411
}

0 commit comments

Comments
 (0)