Skip to content

Commit 153b606

Browse files
committed
feat(cli): add replay command for recorded executions
1 parent 6367af6 commit 153b606

32 files changed

Lines changed: 6024 additions & 5 deletions
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
*
3+
* @file ReplayCommand.hpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira.
7+
* All rights reserved.
8+
* https://github.com/vixcpp/vix
9+
*
10+
* Use of this source code is governed by a MIT license
11+
* that can be found in the License file.
12+
*
13+
* Vix.cpp
14+
*
15+
*/
16+
#ifndef VIX_CLI_COMMANDS_REPLAY_COMMAND_HPP
17+
#define VIX_CLI_COMMANDS_REPLAY_COMMAND_HPP
18+
19+
#include <string>
20+
#include <vector>
21+
22+
namespace vix::commands::ReplayCommand
23+
{
24+
25+
/**
26+
* @brief Run the `vix replay` command.
27+
*
28+
* Supported forms:
29+
* - vix replay
30+
* - vix replay last
31+
* - vix replay latest
32+
* - vix replay failed
33+
* - vix replay <id>
34+
* - vix replay list
35+
* - vix replay list --failed
36+
* - vix replay show <id>
37+
* - vix replay clean
38+
*
39+
* @param args Command arguments after `replay`.
40+
* @return Process exit code.
41+
*/
42+
int run(const std::vector<std::string> &args);
43+
44+
/**
45+
* @brief Print help for the `vix replay` command.
46+
*
47+
* @return Process exit code.
48+
*/
49+
int help();
50+
51+
} // namespace vix::commands::ReplayCommand
52+
53+
#endif // VIX_CLI_COMMANDS_REPLAY_COMMAND_HPP
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
/**
2+
*
3+
* @file ReplayCapture.hpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira.
7+
* All rights reserved.
8+
* https://github.com/vixcpp/vix
9+
*
10+
* Use of this source code is governed by a MIT license
11+
* that can be found in the License file.
12+
*
13+
* Vix.cpp
14+
*
15+
*/
16+
#ifndef VIX_CLI_COMMANDS_REPLAY_CAPTURE_HPP
17+
#define VIX_CLI_COMMANDS_REPLAY_CAPTURE_HPP
18+
19+
#include <cstddef>
20+
#include <filesystem>
21+
#include <string>
22+
#include <vector>
23+
24+
#include <vix/cli/commands/replay/ReplayRecorder.hpp>
25+
#include <vix/cli/commands/replay/ReplayRecord.hpp>
26+
27+
namespace vix::commands::replay
28+
{
29+
30+
namespace fs = std::filesystem;
31+
32+
/**
33+
* @brief Captured output produced by one process execution.
34+
*/
35+
struct ReplayCapturedOutput
36+
{
37+
/**
38+
* @brief Captured stdout text.
39+
*/
40+
std::string stdout_text;
41+
42+
/**
43+
* @brief Captured stderr text.
44+
*/
45+
std::string stderr_text;
46+
47+
/**
48+
* @brief Combined stdout and stderr text.
49+
*/
50+
std::string combined_text;
51+
};
52+
53+
/**
54+
* @brief Normalized execution result captured for replay.
55+
*/
56+
struct ReplayCapturedResult
57+
{
58+
/**
59+
* @brief Captured output.
60+
*/
61+
ReplayCapturedOutput output{};
62+
63+
/**
64+
* @brief Captured process result.
65+
*/
66+
ReplayProcessResult process{};
67+
68+
/**
69+
* @brief Inferred replay status.
70+
*/
71+
ReplayStatus status{ReplayStatus::Unknown};
72+
73+
/**
74+
* @brief Inferred replay error kind.
75+
*/
76+
ReplayErrorKind error_kind{ReplayErrorKind::None};
77+
78+
/**
79+
* @brief Short error message when available.
80+
*/
81+
std::string error_message;
82+
83+
/**
84+
* @brief Optional hint shown to the user.
85+
*/
86+
std::string hint;
87+
};
88+
89+
/**
90+
* @brief Options controlling capture behavior.
91+
*/
92+
struct ReplayCaptureOptions
93+
{
94+
/**
95+
* @brief Maximum amount of stdout text kept in memory.
96+
*
97+
* The recorder may still write logs to disk, but memory is capped.
98+
*/
99+
std::size_t max_stdout_bytes{256 * 1024};
100+
101+
/**
102+
* @brief Maximum amount of stderr text kept in memory.
103+
*
104+
* The recorder may still write logs to disk, but memory is capped.
105+
*/
106+
std::size_t max_stderr_bytes{256 * 1024};
107+
108+
/**
109+
* @brief Maximum amount of combined output kept in memory.
110+
*/
111+
std::size_t max_combined_bytes{512 * 1024};
112+
113+
/**
114+
* @brief True when stdout chunks should be written to the recorder.
115+
*/
116+
bool write_stdout{true};
117+
118+
/**
119+
* @brief True when stderr chunks should be written to the recorder.
120+
*/
121+
bool write_stderr{true};
122+
123+
/**
124+
* @brief True when failed recorder writes should be ignored.
125+
*/
126+
bool ignore_recorder_errors{true};
127+
};
128+
129+
/**
130+
* @brief Stateful helper that mirrors process output into a ReplayRecorder.
131+
*
132+
* ReplayCapture is intentionally small. It does not execute a process by
133+
* itself. It receives stdout/stderr chunks from existing run logic and writes
134+
* them to the replay logs while keeping bounded in-memory excerpts.
135+
*/
136+
class ReplayCapture
137+
{
138+
public:
139+
/**
140+
* @brief Construct an inactive capture.
141+
*/
142+
ReplayCapture() = default;
143+
144+
/**
145+
* @brief Construct a capture bound to a recorder.
146+
*
147+
* @param recorder Replay recorder.
148+
* @param options Capture options.
149+
*/
150+
explicit ReplayCapture(
151+
ReplayRecorder *recorder,
152+
ReplayCaptureOptions options = {});
153+
154+
/**
155+
* @brief Attach this capture to a recorder.
156+
*
157+
* @param recorder Replay recorder.
158+
* @param options Capture options.
159+
*/
160+
void attach(
161+
ReplayRecorder *recorder,
162+
ReplayCaptureOptions options = {});
163+
164+
/**
165+
* @brief Capture one stdout chunk.
166+
*
167+
* @param text Output text.
168+
* @param err Error message written on failure.
169+
* @return true on success.
170+
*/
171+
bool capture_stdout(const std::string &text, std::string &err);
172+
173+
/**
174+
* @brief Capture one stderr chunk.
175+
*
176+
* @param text Error output text.
177+
* @param err Error message written on failure.
178+
* @return true on success.
179+
*/
180+
bool capture_stderr(const std::string &text, std::string &err);
181+
182+
/**
183+
* @brief Capture a stdout chunk and ignore recorder errors.
184+
*
185+
* @param text Output text.
186+
*/
187+
void capture_stdout_noexcept(const std::string &text);
188+
189+
/**
190+
* @brief Capture a stderr chunk and ignore recorder errors.
191+
*
192+
* @param text Error output text.
193+
*/
194+
void capture_stderr_noexcept(const std::string &text);
195+
196+
/**
197+
* @brief Build the current captured output.
198+
*
199+
* @return Captured output.
200+
*/
201+
ReplayCapturedOutput output() const;
202+
203+
/**
204+
* @brief Return true when this capture has a recorder attached.
205+
*
206+
* @return true when active.
207+
*/
208+
bool active() const;
209+
210+
/**
211+
* @brief Return the last recorder error.
212+
*
213+
* @return Last recorder error message.
214+
*/
215+
const std::string &last_error() const;
216+
217+
private:
218+
/**
219+
* @brief Append text to a bounded string buffer.
220+
*
221+
* @param buffer Target buffer.
222+
* @param text Text to append.
223+
* @param maxBytes Maximum buffer size.
224+
*/
225+
static void append_bounded(
226+
std::string &buffer,
227+
const std::string &text,
228+
std::size_t maxBytes);
229+
230+
/**
231+
* @brief Attached replay recorder.
232+
*/
233+
ReplayRecorder *recorder_{nullptr};
234+
235+
/**
236+
* @brief Capture options.
237+
*/
238+
ReplayCaptureOptions options_{};
239+
240+
/**
241+
* @brief Bounded stdout memory buffer.
242+
*/
243+
std::string stdout_buffer_{};
244+
245+
/**
246+
* @brief Bounded stderr memory buffer.
247+
*/
248+
std::string stderr_buffer_{};
249+
250+
/**
251+
* @brief Bounded combined memory buffer.
252+
*/
253+
std::string combined_buffer_{};
254+
255+
/**
256+
* @brief Last recorder error.
257+
*/
258+
std::string last_error_{};
259+
};
260+
261+
/**
262+
* @brief Build a captured result from output and process values.
263+
*
264+
* @param output Captured output.
265+
* @param process Process result.
266+
* @return Captured result with inferred status and error kind.
267+
*/
268+
ReplayCapturedResult make_replay_captured_result(
269+
const ReplayCapturedOutput &output,
270+
const ReplayProcessResult &process);
271+
272+
/**
273+
* @brief Build recorder finish data from a captured result.
274+
*
275+
* @param result Captured result.
276+
* @return Recorder finish data.
277+
*/
278+
ReplayRecorderFinish make_replay_finish_from_capture(
279+
const ReplayCapturedResult &result);
280+
281+
/**
282+
* @brief Extract a short error message from captured output.
283+
*
284+
* @param output Captured output.
285+
* @return Short error message, or empty string.
286+
*/
287+
std::string replay_error_message_from_output(const ReplayCapturedOutput &output);
288+
289+
/**
290+
* @brief Extract a short replay hint from captured output.
291+
*
292+
* @param output Captured output.
293+
* @param process Process result.
294+
* @return Hint string, or empty string.
295+
*/
296+
std::string replay_hint_from_output(
297+
const ReplayCapturedOutput &output,
298+
const ReplayProcessResult &process);
299+
300+
/**
301+
* @brief Return the first non-empty line from a text block.
302+
*
303+
* @param text Text block.
304+
* @return First non-empty line, or empty string.
305+
*/
306+
std::string first_non_empty_line(const std::string &text);
307+
308+
/**
309+
* @brief Return the last non-empty line from a text block.
310+
*
311+
* @param text Text block.
312+
* @return Last non-empty line, or empty string.
313+
*/
314+
std::string last_non_empty_line(const std::string &text);
315+
316+
} // namespace vix::commands::replay
317+
318+
#endif // VIX_CLI_COMMANDS_REPLAY_CAPTURE_HPP

0 commit comments

Comments
 (0)