1use std::fs::File as StdFile;
2use std::hash::{DefaultHasher, Hash, Hasher};
3use std::io::Read;
4use std::sync::Arc;
5
6#[derive(Debug, Clone, Default)]
7pub struct FileId {
8 pub path: Option<String>,
9 content: Arc<[u8]>,
10 pub content_hash: u64,
11}
12
13impl FileId {
14 pub fn new_path(path: String) -> std::io::Result<Self> {
15 let mut file = StdFile::open(&path)?;
16 let capacity = file.metadata().map(|meta| meta.len() as usize).unwrap_or(0);
17 let mut content = Vec::with_capacity(capacity);
18 file.read_to_end(&mut content)?;
19
20 let mut hasher = DefaultHasher::new();
21 content.hash(&mut hasher);
22 let content_hash = hasher.finish();
23
24 Ok(FileId {
25 path: Some(path),
26 content: Arc::from(content),
27 content_hash,
28 })
29 }
30
31 pub fn new_content(content: Vec<u8>) -> Self {
32 let mut hasher = DefaultHasher::new();
33 hasher.write(&content);
34 let content_hash = hasher.finish();
35
36 FileId {
37 path: None,
38 content: Arc::from(content),
39 content_hash,
40 }
41 }
42
43 pub fn content(&self) -> &[u8] {
44 self.content.as_ref()
45 }
46
47 pub fn get_text(&self, start_byte: usize, end_byte: usize) -> Option<String> {
48 let content_bytes = self.content();
49
50 if start_byte > end_byte
51 || start_byte > content_bytes.len()
52 || end_byte > content_bytes.len()
53 {
54 return None;
55 }
56
57 let slice = &content_bytes[start_byte..end_byte];
58 Some(String::from_utf8_lossy(slice).into_owned())
59 }
60
61 pub fn get_full_text(&self) -> Option<String> {
62 let content_bytes = self.content();
63 Some(String::from_utf8_lossy(content_bytes).into_owned())
64 }
65}
66
67#[derive(Debug, Clone, Default)]
68pub struct File {
69 pub file: FileId,
71}
72
73impl File {
74 pub fn new_source(source: Vec<u8>) -> Self {
75 File {
76 file: FileId::new_content(source),
77 }
78 }
79
80 pub fn new_file(file: String) -> std::io::Result<Self> {
81 Ok(File {
82 file: FileId::new_path(file)?,
83 })
84 }
85
86 pub fn content(&self) -> &[u8] {
87 self.file.content()
88 }
89
90 pub fn get_text(&self, start: usize, end: usize) -> String {
91 self.file.get_text(start, end).unwrap()
92 }
93
94 pub fn opt_get_text(&self, start: usize, end: usize) -> Option<String> {
95 self.file.get_text(start, end)
96 }
97
98 pub fn path(&self) -> Option<&str> {
99 self.file.path.as_deref()
100 }
101}