parses paypal soap logs

bug: fix C++ syntax errors in shell completion code

- Add missing cstring header for strcmp
- Fix raw string literal syntax in fish completion
- Ensure proper string escaping in completion scripts

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

dunkirk.sh 63b5bff3 bb3153b3

verified
Changed files
+44 -75
src
+1 -35
flake.lock
··· 18 18 }, 19 19 "root": { 20 20 "inputs": { 21 - "nixpkgs": "nixpkgs", 22 - "utils": "utils" 23 - } 24 - }, 25 - "systems": { 26 - "locked": { 27 - "lastModified": 1681028828, 28 - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 - "owner": "nix-systems", 30 - "repo": "default", 31 - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 - "type": "github" 33 - }, 34 - "original": { 35 - "owner": "nix-systems", 36 - "repo": "default", 37 - "type": "github" 38 - } 39 - }, 40 - "utils": { 41 - "inputs": { 42 - "systems": "systems" 43 - }, 44 - "locked": { 45 - "lastModified": 1731533236, 46 - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 47 - "owner": "numtide", 48 - "repo": "flake-utils", 49 - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 50 - "type": "github" 51 - }, 52 - "original": { 53 - "owner": "numtide", 54 - "repo": "flake-utils", 55 - "type": "github" 21 + "nixpkgs": "nixpkgs" 56 22 } 57 23 } 58 24 },
+17 -14
flake.nix
··· 5 5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 6 }; 7 7 8 - outputs = { self, nixpkgs, ... }: 8 + outputs = 9 + { self, nixpkgs, ... }: 9 10 let 10 11 allSystems = [ 11 12 "x86_64-linux" # 64-bit Intel/AMD Linux ··· 13 14 "x86_64-darwin" # 64-bit Intel macOS 14 15 "aarch64-darwin" # 64-bit ARM macOS 15 16 ]; 16 - forAllSystems = f: 17 + forAllSystems = 18 + f: 17 19 nixpkgs.lib.genAttrs allSystems ( 18 20 system: 19 21 f { ··· 30 32 pname = "soapdump"; 31 33 inherit version; 32 34 src = self; 33 - 34 - nativeBuildInputs = with pkgs; [ 35 + 36 + nativeBuildInputs = with pkgs; [ 35 37 clang 36 - installShellFiles 38 + installShellFiles 37 39 ]; 38 - 40 + 39 41 dontUseCmakeConfigure = true; 40 - 42 + 41 43 buildPhase = '' 42 44 # Direct compilation instead of using CMake 43 45 mkdir -p build 44 46 $CXX -std=c++17 -O3 -o build/soapdump $src/src/soapdump.cpp 45 47 ''; 46 - 48 + 47 49 installPhase = '' 48 50 mkdir -p $out/bin 49 51 cp build/soapdump $out/bin/ 50 - 52 + 51 53 # Generate and install shell completions 52 54 mkdir -p completions 53 55 $out/bin/soapdump --generate-bash-completion > completions/soapdump.bash 54 56 $out/bin/soapdump --generate-zsh-completion > completions/soapdump.zsh 55 57 $out/bin/soapdump --generate-fish-completion > completions/soapdump.fish 56 - 58 + 57 59 installShellCompletion --cmd soapdump \ 58 60 --bash completions/soapdump.bash \ 59 61 --fish completions/soapdump.fish \ 60 62 --zsh completions/soapdump.zsh 61 - 63 + 62 64 # Generate and install man page 63 65 mkdir -p $out/share/man/man1 64 66 $out/bin/soapdump --man > $out/share/man/man1/soapdump.1 65 67 ''; 66 - 68 + 67 69 meta = with pkgs.lib; { 68 70 description = "A high-performance PayPal SOAP log parser"; 69 71 homepage = "https://github.com/taciturnaxolotl/soapdump"; ··· 95 97 buildInputs = with pkgs; [ 96 98 cmake 97 99 clang 100 + self.packages.${pkgs.system}.default 98 101 ]; 99 - 102 + 100 103 shellHook = '' 101 104 echo "SoapDump development environment loaded" 102 105 ''; ··· 106 109 107 110 formatter = forAllSystems ({ pkgs }: pkgs.nixfmt-tree); 108 111 }; 109 - } 112 + }
+26 -26
src/soapdump.cpp
··· 9 9 #include <numeric> 10 10 #include <iomanip> 11 11 #include <getopt.h> 12 - #include <unordered_map> 12 + #include <cstring> 13 13 14 14 // Transaction data structure 15 15 struct Transaction { ··· 253 253 254 254 complete -c soapdump -s h -l help -d "Show help message" 255 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)" 256 + complete -c soapdump -s r -l raw -d "Output raw structured data" 257 257 complete -c soapdump -l generate-bash-completion -d "Generate Bash completion script" 258 258 complete -c soapdump -l generate-zsh-completion -d "Generate Zsh completion script" 259 259 complete -c soapdump -l generate-fish-completion -d "Generate Fish completion script" ··· 345 345 std::vector<std::string> extractRequests(const std::string& logContent) { 346 346 std::vector<std::string> requests; 347 347 std::regex pattern("PPAPIService: Request: (.*)"); 348 - 348 + 349 349 std::string::const_iterator searchStart(logContent.cbegin()); 350 350 std::smatch match; 351 351 while (std::regex_search(searchStart, logContent.cend(), match, pattern)) { ··· 354 354 } 355 355 searchStart = match.suffix().first; 356 356 } 357 - 357 + 358 358 return requests; 359 359 } 360 360 361 361 std::vector<std::string> extractResponses(const std::string& logContent) { 362 362 std::vector<std::string> responses; 363 363 std::regex pattern("PPAPIService: Response: <\\?.*\\?>(.*)"); 364 - 364 + 365 365 std::string::const_iterator searchStart(logContent.cbegin()); 366 366 std::smatch match; 367 367 while (std::regex_search(searchStart, logContent.cend(), match, pattern)) { ··· 370 370 } 371 371 searchStart = match.suffix().first; 372 372 } 373 - 373 + 374 374 return responses; 375 375 } 376 376 377 377 std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls) { 378 378 std::vector<Response> responses; 379 - 379 + 380 380 for (const auto& xml : responseXmls) { 381 381 Response response; 382 382 response.transId = extractXmlValue(xml, "TransactionID"); 383 383 response.status = extractXmlValue(xml, "Ack"); 384 384 response.corrId = extractXmlValue(xml, "CorrelationID"); 385 385 response.procAmount = extractXmlValue(xml, "Amount"); 386 - 386 + 387 387 responses.push_back(response); 388 388 } 389 - 389 + 390 390 return responses; 391 391 } 392 392 393 393 std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses) { 394 394 std::vector<Transaction> transactions; 395 395 int transNum = 1; 396 - 396 + 397 397 for (size_t i = 0; i < requestXmls.size(); ++i) { 398 398 const auto& xml = requestXmls[i]; 399 - 399 + 400 400 Transaction transaction; 401 401 transaction.transNum = transNum++; 402 - 402 + 403 403 // Extract request fields 404 404 transaction.amount = extractXmlValue(xml, "ebl:OrderTotal"); 405 405 transaction.currency = extractXmlAttribute(xml, "currencyID"); ··· 414 414 transaction.expMonth = extractXmlValue(xml, "ebl:ExpMonth"); 415 415 transaction.expYear = extractXmlValue(xml, "ebl:ExpYear"); 416 416 transaction.cvv = extractXmlValue(xml, "ebl:CVV2"); 417 - 417 + 418 418 // Get corresponding response data 419 419 if (i < responses.size()) { 420 420 transaction.transId = responses[i].transId; ··· 422 422 transaction.corrId = responses[i].corrId; 423 423 transaction.procAmount = responses[i].procAmount; 424 424 } 425 - 425 + 426 426 transactions.push_back(transaction); 427 427 } 428 - 428 + 429 429 return transactions; 430 430 } 431 431 ··· 454 454 455 455 void outputSummary(const std::vector<Transaction>& transactions) { 456 456 std::cout << "=== SUMMARY ===" << std::endl; 457 - 457 + 458 458 // Count transactions 459 459 int total = transactions.size(); 460 - int successful = std::count_if(transactions.begin(), transactions.end(), 460 + int successful = std::count_if(transactions.begin(), transactions.end(), 461 461 [](const Transaction& t) { return t.status == "Success"; }); 462 - 462 + 463 463 std::cout << "Total Transactions: " << total << std::endl; 464 464 std::cout << "Successful: " << successful << std::endl; 465 465 std::cout << "Failed: " << (total - successful) << std::endl; 466 466 std::cout << std::endl; 467 - 467 + 468 468 // Top 5 states 469 469 std::map<std::string, int> stateCounts; 470 470 for (const auto& t : transactions) { 471 471 stateCounts[t.state]++; 472 472 } 473 - 473 + 474 474 std::cout << "Top 5 States by Transaction Count:" << std::endl; 475 475 std::vector<std::pair<std::string, int>> stateCountVec(stateCounts.begin(), stateCounts.end()); 476 - std::sort(stateCountVec.begin(), stateCountVec.end(), 476 + std::sort(stateCountVec.begin(), stateCountVec.end(), 477 477 [](const auto& a, const auto& b) { return a.second > b.second; }); 478 - 478 + 479 479 int count = 0; 480 480 for (const auto& sc : stateCountVec) { 481 481 if (count++ >= 5) break; 482 482 std::cout << " " << sc.first << ": " << sc.second << std::endl; 483 483 } 484 484 std::cout << std::endl; 485 - 485 + 486 486 // Transaction amount stats 487 487 std::vector<double> amounts; 488 488 for (const auto& t : transactions) { ··· 492 492 // Skip invalid amounts 493 493 } 494 494 } 495 - 495 + 496 496 if (!amounts.empty()) { 497 497 double totalAmount = std::accumulate(amounts.begin(), amounts.end(), 0.0); 498 498 double largest = *std::max_element(amounts.begin(), amounts.end()); 499 499 double smallest = *std::min_element(amounts.begin(), amounts.end()); 500 - 500 + 501 501 std::cout << "Transaction Amount Stats:" << std::endl; 502 502 std::cout << " Total: $" << std::fixed << std::setprecision(2) << totalAmount << std::endl; 503 503 std::cout << " Largest: $" << std::fixed << std::setprecision(2) << largest << std::endl; 504 504 std::cout << " Smallest: $" << std::fixed << std::setprecision(2) << smallest << std::endl; 505 505 } 506 - } 506 + }