dsh-ops 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +189 -0
- package/LICENSE +30 -0
- package/NOTICE +106 -0
- package/PROVENANCE.md +417 -0
- package/README.en.md +121 -0
- package/README.md +113 -2
- package/README.zh.md +114 -0
- package/bin/dsh-ops.mjs +1216 -0
- package/cordis.patch.yml +160 -0
- package/docs/manual-validation.md +53 -0
- package/docs/schema-baseline.json +64 -0
- package/docs/schema-current.json +84 -0
- package/docs/schema-measurement.md +17 -0
- package/dsh-plugin.json +88 -0
- package/icon.svg +12 -0
- package/lib/binary.js +409 -0
- package/lib/config.js +198 -0
- package/lib/handshake.js +252 -0
- package/lib/index.js +108 -0
- package/lib/jobs.js +42 -0
- package/lib/policy.js +64 -0
- package/lib/presentation.js +63 -0
- package/lib/profile-install.js +61 -0
- package/lib/rust.js +194 -0
- package/lib/session-shells.js +78 -0
- package/lib/shells.js +993 -0
- package/lib/tools.js +657 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +114 -4
- package/vendor/fastctx/Cargo.lock +3210 -0
- package/vendor/fastctx/Cargo.toml +94 -0
- package/vendor/fastctx/FORK.md +119 -0
- package/vendor/fastctx/LICENSE-APACHE +201 -0
- package/vendor/fastctx/NOTICE +40 -0
- package/vendor/fastctx/README.md +439 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
- package/vendor/fastctx/UPSTREAM.md +49 -0
- package/vendor/fastctx/build.rs +413 -0
- package/vendor/fastctx/src/background_status.rs +403 -0
- package/vendor/fastctx/src/binary.rs +75 -0
- package/vendor/fastctx/src/bounded_sort.rs +500 -0
- package/vendor/fastctx/src/budget.rs +781 -0
- package/vendor/fastctx/src/cli/mod.rs +110 -0
- package/vendor/fastctx/src/context_guard.rs +289 -0
- package/vendor/fastctx/src/control/mod.rs +6 -0
- package/vendor/fastctx/src/control/paths.rs +49 -0
- package/vendor/fastctx/src/control/settings.rs +753 -0
- package/vendor/fastctx/src/control/transaction.rs +531 -0
- package/vendor/fastctx/src/edit/document.rs +535 -0
- package/vendor/fastctx/src/edit/locks.rs +371 -0
- package/vendor/fastctx/src/edit/mod.rs +213 -0
- package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
- package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
- package/vendor/fastctx/src/edit/private_storage.rs +234 -0
- package/vendor/fastctx/src/edit/replace.rs +1030 -0
- package/vendor/fastctx/src/edit_server.rs +53 -0
- package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
- package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
- package/vendor/fastctx/src/encoding.rs +1118 -0
- package/vendor/fastctx/src/file_executor.rs +1151 -0
- package/vendor/fastctx/src/file_snapshot.rs +1491 -0
- package/vendor/fastctx/src/glob_filter.rs +98 -0
- package/vendor/fastctx/src/glob_tool.rs +653 -0
- package/vendor/fastctx/src/grep_sink.rs +1162 -0
- package/vendor/fastctx/src/grep_tool.rs +2449 -0
- package/vendor/fastctx/src/lib.rs +45 -0
- package/vendor/fastctx/src/main.rs +15 -0
- package/vendor/fastctx/src/model.rs +51 -0
- package/vendor/fastctx/src/model_guidance.rs +62 -0
- package/vendor/fastctx/src/operation.rs +356 -0
- package/vendor/fastctx/src/ordered_window.rs +1235 -0
- package/vendor/fastctx/src/os_environment.rs +414 -0
- package/vendor/fastctx/src/path_codec.rs +850 -0
- package/vendor/fastctx/src/paths.rs +244 -0
- package/vendor/fastctx/src/process_identity.rs +763 -0
- package/vendor/fastctx/src/process_policy.rs +74 -0
- package/vendor/fastctx/src/read_tool/batch.rs +496 -0
- package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
- package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
- package/vendor/fastctx/src/read_tool/mod.rs +245 -0
- package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
- package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
- package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
- package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
- package/vendor/fastctx/src/render_plan.rs +468 -0
- package/vendor/fastctx/src/runtime/activity.rs +159 -0
- package/vendor/fastctx/src/runtime/hosts.rs +99 -0
- package/vendor/fastctx/src/runtime/journal.rs +556 -0
- package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
- package/vendor/fastctx/src/runtime/mod.rs +746 -0
- package/vendor/fastctx/src/runtime/protocol.rs +296 -0
- package/vendor/fastctx/src/runtime/session.rs +536 -0
- package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
- package/vendor/fastctx/src/search_parallelism.rs +106 -0
- package/vendor/fastctx/src/search_text.rs +227 -0
- package/vendor/fastctx/src/server.rs +359 -0
- package/vendor/fastctx/src/server_manifest.rs +468 -0
- package/vendor/fastctx/src/server_support.rs +826 -0
- package/vendor/fastctx/src/session.rs +629 -0
- package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
- package/vendor/fastctx/src/shell/bash.rs +263 -0
- package/vendor/fastctx/src/shell/buffer.rs +108 -0
- package/vendor/fastctx/src/shell/encoding.rs +403 -0
- package/vendor/fastctx/src/shell/foreground.rs +115 -0
- package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
- package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
- package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
- package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
- package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
- package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
- package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
- package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
- package/vendor/fastctx/src/shell/mod.rs +345 -0
- package/vendor/fastctx/src/shell/normalize.rs +389 -0
- package/vendor/fastctx/src/shell/output.rs +406 -0
- package/vendor/fastctx/src/shell/process.rs +493 -0
- package/vendor/fastctx/src/shell_server.rs +156 -0
- package/vendor/fastctx/src/skip_report.rs +83 -0
- package/vendor/fastctx/src/stdio_transport.rs +177 -0
- package/vendor/fastctx/src/tool_schema.rs +204 -0
- package/vendor/fastctx/src/traversal.rs +846 -0
- package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
//! Streaming terminal-output normalization with deterministic, bounded raw-byte storage.
|
|
2
|
+
|
|
3
|
+
use serde::{Deserialize, Serialize};
|
|
4
|
+
|
|
5
|
+
/// Raw bytes retained per normalized line. This covers 2,000 characters even
|
|
6
|
+
/// for the widest WHATWG encodings while keeping a single-line stream bounded.
|
|
7
|
+
const MAX_LINE_PREFIX_BYTES: usize = 64 * 1024;
|
|
8
|
+
|
|
9
|
+
/// A BOM-locked stream encoding whose code-unit width affects line splitting.
|
|
10
|
+
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
11
|
+
#[serde(rename_all = "kebab-case")]
|
|
12
|
+
pub(crate) enum StreamEncoding {
|
|
13
|
+
Utf16Le,
|
|
14
|
+
Utf16Be,
|
|
15
|
+
Utf32Le,
|
|
16
|
+
Utf32Be,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl StreamEncoding {
|
|
20
|
+
pub(crate) const fn label(self) -> &'static str {
|
|
21
|
+
match self {
|
|
22
|
+
Self::Utf16Le => "UTF-16LE",
|
|
23
|
+
Self::Utf16Be => "UTF-16BE",
|
|
24
|
+
Self::Utf32Le => "UTF-32LE",
|
|
25
|
+
Self::Utf32Be => "UTF-32BE",
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const fn unit_bytes(self) -> usize {
|
|
30
|
+
match self {
|
|
31
|
+
Self::Utf16Le | Self::Utf16Be => 2,
|
|
32
|
+
Self::Utf32Le | Self::Utf32Be => 4,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fn ascii_unit(self, bytes: &[u8]) -> Option<u8> {
|
|
37
|
+
let value = match self {
|
|
38
|
+
Self::Utf16Le => u32::from(u16::from_le_bytes([bytes[0], bytes[1]])),
|
|
39
|
+
Self::Utf16Be => u32::from(u16::from_be_bytes([bytes[0], bytes[1]])),
|
|
40
|
+
Self::Utf32Le => u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
|
|
41
|
+
Self::Utf32Be => u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
|
|
42
|
+
};
|
|
43
|
+
u8::try_from(value).ok().filter(u8::is_ascii)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// One normalized output line whose source bytes remain available for delivery-time decoding.
|
|
48
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
49
|
+
pub(crate) struct NormalizedLine {
|
|
50
|
+
/// Prefix after ANSI stripping and line-ending normalization.
|
|
51
|
+
pub(crate) bytes: Vec<u8>,
|
|
52
|
+
/// Full normalized byte count even when only a bounded prefix is retained.
|
|
53
|
+
pub(crate) total_bytes: u64,
|
|
54
|
+
/// True when CR, LF, or CRLF terminated this line.
|
|
55
|
+
pub(crate) terminated: bool,
|
|
56
|
+
/// BOM-locked encoding for wide streams; absent for ASCII-compatible streams.
|
|
57
|
+
pub(crate) stream_encoding: Option<StreamEncoding>,
|
|
58
|
+
/// True when bytes beyond the bounded raw prefix were discarded.
|
|
59
|
+
pub(crate) raw_truncated: bool,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
63
|
+
enum EscapeState {
|
|
64
|
+
Text,
|
|
65
|
+
Escape,
|
|
66
|
+
Csi,
|
|
67
|
+
Osc,
|
|
68
|
+
OscEscape,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
72
|
+
enum StreamMode {
|
|
73
|
+
Bytes,
|
|
74
|
+
Wide(StreamEncoding),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Streaming events used by the background-log writer. Byte events are not
|
|
78
|
+
/// line-bounded, so an arbitrarily long line never has to live in memory.
|
|
79
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
80
|
+
pub(crate) enum NormalizedEvent {
|
|
81
|
+
/// Announces the stream encoding after the BOM probe is settled.
|
|
82
|
+
Start(Option<StreamEncoding>),
|
|
83
|
+
/// Normalized content bytes with ANSI sequences and line endings removed.
|
|
84
|
+
Bytes(Vec<u8>),
|
|
85
|
+
/// Commits one logical line; an unterminated line is emitted only at stream end.
|
|
86
|
+
LineEnd { terminated: bool },
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[derive(Debug, Default)]
|
|
90
|
+
struct LineAccumulator {
|
|
91
|
+
prefix: Vec<u8>,
|
|
92
|
+
total_bytes: u64,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
impl LineAccumulator {
|
|
96
|
+
fn push(&mut self, bytes: &[u8]) {
|
|
97
|
+
self.total_bytes = self.total_bytes.saturating_add(bytes.len() as u64);
|
|
98
|
+
let remaining = MAX_LINE_PREFIX_BYTES.saturating_sub(self.prefix.len());
|
|
99
|
+
self.prefix
|
|
100
|
+
.extend_from_slice(&bytes[..bytes.len().min(remaining)]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn finish(self, terminated: bool, stream_encoding: Option<StreamEncoding>) -> NormalizedLine {
|
|
104
|
+
NormalizedLine {
|
|
105
|
+
raw_truncated: self.total_bytes > self.prefix.len() as u64,
|
|
106
|
+
bytes: self.prefix,
|
|
107
|
+
total_bytes: self.total_bytes,
|
|
108
|
+
terminated,
|
|
109
|
+
stream_encoding,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Stateful byte processor shared by bounded foreground capture and the complete
|
|
115
|
+
/// background log. It never owns a whole logical line.
|
|
116
|
+
#[derive(Debug)]
|
|
117
|
+
struct NormalizerCore {
|
|
118
|
+
mode: Option<StreamMode>,
|
|
119
|
+
bom_probe: Vec<u8>,
|
|
120
|
+
wide_pending: Vec<u8>,
|
|
121
|
+
escape: EscapeState,
|
|
122
|
+
pending_cr: bool,
|
|
123
|
+
emitted: Vec<u8>,
|
|
124
|
+
line_has_content: bool,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
impl NormalizerCore {
|
|
128
|
+
fn new() -> Self {
|
|
129
|
+
Self {
|
|
130
|
+
mode: None,
|
|
131
|
+
bom_probe: Vec::with_capacity(4),
|
|
132
|
+
wide_pending: Vec::with_capacity(4),
|
|
133
|
+
escape: EscapeState::Text,
|
|
134
|
+
pending_cr: false,
|
|
135
|
+
emitted: Vec::with_capacity(16 * 1024),
|
|
136
|
+
line_has_content: false,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn push(&mut self, bytes: &[u8], output: &mut Vec<NormalizedEvent>) {
|
|
141
|
+
if self.mode.is_none() {
|
|
142
|
+
self.bom_probe.extend_from_slice(bytes);
|
|
143
|
+
self.select_mode(false, output);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
self.process(bytes, output);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn finish(&mut self, output: &mut Vec<NormalizedEvent>) {
|
|
150
|
+
if self.mode.is_none() {
|
|
151
|
+
self.select_mode(true, output);
|
|
152
|
+
}
|
|
153
|
+
if !self.wide_pending.is_empty() {
|
|
154
|
+
let pending = std::mem::take(&mut self.wide_pending);
|
|
155
|
+
self.push_text_unit(&pending, None, output);
|
|
156
|
+
}
|
|
157
|
+
if self.pending_cr {
|
|
158
|
+
self.emit_line(true, output);
|
|
159
|
+
self.pending_cr = false;
|
|
160
|
+
} else if self.line_has_content {
|
|
161
|
+
self.emit_line(false, output);
|
|
162
|
+
}
|
|
163
|
+
self.flush_bytes(output);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn select_mode(&mut self, final_chunk: bool, output: &mut Vec<NormalizedEvent>) {
|
|
167
|
+
let Some((mode, bom_len)) = detect_stream_mode(&self.bom_probe, final_chunk) else {
|
|
168
|
+
return;
|
|
169
|
+
};
|
|
170
|
+
self.mode = Some(mode);
|
|
171
|
+
output.push(NormalizedEvent::Start(match mode {
|
|
172
|
+
StreamMode::Bytes => None,
|
|
173
|
+
StreamMode::Wide(encoding) => Some(encoding),
|
|
174
|
+
}));
|
|
175
|
+
let buffered = std::mem::take(&mut self.bom_probe);
|
|
176
|
+
self.process(&buffered[bom_len.min(buffered.len())..], output);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn process(&mut self, bytes: &[u8], output: &mut Vec<NormalizedEvent>) {
|
|
180
|
+
match self
|
|
181
|
+
.mode
|
|
182
|
+
.expect("stream mode is selected before processing")
|
|
183
|
+
{
|
|
184
|
+
StreamMode::Bytes => {
|
|
185
|
+
for &byte in bytes {
|
|
186
|
+
self.process_unit(&[byte], Some(byte), output);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
StreamMode::Wide(encoding) => {
|
|
190
|
+
for &byte in bytes {
|
|
191
|
+
self.wide_pending.push(byte);
|
|
192
|
+
if self.wide_pending.len() == encoding.unit_bytes() {
|
|
193
|
+
let unit = std::mem::take(&mut self.wide_pending);
|
|
194
|
+
let ascii = encoding.ascii_unit(&unit);
|
|
195
|
+
self.process_unit(&unit, ascii, output);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
self.flush_bytes(output);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
fn process_unit(&mut self, raw: &[u8], ascii: Option<u8>, output: &mut Vec<NormalizedEvent>) {
|
|
204
|
+
match self.escape {
|
|
205
|
+
EscapeState::Text => {
|
|
206
|
+
if ascii == Some(0x1b) {
|
|
207
|
+
self.escape = EscapeState::Escape;
|
|
208
|
+
} else {
|
|
209
|
+
self.push_text_unit(raw, ascii, output);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
EscapeState::Escape => {
|
|
213
|
+
self.escape = match ascii {
|
|
214
|
+
Some(b'[') => EscapeState::Csi,
|
|
215
|
+
Some(b']') => EscapeState::Osc,
|
|
216
|
+
_ => EscapeState::Text,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
EscapeState::Csi => {
|
|
220
|
+
if ascii.is_some_and(|byte| (0x40..=0x7e).contains(&byte)) {
|
|
221
|
+
self.escape = EscapeState::Text;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
EscapeState::Osc => match ascii {
|
|
225
|
+
Some(0x07) => self.escape = EscapeState::Text,
|
|
226
|
+
Some(0x1b) => self.escape = EscapeState::OscEscape,
|
|
227
|
+
_ => {}
|
|
228
|
+
},
|
|
229
|
+
EscapeState::OscEscape => {
|
|
230
|
+
self.escape = match ascii {
|
|
231
|
+
Some(b'\\') => EscapeState::Text,
|
|
232
|
+
Some(0x1b) => EscapeState::OscEscape,
|
|
233
|
+
_ => EscapeState::Osc,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fn push_text_unit(&mut self, raw: &[u8], ascii: Option<u8>, output: &mut Vec<NormalizedEvent>) {
|
|
240
|
+
if self.pending_cr {
|
|
241
|
+
self.emit_line(true, output);
|
|
242
|
+
self.pending_cr = false;
|
|
243
|
+
if ascii == Some(b'\n') {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
match ascii {
|
|
248
|
+
Some(b'\r') => self.pending_cr = true,
|
|
249
|
+
Some(b'\n') => self.emit_line(true, output),
|
|
250
|
+
_ => {
|
|
251
|
+
self.emitted.extend_from_slice(raw);
|
|
252
|
+
self.line_has_content = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn emit_line(&mut self, terminated: bool, output: &mut Vec<NormalizedEvent>) {
|
|
258
|
+
self.flush_bytes(output);
|
|
259
|
+
output.push(NormalizedEvent::LineEnd { terminated });
|
|
260
|
+
self.line_has_content = false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fn flush_bytes(&mut self, output: &mut Vec<NormalizedEvent>) {
|
|
264
|
+
if !self.emitted.is_empty() {
|
|
265
|
+
output.push(NormalizedEvent::Bytes(std::mem::take(&mut self.emitted)));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Incrementally removes ANSI CSI/OSC sequences and returns bounded lines for
|
|
271
|
+
/// foreground presentation.
|
|
272
|
+
#[derive(Debug)]
|
|
273
|
+
pub(crate) struct StreamNormalizer {
|
|
274
|
+
core: NormalizerCore,
|
|
275
|
+
line: LineAccumulator,
|
|
276
|
+
stream_encoding: Option<StreamEncoding>,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
impl StreamNormalizer {
|
|
280
|
+
pub(crate) fn new() -> Self {
|
|
281
|
+
Self {
|
|
282
|
+
core: NormalizerCore::new(),
|
|
283
|
+
line: LineAccumulator::default(),
|
|
284
|
+
stream_encoding: None,
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/// Consumes an arbitrary byte chunk without decoding or corrupting split code units.
|
|
289
|
+
pub(crate) fn push(&mut self, bytes: &[u8], output: &mut Vec<NormalizedLine>) {
|
|
290
|
+
let mut events = Vec::new();
|
|
291
|
+
self.core.push(bytes, &mut events);
|
|
292
|
+
self.consume(events, output);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/// Flushes a final unterminated line and any pending isolated carriage return.
|
|
296
|
+
pub(crate) fn finish(mut self, output: &mut Vec<NormalizedLine>) {
|
|
297
|
+
let mut events = Vec::new();
|
|
298
|
+
self.core.finish(&mut events);
|
|
299
|
+
self.consume(events, output);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fn consume(&mut self, events: Vec<NormalizedEvent>, output: &mut Vec<NormalizedLine>) {
|
|
303
|
+
for event in events {
|
|
304
|
+
match event {
|
|
305
|
+
NormalizedEvent::Start(encoding) => self.stream_encoding = encoding,
|
|
306
|
+
NormalizedEvent::Bytes(bytes) => self.line.push(&bytes),
|
|
307
|
+
NormalizedEvent::LineEnd { terminated } => {
|
|
308
|
+
output.push(
|
|
309
|
+
std::mem::take(&mut self.line).finish(terminated, self.stream_encoding),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Streaming normalizer for the append-only background log. Callers receive
|
|
318
|
+
/// bounded chunks and explicit line boundaries, never a whole-line allocation.
|
|
319
|
+
#[derive(Debug)]
|
|
320
|
+
pub(crate) struct LogNormalizer {
|
|
321
|
+
core: NormalizerCore,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
impl LogNormalizer {
|
|
325
|
+
pub(crate) fn new() -> Self {
|
|
326
|
+
Self {
|
|
327
|
+
core: NormalizerCore::new(),
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
pub(crate) fn push(&mut self, bytes: &[u8], output: &mut Vec<NormalizedEvent>) {
|
|
332
|
+
self.core.push(bytes, output);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
pub(crate) fn finish(mut self, output: &mut Vec<NormalizedEvent>) {
|
|
336
|
+
self.core.finish(output);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
impl Default for StreamNormalizer {
|
|
341
|
+
fn default() -> Self {
|
|
342
|
+
Self::new()
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fn detect_stream_mode(bytes: &[u8], final_chunk: bool) -> Option<(StreamMode, usize)> {
|
|
347
|
+
const UTF32_BE_BOM: &[u8] = &[0x00, 0x00, 0xfe, 0xff];
|
|
348
|
+
const UTF32_LE_BOM: &[u8] = &[0xff, 0xfe, 0x00, 0x00];
|
|
349
|
+
const UTF8_BOM: &[u8] = &[0xef, 0xbb, 0xbf];
|
|
350
|
+
const UTF16_LE_BOM: &[u8] = &[0xff, 0xfe];
|
|
351
|
+
const UTF16_BE_BOM: &[u8] = &[0xfe, 0xff];
|
|
352
|
+
|
|
353
|
+
if bytes.starts_with(UTF32_BE_BOM) {
|
|
354
|
+
return Some((StreamMode::Wide(StreamEncoding::Utf32Be), 4));
|
|
355
|
+
}
|
|
356
|
+
if bytes.starts_with(UTF32_LE_BOM) {
|
|
357
|
+
return Some((StreamMode::Wide(StreamEncoding::Utf32Le), 4));
|
|
358
|
+
}
|
|
359
|
+
if bytes.starts_with(UTF8_BOM) {
|
|
360
|
+
return Some((StreamMode::Bytes, 3));
|
|
361
|
+
}
|
|
362
|
+
if bytes.starts_with(UTF16_BE_BOM) {
|
|
363
|
+
return Some((StreamMode::Wide(StreamEncoding::Utf16Be), 2));
|
|
364
|
+
}
|
|
365
|
+
if bytes.starts_with(UTF16_LE_BOM) {
|
|
366
|
+
if bytes.len() >= 3 && bytes[2] != 0 {
|
|
367
|
+
return Some((StreamMode::Wide(StreamEncoding::Utf16Le), 2));
|
|
368
|
+
}
|
|
369
|
+
if bytes.len() >= 4 || final_chunk {
|
|
370
|
+
return Some((StreamMode::Wide(StreamEncoding::Utf16Le), 2));
|
|
371
|
+
}
|
|
372
|
+
return None;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let possible_bom = [
|
|
376
|
+
UTF32_BE_BOM,
|
|
377
|
+
UTF32_LE_BOM,
|
|
378
|
+
UTF8_BOM,
|
|
379
|
+
UTF16_LE_BOM,
|
|
380
|
+
UTF16_BE_BOM,
|
|
381
|
+
]
|
|
382
|
+
.iter()
|
|
383
|
+
.any(|bom| bom.starts_with(bytes));
|
|
384
|
+
if possible_bom && !final_chunk {
|
|
385
|
+
None
|
|
386
|
+
} else {
|
|
387
|
+
Some((StreamMode::Bytes, 0))
|
|
388
|
+
}
|
|
389
|
+
}
|