this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add shell completion and man page support

- Add command-line options for generating shell completions
- Add command-line option for generating man page
- Update flake.nix to use the new completion generators
- Implement Bash, Zsh, and Fish completion scripts
- Create a detailed man page

💙 Generated with Crush
Co-Authored-By: 💙 Crush <crush@charm.land>

+191 -41
+10 -14
flake.nix
··· 46 46 mkdir -p $out/bin 47 47 cp build/soapdump $out/bin/ 48 48 49 - # Generate shell completions 49 + # Generate and install shell completions 50 50 mkdir -p completions 51 - $out/bin/soapdump --help > /dev/null 2>&1 || true 51 + $out/bin/soapdump --generate-bash-completion > completions/soapdump.bash 52 + $out/bin/soapdump --generate-zsh-completion > completions/soapdump.zsh 53 + $out/bin/soapdump --generate-fish-completion > completions/soapdump.fish 52 54 53 - # Install shell completions if they exist 54 - if [ -f completions/soapdump.bash ]; then 55 - installShellCompletion --bash completions/soapdump.bash 56 - fi 57 - if [ -f completions/soapdump.fish ]; then 58 - installShellCompletion --fish completions/soapdump.fish 59 - fi 60 - if [ -f completions/soapdump.zsh ]; then 61 - installShellCompletion --zsh completions/soapdump.zsh 62 - fi 55 + installShellCompletion --cmd soapdump \ 56 + --bash completions/soapdump.bash \ 57 + --fish completions/soapdump.fish \ 58 + --zsh completions/soapdump.zsh 63 59 64 - # Generate man page 60 + # Generate and install man page 65 61 mkdir -p $out/share/man/man1 66 - $out/bin/soapdump --help | sed 's/^/ /' > $out/share/man/man1/soapdump.1 62 + $out/bin/soapdump --man > $out/share/man/man1/soapdump.1 67 63 ''; 68 64 69 65 meta = with pkgs.lib; {
+181 -27
src/soapdump.cpp
··· 9 9 #include <numeric> 10 10 #include <iomanip> 11 11 #include <getopt.h> 12 + #include <unordered_map> 12 13 13 14 // Transaction data structure 14 15 struct Transaction { ··· 42 43 43 44 // Function prototypes 44 45 void showHelp(const char* programName); 46 + void generateBashCompletion(); 47 + void generateZshCompletion(); 48 + void generateFishCompletion(); 49 + void generateManPage(); 45 50 std::string extractXmlValue(const std::string& xml, const std::string& tag); 46 51 std::string extractXmlAttribute(const std::string& xml, const std::string& attribute); 47 52 std::vector<std::string> extractRequests(const std::string& logContent); ··· 61 66 {"help", no_argument, 0, 'h'}, 62 67 {"summary", no_argument, 0, 's'}, 63 68 {"raw", no_argument, 0, 'r'}, 69 + {"generate-bash-completion", no_argument, 0, 0}, 70 + {"generate-zsh-completion", no_argument, 0, 0}, 71 + {"generate-fish-completion", no_argument, 0, 0}, 72 + {"man", no_argument, 0, 0}, 64 73 {0, 0, 0, 0} 65 74 }; 66 75 ··· 68 77 int opt; 69 78 while ((opt = getopt_long(argc, argv, "hsr", longOptions, &optionIndex)) != -1) { 70 79 switch (opt) { 80 + case 0: 81 + // Long options without short equivalents 82 + if (strcmp(longOptions[optionIndex].name, "generate-bash-completion") == 0) { 83 + generateBashCompletion(); 84 + return 0; 85 + } else if (strcmp(longOptions[optionIndex].name, "generate-zsh-completion") == 0) { 86 + generateZshCompletion(); 87 + return 0; 88 + } else if (strcmp(longOptions[optionIndex].name, "generate-fish-completion") == 0) { 89 + generateFishCompletion(); 90 + return 0; 91 + } else if (strcmp(longOptions[optionIndex].name, "man") == 0) { 92 + generateManPage(); 93 + return 0; 94 + } 95 + break; 71 96 case 'h': 72 97 showHelp(argv[0]); 73 98 return 0; ··· 131 156 std::cout << "USAGE:\n"; 132 157 std::cout << " " << programName << " [OPTIONS] <logfile>\n\n"; 133 158 std::cout << "OPTIONS:\n"; 134 - std::cout << " -h, --help Show this help message\n"; 135 - std::cout << " -s, --summary Show summary statistics only\n"; 136 - std::cout << " -r, --raw Output raw structured data (default)\n\n"; 159 + std::cout << " -h, --help Show this help message\n"; 160 + std::cout << " -s, --summary Show summary statistics only\n"; 161 + std::cout << " -r, --raw Output raw structured data (default)\n"; 162 + std::cout << " --generate-bash-completion Generate Bash completion script\n"; 163 + std::cout << " --generate-zsh-completion Generate Zsh completion script\n"; 164 + std::cout << " --generate-fish-completion Generate Fish completion script\n"; 165 + std::cout << " --man Generate man page\n\n"; 137 166 std::cout << "OUTPUT FORMAT:\n"; 138 167 std::cout << " TRANS_NUM|AMOUNT|CURRENCY|FIRSTNAME|LASTNAME|STREET|CITY|STATE|ZIP|CCTYPE|CCLAST4|EXPMONTH|EXPYEAR|CVV|TRANSID|STATUS|CORRID|PROC_AMOUNT\n\n"; 139 168 std::cout << "FIELD DESCRIPTIONS:\n"; ··· 170 199 std::cout << " " << programName << " -s payments.log\n"; 171 200 } 172 201 202 + void generateBashCompletion() { 203 + std::cout << R"( 204 + _soapdump_completions() 205 + { 206 + local cur prev opts 207 + COMPREPLY=() 208 + cur="${COMP_WORDS[COMP_CWORD]}" 209 + prev="${COMP_WORDS[COMP_CWORD-1]}" 210 + opts="--help --summary --raw --generate-bash-completion --generate-zsh-completion --generate-fish-completion --man" 211 + 212 + if [[ ${cur} == -* ]] ; then 213 + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 214 + return 0 215 + fi 216 + 217 + # Complete with log files if not an option 218 + if [[ ${cur} != -* ]]; then 219 + COMPREPLY=( $(compgen -f -X '!*.log' -- ${cur}) ) 220 + return 0 221 + fi 222 + } 223 + 224 + complete -F _soapdump_completions soapdump 225 + )" << std::endl; 226 + } 227 + 228 + void generateZshCompletion() { 229 + std::cout << R"( 230 + #compdef soapdump 231 + 232 + _arguments -s -S \ 233 + '(-h --help)'{-h,--help}'[Show help message]' \ 234 + '(-s --summary)'{-s,--summary}'[Show summary statistics only]' \ 235 + '(-r --raw)'{-r,--raw}'[Output raw structured data (default)]' \ 236 + '--generate-bash-completion[Generate Bash completion script]' \ 237 + '--generate-zsh-completion[Generate Zsh completion script]' \ 238 + '--generate-fish-completion[Generate Fish completion script]' \ 239 + '--man[Generate man page]' \ 240 + '*:log file:_files -g "*.log"' 241 + )" << std::endl; 242 + } 243 + 244 + void generateFishCompletion() { 245 + std::cout << R"( 246 + function __fish_soapdump_no_subcommand 247 + set cmd (commandline -opc) 248 + if [ (count $cmd) -eq 1 ] 249 + return 0 250 + end 251 + return 1 252 + end 253 + 254 + complete -c soapdump -s h -l help -d "Show help message" 255 + complete -c soapdump -s s -l summary -d "Show summary statistics only" 256 + complete -c soapdump -s r -l raw -d "Output raw structured data (default)" 257 + complete -c soapdump -l generate-bash-completion -d "Generate Bash completion script" 258 + complete -c soapdump -l generate-zsh-completion -d "Generate Zsh completion script" 259 + complete -c soapdump -l generate-fish-completion -d "Generate Fish completion script" 260 + complete -c soapdump -l man -d "Generate man page" 261 + complete -c soapdump -n "__fish_soapdump_no_subcommand" -a "*.log" -d "Log file" 262 + )" << std::endl; 263 + } 264 + 265 + void generateManPage() { 266 + std::cout << R"(.TH SOAPDUMP 1 "September 2025" "soapdump 0.1.0" "User Commands" 267 + .SH NAME 268 + soapdump \- PayPal SOAP log parser 269 + .SH SYNOPSIS 270 + .B soapdump 271 + [\fIOPTIONS\fR] \fILOGFILE\fR 272 + .SH DESCRIPTION 273 + .B soapdump 274 + is a high-performance PayPal SOAP log parser that extracts transaction data from log files and outputs it in a structured format. 275 + .SH OPTIONS 276 + .TP 277 + .BR \-h ", " \-\-help 278 + Show help message and exit. 279 + .TP 280 + .BR \-s ", " \-\-summary 281 + Show summary statistics only. 282 + .TP 283 + .BR \-r ", " \-\-raw 284 + Output raw structured data (default). 285 + .TP 286 + .BR \-\-generate-bash-completion 287 + Generate Bash completion script. 288 + .TP 289 + .BR \-\-generate-zsh-completion 290 + Generate Zsh completion script. 291 + .TP 292 + .BR \-\-generate-fish-completion 293 + Generate Fish completion script. 294 + .TP 295 + .BR \-\-man 296 + Generate this man page. 297 + .SH OUTPUT FORMAT 298 + The output is pipe-separated with the following fields: 299 + .PP 300 + TRANS_NUM|AMOUNT|CURRENCY|FIRSTNAME|LASTNAME|STREET|CITY|STATE|ZIP|CCTYPE|CCLAST4|EXPMONTH|EXPYEAR|CVV|TRANSID|STATUS|CORRID|PROC_AMOUNT 301 + .SH EXAMPLES 302 + .TP 303 + Get all transactions: 304 + .B soapdump payments.log 305 + .TP 306 + Get only successful transactions: 307 + .B soapdump payments.log | grep Success 308 + .TP 309 + Count transactions by state: 310 + .B soapdump payments.log | cut -d'|' -f8 | sort | uniq -c | sort -nr 311 + .TP 312 + Find largest transaction: 313 + .B soapdump payments.log | sort -t'|' -k2 -nr | head -1 314 + .TP 315 + Get transactions over $500: 316 + .B soapdump payments.log | awk -F'|' '$2 > 500' 317 + .TP 318 + Summary stats: 319 + .B soapdump -s payments.log 320 + .SH AUTHOR 321 + Kieran Klukas <me@dunkirk.sh> 322 + .SH COPYRIGHT 323 + Copyright \(co 2025 Kieran Klukas. License: MIT. 324 + )" << std::endl; 325 + } 326 + 173 327 std::string extractXmlValue(const std::string& xml, const std::string& tag) { 174 328 std::regex pattern("<" + tag + "(?:[^>]*)>([^<]*)</" + tag + ">"); 175 329 std::smatch match; ··· 191 345 std::vector<std::string> extractRequests(const std::string& logContent) { 192 346 std::vector<std::string> requests; 193 347 std::regex pattern("PPAPIService: Request: (.*)"); 194 - 348 + 195 349 std::string::const_iterator searchStart(logContent.cbegin()); 196 350 std::smatch match; 197 351 while (std::regex_search(searchStart, logContent.cend(), match, pattern)) { ··· 200 354 } 201 355 searchStart = match.suffix().first; 202 356 } 203 - 357 + 204 358 return requests; 205 359 } 206 360 207 361 std::vector<std::string> extractResponses(const std::string& logContent) { 208 362 std::vector<std::string> responses; 209 363 std::regex pattern("PPAPIService: Response: <\\?.*\\?>(.*)"); 210 - 364 + 211 365 std::string::const_iterator searchStart(logContent.cbegin()); 212 366 std::smatch match; 213 367 while (std::regex_search(searchStart, logContent.cend(), match, pattern)) { ··· 216 370 } 217 371 searchStart = match.suffix().first; 218 372 } 219 - 373 + 220 374 return responses; 221 375 } 222 376 223 377 std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls) { 224 378 std::vector<Response> responses; 225 - 379 + 226 380 for (const auto& xml : responseXmls) { 227 381 Response response; 228 382 response.transId = extractXmlValue(xml, "TransactionID"); 229 383 response.status = extractXmlValue(xml, "Ack"); 230 384 response.corrId = extractXmlValue(xml, "CorrelationID"); 231 385 response.procAmount = extractXmlValue(xml, "Amount"); 232 - 386 + 233 387 responses.push_back(response); 234 388 } 235 - 389 + 236 390 return responses; 237 391 } 238 392 239 393 std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses) { 240 394 std::vector<Transaction> transactions; 241 395 int transNum = 1; 242 - 396 + 243 397 for (size_t i = 0; i < requestXmls.size(); ++i) { 244 398 const auto& xml = requestXmls[i]; 245 - 399 + 246 400 Transaction transaction; 247 401 transaction.transNum = transNum++; 248 - 402 + 249 403 // Extract request fields 250 404 transaction.amount = extractXmlValue(xml, "ebl:OrderTotal"); 251 405 transaction.currency = extractXmlAttribute(xml, "currencyID"); ··· 260 414 transaction.expMonth = extractXmlValue(xml, "ebl:ExpMonth"); 261 415 transaction.expYear = extractXmlValue(xml, "ebl:ExpYear"); 262 416 transaction.cvv = extractXmlValue(xml, "ebl:CVV2"); 263 - 417 + 264 418 // Get corresponding response data 265 419 if (i < responses.size()) { 266 420 transaction.transId = responses[i].transId; ··· 268 422 transaction.corrId = responses[i].corrId; 269 423 transaction.procAmount = responses[i].procAmount; 270 424 } 271 - 425 + 272 426 transactions.push_back(transaction); 273 427 } 274 - 428 + 275 429 return transactions; 276 430 } 277 431 ··· 300 454 301 455 void outputSummary(const std::vector<Transaction>& transactions) { 302 456 std::cout << "=== SUMMARY ===" << std::endl; 303 - 457 + 304 458 // Count transactions 305 459 int total = transactions.size(); 306 - int successful = std::count_if(transactions.begin(), transactions.end(), 460 + int successful = std::count_if(transactions.begin(), transactions.end(), 307 461 [](const Transaction& t) { return t.status == "Success"; }); 308 - 462 + 309 463 std::cout << "Total Transactions: " << total << std::endl; 310 464 std::cout << "Successful: " << successful << std::endl; 311 465 std::cout << "Failed: " << (total - successful) << std::endl; 312 466 std::cout << std::endl; 313 - 467 + 314 468 // Top 5 states 315 469 std::map<std::string, int> stateCounts; 316 470 for (const auto& t : transactions) { 317 471 stateCounts[t.state]++; 318 472 } 319 - 473 + 320 474 std::cout << "Top 5 States by Transaction Count:" << std::endl; 321 475 std::vector<std::pair<std::string, int>> stateCountVec(stateCounts.begin(), stateCounts.end()); 322 - std::sort(stateCountVec.begin(), stateCountVec.end(), 476 + std::sort(stateCountVec.begin(), stateCountVec.end(), 323 477 [](const auto& a, const auto& b) { return a.second > b.second; }); 324 - 478 + 325 479 int count = 0; 326 480 for (const auto& sc : stateCountVec) { 327 481 if (count++ >= 5) break; 328 482 std::cout << " " << sc.first << ": " << sc.second << std::endl; 329 483 } 330 484 std::cout << std::endl; 331 - 485 + 332 486 // Transaction amount stats 333 487 std::vector<double> amounts; 334 488 for (const auto& t : transactions) { ··· 338 492 // Skip invalid amounts 339 493 } 340 494 } 341 - 495 + 342 496 if (!amounts.empty()) { 343 497 double totalAmount = std::accumulate(amounts.begin(), amounts.end(), 0.0); 344 498 double largest = *std::max_element(amounts.begin(), amounts.end()); 345 499 double smallest = *std::min_element(amounts.begin(), amounts.end()); 346 - 500 + 347 501 std::cout << "Transaction Amount Stats:" << std::endl; 348 502 std::cout << " Total: $" << std::fixed << std::setprecision(2) << totalAmount << std::endl; 349 503 std::cout << " Largest: $" << std::fixed << std::setprecision(2) << largest << std::endl; 350 504 std::cout << " Smallest: $" << std::fixed << std::setprecision(2) << smallest << std::endl; 351 505 } 352 - } 506 + }