CH-J Toolsbainisteoireacht freastalaí SSH
Roghchlár
Foinse foilsithe

CH-J Tools

Brabhsáil comhadlanna agus comhaid chun feidhmchlár áirithe a scaoileadh.

Íosluchtaigh foinse ZIP
CH-J / Server Manager 1704 Comhaid
0.1.2-beta Suimigh: · Tógtha:
js.rs Suimigh: Dáta gan taifeadadh 7.2 KB · 214 líne
Cód Íosluchtaigh comhad
1//! Small helpers that reproduce the JavaScript semantics the 0.1.0 disk model relies on
2//! (`Number()`, truthiness, `String()`), so the Rust port produces identical plans.
4use serde_json::Value;
6/// JavaScript `Number(value)`.
7pub fn number(value: &Value) -> f64 {
8 match value {
9 Value::Null => 0.0,
10 Value::Bool(b) => f64::from(u8::from(*b)),
11 Value::Number(n) => n.as_f64().unwrap_or(f64::NAN),
12 Value::String(s) => number_str(s),
13 Value::Array(a) if a.is_empty() => 0.0,
14 Value::Array(a) if a.len() == 1 => number(&a[0]),
15 _ => f64::NAN,
16 }
19/// JavaScript `Number(text)`.
20pub fn number_str(text: &str) -> f64 {
21 let t = text.trim();
22 if t.is_empty() {
23 return 0.0;
24 }
25 if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
26 return u64::from_str_radix(hex, 16).map(|v| v as f64).unwrap_or(f64::NAN);
27 }
28 if t == "Infinity" || t == "+Infinity" {
29 return f64::INFINITY;
30 }
31 if t == "-Infinity" {
32 return f64::NEG_INFINITY;
33 }
34 if t.chars().any(|c| !(c.is_ascii_digit() || matches!(c, '.' | 'e' | 'E' | '+' | '-'))) {
35 return f64::NAN;
36 }
37 t.parse::<f64>().unwrap_or(f64::NAN)
40/// Undefined-aware `Number(x)` for optional values (`undefined` → NaN).
41pub fn number_opt(value: Option<&Value>) -> f64 {
42 value.map(number).unwrap_or(f64::NAN)
45/// 0.1.0 `int()`: `""`, `null`, `undefined` and non-finite → null.
46pub fn int_opt(value: Option<&Value>) -> Value {
47 match value {
48 None | Some(Value::Null) => Value::Null,
49 Some(Value::String(s)) if s.is_empty() => Value::Null,
50 Some(v) => num(number(v)),
51 }
54pub fn int_str(text: Option<&str>) -> Value {
55 match text {
56 None => Value::Null,
57 Some("") => Value::Null,
58 Some(t) => num(number_str(t)),
59 }
62/// JSON number the way `JSON.stringify` writes it (integers without a fraction, NaN → null).
63pub fn num(value: f64) -> Value {
64 if !value.is_finite() {
65 return Value::Null;
66 }
67 if value.fract() == 0.0 && value.abs() < 9.007_199_254_740_992e15 {
68 return Value::from(value as i64);
69 }
70 serde_json::Number::from_f64(value).map(Value::Number).unwrap_or(Value::Null)
73pub fn truthy(value: &Value) -> bool {
74 match value {
75 Value::Null => false,
76 Value::Bool(b) => *b,
77 Value::Number(n) => n.as_f64().is_some_and(|f| f != 0.0 && !f.is_nan()),
78 Value::String(s) => !s.is_empty(),
79 _ => true,
80 }
83pub fn truthy_opt(value: Option<&Value>) -> bool {
84 value.is_some_and(truthy)
87/// JavaScript `String(value ?? "")`.
88pub fn string(value: Option<&Value>) -> String {
89 match value {
90 None | Some(Value::Null) => String::new(),
91 Some(Value::String(s)) => s.clone(),
92 Some(Value::Number(n)) => match n.as_f64() {
93 Some(f) if f.fract() == 0.0 && f.abs() < 1e21 => format!("{}", f as i64),
94 Some(f) => format!("{f}"),
95 None => n.to_string(),
96 },
97 Some(Value::Bool(b)) => b.to_string(),
98 Some(Value::Array(a)) => a.iter().map(|v| string(Some(v))).collect::<Vec<_>>().join(","),
99 Some(Value::Object(_)) => "[object Object]".into(),
100 }
103/// `String(value)` for a step argument (numbers without trailing `.0`).
104pub fn arg(value: &Value) -> String {
105 string(Some(value))
108pub fn num_string(value: f64) -> String {
109 if value.is_nan() {
110 return "NaN".into();
111 }
112 if value.is_infinite() {
113 return if value > 0.0 { "Infinity".into() } else { "-Infinity".into() };
114 }
115 string(Some(&num(value)))
118/// 0.1.0 `text(value, limit)`: control characters → space, trim, then cut to `limit` UTF-16 units.
119pub fn text(value: &str, limit: usize) -> String {
120 let replaced: String = value.chars().map(|c| if (c as u32) < 0x20 || c as u32 == 0x7f { ' ' } else { c }).collect();
121 cut(replaced.trim(), limit)
124pub fn cut(value: &str, limit: usize) -> String {
125 let mut out = String::new();
126 let mut units = 0;
127 for c in value.chars() {
128 units += c.len_utf16();
129 if units > limit {
130 break;
131 }
132 out.push(c);
133 }
134 out
137/// `text(...) || null`.
138pub fn text_or_null(value: &str, limit: usize) -> Value {
139 let t = text(value, limit);
140 if t.is_empty() { Value::Null } else { Value::String(t) }
143pub fn lines(value: &str) -> Vec<&str> {
144 value.split('\n').map(|l| l.strip_suffix('\r').unwrap_or(l)).filter(|l| !l.is_empty()).collect()
147/// `a.localeCompare(b, "en", { numeric: true })` for device names.
148pub fn natural_cmp(a: &str, b: &str) -> std::cmp::Ordering {
149 fn chunks(s: &str) -> Vec<(bool, String)> {
150 let mut out: Vec<(bool, String)> = Vec::new();
151 for c in s.chars() {
152 let digit = c.is_ascii_digit();
153 match out.last_mut() {
154 Some((d, chunk)) if *d == digit => chunk.push(c),
155 _ => out.push((digit, c.to_string())),
156 }
157 }
158 out
159 }
160 let (ca, cb) = (chunks(a), chunks(b));
161 for (x, y) in ca.iter().zip(cb.iter()) {
162 let ordering = match (x.0, y.0) {
163 (true, true) => {
164 let (nx, ny) = (x.1.trim_start_matches('0'), y.1.trim_start_matches('0'));
165 nx.len().cmp(&ny.len()).then_with(|| nx.cmp(ny))
166 }
167 // ICU sorts digits before letters.
168 (true, false) => std::cmp::Ordering::Less,
169 (false, true) => std::cmp::Ordering::Greater,
170 _ => x.1.to_lowercase().cmp(&y.1.to_lowercase()),
171 };
172 if ordering != std::cmp::Ordering::Equal {
173 return ordering;
174 }
175 }
176 ca.len().cmp(&cb.len()).then_with(|| a.cmp(b))
179/// Deep equality where numbers compare by value (JSON written by JavaScript vs. Rust).
180pub fn json_equal(a: &Value, b: &Value) -> bool {
181 match (a, b) {
182 (Value::Number(x), Value::Number(y)) => x.as_f64() == y.as_f64(),
183 (Value::Array(x), Value::Array(y)) => x.len() == y.len() && x.iter().zip(y).all(|(p, q)| json_equal(p, q)),
184 (Value::Object(x), Value::Object(y)) => x.len() == y.len() && x.iter().all(|(k, v)| y.get(k).is_some_and(|w| json_equal(v, w))),
185 _ => a == b,
186 }
189/// First path where two JSON values differ (test diagnostics).
190pub fn first_difference(a: &Value, b: &Value, path: &str) -> Option<String> {
191 match (a, b) {
192 (Value::Object(x), Value::Object(y)) => {
193 for (k, v) in x {
194 match y.get(k) {
195 None => return Some(format!("{path}.{k}: missing in second ({v})")),
196 Some(w) => {
197 if let Some(d) = first_difference(v, w, &format!("{path}.{k}")) {
198 return Some(d);
199 }
200 }
201 }
202 }
203 y.keys().find(|k| !x.contains_key(*k)).map(|k| format!("{path}.{k}: missing in first ({})", y[k]))
204 }
205 (Value::Array(x), Value::Array(y)) => {
206 if x.len() != y.len() {
207 return Some(format!("{path}: length {} vs {}", x.len(), y.len()));
208 }
209 x.iter().zip(y).enumerate().find_map(|(i, (p, q))| first_difference(p, q, &format!("{path}[{i}]")))
210 }
211 _ if json_equal(a, b) => None,
212 _ => Some(format!("{path}: {a} vs {b}")),
213 }
SHA-2569953e01ccb0e5a167c00321f9d3e34689de495b6cd86d8118d7deea9d27b6507

CH-J Proprietary Software License 1.14 · Tá Foinse ar fáil faoin CH-J Proprietary Software License 1.14. Ní chuireann a infhaighteacht a athrú ar na téarmaí ceadúnais nó cearta breise a dheonú.

Cartlann SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Comhaid