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
//! `Send + Sync` host resources. The interpreter
//! grows its native surface as scripts need it. Beyond tasks and futures it now
//! carries the subprocess family, so a `#[tokio::main]` script can spawn a child
//! and stream its pipes from concurrent tasks.
use std::fs::File;
use std::future::Future;
use std::io::{BufRead, BufReader, Read, Write};
use std::net::{TcpListener, TcpStream, UdpSocket};
use std::pin::Pin;
use std::process::{Child, ChildStdin};
use std::sync::Arc;
use std::time::{Instant, SystemTime};
use parking_lot::Mutex;
use super::value::Value;
/// A boxed future that yields a script value. `Send` so it can be driven on any
/// worker thread.
pub type BoxFut = Pin<Box<dyn Future<Output = Value> + Send>>;
/// A line iterator over a pipe. `Send` so a lane reading a child can live on a
/// worker thread.
pub type LineIter = Box<dyn Iterator<Item = std::io::Result<String>> + Send>;
pub enum Native {
/// A spawned task, joined when awaited.
Task(tokio::task::JoinHandle<Value>),
/// A pending future, for example `tokio::time::sleep` or an async request.
Future(BoxFut),
/// An async reqwest client, cheap to clone and shared across tasks.
HttpClient(reqwest::Client),
/// The blocking reqwest client. Safe here because script code always runs
/// on blocking threads, never on a runtime worker.
BlockingHttpClient(reqwest::blocking::Client),
/// A monotonic clock reading used by timed async scripts.
Instant(Instant),
/// A wall clock reading, `SystemTime::now` or a file timestamp.
SystemTime(SystemTime),
/// A spawned child process, waited on through its `Child` value.
Child(Child),
/// The writable end of a child's piped stdin.
ChildStdin(ChildStdin),
/// An open file, buffered, which can also write and seek.
File(BufReader<File>),
/// A buffered reader over a child's piped stdout or stderr.
Reader(BufReader<Box<dyn Read + Send>>),
/// A writer: stdout, stderr, or another byte sink.
Writer(Box<dyn Write + Send>),
/// A bound TCP listener.
Listener(TcpListener),
/// A connected TCP stream.
Stream(TcpStream),
/// A bound UDP socket.
Udp(UdpSocket),
/// A loaded PDF document, the real lopdf value.
Pdf(Box<lopdf::Document>),
/// A temporary directory, deleted when the value drops or on `close`.
TempDir(tempfile::TempDir),
/// A named temporary file.
NamedTempFile(tempfile::NamedTempFile),
/// An in-progress SHA-256 hasher, fed by `update` and read by `finalize`.
Sha256(sha2::Sha256),
/// A lazy line iterator, so `for line in reader.lines()` streams a pipe
/// instead of buffering all of it first.
Lines(LineIter),
/// A response body still in its wire form. Kept undecoded so a script that
/// only wants the byte count never pays for a UTF-8 conversion, which on a
/// binary payload both costs time and inflates the result.
Body(Vec<u8>),
/// A compiled pattern, shared across tasks so it compiles once.
Regex(super::regex_bridge::RegexValue),
/// A single match, holding its source and byte range.
RegexMatch(super::regex_bridge::MatchValue),
/// A capture set, indexable by group number or name.
RegexCaptures(super::regex_bridge::CapturesValue),
/// A lazy iterator, shared like every other handle so `by_ref` and
/// `peekable` keep their real semantics.
Iterator(super::iterator::IteratorState),
/// A consumed handle, left behind after a task or future is taken to await,
/// or after a stdin pipe is closed so the child sees EOF.
Taken,
}
impl Native {
pub fn type_name(&self) -> &'static str {
match self {
Native::Task(_) => "JoinHandle",
Native::Future(_) => "Future",
Native::HttpClient(_) | Native::BlockingHttpClient(_) => "Client",
Native::Instant(_) => "Instant",
Native::SystemTime(_) => "SystemTime",
Native::Child(_) => "Child",
Native::ChildStdin(_) => "ChildStdin",
Native::File(_) => "File",
Native::Reader(_) => "Reader",
Native::Writer(_) => "Writer",
Native::Listener(_) => "TcpListener",
Native::Stream(_) => "TcpStream",
Native::Udp(_) => "UdpSocket",
Native::Pdf(_) => "PdfDocument",
Native::TempDir(_) => "TempDir",
Native::NamedTempFile(_) => "NamedTempFile",
Native::Sha256(_) => "Sha256",
Native::Lines(_) => "Lines",
Native::Body(_) => "Body",
Native::Regex(_) => "Regex",
Native::RegexMatch(_) => "Match",
Native::RegexCaptures(_) => "Captures",
Native::Iterator(_) => "Iterator",
Native::Taken => "Taken",
}
}
/// The readable side of a handle, for the shared reader methods.
pub fn as_read(&mut self) -> Option<&mut dyn Read> {
match self {
Native::File(r) => Some(r),
Native::Reader(r) => Some(r),
_ => None,
}
}
/// The buffered side of a handle, for the reader methods that need a
/// delimiter. The File and Reader variants already own a `BufReader`, so this
/// hands out that buffer instead of wrapping a second one around it, which
/// would eat bytes the next call expects to still be there.
pub fn as_buf_read(&mut self) -> Option<&mut dyn BufRead> {
match self {
Native::File(r) => Some(r),
Native::Reader(r) => Some(r),
_ => None,
}
}
pub fn wrap(self) -> Value {
Value::Native(Arc::new(Mutex::new(self)))
}
}