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
//! PaperBoy — a Rust-native API client (Postman alternative). Two front-ends
//! over one core: a terminal UI (default), and a headless CLI runner
//! (`-c collection.hurl [-e environment.vars]`).
mod cli;
mod collection;
mod environment;
mod git_remote;
mod http;
mod hurl;
mod i18n;
mod persistence;
mod postman;
mod report;
mod report_cli;
mod request;
mod shared_utils;
mod tree;
mod tui;
mod workspace;
use clap::Parser;
/// PaperBoy — a Rust API client with a terminal UI and a headless runner.
#[derive(Parser)]
#[command(
name = "paperboy",
version,
about = "PaperBoy — a Rust-native API client (a Postman alternative).",
long_about = "PaperBoy — a Rust-native API client (a Postman alternative).\n\n\
Runs in one of three modes:\n\
\x20 TUI (default) a terminal user interface\n\
\x20 CLI (-c/--collection) run a Hurl or Postman collection headlessly, then exit\n\
\x20 Report (-r/--report) run a PaperTrail report against a collection, then exit",
after_help = "Examples:\n\
\x20 paperboy Launch the terminal UI (default)\n\
\x20 paperboy -c collection.hurl Run a collection headlessly\n\
\x20 paperboy -c collection.hurl -e environment.vars Run a collection with an environment\n\
\x20 paperboy -c collection.hurl --batch Run as one batch (preserves cookies across requests)\n\
\x20 paperboy -c collection.hurl -e environment.vars -r report.trail Run a report\n\
\x20 paperboy -r report.trail Run a report, taking its collection/environment from the report's own headers\n\
\x20 paperboy -c collection.hurl -e prod.vars -e staging.vars -r report.trail Run a baseline/comparison report\n\
\x20 paperboy -c collection.hurl -r report.trail --dry-run Preview a report without sending anything\n\
\x20 paperboy -c collection.hurl -r report.trail -o out.csv Write the report to a file (- = stdout)\n\n\
Environment (.vars) entries are KEY=value, where the value is a literal or a\n\
{{ ... }} provider reference resolved when the environment is loaded:\n\
\x20 Literal value USERNAME=demo\n\
\x20 Process env var BASE_URL={{ env:DEMO_BASE_URL }}\n\
\x20 1Password (op CLI) API_TOKEN={{ op://Vault/Item/field }}\n\
\x20 AWS SSM parameter DB_PASSWORD={{ ssm:/path/to/param }}\n\n\
Collections are Hurl files (.hurl) or Postman collection exports (.json);\n\
Postman JSON is imported automatically."
)]
struct Cli {
/// Run the given collection (Hurl `.hurl` or Postman `.json`) headlessly and print the results.
#[arg(short = 'c', long, value_name = "FILE")]
collection: Option<String>,
/// Environment (.vars) file supplying `{{ VAR }}` values. Repeatable: pass
/// `-e` more than once to load several environments for a report (`-r`) —
/// each is named by its file stem and becomes selectable in an `ENVS` loop
/// (e.g. `-e prod.vars -e staging.vars` satisfies
/// `FOR … IN ENVS BASELINE("prod"), COMPARISON("staging")`). The first `-e`
/// is the base variable layer. A plain collection run (`-c` only) uses just
/// the first.
#[arg(short = 'e', long, value_name = "FILE")]
env: Vec<String>,
/// Run every request as a single batch instead of streaming each result
/// as soon as it finishes. Slower to show any output, but preserves
/// Hurl's automatic cookie jar (cookies remembered from `Set-Cookie`
/// response headers) across every request in the collection — the
/// default streaming mode does not carry cookies between requests (an
/// explicit `[Cookies]` section on a request is unaffected either way).
#[arg(short = 'b', long)]
batch: bool,
/// Run a PaperTrail report (`.trail`) and exit. The collection to run
/// against comes from `-c`, or (when `-c` is omitted) the report's own
/// `# collection:` header resolved relative to the report's folder. `-e`
/// supplies the base variable layer and (when repeated) the environments an
/// `ENVS` loop can name; with no `-e`, the report's `# environment:` header
/// (if any) is used instead.
#[arg(short = 'r', long, value_name = "FILE")]
report: Option<String>,
/// With `-r`: expand the report and show what it would do without sending
/// any request (no HTTP). Handy before a large run.
#[arg(long)]
dry_run: bool,
/// With `-r`: where to write the report output. `-` writes CSV to stdout
/// (for piping); a path's extension selects the format (`.csv`, `.json`,
/// `.html` or `.xlsx`); omitted derives the file from the report's
/// `# output:`/`# name:` headers (next to the report file, honouring the
/// `{time}` token).
#[arg(short = 'o', long, value_name = "FILE")]
output: Option<String>,
}
fn main() {
let cli = Cli::parse();
// Headless report mode (`-r`): run a PaperTrail report. `-c` may be omitted
// — the report's `# collection:` header (resolved relative to the report's
// folder) is used instead; `report_cli::run` raises a clear error if neither
// is available.
if let Some(report) = cli.report {
std::process::exit(report_cli::run(
cli.collection,
cli.env,
report,
cli.output,
cli.dry_run,
));
}
// Headless CLI mode (explicit "run and exit").
if let Some(collection) = cli.collection {
if cli.env.len() > 1 {
eprintln!(
"warning: multiple -e environments are only used by reports (-r); running the collection with the first one"
);
}
std::process::exit(cli::run(collection, cli.env.into_iter().next(), cli.batch));
}
// Terminal UI (the default).
if let Err(e) = tui::run() {
eprintln!("tui error: {e}");
std::process::exit(1);
}
std::process::exit(0);
}