1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
* PTA-Generator 2025
* SPDX-License-Identifier: Apache-2.0
*/
use crate::setup::{JournalFlavor, ShardType};
use clap::builder::PossibleValue;
use clap::{Parser, Subcommand};
use jiff::Zoned;
use jiff::civil::date;
use jiff::tz::TimeZone;
use std::path::PathBuf;
#[derive(Parser)]
#[command(author, version=env!("VERSION"), about, long_about = None)]
#[command(propagate_version = true)]
#[command(args_conflicts_with_subcommands = true)]
pub(crate) struct Cli {
#[command(subcommand)]
command: Commands,
}
impl Cli {
pub fn cmd(&self) -> Commands {
self.command.clone()
}
}
#[derive(Clone, Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Commands {
/// Generate journal for audit testing
///
/// This will use stable predictable UUID for
/// each transaction, so that Txn Set Checksum will be stay
/// same for the same generated txn sets.
///
/// Supported flavors: tackler
Audit {
#[clap(flatten)]
global_args: GlobalArgs,
/// Flavor of Journal
#[arg(long,
value_parser([
PossibleValue::new(JournalFlavor::TACKLER),
]),
)]
flavor: Option<String>,
},
/// Generate journal in the simplest form
///
/// The simplest journal, could be used as baseline for `audit` and `comm`
/// Beancount IS NOT supported, as it needs always Chart of Accounts.
///
/// Supported flavors: tackler, (h)ledger
Plain {
#[clap(flatten)]
global_args: GlobalArgs,
/// Flavor of Journal
#[arg(long,
value_parser([
PossibleValue::new(JournalFlavor::TACKLER),
PossibleValue::new(JournalFlavor::LEDGER),
]),
)]
flavor: Option<String>,
},
/// Generate journal with commodities
///
/// Journal with commodities
///
/// Supported flavors: tackler, (h)ledger, beancount
Comm {
#[clap(flatten)]
global_args: GlobalArgs,
/// Flavor of Journal
#[arg(long,
value_parser([
PossibleValue::new(JournalFlavor::TACKLER),
PossibleValue::new(JournalFlavor::LEDGER),
PossibleValue::new(JournalFlavor::BEANCOUNT),
]),
)]
flavor: Option<String>,
},
}
#[allow(clippy::doc_overindented_list_items)]
#[derive(Debug, Clone, clap::Args)]
pub(crate) struct GlobalArgs {
/// Path to output directory
///
/// Under this path there will be a folder named as
/// `set-<SIZE>-<SHARD_TYPE>`
#[arg(long = "path", value_name = "path/to/output_directory")]
pub data_path: PathBuf,
/// How to store (shard) the transaction journal
///
/// - single: Use single journal file
/// path(tackler): 'PATH/set-SET-single/txns/SET-journal.txn
/// path(h/ledger): 'PATH/set-SET-single/txns/SET.journal
/// path(beancount): 'PATH/set-SET-single/txns/SET.beancount
/// - month: Use 12 sub journals (shards) based on month of txn
/// path: 'PATH/set-SET-month/txns/YYYY/MM/YYYY-MM.txn
/// - txn: Use own file for each transaction, shard is based on txn timestamp
/// path: 'PATH/set-SET-txn/txns/YYYY/MM/DD/YYYYMMDDTHHMMSS-IDX.txn'
#[arg(long, verbatim_doc_comment,
value_parser([
PossibleValue::new(ShardType::SINGLE),
PossibleValue::new(ShardType::MONTH),
PossibleValue::new(ShardType::TXN),
]),
)]
pub shard_type: String,
/// How many transactions to generate (1e1, 1e2, ...)
///
/// Supported set sizes are:
/// 1e1 = 10
/// 1e2 = 100
/// 1e3 = 1_000
/// 1e4 = 10_000
/// 1e5 = 100_000
/// 1e6 = 1_000_000
#[arg(long, verbatim_doc_comment)]
pub set_size: String,
/// Optional start time in RFC-9557 format
///
/// Default is: 2024-01-01T00:00:00+00:00[UTC]
///
/// If used, must be with `stop`
#[arg(long, value_name = "START_TIMESTAMP", requires = "stop")]
start: Option<String>,
/// Optional stop time in RFC-9557 format
///
/// Default is: 2025-01-01T00:00:00+00:00[UTC]
///
/// If used, must be with `start`
#[arg(long, value_name = "STOP_TIMESTAMP", requires = "start")]
stop: Option<String>,
}
impl GlobalArgs {
pub fn start_ts(&self) -> Result<Zoned, jiff::Error> {
self.start.as_ref().map_or(
date(2024, 1, 1).at(0, 0, 0, 0).to_zoned(TimeZone::UTC),
|d| d.parse(),
)
}
pub fn stop_ts(&self) -> Result<Zoned, jiff::Error> {
self.stop.as_ref().map_or(
date(2025, 1, 1).at(0, 0, 0, 0).to_zoned(TimeZone::UTC),
|d| d.parse(),
)
}
}
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert();
}