diff --git a/3ds.json b/3ds.json index 3255f2a..fbdf65d 100644 --- a/3ds.json +++ b/3ds.json @@ -1,30 +1,32 @@ { "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", "llvm-target": "arm-none-eabihf", - "linker": "arm-none-eabi-g++", + "linker": "arm-none-eabi-gcc", "ar": "arm-none-eabi-ar", "target-endian": "little", "target-pointer-width": "32", "arch": "arm", - "os": "none", + "os": "linux", "cpu": "mpcore", "features": "+vfp2", "relocation-model": "static", - "linker-is-gnu": true, - "has-rpath": true, - "morestack": false, "disable-redzone": true, "executables": true, - "dynamic-linking": false, "no-compiler-rt": true, "exe-suffix": ".elf", - "is-like-windows": true, - "function-sections": false, "pre-link-args": [ - "-specs", - "3dsx.specs", + "-specs=3dsx.specs", "-march=armv6k", "-mtune=mpcore", - "-mfloat-abi=hard" + "-mfloat-abi=hard", + "-mtp=soft" + ], + "post-link-args": [ + "-lc", + "-lm", + "-lsysbase", + "-lc", + "-lgcc", + "-lc" ] } diff --git a/Cargo.toml b/Cargo.toml index 0aa1ecf..f95ca35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ version = "0.4.0" [dependencies.ctru-sys] path = "ctru-sys" +[dependencies.alloc_system3ds] +git = "https://github.com/rust3ds/alloc_system3ds" + [lib] crate-type = ["rlib"] name = "ctru" diff --git a/README.md b/README.md index 6eb5565..75bf43a 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,7 @@ applies to every file in the tree, unless otherwise noted. must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. + +Rust is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0), with portions covered by various BSD-like licenses. + +See [LICENSE-APACHE](https://github.com/rust-lang/rust/blob/master/LICENSE-APACHE), [LICENSE-MIT](https://github.com/rust-lang/rust/blob/master/LICENSE-MIT), and [COPYRIGHT](https://github.com/rust-lang/rust/blob/master/COPYRIGHT) for details. diff --git a/ctru-sys/src/services/fs.rs b/ctru-sys/src/services/fs.rs index 0608bb9..421bbcb 100644 --- a/ctru-sys/src/services/fs.rs +++ b/ctru-sys/src/services/fs.rs @@ -1,44 +1,45 @@ -// TODO: Determine if anonymous enums are properly represented (they probably aren't) +/* automatically generated by rust-bindgen */ + +#![allow(dead_code, + non_camel_case_types, + non_upper_case_globals, + non_snake_case)] use ::{Handle, Result}; -use ::libc::c_void; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed1 { - FS_OPEN_READ = 1, - FS_OPEN_WRITE = 2, - FS_OPEN_CREATE = 4, -} +pub const FS_OPEN_READ: u32 = 1; +pub const FS_OPEN_WRITE: u32 = 2; +pub const FS_OPEN_CREATE: u32 = 4; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed2 { - FS_WRITE_FLUSH = 1, - FS_WRITE_UPDATE_TIME = 256, -} +pub const FS_WRITE_FLUSH: u32 = 1; +pub const FS_WRITE_UPDATE_TIME: u32 = 256; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed3 { - FS_ATTRIBUTE_DIRECTORY = 1, - FS_ATTRIBUTE_HIDDEN = 256, - FS_ATTRIBUTE_ARCHIVE = 65536, - FS_ATTRIBUTE_READ_ONLY = 16777216, -} +pub const FS_ATTRIBUTE_DIRECTORY: u32 = 1; +pub const FS_ATTRIBUTE_HIDDEN: u32 = 256; +pub const FS_ATTRIBUTE_ARCHIVE: u32 = 65536; +pub const FS_ATTRIBUTE_READ_ONLY: u32 = 16777216; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed4 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_MediaType { MEDIATYPE_NAND = 0, MEDIATYPE_SD = 1, MEDIATYPE_GAME_CARD = 2, } - -pub type FS_MediaType = Enum_Unnamed4; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed5 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SystemMediaType { + SYSTEM_MEDIATYPE_CTR_NAND = 0, + SYSTEM_MEDIATYPE_TWL_NAND = 1, + SYSTEM_MEDIATYPE_SD = 2, + SYSTEM_MEDIATYPE_TWL_PHOTO = 3, +} +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_ArchiveID { ARCHIVE_ROMFS = 3, ARCHIVE_SAVEDATA = 4, ARCHIVE_EXTDATA = 6, @@ -57,33 +58,31 @@ pub enum Enum_Unnamed5 { ARCHIVE_SAVEDATA_AND_CONTENT2 = 591751054, ARCHIVE_NAND_CTR_FS = 1450741931, ARCHIVE_TWL_PHOTO = 1450741932, + ARCHIVE_TWL_SOUND = 1450741933, ARCHIVE_NAND_TWL_FS = 1450741934, ARCHIVE_NAND_W_FS = 1450741935, ARCHIVE_GAMECARD_SAVEDATA = 1450741937, ARCHIVE_USER_SAVEDATA = 1450741938, ARCHIVE_DEMO_SAVEDATA = 1450741940, } -pub type FS_ArchiveID = Enum_Unnamed5; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed6 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_PathType { PATH_INVALID = 0, PATH_EMPTY = 1, PATH_BINARY = 2, PATH_ASCII = 3, PATH_UTF16 = 4, } -pub type FS_PathType = Enum_Unnamed6; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed7 { SECUREVALUE_SLOT_SD = 4096, } -pub type FS_SecureValueSlot = Enum_Unnamed7; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed8 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SecureValueSlot { SECUREVALUE_SLOT_SD = 4096, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_CardSpiBaudRate { BAUDRATE_512KHZ = 0, BAUDRATE_1MHZ = 1, BAUDRATE_2MHZ = 2, @@ -91,64 +90,51 @@ pub enum Enum_Unnamed8 { BAUDRATE_8MHZ = 4, BAUDRATE_16MHZ = 5, } -pub type FS_CardSpiBaudRate = Enum_Unnamed8; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed9 { - BUSMODE_1BIT = 0, - BUSMODE_4BIT = 1, -} -pub type FS_CardSpiBusMode = Enum_Unnamed9; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed10 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_CardSpiBusMode { BUSMODE_1BIT = 0, BUSMODE_4BIT = 1, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SpecialContentType { SPECIALCONTENT_UPDATE = 1, SPECIALCONTENT_MANUAL = 2, SPECIALCONTENT_DLP_CHILD = 3, } -pub type FS_SpecialContentType = Enum_Unnamed10; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed11 { CARD_CTR = 0, CARD_TWL = 1, } -pub type FS_CardType = Enum_Unnamed11; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed12 { FS_ACTION_UNKNOWN = 0, } -pub type FS_Action = Enum_Unnamed12; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed13 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_CardType { CARD_CTR = 0, CARD_TWL = 1, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_Action { FS_ACTION_UNKNOWN = 0, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_ArchiveAction { ARCHIVE_ACTION_COMMIT_SAVE_DATA = 0, ARCHIVE_ACTION_GET_TIMESTAMP = 1, } -pub type FS_ArchiveAction = Enum_Unnamed13; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed14 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SecureSaveAction { SECURESAVE_ACTION_DELETE = 0, SECURESAVE_ACTION_FORMAT = 1, } -pub type FS_SecureSaveAction = Enum_Unnamed14; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed15 { FILE_ACTION_UNKNOWN = 0, } -pub type FS_FileAction = Enum_Unnamed15; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed16 { DIRECTORY_ACTION_UNKNOWN = 0, } -pub type FS_DirectoryAction = Enum_Unnamed16; - +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_FileAction { FILE_ACTION_UNKNOWN = 0, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_DirectoryAction { DIRECTORY_ACTION_UNKNOWN = 0, } #[repr(C)] #[derive(Copy)] -pub struct Struct_Unnamed17 { +pub struct FS_DirectoryEntry { pub name: [u16; 262usize], pub shortName: [u8; 10usize], pub shortExt: [u8; 4usize], @@ -157,169 +143,175 @@ pub struct Struct_Unnamed17 { pub attributes: u32, pub fileSize: u64, } -impl ::core::clone::Clone for Struct_Unnamed17 { +impl ::core::clone::Clone for FS_DirectoryEntry { fn clone(&self) -> Self { *self } } -impl ::core::default::Default for Struct_Unnamed17 { +impl ::core::default::Default for FS_DirectoryEntry { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_DirectoryEntry = Struct_Unnamed17; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed18 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ArchiveResource { pub sectorSize: u32, pub clusterSize: u32, pub totalClusters: u32, pub freeClusters: u32, } -impl ::core::clone::Clone for Struct_Unnamed18 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed18 { +impl ::core::default::Default for FS_ArchiveResource { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ArchiveResource = Struct_Unnamed18; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed19 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ProgramInfo { pub programId: u64, pub _bindgen_bitfield_1_: FS_MediaType, pub padding: [u8; 7usize], } -impl ::core::clone::Clone for Struct_Unnamed19 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed19 { +impl ::core::default::Default for FS_ProgramInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ProgramInfo = Struct_Unnamed19; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed20 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ProductInfo { pub productCode: [u8; 16usize], pub companyCode: [u8; 2usize], pub remasterVersion: u16, } -impl ::core::clone::Clone for Struct_Unnamed20 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed20 { +impl ::core::default::Default for FS_ProductInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ProductInfo = Struct_Unnamed20; - #[repr(C)] #[derive(Copy)] -pub struct Struct_Unnamed21 { +pub struct FS_IntegrityVerificationSeed { pub aesCbcMac: [u8; 16usize], pub movableSed: [u8; 288usize], } -impl ::core::clone::Clone for Struct_Unnamed21 { +impl ::core::clone::Clone for FS_IntegrityVerificationSeed { fn clone(&self) -> Self { *self } } -impl ::core::default::Default for Struct_Unnamed21 { +impl ::core::default::Default for FS_IntegrityVerificationSeed { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_IntegrityVerificationSeed = Struct_Unnamed21; - #[repr(C, packed)] -#[derive(Copy)] -pub struct Struct_Unnamed22 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ExtSaveDataInfo { pub _bindgen_bitfield_1_: FS_MediaType, pub unknown: u8, pub reserved1: u16, pub saveId: u64, pub reserved2: u32, } -impl ::core::clone::Clone for Struct_Unnamed22 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed22 { +impl ::core::default::Default for FS_ExtSaveDataInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ExtSaveDataInfo = Struct_Unnamed22; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed23 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_SystemSaveDataInfo { pub _bindgen_bitfield_1_: FS_MediaType, pub unknown: u8, pub reserved: u16, pub saveId: u32, } -impl ::core::clone::Clone for Struct_Unnamed23 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed23 { +impl ::core::default::Default for FS_SystemSaveDataInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_SystemSaveDataInfo = Struct_Unnamed23; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed24 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_DeviceMoveContext { pub ivs: [u8; 16usize], pub encryptParameter: [u8; 16usize], } -impl ::core::clone::Clone for Struct_Unnamed24 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed24 { +impl ::core::default::Default for FS_DeviceMoveContext { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_DeviceMoveContext = Struct_Unnamed24; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed25 { - pub _type: FS_PathType, +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_Path { + pub type_: FS_PathType, pub size: u32, - pub data: *const c_void, + pub data: *const ::libc::c_void, } -impl ::core::clone::Clone for Struct_Unnamed25 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed25 { +impl ::core::default::Default for FS_Path { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_Path = Struct_Unnamed25; - +pub type FS_Archive = u64; #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed26 { - pub id: u32, - pub lowPath: FS_Path, - pub handle: u64, +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct romfs_header { + pub headerSize: u32, + pub dirHashTableOff: u32, + pub dirHashTableSize: u32, + pub dirTableOff: u32, + pub dirTableSize: u32, + pub fileHashTableOff: u32, + pub fileHashTableSize: u32, + pub fileTableOff: u32, + pub fileTableSize: u32, + pub fileDataOff: u32, +} +impl ::core::default::Default for romfs_header { + fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -impl ::core::clone::Clone for Struct_Unnamed26 { - fn clone(&self) -> Self { *self } +#[repr(C)] +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct romfs_dir { + pub parent: u32, + pub sibling: u32, + pub childDir: u32, + pub childFile: u32, + pub nextHash: u32, + pub nameLen: u32, + pub name: [u16; 0usize], +} +impl ::core::default::Default for romfs_dir { + fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -impl ::core::default::Default for Struct_Unnamed26 { +#[repr(C)] +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct romfs_file { + pub parent: u32, + pub sibling: u32, + pub dataOff: u64, + pub dataSize: u64, + pub nextHash: u32, + pub nameLen: u32, + pub name: [u16; 0usize], +} +impl ::core::default::Default for romfs_file { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_Archive = Struct_Unnamed26; +pub enum romfs_mount { } extern "C" { pub fn fsInit() -> Result; pub fn fsExit(); - pub fn fsUseSession(session: Handle, sdmc: u8); + pub fn fsUseSession(session: Handle); pub fn fsEndUseSession(); - pub fn fsMakePath(_type: FS_PathType, path: *const c_void) + pub fn fsExemptFromSession(archive: FS_Archive); + pub fn fsUnexemptFromSession(archive: FS_Archive); + pub fn fsMakePath(type_: FS_PathType, path: *const ::libc::c_void) -> FS_Path; pub fn fsGetSessionHandle() -> *mut Handle; - pub fn FSUSER_Control(action: FS_Action, - input: *mut c_void, inputSize: u32, - output: *mut c_void, + pub fn FSUSER_Control(action: FS_Action, input: *mut ::libc::c_void, + inputSize: u32, output: *mut ::libc::c_void, outputSize: u32) -> Result; pub fn FSUSER_Initialize(session: Handle) -> Result; pub fn FSUSER_OpenFile(out: *mut Handle, archive: FS_Archive, path: FS_Path, openFlags: u32, attributes: u32) -> Result; - pub fn FSUSER_OpenFileDirectly(out: *mut Handle, archive: FS_Archive, - path: FS_Path, openFlags: u32, - attributes: u32) -> Result; + pub fn FSUSER_OpenFileDirectly(out: *mut Handle, archiveId: FS_ArchiveID, + archivePath: FS_Path, filePath: FS_Path, + openFlags: u32, attributes: u32) + -> Result; pub fn FSUSER_DeleteFile(archive: FS_Archive, path: FS_Path) -> Result; pub fn FSUSER_RenameFile(srcArchive: FS_Archive, srcPath: FS_Path, dstArchive: FS_Archive, dstPath: FS_Path) @@ -337,17 +329,17 @@ extern "C" { -> Result; pub fn FSUSER_OpenDirectory(out: *mut Handle, archive: FS_Archive, path: FS_Path) -> Result; - pub fn FSUSER_OpenArchive(archive: *mut FS_Archive) -> Result; + pub fn FSUSER_OpenArchive(archive: *mut FS_Archive, id: FS_ArchiveID, + path: FS_Path) -> Result; pub fn FSUSER_ControlArchive(archive: FS_Archive, action: FS_ArchiveAction, - input: *mut c_void, - inputSize: u32, - output: *mut c_void, + input: *mut ::libc::c_void, inputSize: u32, + output: *mut ::libc::c_void, outputSize: u32) -> Result; - pub fn FSUSER_CloseArchive(archive: *mut FS_Archive) -> Result; + pub fn FSUSER_CloseArchive(archive: FS_Archive) -> Result; pub fn FSUSER_GetFreeBytes(freeBytes: *mut u64, archive: FS_Archive) -> Result; - pub fn FSUSER_GetCardType(_type: *mut FS_CardType) -> Result; + pub fn FSUSER_GetCardType(type_: *mut FS_CardType) -> Result; pub fn FSUSER_GetSdmcArchiveResource(archiveResource: *mut FS_ArchiveResource) -> Result; @@ -401,12 +393,13 @@ extern "C" { pub fn FSUSER_GetSpecialContentIndex(index: *mut u16, mediaType: FS_MediaType, programId: u64, - _type: FS_SpecialContentType) + type_: FS_SpecialContentType) -> Result; pub fn FSUSER_GetLegacyRomHeader(mediaType: FS_MediaType, programId: u64, header: *mut u8) -> Result; - pub fn FSUSER_GetLegacyBannerData(mediaType: FS_MediaType, programId: u64, - banner: *mut u8) -> Result; + pub fn FSUSER_GetLegacyBannerData(mediaType: FS_MediaType, + programId: u64, banner: *mut u8) + -> Result; pub fn FSUSER_CheckAuthorityToAccessExtSaveData(access: *mut u8, mediaType: FS_MediaType, saveId: u64, @@ -425,11 +418,12 @@ extern "C" { archiveId: FS_ArchiveID, path: FS_Path) -> Result; pub fn FSUSER_GetLegacyRomHeader2(headerSize: u32, - mediaType: FS_MediaType, programId: u64, - header: *mut u8) -> Result; + mediaType: FS_MediaType, + programId: u64, header: *mut u8) + -> Result; pub fn FSUSER_GetSdmcCtrRootPath(out: *mut u8, length: u32) -> Result; pub fn FSUSER_GetArchiveResource(archiveResource: *mut FS_ArchiveResource, - mediaType: FS_MediaType) -> Result; + mediaType: FS_SystemMediaType) -> Result; pub fn FSUSER_ExportIntegrityVerificationSeed(seed: *mut FS_IntegrityVerificationSeed) -> Result; @@ -482,13 +476,14 @@ extern "C" { emulateEndurance: u8) -> Result; pub fn FSUSER_SwitchCleanupInvalidSaveData(enable: u8) -> Result; pub fn FSUSER_EnumerateSystemSaveData(idsWritten: *mut u32, - idsSize: u32, ids: *mut u64) + idsSize: u32, ids: *mut u32) -> Result; pub fn FSUSER_InitializeWithSdkVersion(session: Handle, version: u32) -> Result; pub fn FSUSER_SetPriority(priority: u32) -> Result; pub fn FSUSER_GetPriority(priority: *mut u32) -> Result; - pub fn FSUSER_SetSaveDataSecureValue(value: u64, slot: FS_SecureValueSlot, + pub fn FSUSER_SetSaveDataSecureValue(value: u64, + slot: FS_SecureValueSlot, titleUniqueId: u32, titleVariation: u8) -> Result; pub fn FSUSER_GetSaveDataSecureValue(exists: *mut u8, value: *mut u64, @@ -496,22 +491,21 @@ extern "C" { titleUniqueId: u32, titleVariation: u8) -> Result; pub fn FSUSER_ControlSecureSave(action: FS_SecureSaveAction, - input: *mut c_void, + input: *mut ::libc::c_void, inputSize: u32, - output: *mut c_void, + output: *mut ::libc::c_void, outputSize: u32) -> Result; pub fn FSUSER_GetMediaType(mediaType: *mut FS_MediaType) -> Result; pub fn FSFILE_Control(handle: Handle, action: FS_FileAction, - input: *mut c_void, inputSize: u32, - output: *mut c_void, - outputSize: u32) -> Result; + input: *mut ::libc::c_void, inputSize: u32, + output: *mut ::libc::c_void, outputSize: u32) + -> Result; pub fn FSFILE_OpenSubFile(handle: Handle, subFile: *mut Handle, offset: u64, size: u64) -> Result; pub fn FSFILE_Read(handle: Handle, bytesRead: *mut u32, offset: u64, - buffer: *mut c_void, size: u32) - -> Result; + buffer: *mut ::libc::c_void, size: u32) -> Result; pub fn FSFILE_Write(handle: Handle, bytesWritten: *mut u32, offset: u64, - buffer: *const c_void, size: u32, + buffer: *const ::libc::c_void, size: u32, flags: u32) -> Result; pub fn FSFILE_GetSize(handle: Handle, size: *mut u64) -> Result; pub fn FSFILE_SetSize(handle: Handle, size: u64) -> Result; @@ -525,13 +519,18 @@ extern "C" { pub fn FSFILE_OpenLinkFile(handle: Handle, linkFile: *mut Handle) -> Result; pub fn FSDIR_Control(handle: Handle, action: FS_DirectoryAction, - input: *mut c_void, inputSize: u32, - output: *mut c_void, - outputSize: u32) -> Result; + input: *mut ::libc::c_void, inputSize: u32, + output: *mut ::libc::c_void, outputSize: u32) + -> Result; pub fn FSDIR_Read(handle: Handle, entriesRead: *mut u32, entryCount: u32, entries: *mut FS_DirectoryEntry) -> Result; pub fn FSDIR_Close(handle: Handle) -> Result; pub fn FSDIR_SetPriority(handle: Handle, priority: u32) -> Result; pub fn FSDIR_GetPriority(handle: Handle, priority: *mut u32) -> Result; + pub fn romfsMount(mount: *mut *mut romfs_mount) -> Result; + pub fn romfsMountFromFile(file: Handle, offset: u32, + mount: *mut *mut romfs_mount) -> Result; + pub fn romfsBind(mount: *mut romfs_mount) -> Result; + pub fn romfsUnmount(mount: *mut romfs_mount) -> Result; } diff --git a/src/ascii.rs b/src/ascii.rs new file mode 100644 index 0000000..93f447b --- /dev/null +++ b/src/ascii.rs @@ -0,0 +1,554 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Operations on ASCII strings and characters. + +use core::mem; +use core::ops::Range; +use collections::{String, Vec}; + +/// Extension methods for ASCII-subset only operations on string slices. +/// +/// Be aware that operations on seemingly non-ASCII characters can sometimes +/// have unexpected results. Consider this example: +/// +/// ``` +/// use std::ascii::AsciiExt; +/// +/// assert_eq!("café".to_ascii_uppercase(), "CAFÉ"); +/// assert_eq!("café".to_ascii_uppercase(), "CAFé"); +/// ``` +/// +/// In the first example, the lowercased string is represented `"cafe\u{301}"` +/// (the last character is an acute accent [combining character]). Unlike the +/// other characters in the string, the combining character will not get mapped +/// to an uppercase variant, resulting in `"CAFE\u{301}"`. In the second +/// example, the lowercased string is represented `"caf\u{e9}"` (the last +/// character is a single Unicode character representing an 'e' with an acute +/// accent). Since the last character is defined outside the scope of ASCII, +/// it will not get mapped to an uppercase variant, resulting in `"CAF\u{e9}"`. +/// +/// [combining character]: https://en.wikipedia.org/wiki/Combining_character +pub trait AsciiExt { + /// Container type for copied ASCII characters. + type Owned; + + /// Checks if the value is within the ASCII range. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii = 'a'; + /// let utf8 = '❤'; + /// + /// assert!(ascii.is_ascii()); + /// assert!(!utf8.is_ascii()); + /// ``` + fn is_ascii(&self) -> bool; + + /// Makes a copy of the string in ASCII upper case. + /// + /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', + /// but non-ASCII letters are unchanged. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii = 'a'; + /// let utf8 = '❤'; + /// + /// assert_eq!('A', ascii.to_ascii_uppercase()); + /// assert_eq!('❤', utf8.to_ascii_uppercase()); + /// ``` + fn to_ascii_uppercase(&self) -> Self::Owned; + + /// Makes a copy of the string in ASCII lower case. + /// + /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z', + /// but non-ASCII letters are unchanged. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii = 'A'; + /// let utf8 = '❤'; + /// + /// assert_eq!('a', ascii.to_ascii_lowercase()); + /// assert_eq!('❤', utf8.to_ascii_lowercase()); + /// ``` + fn to_ascii_lowercase(&self) -> Self::Owned; + + /// Checks that two strings are an ASCII case-insensitive match. + /// + /// Same as `to_ascii_lowercase(a) == to_ascii_lowercase(b)`, + /// but without allocating and copying temporary strings. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii1 = 'A'; + /// let ascii2 = 'a'; + /// let ascii3 = 'A'; + /// let ascii4 = 'z'; + /// + /// assert!(ascii1.eq_ignore_ascii_case(&ascii2)); + /// assert!(ascii1.eq_ignore_ascii_case(&ascii3)); + /// assert!(!ascii1.eq_ignore_ascii_case(&ascii4)); + /// ``` + fn eq_ignore_ascii_case(&self, other: &Self) -> bool; + + /// Converts this type to its ASCII upper case equivalent in-place. + /// + /// See `to_ascii_uppercase` for more information. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let mut ascii = 'a'; + /// + /// ascii.make_ascii_uppercase(); + /// + /// assert_eq!('A', ascii); + /// ``` + fn make_ascii_uppercase(&mut self); + + /// Converts this type to its ASCII lower case equivalent in-place. + /// + /// See `to_ascii_lowercase` for more information. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let mut ascii = 'A'; + /// + /// ascii.make_ascii_lowercase(); + /// + /// assert_eq!('a', ascii); + /// ``` + fn make_ascii_lowercase(&mut self); +} + +impl AsciiExt for str { + type Owned = String; + + #[inline] + fn is_ascii(&self) -> bool { + self.bytes().all(|b| b.is_ascii()) + } + + #[inline] + fn to_ascii_uppercase(&self) -> String { + let mut bytes = self.as_bytes().to_vec(); + bytes.make_ascii_uppercase(); + // make_ascii_uppercase() preserves the UTF-8 invariant. + unsafe { String::from_utf8_unchecked(bytes) } + } + + #[inline] + fn to_ascii_lowercase(&self) -> String { + let mut bytes = self.as_bytes().to_vec(); + bytes.make_ascii_lowercase(); + // make_ascii_uppercase() preserves the UTF-8 invariant. + unsafe { String::from_utf8_unchecked(bytes) } + } + + #[inline] + fn eq_ignore_ascii_case(&self, other: &str) -> bool { + self.as_bytes().eq_ignore_ascii_case(other.as_bytes()) + } + + fn make_ascii_uppercase(&mut self) { + let me: &mut [u8] = unsafe { mem::transmute(self) }; + me.make_ascii_uppercase() + } + + fn make_ascii_lowercase(&mut self) { + let me: &mut [u8] = unsafe { mem::transmute(self) }; + me.make_ascii_lowercase() + } +} + +impl AsciiExt for [u8] { + type Owned = Vec; + #[inline] + fn is_ascii(&self) -> bool { + self.iter().all(|b| b.is_ascii()) + } + + #[inline] + fn to_ascii_uppercase(&self) -> Vec { + let mut me = self.to_vec(); + me.make_ascii_uppercase(); + return me + } + + #[inline] + fn to_ascii_lowercase(&self) -> Vec { + let mut me = self.to_vec(); + me.make_ascii_lowercase(); + return me + } + + #[inline] + fn eq_ignore_ascii_case(&self, other: &[u8]) -> bool { + self.len() == other.len() && + self.iter().zip(other).all(|(a, b)| { + a.eq_ignore_ascii_case(b) + }) + } + + fn make_ascii_uppercase(&mut self) { + for byte in self { + byte.make_ascii_uppercase(); + } + } + + fn make_ascii_lowercase(&mut self) { + for byte in self { + byte.make_ascii_lowercase(); + } + } +} + +impl AsciiExt for u8 { + type Owned = u8; + #[inline] + fn is_ascii(&self) -> bool { *self & 128 == 0 } + #[inline] + fn to_ascii_uppercase(&self) -> u8 { ASCII_UPPERCASE_MAP[*self as usize] } + #[inline] + fn to_ascii_lowercase(&self) -> u8 { ASCII_LOWERCASE_MAP[*self as usize] } + #[inline] + fn eq_ignore_ascii_case(&self, other: &u8) -> bool { + self.to_ascii_lowercase() == other.to_ascii_lowercase() + } + #[inline] + fn make_ascii_uppercase(&mut self) { *self = self.to_ascii_uppercase(); } + #[inline] + fn make_ascii_lowercase(&mut self) { *self = self.to_ascii_lowercase(); } +} + +impl AsciiExt for char { + type Owned = char; + #[inline] + fn is_ascii(&self) -> bool { + *self as u32 <= 0x7F + } + + #[inline] + fn to_ascii_uppercase(&self) -> char { + if self.is_ascii() { + (*self as u8).to_ascii_uppercase() as char + } else { + *self + } + } + + #[inline] + fn to_ascii_lowercase(&self) -> char { + if self.is_ascii() { + (*self as u8).to_ascii_lowercase() as char + } else { + *self + } + } + + #[inline] + fn eq_ignore_ascii_case(&self, other: &char) -> bool { + self.to_ascii_lowercase() == other.to_ascii_lowercase() + } + + #[inline] + fn make_ascii_uppercase(&mut self) { *self = self.to_ascii_uppercase(); } + #[inline] + fn make_ascii_lowercase(&mut self) { *self = self.to_ascii_lowercase(); } +} + +/// An iterator over the escaped version of a byte, constructed via +/// `std::ascii::escape_default`. +pub struct EscapeDefault { + range: Range, + data: [u8; 4], +} + +/// Returns an iterator that produces an escaped version of a `u8`. +/// +/// The default is chosen with a bias toward producing literals that are +/// legal in a variety of languages, including C++11 and similar C-family +/// languages. The exact rules are: +/// +/// - Tab, CR and LF are escaped as '\t', '\r' and '\n' respectively. +/// - Single-quote, double-quote and backslash chars are backslash-escaped. +/// - Any other chars in the range [0x20,0x7e] are not escaped. +/// - Any other chars are given hex escapes of the form '\xNN'. +/// - Unicode escapes are never generated by this function. +/// +/// # Examples +/// +/// ``` +/// use std::ascii; +/// +/// let escaped = ascii::escape_default(b'0').next().unwrap(); +/// assert_eq!(b'0', escaped); +/// +/// let mut escaped = ascii::escape_default(b'\t'); +/// +/// assert_eq!(b'\\', escaped.next().unwrap()); +/// assert_eq!(b't', escaped.next().unwrap()); +/// ``` +pub fn escape_default(c: u8) -> EscapeDefault { + let (data, len) = match c { + b'\t' => ([b'\\', b't', 0, 0], 2), + b'\r' => ([b'\\', b'r', 0, 0], 2), + b'\n' => ([b'\\', b'n', 0, 0], 2), + b'\\' => ([b'\\', b'\\', 0, 0], 2), + b'\'' => ([b'\\', b'\'', 0, 0], 2), + b'"' => ([b'\\', b'"', 0, 0], 2), + b'\x20' ... b'\x7e' => ([c, 0, 0, 0], 1), + _ => ([b'\\', b'x', hexify(c >> 4), hexify(c & 0xf)], 4), + }; + + return EscapeDefault { range: (0.. len), data: data }; + + fn hexify(b: u8) -> u8 { + match b { + 0 ... 9 => b'0' + b, + _ => b'a' + b - 10, + } + } +} + +impl Iterator for EscapeDefault { + type Item = u8; + fn next(&mut self) -> Option { self.range.next().map(|i| self.data[i]) } + fn size_hint(&self) -> (usize, Option) { self.range.size_hint() } +} +impl DoubleEndedIterator for EscapeDefault { + fn next_back(&mut self) -> Option { + self.range.next_back().map(|i| self.data[i]) + } +} +impl ExactSizeIterator for EscapeDefault {} + +static ASCII_LOWERCASE_MAP: [u8; 256] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + b' ', b'!', b'"', b'#', b'$', b'%', b'&', b'\'', + b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', + b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', + b'@', + + b'a', b'b', b'c', b'd', b'e', b'f', b'g', + b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', + b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', + b'x', b'y', b'z', + + b'[', b'\\', b']', b'^', b'_', + b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', + b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', + b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', + b'x', b'y', b'z', b'{', b'|', b'}', b'~', 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, +]; + +static ASCII_UPPERCASE_MAP: [u8; 256] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + b' ', b'!', b'"', b'#', b'$', b'%', b'&', b'\'', + b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', + b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', + b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', + b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', + b'X', b'Y', b'Z', b'[', b'\\', b']', b'^', b'_', + b'`', + + b'A', b'B', b'C', b'D', b'E', b'F', b'G', + b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', + b'X', b'Y', b'Z', + + b'{', b'|', b'}', b'~', 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, +]; + + +#[cfg(test)] +mod tests { + use super::*; + use rustc_unicode::char::from_u32; + use collections::string::ToString; + + #[test] + fn test_is_ascii() { + assert!(b"".is_ascii()); + assert!(b"banana\0\x7F".is_ascii()); + assert!(b"banana\0\x7F".iter().all(|b| b.is_ascii())); + assert!(!b"Vi\xe1\xbb\x87t Nam".is_ascii()); + assert!(!b"Vi\xe1\xbb\x87t Nam".iter().all(|b| b.is_ascii())); + assert!(!b"\xe1\xbb\x87".iter().any(|b| b.is_ascii())); + + assert!("".is_ascii()); + assert!("banana\0\u{7F}".is_ascii()); + assert!("banana\0\u{7F}".chars().all(|c| c.is_ascii())); + assert!(!"ประเทศไทย中华Việt Nam".chars().all(|c| c.is_ascii())); + + // NOTE: This test fails for some reason. + assert!(!"ประเทศไทย中华ệ ".chars().any(|c| c.is_ascii())); + } + + #[test] + fn test_to_ascii_uppercase() { + assert_eq!("url()URL()uRl()ürl".to_ascii_uppercase(), "URL()URL()URL()üRL"); + assert_eq!("hıKß".to_ascii_uppercase(), "HıKß"); + + for i in 0..501 { + let upper = if 'a' as u32 <= i && i <= 'z' as u32 { i + 'A' as u32 - 'a' as u32 } + else { i }; + assert_eq!((from_u32(i).unwrap()).to_string().to_ascii_uppercase(), + (from_u32(upper).unwrap()).to_string()); + } + } + + #[test] + fn test_to_ascii_lowercase() { + assert_eq!("url()URL()uRl()Ürl".to_ascii_lowercase(), "url()url()url()Ürl"); + // Dotted capital I, Kelvin sign, Sharp S. + assert_eq!("HİKß".to_ascii_lowercase(), "hİKß"); + + for i in 0..501 { + let lower = if 'A' as u32 <= i && i <= 'Z' as u32 { i + 'a' as u32 - 'A' as u32 } + else { i }; + assert_eq!((from_u32(i).unwrap()).to_string().to_ascii_lowercase(), + (from_u32(lower).unwrap()).to_string()); + } + } + + #[test] + fn test_make_ascii_lower_case() { + macro_rules! test { + ($from: expr, $to: expr) => { + { + let mut x = $from; + x.make_ascii_lowercase(); + assert_eq!(x, $to); + } + } + } + test!(b'A', b'a'); + test!(b'a', b'a'); + test!(b'!', b'!'); + test!('A', 'a'); + test!('À', 'À'); + test!('a', 'a'); + test!('!', '!'); + test!(b"H\xc3\x89".to_vec(), b"h\xc3\x89"); + test!("HİKß".to_string(), "hİKß"); + } + + + #[test] + fn test_make_ascii_upper_case() { + macro_rules! test { + ($from: expr, $to: expr) => { + { + let mut x = $from; + x.make_ascii_uppercase(); + assert_eq!(x, $to); + } + } + } + test!(b'a', b'A'); + test!(b'A', b'A'); + test!(b'!', b'!'); + test!('a', 'A'); + test!('à', 'à'); + test!('A', 'A'); + test!('!', '!'); + test!(b"h\xc3\xa9".to_vec(), b"H\xc3\xa9"); + test!("hıKß".to_string(), "HıKß"); + + let mut x = "Hello".to_string(); + x[..3].make_ascii_uppercase(); // Test IndexMut on String. + assert_eq!(x, "HELlo") + } + + #[test] + fn test_eq_ignore_ascii_case() { + assert!("url()URL()uRl()Ürl".eq_ignore_ascii_case("url()url()url()Ürl")); + assert!(!"Ürl".eq_ignore_ascii_case("ürl")); + // Dotted capital I, Kelvin sign, Sharp S. + assert!("HİKß".eq_ignore_ascii_case("hİKß")); + assert!(!"İ".eq_ignore_ascii_case("i")); + assert!(!"K".eq_ignore_ascii_case("k")); + assert!(!"ß".eq_ignore_ascii_case("s")); + + for i in 0..501 { + let lower = if 'A' as u32 <= i && i <= 'Z' as u32 { i + 'a' as u32 - 'A' as u32 } + else { i }; + assert!((from_u32(i).unwrap()).to_string().eq_ignore_ascii_case( + &from_u32(lower).unwrap().to_string())); + } + } + + #[test] + fn inference_works() { + let x = "a".to_string(); + x.eq_ignore_ascii_case("A"); + } +} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs new file mode 100644 index 0000000..3a2d5f1 --- /dev/null +++ b/src/ffi/mod.rs @@ -0,0 +1,3 @@ +pub use self::os_str::{OsString, OsStr}; + +mod os_str; diff --git a/src/ffi/os_str.rs b/src/ffi/os_str.rs new file mode 100644 index 0000000..d062d55 --- /dev/null +++ b/src/ffi/os_str.rs @@ -0,0 +1,576 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use collections::borrow::{Borrow, Cow, ToOwned}; +use core::fmt::{self, Debug}; +use core::mem; +use collections::String; +use core::ops; +use core::cmp; +use core::hash::{Hash, Hasher}; +use collections::Vec; + +use sys::wtf8::{Wtf8, Wtf8Buf}; +use sys::{AsInner, IntoInner, FromInner}; + +pub use sys::wtf8::EncodeWide; + +/// A type that can represent owned, mutable platform-native strings, but is +/// cheaply inter-convertible with Rust strings. +/// +/// The need for this type arises from the fact that: +/// +/// * On Unix systems, strings are often arbitrary sequences of non-zero +/// bytes, in many cases interpreted as UTF-8. +/// +/// * On Windows, strings are often arbitrary sequences of non-zero 16-bit +/// values, interpreted as UTF-16 when it is valid to do so. +/// +/// * In Rust, strings are always valid UTF-8, but may contain zeros. +/// +/// `OsString` and `OsStr` bridge this gap by simultaneously representing Rust +/// and platform-native string values, and in particular allowing a Rust string +/// to be converted into an "OS" string with no cost. +#[derive(Clone)] +pub struct OsString { + inner: Wtf8Buf +} + +/// Slices into OS strings (see `OsString`). +pub struct OsStr { + inner: Wtf8 +} + +impl OsString { + /// Constructs a new empty `OsString`. + pub fn new() -> OsString { + OsString { inner: Wtf8Buf::from_string(String::new()) } + } + + fn _from_bytes(vec: Vec) -> Option { + String::from_utf8(vec).ok().map(OsString::from) + } + + /// Converts to an `OsStr` slice. + pub fn as_os_str(&self) -> &OsStr { + self + } + + /// Converts the `OsString` into a `String` if it contains valid Unicode data. + /// + /// On failure, ownership of the original `OsString` is returned. + pub fn into_string(self) -> Result { + self.inner.into_string().map_err(|buf| OsString { inner: buf} ) + } + + /// Extends the string with the given `&OsStr` slice. + pub fn push>(&mut self, s: T) { + self.inner.push_wtf8(&s.as_ref().inner) + } + + /// Creates a new `OsString` with the given capacity. + /// + /// The string will be able to hold exactly `capacity` lenth units of other + /// OS strings without reallocating. If `capacity` is 0, the string will not + /// allocate. + /// + /// See main `OsString` documentation information about encoding. + pub fn with_capacity(capacity: usize) -> OsString { + OsString { + inner: Wtf8Buf::with_capacity(capacity) + } + } + + /// Truncates the `OsString` to zero length. + pub fn clear(&mut self) { + self.inner.clear() + } + + /// Returns the capacity this `OsString` can hold without reallocating. + /// + /// See `OsString` introduction for information about encoding. + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + /// Reserves capacity for at least `additional` more capacity to be inserted + /// in the given `OsString`. + /// + /// The collection may reserve more space to avoid frequent reallocations. + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional) + } + + /// Reserves the minimum capacity for exactly `additional` more capacity to + /// be inserted in the given `OsString`. Does nothing if the capacity is + /// already sufficient. + /// + /// Note that the allocator may give the collection more space than it + /// requests. Therefore capacity can not be relied upon to be precisely + /// minimal. Prefer reserve if future insertions are expected. + pub fn reserve_exact(&mut self, additional: usize) { + self.inner.reserve_exact(additional) + } + + /// Creates an `OsString` from a potentially ill-formed UTF-16 slice of + /// 16-bit code units. + /// + /// This is lossless: calling `.encode_wide()` on the resulting string + /// will always return the original code units. + /// + /// NOTE: This function was copied from the windows implementation of OsStringExt + pub fn from_wide(wide: &[u16]) -> OsString { + OsString { inner: Wtf8Buf::from_wide(wide) } + } +} + +impl From for OsString { + fn from(s: String) -> OsString { + OsString { inner: Wtf8Buf::from_string(s) } + } +} + +impl<'a, T: ?Sized + AsRef> From<&'a T> for OsString { + fn from(s: &'a T) -> OsString { + s.as_ref().to_os_string() + } +} + +impl ops::Index for OsString { + type Output = OsStr; + + #[inline] + fn index(&self, _index: ops::RangeFull) -> &OsStr { + OsStr::from_inner(self.inner.as_slice()) + } +} + +impl ops::Deref for OsString { + type Target = OsStr; + + #[inline] + fn deref(&self) -> &OsStr { + &self[..] + } +} + +impl Default for OsString { + #[inline] + fn default() -> OsString { + OsString::new() + } +} + +impl Debug for OsString { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&**self, formatter) + } +} + +impl PartialEq for OsString { + fn eq(&self, other: &OsString) -> bool { + &**self == &**other + } +} + +impl PartialEq for OsString { + fn eq(&self, other: &str) -> bool { + &**self == other + } +} + +impl PartialEq for str { + fn eq(&self, other: &OsString) -> bool { + &**other == self + } +} + +impl Eq for OsString {} + +impl PartialOrd for OsString { + #[inline] + fn partial_cmp(&self, other: &OsString) -> Option { + (&**self).partial_cmp(&**other) + } + #[inline] + fn lt(&self, other: &OsString) -> bool { &**self < &**other } + #[inline] + fn le(&self, other: &OsString) -> bool { &**self <= &**other } + #[inline] + fn gt(&self, other: &OsString) -> bool { &**self > &**other } + #[inline] + fn ge(&self, other: &OsString) -> bool { &**self >= &**other } +} + +impl PartialOrd for OsString { + #[inline] + fn partial_cmp(&self, other: &str) -> Option { + (&**self).partial_cmp(other) + } +} + +impl Ord for OsString { + #[inline] + fn cmp(&self, other: &OsString) -> cmp::Ordering { + (&**self).cmp(&**other) + } +} + +impl Hash for OsString { + #[inline] + fn hash(&self, state: &mut H) { + (&**self).hash(state) + } +} + +impl OsStr { + /// Coerces into an `OsStr` slice. + pub fn new + ?Sized>(s: &S) -> &OsStr { + s.as_ref() + } + + fn from_inner(inner: &Wtf8) -> &OsStr { + unsafe { mem::transmute(inner) } + } + + /// Yields a `&str` slice if the `OsStr` is valid Unicode. + /// + /// This conversion may entail doing a check for UTF-8 validity. + pub fn to_str(&self) -> Option<&str> { + self.inner.as_str() + } + + /// Converts an `OsStr` to a `Cow`. + /// + /// Any non-Unicode sequences are replaced with U+FFFD REPLACEMENT CHARACTER. + pub fn to_string_lossy(&self) -> Cow { + self.inner.to_string_lossy() + } + + /// Copies the slice into an owned `OsString`. + pub fn to_os_string(&self) -> OsString { + let mut buf = Wtf8Buf::with_capacity(self.inner.len()); + buf.push_wtf8(&self.inner); + OsString { inner: buf } + } + + /// Checks whether the `OsStr` is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// Returns the length of this `OsStr`. + /// + /// Note that this does **not** return the number of bytes in this string + /// as, for example, OS strings on Windows are encoded as a list of `u16` + /// rather than a list of bytes. This number is simply useful for passing to + /// other methods like `OsString::with_capacity` to avoid reallocations. + /// + /// See `OsStr` introduction for more information about encoding. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Gets the underlying byte representation. + /// + /// Note: it is *crucial* that this API is private, to avoid + /// revealing the internal, platform-specific encodings. + fn bytes(&self) -> &[u8] { + unsafe { mem::transmute(&self.inner) } + } + + /// Re-encodes an `OsStr` as a wide character sequence, + /// i.e. potentially ill-formed UTF-16. + /// This is lossless. Note that the encoding does not include a final + /// null. + /// + /// NOTE: This function was copied from the windows implementation of OsStrExt + pub fn encode_wide(&self) -> EncodeWide { + self.inner.encode_wide() + } + +} + +impl<'a> Default for &'a OsStr { + #[inline] + fn default() -> &'a OsStr { + OsStr::new("") + } +} + +impl PartialEq for OsStr { + fn eq(&self, other: &OsStr) -> bool { + self.bytes().eq(other.bytes()) + } +} + +impl PartialEq for OsStr { + fn eq(&self, other: &str) -> bool { + *self == *OsStr::new(other) + } +} + +impl PartialEq for str { + fn eq(&self, other: &OsStr) -> bool { + *other == *OsStr::new(self) + } +} + +impl Eq for OsStr {} + +impl PartialOrd for OsStr { + #[inline] + fn partial_cmp(&self, other: &OsStr) -> Option { + self.bytes().partial_cmp(other.bytes()) + } + #[inline] + fn lt(&self, other: &OsStr) -> bool { self.bytes().lt(other.bytes()) } + #[inline] + fn le(&self, other: &OsStr) -> bool { self.bytes().le(other.bytes()) } + #[inline] + fn gt(&self, other: &OsStr) -> bool { self.bytes().gt(other.bytes()) } + #[inline] + fn ge(&self, other: &OsStr) -> bool { self.bytes().ge(other.bytes()) } +} + +impl PartialOrd for OsStr { + #[inline] + fn partial_cmp(&self, other: &str) -> Option { + self.partial_cmp(OsStr::new(other)) + } +} + +// FIXME (#19470): cannot provide PartialOrd for str until we +// have more flexible coherence rules. + +impl Ord for OsStr { + #[inline] + fn cmp(&self, other: &OsStr) -> cmp::Ordering { self.bytes().cmp(other.bytes()) } +} + +macro_rules! impl_cmp { + ($lhs:ty, $rhs: ty) => { + impl<'a, 'b> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { ::eq(self, other) } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { ::eq(self, other) } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self, other) + } + } + } +} + +impl_cmp!(OsString, OsStr); +impl_cmp!(OsString, &'a OsStr); +impl_cmp!(Cow<'a, OsStr>, OsStr); +impl_cmp!(Cow<'a, OsStr>, &'b OsStr); +impl_cmp!(Cow<'a, OsStr>, OsString); + +impl Hash for OsStr { + #[inline] + fn hash(&self, state: &mut H) { + self.bytes().hash(state) + } +} + +impl Debug for OsStr { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.inner.fmt(formatter) + } +} + +impl Borrow for OsString { + fn borrow(&self) -> &OsStr { &self[..] } +} + +impl ToOwned for OsStr { + type Owned = OsString; + fn to_owned(&self) -> OsString { self.to_os_string() } +} + +impl AsRef for OsStr { + fn as_ref(&self) -> &OsStr { + self + } +} + +impl AsRef for OsString { + fn as_ref(&self) -> &OsStr { + self + } +} + +impl AsRef for str { + fn as_ref(&self) -> &OsStr { + OsStr::from_inner(Wtf8::from_str(self)) + } +} + +impl AsRef for String { + fn as_ref(&self) -> &OsStr { + (&**self).as_ref() + } +} + +impl FromInner for OsString { + fn from_inner(buf: Wtf8Buf) -> OsString { + OsString { inner: buf } + } +} + +impl IntoInner for OsString { + fn into_inner(self) -> Wtf8Buf { + self.inner + } +} + +impl AsInner for OsStr { + fn as_inner(&self) -> &Wtf8 { + &self.inner + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sys::{AsInner, IntoInner}; + + #[test] + fn test_os_string_with_capacity() { + let os_string = OsString::with_capacity(0); + assert_eq!(0, os_string.inner.capacity()); + + let os_string = OsString::with_capacity(10); + assert_eq!(10, os_string.inner.capacity()); + + let mut os_string = OsString::with_capacity(0); + os_string.push("abc"); + assert!(os_string.inner.capacity() >= 3); + } + + #[test] + fn test_os_string_clear() { + let mut os_string = OsString::from("abc"); + assert_eq!(3, os_string.inner.len()); + + os_string.clear(); + assert_eq!(&os_string, ""); + assert_eq!(0, os_string.inner.len()); + } + + #[test] + fn test_os_string_capacity() { + let os_string = OsString::with_capacity(0); + assert_eq!(0, os_string.capacity()); + + let os_string = OsString::with_capacity(10); + assert_eq!(10, os_string.capacity()); + + let mut os_string = OsString::with_capacity(0); + os_string.push("abc"); + assert!(os_string.capacity() >= 3); + } + + #[test] + fn test_os_string_reserve() { + let mut os_string = OsString::new(); + assert_eq!(os_string.capacity(), 0); + + os_string.reserve(2); + assert!(os_string.capacity() >= 2); + + for _ in 0..16 { + os_string.push("a"); + } + + assert!(os_string.capacity() >= 16); + os_string.reserve(16); + assert!(os_string.capacity() >= 32); + + os_string.push("a"); + + os_string.reserve(16); + assert!(os_string.capacity() >= 33) + } + + #[test] + fn test_os_string_reserve_exact() { + let mut os_string = OsString::new(); + assert_eq!(os_string.capacity(), 0); + + os_string.reserve_exact(2); + assert!(os_string.capacity() >= 2); + + for _ in 0..16 { + os_string.push("a"); + } + + assert!(os_string.capacity() >= 16); + os_string.reserve_exact(16); + assert!(os_string.capacity() >= 32); + + os_string.push("a"); + + os_string.reserve_exact(16); + assert!(os_string.capacity() >= 33) + } + + #[test] + fn test_os_string_default() { + let os_string: OsString = Default::default(); + assert_eq!("", &os_string); + } + + #[test] + fn test_os_str_is_empty() { + let mut os_string = OsString::new(); + assert!(os_string.is_empty()); + + os_string.push("abc"); + assert!(!os_string.is_empty()); + + os_string.clear(); + assert!(os_string.is_empty()); + } + + #[test] + fn test_os_str_len() { + let mut os_string = OsString::new(); + assert_eq!(0, os_string.len()); + + os_string.push("abc"); + assert_eq!(3, os_string.len()); + + os_string.clear(); + assert_eq!(0, os_string.len()); + } + + #[test] + fn test_os_str_default() { + let os_str: &OsStr = Default::default(); + assert_eq!("", os_str); + } +} diff --git a/src/lib.rs b/src/lib.rs index dc225c6..14702af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,36 @@ -#![feature(alloc, collections, lang_items)] +#![feature(alloc)] +#![feature(collections)] +#![feature(char_escape_debug)] +#![feature(lang_items)] +#![feature(question_mark)] +#![feature(slice_patterns)] +#![feature(str_internals)] +#![feature(unicode)] + #![no_std] + #![crate_type = "rlib"] #![crate_name = "ctru"] -extern crate ctru_sys as libctru; - extern crate alloc; +extern crate alloc_system; extern crate collections; +extern crate rustc_unicode; + +extern crate ctru_sys as libctru; pub mod console; pub mod srv; pub mod gfx; +pub mod services; pub mod sdmc; -pub mod services; +pub mod ascii; +pub mod ffi; pub mod panic; +pub mod path; + +mod sys; pub use srv::Srv; pub use gfx::Gfx; diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..3fb6357 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,3378 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Cross-platform path manipulation. +//! +//! This module provides two types, `PathBuf` and `Path` (akin to `String` and +//! `str`), for working with paths abstractly. These types are thin wrappers +//! around `OsString` and `OsStr` respectively, meaning that they work directly +//! on strings according to the local platform's path syntax. +//! +//! ## Simple usage +//! +//! Path manipulation includes both parsing components from slices and building +//! new owned paths. +//! +//! To parse a path, you can create a `Path` slice from a `str` +//! slice and start asking questions: +//! +//! ```rust +//! use std::path::Path; +//! +//! let path = Path::new("/tmp/foo/bar.txt"); +//! let file = path.file_name(); +//! let extension = path.extension(); +//! let parent_dir = path.parent(); +//! ``` +//! +//! To build or modify paths, use `PathBuf`: +//! +//! ```rust +//! use std::path::PathBuf; +//! +//! let mut path = PathBuf::from("c:\\"); +//! path.push("windows"); +//! path.push("system32"); +//! path.set_extension("dll"); +//! ``` +//! +//! ## Path components and normalization +//! +//! The path APIs are built around the notion of "components", which roughly +//! correspond to the substrings between path separators (`/` and, on Windows, +//! `\`). The APIs for path parsing are largely specified in terms of the path's +//! components, so it's important to clearly understand how those are +//! determined. +//! +//! A path can always be reconstructed into an *equivalent* path by +//! putting together its components via `push`. Syntactically, the +//! paths may differ by the normalization described below. +//! +//! ### Component types +//! +//! Components come in several types: +//! +//! * Normal components are the default: standard references to files or +//! directories. The path `a/b` has two normal components, `a` and `b`. +//! +//! * Current directory components represent the `.` character. For example, +//! `./a` has a current directory component and a normal component `a`. +//! +//! * The root directory component represents a separator that designates +//! starting from root. For example, `/a/b` has a root directory component +//! followed by normal components `a` and `b`. +//! +//! On Windows, an additional component type comes into play: +//! +//! * Prefix components, of which there is a large variety. For example, `C:` +//! and `\\server\share` are prefixes. The path `C:windows` has a prefix +//! component `C:` and a normal component `windows`; the path `C:\windows` has a +//! prefix component `C:`, a root directory component, and a normal component +//! `windows`. +//! +//! ### Normalization +//! +//! Aside from splitting on the separator(s), there is a small amount of +//! "normalization": +//! +//! * Repeated separators are ignored: `a/b` and `a//b` both have components `a` +//! and `b`. +//! +//! * Occurrences of `.` are normalized away, *except* if they are at +//! the beginning of the path (in which case they are often meaningful +//! in terms of path searching). So, for example, `a/./b`, `a/b/`, +//! `/a/b/.` and `a/b` all have components `a` and `b`, but `./a/b` +//! has a leading current directory component. +//! +//! No other normalization takes place by default. In particular, +//! `a/c` and `a/b/../c` are distinct, to account for the possibility +//! that `b` is a symbolic link (so its parent isn't `a`). Further +//! normalization is possible to build on top of the components APIs, +//! and will be included in this library in the near future. + +use ascii::*; +use collections::borrow::{Borrow, ToOwned, Cow}; +use core::cmp; +//use error::Error; +use core::fmt; +//use fs; +use core::hash::{Hash, Hasher}; +//use io; +use core::iter; +use core::mem; +use core::ops::{self, Deref}; +use collections::String; +use collections::Vec; + +use ffi::{OsStr, OsString}; + +use self::platform::{is_sep_byte, is_verbatim_sep, MAIN_SEP_STR, parse_prefix}; + +//////////////////////////////////////////////////////////////////////////////// +// GENERAL NOTES +//////////////////////////////////////////////////////////////////////////////// +// +// Parsing in this module is done by directly transmuting OsStr to [u8] slices, +// taking advantage of the fact that OsStr always encodes ASCII characters +// as-is. Eventually, this transmutation should be replaced by direct uses of +// OsStr APIs for parsing, but it will take a while for those to become +// available. + +//////////////////////////////////////////////////////////////////////////////// +// Platform-specific definitions +//////////////////////////////////////////////////////////////////////////////// + +// The following modules give the most basic tools for parsing paths on various +// platforms. The bulk of the code is devoted to parsing prefixes on Windows. + +#[cfg(unix)] +mod platform { + use super::Prefix; + use ffi::OsStr; + + #[inline] + pub fn is_sep_byte(b: u8) -> bool { + b == b'/' + } + + #[inline] + pub fn is_verbatim_sep(b: u8) -> bool { + b == b'/' + } + + pub fn parse_prefix(_: &OsStr) -> Option { + None + } + + pub const MAIN_SEP_STR: &'static str = "/"; + pub const MAIN_SEP: char = '/'; +} + +#[cfg(windows)] +mod platform { + use ascii::*; + + use super::{os_str_as_u8_slice, u8_slice_as_os_str, Prefix}; + use ffi::OsStr; + + #[inline] + pub fn is_sep_byte(b: u8) -> bool { + b == b'/' || b == b'\\' + } + + #[inline] + pub fn is_verbatim_sep(b: u8) -> bool { + b == b'\\' + } + + pub fn parse_prefix<'a>(path: &'a OsStr) -> Option { + use super::Prefix::*; + unsafe { + // The unsafety here stems from converting between &OsStr and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &OsStr values are produced + // only from ASCII-bounded slices of existing &OsStr values. + let mut path = os_str_as_u8_slice(path); + + if path.starts_with(br"\\") { + // \\ + path = &path[2..]; + if path.starts_with(br"?\") { + // \\?\ + path = &path[2..]; + if path.starts_with(br"UNC\") { + // \\?\UNC\server\share + path = &path[4..]; + let (server, share) = match parse_two_comps(path, is_verbatim_sep) { + Some((server, share)) => + (u8_slice_as_os_str(server), u8_slice_as_os_str(share)), + None => (u8_slice_as_os_str(path), u8_slice_as_os_str(&[])), + }; + return Some(VerbatimUNC(server, share)); + } else { + // \\?\path + let idx = path.iter().position(|&b| b == b'\\'); + if idx == Some(2) && path[1] == b':' { + let c = path[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + // \\?\C:\ path + return Some(VerbatimDisk(c.to_ascii_uppercase())); + } + } + let slice = &path[..idx.unwrap_or(path.len())]; + return Some(Verbatim(u8_slice_as_os_str(slice))); + } + } else if path.starts_with(b".\\") { + // \\.\path + path = &path[2..]; + let pos = path.iter().position(|&b| b == b'\\'); + let slice = &path[..pos.unwrap_or(path.len())]; + return Some(DeviceNS(u8_slice_as_os_str(slice))); + } + match parse_two_comps(path, is_sep_byte) { + Some((server, share)) if !server.is_empty() && !share.is_empty() => { + // \\server\share + return Some(UNC(u8_slice_as_os_str(server), u8_slice_as_os_str(share))); + } + _ => (), + } + } else if path.get(1) == Some(& b':') { + // C: + let c = path[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + return Some(Disk(c.to_ascii_uppercase())); + } + } + return None; + } + + fn parse_two_comps(mut path: &[u8], f: fn(u8) -> bool) -> Option<(&[u8], &[u8])> { + let first = match path.iter().position(|x| f(*x)) { + None => return None, + Some(x) => &path[..x], + }; + path = &path[(first.len() + 1)..]; + let idx = path.iter().position(|x| f(*x)); + let second = &path[..idx.unwrap_or(path.len())]; + Some((first, second)) + } + } + + pub const MAIN_SEP_STR: &'static str = "\\"; + pub const MAIN_SEP: char = '\\'; +} + +//////////////////////////////////////////////////////////////////////////////// +// Windows Prefixes +//////////////////////////////////////////////////////////////////////////////// + +/// Path prefixes (Windows only). +/// +/// Windows uses a variety of path styles, including references to drive +/// volumes (like `C:`), network shared folders (like `\\server\share`) and +/// others. In addition, some path prefixes are "verbatim", in which case +/// `/` is *not* treated as a separator and essentially no normalization is +/// performed. +#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub enum Prefix<'a> { + /// Prefix `\\?\`, together with the given component immediately following it. + Verbatim(&'a OsStr), + /// Prefix `\\?\UNC\`, with the "server" and "share" components following it. + VerbatimUNC( + &'a OsStr, + &'a OsStr, + ), + + /// Prefix like `\\?\C:\`, for the given drive letter + VerbatimDisk(u8), + + /// Prefix `\\.\`, together with the given component immediately following it. + DeviceNS(&'a OsStr), + + /// Prefix `\\server\share`, with the given "server" and "share" components. + UNC( + &'a OsStr, + &'a OsStr, + ), + + /// Prefix `C:` for the given disk drive. + Disk(u8), +} + +impl<'a> Prefix<'a> { + #[inline] + fn len(&self) -> usize { + use self::Prefix::*; + fn os_str_len(s: &OsStr) -> usize { + os_str_as_u8_slice(s).len() + } + match *self { + Verbatim(x) => 4 + os_str_len(x), + VerbatimUNC(x, y) => { + 8 + os_str_len(x) + + if os_str_len(y) > 0 { + 1 + os_str_len(y) + } else { + 0 + } + }, + VerbatimDisk(_) => 6, + UNC(x, y) => { + 2 + os_str_len(x) + + if os_str_len(y) > 0 { + 1 + os_str_len(y) + } else { + 0 + } + }, + DeviceNS(x) => 4 + os_str_len(x), + Disk(_) => 2, + } + + } + + /// Determines if the prefix is verbatim, i.e. begins with `\\?\`. + #[inline] + pub fn is_verbatim(&self) -> bool { + use self::Prefix::*; + match *self { + Verbatim(_) | VerbatimDisk(_) | VerbatimUNC(_, _) => true, + _ => false, + } + } + + #[inline] + fn is_drive(&self) -> bool { + match *self { + Prefix::Disk(_) => true, + _ => false, + } + } + + #[inline] + fn has_implicit_root(&self) -> bool { + !self.is_drive() + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Exposed parsing helpers +//////////////////////////////////////////////////////////////////////////////// + +/// Determines whether the character is one of the permitted path +/// separators for the current platform. +/// +/// # Examples +/// +/// ``` +/// use std::path; +/// +/// assert!(path::is_separator('/')); +/// assert!(!path::is_separator('❤')); +/// ``` +pub fn is_separator(c: char) -> bool { + c.is_ascii() && is_sep_byte(c as u8) +} + +/// The primary separator for the current platform +pub const MAIN_SEPARATOR: char = platform::MAIN_SEP; + +//////////////////////////////////////////////////////////////////////////////// +// Misc helpers +//////////////////////////////////////////////////////////////////////////////// + +// Iterate through `iter` while it matches `prefix`; return `None` if `prefix` +// is not a prefix of `iter`, otherwise return `Some(iter_after_prefix)` giving +// `iter` after having exhausted `prefix`. +fn iter_after(mut iter: I, mut prefix: J) -> Option + where I: Iterator + Clone, + J: Iterator, + A: PartialEq +{ + loop { + let mut iter_next = iter.clone(); + match (iter_next.next(), prefix.next()) { + (Some(ref x), Some(ref y)) if x == y => (), + (Some(_), Some(_)) => return None, + (Some(_), None) => return Some(iter), + (None, None) => return Some(iter), + (None, Some(_)) => return None, + } + iter = iter_next; + } +} + +// See note at the top of this module to understand why these are used: +fn os_str_as_u8_slice(s: &OsStr) -> &[u8] { + unsafe { mem::transmute(s) } +} +unsafe fn u8_slice_as_os_str(s: &[u8]) -> &OsStr { + mem::transmute(s) +} + +//////////////////////////////////////////////////////////////////////////////// +// Cross-platform, iterator-independent parsing +//////////////////////////////////////////////////////////////////////////////// + +/// Says whether the first byte after the prefix is a separator. +fn has_physical_root(s: &[u8], prefix: Option) -> bool { + let path = if let Some(p) = prefix { + &s[p.len()..] + } else { + s + }; + !path.is_empty() && is_sep_byte(path[0]) +} + +// basic workhorse for splitting stem and extension +fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) { + unsafe { + if os_str_as_u8_slice(file) == b".." { + return (Some(file), None); + } + + // The unsafety here stems from converting between &OsStr and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &OsStr values are produced + // only from ASCII-bounded slices of existing &OsStr values. + + let mut iter = os_str_as_u8_slice(file).rsplitn(2, |b| *b == b'.'); + let after = iter.next(); + let before = iter.next(); + if before == Some(b"") { + (Some(file), None) + } else { + (before.map(|s| u8_slice_as_os_str(s)), + after.map(|s| u8_slice_as_os_str(s))) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The core iterators +//////////////////////////////////////////////////////////////////////////////// + +/// Component parsing works by a double-ended state machine; the cursors at the +/// front and back of the path each keep track of what parts of the path have +/// been consumed so far. +/// +/// Going front to back, a path is made up of a prefix, a starting +/// directory component, and a body (of normal components) +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +enum State { + Prefix = 0, // c: + StartDir = 1, // / or . or nothing + Body = 2, // foo/bar/baz + Done = 3, +} + +/// A Windows path prefix, e.g. `C:` or `\\server\share`. +/// +/// Does not occur on Unix. +#[derive(Copy, Clone, Eq, Debug)] +pub struct PrefixComponent<'a> { + /// The prefix as an unparsed `OsStr` slice. + raw: &'a OsStr, + + /// The parsed prefix data. + parsed: Prefix<'a>, +} + +impl<'a> PrefixComponent<'a> { + /// The parsed prefix data. + pub fn kind(&self) -> Prefix<'a> { + self.parsed + } + + /// The raw `OsStr` slice for this prefix. + pub fn as_os_str(&self) -> &'a OsStr { + self.raw + } +} + +impl<'a> cmp::PartialEq for PrefixComponent<'a> { + fn eq(&self, other: &PrefixComponent<'a>) -> bool { + cmp::PartialEq::eq(&self.parsed, &other.parsed) + } +} + +impl<'a> cmp::PartialOrd for PrefixComponent<'a> { + fn partial_cmp(&self, other: &PrefixComponent<'a>) -> Option { + cmp::PartialOrd::partial_cmp(&self.parsed, &other.parsed) + } +} + +impl<'a> cmp::Ord for PrefixComponent<'a> { + fn cmp(&self, other: &PrefixComponent<'a>) -> cmp::Ordering { + cmp::Ord::cmp(&self.parsed, &other.parsed) + } +} + +impl<'a> Hash for PrefixComponent<'a> { + fn hash(&self, h: &mut H) { + self.parsed.hash(h); + } +} + +/// A single component of a path. +/// +/// See the module documentation for an in-depth explanation of components and +/// their role in the API. +/// +/// This `enum` is created from iterating over the [`path::Components`] +/// `struct`. +/// +/// # Examples +/// +/// ```rust +/// use std::path::{Component, Path}; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// let components = path.components().collect::>(); +/// assert_eq!(&components, &[ +/// Component::RootDir, +/// Component::Normal("tmp".as_ref()), +/// Component::Normal("foo".as_ref()), +/// Component::Normal("bar.txt".as_ref()), +/// ]); +/// ``` +/// +/// [`path::Components`]: struct.Components.html +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Component<'a> { + /// A Windows path prefix, e.g. `C:` or `\\server\share`. + /// + /// Does not occur on Unix. + Prefix( + PrefixComponent<'a> + ), + + /// The root directory component, appears after any prefix and before anything else + RootDir, + + /// A reference to the current directory, i.e. `.` + CurDir, + + /// A reference to the parent directory, i.e. `..` + ParentDir, + + /// A normal component, i.e. `a` and `b` in `a/b` + Normal(&'a OsStr), +} + +impl<'a> Component<'a> { + /// Extracts the underlying `OsStr` slice + pub fn as_os_str(self) -> &'a OsStr { + match self { + Component::Prefix(p) => p.as_os_str(), + Component::RootDir => OsStr::new(MAIN_SEP_STR), + Component::CurDir => OsStr::new("."), + Component::ParentDir => OsStr::new(".."), + Component::Normal(path) => path, + } + } +} + +impl<'a> AsRef for Component<'a> { + fn as_ref(&self) -> &OsStr { + self.as_os_str() + } +} + +/// The core iterator giving the components of a path. +/// +/// See the module documentation for an in-depth explanation of components and +/// their role in the API. +/// +/// This `struct` is created by the [`path::Path::components`] method. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// +/// for component in path.components() { +/// println!("{:?}", component); +/// } +/// ``` +/// +/// [`path::Path::components`]: struct.Path.html#method.components +#[derive(Clone)] +pub struct Components<'a> { + // The path left to parse components from + path: &'a [u8], + + // The prefix as it was originally parsed, if any + prefix: Option>, + + // true if path *physically* has a root separator; for most Windows + // prefixes, it may have a "logical" rootseparator for the purposes of + // normalization, e.g. \\server\share == \\server\share\. + has_physical_root: bool, + + // The iterator is double-ended, and these two states keep track of what has + // been produced from either end + front: State, + back: State, +} + +/// An iterator over the components of a path, as `OsStr` slices. +#[derive(Clone)] +pub struct Iter<'a> { + inner: Components<'a>, +} + +impl<'a> Components<'a> { + // how long is the prefix, if any? + #[inline] + fn prefix_len(&self) -> usize { + self.prefix.as_ref().map(Prefix::len).unwrap_or(0) + } + + #[inline] + fn prefix_verbatim(&self) -> bool { + self.prefix.as_ref().map(Prefix::is_verbatim).unwrap_or(false) + } + + /// how much of the prefix is left from the point of view of iteration? + #[inline] + fn prefix_remaining(&self) -> usize { + if self.front == State::Prefix { + self.prefix_len() + } else { + 0 + } + } + + // Given the iteration so far, how much of the pre-State::Body path is left? + #[inline] + fn len_before_body(&self) -> usize { + let root = if self.front <= State::StartDir && self.has_physical_root { + 1 + } else { + 0 + }; + let cur_dir = if self.front <= State::StartDir && self.include_cur_dir() { + 1 + } else { + 0 + }; + self.prefix_remaining() + root + cur_dir + } + + // is the iteration complete? + #[inline] + fn finished(&self) -> bool { + self.front == State::Done || self.back == State::Done || self.front > self.back + } + + #[inline] + fn is_sep_byte(&self, b: u8) -> bool { + if self.prefix_verbatim() { + is_verbatim_sep(b) + } else { + is_sep_byte(b) + } + } + + /// Extracts a slice corresponding to the portion of the path remaining for iteration. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let mut components = Path::new("/tmp/foo/bar.txt").components(); + /// components.next(); + /// components.next(); + /// + /// assert_eq!(Path::new("foo/bar.txt"), components.as_path()); + /// ``` + pub fn as_path(&self) -> &'a Path { + let mut comps = self.clone(); + if comps.front == State::Body { + comps.trim_left(); + } + if comps.back == State::Body { + comps.trim_right(); + } + unsafe { Path::from_u8_slice(comps.path) } + } + + /// Is the *original* path rooted? + fn has_root(&self) -> bool { + if self.has_physical_root { + return true; + } + if let Some(p) = self.prefix { + if p.has_implicit_root() { + return true; + } + } + false + } + + /// Should the normalized path include a leading . ? + fn include_cur_dir(&self) -> bool { + if self.has_root() { + return false; + } + let mut iter = self.path[self.prefix_len()..].iter(); + match (iter.next(), iter.next()) { + (Some(&b'.'), None) => true, + (Some(&b'.'), Some(&b)) => self.is_sep_byte(b), + _ => false, + } + } + + // parse a given byte sequence into the corresponding path component + fn parse_single_component<'b>(&self, comp: &'b [u8]) -> Option> { + match comp { + b"." if self.prefix_verbatim() => Some(Component::CurDir), + b"." => None, // . components are normalized away, except at + // the beginning of a path, which is treated + // separately via `include_cur_dir` + b".." => Some(Component::ParentDir), + b"" => None, + _ => Some(Component::Normal(unsafe { u8_slice_as_os_str(comp) })), + } + } + + // parse a component from the left, saying how many bytes to consume to + // remove the component + fn parse_next_component(&self) -> (usize, Option>) { + debug_assert!(self.front == State::Body); + let (extra, comp) = match self.path.iter().position(|b| self.is_sep_byte(*b)) { + None => (0, self.path), + Some(i) => (1, &self.path[..i]), + }; + (comp.len() + extra, self.parse_single_component(comp)) + } + + // parse a component from the right, saying how many bytes to consume to + // remove the component + fn parse_next_component_back(&self) -> (usize, Option>) { + debug_assert!(self.back == State::Body); + let start = self.len_before_body(); + let (extra, comp) = match self.path[start..].iter().rposition(|b| self.is_sep_byte(*b)) { + None => (0, &self.path[start..]), + Some(i) => (1, &self.path[start + i + 1..]), + }; + (comp.len() + extra, self.parse_single_component(comp)) + } + + // trim away repeated separators (i.e. empty components) on the left + fn trim_left(&mut self) { + while !self.path.is_empty() { + let (size, comp) = self.parse_next_component(); + if comp.is_some() { + return; + } else { + self.path = &self.path[size..]; + } + } + } + + // trim away repeated separators (i.e. empty components) on the right + fn trim_right(&mut self) { + while self.path.len() > self.len_before_body() { + let (size, comp) = self.parse_next_component_back(); + if comp.is_some() { + return; + } else { + self.path = &self.path[..self.path.len() - size]; + } + } + } +} + +impl<'a> AsRef for Components<'a> { + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl<'a> AsRef for Components<'a> { + fn as_ref(&self) -> &OsStr { + self.as_path().as_os_str() + } +} + +impl<'a> Iter<'a> { + /// Extracts a slice corresponding to the portion of the path remaining for iteration. + pub fn as_path(&self) -> &'a Path { + self.inner.as_path() + } +} + +impl<'a> AsRef for Iter<'a> { + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl<'a> AsRef for Iter<'a> { + fn as_ref(&self) -> &OsStr { + self.as_path().as_os_str() + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a OsStr; + + fn next(&mut self) -> Option<&'a OsStr> { + self.inner.next().map(Component::as_os_str) + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option<&'a OsStr> { + self.inner.next_back().map(Component::as_os_str) + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option> { + while !self.finished() { + match self.front { + State::Prefix if self.prefix_len() > 0 => { + self.front = State::StartDir; + debug_assert!(self.prefix_len() <= self.path.len()); + let raw = &self.path[..self.prefix_len()]; + self.path = &self.path[self.prefix_len()..]; + return Some(Component::Prefix(PrefixComponent { + raw: unsafe { u8_slice_as_os_str(raw) }, + parsed: self.prefix.unwrap(), + })); + } + State::Prefix => { + self.front = State::StartDir; + } + State::StartDir => { + self.front = State::Body; + if self.has_physical_root { + debug_assert!(!self.path.is_empty()); + self.path = &self.path[1..]; + return Some(Component::RootDir); + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir); + } + } else if self.include_cur_dir() { + debug_assert!(!self.path.is_empty()); + self.path = &self.path[1..]; + return Some(Component::CurDir); + } + } + State::Body if !self.path.is_empty() => { + let (size, comp) = self.parse_next_component(); + self.path = &self.path[size..]; + if comp.is_some() { + return comp; + } + } + State::Body => { + self.front = State::Done; + } + State::Done => unreachable!(), + } + } + None + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option> { + while !self.finished() { + match self.back { + State::Body if self.path.len() > self.len_before_body() => { + let (size, comp) = self.parse_next_component_back(); + self.path = &self.path[..self.path.len() - size]; + if comp.is_some() { + return comp; + } + } + State::Body => { + self.back = State::StartDir; + } + State::StartDir => { + self.back = State::Prefix; + if self.has_physical_root { + self.path = &self.path[..self.path.len() - 1]; + return Some(Component::RootDir); + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir); + } + } else if self.include_cur_dir() { + self.path = &self.path[..self.path.len() - 1]; + return Some(Component::CurDir); + } + } + State::Prefix if self.prefix_len() > 0 => { + self.back = State::Done; + return Some(Component::Prefix(PrefixComponent { + raw: unsafe { u8_slice_as_os_str(self.path) }, + parsed: self.prefix.unwrap(), + })); + } + State::Prefix => { + self.back = State::Done; + return None; + } + State::Done => unreachable!(), + } + } + None + } +} + +impl<'a> cmp::PartialEq for Components<'a> { + fn eq(&self, other: &Components<'a>) -> bool { + Iterator::eq(self.clone(), other.clone()) + } +} + +impl<'a> cmp::Eq for Components<'a> {} + +impl<'a> cmp::PartialOrd for Components<'a> { + fn partial_cmp(&self, other: &Components<'a>) -> Option { + Iterator::partial_cmp(self.clone(), other.clone()) + } +} + +impl<'a> cmp::Ord for Components<'a> { + fn cmp(&self, other: &Components<'a>) -> cmp::Ordering { + Iterator::cmp(self.clone(), other.clone()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Basic types and traits +//////////////////////////////////////////////////////////////////////////////// + +/// An owned, mutable path (akin to `String`). +/// +/// This type provides methods like `push` and `set_extension` that mutate the +/// path in place. It also implements `Deref` to `Path`, meaning that all +/// methods on `Path` slices are available on `PathBuf` values as well. +/// +/// More details about the overall approach can be found in +/// the module documentation. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// let mut path = PathBuf::from("c:\\"); +/// path.push("windows"); +/// path.push("system32"); +/// path.set_extension("dll"); +/// ``` +#[derive(Clone)] +pub struct PathBuf { + inner: OsString, +} + +impl PathBuf { + fn as_mut_vec(&mut self) -> &mut Vec { + unsafe { &mut *(self as *mut PathBuf as *mut Vec) } + } + + /// Allocates an empty `PathBuf`. + pub fn new() -> PathBuf { + PathBuf { inner: OsString::new() } + } + + /// Coerces to a `Path` slice. + pub fn as_path(&self) -> &Path { + self + } + + /// Extends `self` with `path`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// On Windows: + /// + /// * if `path` has a root but no prefix (e.g. `\windows`), it + /// replaces everything except for the prefix (if any) of `self`. + /// * if `path` has a prefix but no root, it replaces `self`. + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// + /// let mut path = PathBuf::new(); + /// path.push("/tmp"); + /// path.push("file.bk"); + /// assert_eq!(path, PathBuf::from("/tmp/file.bk")); + /// + /// // Pushing an absolute path replaces the current path + /// path.push("/etc/passwd"); + /// assert_eq!(path, PathBuf::from("/etc/passwd")); + /// ``` + pub fn push>(&mut self, path: P) { + self._push(path.as_ref()) + } + + fn _push(&mut self, path: &Path) { + // in general, a separator is needed if the rightmost byte is not a separator + let mut need_sep = self.as_mut_vec().last().map(|c| !is_sep_byte(*c)).unwrap_or(false); + + // in the special case of `C:` on Windows, do *not* add a separator + { + let comps = self.components(); + if comps.prefix_len() > 0 && comps.prefix_len() == comps.path.len() && + comps.prefix.unwrap().is_drive() { + need_sep = false + } + } + + // absolute `path` replaces `self` + if path.is_absolute() || path.prefix().is_some() { + self.as_mut_vec().truncate(0); + + // `path` has a root but no prefix, e.g. `\windows` (Windows only) + } else if path.has_root() { + let prefix_len = self.components().prefix_remaining(); + self.as_mut_vec().truncate(prefix_len); + + // `path` is a pure relative path + } else if need_sep { + self.inner.push(MAIN_SEP_STR); + } + + self.inner.push(path); + } + + /// Truncate `self` to `self.parent()`. + /// + /// Returns false and does nothing if `self.file_name()` is `None`. + /// Otherwise, returns `true`. + pub fn pop(&mut self) -> bool { + match self.parent().map(|p| p.as_u8_slice().len()) { + Some(len) => { + self.as_mut_vec().truncate(len); + true + } + None => false, + } + } + + /// Updates `self.file_name()` to `file_name`. + /// + /// If `self.file_name()` was `None`, this is equivalent to pushing + /// `file_name`. + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// + /// let mut buf = PathBuf::from("/"); + /// assert!(buf.file_name() == None); + /// buf.set_file_name("bar"); + /// assert!(buf == PathBuf::from("/bar")); + /// assert!(buf.file_name().is_some()); + /// buf.set_file_name("baz.txt"); + /// assert!(buf == PathBuf::from("/baz.txt")); + /// ``` + pub fn set_file_name>(&mut self, file_name: S) { + self._set_file_name(file_name.as_ref()) + } + + fn _set_file_name(&mut self, file_name: &OsStr) { + if self.file_name().is_some() { + let popped = self.pop(); + debug_assert!(popped); + } + self.push(file_name); + } + + /// Updates `self.extension()` to `extension`. + /// + /// If `self.file_name()` is `None`, does nothing and returns `false`. + /// + /// Otherwise, returns `true`; if `self.extension()` is `None`, the extension + /// is added; otherwise it is replaced. + pub fn set_extension>(&mut self, extension: S) -> bool { + self._set_extension(extension.as_ref()) + } + + fn _set_extension(&mut self, extension: &OsStr) -> bool { + if self.file_name().is_none() { + return false; + } + + let mut stem = match self.file_stem() { + Some(stem) => stem.to_os_string(), + None => OsString::new(), + }; + + if !os_str_as_u8_slice(extension).is_empty() { + stem.push("."); + stem.push(extension); + } + self.set_file_name(&stem); + + true + } + + /// Consumes the `PathBuf`, yielding its internal `OsString` storage. + pub fn into_os_string(self) -> OsString { + self.inner + } +} + +impl<'a, T: ?Sized + AsRef> From<&'a T> for PathBuf { + fn from(s: &'a T) -> PathBuf { + PathBuf::from(s.as_ref().to_os_string()) + } +} + +impl From for PathBuf { + fn from(s: OsString) -> PathBuf { + PathBuf { inner: s } + } +} + +impl From for PathBuf { + fn from(s: String) -> PathBuf { + PathBuf::from(OsString::from(s)) + } +} + +impl> iter::FromIterator

for PathBuf { + fn from_iter>(iter: I) -> PathBuf { + let mut buf = PathBuf::new(); + buf.extend(iter); + buf + } +} + +impl> iter::Extend

for PathBuf { + fn extend>(&mut self, iter: I) { + for p in iter { + self.push(p.as_ref()) + } + } +} + +impl fmt::Debug for PathBuf { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&**self, formatter) + } +} + +impl ops::Deref for PathBuf { + type Target = Path; + + fn deref(&self) -> &Path { + Path::new(&self.inner) + } +} + +impl Borrow for PathBuf { + fn borrow(&self) -> &Path { + self.deref() + } +} + +impl<'a> From<&'a Path> for Cow<'a, Path> { + #[inline] + fn from(s: &'a Path) -> Cow<'a, Path> { + Cow::Borrowed(s) + } +} + +impl<'a> From for Cow<'a, Path> { + #[inline] + fn from(s: PathBuf) -> Cow<'a, Path> { + Cow::Owned(s) + } +} + +impl ToOwned for Path { + type Owned = PathBuf; + fn to_owned(&self) -> PathBuf { + self.to_path_buf() + } +} + +impl cmp::PartialEq for PathBuf { + fn eq(&self, other: &PathBuf) -> bool { + self.components() == other.components() + } +} + +impl Hash for PathBuf { + fn hash(&self, h: &mut H) { + self.as_path().hash(h) + } +} + +impl cmp::Eq for PathBuf {} + +impl cmp::PartialOrd for PathBuf { + fn partial_cmp(&self, other: &PathBuf) -> Option { + self.components().partial_cmp(other.components()) + } +} + +impl cmp::Ord for PathBuf { + fn cmp(&self, other: &PathBuf) -> cmp::Ordering { + self.components().cmp(other.components()) + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &OsStr { + &self.inner[..] + } +} + +impl Into for PathBuf { + fn into(self) -> OsString { + self.inner + } +} + +/// A slice of a path (akin to `str`). +/// +/// This type supports a number of operations for inspecting a path, including +/// breaking the path into its components (separated by `/` or `\`, depending on +/// the platform), extracting the file name, determining whether the path is +/// absolute, and so on. More details about the overall approach can be found in +/// the module documentation. +/// +/// This is an *unsized* type, meaning that it must always be used behind a +/// pointer like `&` or `Box`. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// let file = path.file_name(); +/// let extension = path.extension(); +/// let parent_dir = path.parent(); +/// ``` +/// +pub struct Path { + inner: OsStr, +} + +/// An error returned from the `Path::strip_prefix` method indicating that the +/// prefix was not found in `self`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StripPrefixError(()); + +impl Path { + // The following (private!) function allows construction of a path from a u8 + // slice, which is only safe when it is known to follow the OsStr encoding. + unsafe fn from_u8_slice(s: &[u8]) -> &Path { + Path::new(u8_slice_as_os_str(s)) + } + // The following (private!) function reveals the byte encoding used for OsStr. + fn as_u8_slice(&self) -> &[u8] { + os_str_as_u8_slice(&self.inner) + } + + /// Directly wrap a string slice as a `Path` slice. + /// + /// This is a cost-free conversion. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// Path::new("foo.txt"); + /// ``` + /// + /// You can create `Path`s from `String`s, or even other `Path`s: + /// + /// ``` + /// use std::path::Path; + /// + /// let string = String::from("foo.txt"); + /// let from_string = Path::new(&string); + /// let from_path = Path::new(&from_string); + /// assert_eq!(from_string, from_path); + /// ``` + pub fn new + ?Sized>(s: &S) -> &Path { + unsafe { mem::transmute(s.as_ref()) } + } + + /// Yields the underlying `OsStr` slice. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let os_str = Path::new("foo.txt").as_os_str(); + /// assert_eq!(os_str, std::ffi::OsStr::new("foo.txt")); + /// ``` + pub fn as_os_str(&self) -> &OsStr { + &self.inner + } + + /// Yields a `&str` slice if the `Path` is valid unicode. + /// + /// This conversion may entail doing a check for UTF-8 validity. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path_str = Path::new("foo.txt").to_str(); + /// assert_eq!(path_str, Some("foo.txt")); + /// ``` + pub fn to_str(&self) -> Option<&str> { + self.inner.to_str() + } + + /// Converts a `Path` to a `Cow`. + /// + /// Any non-Unicode sequences are replaced with U+FFFD REPLACEMENT CHARACTER. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path_str = Path::new("foo.txt").to_string_lossy(); + /// assert_eq!(path_str, "foo.txt"); + /// ``` + pub fn to_string_lossy(&self) -> Cow { + self.inner.to_string_lossy() + } + + /// Converts a `Path` to an owned `PathBuf`. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path_buf = Path::new("foo.txt").to_path_buf(); + /// assert_eq!(path_buf, std::path::PathBuf::from("foo.txt")); + /// ``` + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.inner.to_os_string()) + } + + /// A path is *absolute* if it is independent of the current directory. + /// + /// * On Unix, a path is absolute if it starts with the root, so + /// `is_absolute` and `has_root` are equivalent. + /// + /// * On Windows, a path is absolute if it has a prefix and starts with the + /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// assert!(!Path::new("foo.txt").is_absolute()); + /// ``` + #[allow(deprecated)] + pub fn is_absolute(&self) -> bool { + self.has_root() && (cfg!(unix) || self.prefix().is_some()) + } + + /// A path is *relative* if it is not absolute. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// assert!(Path::new("foo.txt").is_relative()); + /// ``` + pub fn is_relative(&self) -> bool { + !self.is_absolute() + } + + fn prefix(&self) -> Option { + self.components().prefix + } + + /// A path has a root if the body of the path begins with the directory separator. + /// + /// * On Unix, a path has a root if it begins with `/`. + /// + /// * On Windows, a path has a root if it: + /// * has no prefix and begins with a separator, e.g. `\\windows` + /// * has a prefix followed by a separator, e.g. `c:\windows` but not `c:windows` + /// * has any non-disk prefix, e.g. `\\server\share` + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// assert!(Path::new("/etc/passwd").has_root()); + /// ``` + pub fn has_root(&self) -> bool { + self.components().has_root() + } + + /// The path without its final component, if any. + /// + /// Returns `None` if the path terminates in a root or prefix. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/foo/bar"); + /// let parent = path.parent().unwrap(); + /// assert_eq!(parent, Path::new("/foo")); + /// + /// let grand_parent = parent.parent().unwrap(); + /// assert_eq!(grand_parent, Path::new("/")); + /// assert_eq!(grand_parent.parent(), None); + /// ``` + pub fn parent(&self) -> Option<&Path> { + let mut comps = self.components(); + let comp = comps.next_back(); + comp.and_then(|p| { + match p { + Component::Normal(_) | + Component::CurDir | + Component::ParentDir => Some(comps.as_path()), + _ => None, + } + }) + } + + /// The final component of the path, if it is a normal file. + /// + /// If the path terminates in `..`, `file_name` will return `None`. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use std::ffi::OsStr; + /// + /// let path = Path::new("foo.txt"); + /// let os_str = OsStr::new("foo.txt"); + /// + /// assert_eq!(Some(os_str), path.file_name()); + /// ``` + /// + /// # Other examples + /// + /// ``` + /// use std::path::Path; + /// use std::ffi::OsStr; + /// + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.//").file_name()); + /// assert_eq!(None, Path::new("foo.txt/..").file_name()); + /// ``` + pub fn file_name(&self) -> Option<&OsStr> { + self.components().next_back().and_then(|p| { + match p { + Component::Normal(p) => Some(p.as_ref()), + _ => None, + } + }) + } + + /// Returns a path that, when joined onto `base`, yields `self`. + /// + /// # Errors + /// + /// If `base` is not a prefix of `self` (i.e. `starts_with` + /// returns `false`), returns `Err`. + pub fn strip_prefix<'a, P: ?Sized>(&'a self, base: &'a P) + -> Result<&'a Path, StripPrefixError> + where P: AsRef + { + self._strip_prefix(base.as_ref()) + } + + fn _strip_prefix<'a>(&'a self, base: &'a Path) + -> Result<&'a Path, StripPrefixError> { + iter_after(self.components(), base.components()) + .map(|c| c.as_path()) + .ok_or(StripPrefixError(())) + } + + /// Determines whether `base` is a prefix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.starts_with("/etc")); + /// + /// assert!(!path.starts_with("/e")); + /// ``` + pub fn starts_with>(&self, base: P) -> bool { + self._starts_with(base.as_ref()) + } + + fn _starts_with(&self, base: &Path) -> bool { + iter_after(self.components(), base.components()).is_some() + } + + /// Determines whether `child` is a suffix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.ends_with("passwd")); + /// ``` + pub fn ends_with>(&self, child: P) -> bool { + self._ends_with(child.as_ref()) + } + + fn _ends_with(&self, child: &Path) -> bool { + iter_after(self.components().rev(), child.components().rev()).is_some() + } + + /// Extracts the stem (non-extension) portion of `self.file_name()`. + /// + /// The stem is: + /// + /// * None, if there is no file name; + /// * The entire file name if there is no embedded `.`; + /// * The entire file name if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name before the final `.` + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("foo", path.file_stem().unwrap()); + /// ``` + pub fn file_stem(&self) -> Option<&OsStr> { + self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.or(after)) + } + + /// Extracts the extension of `self.file_name()`, if possible. + /// + /// The extension is: + /// + /// * None, if there is no file name; + /// * None, if there is no embedded `.`; + /// * None, if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name after the final `.` + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("rs", path.extension().unwrap()); + /// ``` + pub fn extension(&self) -> Option<&OsStr> { + self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.and(after)) + } + + /// Creates an owned `PathBuf` with `path` adjoined to `self`. + /// + /// See `PathBuf::push` for more details on what it means to adjoin a path. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// + /// assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd")); + /// ``` + pub fn join>(&self, path: P) -> PathBuf { + self._join(path.as_ref()) + } + + fn _join(&self, path: &Path) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.push(path); + buf + } + + /// Creates an owned `PathBuf` like `self` but with the given file name. + /// + /// See `PathBuf::set_file_name` for more details. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// + /// let path = Path::new("/tmp/foo.txt"); + /// assert_eq!(path.with_file_name("bar.txt"), PathBuf::from("/tmp/bar.txt")); + /// ``` + pub fn with_file_name>(&self, file_name: S) -> PathBuf { + self._with_file_name(file_name.as_ref()) + } + + fn _with_file_name(&self, file_name: &OsStr) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.set_file_name(file_name); + buf + } + + /// Creates an owned `PathBuf` like `self` but with the given extension. + /// + /// See `PathBuf::set_extension` for more details. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// + /// let path = Path::new("foo.rs"); + /// assert_eq!(path.with_extension("txt"), PathBuf::from("foo.txt")); + /// ``` + pub fn with_extension>(&self, extension: S) -> PathBuf { + self._with_extension(extension.as_ref()) + } + + fn _with_extension(&self, extension: &OsStr) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.set_extension(extension); + buf + } + + /// Produce an iterator over the components of the path. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, Component}; + /// use std::ffi::OsStr; + /// + /// let mut components = Path::new("/tmp/foo.txt").components(); + /// + /// assert_eq!(components.next(), Some(Component::RootDir)); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("tmp")))); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("foo.txt")))); + /// assert_eq!(components.next(), None) + /// ``` + pub fn components(&self) -> Components { + let prefix = parse_prefix(self.as_os_str()); + Components { + path: self.as_u8_slice(), + prefix: prefix, + has_physical_root: has_physical_root(self.as_u8_slice(), prefix), + front: State::Prefix, + back: State::Body, + } + } + + /// Produce an iterator over the path's components viewed as `OsStr` slices. + /// + /// # Examples + /// + /// ``` + /// use std::path::{self, Path}; + /// use std::ffi::OsStr; + /// + /// let mut it = Path::new("/tmp/foo.txt").iter(); + /// assert_eq!(it.next(), Some(OsStr::new(&path::MAIN_SEPARATOR.to_string()))); + /// assert_eq!(it.next(), Some(OsStr::new("tmp"))); + /// assert_eq!(it.next(), Some(OsStr::new("foo.txt"))); + /// assert_eq!(it.next(), None) + /// ``` + pub fn iter(&self) -> Iter { + Iter { inner: self.components() } + } + + /// Returns an object that implements `Display` for safely printing paths + /// that may contain non-Unicode data. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/tmp/foo.rs"); + /// + /// println!("{}", path.display()); + /// ``` + pub fn display(&self) -> Display { + Display { path: self } + } + + + //NOTE: The following functions rely on filesystem functionality that + //probably have to be implemented in ctru-rs instead of this library, + //and thus are commented out + + /* + /// Query the file system to get information about a file, directory, etc. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. + /// + /// This is an alias to [`fs::metadata`]. + /// + /// [`fs::metadata`]: ../fs/fn.metadata.html + pub fn metadata(&self) -> io::Result { + fs::metadata(self) + } + + /// Query the metadata about a file without following symlinks. + /// + /// This is an alias to [`fs::symlink_metadata`]. + /// + /// [`fs::symlink_metadata`]: ../fs/fn.symlink_metadata.html + + pub fn symlink_metadata(&self) -> io::Result { + fs::symlink_metadata(self) + } + + + /// Returns the canonical form of the path with all intermediate components + /// normalized and symbolic links resolved. + /// + /// This is an alias to [`fs::canonicalize`]. + /// + /// [`fs::canonicalize`]: ../fs/fn.canonicalize.html + + pub fn canonicalize(&self) -> io::Result { + fs::canonicalize(self) + } + + + /// Reads a symbolic link, returning the file that the link points to. + /// + /// This is an alias to [`fs::read_link`]. + /// + /// [`fs::read_link`]: ../fs/fn.read_link.html + + pub fn read_link(&self) -> io::Result { + fs::read_link(self) + } + + + /// Returns an iterator over the entries within a directory. + /// + /// The iterator will yield instances of `io::Result`. New errors may + /// be encountered after an iterator is initially constructed. + /// + /// This is an alias to [`fs::read_dir`]. + /// + /// [`fs::read_dir`]: ../fs/fn.read_dir.html + + pub fn read_dir(&self) -> io::Result { + fs::read_dir(self) + } + + + /// Returns whether the path points at an existing entity. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use std::path::Path; + /// assert_eq!(Path::new("does_not_exist.txt").exists(), false); + /// ``` + + pub fn exists(&self) -> bool { + fs::metadata(self).is_ok() + } + + + /// Returns whether the path is pointing at a regular file. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use std::path::Path; + /// assert_eq!(Path::new("./is_a_directory/").is_file(), false); + /// assert_eq!(Path::new("a_file.txt").is_file(), true); + /// ``` + + pub fn is_file(&self) -> bool { + fs::metadata(self).map(|m| m.is_file()).unwrap_or(false) + } + + + /// Returns whether the path is pointing at a directory. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use std::path::Path; + /// assert_eq!(Path::new("./is_a_directory/").is_dir(), true); + /// assert_eq!(Path::new("a_file.txt").is_dir(), false); + /// ``` + + pub fn is_dir(&self) -> bool { + fs::metadata(self).map(|m| m.is_dir()).unwrap_or(false) + } + */ +} + +impl AsRef for Path { + fn as_ref(&self) -> &OsStr { + &self.inner + } +} + +impl fmt::Debug for Path { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.inner.fmt(formatter) + } +} + +/// Helper struct for safely printing paths with `format!()` and `{}` +pub struct Display<'a> { + path: &'a Path, +} + +impl<'a> fmt::Debug for Display<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.path.to_string_lossy(), f) + } +} + +impl<'a> fmt::Display for Display<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.path.to_string_lossy(), f) + } +} + +impl cmp::PartialEq for Path { + fn eq(&self, other: &Path) -> bool { + self.components().eq(other.components()) + } +} + +impl Hash for Path { + fn hash(&self, h: &mut H) { + for component in self.components() { + component.hash(h); + } + } +} + +impl cmp::Eq for Path {} + +impl cmp::PartialOrd for Path { + fn partial_cmp(&self, other: &Path) -> Option { + self.components().partial_cmp(other.components()) + } +} + +impl cmp::Ord for Path { + fn cmp(&self, other: &Path) -> cmp::Ordering { + self.components().cmp(other.components()) + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &Path { + self + } +} + +impl AsRef for OsStr { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl<'a> AsRef for Cow<'a, OsStr> { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for OsString { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for str { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for String { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + self + } +} + +impl<'a> IntoIterator for &'a PathBuf { + type Item = &'a OsStr; + type IntoIter = Iter<'a>; + fn into_iter(self) -> Iter<'a> { self.iter() } +} + +impl<'a> IntoIterator for &'a Path { + type Item = &'a OsStr; + type IntoIter = Iter<'a>; + fn into_iter(self) -> Iter<'a> { self.iter() } +} + +macro_rules! impl_cmp { + ($lhs:ty, $rhs: ty) => { + impl<'a, 'b> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { ::eq(self, other) } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { ::eq(self, other) } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self, other) + } + } + } +} + +impl_cmp!(PathBuf, Path); +impl_cmp!(PathBuf, &'a Path); +impl_cmp!(Cow<'a, Path>, Path); +impl_cmp!(Cow<'a, Path>, &'b Path); +impl_cmp!(Cow<'a, Path>, PathBuf); + +macro_rules! impl_cmp_os_str { + ($lhs:ty, $rhs: ty) => { + impl<'a, 'b> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { ::eq(self, other.as_ref()) } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { ::eq(self.as_ref(), other) } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other.as_ref()) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self.as_ref(), other) + } + } + } +} + +impl_cmp_os_str!(PathBuf, OsStr); +impl_cmp_os_str!(PathBuf, &'a OsStr); +impl_cmp_os_str!(PathBuf, Cow<'a, OsStr>); +impl_cmp_os_str!(PathBuf, OsString); +impl_cmp_os_str!(Path, OsStr); +impl_cmp_os_str!(Path, &'a OsStr); +impl_cmp_os_str!(Path, Cow<'a, OsStr>); +impl_cmp_os_str!(Path, OsString); +impl_cmp_os_str!(&'a Path, OsStr); +impl_cmp_os_str!(&'a Path, Cow<'b, OsStr>); +impl_cmp_os_str!(&'a Path, OsString); +impl_cmp_os_str!(Cow<'a, Path>, OsStr); +impl_cmp_os_str!(Cow<'a, Path>, &'b OsStr); +impl_cmp_os_str!(Cow<'a, Path>, OsString); + +/* +impl fmt::Display for StripPrefixError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.description().fmt(f) + } +} +*/ + +/* +impl Error for StripPrefixError { + fn description(&self) -> &str { "prefix not found" } +} +*/ + +#[cfg(test)] +mod tests { + use super::*; + use collections::string::{ToString, String}; + use collections::borrow; + use collections::Vec; + + macro_rules! t( + ($path:expr, iter: $iter:expr) => ( + { + let path = Path::new($path); + + // Forward iteration + let comps = path.iter() + .map(|p| p.to_string_lossy().into_owned()) + .collect::>(); + let exp: &[&str] = &$iter; + let exps = exp.iter().map(|s| s.to_string()).collect::>(); + assert!(comps == exps, "iter: Expected {:?}, found {:?}", + exps, comps); + + // Reverse iteration + let comps = Path::new($path).iter().rev() + .map(|p| p.to_string_lossy().into_owned()) + .collect::>(); + let exps = exps.into_iter().rev().collect::>(); + assert!(comps == exps, "iter().rev(): Expected {:?}, found {:?}", + exps, comps); + } + ); + + ($path:expr, has_root: $has_root:expr, is_absolute: $is_absolute:expr) => ( + { + let path = Path::new($path); + + let act_root = path.has_root(); + assert!(act_root == $has_root, "has_root: Expected {:?}, found {:?}", + $has_root, act_root); + + let act_abs = path.is_absolute(); + assert!(act_abs == $is_absolute, "is_absolute: Expected {:?}, found {:?}", + $is_absolute, act_abs); + } + ); + + ($path:expr, parent: $parent:expr, file_name: $file:expr) => ( + { + let path = Path::new($path); + + let parent = path.parent().map(|p| p.to_str().unwrap()); + let exp_parent: Option<&str> = $parent; + assert!(parent == exp_parent, "parent: Expected {:?}, found {:?}", + exp_parent, parent); + + let file = path.file_name().map(|p| p.to_str().unwrap()); + let exp_file: Option<&str> = $file; + assert!(file == exp_file, "file_name: Expected {:?}, found {:?}", + exp_file, file); + } + ); + + ($path:expr, file_stem: $file_stem:expr, extension: $extension:expr) => ( + { + let path = Path::new($path); + + let stem = path.file_stem().map(|p| p.to_str().unwrap()); + let exp_stem: Option<&str> = $file_stem; + assert!(stem == exp_stem, "file_stem: Expected {:?}, found {:?}", + exp_stem, stem); + + let ext = path.extension().map(|p| p.to_str().unwrap()); + let exp_ext: Option<&str> = $extension; + assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}", + exp_ext, ext); + } + ); + + ($path:expr, iter: $iter:expr, + has_root: $has_root:expr, is_absolute: $is_absolute:expr, + parent: $parent:expr, file_name: $file:expr, + file_stem: $file_stem:expr, extension: $extension:expr) => ( + { + t!($path, iter: $iter); + t!($path, has_root: $has_root, is_absolute: $is_absolute); + t!($path, parent: $parent, file_name: $file); + t!($path, file_stem: $file_stem, extension: $extension); + } + ); + ); + + #[test] + fn into() { + use collections::borrow::Cow; + + let static_path = Path::new("/home/foo"); + let static_cow_path: Cow<'static, Path> = static_path.into(); + let pathbuf = PathBuf::from("/home/foo"); + + { + let path: &Path = &pathbuf; + let borrowed_cow_path: Cow = path.into(); + + assert_eq!(static_cow_path, borrowed_cow_path); + } + + let owned_cow_path: Cow<'static, Path> = pathbuf.into(); + + assert_eq!(static_cow_path, owned_cow_path); + } + + #[test] + #[cfg(unix)] + pub fn test_decompositions_unix() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/", + iter: ["/"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/foo/", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("/foo/bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("///foo///", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("///foo///bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./.", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/..", + iter: ["/", ".."], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("../", + iter: [".."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/.", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/./bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("foo/../", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("./", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/./b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None + ); + + t!(".foo", + iter: [".foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some(".foo"), + file_stem: Some(".foo"), + extension: None + ); + } + + #[test] + #[cfg(windows)] + pub fn test_decompositions_windows() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/", + iter: ["\\"], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\", + iter: ["\\"], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:", + iter: ["c:"], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:\\", + iter: ["c:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:/", + iter: ["c:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/foo/", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("/foo/bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("///foo///", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("///foo///bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./.", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/..", + iter: ["\\", ".."], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("../", + iter: [".."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/.", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/./bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("foo/../", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("./", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/./b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None); + + t!("a\\b\\c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a\\b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None + ); + + t!("\\a", + iter: ["\\", "a"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!("c:\\foo.txt", + iter: ["c:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("c:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\server\\share\\foo.txt", + iter: ["\\\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\server\\share", + iter: ["\\\\server\\share", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\server", + iter: ["\\", "server"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("server"), + file_stem: Some("server"), + extension: None + ); + + t!("\\\\?\\bar\\foo.txt", + iter: ["\\\\?\\bar", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\bar\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\?\\bar", + iter: ["\\\\?\\bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\", + iter: ["\\\\?\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\UNC\\server\\share\\foo.txt", + iter: ["\\\\?\\UNC\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\UNC\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\?\\UNC\\server", + iter: ["\\\\?\\UNC\\server"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\UNC\\", + iter: ["\\\\?\\UNC\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\C:\\foo.txt", + iter: ["\\\\?\\C:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + + t!("\\\\?\\C:\\", + iter: ["\\\\?\\C:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\C:", + iter: ["\\\\?\\C:"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\foo/bar", + iter: ["\\\\?\\foo/bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\C:/foo", + iter: ["\\\\?\\C:/foo"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo\\bar", + iter: ["\\\\.\\foo", "\\", "bar"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + + t!("\\\\.\\foo", + iter: ["\\\\.\\foo", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo/bar", + iter: ["\\\\.\\foo/bar", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo\\bar/baz", + iter: ["\\\\.\\foo", "\\", "bar", "baz"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\bar"), + file_name: Some("baz"), + file_stem: Some("baz"), + extension: None + ); + + + t!("\\\\.\\", + iter: ["\\\\.\\", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\a\\b\\", + iter: ["\\\\?\\a", "\\", "b"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\a\\"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + } + + #[test] + pub fn test_stem_ext() { + t!("foo", + file_stem: Some("foo"), + extension: None + ); + + t!("foo.", + file_stem: Some("foo"), + extension: Some("") + ); + + t!(".foo", + file_stem: Some(".foo"), + extension: None + ); + + t!("foo.txt", + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("foo.bar.txt", + file_stem: Some("foo.bar"), + extension: Some("txt") + ); + + t!("foo.bar.", + file_stem: Some("foo.bar"), + extension: Some("") + ); + + t!(".", + file_stem: None, + extension: None + ); + + t!("..", + file_stem: None, + extension: None + ); + + t!("", + file_stem: None, + extension: None + ); + } + + #[test] + pub fn test_push() { + macro_rules! tp( + ($path:expr, $push:expr, $expected:expr) => ( { + let mut actual = PathBuf::from($path); + actual.push($push); + assert!(actual.to_str() == Some($expected), + "pushing {:?} onto {:?}: Expected {:?}, got {:?}", + $push, $path, $expected, actual.to_str().unwrap()); + }); + ); + + if cfg!(unix) { + tp!("", "foo", "foo"); + tp!("foo", "bar", "foo/bar"); + tp!("foo/", "bar", "foo/bar"); + tp!("foo//", "bar", "foo//bar"); + tp!("foo/.", "bar", "foo/./bar"); + tp!("foo./.", "bar", "foo././bar"); + tp!("foo", "", "foo/"); + tp!("foo", ".", "foo/."); + tp!("foo", "..", "foo/.."); + tp!("foo", "/", "/"); + tp!("/foo/bar", "/", "/"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", "./baz", "/foo/bar/./baz"); + } else { + tp!("", "foo", "foo"); + tp!("foo", "bar", r"foo\bar"); + tp!("foo/", "bar", r"foo/bar"); + tp!(r"foo\", "bar", r"foo\bar"); + tp!("foo//", "bar", r"foo//bar"); + tp!(r"foo\\", "bar", r"foo\\bar"); + tp!("foo/.", "bar", r"foo/.\bar"); + tp!("foo./.", "bar", r"foo./.\bar"); + tp!(r"foo\.", "bar", r"foo\.\bar"); + tp!(r"foo.\.", "bar", r"foo.\.\bar"); + tp!("foo", "", "foo\\"); + tp!("foo", ".", r"foo\."); + tp!("foo", "..", r"foo\.."); + tp!("foo", "/", "/"); + tp!("foo", r"\", r"\"); + tp!("/foo/bar", "/", "/"); + tp!(r"\foo\bar", r"\", r"\"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", r"\baz", r"\baz"); + tp!("/foo/bar", "./baz", r"/foo/bar\./baz"); + tp!("/foo/bar", r".\baz", r"/foo/bar\.\baz"); + + tp!("c:\\", "windows", "c:\\windows"); + tp!("c:", "windows", "c:windows"); + + tp!("a\\b\\c", "d", "a\\b\\c\\d"); + tp!("\\a\\b\\c", "d", "\\a\\b\\c\\d"); + tp!("a\\b", "c\\d", "a\\b\\c\\d"); + tp!("a\\b", "\\c\\d", "\\c\\d"); + tp!("a\\b", ".", "a\\b\\."); + tp!("a\\b", "..\\c", "a\\b\\..\\c"); + tp!("a\\b", "C:a.txt", "C:a.txt"); + tp!("a\\b", "C:\\a.txt", "C:\\a.txt"); + tp!("C:\\a", "C:\\b.txt", "C:\\b.txt"); + tp!("C:\\a\\b\\c", "C:d", "C:d"); + tp!("C:a\\b\\c", "C:d", "C:d"); + tp!("C:", r"a\b\c", r"C:a\b\c"); + tp!("C:", r"..\a", r"C:..\a"); + tp!("\\\\server\\share\\foo", + "bar", + "\\\\server\\share\\foo\\bar"); + tp!("\\\\server\\share\\foo", "C:baz", "C:baz"); + tp!("\\\\?\\C:\\a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:\\a\\b", "C:\\c\\d", "C:\\c\\d"); + tp!("\\\\?\\foo\\bar", "baz", "\\\\?\\foo\\bar\\baz"); + tp!("\\\\?\\UNC\\server\\share\\foo", + "bar", + "\\\\?\\UNC\\server\\share\\foo\\bar"); + tp!("\\\\?\\UNC\\server\\share", "C:\\a", "C:\\a"); + tp!("\\\\?\\UNC\\server\\share", "C:a", "C:a"); + + // Note: modified from old path API + tp!("\\\\?\\UNC\\server", "foo", "\\\\?\\UNC\\server\\foo"); + + tp!("C:\\a", + "\\\\?\\UNC\\server\\share", + "\\\\?\\UNC\\server\\share"); + tp!("\\\\.\\foo\\bar", "baz", "\\\\.\\foo\\bar\\baz"); + tp!("\\\\.\\foo\\bar", "C:a", "C:a"); + // again, not sure about the following, but I'm assuming \\.\ should be verbatim + tp!("\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar"); + + tp!("\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one + } + } + + #[test] + pub fn test_pop() { + macro_rules! tp( + ($path:expr, $expected:expr, $output:expr) => ( { + let mut actual = PathBuf::from($path); + let output = actual.pop(); + assert!(actual.to_str() == Some($expected) && output == $output, + "popping from {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $expected, $output, + actual.to_str().unwrap(), output); + }); + ); + + tp!("", "", false); + tp!("/", "/", false); + tp!("foo", "", true); + tp!(".", "", true); + tp!("/foo", "/", true); + tp!("/foo/bar", "/foo", true); + tp!("foo/bar", "foo", true); + tp!("foo/.", "", true); + tp!("foo//bar", "foo", true); + + if cfg!(windows) { + tp!("a\\b\\c", "a\\b", true); + tp!("\\a", "\\", true); + tp!("\\", "\\", false); + + tp!("C:\\a\\b", "C:\\a", true); + tp!("C:\\a", "C:\\", true); + tp!("C:\\", "C:\\", false); + tp!("C:a\\b", "C:a", true); + tp!("C:a", "C:", true); + tp!("C:", "C:", false); + tp!("\\\\server\\share\\a\\b", "\\\\server\\share\\a", true); + tp!("\\\\server\\share\\a", "\\\\server\\share\\", true); + tp!("\\\\server\\share", "\\\\server\\share", false); + tp!("\\\\?\\a\\b\\c", "\\\\?\\a\\b", true); + tp!("\\\\?\\a\\b", "\\\\?\\a\\", true); + tp!("\\\\?\\a", "\\\\?\\a", false); + tp!("\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", true); + tp!("\\\\?\\C:\\a", "\\\\?\\C:\\", true); + tp!("\\\\?\\C:\\", "\\\\?\\C:\\", false); + tp!("\\\\?\\UNC\\server\\share\\a\\b", + "\\\\?\\UNC\\server\\share\\a", + true); + tp!("\\\\?\\UNC\\server\\share\\a", + "\\\\?\\UNC\\server\\share\\", + true); + tp!("\\\\?\\UNC\\server\\share", + "\\\\?\\UNC\\server\\share", + false); + tp!("\\\\.\\a\\b\\c", "\\\\.\\a\\b", true); + tp!("\\\\.\\a\\b", "\\\\.\\a\\", true); + tp!("\\\\.\\a", "\\\\.\\a", false); + + tp!("\\\\?\\a\\b\\", "\\\\?\\a\\", true); + } + } + + #[test] + pub fn test_set_file_name() { + macro_rules! tfn( + ($path:expr, $file:expr, $expected:expr) => ( { + let mut p = PathBuf::from($path); + p.set_file_name($file); + assert!(p.to_str() == Some($expected), + "setting file name of {:?} to {:?}: Expected {:?}, got {:?}", + $path, $file, $expected, + p.to_str().unwrap()); + }); + ); + + tfn!("foo", "foo", "foo"); + tfn!("foo", "bar", "bar"); + tfn!("foo", "", ""); + tfn!("", "foo", "foo"); + if cfg!(unix) { + tfn!(".", "foo", "./foo"); + tfn!("foo/", "bar", "bar"); + tfn!("foo/.", "bar", "bar"); + tfn!("..", "foo", "../foo"); + tfn!("foo/..", "bar", "foo/../bar"); + tfn!("/", "foo", "/foo"); + } else { + tfn!(".", "foo", r".\foo"); + tfn!(r"foo\", "bar", r"bar"); + tfn!(r"foo\.", "bar", r"bar"); + tfn!("..", "foo", r"..\foo"); + tfn!(r"foo\..", "bar", r"foo\..\bar"); + tfn!(r"\", "foo", r"\foo"); + } + } + + #[test] + pub fn test_set_extension() { + macro_rules! tfe( + ($path:expr, $ext:expr, $expected:expr, $output:expr) => ( { + let mut p = PathBuf::from($path); + let output = p.set_extension($ext); + assert!(p.to_str() == Some($expected) && output == $output, + "setting extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $ext, $expected, $output, + p.to_str().unwrap(), output); + }); + ); + + tfe!("foo", "txt", "foo.txt", true); + tfe!("foo.bar", "txt", "foo.txt", true); + tfe!("foo.bar.baz", "txt", "foo.bar.txt", true); + tfe!(".test", "txt", ".test.txt", true); + tfe!("foo.txt", "", "foo", true); + tfe!("foo", "", "foo", true); + tfe!("", "foo", "", false); + tfe!(".", "foo", ".", false); + tfe!("foo/", "bar", "foo.bar", true); + tfe!("foo/.", "bar", "foo.bar", true); + tfe!("..", "foo", "..", false); + tfe!("foo/..", "bar", "foo/..", false); + tfe!("/", "foo", "/", false); + } + + #[test] + fn test_eq_recievers() { + use collections::borrow::Cow; + + let borrowed: &Path = Path::new("foo/bar"); + let mut owned: PathBuf = PathBuf::new(); + owned.push("foo"); + owned.push("bar"); + let borrowed_cow: Cow = borrowed.into(); + let owned_cow: Cow = owned.clone().into(); + + macro_rules! t { + ($($current:expr),+) => { + $( + assert_eq!($current, borrowed); + assert_eq!($current, owned); + assert_eq!($current, borrowed_cow); + assert_eq!($current, owned_cow); + )+ + } + } + + t!(borrowed, owned, borrowed_cow, owned_cow); + } + + #[test] + pub fn test_compare() { + use core::hash::{Hash, Hasher, SipHasher}; + + fn hash(t: T) -> u64 { + let mut s = SipHasher::new_with_keys(0, 0); + t.hash(&mut s); + s.finish() + } + + macro_rules! tc( + ($path1:expr, $path2:expr, eq: $eq:expr, + starts_with: $starts_with:expr, ends_with: $ends_with:expr, + relative_from: $relative_from:expr) => ({ + let path1 = Path::new($path1); + let path2 = Path::new($path2); + + let eq = path1 == path2; + assert!(eq == $eq, "{:?} == {:?}, expected {:?}, got {:?}", + $path1, $path2, $eq, eq); + assert!($eq == (hash(path1) == hash(path2)), + "{:?} == {:?}, expected {:?}, got {} and {}", + $path1, $path2, $eq, hash(path1), hash(path2)); + + let starts_with = path1.starts_with(path2); + assert!(starts_with == $starts_with, + "{:?}.starts_with({:?}), expected {:?}, got {:?}", $path1, $path2, + $starts_with, starts_with); + + let ends_with = path1.ends_with(path2); + assert!(ends_with == $ends_with, + "{:?}.ends_with({:?}), expected {:?}, got {:?}", $path1, $path2, + $ends_with, ends_with); + + let relative_from = path1.strip_prefix(path2) + .map(|p| p.to_str().unwrap()) + .ok(); + let exp: Option<&str> = $relative_from; + assert!(relative_from == exp, + "{:?}.strip_prefix({:?}), expected {:?}, got {:?}", + $path1, $path2, exp, relative_from); + }); + ); + + tc!("", "", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo", "", + eq: false, + starts_with: true, + ends_with: true, + relative_from: Some("foo") + ); + + tc!("", "foo", + eq: false, + starts_with: false, + ends_with: false, + relative_from: None + ); + + tc!("foo", "foo", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo/", "foo", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo/bar", "foo", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("bar") + ); + + tc!("foo/bar/baz", "foo/bar", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("baz") + ); + + tc!("foo/bar", "foo/bar/baz", + eq: false, + starts_with: false, + ends_with: false, + relative_from: None + ); + + tc!("./foo/bar/", ".", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("foo/bar") + ); + + if cfg!(windows) { + tc!(r"C:\src\rust\cargo-test\test\Cargo.toml", + r"c:\src\rust\cargo-test\test", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("Cargo.toml") + ); + + tc!(r"c:\foo", r"C:\foo", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + } + } +} diff --git a/src/services/apt.rs b/src/services/apt.rs index 28e0500..69094b2 100644 --- a/src/services/apt.rs +++ b/src/services/apt.rs @@ -66,26 +66,6 @@ impl Apt { } } - - pub fn get_status(&self) -> AppStatus { - unsafe { apt::aptGetStatus().into() } - } - - pub fn set_status(&mut self, status: AppStatus) { - unsafe { apt::aptSetStatus(status.into()) }; - } - - /// Return to the home menu. - /// - /// When `get_status` returns `AppStatus::Suspending`, you should call this, - /// otherwise the app will be left stuck in that state. - /// - /// The program will not return from this function until the system returns - /// to the application, or when the status changes to `AppStatus::Exiting`. - pub fn return_to_menu(&mut self) { - unsafe { apt::aptReturnToMenu() } - } - pub fn main_loop(&self) -> bool { unsafe { match apt::aptMainLoop() { diff --git a/src/services/fs.rs b/src/services/fs.rs new file mode 100644 index 0000000..d4aedc4 --- /dev/null +++ b/src/services/fs.rs @@ -0,0 +1,892 @@ +//! Filesystem service +//! +//! This module contains basic methods to manipulate the contents of the 3DS's filesystem. +//! Only the SD card is currently supported. + + +use core::marker::PhantomData; +use core::ptr; +use core::slice; +use core::mem; +use alloc::arc::Arc; +use collections::Vec; + +use path::{Path, PathBuf}; +use ffi::OsString; + +use libctru::services::fs::*; + +#[derive(Copy, Clone, Debug)] +pub enum PathType { + Invalid, + Empty, + Binary, + ASCII, + UTF16, +} + +#[derive(Copy, Clone, Debug)] +pub enum ArchiveID { + RomFS, + Savedata, + Extdata, + SharedExtdata, + SystemSavedata, + Sdmc, + SdmcWriteOnly, + BossExtdata, + CardSpiFS, + ExtDataAndBossExtdata, + SystemSaveData2, + NandRW, + NandRO, + NandROWriteAccess, + SaveDataAndContent, + SaveDataAndContent2, + NandCtrFS, + TwlPhoto, + NandTwlFS, + GameCardSavedata, + UserSavedata, + DemoSavedata, +} + +/// Represents the filesystem service. No file IO can be performed +/// until an instance of this struct is created. +/// +/// The service exits when all instances of this struct go out of scope. +pub struct Fs { + pd: PhantomData, +} + +/// Handle to an open filesystem archive. +/// +/// Archives are automatically closed when they go out of scope. +/// +/// # Examples +/// +/// ```no_run +/// use ctru::services::fs::Fs +/// +/// let fs = Fs::init().unwrap(); +/// let sdmc_archive = fs.sdmc().unwrap(); +/// ``` +pub struct Archive { + id: ArchiveID, + handle: u64, +} + +/// A reference to an open file on the filesystem. +/// +/// An instance of a `File` can be read and/or written to depending +/// on what options It was opened with. +/// +/// Files are automatically closed when they go out of scope. +pub struct File { + handle: u32, + offset: u64, +} + +/// Metadata information about a file. +/// +/// This structure is returned from the [`metadata`] function and +/// represents known metadata about a file. +/// +/// [`metadata`]: fn.metadata.html +pub struct Metadata { + attributes: u32, + size: u64, +} + +/// Options and flags which can be used to configure how a [`File`] is opened. +/// This builder exposes the ability to configure how a `File` is opened +/// and what operations are permitted on the open file. The [`File::open`] +/// and [`File::create`] methods are aliases for commonly used options +/// using this builder. +/// +/// [`File`]: struct.File.html +/// [`File::open`]: struct.File.html#method.open +/// [`File::create`]: struct.File.html#method.create +/// +/// Generally speaking, when using `OpenOptions`, you'll first call [`new()`], +/// then chain calls to methods to set each option, then call [`open()`], +/// passing the path of the file you're trying to open. +/// +/// It is required to also pass a reference to the [`Archive`] that the +/// file lives in. +/// +/// [`new()`]: struct.OpenOptions.html#method.new +/// [`open()`]: struct.OpenOptions.html#method.open +/// [`Archive`]: struct.Archive.html +/// +/// # Examples +/// +/// Opening a file to read: +/// +/// ```no_run +/// use ctru::services::fs::OpenOptions; +/// +/// let fs = Fs::init().unwrap(); +/// let sdmc_archive = fs.sdmc().unwrap(); +/// let file = OpenOptions::new() +/// .read(true) +/// .archive(&sdmc_archive) +/// .open("foo.txt") +/// .unwrap(); +/// ``` +/// +/// Opening a file for both reading and writing, as well as creating it if it +/// doesn't exist: +/// +/// ```no_run +/// use ctru::services::fs::OpenOptions; +/// +/// let fs = Fs::init().unwrap(); +/// let sdmc_archive = fs.sdmc().unwrap(); +/// let file = OpenOptions::new() +/// .read(true) +/// .write(true) +/// .create(true) +/// .archive(&sdmc_archive) +/// .open("foo.txt") +/// .unwrap(); +/// ``` +#[derive(Clone)] +pub struct OpenOptions { + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + arch_handle: u64, +} + +/// Iterator over the entries in a directory. +/// +/// This iterator is returned from the [`read_dir`] function of this module and +/// will yield instances of `Result`. Through a [`DirEntry`] +/// information like the entry's path and possibly other metadata can be +/// learned. +/// +/// [`read_dir`]: fn.read_dir.html +/// [`DirEntry`]: struct.DirEntry.html +/// +/// # Errors +/// +/// This Result will return Err if there's some sort of intermittent IO error +/// during iteration. +pub struct ReadDir<'a> { + handle: Dir, + root: Arc, + arch: &'a Archive, +} + +/// Entries returned by the [`ReadDir`] iterator. +/// +/// [`ReadDir`]: struct.ReadDir.html +/// +/// An instance of `DirEntry` represents an entry inside of a directory on the +/// filesystem. Each entry can be inspected via methods to learn about the full +/// path or possibly other metadata. +pub struct DirEntry<'a> { + entry: FS_DirectoryEntry, + root: Arc, + arch: &'a Archive, +} + +#[doc(hidden)] +struct Dir(u32); + +#[doc(hidden)] +unsafe impl Send for Dir {} +#[doc(hidden)] +unsafe impl Sync for Dir {} + +impl Fs { + /// Initializes the FS service. + /// + /// # Errors + /// + /// This function will return Err if there was an error initializing the + /// FS service, which in practice should never happen unless there is + /// an error in the execution environment (i.e. the homebrew launcher + /// somehow fails to provide fs:USER permissions) + /// + /// ctrulib services are reference counted, so this function may be called + /// as many times as desired and the service will not exit until all + /// instances of Fs drop out of scope. + pub fn init() -> Result { + unsafe { + let r = fsInit(); + if r < 0 { + Err(r) + } else { + Ok(Fs { pd: PhantomData }) + } + } + } + + /// Returns a handle to the SDMC (memory card) Archive. + pub fn sdmc(&self) -> Result { + unsafe { + let mut handle = 0; + let id = ArchiveID::Sdmc; + let path = fsMakePath(PathType::Empty.into(), ptr::null() as _); + let r = FSUSER_OpenArchive(&mut handle, id.into(), path); + if r < 0 { + Err(r) + } else { + Ok(Archive { + handle: handle, + id: id, + }) + } + } + } +} + +impl Archive { + /// Retrieves an Archive's [`ArchiveID`] + /// + /// [`ArchiveID`]: enum.ArchiveID.html + pub fn get_id(&self) -> ArchiveID { + self.id + } +} + +impl File { + /// Attempts to open a file in read-only mode. + /// + /// See the [`OpenOptions::open`] method for more details. + /// + /// # Errors + /// + /// This function will return an error if `path` does not already exit. + /// Other errors may also be returned accoridng to [`OpenOptions::open`] + /// + /// [`OpenOptions::open`]: struct.OpenOptions.html#method.open + /// + /// # Examples + /// + /// ```no_run + /// use ctru::services::fs::{Fs, File}; + /// + /// let fs = Fs::init().unwrap() + /// let sdmc_archive = fs.sdmc().unwrap() + /// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap(); + /// ``` + pub fn open>(arch: &Archive, path: P) -> Result { + OpenOptions::new().read(true).archive(arch).open(path.as_ref()) + } + + /// Opens a file in write-only mode. + /// + /// This function will create a file if it does not exist. + /// + /// See the [`OpenOptions::create`] method for more details. + /// + /// # Errors + /// + /// This function will return an error if `path` does not already exit. + /// Other errors may also be returned accoridng to [`OpenOptions::create`] + /// + /// [`OpenOptions::create`]: struct.OpenOptions.html#method.create + /// + /// # Examples + /// + /// ```no_run + /// use ctru::services::fs::{Fs, File}; + /// + /// let fs = Fs::init().unwrap() + /// let sdmc_archive = fs.sdmc().unwrap() + /// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap(); + /// ``` + pub fn create>(arch: &Archive, path: P) -> Result { + OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) + } + + /// Truncates or extends the underlying file, updating the size of this file to become size. + /// + /// If the size is less than the current file's size, then the file will be shrunk. If it is + /// greater than the current file's size, then the file will be extended to size and have all + /// of the intermediate data filled in with 0s. + pub fn set_len(&mut self, size: u64) -> Result<(), i32> { + unsafe { + let r = FSFILE_SetSize(self.handle, size); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } + } + + /// Queries metadata about the underlying file. + pub fn metadata(&self) -> Result { + // The only metadata we have for files right now is file size. + // This is likely to change in the future. + unsafe { + let mut size = 0; + let r = FSFILE_GetSize(self.handle, &mut size); + if r < 0 { + Err(r) + } else { + Ok(Metadata { attributes: 0, size: size }) + } + } + } + + /// Pull some bytes from the file into the specified buffer, returning + /// how many bytes were read. + /// + /// This function will become private when std::io support is ported + /// to this library. + pub fn read(&mut self, buf: &mut [u8]) -> Result { + unsafe { + let mut n_read = 0; + let r = FSFILE_Read( + self.handle, + &mut n_read, + self.offset, + buf.as_mut_ptr() as _, + buf.len() as u32 + ); + self.offset += n_read as u64; + if r < 0 { + Err(r) + } else { + Ok(n_read as usize) + } + } + } + + /// Read all bytes until EOF in this source, placing them into buf. + /// + /// This function will become private when std::io support is ported + /// to this library. + pub fn read_to_end(&mut self, buf: &mut Vec) -> Result { + unsafe { + read_to_end_uninitialized(self, buf) + } + } + + /// Write a buffer into this object, returning how many bytes were written. + /// + /// This function will become private when std::io support is ported + /// to this library. + pub fn write(&mut self, buf: &[u8]) -> Result { + unsafe { + let mut n_written = 0; + let r = FSFILE_Write( + self.handle, + &mut n_written, + self.offset, + buf.as_ptr() as _, + buf.len() as u32, + FS_WRITE_UPDATE_TIME + ); + self.offset += n_written as u64; + if r < 0 { + Err(r) + } else { + Ok(n_written as usize) + } + } + } +} + +impl Metadata { + /// Returns whether this metadata is for a directory. + pub fn is_dir(&self) -> bool { + self.attributes == self.attributes | FS_ATTRIBUTE_DIRECTORY + } + + /// Returns whether this metadata is for a regular file. + pub fn is_file(&self) -> bool { + !self.is_dir() + } + + /// Returns the size, in bytes, this metadata is for. + /// + /// Directories return size = 0. + pub fn len(&self) -> u64 { + self.size + } +} + +impl OpenOptions { + /// Creates a blank set of options ready for configuration. + /// + /// All options are initially set to `false` + pub fn new() -> OpenOptions { + OpenOptions { + read: false, + write: false, + append: false, + truncate: false, + create: false, + arch_handle: 0, + } + } + + /// Sets the option for read access. + /// + /// This option, when true, will indicate that the file should be + /// `read`-able if opened. + pub fn read(&mut self, read: bool) -> &mut OpenOptions { + self.read = read; + self + } + + /// Sets the option for write access. + /// + /// This option, when true, will indicate that the file should be + /// `write`-able if opened. + /// + /// If the file already exists, any write calls on it will overwrite + /// its contents, without truncating it. + pub fn write(&mut self, write: bool) -> &mut OpenOptions { + self.write = write; + self + } + + /// Sets the option for the append mode. + /// + /// This option, when true, means that writes will append to a file instead + /// of overwriting previous contents. Note that setting .write(true).append(true) + /// has the same effect as setting only .append(true). + /// + /// If both truncate and append are set to true, the file will simply be truncated + pub fn append(&mut self, append: bool) -> &mut OpenOptions { + self.append = append; + self + } + + /// Sets the option for truncating a previous file. + /// + /// If a file is successfully opened with this option set it will truncate + /// the file to 0 length if it already exists. + /// + /// The file must be opened with write access for truncate to work. + pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions { + self.truncate = truncate; + self + } + + /// Sets the option for creating a new file. + /// + /// This option indicates whether a new file will be created + /// if the file does not yet already + /// exist. + /// + /// In order for the file to be created, write access must also be used. + pub fn create(&mut self, create: bool) -> &mut OpenOptions { + self.create = create; + self + } + + /// Sets which archive the file is to be opened in. + /// + /// Failing to pass in an archive will result in the file failing to open. + pub fn archive(&mut self, archive: &Archive) -> &mut OpenOptions { + self.arch_handle = archive.handle; + self + } + + /// Opens a file at `path` with the options specified by `self` + /// + /// # Errors + /// + /// This function will return an error under a number of different + /// circumstances, including but not limited to: + /// + /// * Opening a file that doesn't exist without setting `create`. + /// * Attempting to open a file without passing an [`Archive`] reference + /// to the `archive` method. + /// * Filesystem-level errors (full disk, etc). + /// * Invalid combinations of open options. + /// + /// [`Archive`]: struct.Archive.html + pub fn open>(&self, path: P) -> Result { + self._open(path.as_ref(), self.get_open_flags()) + } + + fn _open(&self, path: &Path, flags: u32) -> Result { + unsafe { + let mut file_handle = 0; + let path = to_utf16(path); + let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); + let r = FSUSER_OpenFile(&mut file_handle, self.arch_handle, fs_path, flags, 0); + if r < 0 { + return Err(r); + } + + let mut file = File { handle: file_handle, offset: 0 }; + + if self.append { + let metadata = try!(file.metadata()); + file.offset = metadata.len(); + } + + // set the offset to 0 just in case both append and truncate were + // set to true + if self.truncate { + try!(file.set_len(0)); + file.offset = 0; + } + Ok(file) + } + } + + fn get_open_flags(&self) -> u32 { + match (self.read, self.write || self.append, self.create) { + (true, false, false) => FS_OPEN_READ, + (false, true, false) => FS_OPEN_WRITE, + (false, true, true) => FS_OPEN_WRITE | FS_OPEN_CREATE, + (true, false, true) => FS_OPEN_READ | FS_OPEN_CREATE, + (true, true, false) => FS_OPEN_READ | FS_OPEN_WRITE, + (true, true, true) => FS_OPEN_READ | FS_OPEN_WRITE | FS_OPEN_CREATE, + _ => 0, //failure case + } + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = Result, i32>; + + fn next(&mut self) -> Option, i32>> { + unsafe { + let mut ret = DirEntry { + entry: mem::zeroed(), + root: self.root.clone(), + arch: self.arch, + }; + let mut entries_read = 0; + let entry_count = 1; + let r = FSDIR_Read(self.handle.0, &mut entries_read, entry_count, &mut ret.entry); + + if r < 0 { + return Some(Err(r)) + } + if entries_read != entry_count { + return None + } + Some(Ok(ret)) + } + } +} + +impl<'a> DirEntry<'a> { + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + pub fn path(&self) -> PathBuf { + self.root.join(&self.file_name()) + } + + /// Return the metadata for the file that this entry points at. + pub fn metadata(&self) -> Result { + metadata(self.arch, self.path()) + } + + /// Returns the bare file name of this directory entry without any other leading path + /// component. + pub fn file_name(&self) -> OsString { + let filename = truncate_utf16_at_nul(&self.entry.name); + OsString::from_wide(filename) + } +} + +/// Creates a new, empty directory at the provided path +/// +/// # Errors +/// +/// This function will return an error in the following situations, +/// but is not limited to just these cases: +/// +/// * User lacks permissions to create directory at `path` +pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let path = to_utf16(path.as_ref()); + let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); + let r = FSUSER_CreateDirectory(arch.handle, fs_path, FS_ATTRIBUTE_DIRECTORY); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + +/// Recursively create a directory and all of its parent components if they are missing. +/// +/// # Errors +/// +/// This function will return an error in the following situations, +/// but is not limited to just these cases: +/// +/// * If any directory in the path specified by `path` does not already exist +/// and it could not be created otherwise. +pub fn create_dir_all>(arch: &Archive, path: P) -> Result<(), i32> { + let path = path.as_ref(); + let mut dir = PathBuf::new(); + let mut result = Ok(()); + + for component in path.components() { + let component = component.as_os_str(); + dir.push(component); + result = create_dir(arch, &dir); + } + result +} + +/// Given a path, query the file system to get information about a file, directory, etc +pub fn metadata>(arch: &Archive, path: P) -> Result { + let maybe_file = File::open(&arch, path.as_ref()); + let maybe_dir = read_dir(&arch, path.as_ref()); + match (maybe_file, maybe_dir) { + (Ok(file), _) => file.metadata(), + (_, Ok(_dir)) => Ok(Metadata { attributes: FS_ATTRIBUTE_DIRECTORY, size: 0 }), + (Err(r), _) => Err(r), + } +} + +/// Removes an existing, empty directory. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not limited to just +/// these cases: +/// +/// * The user lacks permissions to remove the directory at the provided path. +/// * The directory isn't empty. +pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let path = to_utf16(path.as_ref()); + let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); + let r = FSUSER_DeleteDirectory(arch.handle, fs_path); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + +/// Removes a directory at this path, after removing all its contents. Use carefully! +/// +/// # Errors +/// +/// see `file::remove_file` and `fs::remove_dir` +pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let path = to_utf16(path.as_ref()); + let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); + let r = FSUSER_DeleteDirectoryRecursively(arch.handle, fs_path); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + +/// Returns an iterator over the entries within a directory. +/// +/// The iterator will yield instances of Result. New errors +/// may be encountered after an iterator is initially constructed. +/// +/// This function will return an error in the following situations, but is not limited to just +/// these cases: +/// +/// * The provided path doesn't exist. +/// * The process lacks permissions to view the contents. +/// * The path points at a non-directory file. +pub fn read_dir>(arch: &Archive, path: P) -> Result { + unsafe { + let mut handle = 0; + let root = Arc::new(path.as_ref().to_path_buf()); + let path = to_utf16(path.as_ref()); + let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); + let r = FSUSER_OpenDirectory(&mut handle, arch.handle, fs_path); + if r < 0 { + Err(r) + } else { + Ok(ReadDir { handle: Dir(handle), root: root, arch: arch}) + } + } +} + +/// Removes a file from the filesystem. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not limited to just +/// these cases: +/// +/// * path points to a directory. +/// * The user lacks permissions to remove the file. +pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let path = to_utf16(path.as_ref()); + let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); + let r = FSUSER_DeleteFile(arch.handle, fs_path); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + +/// Rename a file or directory to a new name, replacing the original file +/// if to already exists. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not limited to just +/// these cases: +/// +/// * from does not exist. +/// * The user lacks permissions to view contents. +pub fn rename(arch: &Archive, from: P, to: Q) -> Result<(), i32> + where P: AsRef, + Q: AsRef { + + unsafe { + let from = to_utf16(from.as_ref()); + let to = to_utf16(to.as_ref()); + + let fs_from = fsMakePath(PathType::UTF16.into(), from.as_ptr() as _); + let fs_to = fsMakePath(PathType::UTF16.into(), to.as_ptr() as _); + + let r = FSUSER_RenameFile(arch.handle, fs_from, arch.handle, fs_to); + if r == 0 { + return Ok(()) + } + let r = FSUSER_RenameDirectory(arch.handle, fs_from, arch.handle, fs_to); + if r == 0 { + return Ok(()) + } + Err((r)) + } +} + +// TODO: Determine if we should check UTF-16 paths for interior NULs +fn to_utf16(path: &Path) -> Vec { + path.as_os_str().encode_wide().collect::>() +} + +// Adapted from sys/windows/fs.rs in libstd +fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] { + match v.iter().position(|c| *c == 0) { + // don't include the 0 + Some(i) => &v[..i], + None => v + } +} + +// Adapted from sys/common/io.rs in libstd +unsafe fn read_to_end_uninitialized(f: &mut File, buf: &mut Vec) -> Result { + let start_len = buf.len(); + buf.reserve(16); + + // Always try to read into the empty space of the vector (from the length to the capacity). + // If the vector ever fills up then we reserve an extra byte which should trigger the normal + // reallocation routines for the vector, which will likely double the size. + // + // This function is similar to the read_to_end function in std::io, but the logic about + // reservations and slicing is different enough that this is duplicated here. + loop { + if buf.len() == buf.capacity() { + buf.reserve(1); + } + + let buf_slice = slice::from_raw_parts_mut(buf.as_mut_ptr().offset(buf.len() as isize), + buf.capacity() - buf.len()); + + match f.read(buf_slice) { + Ok(0) => { return Ok(buf.len() - start_len); } + Ok(n) => { let len = buf.len() + n; buf.set_len(len); }, + Err(e) => { return Err(e); } + } + } +} + +impl Drop for Fs { + fn drop(&mut self) { + unsafe { + fsExit(); + } + } +} + +impl Drop for Archive { + fn drop(&mut self) { + unsafe { + FSUSER_CloseArchive(self.handle); + } + } +} + +impl Drop for File { + fn drop(&mut self) { + unsafe { + FSFILE_Close(self.handle); + } + } +} + +impl Drop for Dir { + fn drop(&mut self) { + unsafe { + FSDIR_Close(self.0); + } + } +} + +impl From for FS_PathType { + fn from(p: PathType) -> Self { + use self::PathType::*; + use libctru::services::fs::FS_PathType::*; + match p { + Invalid => PATH_INVALID, + Empty => PATH_EMPTY, + Binary => PATH_BINARY, + ASCII => PATH_ASCII, + UTF16 => PATH_UTF16, + } + } +} + +impl From for FS_ArchiveID { + fn from(a: ArchiveID) -> Self { + use self::ArchiveID::*; + use libctru::services::fs::FS_ArchiveID::*; + match a { + RomFS => ARCHIVE_ROMFS, + Savedata => ARCHIVE_SAVEDATA, + Extdata => ARCHIVE_EXTDATA, + SharedExtdata => ARCHIVE_SHARED_EXTDATA, + SystemSavedata => ARCHIVE_SYSTEM_SAVEDATA, + Sdmc => ARCHIVE_SDMC, + SdmcWriteOnly => ARCHIVE_SDMC_WRITE_ONLY, + BossExtdata => ARCHIVE_BOSS_EXTDATA, + CardSpiFS => ARCHIVE_CARD_SPIFS, + ExtDataAndBossExtdata => ARCHIVE_EXTDATA_AND_BOSS_EXTDATA, + SystemSaveData2 => ARCHIVE_SYSTEM_SAVEDATA2, + NandRW => ARCHIVE_NAND_RW, + NandRO => ARCHIVE_NAND_RO, + NandROWriteAccess => ARCHIVE_NAND_RO_WRITE_ACCESS, + SaveDataAndContent => ARCHIVE_SAVEDATA_AND_CONTENT, + SaveDataAndContent2 => ARCHIVE_SAVEDATA_AND_CONTENT2, + NandCtrFS => ARCHIVE_NAND_CTR_FS, + TwlPhoto => ARCHIVE_TWL_PHOTO, + NandTwlFS => ARCHIVE_NAND_TWL_FS, + GameCardSavedata => ARCHIVE_GAMECARD_SAVEDATA, + UserSavedata => ARCHIVE_USER_SAVEDATA, + DemoSavedata => ARCHIVE_DEMO_SAVEDATA, + } + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index 734cac0..bb96484 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,4 +1,5 @@ pub mod apt; +pub mod fs; pub mod hid; pub mod gspgpu; diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..86f49e5 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,25 @@ +/// A trait for viewing representations from std types +#[doc(hidden)] +pub trait AsInner { + fn as_inner(&self) -> &Inner; +} + +/// A trait for viewing representations from std types +#[doc(hidden)] +pub trait AsInnerMut { + fn as_inner_mut(&mut self) -> &mut Inner; +} + +/// A trait for extracting representations from std types +#[doc(hidden)] +pub trait IntoInner { + fn into_inner(self) -> Inner; +} + +/// A trait for creating std types from internal representations +#[doc(hidden)] +pub trait FromInner { + fn from_inner(inner: Inner) -> Self; +} + +pub mod wtf8; diff --git a/src/sys/wtf8.rs b/src/sys/wtf8.rs new file mode 100644 index 0000000..12ca7a1 --- /dev/null +++ b/src/sys/wtf8.rs @@ -0,0 +1,1206 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of [the WTF-8 encoding](https://simonsapin.github.io/wtf-8/). +//! +//! This library uses Rust’s type system to maintain +//! [well-formedness](https://simonsapin.github.io/wtf-8/#well-formed), +//! like the `String` and `&str` types do for UTF-8. +//! +//! Since [WTF-8 must not be used +//! for interchange](https://simonsapin.github.io/wtf-8/#intended-audience), +//! this library deliberately does not provide access to the underlying bytes +//! of WTF-8 strings, +//! nor can it decode WTF-8 from arbitrary bytes. +//! WTF-8 strings can be obtained from UTF-8, UTF-16, or code points. + +// this module is imported from @SimonSapin's repo and has tons of dead code on +// unix (it's mostly used on windows), so don't worry about dead code here. +#![allow(dead_code)] + +use core::str::next_code_point; + +use ascii::*; +use collections::borrow::Cow; +use rustc_unicode::char; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::iter::FromIterator; +use core::mem; +use core::ops; +use collections::slice; +use core::str; +use collections::String; +use sys::AsInner; +use collections::Vec; + +const UTF8_REPLACEMENT_CHARACTER: &'static [u8] = b"\xEF\xBF\xBD"; + +/// A Unicode code point: from U+0000 to U+10FFFF. +/// +/// Compare with the `char` type, +/// which represents a Unicode scalar value: +/// a code point that is not a surrogate (U+D800 to U+DFFF). +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] +pub struct CodePoint { + value: u32, +} + +/// Format the code point as `U+` followed by four to six hexadecimal digits. +/// Example: `U+1F4A9` +impl fmt::Debug for CodePoint { + #[inline] + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(formatter, "U+{:04X}", self.value) + } +} + +impl CodePoint { + /// Unsafely creates a new `CodePoint` without checking the value. + /// + /// Only use when `value` is known to be less than or equal to 0x10FFFF. + #[inline] + pub unsafe fn from_u32_unchecked(value: u32) -> CodePoint { + CodePoint { value: value } + } + + /// Creates a new `CodePoint` if the value is a valid code point. + /// + /// Returns `None` if `value` is above 0x10FFFF. + #[inline] + pub fn from_u32(value: u32) -> Option { + match value { + 0...0x10FFFF => Some(CodePoint { value: value }), + _ => None, + } + } + + /// Creates a new `CodePoint` from a `char`. + /// + /// Since all Unicode scalar values are code points, this always succeeds. + #[inline] + pub fn from_char(value: char) -> CodePoint { + CodePoint { value: value as u32 } + } + + /// Returns the numeric value of the code point. + #[inline] + pub fn to_u32(&self) -> u32 { + self.value + } + + /// Optionally returns a Unicode scalar value for the code point. + /// + /// Returns `None` if the code point is a surrogate (from U+D800 to U+DFFF). + #[inline] + pub fn to_char(&self) -> Option { + match self.value { + 0xD800...0xDFFF => None, + _ => Some(unsafe { char::from_u32_unchecked(self.value) }), + } + } + + /// Returns a Unicode scalar value for the code point. + /// + /// Returns `'\u{FFFD}'` (the replacement character “�”) + /// if the code point is a surrogate (from U+D800 to U+DFFF). + #[inline] + pub fn to_char_lossy(&self) -> char { + self.to_char().unwrap_or('\u{FFFD}') + } +} + +/// An owned, growable string of well-formed WTF-8 data. +/// +/// Similar to `String`, but can additionally contain surrogate code points +/// if they’re not in a surrogate pair. +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone)] +pub struct Wtf8Buf { + bytes: Vec, +} + +impl ops::Deref for Wtf8Buf { + type Target = Wtf8; + + fn deref(&self) -> &Wtf8 { + self.as_slice() + } +} + +/// Format the string with double quotes, +/// and surrogates as `\u` followed by four hexadecimal digits. +/// Example: `"a\u{D800}"` for a string with code points [U+0061, U+D800] +impl fmt::Debug for Wtf8Buf { + #[inline] + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&**self, formatter) + } +} + +impl Wtf8Buf { + /// Creates a new, empty WTF-8 string. + #[inline] + pub fn new() -> Wtf8Buf { + Wtf8Buf { bytes: Vec::new() } + } + + /// Creates a new, empty WTF-8 string with pre-allocated capacity for `n` bytes. + #[inline] + pub fn with_capacity(n: usize) -> Wtf8Buf { + Wtf8Buf { bytes: Vec::with_capacity(n) } + } + + /// Creates a WTF-8 string from a UTF-8 `String`. + /// + /// This takes ownership of the `String` and does not copy. + /// + /// Since WTF-8 is a superset of UTF-8, this always succeeds. + #[inline] + pub fn from_string(string: String) -> Wtf8Buf { + Wtf8Buf { bytes: string.into_bytes() } + } + + /// Creates a WTF-8 string from a UTF-8 `&str` slice. + /// + /// This copies the content of the slice. + /// + /// Since WTF-8 is a superset of UTF-8, this always succeeds. + #[inline] + pub fn from_str(str: &str) -> Wtf8Buf { + Wtf8Buf { bytes: <[_]>::to_vec(str.as_bytes()) } + } + + pub fn clear(&mut self) { + self.bytes.clear() + } + + /// Creates a WTF-8 string from a potentially ill-formed UTF-16 slice of 16-bit code units. + /// + /// This is lossless: calling `.encode_wide()` on the resulting string + /// will always return the original code units. + pub fn from_wide(v: &[u16]) -> Wtf8Buf { + let mut string = Wtf8Buf::with_capacity(v.len()); + for item in char::decode_utf16(v.iter().cloned()) { + match item { + Ok(ch) => string.push_char(ch), + Err(surrogate) => { + let surrogate = surrogate.unpaired_surrogate(); + // Surrogates are known to be in the code point range. + let code_point = unsafe { CodePoint::from_u32_unchecked(surrogate as u32) }; + // Skip the WTF-8 concatenation check, + // surrogate pairs are already decoded by decode_utf16 + string.push_code_point_unchecked(code_point) + } + } + } + string + } + + /// Copied from String::push + /// This does **not** include the WTF-8 concatenation check. + fn push_code_point_unchecked(&mut self, code_point: CodePoint) { + let c = unsafe { + char::from_u32_unchecked(code_point.value) + }; + let mut bytes = [0; 4]; + let bytes = c.encode_utf8(&mut bytes).as_bytes(); + self.bytes.extend_from_slice(bytes) + } + + #[inline] + pub fn as_slice(&self) -> &Wtf8 { + unsafe { Wtf8::from_bytes_unchecked(&self.bytes) } + } + + /// Reserves capacity for at least `additional` more bytes to be inserted + /// in the given `Wtf8Buf`. + /// The collection may reserve more space to avoid frequent reallocations. + /// + /// # Panics + /// + /// Panics if the new capacity overflows `usize`. + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.bytes.reserve(additional) + } + + #[inline] + pub fn reserve_exact(&mut self, additional: usize) { + self.bytes.reserve_exact(additional) + } + + /// Returns the number of bytes that this string buffer can hold without reallocating. + #[inline] + pub fn capacity(&self) -> usize { + self.bytes.capacity() + } + + /// Append a UTF-8 slice at the end of the string. + #[inline] + pub fn push_str(&mut self, other: &str) { + self.bytes.extend_from_slice(other.as_bytes()) + } + + /// Append a WTF-8 slice at the end of the string. + /// + /// This replaces newly paired surrogates at the boundary + /// with a supplementary code point, + /// like concatenating ill-formed UTF-16 strings effectively would. + #[inline] + pub fn push_wtf8(&mut self, other: &Wtf8) { + match ((&*self).final_lead_surrogate(), other.initial_trail_surrogate()) { + // Replace newly paired surrogates by a supplementary code point. + (Some(lead), Some(trail)) => { + let len_without_lead_surrogate = self.len() - 3; + self.bytes.truncate(len_without_lead_surrogate); + let other_without_trail_surrogate = &other.bytes[3..]; + // 4 bytes for the supplementary code point + self.bytes.reserve(4 + other_without_trail_surrogate.len()); + self.push_char(decode_surrogate_pair(lead, trail)); + self.bytes.extend_from_slice(other_without_trail_surrogate); + } + _ => self.bytes.extend_from_slice(&other.bytes), + } + } + + /// Append a Unicode scalar value at the end of the string. + #[inline] + pub fn push_char(&mut self, c: char) { + self.push_code_point_unchecked(CodePoint::from_char(c)) + } + + /// Append a code point at the end of the string. + /// + /// This replaces newly paired surrogates at the boundary + /// with a supplementary code point, + /// like concatenating ill-formed UTF-16 strings effectively would. + #[inline] + pub fn push(&mut self, code_point: CodePoint) { + if let trail @ 0xDC00...0xDFFF = code_point.to_u32() { + if let Some(lead) = (&*self).final_lead_surrogate() { + let len_without_lead_surrogate = self.len() - 3; + self.bytes.truncate(len_without_lead_surrogate); + self.push_char(decode_surrogate_pair(lead, trail as u16)); + return; + } + } + + // No newly paired surrogates at the boundary. + self.push_code_point_unchecked(code_point) + } + + /// Shortens a string to the specified length. + /// + /// # Panics + /// + /// Panics if `new_len` > current length, + /// or if `new_len` is not a code point boundary. + #[inline] + pub fn truncate(&mut self, new_len: usize) { + assert!(is_code_point_boundary(self, new_len)); + self.bytes.truncate(new_len) + } + + /// Consumes the WTF-8 string and tries to convert it to UTF-8. + /// + /// This does not copy the data. + /// + /// If the contents are not well-formed UTF-8 + /// (that is, if the string contains surrogates), + /// the original WTF-8 string is returned instead. + pub fn into_string(self) -> Result { + match self.next_surrogate(0) { + None => Ok(unsafe { String::from_utf8_unchecked(self.bytes) }), + Some(_) => Err(self), + } + } + + /// Consumes the WTF-8 string and converts it lossily to UTF-8. + /// + /// This does not copy the data (but may overwrite parts of it in place). + /// + /// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”) + pub fn into_string_lossy(mut self) -> String { + let mut pos = 0; + loop { + match self.next_surrogate(pos) { + Some((surrogate_pos, _)) => { + pos = surrogate_pos + 3; + self.bytes[surrogate_pos..pos].copy_from_slice(UTF8_REPLACEMENT_CHARACTER); + } + None => return unsafe { String::from_utf8_unchecked(self.bytes) }, + } + } + } +} + +/// Create a new WTF-8 string from an iterator of code points. +/// +/// This replaces surrogate code point pairs with supplementary code points, +/// like concatenating ill-formed UTF-16 strings effectively would. +impl FromIterator for Wtf8Buf { + fn from_iter>(iter: T) -> Wtf8Buf { + let mut string = Wtf8Buf::new(); + string.extend(iter); + string + } +} + +/// Append code points from an iterator to the string. +/// +/// This replaces surrogate code point pairs with supplementary code points, +/// like concatenating ill-formed UTF-16 strings effectively would. +impl Extend for Wtf8Buf { + fn extend>(&mut self, iter: T) { + let iterator = iter.into_iter(); + let (low, _high) = iterator.size_hint(); + // Lower bound of one byte per code point (ASCII only) + self.bytes.reserve(low); + for code_point in iterator { + self.push(code_point); + } + } +} + +/// A borrowed slice of well-formed WTF-8 data. +/// +/// Similar to `&str`, but can additionally contain surrogate code points +/// if they’re not in a surrogate pair. +#[derive(Eq, Ord, PartialEq, PartialOrd)] +pub struct Wtf8 { + bytes: [u8], +} + +impl AsInner<[u8]> for Wtf8 { + fn as_inner(&self) -> &[u8] { + &self.bytes + } +} + +/// Format the slice with double quotes, +/// and surrogates as `\u` followed by four hexadecimal digits. +/// Example: `"a\u{D800}"` for a slice with code points [U+0061, U+D800] +impl fmt::Debug for Wtf8 { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn write_str_escaped(f: &mut fmt::Formatter, s: &str) -> fmt::Result { + use core::fmt::Write; + for c in s.chars().flat_map(|c| c.escape_debug()) { + f.write_char(c)? + } + Ok(()) + } + + formatter.write_str("\"")?; + let mut pos = 0; + loop { + match self.next_surrogate(pos) { + None => break, + Some((surrogate_pos, surrogate)) => { + write_str_escaped(formatter, unsafe { + str::from_utf8_unchecked(&self.bytes[pos..surrogate_pos]) + }) + ?; + write!(formatter, "\\u{{{:x}}}", surrogate)?; + pos = surrogate_pos + 3; + } + } + } + write_str_escaped(formatter, + unsafe { str::from_utf8_unchecked(&self.bytes[pos..]) }) + ?; + formatter.write_str("\"") + } +} + +impl Wtf8 { + /// Creates a WTF-8 slice from a UTF-8 `&str` slice. + /// + /// Since WTF-8 is a superset of UTF-8, this always succeeds. + #[inline] + pub fn from_str(value: &str) -> &Wtf8 { + unsafe { Wtf8::from_bytes_unchecked(value.as_bytes()) } + } + + /// Creates a WTF-8 slice from a WTF-8 byte slice. + /// + /// Since the byte slice is not checked for valid WTF-8, this functions is + /// marked unsafe. + #[inline] + unsafe fn from_bytes_unchecked(value: &[u8]) -> &Wtf8 { + mem::transmute(value) + } + + /// Returns the length, in WTF-8 bytes. + #[inline] + pub fn len(&self) -> usize { + self.bytes.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + /// Returns the code point at `position` if it is in the ASCII range, + /// or `b'\xFF' otherwise. + /// + /// # Panics + /// + /// Panics if `position` is beyond the end of the string. + #[inline] + pub fn ascii_byte_at(&self, position: usize) -> u8 { + match self.bytes[position] { + ascii_byte @ 0x00...0x7F => ascii_byte, + _ => 0xFF, + } + } + + /// Returns an iterator for the string’s code points. + #[inline] + pub fn code_points(&self) -> Wtf8CodePoints { + Wtf8CodePoints { bytes: self.bytes.iter() } + } + + /// Tries to convert the string to UTF-8 and return a `&str` slice. + /// + /// Returns `None` if the string contains surrogates. + /// + /// This does not copy the data. + #[inline] + pub fn as_str(&self) -> Option<&str> { + // Well-formed WTF-8 is also well-formed UTF-8 + // if and only if it contains no surrogate. + match self.next_surrogate(0) { + None => Some(unsafe { str::from_utf8_unchecked(&self.bytes) }), + Some(_) => None, + } + } + + /// Lossily converts the string to UTF-8. + /// Returns a UTF-8 `&str` slice if the contents are well-formed in UTF-8. + /// + /// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”). + /// + /// This only copies the data if necessary (if it contains any surrogate). + pub fn to_string_lossy(&self) -> Cow { + let surrogate_pos = match self.next_surrogate(0) { + None => return Cow::Borrowed(unsafe { str::from_utf8_unchecked(&self.bytes) }), + Some((pos, _)) => pos, + }; + let wtf8_bytes = &self.bytes; + let mut utf8_bytes = Vec::with_capacity(self.len()); + utf8_bytes.extend_from_slice(&wtf8_bytes[..surrogate_pos]); + utf8_bytes.extend_from_slice(UTF8_REPLACEMENT_CHARACTER); + let mut pos = surrogate_pos + 3; + loop { + match self.next_surrogate(pos) { + Some((surrogate_pos, _)) => { + utf8_bytes.extend_from_slice(&wtf8_bytes[pos..surrogate_pos]); + utf8_bytes.extend_from_slice(UTF8_REPLACEMENT_CHARACTER); + pos = surrogate_pos + 3; + } + None => { + utf8_bytes.extend_from_slice(&wtf8_bytes[pos..]); + return Cow::Owned(unsafe { String::from_utf8_unchecked(utf8_bytes) }); + } + } + } + } + + /// Converts the WTF-8 string to potentially ill-formed UTF-16 + /// and return an iterator of 16-bit code units. + /// + /// This is lossless: + /// calling `Wtf8Buf::from_ill_formed_utf16` on the resulting code units + /// would always return the original WTF-8 string. + #[inline] + pub fn encode_wide(&self) -> EncodeWide { + EncodeWide { + code_points: self.code_points(), + extra: 0, + } + } + + #[inline] + fn next_surrogate(&self, mut pos: usize) -> Option<(usize, u16)> { + let mut iter = self.bytes[pos..].iter(); + loop { + let b = match iter.next() { + None => return None, + Some(&b) => b, + }; + if b < 0x80 { + pos += 1; + } else if b < 0xE0 { + iter.next(); + pos += 2; + } else if b == 0xED { + match (iter.next(), iter.next()) { + (Some(&b2), Some(&b3)) if b2 >= 0xA0 => { + return Some((pos, decode_surrogate(b2, b3))) + } + _ => pos += 3, + } + } else if b < 0xF0 { + iter.next(); + iter.next(); + pos += 3; + } else { + iter.next(); + iter.next(); + iter.next(); + pos += 4; + } + } + } + + #[inline] + fn final_lead_surrogate(&self) -> Option { + let len = self.len(); + if len < 3 { + return None; + } + match &self.bytes[(len - 3)..] { + &[0xED, b2 @ 0xA0...0xAF, b3] => Some(decode_surrogate(b2, b3)), + _ => None, + } + } + + #[inline] + fn initial_trail_surrogate(&self) -> Option { + let len = self.len(); + if len < 3 { + return None; + } + match &self.bytes[..3] { + &[0xED, b2 @ 0xB0...0xBF, b3] => Some(decode_surrogate(b2, b3)), + _ => None, + } + } +} + + +/// Return a slice of the given string for the byte range [`begin`..`end`). +/// +/// # Panics +/// +/// Panics when `begin` and `end` do not point to code point boundaries, +/// or point beyond the end of the string. +impl ops::Index> for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, range: ops::Range) -> &Wtf8 { + // is_code_point_boundary checks that the index is in [0, .len()] + if range.start <= range.end && is_code_point_boundary(self, range.start) && + is_code_point_boundary(self, range.end) { + unsafe { slice_unchecked(self, range.start, range.end) } + } else { + slice_error_fail(self, range.start, range.end) + } + } +} + +/// Return a slice of the given string from byte `begin` to its end. +/// +/// # Panics +/// +/// Panics when `begin` is not at a code point boundary, +/// or is beyond the end of the string. +impl ops::Index> for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, range: ops::RangeFrom) -> &Wtf8 { + // is_code_point_boundary checks that the index is in [0, .len()] + if is_code_point_boundary(self, range.start) { + unsafe { slice_unchecked(self, range.start, self.len()) } + } else { + slice_error_fail(self, range.start, self.len()) + } + } +} + +/// Return a slice of the given string from its beginning to byte `end`. +/// +/// # Panics +/// +/// Panics when `end` is not at a code point boundary, +/// or is beyond the end of the string. +impl ops::Index> for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, range: ops::RangeTo) -> &Wtf8 { + // is_code_point_boundary checks that the index is in [0, .len()] + if is_code_point_boundary(self, range.end) { + unsafe { slice_unchecked(self, 0, range.end) } + } else { + slice_error_fail(self, 0, range.end) + } + } +} + +impl ops::Index for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, _range: ops::RangeFull) -> &Wtf8 { + self + } +} + +#[inline] +fn decode_surrogate(second_byte: u8, third_byte: u8) -> u16 { + // The first byte is assumed to be 0xED + 0xD800 | (second_byte as u16 & 0x3F) << 6 | third_byte as u16 & 0x3F +} + +#[inline] +fn decode_surrogate_pair(lead: u16, trail: u16) -> char { + let code_point = 0x10000 + ((((lead - 0xD800) as u32) << 10) | (trail - 0xDC00) as u32); + unsafe { char::from_u32_unchecked(code_point) } +} + +/// Copied from core::str::StrPrelude::is_char_boundary +#[inline] +pub fn is_code_point_boundary(slice: &Wtf8, index: usize) -> bool { + if index == slice.len() { + return true; + } + match slice.bytes.get(index) { + None => false, + Some(&b) => b < 128 || b >= 192, + } +} + +/// Copied from core::str::raw::slice_unchecked +#[inline] +pub unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 { + // memory layout of an &[u8] and &Wtf8 are the same + Wtf8::from_bytes_unchecked(slice::from_raw_parts(s.bytes.as_ptr().offset(begin as isize), + end - begin)) +} + +/// Copied from core::str::raw::slice_error_fail +#[inline(never)] +pub fn slice_error_fail(s: &Wtf8, begin: usize, end: usize) -> ! { + assert!(begin <= end); + panic!("index {} and/or {} in `{:?}` do not lie on character boundary", + begin, + end, + s); +} + +/// Iterator for the code points of a WTF-8 string. +/// +/// Created with the method `.code_points()`. +#[derive(Clone)] +pub struct Wtf8CodePoints<'a> { + bytes: slice::Iter<'a, u8>, +} + +impl<'a> Iterator for Wtf8CodePoints<'a> { + type Item = CodePoint; + + #[inline] + fn next(&mut self) -> Option { + next_code_point(&mut self.bytes).map(|c| CodePoint { value: c }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.bytes.len(); + (len.saturating_add(3) / 4, Some(len)) + } +} + +#[derive(Clone)] +pub struct EncodeWide<'a> { + code_points: Wtf8CodePoints<'a>, + extra: u16, +} + +// Copied from libunicode/u_str.rs +impl<'a> Iterator for EncodeWide<'a> { + type Item = u16; + + #[inline] + fn next(&mut self) -> Option { + if self.extra != 0 { + let tmp = self.extra; + self.extra = 0; + return Some(tmp); + } + + let mut buf = [0; 2]; + self.code_points.next().map(|code_point| { + let c = unsafe { + char::from_u32_unchecked(code_point.value) + }; + let n = c.encode_utf16(&mut buf).len(); + if n == 2 { + self.extra = buf[1]; + } + buf[0] + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let (low, high) = self.code_points.size_hint(); + // every code point gets either one u16 or two u16, + // so this iterator is between 1 or 2 times as + // long as the underlying iterator. + (low, high.and_then(|n| n.checked_mul(2))) + } +} + +impl Hash for CodePoint { + #[inline] + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + +impl Hash for Wtf8Buf { + #[inline] + fn hash(&self, state: &mut H) { + state.write(&self.bytes); + 0xfeu8.hash(state) + } +} + +impl Hash for Wtf8 { + #[inline] + fn hash(&self, state: &mut H) { + state.write(&self.bytes); + 0xfeu8.hash(state) + } +} + +impl AsciiExt for Wtf8 { + type Owned = Wtf8Buf; + + fn is_ascii(&self) -> bool { + self.bytes.is_ascii() + } + fn to_ascii_uppercase(&self) -> Wtf8Buf { + Wtf8Buf { bytes: self.bytes.to_ascii_uppercase() } + } + fn to_ascii_lowercase(&self) -> Wtf8Buf { + Wtf8Buf { bytes: self.bytes.to_ascii_lowercase() } + } + fn eq_ignore_ascii_case(&self, other: &Wtf8) -> bool { + self.bytes.eq_ignore_ascii_case(&other.bytes) + } + + fn make_ascii_uppercase(&mut self) { + self.bytes.make_ascii_uppercase() + } + fn make_ascii_lowercase(&mut self) { + self.bytes.make_ascii_lowercase() + } +} + +#[cfg(test)] +mod tests { + use collections::borrow::Cow; + use collections::{String, Vec}; + use super::*; + + #[test] + fn code_point_from_u32() { + assert!(CodePoint::from_u32(0).is_some()); + assert!(CodePoint::from_u32(0xD800).is_some()); + assert!(CodePoint::from_u32(0x10FFFF).is_some()); + assert!(CodePoint::from_u32(0x110000).is_none()); + } + + #[test] + fn code_point_to_u32() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + assert_eq!(c(0).to_u32(), 0); + assert_eq!(c(0xD800).to_u32(), 0xD800); + assert_eq!(c(0x10FFFF).to_u32(), 0x10FFFF); + } + + #[test] + fn code_point_from_char() { + assert_eq!(CodePoint::from_char('a').to_u32(), 0x61); + assert_eq!(CodePoint::from_char('💩').to_u32(), 0x1F4A9); + } + + #[test] + fn code_point_to_string() { + assert_eq!(format!("{:?}", CodePoint::from_char('a')), "U+0061"); + assert_eq!(format!("{:?}", CodePoint::from_char('💩')), "U+1F4A9"); + } + + #[test] + fn code_point_to_char() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + assert_eq!(c(0x61).to_char(), Some('a')); + assert_eq!(c(0x1F4A9).to_char(), Some('💩')); + assert_eq!(c(0xD800).to_char(), None); + } + + #[test] + fn code_point_to_char_lossy() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + assert_eq!(c(0x61).to_char_lossy(), 'a'); + assert_eq!(c(0x1F4A9).to_char_lossy(), '💩'); + assert_eq!(c(0xD800).to_char_lossy(), '\u{FFFD}'); + } + + #[test] + fn wtf8buf_new() { + assert_eq!(Wtf8Buf::new().bytes, b""); + } + + #[test] + fn wtf8buf_from_str() { + assert_eq!(Wtf8Buf::from_str("").bytes, b""); + assert_eq!(Wtf8Buf::from_str("aé 💩").bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_from_string() { + assert_eq!(Wtf8Buf::from_string(String::from("")).bytes, b""); + assert_eq!(Wtf8Buf::from_string(String::from("aé 💩")).bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_from_wide() { + assert_eq!(Wtf8Buf::from_wide(&[]).bytes, b""); + assert_eq!(Wtf8Buf::from_wide(&[0x61, 0xE9, 0x20, 0xD83D, 0xD83D, 0xDCA9]).bytes, + b"a\xC3\xA9 \xED\xA0\xBD\xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_push_str() { + let mut string = Wtf8Buf::new(); + assert_eq!(string.bytes, b""); + string.push_str("aé 💩"); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_push_char() { + let mut string = Wtf8Buf::from_str("aé "); + assert_eq!(string.bytes, b"a\xC3\xA9 "); + string.push_char('💩'); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_push() { + let mut string = Wtf8Buf::from_str("aé "); + assert_eq!(string.bytes, b"a\xC3\xA9 "); + string.push(CodePoint::from_char('💩')); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + + let mut string = Wtf8Buf::new(); + string.push(c(0xD83D)); // lead + string.push(c(0xDCA9)); // trail + assert_eq!(string.bytes, b"\xF0\x9F\x92\xA9"); // Magic! + + let mut string = Wtf8Buf::new(); + string.push(c(0xD83D)); // lead + string.push(c(0x20)); // not surrogate + string.push(c(0xDCA9)); // trail + assert_eq!(string.bytes, b"\xED\xA0\xBD \xED\xB2\xA9"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xD800)); // lead + string.push(c(0xDBFF)); // lead + assert_eq!(string.bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xD800)); // lead + string.push(c(0xE000)); // not surrogate + assert_eq!(string.bytes, b"\xED\xA0\x80\xEE\x80\x80"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xD7FF)); // not surrogate + string.push(c(0xDC00)); // trail + assert_eq!(string.bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push(c(0x61)); // not surrogate, < 3 bytes + string.push(c(0xDC00)); // trail + assert_eq!(string.bytes, b"\x61\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xDC00)); // trail + assert_eq!(string.bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_push_wtf8() { + let mut string = Wtf8Buf::from_str("aé"); + assert_eq!(string.bytes, b"a\xC3\xA9"); + string.push_wtf8(Wtf8::from_str(" 💩")); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + fn w(v: &[u8]) -> &Wtf8 { + unsafe { Wtf8::from_bytes_unchecked(v) } + } + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\xBD")); // lead + string.push_wtf8(w(b"\xED\xB2\xA9")); // trail + assert_eq!(string.bytes, b"\xF0\x9F\x92\xA9"); // Magic! + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\xBD")); // lead + string.push_wtf8(w(b" ")); // not surrogate + string.push_wtf8(w(b"\xED\xB2\xA9")); // trail + assert_eq!(string.bytes, b"\xED\xA0\xBD \xED\xB2\xA9"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\x80")); // lead + string.push_wtf8(w(b"\xED\xAF\xBF")); // lead + assert_eq!(string.bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\x80")); // lead + string.push_wtf8(w(b"\xEE\x80\x80")); // not surrogate + assert_eq!(string.bytes, b"\xED\xA0\x80\xEE\x80\x80"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\x9F\xBF")); // not surrogate + string.push_wtf8(w(b"\xED\xB0\x80")); // trail + assert_eq!(string.bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"a")); // not surrogate, < 3 bytes + string.push_wtf8(w(b"\xED\xB0\x80")); // trail + assert_eq!(string.bytes, b"\x61\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xB0\x80")); // trail + assert_eq!(string.bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_truncate() { + let mut string = Wtf8Buf::from_str("aé"); + string.truncate(1); + assert_eq!(string.bytes, b"a"); + } + + #[test] + #[should_panic] + fn wtf8buf_truncate_fail_code_point_boundary() { + let mut string = Wtf8Buf::from_str("aé"); + string.truncate(2); + } + + #[test] + #[should_panic] + fn wtf8buf_truncate_fail_longer() { + let mut string = Wtf8Buf::from_str("aé"); + string.truncate(4); + } + + #[test] + fn wtf8buf_into_string() { + let mut string = Wtf8Buf::from_str("aé 💩"); + assert_eq!(string.clone().into_string(), Ok(String::from("aé 💩"))); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(string.clone().into_string(), Err(string)); + } + + #[test] + fn wtf8buf_into_string_lossy() { + let mut string = Wtf8Buf::from_str("aé 💩"); + assert_eq!(string.clone().into_string_lossy(), String::from("aé 💩")); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(string.clone().into_string_lossy(), + String::from("aé 💩�")); + } + + #[test] + fn wtf8buf_from_iterator() { + fn f(values: &[u32]) -> Wtf8Buf { + values.iter().map(|&c| CodePoint::from_u32(c).unwrap()).collect::() + } + assert_eq!(f(&[0x61, 0xE9, 0x20, 0x1F4A9]).bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + assert_eq!(f(&[0xD83D, 0xDCA9]).bytes, b"\xF0\x9F\x92\xA9"); // Magic! + assert_eq!(f(&[0xD83D, 0x20, 0xDCA9]).bytes, + b"\xED\xA0\xBD \xED\xB2\xA9"); + assert_eq!(f(&[0xD800, 0xDBFF]).bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + assert_eq!(f(&[0xD800, 0xE000]).bytes, b"\xED\xA0\x80\xEE\x80\x80"); + assert_eq!(f(&[0xD7FF, 0xDC00]).bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + assert_eq!(f(&[0x61, 0xDC00]).bytes, b"\x61\xED\xB0\x80"); + assert_eq!(f(&[0xDC00]).bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_extend() { + fn e(initial: &[u32], extended: &[u32]) -> Wtf8Buf { + fn c(value: &u32) -> CodePoint { + CodePoint::from_u32(*value).unwrap() + } + let mut string = initial.iter().map(c).collect::(); + string.extend(extended.iter().map(c)); + string + } + + assert_eq!(e(&[0x61, 0xE9], &[0x20, 0x1F4A9]).bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + assert_eq!(e(&[0xD83D], &[0xDCA9]).bytes, b"\xF0\x9F\x92\xA9"); // Magic! + assert_eq!(e(&[0xD83D, 0x20], &[0xDCA9]).bytes, + b"\xED\xA0\xBD \xED\xB2\xA9"); + assert_eq!(e(&[0xD800], &[0xDBFF]).bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + assert_eq!(e(&[0xD800], &[0xE000]).bytes, b"\xED\xA0\x80\xEE\x80\x80"); + assert_eq!(e(&[0xD7FF], &[0xDC00]).bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + assert_eq!(e(&[0x61], &[0xDC00]).bytes, b"\x61\xED\xB0\x80"); + assert_eq!(e(&[], &[0xDC00]).bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_show() { + let mut string = Wtf8Buf::from_str("a\té \u{7f}💩\r"); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(format!("{:?}", string), + "\"a\\té \\u{7f}\u{1f4a9}\\r\\u{d800}\""); + } + + #[test] + fn wtf8buf_as_slice() { + assert_eq!(Wtf8Buf::from_str("aé").as_slice(), Wtf8::from_str("aé")); + } + + #[test] + fn wtf8buf_show_str() { + let text = "a\té 💩\r"; + let string = Wtf8Buf::from_str(text); + assert_eq!(format!("{:?}", text), format!("{:?}", string)); + } + + #[test] + fn wtf8_from_str() { + assert_eq!(&Wtf8::from_str("").bytes, b""); + assert_eq!(&Wtf8::from_str("aé 💩").bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8_len() { + assert_eq!(Wtf8::from_str("").len(), 0); + assert_eq!(Wtf8::from_str("aé 💩").len(), 8); + } + + #[test] + fn wtf8_slice() { + assert_eq!(&Wtf8::from_str("aé 💩")[1..4].bytes, b"\xC3\xA9 "); + } + + #[test] + #[should_panic] + fn wtf8_slice_not_code_point_boundary() { + &Wtf8::from_str("aé 💩")[2..4]; + } + + #[test] + fn wtf8_slice_from() { + assert_eq!(&Wtf8::from_str("aé 💩")[1..].bytes, + b"\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + #[should_panic] + fn wtf8_slice_from_not_code_point_boundary() { + &Wtf8::from_str("aé 💩")[2..]; + } + + #[test] + fn wtf8_slice_to() { + assert_eq!(&Wtf8::from_str("aé 💩")[..4].bytes, b"a\xC3\xA9 "); + } + + #[test] + #[should_panic] + fn wtf8_slice_to_not_code_point_boundary() { + &Wtf8::from_str("aé 💩")[5..]; + } + + #[test] + fn wtf8_ascii_byte_at() { + let slice = Wtf8::from_str("aé 💩"); + assert_eq!(slice.ascii_byte_at(0), b'a'); + assert_eq!(slice.ascii_byte_at(1), b'\xFF'); + assert_eq!(slice.ascii_byte_at(2), b'\xFF'); + assert_eq!(slice.ascii_byte_at(3), b' '); + assert_eq!(slice.ascii_byte_at(4), b'\xFF'); + } + + #[test] + fn wtf8_code_points() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + fn cp(string: &Wtf8Buf) -> Vec> { + string.code_points().map(|c| c.to_char()).collect::>() + } + let mut string = Wtf8Buf::from_str("é "); + assert_eq!(cp(&string), [Some('é'), Some(' ')]); + string.push(c(0xD83D)); + assert_eq!(cp(&string), [Some('é'), Some(' '), None]); + string.push(c(0xDCA9)); + assert_eq!(cp(&string), [Some('é'), Some(' '), Some('💩')]); + } + + #[test] + fn wtf8_as_str() { + assert_eq!(Wtf8::from_str("").as_str(), Some("")); + assert_eq!(Wtf8::from_str("aé 💩").as_str(), Some("aé 💩")); + let mut string = Wtf8Buf::new(); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(string.as_str(), None); + } + + #[test] + fn wtf8_to_string_lossy() { + assert_eq!(Wtf8::from_str("").to_string_lossy(), Cow::Borrowed("")); + assert_eq!(Wtf8::from_str("aé 💩").to_string_lossy(), + Cow::Borrowed("aé 💩")); + let mut string = Wtf8Buf::from_str("aé 💩"); + string.push(CodePoint::from_u32(0xD800).unwrap()); + let expected: Cow = Cow::Owned(String::from("aé 💩�")); + assert_eq!(string.to_string_lossy(), expected); + } + + #[test] + fn wtf8_encode_wide() { + let mut string = Wtf8Buf::from_str("aé "); + string.push(CodePoint::from_u32(0xD83D).unwrap()); + string.push_char('💩'); + assert_eq!(string.encode_wide().collect::>(), + vec![0x61, 0xE9, 0x20, 0xD83D, 0xD83D, 0xDCA9]); + } +}