Skip to content

Struct std::fs::File

提供对文件系统上打开文件的访问权限的对象。

可以通过打开 File 的选项来读取或者写入 File 的实例。文件还实现 Seek,以更改文件内部包含的逻辑游标。

文件离开作用域时将自动关闭。Drop 的实现将忽略在关闭时检测到的错误。如果必须手动处理这些错误,请使用方法 sync_all

1.实现

impl File

open

尝试以只读模式打开文件。

rust
pub fn open<P: AsRef<Path>>(path: P) -> Result<File>

参数

  • path:文件路径

返回值:返回一个Result,若打开成功,则返回Ok(File),否则返回相应错误信息

rust
use std::fs::File;
fn main() {
    let f = File::open("test.txt");			// 直接打开项目根路径下的文件
    println!("{:#?}", f);
	/*
	Ok(
		File {
			handle: 0x00000000000000b4,
			path: "\\\\?\\C:\\Users\\22357\\Desktop\\Test\\rust-test\\test.txt",
		},
	)
	 */
}

TIP

有关更多详细信息,请参见 OpenOptions::open 方法。

如果您只需要读取整个文件内容,请考虑使用 std::fs::read()std::fs::read_to_string()

如果 path 还不存在,则此函数将返回错误。 根据 OpenOptions::open,可能还会返回其他错误。

create

只写模式打开文件。

如果该文件不存在,则此函数将创建一个文件,如果存在则将覆盖该文件。

rust
pub fn create<P: AsRef<Path>>(path: P) -> Result<File>

参数

  • path:创建文件的路径,若不指定盘符,则在项目根目录下创建

返回值:返回一个Result,若创建成功,则返回Ok(File),否则返回相应错误信息

rust
use std::fs::File;
fn main() {
    let f = File::create("test.txt");
    println!("{:#?}", f);
	/*
		Ok(
			File {
				handle: 0x00000000000000b4,
				path: "\\\\?\\C:\\Users\\22357\\Desktop\\Test\\rust-test\\test.txt",
			},
		)
	 */
}

TIP

根据平台,如果完整目录路径不存在,此函数可能会失败。 有关更多详细信息,请参见 OpenOptions::open 函数。

另请参见 std::fs::write(),了解使用给定数据创建文件的简单函数。

create_new

读写模式创建一个新文件; 如果文件存在则出错。

如果文件不存在,此函数将创建一个文件,如果存在则返回错误。 这样,如果调用成功,则保证返回的文件是新的。

此选项很有用,因为它是原子的。 否则,在检查文件是否存在与创建新文件之间,文件可能是由另一个进程创建的 (TOCTOU 竞态条件 / 攻击)。

这也可以使用 File::options().read(true).write(true).create_new(true).open(...) 编写。

rust
pub fn create_new<P: AsRef<Path>>(path: P) -> Result<File>

参数

  • path:创建文件的路径,若不指定盘符,则在项目根目录下创建

返回值:返回一个Result,若创建成功,则返回Ok(File),否则返回相应错误信息

rust
use std::fs::File;
fn main() {
    let f = File::create_new("test111.txt");
    println!("{:#?}", f);
	/*
		Ok(
			File {
				handle: 0x00000000000000b4,
				path: "\\\\?\\C:\\Users\\22357\\Desktop\\Test\\rust-test\\test.txt",
			},
		)
	 */
}

options

返回一个新的 OpenOptions 对象。

如果不适合使用 open()create(),则此函数返回一个新的 OpenOptions 对象,可用于打开或创建具有特定选项的文件。

rust
pub fn options() -> OpenOptions

返回值:返回一个新的 OpenOptions 对象。

rust
use std::fs::File;
fn main() {
    let f = File::options();
    println!("{:#?}", f);
	/*
		OpenOptions(
			OpenOptions {
				read: false,
				write: false,
				append: false,
				truncate: false,
				create: false,
				create_new: false,
				custom_flags: 0,
				access_mode: None,
				attributes: 0,
				share_mode: 7,
				security_qos_flags: 0,
				inherit_handle: false,
			},
		)
	 */
}

TIP

它相当于 OpenOptions::new(),但允许您编写更具可读性的代码。 您可以写 File::options().append(true).open("example.log"),而不是 OpenOptions::new().append(true).open("example.log")。 这也避免了导入 OpenOptions 的需要。

有关更多详细信息,请参见 OpenOptions::new 函数。

sync_all

尝试将所有操作系统内部元数据同步到磁盘。

确保:

  • 文件内容已写入磁盘
  • 文件的元数据(大小、时间戳等)也已写入

此函数将尝试确保所有内存数据在返回之前都已到达文件系统。

这可用于处理错误,否则这些错误仅在 File 关闭时才会被捕获。 丢弃文件将忽略同步此内存中数据的错误。

rust
pub fn sync_all(&self) -> Result<()>

返回值:返回一个Result,若保存成功,则返回Ok(()),否则返回相应错误信息

rust
use std::fs::File;
use std::io::Write;
fn main() -> std::io::Result<()> {
    let mut f = File::create("test.txt").unwrap();
    f.write_all(b"hello")?;
    f.sync_all()?;
	Ok(())
}

sync_data

强制将文件的数据(data)刷新到磁盘

不保证文件元数据(metadata)一定已经落盘

rust
pub fn sync_data(&self) -> Result<()>

返回值:返回一个Result,若保存成功,则返回Ok(()),否则返回相应错误信息

rust
use std::fs::File;
use std::io::{self, Write};

let mut file = File::create("foo.txt")?;
file.write_all(b"hello")?;
file.sync_data()?;

set_len

截断或扩展底层文件,将此文件的大小更新为 size

如果 size 小于当前文件的大小,则文件将被缩小。 如果它大于当前文件的大小,则文件将扩展到 size,并且所有中间数据都用 0 填充。

文件的游标未更改。特别是,如果游标位于末尾,并且使用此操作将文件缩小了,那么游标现在将超过末尾。

rust
pub fn set_len(&self, size: u64) -> Result<()>

参数

  • size:需要截断的长度大小

返回值:返回一个Result,若操作成功,则返回Ok(()),否则返回相应错误信息

rust
use std::fs::File;
fn main() {
    let f = File::create("test.txt").unwrap();
    let res = f.set_len(1024 * 1024 * 1);
    println!("{:#?}", res);
}

image-20260501101106390

metadata

查询有关底层文件的元数据

rust
pub fn metadata(&self) -> Result<Metadata>

返回值:返回一个Result,若查询成功,则返回Ok(Metadata),否则返回相应错误信息

rust
use std::fs::File;
fn main() {
    let f = File::create("test.txt").unwrap();
    let res = f.metadata();
    println!("{:#?}", res);
	/*
        Ok(
            Metadata {
                file_type: FileType {
                    is_file: true,
                    is_dir: false,
                    is_symlink: false,
                    ..
                },
                permissions: Permissions(
                    FilePermissions {
                        attrs: 32,
                    },
                ),
                len: 0,
                modified: SystemTime {
                    intervals: 134220751357665475,
                },
                accessed: SystemTime {
                    intervals: 134220751357665475,
                },
                created: SystemTime {
                    intervals: 134220723382471503,
                },
                ..
            },
        )
	 */
}

try_clone

创建一个新的 File 实例,该实例与现有 File 实例共享相同的底层文件句柄。 读取,写入和查找将同时影响两个 File 实例

rust
pub fn try_clone(&self) -> Result<File>

返回值:返回一个Result,若创建成功,则返回Ok(File),否则返回相应错误信息

现有一个空的test.txt

image-20260501101802397

rust
use std::{fs::File, io::Write};
fn main() {
    let f = File::create("test.txt").unwrap();
    let mut copy_f = f.try_clone().unwrap();		// 创建一个拷贝

    let res = copy_f.write(b"hello");			// 修改拷贝的文件内容
    println!("{:#?}", res);
}

源文件一样会被修改

image-20260501101844402

set_permissions

更改底层文件的权限

rust
pub fn set_permissions(&self, perm: Permissions) -> Result<()>

参数

  • perm:一个权限对象Permissions

返回值:返回一个Result,若修改成功,则返回Ok(File),否则返回相应错误信息

rust
fn main() -> std::io::Result<()> {
    use std::fs::File;

    let file = File::open("foo.txt")?;
    let mut perms = file.metadata()?.permissions();  // 先从metadata中获取权限对象
    perms.set_readonly(true);	// 修改权限
    file.set_permissions(perms)?;		// 设置权限
    Ok(())
}

set_times

更改底层文件的时间戳。

rust
pub fn set_times(&self, times: FileTimes) -> Result<()>

参数

  • times:一个FileTimes对象

返回值:返回一个Result,若修改成功,则返回Ok(()),否则返回相应错误信息

rust
use std::fs::{File, FileTimes};
use std::os::windows::fs::FileTimesExt;
use std::time::SystemTime;
fn main() {
    // 先使用options以读写方式打开文件
    let file = File::options()
        .read(true)
        .write(true) //
        .open("test.txt")
        .unwrap();

    // 然后修改文件的访问时间、修改时间、创建时间
    let times = FileTimes::new()
        .set_accessed(SystemTime::now())
        .set_modified(SystemTime::now())
        .set_created(SystemTime::now());

    let res = file.set_times(times);
    println!("{:#?}", res);
}

修改前的属性

image-20260501105145882

修改后的属性

image-20260501105219836

TIP

如果用户没有更改底层文件时间戳的权限,此函数将返回错误。 在其他特定于操作系统的未指定情况下,它也可能返回错误。

如果操作系统不支持更改 FileTimes 结构体中设置的一个或多个时间戳,则此函数可能会返回错误。

set_modified

更改底层文件的修改时间

这是 set_times(FileTimes::new().set_modified(time)) 的别名。

rust
pub fn set_modified(&self, time: SystemTime) -> Result<()>

参数

  • times:一个SystemTime时间对象

返回值:返回一个Result,若修改成功,则返回Ok(()),否则返回相应错误信息

rust
use std::fs::{File, FileTimes};
use std::os::windows::fs::FileTimesExt;
use std::time::SystemTime;
fn main() {
    // 先使用options以读写方式打开文件
    let file = File::options()
        .read(true)
        .write(true) //
        .open("test.txt")
        .unwrap();

    let res = file.set_modified(SystemTime::now());
    println!("{:#?}", res);
}

修改前的属性

image-20260501105558363

修改后的属性

image-20260501105622912

2.Trait 实现

impl AsFd for FIle

as_fd

借用文件描述符

impl AsHandle for File

只在windows上可用

as_handle

借用句柄。

rust
fn as_handle(&self) -> BorrowedHandle<'_>

impl AsRawFd for File

提取原始文件描述符。

rust
fn as_raw_fd(&self) -> RawFd

impl AsRawHandle for File

提取原始句柄。

rust
fn as_raw_handle(&self) -> RawHandle

impl Debug for File

使用给定的格式化程序格式化该值。

rust
fn fmt(&self, f: &mut Formatter<'_>) -> Result

impl FileExt for File

TIP

只可在WASI上运行

read_vectored_at

从给定的偏移量开始读取多个字节。

rust
fn read_vectored_at(
    &self,
    bufs: &mut [IoSliceMut<'_>],
    offset: u64
) -> Result<usize>

write_vectored_at

从给定的偏移量开始写入多个字节。

rust
fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> Result<usize>

tell

返回文件中的当前位置。

rust
fn tell(&self) -> Result<u64>

fdstat_set_flags

调整与此文件关联的标志。

rust
fn fdstat_set_flags(&self, flags: u16) -> Result<()>

fdstat_set_rights

调整与此文件关联的权限。

rust
fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> Result<()>

advise

提供有关文件描述符的文件咨询信息。

rust
fn advise(&self, offset: u64, len: u64, advice: u8) -> Result<()>

allocate

强制在文件中分配空间。

rust
fn allocate(&self, offset: u64, len: u64) -> Result<()>

create_directory

创建一个目录。

rust
fn create_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()>

读取符号链接的内容。

rust
fn read_link<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf>

metadata_at

返回文件或目录的属性。

rust
fn metadata_at<P: AsRef<Path>>(
    &self,
    lookup_flags: u32,
    path: P
) -> Result<Metadata>

remove_file

取消链接文件。

rust
fn remove_file<P: AsRef<Path>>(&self, path: P) -> Result<()>

remove_directory

删除目录。

rust
fn remove_directory<P: AsRef<Path>>(&self, path: P) -> Result<()>

read_at

从给定的偏移量开始读取多个字节。

rust
fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize>

read_exact_at

从给定的偏移量读取填充 buf 所需的确切字节数。

rust
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<()>

write_at

从给定的偏移量开始写入多个字节。

rust
fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize>

write_all_at

尝试从给定的偏移量开始写入整个缓冲区。

rust
fn write_all_at(&self, buf: &[u8], offset: u64) -> Result<()>

impl FileExt for File

TIP

仅在Windows上可用

seek_read

搜寻到给定位置并读取多个字节。

rust
fn seek_read(&self, buf: &mut [u8], offset: u64) -> Result<usize>

seek_write

搜寻到给定位置并写入多个字节。

rust
fn seek_write(&self, buf: &[u8], offset: u64) -> Result<usize>

impl FileExt for File

TIP

只可在Unix上运行

read_at

从给定的偏移量开始读取多个字节。

rust
fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize>

read_vectored_at

与 read_at 类似,只是它读入一片缓冲区。

rust
fn read_vectored_at(
    &self,
    bufs: &mut [IoSliceMut<'_>],
    offset: u64
) -> Result<usize>

write_at

从给定的偏移量开始写入多个字节。

rust
fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize>

write_vectored_at

write_at 类似,只是它从缓冲区的一片中写入。

rust
fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> Result<usize>

read_exact_at

rust
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<()>

从给定的偏移量读取填充 buf 所需的确切字节数。

write_all_at

rust
fn write_all_at(&self, buf: &[u8], offset: u64) -> Result<()>

尝试从给定的偏移量开始写入整个缓冲区。

impl From<File> for OwnedFd

from

从输入类型转换为此类型。

rust
fn from(file: File) -> OwnedFd

impl From<File> for OwnedHandle

TIP

仅在Windows上可用

from

从输入类型转换为此类型。

rust
fn from(file: File) -> OwnedHandle

impl From<File> for Stdio

from

将 File 转换为 Stdio。

rust
fn from(file: File) -> Stdio

Examples File 将在引擎盖下使用 Stdio::from 转换为 Stdio。

rust
use std::fs::File;
use std::process::Command;

// 使用包含 "Hello, world!" 的 `foo.txt` 文件
let file = File::open("foo.txt").unwrap();

let reverse = Command::new("rev")
    .stdin(file)  // 隐式文件转换为 Stdio
    .output()
    .expect("failed reverse command");

assert_eq!(reverse.stdout, b"!dlrow ,olleH");

impl From<OwnedFd> for File

from

从输入类型转换为此类型。

rust
fn from(owned_fd: OwnedFd) -> Self

impl From<OwnedHandle> for File

TIP

仅在Windows上可用

from

从输入类型转换为此类型。

rust
fn from(owned: OwnedHandle) -> Self

impl FromRawFd for File

from_raw_fd

根据给定的原始文件描述符构造 Self 的新实例。

rust
unsafe fn from_raw_fd(fd: RawFd) -> File

impl FromRawHandle for File

TIP

仅在Windows上可用

from_raw_handle

从指定的原始句柄创建一个新的 I/O 对象。

rust
unsafe fn from_raw_handle(handle: RawHandle) -> File

impl IntoRawFd for File

into_raw_fd

消费这个对象,返回原始的底层文件描述符。

rust
fn into_raw_fd(self) -> RawFd

impl IntoRawHandle for File

TIP

仅在Windows上可用

into_raw_handle

消耗此对象,返回原始底层句柄。

rust
fn into_raw_handle(self) -> RawHandle

impl IsTerminal for File

is_terminal

如果 descriptor/handle 引用 terminal/tty,则返回 true。

rust
fn is_terminal(&self) -> bool

impl Read for &File

read

从该源中提取一些字节到指定的缓冲区中,返回读取的字节数。

rust
fn read(&mut self, buf: &mut [u8]) -> Result<usize>

read_buf

从此源中提取一些字节到指定的缓冲区中。

rust
fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> Result<()>

read_vectored

与 read 相似,不同之处在于它读入缓冲区的一部分。

rust
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize>

is_read_vectored

确定此 Read 是否具有有效的 read_vectored 实现。

rust
fn is_read_vectored(&self) -> bool

read_to_end

读取所有字节,直到此源中的 EOF 为止,然后将它们放入 buf。

rust
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize>

read_to_string

读取这个源中的所有字节,直到 EOF 为止,然后将它们追加到 buf。

rust
fn read_to_string(&mut self, buf: &mut String) -> Result<usize>

read_exact

读取填充 buf 所需的确切字节数。

rust
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()>

read_buf_exact

读取填充 cursor 所需的确切字节数。

rust
fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()>

by_ref

为这个 Read 实例创建一个 “by reference” 适配器。

rust
fn by_ref(&mut self) -> &mut Self
where
    Self: Sized,

bytes

将此 Read 实例的字节数转换为 Iterator。

rust
fn bytes(self) -> Bytes<Self> ⓘ
where
    Self: Sized,

chain

创建一个适配器,将这个流与另一个链接起来。

rust
fn chain<R: Read>(self, next: R) -> Chain<Self, R> ⓘ
where
    Self: Sized,

take

创建一个适配器,最多从中读取 limit 个字节。

rust
fn take(self, limit: u64) -> Take<Self> ⓘ
where
    Self: Sized,

impl Read for File

read

从该源中提取一些字节到指定的缓冲区中,返回读取的字节数。

rust
fn read(&mut self, buf: &mut [u8]) -> Result<usize>

read_vectored

与 read 相似,不同之处在于它读入缓冲区的一部分。

rust
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize>

read_buf

从此源中提取一些字节到指定的缓冲区中。

rust
fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> Result<()>

is_read_vectored

确定此 Read 是否具有有效的 read_vectored 实现。

rust
fn is_read_vectored(&self) -> bool

read_to_end

读取所有字节,直到此源中的 EOF 为止,然后将它们放入 buf。

rust

fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize>

read_to_string

读取这个源中的所有字节,直到 EOF 为止,然后将它们追加到 buf。

rust

fn read_to_string(&mut self, buf: &mut String) -> Result<usize>

read_exact

读取填充 buf 所需的确切字节数。

rust
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()>

read_buf_exact

读取填充 cursor 所需的确切字节数。

rust
fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()>

by_ref

为这个 Read 实例创建一个 “by reference” 适配器。

rust
fn by_ref(&mut self) -> &mut Self
where
    Self: Sized,

bytes

将此 Read 实例的字节数转换为 Iterator。

rust
fn bytes(self) -> Bytes<Self> ⓘ
where
    Self: Sized,

chain

创建一个适配器,将这个流与另一个链接起来。

rust
fn chain<R: Read>(self, next: R) -> Chain<Self, R> 
where
    Self: Sized,

take

创建一个适配器,最多从中读取 limit 个字节。

rust
fn take(self, limit: u64) -> Take<Self> 
where
    Self: Sized,

impl Seek for &File

seek

在流中寻找以字节为单位的偏移量。

rust
fn seek(&mut self, pos: SeekFrom) -> Result<u64>

rewind

返回到流的开头。

rust
fn rewind(&mut self) -> Result<()>

stream_len

返回此流的长度 (以字节为单位)。

rust
fn stream_len(&mut self) -> Result<u64>

stream_position

从流的开头返回当前查找位置。

rust
fn stream_position(&mut self) -> Result<u64>

impl Seek for File

seek

在流中寻找以字节为单位的偏移量。

rust
fn seek(&mut self, pos: SeekFrom) -> Result<u64>

rewind

返回到流的开头。

rust
fn rewind(&mut self) -> Result<()>

stream_len

返回此流的长度 (以字节为单位)。

rust
fn stream_len(&mut self) -> Result<u64>

stream_position

从流的开头返回当前查找位置。

rust
fn stream_position(&mut self) -> Result<u64>

impl Write for &File

write

在此 writer 中写入一个缓冲区,返回写入的字节数。

rust
fn write(&mut self, buf: &[u8]) -> Result<usize>

write_vectored

类似于 write,不同之处在于它是从缓冲区切片中写入数据的。

rust
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize>

is_write_vectored

确定此 Writer 是否具有有效的 write_vectored 实现。

rust
fn is_write_vectored(&self) -> bool

flush

刷新此输出流,确保所有中间缓冲的内容均到达其目的地。

rust
fn flush(&mut self) -> Result<()>

write_all

尝试将整个缓冲区写入此 writer。

rust
fn write_all(&mut self, buf: &[u8]) -> Result<()>

write_all_vectored

尝试将多个缓冲区写入此 writer。

rust
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()>

write_fmt

将格式化的字符串写入此 writer,返回遇到的任何错误。

rust
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()>

by_ref

为这个 Write 实例创建一个 “by reference” 适配器。

rust
fn by_ref(&mut self) -> &mut Self
where
    Self: Sized,

impl Write for File

write

在此 writer 中写入一个缓冲区,返回写入的字节数。

rust
fn write(&mut self, buf: &[u8]) -> Result<usize>

write_vectored

类似于 write,不同之处在于它是从缓冲区切片中写入数据的。

rust
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize>

is_write_vectored

确定此 Writer 是否具有有效的 write_vectored 实现。

rust
fn is_write_vectored(&self) -> bool

flush

刷新此输出流,确保所有中间缓冲的内容均到达其目的地。

rust
fn flush(&mut self) -> Result<()>

write_all

尝试将整个缓冲区写入此 writer。

rust
fn write_all(&mut self, buf: &[u8]) -> Result<()>

write_all_vectored

尝试将多个缓冲区写入此 writer。

rust
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()>

write_fmt

将格式化的字符串写入此 writer,返回遇到的任何错误。

rust
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()>

by_ref

为这个 Write 实例创建一个 “by reference” 适配器。

rust
fn by_ref(&mut self) -> &mut Self
where
    Self: Sized,

Auto Trait Implementations

impl RefUnwindSafe for File

impl Send for File

impl Sync for File

impl Unpin for File

impl UnwindSafe for File

Blanket Implementations

impl<T> Any for T

rust
impl<T> Any for T
where
    T: 'static + ?Sized,

impl<T> Borrow<T> for T

rust
impl<T> Borrow<T> for T
where
    T: ?Sized,

impl<T> BorrowMut<T> for T

rust
impl<T> BorrowMut<T> for T
where
    T: ?Sized,

impl<T> From<T> for T

rust
impl<T> From<T> for T

impl<T, U> Into<U> for T

rust
impl<T, U> Into<U> for T
where
    U: From<T>,

impl<T, U> TryFrom<U> for T

rust
impl<T, U> TryFrom<U> for T
where
    U: Into<T>,

impl<T, U> TryInto<U> for T

rust
impl<T, U> TryInto<U> for T
where
    U: TryFrom<T>,

MIT Licensed