word_tally/input/
mapped.rs1use std::{
4 fmt::{self, Display, Formatter},
5 ops::Deref,
6 path::{Path, PathBuf},
7};
8
9use memmap2::Mmap;
10
11use super::{FileType, Metadata, open_file_with_error_context};
12use crate::WordTallyError;
13
14#[derive(Debug)]
16pub enum Mapped {
17 File {
19 path: PathBuf,
20 mmap: Mmap,
21 file_type: FileType,
22 },
23 Memory { bytes: Box<[u8]> },
25}
26
27impl AsRef<[u8]> for Mapped {
28 fn as_ref(&self) -> &[u8] {
30 match self {
31 Self::File { mmap, .. } => mmap,
32 Self::Memory { bytes } => bytes,
33 }
34 }
35}
36
37impl Deref for Mapped {
38 type Target = [u8];
39
40 fn deref(&self) -> &Self::Target {
42 match self {
43 Self::File { mmap, .. } => mmap,
44 Self::Memory { bytes } => bytes,
45 }
46 }
47}
48
49impl Display for Mapped {
50 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::File { path, .. } => write!(f, "{}", path.display()),
55 Self::Memory { .. } => write!(f, "<bytes>"),
56 }
57 }
58}
59
60impl From<Box<[u8]>> for Mapped {
61 fn from(bytes: Box<[u8]>) -> Self {
63 Self::Memory { bytes }
64 }
65}
66
67impl From<&[u8]> for Mapped {
68 fn from(bytes: &[u8]) -> Self {
70 Self::Memory {
71 bytes: Box::from(bytes),
72 }
73 }
74}
75
76impl From<Vec<u8>> for Mapped {
77 fn from(bytes: Vec<u8>) -> Self {
79 Self::Memory {
80 bytes: bytes.into_boxed_slice(),
81 }
82 }
83}
84
85impl<const N: usize> From<&[u8; N]> for Mapped {
86 fn from(bytes: &[u8; N]) -> Self {
88 Self::Memory {
89 bytes: Box::from(bytes.as_slice()),
90 }
91 }
92}
93
94impl TryFrom<&Path> for Mapped {
95 type Error = WordTallyError;
96
97 fn try_from(path: &Path) -> Result<Self, Self::Error> {
106 let metadata = std::fs::metadata(path).map_err(|source| {
108 let path_str = path.display().to_string();
109 let message = match source.kind() {
110 std::io::ErrorKind::NotFound => "no such file".to_string(),
111 std::io::ErrorKind::PermissionDenied => "permission denied".to_string(),
112 _ => "failed to read metadata".to_string(),
113 };
114 WordTallyError::Io {
115 path: path_str,
116 message,
117 file_type: None, source,
119 }
120 })?;
121
122 let file_type = FileType::from(&metadata);
124
125 #[cfg(unix)]
127 let is_mappable = {
128 use std::os::unix::fs::FileTypeExt;
129 let ft = metadata.file_type();
130 ft.is_file() || ft.is_block_device()
131 };
132 #[cfg(not(unix))]
133 let is_mappable = metadata.is_file();
134
135 if !is_mappable {
136 return Err(WordTallyError::NotMappable {
137 file_type,
138 path: path.display().to_string(),
139 });
140 }
141
142 let file = open_file_with_error_context(path)?;
144
145 #[allow(unsafe_code)]
147 let mmap = unsafe { Mmap::map(&file) }.map_err(|e| WordTallyError::Io {
148 path: path.display().to_string(),
149 message: "failed to create memory map".into(),
150 file_type: Some(file_type),
151 source: e,
152 })?;
153
154 Ok(Self::File {
155 path: path.to_path_buf(),
156 mmap,
157 file_type,
158 })
159 }
160}
161
162impl TryFrom<PathBuf> for Mapped {
163 type Error = WordTallyError;
164
165 fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
173 Self::try_from(path.as_path())
174 }
175}
176
177impl TryFrom<&str> for Mapped {
178 type Error = WordTallyError;
179
180 fn try_from(path: &str) -> Result<Self, Self::Error> {
189 if path == "-" {
190 return Err(WordTallyError::StdinNotMappable);
191 }
192 Self::try_from(Path::new(path))
193 }
194}
195
196impl Metadata for Mapped {
197 fn path(&self) -> Option<&Path> {
199 match self {
200 Self::File { path, .. } => Some(path.as_path()),
201 Self::Memory { .. } => None,
202 }
203 }
204
205 fn size(&self) -> Option<u64> {
207 Some(self.len() as u64)
208 }
209
210 fn file_type(&self) -> Option<FileType> {
212 match self {
213 Self::File { file_type, .. } => Some(*file_type),
214 Self::Memory { .. } => None,
215 }
216 }
217}