• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mattwparas / steel / 17772690385

16 Sep 2025 04:36PM UTC coverage: 43.331% (+0.04%) from 43.289%
17772690385

Pull #519

github

web-flow
Merge 98d4fd22c into 3c10433b9
Pull Request #519: fix a bunch more clippy lints

56 of 123 new or added lines in 30 files covered. (45.53%)

8 existing lines in 3 files now uncovered.

12416 of 28654 relevant lines covered (43.33%)

2985398.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

36.25
/crates/steel-core/src/values/port.rs
1
use std::fs::File;
2
use std::fs::OpenOptions;
3
use std::io;
4
use std::io::prelude::*;
5
use std::io::Cursor;
6
use std::io::Stderr;
7
use std::io::{BufReader, BufWriter, Stdin, Stdout};
8
use std::net::TcpStream;
9
use std::process::ChildStderr;
10
use std::process::ChildStdin;
11
use std::process::ChildStdout;
12
use std::sync::Arc;
13
use std::sync::Mutex;
14

15
use crate::gc::shared::ShareableMut;
16
use crate::gc::Gc;
17
use crate::gc::GcMut;
18
use crate::rvals::Result;
19
use crate::SteelVal;
20

21
// use crate::rvals::{new_rc_ref_cell, RcRefSteelVal};
22

23
thread_local! {
24
    // TODO: This needs to be per engine, not global, and functions should accept the port they use
25
    // Probably by boxing up the port that gets used
26
    pub static DEFAULT_OUTPUT_PORT: GcMut<SteelPort> = Gc::new_mut(SteelPort { port: Gc::new_mut(SteelPortRepr::StdOutput(io::stdout())) } );
27
    pub static CAPTURED_OUTPUT_PORT: GcMut<BufWriter<Vec<u8>>> = Gc::new_mut(BufWriter::new(Vec::new()));
28
}
29

30
#[derive(Debug, Clone)]
31
pub struct SteelPort {
32
    pub(crate) port: GcMut<SteelPortRepr>,
33
}
34

35
impl std::fmt::Display for SteelPort {
36
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
37
        write!(f, "{}", self.port.read())
×
38
    }
39
}
40

41
// pub trait PortLike {
42
//     fn as_any_ref(&self) -> &dyn Any;
43
//     fn into_port(self) -> SteelVal;
44
// }
45

46
// impl<T: Write + Send + Sync + 'static> PortLike for T {
47
//     fn as_any_ref(&self) -> &dyn Any {
48
//         self as &dyn Any
49
//     }
50

51
//     //
52
//     fn into_port(self) -> SteelVal {}
53
// }
54

55
#[derive(Debug)]
56
pub struct Peekable<R> {
57
    inner: R,
58
    peek: [u8; 4],
59
    idx: usize,
60
}
61

62
impl<T> Peekable<T> {
63
    pub fn new(inner: T) -> Self {
2,734✔
64
        Peekable {
65
            inner,
66
            peek: [0; 4],
2,734✔
67
            idx: 0,
68
        }
69
    }
70
}
71

72
impl<R: Read> Peekable<R> {
73
    fn read_byte(&mut self) -> Result<MaybeBlocking<Option<u8>>> {
26,760,350✔
74
        if self.idx > 0 {
26,760,350✔
75
            let peek = self.peek[0];
12✔
76

77
            self.peek[..].rotate_left(1);
12✔
78
            self.idx -= 1;
6✔
79

80
            return Ok(MaybeBlocking::Nonblocking(Some(peek)));
6✔
81
        }
82

83
        let mut byte = [0];
×
84
        if let Err(err) = self.inner.read_exact(&mut byte) {
8✔
85
            if err.kind() == io::ErrorKind::UnexpectedEof {
×
86
                return Ok(MaybeBlocking::Nonblocking(None));
8✔
87
            }
88

89
            if err.kind() == io::ErrorKind::WouldBlock {
×
90
                return Ok(MaybeBlocking::WouldBlock);
×
91
            }
92

93
            return Err(err.into());
×
94
        }
95

96
        Ok(MaybeBlocking::Nonblocking(Some(byte[0])))
26,760,336✔
97
    }
98

99
    fn peek_char(&mut self) -> Result<MaybeBlocking<Option<char>>> {
4✔
100
        match read_full(&mut self.inner, &mut self.peek[self.idx..])? {
12✔
101
            MaybeBlocking::Nonblocking(n) => self.idx += n,
4✔
102
            MaybeBlocking::WouldBlock => return Ok(MaybeBlocking::WouldBlock),
×
103
        };
104

105
        if self.idx == 0 {
×
106
            return Ok(MaybeBlocking::Nonblocking(None));
×
107
        }
108

109
        match std::str::from_utf8(&self.peek[0..self.idx]) {
×
110
            Ok(str) => Ok(MaybeBlocking::Nonblocking(str.chars().next())),
4✔
111
            Err(err) => {
×
112
                if err.valid_up_to() > 0 {
×
113
                    let s = std::str::from_utf8(&self.peek[0..err.valid_up_to()]).unwrap();
×
114
                    Ok(MaybeBlocking::Nonblocking(s.chars().next()))
×
115
                } else if let Some(len) = err.error_len() {
×
116
                    self.idx -= len;
×
117
                    self.peek[..].rotate_left(len);
×
118
                    Ok(MaybeBlocking::Nonblocking(Some(
×
119
                        char::REPLACEMENT_CHARACTER,
×
120
                    )))
121
                } else {
122
                    // if error_len is None, it means that there should be more
123
                    // bytes coming after, but we tried to fill the buf to a len
124
                    // of 4, the maximum, so that just means we got incomplete utf8
125
                    self.idx = 0;
×
126
                    Ok(MaybeBlocking::Nonblocking(Some(
×
127
                        char::REPLACEMENT_CHARACTER,
×
128
                    )))
129
                }
130
            }
131
        }
132
    }
133

134
    fn peek_byte(&mut self) -> Result<MaybeBlocking<Option<u8>>> {
3✔
135
        if self.idx > 0 {
3✔
136
            Ok(MaybeBlocking::Nonblocking(Some(self.peek[0])))
3✔
137
        } else {
138
            match self.read_byte()? {
×
139
                MaybeBlocking::Nonblocking(None) => Ok(MaybeBlocking::Nonblocking(None)),
×
140
                MaybeBlocking::Nonblocking(Some(peek)) => {
×
141
                    self.peek[0] = peek;
×
142
                    self.idx += 1;
×
143
                    Ok(MaybeBlocking::Nonblocking(Some(peek)))
×
144
                }
145
                MaybeBlocking::WouldBlock => Ok(MaybeBlocking::WouldBlock),
×
146
            }
147
        }
148
    }
149

150
    pub fn read_bytes_amt(&mut self, mut buf: &mut [u8]) -> Result<MaybeBlocking<usize>> {
×
151
        let mut amt = 0;
×
152

153
        if !buf.is_empty() && self.idx > 0 {
×
154
            let len = usize::min(buf.len(), self.idx);
×
155
            for i in 0..len {
×
156
                buf[i] = self.peek[i];
×
157
            }
158

159
            amt += len;
×
160
            buf = &mut buf[len..];
×
161

162
            self.idx -= len;
×
163
            self.peek[..].rotate_left(len);
×
164
        }
165

166
        match read_full(&mut self.inner, buf)? {
×
167
            MaybeBlocking::Nonblocking(n) => amt += n,
×
168
            MaybeBlocking::WouldBlock => return Ok(MaybeBlocking::WouldBlock),
×
169
        };
170

171
        Ok(MaybeBlocking::Nonblocking(amt))
×
172
    }
173
}
174

175
fn read_full<R: Read>(mut reader: R, mut buf: &mut [u8]) -> Result<MaybeBlocking<usize>> {
4✔
176
    let mut amt = 0;
8✔
177
    while !buf.is_empty() {
6✔
178
        match reader.read(buf) {
3✔
179
            Ok(0) => break,
1✔
180
            Ok(n) => {
2✔
181
                amt += n;
×
182
                buf = &mut buf[n..];
×
183
            }
184
            Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
×
185
            Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
×
186
                if amt == 0 {
×
187
                    return Ok(MaybeBlocking::WouldBlock);
×
188
                } else {
189
                    break;
×
190
                }
191
            }
192
            Err(err) => return Err(err.into()),
×
193
        }
194
    }
195

196
    Ok(MaybeBlocking::Nonblocking(amt))
4✔
197
}
198

199
// #[derive(Debug)]
200
pub enum SteelPortRepr {
201
    FileInput(String, Peekable<BufReader<File>>),
202
    FileOutput(String, BufWriter<File>),
203
    StdInput(Peekable<Stdin>),
204
    StdOutput(Stdout),
205
    StdError(Stderr),
206
    ChildStdOutput(Peekable<BufReader<ChildStdout>>),
207
    ChildStdError(Peekable<BufReader<ChildStderr>>),
208
    ChildStdInput(BufWriter<ChildStdin>),
209
    StringInput(Peekable<Cursor<Vec<u8>>>),
210
    StringOutput(Vec<u8>),
211

212
    // TODO: This does not need to be Arc<Mutex<dyn ...>> - it can
213
    // get away with just Box<dyn ...> - and also it should be dyn Portlike
214
    // with blanket trait impls to do the thing otherwise.
215
    DynWriter(Arc<Mutex<dyn Write + Send + Sync>>),
216
    DynReader(Peekable<BufReader<Box<dyn Read + Send + Sync>>>),
217
    TcpStream(Peekable<TcpStream>),
218
    // DynReader(Box<dyn Read>),
219
    Closed,
220
}
221

222
impl std::fmt::Debug for SteelPortRepr {
223
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
224
        match self {
×
225
            SteelPortRepr::FileInput(name, w) => {
×
226
                f.debug_tuple("FileInput").field(name).field(w).finish()
×
227
            }
228
            SteelPortRepr::FileOutput(name, w) => {
×
229
                f.debug_tuple("FileOutput").field(name).field(w).finish()
×
230
            }
231
            SteelPortRepr::StdInput(s) => f.debug_tuple("StdInput").field(s).finish(),
×
232
            SteelPortRepr::StdOutput(s) => f.debug_tuple("StdOutput").field(s).finish(),
×
233
            SteelPortRepr::StdError(s) => f.debug_tuple("StdError").field(s).finish(),
×
234
            SteelPortRepr::ChildStdOutput(s) => f.debug_tuple("ChildStdOutput").field(s).finish(),
×
235
            SteelPortRepr::ChildStdError(s) => f.debug_tuple("ChildStdError").field(s).finish(),
×
236
            SteelPortRepr::ChildStdInput(s) => f.debug_tuple("ChildStdInput").field(s).finish(),
×
237
            SteelPortRepr::StringInput(s) => f.debug_tuple("StringInput").field(s).finish(),
×
238
            SteelPortRepr::StringOutput(s) => f.debug_tuple("StringOutput").field(s).finish(),
×
239
            SteelPortRepr::DynWriter(_) => f.debug_tuple("DynWriter").field(&"#<opaque>").finish(),
×
240
            SteelPortRepr::DynReader(_) => f
×
241
                .debug_tuple("DynReader")
242
                .field(&"#<opaque-reader>")
×
243
                .finish(),
244
            SteelPortRepr::TcpStream(_) => f.debug_tuple("TcpStream").finish(),
×
245
            SteelPortRepr::Closed => f.debug_tuple("Closed").finish(),
×
246
        }
247
    }
248
}
249

250
impl std::fmt::Display for SteelPortRepr {
251
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
252
        match self {
×
253
            SteelPortRepr::FileInput(file, _) => write!(f, "#<input-port:{file}>"),
×
254
            SteelPortRepr::FileOutput(file, _) => write!(f, "#<output-port:{file}>"),
×
255
            SteelPortRepr::StdInput(_) => write!(f, "#<input-port:stdin>"),
×
256
            SteelPortRepr::StdOutput(_) => write!(f, "#<output-port:stdout>"),
×
257
            SteelPortRepr::StdError(_) => write!(f, "#<output-port:stderr>"),
×
258
            SteelPortRepr::ChildStdOutput(_) => write!(f, "#<input-port:child-stdout>"),
×
259
            SteelPortRepr::ChildStdError(_) => write!(f, "#<input-port:child-stderr>"),
×
260
            SteelPortRepr::ChildStdInput(_) => write!(f, "#<output-port:child-stdin>"),
×
261
            SteelPortRepr::StringInput(_) => write!(f, "#<input-port:string>"),
×
262
            SteelPortRepr::StringOutput(_) => write!(f, "#<output-port:string>"),
×
263
            SteelPortRepr::DynWriter(_) => write!(f, "#<output-port:opaque>"),
×
264
            SteelPortRepr::DynReader(_) => write!(f, "#<input-port:opaque>"),
×
265
            SteelPortRepr::TcpStream(_) => write!(f, "#<port:tcp>"),
×
266
            SteelPortRepr::Closed => write!(f, "#<port:closed>"),
×
267
        }
268
    }
269
}
270

271
pub enum SendablePort {
272
    StdInput(Stdin),
273
    StdOutput(Stdout),
274
    StdError(Stderr),
275
    BoxDynWriter(Arc<Mutex<dyn Write + Send + Sync>>),
276
    Closed,
277
}
278

279
impl SendablePort {
280
    fn from_port_repr(value: &SteelPortRepr) -> Result<SendablePort> {
×
281
        match value {
×
282
            SteelPortRepr::StdInput(_) => Ok(SendablePort::StdInput(io::stdin())),
×
283
            SteelPortRepr::StdOutput(_) => Ok(SendablePort::StdOutput(io::stdout())),
×
284
            SteelPortRepr::StdError(_) => Ok(SendablePort::StdError(io::stderr())),
×
285
            SteelPortRepr::Closed => Ok(SendablePort::Closed),
×
286
            _ => stop!(Generic => "Unable to send port across threads: {}", value),
×
287
        }
288
    }
289

290
    pub fn from_port(value: SteelPort) -> Result<SendablePort> {
×
291
        Self::from_port_repr(&value.port.read())
×
292
    }
293
}
294

295
impl SteelPort {
296
    pub fn from_sendable_port(value: SendablePort) -> Self {
×
297
        match value {
×
298
            SendablePort::StdInput(s) => SteelPort {
299
                port: Gc::new_mut(SteelPortRepr::StdInput(Peekable::new(s))),
300
            },
301
            SendablePort::StdOutput(s) => SteelPort {
302
                port: Gc::new_mut(SteelPortRepr::StdOutput(s)),
×
303
            },
304
            SendablePort::StdError(s) => SteelPort {
305
                port: Gc::new_mut(SteelPortRepr::StdError(s)),
×
306
            },
307
            SendablePort::Closed => SteelPort {
308
                port: Gc::new_mut(SteelPortRepr::Closed),
×
309
            },
310
            SendablePort::BoxDynWriter(w) => SteelPort {
311
                port: Gc::new_mut(SteelPortRepr::DynWriter(w)),
×
312
            },
313
        }
314
    }
315
}
316

317
macro_rules! port_read_str_fn {
318
    ($br: expr, $fn: ident) => {{
319
        let mut result = String::new();
320
        let size = $br.$fn(&mut result)?;
321
        Ok((size, result))
322
    }};
323
}
324

325
impl SteelPortRepr {
326
    pub fn read_line(&mut self) -> Result<(usize, String)> {
158,019✔
327
        match self {
158,019✔
328
            SteelPortRepr::FileInput(_, br) => port_read_str_fn!(br.inner, read_line),
158,015✔
329
            SteelPortRepr::StdInput(br) => port_read_str_fn!(br.inner, read_line),
×
330
            SteelPortRepr::StringInput(s) => port_read_str_fn!(s.inner, read_line),
12✔
331
            SteelPortRepr::ChildStdOutput(br) => port_read_str_fn!(br.inner, read_line),
×
332
            SteelPortRepr::ChildStdError(br) => port_read_str_fn!(br.inner, read_line),
×
333
            SteelPortRepr::DynReader(br) => port_read_str_fn!(br.inner, read_line),
×
334
            // FIXME: fix this and the functions below
335
            _ => stop!(ContractViolation => "expected input-port?, found {}", self),
×
336
        }
337
    }
338

339
    pub fn flush(&mut self) -> Result<()> {
36✔
340
        match self {
36✔
341
            SteelPortRepr::FileOutput(_, s) => Ok(s.flush()?),
×
342
            SteelPortRepr::StdOutput(s) => Ok(s.flush()?),
108✔
343
            SteelPortRepr::ChildStdInput(s) => Ok(s.flush()?),
×
344
            SteelPortRepr::StringOutput(s) => Ok(s.flush()?),
×
345
            SteelPortRepr::DynWriter(s) => Ok(s.lock().unwrap().flush()?),
×
346
            SteelPortRepr::Closed => Ok(()),
×
347
            _ => stop!(ContractViolation => "expected output-port?, found {}", self),
×
348
        }
349
    }
350

351
    pub fn read_all_str(&mut self) -> Result<(usize, String)> {
5,073✔
352
        match self {
5,073✔
353
            SteelPortRepr::FileInput(_, br) => port_read_str_fn!(br.inner, read_to_string),
5,064✔
354
            SteelPortRepr::StdInput(br) => port_read_str_fn!(br.inner, read_to_string),
×
355
            SteelPortRepr::StringInput(s) => port_read_str_fn!(s.inner, read_to_string),
27✔
356
            SteelPortRepr::ChildStdOutput(br) => port_read_str_fn!(br.inner, read_to_string),
×
357
            SteelPortRepr::ChildStdError(br) => port_read_str_fn!(br.inner, read_to_string),
×
358
            SteelPortRepr::DynReader(br) => port_read_str_fn!(br.inner, read_to_string),
×
359
            _ => stop!(ContractViolation => "expected input-port?, found {}", self),
×
360
        }
361
    }
362

363
    pub fn read_char(&mut self) -> Result<MaybeBlocking<Option<char>>> {
26,760,346✔
364
        let mut buf = [0; 4];
53,520,692✔
365

366
        for i in 0..4 {
53,520,696✔
367
            let result = self.read_byte()?;
80,281,044✔
368

369
            let b = match result {
53,520,689✔
370
                MaybeBlocking::Nonblocking(Some(b)) => b,
53,520,682✔
371
                MaybeBlocking::Nonblocking(None) => {
372
                    if i == 0 {
7✔
373
                        return Ok(MaybeBlocking::Nonblocking(None));
7✔
374
                    } else {
375
                        return Ok(MaybeBlocking::Nonblocking(Some(
×
376
                            char::REPLACEMENT_CHARACTER,
×
377
                        )));
378
                    }
379
                }
380
                MaybeBlocking::WouldBlock => return Ok(MaybeBlocking::WouldBlock),
×
381
            };
382

383
            buf[i] = b;
26,760,341✔
384

385
            match std::str::from_utf8(&buf[0..=i]) {
53,520,682✔
386
                Ok(s) => return Ok(MaybeBlocking::Nonblocking(s.chars().next())),
26,760,339✔
387
                Err(err) if err.error_len().is_some() => {
4✔
388
                    return Ok(MaybeBlocking::Nonblocking(Some(
×
389
                        char::REPLACEMENT_CHARACTER,
×
390
                    )));
391
                }
392
                _ => {}
2✔
393
            }
394
        }
395

396
        Ok(MaybeBlocking::Nonblocking(Some(
×
397
            char::REPLACEMENT_CHARACTER,
×
398
        )))
399
    }
400

401
    pub fn peek_char(&mut self) -> Result<MaybeBlocking<Option<char>>> {
4✔
402
        match self {
4✔
403
            SteelPortRepr::FileInput(_, br) => br.peek_char(),
×
404
            SteelPortRepr::StdInput(br) => br.peek_char(),
×
405
            SteelPortRepr::StringInput(s) => s.peek_char(),
12✔
406
            SteelPortRepr::ChildStdOutput(br) => br.peek_char(),
×
407
            SteelPortRepr::ChildStdError(br) => br.peek_char(),
×
408
            SteelPortRepr::DynReader(br) => br.peek_char(),
×
409
            _ => stop!(TypeMismatch => "expected an input port"),
×
410
        }
411
    }
412

413
    pub fn read_bytes_amt(&mut self, buf: &mut [u8]) -> Result<MaybeBlocking<usize>> {
×
414
        match self {
×
415
            SteelPortRepr::FileInput(_, reader) => reader.read_bytes_amt(buf),
×
416
            SteelPortRepr::StdInput(stdin) => stdin.read_bytes_amt(buf),
×
417
            SteelPortRepr::ChildStdOutput(output) => output.read_bytes_amt(buf),
×
418
            SteelPortRepr::ChildStdError(output) => output.read_bytes_amt(buf),
×
419
            SteelPortRepr::StringInput(reader) => reader.read_bytes_amt(buf),
×
420
            SteelPortRepr::DynReader(reader) => reader.read_bytes_amt(buf),
×
421
            SteelPortRepr::TcpStream(t) => t.read_bytes_amt(buf),
×
422
            SteelPortRepr::FileOutput(_, _)
423
            | SteelPortRepr::StdOutput(_)
424
            | SteelPortRepr::StdError(_)
425
            | SteelPortRepr::ChildStdInput(_)
426
            | SteelPortRepr::StringOutput(_)
427
            | SteelPortRepr::DynWriter(_) => {
428
                stop!(ContractViolation => "expected input-port?, found {}", self)
×
429
            }
430
            SteelPortRepr::Closed => stop!(ContractViolation => "input port is closed"),
×
431
        }
432
    }
433

434
    pub fn read_byte(&mut self) -> Result<MaybeBlocking<Option<u8>>> {
26,760,350✔
435
        match self {
26,760,350✔
436
            SteelPortRepr::FileInput(_, reader) => reader.read_byte(),
26,760,342✔
437
            SteelPortRepr::StdInput(stdin) => stdin.read_byte(),
×
438
            SteelPortRepr::ChildStdOutput(output) => output.read_byte(),
×
439
            SteelPortRepr::ChildStdError(output) => output.read_byte(),
×
440
            SteelPortRepr::StringInput(reader) => reader.read_byte(),
24✔
441
            SteelPortRepr::DynReader(reader) => reader.read_byte(),
×
442
            SteelPortRepr::TcpStream(t) => t.read_byte(),
×
443
            SteelPortRepr::FileOutput(_, _)
444
            | SteelPortRepr::StdOutput(_)
445
            | SteelPortRepr::StdError(_)
446
            | SteelPortRepr::ChildStdInput(_)
447
            | SteelPortRepr::StringOutput(_)
448
            | SteelPortRepr::DynWriter(_) => {
449
                stop!(ContractViolation => "expected input-port?, found {}", self)
×
450
            }
NEW
451
            SteelPortRepr::Closed => Ok(MaybeBlocking::Nonblocking(None)),
×
452
        }
453
    }
454

455
    pub fn peek_byte(&mut self) -> Result<MaybeBlocking<Option<u8>>> {
3✔
456
        match self {
3✔
457
            SteelPortRepr::FileInput(_, reader) => reader.peek_byte(),
×
458
            SteelPortRepr::StdInput(stdin) => stdin.peek_byte(),
×
459
            SteelPortRepr::ChildStdOutput(output) => output.peek_byte(),
×
460
            SteelPortRepr::ChildStdError(output) => output.peek_byte(),
×
461
            SteelPortRepr::StringInput(reader) => reader.peek_byte(),
9✔
462
            SteelPortRepr::DynReader(reader) => reader.peek_byte(),
×
463
            SteelPortRepr::TcpStream(t) => t.peek_byte(),
×
464
            SteelPortRepr::FileOutput(_, _)
465
            | SteelPortRepr::StdOutput(_)
466
            | SteelPortRepr::StdError(_)
467
            | SteelPortRepr::ChildStdInput(_)
468
            | SteelPortRepr::StringOutput(_)
469
            | SteelPortRepr::DynWriter(_) => {
470
                stop!(ContractViolation => "expected input-port?, found {}", self)
×
471
            }
NEW
472
            SteelPortRepr::Closed => Ok(MaybeBlocking::Nonblocking(None)),
×
473
        }
474
    }
475

476
    pub fn write_char(&mut self, c: char) -> Result<()> {
4,618,694✔
477
        let mut buf = [0; 4];
9,237,388✔
478
        let s = c.encode_utf8(&mut buf);
18,474,776✔
479
        self.write(s.as_bytes())
13,856,082✔
480
    }
481

482
    pub fn write_string_line(&mut self, string: &str) -> Result<()> {
×
483
        self.write(string.as_bytes())?;
×
484
        self.write(b"\n")
×
485
    }
486

487
    pub fn is_input(&self) -> bool {
×
488
        matches!(
×
489
            self,
×
490
            SteelPortRepr::FileInput(_, _)
491
                | SteelPortRepr::StdInput(_)
492
                | SteelPortRepr::ChildStdOutput(_)
493
                | SteelPortRepr::ChildStdError(_)
494
                | SteelPortRepr::StringInput(_)
495
        )
496
    }
497

498
    pub fn is_string_input(&self) -> bool {
7,520✔
499
        matches!(self, SteelPortRepr::StringInput(_))
15,031✔
500
    }
501

502
    pub fn is_file_input(&self) -> bool {
7,511✔
503
        matches!(self, SteelPortRepr::FileInput(_, _))
7,511✔
504
    }
505

506
    pub fn is_output(&self) -> bool {
×
507
        matches!(
×
508
            self,
×
509
            SteelPortRepr::FileOutput(_, _)
510
                | SteelPortRepr::StdOutput(_)
511
                | SteelPortRepr::StdError(_)
512
                | SteelPortRepr::DynWriter(_)
513
                | SteelPortRepr::ChildStdInput(_)
514
                | SteelPortRepr::StringOutput(_)
515
        )
516
    }
517

518
    pub fn get_output(&self) -> Result<Option<Vec<u8>>> {
2,145✔
519
        let buf: &Vec<u8> = if let SteelPortRepr::StringOutput(s) = self {
4,290✔
520
            s
521
        } else {
522
            return Ok(None);
×
523
        };
524

525
        Ok(Some(buf.clone()))
526
    }
527

528
    pub fn close_port(&mut self) {
2,528✔
529
        *self = SteelPortRepr::Closed;
5,056✔
530
    }
531

532
    pub fn close_output_port(&mut self) -> Result<()> {
×
533
        if self.is_output() {
×
534
            *self = SteelPortRepr::Closed;
×
535
            Ok(())
×
536
        } else {
537
            stop!(TypeMismatch => "expected output-port?, found {}", self)
×
538
        }
539
    }
540

541
    pub fn close_input_port(&mut self) -> Result<()> {
×
542
        if self.is_input() {
×
543
            *self = SteelPortRepr::Closed;
×
544
            Ok(())
×
545
        } else {
546
            stop!(TypeMismatch => "expected input-port?, found {}", self)
×
547
        }
548
    }
549

550
    pub fn write(&mut self, buf: &[u8]) -> Result<()> {
4,786,051✔
551
        match self {
4,786,051✔
552
            SteelPortRepr::FileOutput(_, writer) => writer.write_all(buf)?,
19,084,304✔
553
            SteelPortRepr::StdOutput(writer) => writer.write_all(buf)?,
25,144✔
554
            SteelPortRepr::StdError(writer) => writer.write_all(buf)?,
×
555
            SteelPortRepr::ChildStdInput(writer) => writer.write_all(buf)?,
×
556
            SteelPortRepr::StringOutput(writer) => writer.write_all(buf)?,
31,432✔
557
            SteelPortRepr::DynWriter(writer) => writer.lock().unwrap().write_all(buf)?,
2,493✔
558
            // TODO: Should tcp streams be both input and output ports?
559
            SteelPortRepr::TcpStream(tcp) => tcp.inner.write_all(buf)?,
×
560
            SteelPortRepr::FileInput(_, _)
561
            | SteelPortRepr::StdInput(_)
562
            | SteelPortRepr::DynReader(_)
563
            | SteelPortRepr::ChildStdOutput(_)
564
            | SteelPortRepr::ChildStdError(_)
565
            | SteelPortRepr::StringInput(_) => {
566
                stop!(ContractViolation => "expected output-port?, found {}", self)
×
567
            }
568
            SteelPortRepr::Closed => stop!(Io => "port is closed"),
×
569
        }
570

571
        Ok(())
4,786,051✔
572
    }
573
}
574

575
pub enum MaybeBlocking<T> {
576
    Nonblocking(T),
577
    WouldBlock,
578
}
579

580
impl SteelPort {
581
    pub fn new_textual_file_input(path: &str) -> Result<SteelPort> {
2,575✔
582
        let file = OpenOptions::new().read(true).open(path)?;
10,300✔
583

584
        Ok(SteelPort {
585
            port: Gc::new_mut(SteelPortRepr::FileInput(
586
                path.to_string(),
587
                Peekable::new(BufReader::new(file)),
588
            )),
589
        })
590
    }
591

592
    pub fn new_textual_file_output(path: &str) -> Result<SteelPort> {
×
593
        let file = OpenOptions::new()
×
594
            .truncate(true)
595
            .write(true)
596
            .create(true)
597
            .open(path)?;
×
598

599
        Ok(SteelPort {
×
600
            port: Gc::new_mut(SteelPortRepr::FileOutput(
×
601
                path.to_string(),
×
602
                BufWriter::new(file),
×
603
            )),
604
        })
605
    }
606

607
    pub fn new_textual_file_output_with_options(
6✔
608
        path: &str,
609
        open_options: OpenOptions,
610
    ) -> Result<SteelPort> {
611
        let file = open_options.open(path)?;
24✔
612

613
        Ok(SteelPort {
614
            port: Gc::new_mut(SteelPortRepr::FileOutput(
615
                path.to_string(),
616
                BufWriter::new(file),
617
            )),
618
        })
619
    }
620

621
    pub fn new_input_port_string(string: String) -> SteelPort {
11✔
622
        SteelPort {
623
            port: Gc::new_mut(SteelPortRepr::StringInput(Peekable::new(Cursor::new(
44✔
624
                string.into_bytes(),
625
            )))),
626
        }
627
    }
628

629
    pub fn new_input_port_bytevector(vec: Vec<u8>) -> SteelPort {
×
630
        SteelPort {
631
            port: Gc::new_mut(SteelPortRepr::StringInput(Peekable::new(Cursor::new(vec)))),
×
632
        }
633
    }
634

635
    pub fn new_output_port_string() -> SteelPort {
2,145✔
636
        SteelPort {
637
            port: Gc::new_mut(SteelPortRepr::StringOutput(Vec::new())),
2,145✔
638
        }
639
    }
640

641
    //
642
    // Read functions
643
    //
644
    pub fn read_line(&self) -> Result<(usize, String)> {
158,019✔
645
        self.port.write().read_line()
158,019✔
646
    }
647

648
    // TODO: Implement the rest of the flush methods
649
    pub fn flush(&self) -> Result<()> {
36✔
650
        self.port.write().flush()
36✔
651
    }
652

653
    pub fn read_all_str(&self) -> Result<(usize, String)> {
5,073✔
654
        self.port.write().read_all_str()
5,073✔
655
    }
656

657
    pub fn read_char(&self) -> Result<MaybeBlocking<Option<char>>> {
26,760,346✔
658
        self.port.write().read_char()
26,760,346✔
659
    }
660

661
    pub fn peek_char(&self) -> Result<MaybeBlocking<Option<char>>> {
4✔
662
        self.port.write().peek_char()
4✔
663
    }
664

665
    pub fn read_byte(&self) -> Result<MaybeBlocking<Option<u8>>> {
2✔
666
        self.port.write().read_byte()
2✔
667
    }
668

669
    pub fn read_bytes(&self, amount: usize) -> Result<MaybeBlocking<Vec<u8>>> {
×
670
        // TODO: This is going to allocate unnecessarily
671
        let mut buf = vec![0; amount];
×
672
        match self.port.write().read_bytes_amt(&mut buf)? {
×
673
            MaybeBlocking::Nonblocking(amount_read) => {
×
674
                buf.truncate(amount_read);
675
                Ok(MaybeBlocking::Nonblocking(buf))
676
            }
677
            MaybeBlocking::WouldBlock => Ok(MaybeBlocking::WouldBlock),
×
678
        }
679
    }
680

681
    pub fn read_bytes_into_buf(&self, buf: &mut [u8]) -> Result<MaybeBlocking<usize>> {
×
682
        self.port.write().read_bytes_amt(buf)
×
683
    }
684

685
    pub fn peek_byte(&self) -> Result<MaybeBlocking<Option<u8>>> {
3✔
686
        self.port.write().peek_byte()
3✔
687
    }
688

689
    //
690
    // Write functions
691
    //
692
    pub fn write_char(&self, c: char) -> Result<()> {
4,618,694✔
693
        self.port.write().write_char(c)
9,237,388✔
694
    }
695

696
    pub fn write(&self, buf: &[u8]) -> Result<()> {
167,357✔
697
        self.port.write().write(buf)
334,714✔
698
    }
699

700
    pub fn write_string_line(&self, string: &str) -> Result<()> {
×
701
        self.port.write().write_string_line(string)
×
702
    }
703

704
    //
705
    // Checks
706
    //
707
    pub fn is_input(&self) -> bool {
×
708
        self.port.read().is_input()
×
709
    }
710

711
    pub fn is_string_input(&self) -> bool {
7,520✔
712
        self.port.read().is_string_input()
7,520✔
713
    }
714

715
    pub fn is_file_input(&self) -> bool {
7,511✔
716
        self.port.read().is_file_input()
7,511✔
717
    }
718

719
    pub fn is_output(&self) -> bool {
×
720
        self.port.read().is_output()
×
721
    }
722

723
    pub fn default_current_input_port() -> Self {
378✔
724
        SteelPort {
725
            port: Gc::new_mut(SteelPortRepr::StdInput(Peekable::new(io::stdin()))),
756✔
726
        }
727
    }
728

729
    pub fn default_current_output_port() -> Self {
378✔
730
        if cfg!(test) {
731
            // Write out to thread safe port
732
            SteelPort {
733
                port: Gc::new_mut(SteelPortRepr::DynWriter(Arc::new(Mutex::new(
920✔
734
                    BufWriter::new(Vec::new()),
735
                )))),
736
            }
737
        } else {
738
            SteelPort {
739
                port: Gc::new_mut(SteelPortRepr::StdOutput(io::stdout())),
148✔
740
            }
741
        }
742
    }
743

744
    pub fn default_current_error_port() -> Self {
378✔
745
        if cfg!(test) {
746
            // Write out to thread safe port
747
            SteelPort {
748
                port: Gc::new_mut(SteelPortRepr::DynWriter(Arc::new(Mutex::new(
920✔
749
                    BufWriter::new(Vec::new()),
750
                )))),
751
            }
752
        } else {
753
            SteelPort {
754
                port: Gc::new_mut(SteelPortRepr::StdError(io::stderr())),
148✔
755
            }
756
        }
757
    }
758

759
    pub fn get_output(&self) -> Result<Option<Vec<u8>>> {
2,145✔
760
        self.port.write().get_output()
2,145✔
761
    }
762

763
    pub fn close_port(&self) {
2,528✔
764
        self.port.write().close_port()
2,528✔
765
    }
766

767
    pub fn close_output_port(&self) -> Result<()> {
×
768
        self.port.write().close_output_port()
×
769
    }
770

771
    pub fn close_input_port(&self) -> Result<()> {
×
772
        self.port.write().close_input_port()
×
773
    }
774
}
775

776
#[cfg(not(feature = "sync"))]
777
thread_local! {
778
    pub static WOULD_BLOCK_OBJECT: once_cell::unsync::Lazy<(crate::SteelVal,
779
        super::structs::StructTypeDescriptor)>= once_cell::unsync::Lazy::new(|| {
780
        super::structs::make_struct_singleton("would-block")
781
    });
782
}
783

784
#[cfg(feature = "sync")]
785
pub static WOULD_BLOCK_OBJECT: once_cell::sync::Lazy<(
786
    crate::SteelVal,
787
    super::structs::StructTypeDescriptor,
788
)> = once_cell::sync::Lazy::new(|| super::structs::make_struct_singleton("eof"));
×
789

790
pub fn would_block() -> SteelVal {
×
791
    #[cfg(feature = "sync")]
792
    {
793
        WOULD_BLOCK_OBJECT.0.clone()
×
794
    }
795

796
    #[cfg(not(feature = "sync"))]
797
    {
798
        WOULD_BLOCK_OBJECT.with(|eof| eof.0.clone())
799
    }
800
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc