mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-10-29 07:45:02 -04:00
update various deps
This brings most things reasonably up-to-date. libpasta's deps are dragging a bit, keeping us on an older ring to avoid duplication, and causing us to use three versions of base64. And I need to update a few of my companion crates' parking_lot dep to match tokio.
This commit is contained in:
parent
8512199d85
commit
269db57a53
@ -23,7 +23,7 @@ matrix:
|
|||||||
script:
|
script:
|
||||||
- ci/script-rust.sh
|
- ci/script-rust.sh
|
||||||
- language: rust
|
- language: rust
|
||||||
rust: 1.42.0
|
rust: 1.45.0
|
||||||
script:
|
script:
|
||||||
- ci/script-rust.sh
|
- ci/script-rust.sh
|
||||||
- language: node_js
|
- language: node_js
|
||||||
|
|||||||
1430
Cargo.lock
generated
1430
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@ -21,12 +21,12 @@ members = ["base", "db"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base = { package = "moonfire-base", path = "base" }
|
base = { package = "moonfire-base", path = "base" }
|
||||||
base64 = "0.11.0"
|
base64 = "0.13.0"
|
||||||
blake3 = "0.2.2"
|
blake3 = "0.3.7"
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
cstr = "0.1.7"
|
cstr = "0.2.5"
|
||||||
cursive = "0.14.0"
|
cursive = "0.15.0"
|
||||||
db = { package = "moonfire-db", path = "db" }
|
db = { package = "moonfire-db", path = "db" }
|
||||||
failure = "0.1.1"
|
failure = "0.1.1"
|
||||||
ffmpeg = { package = "moonfire-ffmpeg", git = "https://github.com/scottlamb/moonfire-ffmpeg" }
|
ffmpeg = { package = "moonfire-ffmpeg", git = "https://github.com/scottlamb/moonfire-ffmpeg" }
|
||||||
@ -43,8 +43,8 @@ memchr = "2.0.2"
|
|||||||
memmap = "0.7"
|
memmap = "0.7"
|
||||||
moonfire-tflite = { git = "https://github.com/scottlamb/moonfire-tflite", features = ["edgetpu"], optional = true }
|
moonfire-tflite = { git = "https://github.com/scottlamb/moonfire-tflite", features = ["edgetpu"], optional = true }
|
||||||
mylog = { git = "https://github.com/scottlamb/mylog" }
|
mylog = { git = "https://github.com/scottlamb/mylog" }
|
||||||
nix = "0.17.0"
|
nix = "0.19.0"
|
||||||
nom = "5.1.1"
|
nom = "6.0.0"
|
||||||
parking_lot = { version = "0.10", features = [] }
|
parking_lot = { version = "0.10", features = [] }
|
||||||
protobuf = { git = "https://github.com/stepancheg/rust-protobuf" }
|
protobuf = { git = "https://github.com/stepancheg/rust-protobuf" }
|
||||||
reffers = "0.6.0"
|
reffers = "0.6.0"
|
||||||
@ -56,7 +56,7 @@ smallvec = "1.0"
|
|||||||
structopt = { version = "0.3.13", features = ["default", "wrap_help"] }
|
structopt = { version = "0.3.13", features = ["default", "wrap_help"] }
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
tokio = { version = "0.2.0", features = ["blocking", "macros", "parking_lot", "rt-threaded", "signal"] }
|
tokio = { version = "0.2.0", features = ["blocking", "macros", "parking_lot", "rt-threaded", "signal"] }
|
||||||
tokio-tungstenite = "0.10.1"
|
tokio-tungstenite = "0.11.0"
|
||||||
url = "2.1.1"
|
url = "2.1.1"
|
||||||
uuid = { version = "0.8", features = ["serde", "std", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "std", "v4"] }
|
||||||
|
|
||||||
|
|||||||
@ -17,5 +17,5 @@ lazy_static = "1.0"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
parking_lot = { version = "0.10", features = [] }
|
parking_lot = { version = "0.10", features = [] }
|
||||||
nom = "5.1.1"
|
nom = "6.0.0"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
|
|||||||
@ -49,7 +49,7 @@ pub const TIME_UNITS_PER_SEC: i64 = 90_000;
|
|||||||
pub struct Time(pub i64);
|
pub struct Time(pub i64);
|
||||||
|
|
||||||
/// Returns a parser for a `len`-digit non-negative number which fits into an i32.
|
/// Returns a parser for a `len`-digit non-negative number which fits into an i32.
|
||||||
fn fixed_len_num<'a>(len: usize) -> impl Fn(&'a str) -> IResult<&'a str, i32> {
|
fn fixed_len_num<'a>(len: usize) -> impl FnMut(&'a str) -> IResult<&'a str, i32> {
|
||||||
map_res(take_while_m_n(len, len, |c: char| c.is_ascii_digit()),
|
map_res(take_while_m_n(len, len, |c: char| c.is_ascii_digit()),
|
||||||
|input: &str| i32::from_str_radix(input, 10))
|
|input: &str| i32::from_str_radix(input, 10))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,10 +13,10 @@ path = "lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base = { package = "moonfire-base", path = "../base" }
|
base = { package = "moonfire-base", path = "../base" }
|
||||||
base64 = "0.11.0"
|
base64 = "0.13.0"
|
||||||
blake3 = "0.2.2"
|
blake3 = "0.3.7"
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
cstr = "0.1.7"
|
cstr = "0.2.5"
|
||||||
failure = "0.1.1"
|
failure = "0.1.1"
|
||||||
fnv = "1.0"
|
fnv = "1.0"
|
||||||
h264-reader = { git = "https://github.com/dholroyd/h264-reader" }
|
h264-reader = { git = "https://github.com/dholroyd/h264-reader" }
|
||||||
@ -26,7 +26,7 @@ libc = "0.2"
|
|||||||
libpasta = "0.1.0-rc2"
|
libpasta = "0.1.0-rc2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mylog = { git = "https://github.com/scottlamb/mylog" }
|
mylog = { git = "https://github.com/scottlamb/mylog" }
|
||||||
nix = "0.17.0"
|
nix = "0.19.0"
|
||||||
odds = { version = "0.4.0", features = ["std-vec"] }
|
odds = { version = "0.4.0", features = ["std-vec"] }
|
||||||
parking_lot = { version = "0.10", features = [] }
|
parking_lot = { version = "0.10", features = [] }
|
||||||
prettydiff = "0.3.1"
|
prettydiff = "0.3.1"
|
||||||
|
|||||||
@ -39,7 +39,6 @@ use failure::Error;
|
|||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use log::error;
|
use log::error;
|
||||||
use nix::fcntl::AtFlags;
|
use nix::fcntl::AtFlags;
|
||||||
use protobuf::prelude::MessageField;
|
|
||||||
use rusqlite::params;
|
use rusqlite::params;
|
||||||
use crate::schema;
|
use crate::schema;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
@ -80,7 +79,7 @@ pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<(), Error> {
|
|||||||
meta.db_uuid.extend_from_slice(&db_uuid.as_bytes()[..]);
|
meta.db_uuid.extend_from_slice(&db_uuid.as_bytes()[..]);
|
||||||
meta.dir_uuid.extend_from_slice(&dir_uuid.0.as_bytes()[..]);
|
meta.dir_uuid.extend_from_slice(&dir_uuid.0.as_bytes()[..]);
|
||||||
{
|
{
|
||||||
let o = meta.last_complete_open.mut_message();
|
let o = meta.last_complete_open.set_default();
|
||||||
o.id = open_id;
|
o.id = open_id;
|
||||||
o.uuid.extend_from_slice(&open_uuid.0.as_bytes()[..]);
|
o.uuid.extend_from_slice(&open_uuid.0.as_bytes()[..]);
|
||||||
}
|
}
|
||||||
|
|||||||
10
db/db.rs
10
db/db.rs
@ -66,7 +66,6 @@ use hashlink::LinkedHashMap;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{error, info, trace};
|
use log::{error, info, trace};
|
||||||
use parking_lot::{Mutex,MutexGuard};
|
use parking_lot::{Mutex,MutexGuard};
|
||||||
use protobuf::prelude::MessageField;
|
|
||||||
use rusqlite::{named_params, params};
|
use rusqlite::{named_params, params};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@ -379,7 +378,7 @@ impl SampleFileDir {
|
|||||||
meta.db_uuid.extend_from_slice(&db_uuid.as_bytes()[..]);
|
meta.db_uuid.extend_from_slice(&db_uuid.as_bytes()[..]);
|
||||||
meta.dir_uuid.extend_from_slice(&self.uuid.as_bytes()[..]);
|
meta.dir_uuid.extend_from_slice(&self.uuid.as_bytes()[..]);
|
||||||
if let Some(o) = self.last_complete_open {
|
if let Some(o) = self.last_complete_open {
|
||||||
let open = meta.last_complete_open.mut_message();
|
let open = meta.last_complete_open.set_default();
|
||||||
open.id = o.id;
|
open.id = o.id;
|
||||||
open.uuid.extend_from_slice(&o.uuid.as_bytes()[..]);
|
open.uuid.extend_from_slice(&o.uuid.as_bytes()[..]);
|
||||||
}
|
}
|
||||||
@ -1210,7 +1209,7 @@ impl LockedDatabase {
|
|||||||
if dir.dir.is_some() { continue }
|
if dir.dir.is_some() { continue }
|
||||||
let mut meta = dir.meta(&self.uuid);
|
let mut meta = dir.meta(&self.uuid);
|
||||||
if let Some(o) = self.open.as_ref() {
|
if let Some(o) = self.open.as_ref() {
|
||||||
let open = meta.in_progress_open.mut_message();
|
let open = meta.in_progress_open.set_default();
|
||||||
open.id = o.id;
|
open.id = o.id;
|
||||||
open.uuid.extend_from_slice(&o.uuid.as_bytes()[..]);
|
open.uuid.extend_from_slice(&o.uuid.as_bytes()[..]);
|
||||||
}
|
}
|
||||||
@ -1703,7 +1702,7 @@ impl LockedDatabase {
|
|||||||
{
|
{
|
||||||
meta.db_uuid.extend_from_slice(&self.uuid.as_bytes()[..]);
|
meta.db_uuid.extend_from_slice(&self.uuid.as_bytes()[..]);
|
||||||
meta.dir_uuid.extend_from_slice(uuid_bytes);
|
meta.dir_uuid.extend_from_slice(uuid_bytes);
|
||||||
let open = meta.in_progress_open.mut_message();
|
let open = meta.in_progress_open.set_default();
|
||||||
open.id = o.id;
|
open.id = o.id;
|
||||||
open.uuid.extend_from_slice(&o.uuid.as_bytes()[..]);
|
open.uuid.extend_from_slice(&o.uuid.as_bytes()[..]);
|
||||||
}
|
}
|
||||||
@ -1761,8 +1760,7 @@ impl LockedDatabase {
|
|||||||
bail!("Can't delete sample file directory {} which still has files", &d.get().path);
|
bail!("Can't delete sample file directory {} which still has files", &d.get().path);
|
||||||
}
|
}
|
||||||
let mut meta = d.get().meta(&self.uuid);
|
let mut meta = d.get().meta(&self.uuid);
|
||||||
meta.in_progress_open = mem::replace(&mut meta.last_complete_open,
|
meta.in_progress_open = meta.last_complete_open.take().into();
|
||||||
::protobuf::SingularPtrField::none());
|
|
||||||
dir.write_meta(&meta)?;
|
dir.write_meta(&meta)?;
|
||||||
if self.conn.execute("delete from sample_file_dir where id = ?", params![dir_id])? != 1 {
|
if self.conn.execute("delete from sample_file_dir where id = ?", params![dir_id])? != 1 {
|
||||||
bail!("missing database row for dir {}", dir_id);
|
bail!("missing database row for dir {}", dir_id);
|
||||||
|
|||||||
17
db/dir.rs
17
db/dir.rs
@ -35,7 +35,7 @@
|
|||||||
use crate::coding;
|
use crate::coding;
|
||||||
use crate::db::CompositeId;
|
use crate::db::CompositeId;
|
||||||
use crate::schema;
|
use crate::schema;
|
||||||
use cstr::*;
|
use cstr::cstr;
|
||||||
use failure::{Error, Fail, bail, format_err};
|
use failure::{Error, Fail, bail, format_err};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use protobuf::Message;
|
use protobuf::Message;
|
||||||
@ -322,7 +322,6 @@ pub(crate) fn parse_id(id: &[u8]) -> Result<CompositeId, ()> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use protobuf::prelude::MessageField;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -343,10 +342,16 @@ mod tests {
|
|||||||
let fake_uuid = &[0u8; 16][..];
|
let fake_uuid = &[0u8; 16][..];
|
||||||
meta.db_uuid.extend_from_slice(fake_uuid);
|
meta.db_uuid.extend_from_slice(fake_uuid);
|
||||||
meta.dir_uuid.extend_from_slice(fake_uuid);
|
meta.dir_uuid.extend_from_slice(fake_uuid);
|
||||||
meta.last_complete_open.mut_message().id = u32::max_value();
|
{
|
||||||
meta.last_complete_open.mut_message().id = u32::max_value();
|
let o = meta.last_complete_open.set_default();
|
||||||
meta.in_progress_open.mut_message().uuid.extend_from_slice(fake_uuid);
|
o.id = u32::max_value();
|
||||||
meta.in_progress_open.mut_message().uuid.extend_from_slice(fake_uuid);
|
o.uuid.extend_from_slice(fake_uuid);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let o = meta.in_progress_open.set_default();
|
||||||
|
o.id = u32::max_value();
|
||||||
|
o.uuid.extend_from_slice(fake_uuid);
|
||||||
|
}
|
||||||
let data = meta.write_length_delimited_to_bytes().expect("proto3->vec is infallible");
|
let data = meta.write_length_delimited_to_bytes().expect("proto3->vec is infallible");
|
||||||
assert!(data.len() <= FIXED_DIR_META_LEN, "{} vs {}", data.len(), FIXED_DIR_META_LEN);
|
assert!(data.len() <= FIXED_DIR_META_LEN, "{} vs {}", data.len(), FIXED_DIR_META_LEN);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,6 @@ use crate::dir;
|
|||||||
use failure::{Error, bail, format_err};
|
use failure::{Error, bail, format_err};
|
||||||
use nix::fcntl::{FlockArg, OFlag};
|
use nix::fcntl::{FlockArg, OFlag};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use protobuf::prelude::MessageField;
|
|
||||||
use rusqlite::params;
|
use rusqlite::params;
|
||||||
use crate::schema::DirMeta;
|
use crate::schema::DirMeta;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
@ -116,7 +115,7 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
|
|||||||
{
|
{
|
||||||
meta.db_uuid.extend_from_slice(db_uuid_bytes);
|
meta.db_uuid.extend_from_slice(db_uuid_bytes);
|
||||||
meta.dir_uuid.extend_from_slice(dir_uuid_bytes);
|
meta.dir_uuid.extend_from_slice(dir_uuid_bytes);
|
||||||
let open = meta.last_complete_open.mut_message();
|
let open = meta.last_complete_open.set_default();
|
||||||
open.id = open_id;
|
open.id = open_id;
|
||||||
open.uuid.extend_from_slice(&open_uuid_bytes);
|
open.uuid.extend_from_slice(&open_uuid_bytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,6 @@ use crate::db::{self, FromSqlUuid};
|
|||||||
use crate::dir;
|
use crate::dir;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use crate::schema;
|
use crate::schema;
|
||||||
use protobuf::prelude::MessageField;
|
|
||||||
use rusqlite::params;
|
use rusqlite::params;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -66,7 +65,7 @@ fn open_sample_file_dir(tx: &rusqlite::Transaction) -> Result<Arc<dir::SampleFil
|
|||||||
meta.db_uuid.extend_from_slice(&db_uuid.0.as_bytes()[..]);
|
meta.db_uuid.extend_from_slice(&db_uuid.0.as_bytes()[..]);
|
||||||
meta.dir_uuid.extend_from_slice(&s_uuid.0.as_bytes()[..]);
|
meta.dir_uuid.extend_from_slice(&s_uuid.0.as_bytes()[..]);
|
||||||
{
|
{
|
||||||
let open = meta.last_complete_open.mut_message();
|
let open = meta.last_complete_open.set_default();
|
||||||
open.id = o_id as u32;
|
open.id = o_id as u32;
|
||||||
open.uuid.extend_from_slice(&o_uuid.0.as_bytes()[..]);
|
open.uuid.extend_from_slice(&o_uuid.0.as_bytes()[..]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,12 +35,12 @@
|
|||||||
|
|
||||||
use crate::db::FromSqlUuid;
|
use crate::db::FromSqlUuid;
|
||||||
use crate::{dir, schema};
|
use crate::{dir, schema};
|
||||||
use cstr::*;
|
use cstr::cstr;
|
||||||
use failure::{Error, Fail, bail};
|
use failure::{Error, Fail, bail};
|
||||||
use log::info;
|
use log::info;
|
||||||
use nix::fcntl::{FlockArg, OFlag};
|
use nix::fcntl::{FlockArg, OFlag};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use protobuf::{Message, prelude::MessageField};
|
use protobuf::Message;
|
||||||
use rusqlite::params;
|
use rusqlite::params;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
@ -137,7 +137,7 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
|
|||||||
db_meta.dir_uuid.extend_from_slice(&dir_uuid.0.as_bytes()[..]);
|
db_meta.dir_uuid.extend_from_slice(&dir_uuid.0.as_bytes()[..]);
|
||||||
match (open_id, open_uuid) {
|
match (open_id, open_uuid) {
|
||||||
(Some(id), Some(uuid)) => {
|
(Some(id), Some(uuid)) => {
|
||||||
let mut o = db_meta.last_complete_open.mut_message();
|
let mut o = db_meta.last_complete_open.set_default();
|
||||||
o.id = id;
|
o.id = id;
|
||||||
o.uuid.extend_from_slice(&uuid.0.as_bytes()[..]);
|
o.uuid.extend_from_slice(&uuid.0.as_bytes()[..]);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,7 +48,7 @@ $ sudo apt-get install \
|
|||||||
tzdata
|
tzdata
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, you need Rust 1.42+ and Cargo. The easiest way to install them is by
|
Next, you need Rust 1.45+ and Cargo. The easiest way to install them is by
|
||||||
following the instructions at [rustup.rs](https://www.rustup.rs/).
|
following the instructions at [rustup.rs](https://www.rustup.rs/).
|
||||||
|
|
||||||
Finally, building the UI requires [yarn](https://yarnpkg.com/en/).
|
Finally, building the UI requires [yarn](https://yarnpkg.com/en/).
|
||||||
|
|||||||
@ -40,7 +40,7 @@ fi
|
|||||||
NODE_MIN_VERSION="10"
|
NODE_MIN_VERSION="10"
|
||||||
YARN_MIN_VERSION="1.0"
|
YARN_MIN_VERSION="1.0"
|
||||||
CARGO_MIN_VERSION="0.2"
|
CARGO_MIN_VERSION="0.2"
|
||||||
RUSTC_MIN_VERSION="1.42"
|
RUSTC_MIN_VERSION="1.45"
|
||||||
|
|
||||||
normalizeDirPath()
|
normalizeDirPath()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
//! * H.264/H.265 decoding on every frame but performing object detection at a minimum pts
|
//! * H.264/H.265 decoding on every frame but performing object detection at a minimum pts
|
||||||
//! interval to cut down on expense.
|
//! interval to cut down on expense.
|
||||||
|
|
||||||
use cstr::*;
|
use cstr::cstr;
|
||||||
use failure::{Error, format_err};
|
use failure::{Error, format_err};
|
||||||
use ffmpeg;
|
use ffmpeg;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
//! configuration will likely be almost entirely done through a web-based UI.
|
//! configuration will likely be almost entirely done through a web-based UI.
|
||||||
|
|
||||||
use base::clock;
|
use base::clock;
|
||||||
use cursive::Cursive;
|
use cursive::{Cursive, CursiveExt};
|
||||||
use cursive::views;
|
use cursive::views;
|
||||||
use db;
|
use db;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::h264;
|
use crate::h264;
|
||||||
use cstr::*;
|
use cstr::cstr;
|
||||||
use failure::{Error, bail};
|
use failure::{Error, bail};
|
||||||
use ffmpeg;
|
use ffmpeg;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|||||||
@ -190,7 +190,7 @@ struct Segments {
|
|||||||
end_time: Option<i64>,
|
end_time: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn num<'a, T: FromStr>() -> impl Fn(&'a str) -> IResult<&'a str, T> {
|
fn num<'a, T: FromStr>() -> impl FnMut(&'a str) -> IResult<&'a str, T> {
|
||||||
map_res(take_while1(|c: char| c.is_ascii_digit()), FromStr::from_str)
|
map_res(take_while1(|c: char| c.is_ascii_digit()), FromStr::from_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user