+1
-35
flake.lock
+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
+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
+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
+
}