qubit_redact/formats/process/process_redaction_writer.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Aggregate process-command rendering through one borrowed transaction.
9
10use std::ffi::OsStr;
11
12use super::command_items::CommandItems;
13use crate::formats::argv::ArgvItem;
14use crate::formats::argv::ArgvRedactionWriter;
15use crate::formats::env::EnvRedactionWriter;
16use crate::runtime::TextSession;
17use crate::runtime::runtime_session::RuntimeSession;
18
19/// A borrowed process-command facade over one active redaction transaction.
20///
21/// This type owns no redactor, policy, result, or budget. Each operation
22/// delegates directly to the argv or environment namespace of the parent
23/// session, so process diagnostics participate in the same atomic output.
24///
25/// # Type Parameters
26///
27/// * `'session` - Borrow of the parent composer's unpublished transaction.
28///
29/// # Examples
30///
31/// ```
32/// use std::ffi::OsStr;
33/// use qubit_redact::Redactor;
34/// use qubit_redact::formats::argv::ArgvItem;
35///
36/// let output = Redactor::standard().text_composer().process(|process| {
37/// process.command(
38/// OsStr::new("client"),
39/// [ArgvItem::plain(OsStr::new("--help"))],
40/// [(OsStr::new("PASSWORD"), OsStr::new("raw-password"))],
41/// );
42/// }).finish();
43/// assert!(!output.text().as_str().contains("raw-password"));
44/// ```
45pub struct ProcessRedactionWriter<'session> {
46 /// The transaction receiving every rendered process component.
47 session: &'session mut TextSession,
48}
49
50impl<'session> ProcessRedactionWriter<'session> {
51 /// Creates a process facade that borrows `session` for one adapter closure.
52 ///
53 /// # Parameters
54 ///
55 /// * `session` - The active transaction that owns policy, budget, and
56 /// unpublished output.
57 ///
58 /// # Returns
59 ///
60 /// A façade that cannot outlive the borrowed transaction.
61 #[inline(always)]
62 #[must_use]
63 pub(crate) const fn new(session: &'session mut TextSession) -> Self {
64 Self { session }
65 }
66
67 /// Redacts a complete process command into the parent aggregate output.
68 ///
69 /// `program` is inserted as the first argv item. `arguments` therefore
70 /// must contain only the arguments following the executable. Each argument
71 /// preserves caller-supplied [`ArgvItem`] sensitivity. `variables` uses
72 /// borrowed operating-system name/value pairs and is rendered after argv.
73 /// No key or independently published result is created.
74 ///
75 /// # Type Parameters
76 ///
77 /// * `'arguments` - Lifetime of borrowed argument values.
78 /// * `A` - Finite argument source.
79 /// * `'variables` - Lifetime of borrowed environment names and values.
80 /// * `E` - Finite environment source.
81 ///
82 /// # Parameters
83 ///
84 /// * `program` - Executable path or program name.
85 /// * `arguments` - Arguments after `program`.
86 /// * `variables` - Environment-variable pairs associated with the command.
87 ///
88 /// # Returns
89 ///
90 /// This facade for further aggregate process operations.
91 pub fn command<'arguments, 'variables, A, E>(
92 &mut self,
93 program: &'arguments OsStr,
94 arguments: A,
95 variables: E,
96 ) -> &mut Self
97 where
98 A: IntoIterator<Item = ArgvItem<'arguments>>,
99 E: IntoIterator<Item = (&'variables OsStr, &'variables OsStr)>,
100 {
101 if self.session.skip_aggregate_for_exhausted_output() {
102 return self;
103 }
104 let arguments = arguments.into_iter();
105 let mut argv = ArgvRedactionWriter::new(self.session);
106 argv.heuristic_items(CommandItems::new(ArgvItem::plain(program), arguments));
107 if self.session.skip_aggregate_for_exhausted_output() {
108 return self;
109 }
110 let mut environment = EnvRedactionWriter::new(self.session);
111 environment.os_pairs(variables);
112 self
113 }
114
115 /// Redacts command-line arguments into the parent aggregate output.
116 ///
117 /// Plain arguments use the argv namespace's documented heuristic rules;
118 /// explicitly sensitive [`ArgvItem`] values are masked at their supplied
119 /// sensitivity. No key or standalone output is created.
120 ///
121 /// # Type Parameters
122 ///
123 /// * `'arguments` - Lifetime of borrowed argument values.
124 /// * `A` - Finite argument source.
125 ///
126 /// # Parameters
127 ///
128 /// * `arguments` - Borrowed argv items to render.
129 ///
130 /// # Returns
131 ///
132 /// This facade for further aggregate process operations.
133 #[inline(always)]
134 pub fn arguments<'arguments, A>(&mut self, arguments: A) -> &mut Self
135 where
136 A: IntoIterator<Item = ArgvItem<'arguments>>,
137 {
138 let mut argv = ArgvRedactionWriter::new(self.session);
139 argv.heuristic_items(arguments);
140 self
141 }
142
143 /// Redacts process environment variables into the parent aggregate output.
144 ///
145 /// Names and values remain borrowed until the environment namespace has
146 /// processed them. No key or standalone output is created.
147 ///
148 /// # Type Parameters
149 ///
150 /// * `'variables` - Lifetime of borrowed environment names and values.
151 /// * `E` - Finite environment source.
152 ///
153 /// # Parameters
154 ///
155 /// * `variables` - Borrowed operating-system environment name/value pairs.
156 ///
157 /// # Returns
158 ///
159 /// This facade for further aggregate process operations.
160 #[inline(always)]
161 pub fn variables<'variables, E>(&mut self, variables: E) -> &mut Self
162 where
163 E: IntoIterator<Item = (&'variables OsStr, &'variables OsStr)>,
164 {
165 let mut env = EnvRedactionWriter::new(self.session);
166 env.os_pairs(variables);
167 self
168 }
169}