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
···
},
"root": {
"inputs": {
-
"nixpkgs": "nixpkgs",
-
"utils": "utils"
-
}
-
},
-
"systems": {
-
"locked": {
-
"lastModified": 1681028828,
-
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-
"owner": "nix-systems",
-
"repo": "default",
-
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-
"type": "github"
-
},
-
"original": {
-
"owner": "nix-systems",
-
"repo": "default",
-
"type": "github"
-
}
-
},
-
"utils": {
-
"inputs": {
-
"systems": "systems"
-
},
-
"locked": {
-
"lastModified": 1731533236,
-
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
-
"owner": "numtide",
-
"repo": "flake-utils",
-
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
-
"type": "github"
-
},
-
"original": {
-
"owner": "numtide",
-
"repo": "flake-utils",
-
"type": "github"
}
}
},
···
},
"root": {
"inputs": {
+
"nixpkgs": "nixpkgs"
}
}
},
+17 -14
flake.nix
···
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
-
outputs = { self, nixpkgs, ... }:
let
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
···
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
-
forAllSystems = f:
nixpkgs.lib.genAttrs allSystems (
system:
f {
···
pname = "soapdump";
inherit version;
src = self;
-
-
nativeBuildInputs = with pkgs; [
clang
-
installShellFiles
];
-
dontUseCmakeConfigure = true;
-
buildPhase = ''
# Direct compilation instead of using CMake
mkdir -p build
$CXX -std=c++17 -O3 -o build/soapdump $src/src/soapdump.cpp
'';
-
installPhase = ''
mkdir -p $out/bin
cp build/soapdump $out/bin/
-
# Generate and install shell completions
mkdir -p completions
$out/bin/soapdump --generate-bash-completion > completions/soapdump.bash
$out/bin/soapdump --generate-zsh-completion > completions/soapdump.zsh
$out/bin/soapdump --generate-fish-completion > completions/soapdump.fish
-
installShellCompletion --cmd soapdump \
--bash completions/soapdump.bash \
--fish completions/soapdump.fish \
--zsh completions/soapdump.zsh
-
# Generate and install man page
mkdir -p $out/share/man/man1
$out/bin/soapdump --man > $out/share/man/man1/soapdump.1
'';
-
meta = with pkgs.lib; {
description = "A high-performance PayPal SOAP log parser";
homepage = "https://github.com/taciturnaxolotl/soapdump";
···
buildInputs = with pkgs; [
cmake
clang
];
-
shellHook = ''
echo "SoapDump development environment loaded"
'';
···
formatter = forAllSystems ({ pkgs }: pkgs.nixfmt-tree);
};
-
}
···
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
+
outputs =
+
{ self, nixpkgs, ... }:
let
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
···
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
+
forAllSystems =
+
f:
nixpkgs.lib.genAttrs allSystems (
system:
f {
···
pname = "soapdump";
inherit version;
src = self;
+
+
nativeBuildInputs = with pkgs; [
clang
+
installShellFiles
];
+
dontUseCmakeConfigure = true;
+
buildPhase = ''
# Direct compilation instead of using CMake
mkdir -p build
$CXX -std=c++17 -O3 -o build/soapdump $src/src/soapdump.cpp
'';
+
installPhase = ''
mkdir -p $out/bin
cp build/soapdump $out/bin/
+
# Generate and install shell completions
mkdir -p completions
$out/bin/soapdump --generate-bash-completion > completions/soapdump.bash
$out/bin/soapdump --generate-zsh-completion > completions/soapdump.zsh
$out/bin/soapdump --generate-fish-completion > completions/soapdump.fish
+
installShellCompletion --cmd soapdump \
--bash completions/soapdump.bash \
--fish completions/soapdump.fish \
--zsh completions/soapdump.zsh
+
# Generate and install man page
mkdir -p $out/share/man/man1
$out/bin/soapdump --man > $out/share/man/man1/soapdump.1
'';
+
meta = with pkgs.lib; {
description = "A high-performance PayPal SOAP log parser";
homepage = "https://github.com/taciturnaxolotl/soapdump";
···
buildInputs = with pkgs; [
cmake
clang
+
self.packages.${pkgs.system}.default
];
+
shellHook = ''
echo "SoapDump development environment loaded"
'';
···
formatter = forAllSystems ({ pkgs }: pkgs.nixfmt-tree);
};
+
}
+26 -26
src/soapdump.cpp
···
#include <numeric>
#include <iomanip>
#include <getopt.h>
-
#include <unordered_map>
// Transaction data structure
struct Transaction {
···
complete -c soapdump -s h -l help -d "Show help message"
complete -c soapdump -s s -l summary -d "Show summary statistics only"
-
complete -c soapdump -s r -l raw -d "Output raw structured data (default)"
complete -c soapdump -l generate-bash-completion -d "Generate Bash completion script"
complete -c soapdump -l generate-zsh-completion -d "Generate Zsh completion script"
complete -c soapdump -l generate-fish-completion -d "Generate Fish completion script"
···
std::vector<std::string> extractRequests(const std::string& logContent) {
std::vector<std::string> requests;
std::regex pattern("PPAPIService: Request: (.*)");
-
std::string::const_iterator searchStart(logContent.cbegin());
std::smatch match;
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
···
}
searchStart = match.suffix().first;
}
-
return requests;
}
std::vector<std::string> extractResponses(const std::string& logContent) {
std::vector<std::string> responses;
std::regex pattern("PPAPIService: Response: <\\?.*\\?>(.*)");
-
std::string::const_iterator searchStart(logContent.cbegin());
std::smatch match;
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
···
}
searchStart = match.suffix().first;
}
-
return responses;
}
std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls) {
std::vector<Response> responses;
-
for (const auto& xml : responseXmls) {
Response response;
response.transId = extractXmlValue(xml, "TransactionID");
response.status = extractXmlValue(xml, "Ack");
response.corrId = extractXmlValue(xml, "CorrelationID");
response.procAmount = extractXmlValue(xml, "Amount");
-
responses.push_back(response);
}
-
return responses;
}
std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses) {
std::vector<Transaction> transactions;
int transNum = 1;
-
for (size_t i = 0; i < requestXmls.size(); ++i) {
const auto& xml = requestXmls[i];
-
Transaction transaction;
transaction.transNum = transNum++;
-
// Extract request fields
transaction.amount = extractXmlValue(xml, "ebl:OrderTotal");
transaction.currency = extractXmlAttribute(xml, "currencyID");
···
transaction.expMonth = extractXmlValue(xml, "ebl:ExpMonth");
transaction.expYear = extractXmlValue(xml, "ebl:ExpYear");
transaction.cvv = extractXmlValue(xml, "ebl:CVV2");
-
// Get corresponding response data
if (i < responses.size()) {
transaction.transId = responses[i].transId;
···
transaction.corrId = responses[i].corrId;
transaction.procAmount = responses[i].procAmount;
}
-
transactions.push_back(transaction);
}
-
return transactions;
}
···
void outputSummary(const std::vector<Transaction>& transactions) {
std::cout << "=== SUMMARY ===" << std::endl;
-
// Count transactions
int total = transactions.size();
-
int successful = std::count_if(transactions.begin(), transactions.end(),
[](const Transaction& t) { return t.status == "Success"; });
-
std::cout << "Total Transactions: " << total << std::endl;
std::cout << "Successful: " << successful << std::endl;
std::cout << "Failed: " << (total - successful) << std::endl;
std::cout << std::endl;
-
// Top 5 states
std::map<std::string, int> stateCounts;
for (const auto& t : transactions) {
stateCounts[t.state]++;
}
-
std::cout << "Top 5 States by Transaction Count:" << std::endl;
std::vector<std::pair<std::string, int>> stateCountVec(stateCounts.begin(), stateCounts.end());
-
std::sort(stateCountVec.begin(), stateCountVec.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
-
int count = 0;
for (const auto& sc : stateCountVec) {
if (count++ >= 5) break;
std::cout << " " << sc.first << ": " << sc.second << std::endl;
}
std::cout << std::endl;
-
// Transaction amount stats
std::vector<double> amounts;
for (const auto& t : transactions) {
···
// Skip invalid amounts
}
}
-
if (!amounts.empty()) {
double totalAmount = std::accumulate(amounts.begin(), amounts.end(), 0.0);
double largest = *std::max_element(amounts.begin(), amounts.end());
double smallest = *std::min_element(amounts.begin(), amounts.end());
-
std::cout << "Transaction Amount Stats:" << std::endl;
std::cout << " Total: $" << std::fixed << std::setprecision(2) << totalAmount << std::endl;
std::cout << " Largest: $" << std::fixed << std::setprecision(2) << largest << std::endl;
std::cout << " Smallest: $" << std::fixed << std::setprecision(2) << smallest << std::endl;
}
-
}
···
#include <numeric>
#include <iomanip>
#include <getopt.h>
+
#include <cstring>
// Transaction data structure
struct Transaction {
···
complete -c soapdump -s h -l help -d "Show help message"
complete -c soapdump -s s -l summary -d "Show summary statistics only"
+
complete -c soapdump -s r -l raw -d "Output raw structured data"
complete -c soapdump -l generate-bash-completion -d "Generate Bash completion script"
complete -c soapdump -l generate-zsh-completion -d "Generate Zsh completion script"
complete -c soapdump -l generate-fish-completion -d "Generate Fish completion script"
···
std::vector<std::string> extractRequests(const std::string& logContent) {
std::vector<std::string> requests;
std::regex pattern("PPAPIService: Request: (.*)");
+
std::string::const_iterator searchStart(logContent.cbegin());
std::smatch match;
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
···
}
searchStart = match.suffix().first;
}
+
return requests;
}
std::vector<std::string> extractResponses(const std::string& logContent) {
std::vector<std::string> responses;
std::regex pattern("PPAPIService: Response: <\\?.*\\?>(.*)");
+
std::string::const_iterator searchStart(logContent.cbegin());
std::smatch match;
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
···
}
searchStart = match.suffix().first;
}
+
return responses;
}
std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls) {
std::vector<Response> responses;
+
for (const auto& xml : responseXmls) {
Response response;
response.transId = extractXmlValue(xml, "TransactionID");
response.status = extractXmlValue(xml, "Ack");
response.corrId = extractXmlValue(xml, "CorrelationID");
response.procAmount = extractXmlValue(xml, "Amount");
+
responses.push_back(response);
}
+
return responses;
}
std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses) {
std::vector<Transaction> transactions;
int transNum = 1;
+
for (size_t i = 0; i < requestXmls.size(); ++i) {
const auto& xml = requestXmls[i];
+
Transaction transaction;
transaction.transNum = transNum++;
+
// Extract request fields
transaction.amount = extractXmlValue(xml, "ebl:OrderTotal");
transaction.currency = extractXmlAttribute(xml, "currencyID");
···
transaction.expMonth = extractXmlValue(xml, "ebl:ExpMonth");
transaction.expYear = extractXmlValue(xml, "ebl:ExpYear");
transaction.cvv = extractXmlValue(xml, "ebl:CVV2");
+
// Get corresponding response data
if (i < responses.size()) {
transaction.transId = responses[i].transId;
···
transaction.corrId = responses[i].corrId;
transaction.procAmount = responses[i].procAmount;
}
+
transactions.push_back(transaction);
}
+
return transactions;
}
···
void outputSummary(const std::vector<Transaction>& transactions) {
std::cout << "=== SUMMARY ===" << std::endl;
+
// Count transactions
int total = transactions.size();
+
int successful = std::count_if(transactions.begin(), transactions.end(),
[](const Transaction& t) { return t.status == "Success"; });
+
std::cout << "Total Transactions: " << total << std::endl;
std::cout << "Successful: " << successful << std::endl;
std::cout << "Failed: " << (total - successful) << std::endl;
std::cout << std::endl;
+
// Top 5 states
std::map<std::string, int> stateCounts;
for (const auto& t : transactions) {
stateCounts[t.state]++;
}
+
std::cout << "Top 5 States by Transaction Count:" << std::endl;
std::vector<std::pair<std::string, int>> stateCountVec(stateCounts.begin(), stateCounts.end());
+
std::sort(stateCountVec.begin(), stateCountVec.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
+
int count = 0;
for (const auto& sc : stateCountVec) {
if (count++ >= 5) break;
std::cout << " " << sc.first << ": " << sc.second << std::endl;
}
std::cout << std::endl;
+
// Transaction amount stats
std::vector<double> amounts;
for (const auto& t : transactions) {
···
// Skip invalid amounts
}
}
+
if (!amounts.empty()) {
double totalAmount = std::accumulate(amounts.begin(), amounts.end(), 0.0);
double largest = *std::max_element(amounts.begin(), amounts.end());
double smallest = *std::min_element(amounts.begin(), amounts.end());
+
std::cout << "Transaction Amount Stats:" << std::endl;
std::cout << " Total: $" << std::fixed << std::setprecision(2) << totalAmount << std::endl;
std::cout << " Largest: $" << std::fixed << std::setprecision(2) << largest << std::endl;
std::cout << " Smallest: $" << std::fixed << std::setprecision(2) << smallest << std::endl;
}
+
}