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
171
172
173
174
175
//! What:
//! The few text behaviors of the standards-review log that JavaScript defines and Rust does not share:
//! UTF-16 lengths and prefixes, `Number` to text, `toFixed(1)` of a duration, and Git's `%s` subject.
//! Why:
//! The root `mise.toml` task `shadow-review` measures and cuts text in UTF-16 code units and prints numbers the way
//! ECMAScript does, and its log is compared entry by entry with this port's, so the same text must give the same
//! counts and the same bytes.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! text.length; text.slice(0, limit); String(number); (milliseconds / 1000).toFixed(1); // git log --format=%s
//! ```
/// What:
/// The length of `text` in UTF-16 code units, which is what JavaScript's `.length` counts.
/// `usize` is the unsigned size every length uses (siblings `u32`, `u64`).
/// Why:
/// The log prints `luna input N chars` with that count, and a Rust `.len()` would count UTF-8 bytes instead.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// text.length
/// ```
pub fn utf16_length(text: &str) -> usize {
return text.chars().map(char::len_utf16).sum();
}
/// What:
/// The first `limit` UTF-16 code units of `text`, as JavaScript's `text.slice(0, limit)` keeps them.
/// A cut that falls inside a surrogate pair drops the first half, because a Rust `String` cannot hold half a pair.
/// Why:
/// The model input and the failure excerpt are cut by code units; a character that a cut would split is the only
/// place where the result differs from the script's, and the script would send a lone surrogate there.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// text.slice(0, limit)
/// ```
pub fn utf16_prefix(text: &str, limit: usize) -> String {
let mut kept: String = String::new();
let mut used: usize = 0;
for character in text.chars() {
let width: usize = character.len_utf16();
if used + width > limit {
break;
}
kept.push(character);
used += width;
}
return kept;
}
/// What:
/// `value` as ECMAScript's `String(value)` prints a finite number: shortest digits, no exponent between 1e-6 and
/// 1e21, otherwise `1e-7` or `1.5e+21` style.
/// Why:
/// Rust prints `0.0000001` where JavaScript prints `1e-7`, and the log carries the gateway's probabilities as
/// `Number` text.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// String(value)
/// ```
pub fn js_number_text(value: f64) -> String {
if value == 0.0 {
return String::from("0");
}
let sign: &str = if value < 0.0 { "-" } else { "" };
// Rust's `{:e}` prints the shortest digits that round-trip, as `d.ddde-5`.
let scientific: String = format!("{:e}", value.abs());
let (mantissa, exponent_text) = scientific.split_once('e').unwrap_or((scientific.as_str(), "0"));
let digits: String = mantissa.replace('.', "");
let exponent: i32 = exponent_text.parse::<i32>().unwrap_or(0);
let count: i32 = i32::try_from(digits.len()).unwrap_or(i32::MAX);
// ECMAScript's `n`: the decimal point sits after the first `n` digits.
let point: i32 = exponent + 1;
let body: String = if count <= point && point <= 21 {
format!("{digits}{}", "0".repeat(usize::try_from(point - count).unwrap_or(0)))
} else if 0 < point && point <= 21 {
let split: usize = usize::try_from(point).unwrap_or(0);
format!("{}.{}", &digits[..split], &digits[split..])
} else if -6 < point && point <= 0 {
format!("0.{}{digits}", "0".repeat(usize::try_from(-point).unwrap_or(0)))
} else {
let power: i32 = point - 1;
let power_sign: &str = if power < 0 { "-" } else { "+" };
let lead: &str = &digits[..1];
let rest: &str = &digits[1..];
if rest.is_empty() {
format!("{lead}e{power_sign}{}", power.abs())
} else {
format!("{lead}.{rest}e{power_sign}{}", power.abs())
}
};
return format!("{sign}{body}");
}
/// What:
/// `(milliseconds / 1000).toFixed(1)`: seconds with one decimal, a tie rounded up as JavaScript rounds it.
/// Why:
/// The log prints the review's wall-clock cost that way. Only a value that is exactly a quarter second ties
/// (250 ms past a half second), and Rust rounds a tie to even where JavaScript takes the larger neighbor.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// (milliseconds / 1000).toFixed(1)
/// ```
pub fn seconds_with_tenth(milliseconds: u64) -> String {
const TIE_PERIOD_MS: u64 = 500;
const TIE_REMAINDER_MS: u64 = 250;
const TENTH_MS: u64 = 100;
if milliseconds % TIE_PERIOD_MS == TIE_REMAINDER_MS {
let tenths: u64 = milliseconds / TENTH_MS + 1;
return format!("{}.{}", tenths / 10, tenths % 10);
}
let seconds: f64 = milliseconds as f64 / 1000.0;
return format!("{seconds:.1}");
}
/// What:
/// The subject Git's `%s` prints for a commit message, trimmed as the script trims it: blank lines skipped, the
/// first paragraph's lines joined by one space, each line without trailing whitespace.
/// Why:
/// The landing holds the commit message, so the subject needs no `git log` start.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// execFileSync('git', ['log', '-1', '--format=%s', sha]).trim()
/// ```
pub fn commit_subject(message: &str) -> String {
let mut paragraph: Vec<&str> = Vec::new();
for line in message.split('\n') {
let trimmed: &str = line.trim_end();
if trimmed.trim_start().is_empty() {
if paragraph.is_empty() {
continue;
}
break;
}
paragraph.push(trimmed);
}
return paragraph.join(" ").trim().to_string();
}
/// What:
/// `text` on one line: every control character and Unicode line or paragraph separator becomes a visible escape
/// (`\n`, `\r`, `\t`, otherwise `\u{hex}`), and a backslash stays itself.
/// Why:
/// The log is line-structured and a reader parses it by lines. A response body, a transport error or a model answer
/// that carried a line break could otherwise start a fake entry. The script wrote such text unchanged.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// text.replace(/[\u0000-\u001f\u007f-\u009f
]/g, (c) => escapeVisible(c))
/// ```
pub fn on_one_line(text: &str) -> String {
let mut line: String = String::with_capacity(text.len());
for character in text.chars() {
match character {
'\n' => line.push_str("\\n"),
'\r' => line.push_str("\\r"),
'\t' => line.push_str("\\t"),
other if other.is_control() || other == '\u{2028}' || other == '\u{2029}' => {
line.push_str(&format!("\\u{{{:x}}}", u32::from(other)));
}
other => line.push(other),
}
}
return line;
}
/// Controls stay out of the release executable.
#[cfg(test)]
#[path = "standards_review_text_tests.rs"]
mod tests;