mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 11:48:13 +01:00
Merge pull request #588 from asciinema/rust-cat
New impl of `cat` command
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
|
use crate::format::asciicast;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
@@ -9,6 +11,30 @@ pub struct Cli {
|
|||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub fn run(self) -> Result<()> {
|
pub fn run(self) -> Result<()> {
|
||||||
todo!();
|
let mut writer = asciicast::Writer::new(io::stdout(), 0);
|
||||||
|
let mut time_offset: u64 = 0;
|
||||||
|
let mut first = true;
|
||||||
|
|
||||||
|
for path in self.filename.iter() {
|
||||||
|
let reader = io::BufReader::new(fs::File::open(path)?);
|
||||||
|
let (header, events) = asciicast::open(reader)?;
|
||||||
|
let mut time = time_offset;
|
||||||
|
|
||||||
|
if first {
|
||||||
|
writer.write_header(&header)?;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
let mut event = event?;
|
||||||
|
time = time_offset + event.time;
|
||||||
|
event.time = time;
|
||||||
|
writer.write_event(event)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_offset = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ impl Cli {
|
|||||||
let time_offset = if append {
|
let time_offset = if append {
|
||||||
asciicast::get_duration(&self.filename)?
|
asciicast::get_duration(&self.filename)?
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(asciicast::Writer::new(file, time_offset))
|
Box::new(asciicast::Writer::new(file, time_offset))
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use std::{collections::HashMap, io};
|
|||||||
|
|
||||||
pub trait Writer {
|
pub trait Writer {
|
||||||
fn header(&mut self, header: &Header) -> io::Result<()>;
|
fn header(&mut self, header: &Header) -> io::Result<()>;
|
||||||
fn output(&mut self, time: f64, data: &[u8]) -> io::Result<()>;
|
fn output(&mut self, time: u64, data: &[u8]) -> io::Result<()>;
|
||||||
fn input(&mut self, time: f64, data: &[u8]) -> io::Result<()>;
|
fn input(&mut self, time: u64, data: &[u8]) -> io::Result<()>;
|
||||||
fn resize(&mut self, time: f64, size: (u16, u16)) -> io::Result<()>;
|
fn resize(&mut self, time: u64, size: (u16, u16)) -> io::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use anyhow::bail;
|
use serde::de::Error;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -9,7 +9,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
pub struct Writer<W: Write> {
|
pub struct Writer<W: Write> {
|
||||||
writer: io::LineWriter<W>,
|
writer: io::LineWriter<W>,
|
||||||
time_offset: f64,
|
time_offset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -23,8 +23,11 @@ pub struct Header {
|
|||||||
env: HashMap<String, String>,
|
env: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
pub time: f64,
|
#[serde(deserialize_with = "deserialize_time")]
|
||||||
|
pub time: u64,
|
||||||
|
#[serde(deserialize_with = "deserialize_code")]
|
||||||
pub code: EventCode,
|
pub code: EventCode,
|
||||||
pub data: String,
|
pub data: String,
|
||||||
}
|
}
|
||||||
@@ -42,7 +45,7 @@ impl<W> Writer<W>
|
|||||||
where
|
where
|
||||||
W: Write,
|
W: Write,
|
||||||
{
|
{
|
||||||
pub fn new(writer: W, time_offset: f64) -> Self {
|
pub fn new(writer: W, time_offset: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
writer: io::LineWriter::new(writer),
|
writer: io::LineWriter::new(writer),
|
||||||
time_offset,
|
time_offset,
|
||||||
@@ -50,13 +53,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_header(&mut self, header: &Header) -> io::Result<()> {
|
pub fn write_header(&mut self, header: &Header) -> io::Result<()> {
|
||||||
writeln!(self.writer, "{}", serde_json::to_string(&header)?)
|
writeln!(self.writer, "{}", serde_json::to_string(header)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_event(&mut self, mut event: Event) -> io::Result<()> {
|
pub fn write_event(&mut self, mut event: Event) -> io::Result<()> {
|
||||||
event.time += self.time_offset;
|
event.time += self.time_offset;
|
||||||
|
|
||||||
writeln!(self.writer, "{}", serde_json::to_string(&event)?)
|
writeln!(self.writer, "{}", serialize_event(&event)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,73 +71,97 @@ where
|
|||||||
self.write_header(&header.into())
|
self.write_header(&header.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output(&mut self, time: f64, data: &[u8]) -> io::Result<()> {
|
fn output(&mut self, time: u64, data: &[u8]) -> io::Result<()> {
|
||||||
self.write_event(Event::output(time, data))
|
self.write_event(Event::output(time, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input(&mut self, time: f64, data: &[u8]) -> io::Result<()> {
|
fn input(&mut self, time: u64, data: &[u8]) -> io::Result<()> {
|
||||||
self.write_event(Event::input(time, data))
|
self.write_event(Event::input(time, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&mut self, time: f64, size: (u16, u16)) -> io::Result<()> {
|
fn resize(&mut self, time: u64, size: (u16, u16)) -> io::Result<()> {
|
||||||
self.write_event(Event::resize(time, size))
|
self.write_event(Event::resize(time, size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open<R: BufRead>(
|
pub fn get_duration<S: AsRef<Path>>(path: S) -> anyhow::Result<u64> {
|
||||||
reader: R,
|
|
||||||
) -> anyhow::Result<(super::Header, impl Iterator<Item = anyhow::Result<Event>>)> {
|
|
||||||
let mut lines = reader.lines();
|
|
||||||
let first_line = lines.next().ok_or(anyhow::anyhow!("empty"))??;
|
|
||||||
let header: Header = serde_json::from_str(&first_line)?;
|
|
||||||
let header: super::Header = (&header).into();
|
|
||||||
|
|
||||||
let events = lines
|
|
||||||
.filter(|l| l.as_ref().map_or(true, |l| !l.is_empty()))
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, l)| l.map(|l| parse_event(l, i + 2))?);
|
|
||||||
|
|
||||||
Ok((header, events))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_event(line: String, i: usize) -> anyhow::Result<Event> {
|
|
||||||
use EventCode::*;
|
|
||||||
|
|
||||||
let value: serde_json::Value = serde_json::from_str(&line)?;
|
|
||||||
|
|
||||||
let time = value[0]
|
|
||||||
.as_f64()
|
|
||||||
.ok_or(anyhow::anyhow!("line {}: invalid event time", i))?;
|
|
||||||
|
|
||||||
let code = match value[1].as_str() {
|
|
||||||
Some("o") => Output,
|
|
||||||
Some("i") => Input,
|
|
||||||
Some("r") => Resize,
|
|
||||||
Some("m") => Marker,
|
|
||||||
Some(s) if !s.is_empty() => Other(s.chars().next().unwrap()),
|
|
||||||
Some(_) => bail!("line {}: missing event code", i),
|
|
||||||
None => bail!("line {}: event code must be a string", i),
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = match value[2].as_str() {
|
|
||||||
Some(data) => data.to_owned(),
|
|
||||||
None => bail!("line {}: event data must be a string", i),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Event { time, code, data })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_duration<S: AsRef<Path>>(path: S) -> anyhow::Result<f64> {
|
|
||||||
let file = fs::File::open(path)?;
|
let file = fs::File::open(path)?;
|
||||||
let reader = io::BufReader::new(file);
|
let reader = io::BufReader::new(file);
|
||||||
let (_header, events) = open(reader)?;
|
let (_header, events) = open(reader)?;
|
||||||
let time = events.last().map_or(Ok(0.0), |e| e.map(|e| e.time))?;
|
let time = events.last().map_or(Ok(0), |e| e.map(|e| e.time))?;
|
||||||
|
|
||||||
Ok(time)
|
Ok(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn open<R: BufRead>(
|
||||||
|
reader: R,
|
||||||
|
) -> anyhow::Result<(Header, impl Iterator<Item = anyhow::Result<Event>>)> {
|
||||||
|
let mut lines = reader.lines();
|
||||||
|
let first_line = lines.next().ok_or(anyhow::anyhow!("empty file"))??;
|
||||||
|
let header: Header = serde_json::from_str(&first_line)?;
|
||||||
|
let events = lines.filter_map(parse_event);
|
||||||
|
|
||||||
|
Ok((header, events))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_event(line: io::Result<String>) -> Option<anyhow::Result<Event>> {
|
||||||
|
match line {
|
||||||
|
Ok(line) => {
|
||||||
|
if line.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(serde_json::from_str(&line).map_err(|e| e.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => Some(Err(e.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_time<'de, D>(deserializer: D) -> Result<u64, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value: serde_json::Value = Deserialize::deserialize(deserializer)?;
|
||||||
|
let string = value.to_string();
|
||||||
|
let parts: Vec<&str> = string.split('.').collect();
|
||||||
|
|
||||||
|
match parts.as_slice() {
|
||||||
|
[left, right] => {
|
||||||
|
let secs: u64 = left.parse().map_err(Error::custom)?;
|
||||||
|
|
||||||
|
let right = right.trim();
|
||||||
|
let micros: u64 = format!("{:0<6}", &right[..(6.min(right.len()))])
|
||||||
|
.parse()
|
||||||
|
.map_err(Error::custom)?;
|
||||||
|
|
||||||
|
Ok(secs * 1_000_000 + micros)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(Error::custom("invalid time format")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_code<'de, D>(deserializer: D) -> Result<EventCode, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use EventCode::*;
|
||||||
|
|
||||||
|
let value: &str = Deserialize::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
"o" => Ok(Output),
|
||||||
|
"i" => Ok(Input),
|
||||||
|
"r" => Ok(Resize),
|
||||||
|
"m" => Ok(Marker),
|
||||||
|
"" => Err(Error::custom("missing event code")),
|
||||||
|
s => Ok(Other(s.chars().next().unwrap())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
pub fn output(time: f64, data: &[u8]) -> Self {
|
pub fn output(time: u64, data: &[u8]) -> Self {
|
||||||
Event {
|
Event {
|
||||||
time,
|
time,
|
||||||
code: EventCode::Output,
|
code: EventCode::Output,
|
||||||
@@ -142,7 +169,7 @@ impl Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input(time: f64, data: &[u8]) -> Self {
|
pub fn input(time: u64, data: &[u8]) -> Self {
|
||||||
Event {
|
Event {
|
||||||
time,
|
time,
|
||||||
code: EventCode::Input,
|
code: EventCode::Input,
|
||||||
@@ -150,7 +177,7 @@ impl Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(time: f64, size: (u16, u16)) -> Self {
|
pub fn resize(time: u64, size: (u16, u16)) -> Self {
|
||||||
Event {
|
Event {
|
||||||
time,
|
time,
|
||||||
code: EventCode::Resize,
|
code: EventCode::Resize,
|
||||||
@@ -224,18 +251,17 @@ impl serde::Serialize for Header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::Serialize for Event {
|
fn serialize_event(event: &Event) -> Result<String, serde_json::Error> {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
Ok(format!(
|
||||||
where
|
"[{}, {}, {}]",
|
||||||
S: serde::Serializer,
|
format_time(event.time).trim_end_matches('0'),
|
||||||
{
|
serde_json::to_string(&event.code.to_string())?,
|
||||||
use serde::ser::SerializeTuple;
|
serde_json::to_string(&event.data)?
|
||||||
let mut tup = serializer.serialize_tuple(3)?;
|
))
|
||||||
tup.serialize_element(&self.time)?;
|
}
|
||||||
tup.serialize_element(&self.code.to_string())?;
|
|
||||||
tup.serialize_element(&self.data)?;
|
fn format_time(time: u64) -> String {
|
||||||
tup.end()
|
format!("{}.{:0>6}", time / 1_000_000, time % 1_000_000)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Header> for super::Header {
|
impl From<&Header> for super::Header {
|
||||||
@@ -283,17 +309,17 @@ mod tests {
|
|||||||
.collect::<anyhow::Result<Vec<Event>>>()
|
.collect::<anyhow::Result<Vec<Event>>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!((header.cols, header.rows), (75, 18));
|
assert_eq!((header.width, header.height), (75, 18));
|
||||||
|
|
||||||
assert_eq!(events[1].time, 0.100989);
|
assert_eq!(events[1].time, 100989);
|
||||||
assert_eq!(events[1].code, EventCode::Output);
|
assert_eq!(events[1].code, EventCode::Output);
|
||||||
assert_eq!(events[1].data, "\u{1b}[?2004h");
|
assert_eq!(events[1].data, "\u{1b}[?2004h");
|
||||||
|
|
||||||
assert_eq!(events[5].time, 1.511526);
|
assert_eq!(events[5].time, 1511526);
|
||||||
assert_eq!(events[5].code, EventCode::Input);
|
assert_eq!(events[5].code, EventCode::Input);
|
||||||
assert_eq!(events[5].data, "v");
|
assert_eq!(events[5].data, "v");
|
||||||
|
|
||||||
assert_eq!(events[6].time, 1.511937);
|
assert_eq!(events[6].time, 1511937);
|
||||||
assert_eq!(events[6].code, EventCode::Output);
|
assert_eq!(events[6].code, EventCode::Output);
|
||||||
assert_eq!(events[6].data, "v");
|
assert_eq!(events[6].data, "v");
|
||||||
}
|
}
|
||||||
@@ -304,7 +330,7 @@ mod tests {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let cursor = io::Cursor::new(&mut data);
|
let cursor = io::Cursor::new(&mut data);
|
||||||
let mut fw = Writer::new(cursor, 0.0);
|
let mut fw = Writer::new(cursor, 0);
|
||||||
|
|
||||||
let header = Header {
|
let header = Header {
|
||||||
width: 80,
|
width: 80,
|
||||||
@@ -317,7 +343,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fw.write_header(&header).unwrap();
|
fw.write_header(&header).unwrap();
|
||||||
fw.write_event(Event::output(1.0, "hello\r\n".as_bytes()))
|
fw.write_event(Event::output(1000001, "hello\r\n".as_bytes()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,12 +351,13 @@ mod tests {
|
|||||||
let data_len = data.len() as u64;
|
let data_len = data.len() as u64;
|
||||||
let mut cursor = io::Cursor::new(&mut data);
|
let mut cursor = io::Cursor::new(&mut data);
|
||||||
cursor.set_position(data_len);
|
cursor.set_position(data_len);
|
||||||
let mut fw = Writer::new(cursor, 1.0);
|
let mut fw = Writer::new(cursor, 1000001);
|
||||||
|
|
||||||
fw.write_event(Event::output(1.0, "world".as_bytes()))
|
fw.write_event(Event::output(1000001, "world".as_bytes()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fw.write_event(Event::input(2.0, " ".as_bytes())).unwrap();
|
fw.write_event(Event::input(2000002, " ".as_bytes()))
|
||||||
fw.write_event(Event::resize(3.0, (100, 40))).unwrap();
|
.unwrap();
|
||||||
|
fw.write_event(Event::resize(3000003, (100, 40))).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let lines = parse(data);
|
let lines = parse(data);
|
||||||
@@ -339,16 +366,16 @@ mod tests {
|
|||||||
assert_eq!(lines[0]["width"], 80);
|
assert_eq!(lines[0]["width"], 80);
|
||||||
assert_eq!(lines[0]["height"], 24);
|
assert_eq!(lines[0]["height"], 24);
|
||||||
assert_eq!(lines[0]["timestamp"], 1);
|
assert_eq!(lines[0]["timestamp"], 1);
|
||||||
assert_eq!(lines[1][0], 1.0);
|
assert_eq!(lines[1][0], 1.000001);
|
||||||
assert_eq!(lines[1][1], "o");
|
assert_eq!(lines[1][1], "o");
|
||||||
assert_eq!(lines[1][2], "hello\r\n");
|
assert_eq!(lines[1][2], "hello\r\n");
|
||||||
assert_eq!(lines[2][0], 2.0);
|
assert_eq!(lines[2][0], 2.000002);
|
||||||
assert_eq!(lines[2][1], "o");
|
assert_eq!(lines[2][1], "o");
|
||||||
assert_eq!(lines[2][2], "world");
|
assert_eq!(lines[2][2], "world");
|
||||||
assert_eq!(lines[3][0], 3.0);
|
assert_eq!(lines[3][0], 3.000003);
|
||||||
assert_eq!(lines[3][1], "i");
|
assert_eq!(lines[3][1], "i");
|
||||||
assert_eq!(lines[3][2], " ");
|
assert_eq!(lines[3][2], " ");
|
||||||
assert_eq!(lines[4][0], 4.0);
|
assert_eq!(lines[4][0], 4.000004);
|
||||||
assert_eq!(lines[4][1], "r");
|
assert_eq!(lines[4][1], "r");
|
||||||
assert_eq!(lines[4][2], "100x40");
|
assert_eq!(lines[4][2], "100x40");
|
||||||
}
|
}
|
||||||
@@ -358,7 +385,7 @@ mod tests {
|
|||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut fw = Writer::new(io::Cursor::new(&mut data), 0.0);
|
let mut fw = Writer::new(io::Cursor::new(&mut data), 0);
|
||||||
|
|
||||||
let mut env = HashMap::new();
|
let mut env = HashMap::new();
|
||||||
env.insert("SHELL".to_owned(), "/usr/bin/fish".to_owned());
|
env.insert("SHELL".to_owned(), "/usr/bin/fish".to_owned());
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ impl<W: Write> super::Writer for Writer<W> {
|
|||||||
write!(self.writer, "\x1b[8;{};{}t", header.rows, header.cols)
|
write!(self.writer, "\x1b[8;{};{}t", header.rows, header.cols)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output(&mut self, _time: f64, data: &[u8]) -> io::Result<()> {
|
fn output(&mut self, _time: u64, data: &[u8]) -> io::Result<()> {
|
||||||
self.writer.write_all(data)
|
self.writer.write_all(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input(&mut self, _time: f64, _data: &[u8]) -> io::Result<()> {
|
fn input(&mut self, _time: u64, _data: &[u8]) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&mut self, _time: f64, _size: (u16, u16)) -> io::Result<()> {
|
fn resize(&mut self, _time: u64, _size: (u16, u16)) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ pub struct Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Message {
|
enum Message {
|
||||||
Output(f64, Vec<u8>),
|
Output(u64, Vec<u8>),
|
||||||
Input(f64, Vec<u8>),
|
Input(u64, Vec<u8>),
|
||||||
Resize(f64, (u16, u16)),
|
Resize(u64, (u16, u16)),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct JoinHandle(Option<thread::JoinHandle<()>>);
|
struct JoinHandle(Option<thread::JoinHandle<()>>);
|
||||||
@@ -55,8 +55,8 @@ impl Recorder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elapsed_time(&self) -> f64 {
|
fn elapsed_time(&self) -> u64 {
|
||||||
self.start_time.elapsed().as_secs_f64()
|
self.start_time.elapsed().as_micros() as u64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user