Skip to content

Commit 9d55e63

Browse files
committed
Formatted and moved paths.
Paths are now based on &str.
1 parent 1e7d94b commit 9d55e63

File tree

4 files changed

+362
-226
lines changed

4 files changed

+362
-226
lines changed

src/dir.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
//! Directory related types
22
3+
// ============================================================================
4+
// Imports
5+
// ============================================================================
6+
7+
// None
8+
9+
// ============================================================================
10+
// Constants
11+
// ============================================================================
12+
13+
// None
14+
15+
// ============================================================================
16+
// Types
17+
// ============================================================================
18+
319
/// Represents an open directory
420
#[repr(C)]
521
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
@@ -8,12 +24,9 @@ pub struct Handle(u8);
824
impl Handle {
925
/// Construct a new `Handle` from an integer.
1026
///
11-
/// Only the OS should call this.
12-
///
13-
/// # Safety
14-
///
15-
/// The integer given must be a valid index for an open Directory.
16-
#[cfg(feature = "os")]
27+
/// Only the OS should call this - applications should not be constructing
28+
/// their own dir handles! But if you do, you probably can't harm anything
29+
/// and it's no worse that C just using `int`.
1730
pub const fn new(value: u8) -> Handle {
1831
Handle(value)
1932
}
@@ -30,8 +43,28 @@ impl Handle {
3043
#[repr(C)]
3144
#[derive(Clone, Debug, PartialEq, Eq)]
3245
pub struct Entry {
33-
/// The name and extension of the file
46+
/// The name and extension of the file.
47+
///
48+
/// The name and extension are separated by a single '.'.
49+
///
50+
/// The filename will be in ASCII. Unicode filenames are not supported.
3451
pub name: [u8; crate::MAX_FILENAME_LEN],
35-
/// File properties
52+
/// The properties for the file/directory this entry represents.
3653
pub properties: crate::file::Stat,
3754
}
55+
56+
// ============================================================================
57+
// Functions
58+
// ============================================================================
59+
60+
// None
61+
62+
// ============================================================================
63+
// Tests
64+
// ============================================================================
65+
66+
// None
67+
68+
// ============================================================================
69+
// End of File
70+
// ============================================================================

src/file.rs

Lines changed: 22 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,20 @@
11
//! File related types
22
3-
use bitflags::bitflags;
4-
use neotron_ffi::FfiString;
5-
6-
/// Represents a (borrowed) path to file.
7-
///
8-
/// Neotron OS uses the following format for file paths:
9-
///
10-
/// `<disk>:/[<directory>/]+<filename>.<extension>`
11-
///
12-
/// Unlike on MS-DOS, the `disk` specifier portion is not limited to a single
13-
/// ASCII letter and can be any UTF-8 string that does not contain `:` or `/`.
14-
///
15-
/// Paths are a sub-set of UTF-8 strings in this API, but be aware that not all
16-
/// filesystems support all Unicode characters. In particular FAT16 and FAT32
17-
/// volumes are likely to be limited to only `A-Z`, `a-z`, `0-9` and
18-
/// `$%-_@~\`!(){}^#&`. This API will expressly disallow UTF-8 codepoints below
19-
/// 32 (i.e. C0 control characters) to avoid confusion.
20-
///
21-
/// Paths are case-preserving but may not be case-sensitive. Paths may contain
22-
/// spaces, if your filesystem supports it.
23-
///
24-
/// Here are some examples of valid paths:
25-
///
26-
/// ```text
27-
/// Documents/2023/June/Sales in €.xls - relative to the Current Directory
28-
/// HD0:/MYDOCU~1/SALES.TXT - a file on drive HD0
29-
/// SD0:/MYDOCU~1/ - a directory on drive SD0
30-
/// SD0:/BOOTLDR - a file on drive SD0, with no file extension
31-
/// CON$: - a special device file
32-
/// SER0$:/bps=9600/parity=N/timeout=100: - a special device file with parameters
33-
/// ```
34-
#[repr(C)]
35-
pub struct Path<'a>(FfiString<'a>);
36-
37-
impl<'a> Path<'a> {
38-
/// The character that separates one directory name from another directory name.
39-
pub const PATH_SEP: char = '/';
40-
41-
/// The character that separates drive specifiers from directories.
42-
pub const DRIVE_SEP: char = ':';
43-
44-
/// Create a path from a string.
45-
///
46-
/// If the given string is not a valid path, an `Err` is returned.
47-
pub fn new(path_str: &'a str) -> Result<Path<'a>, crate::Error> {
48-
// No empty paths in drive specifier
49-
if path_str.is_empty() {
50-
return Err(crate::Error::InvalidPath);
51-
}
52-
53-
if let Some((drive_specifier, directory_path)) = path_str.split_once(Self::DRIVE_SEP) {
54-
if drive_specifier.contains(Self::PATH_SEP) {
55-
// No slashes in drive specifier
56-
return Err(crate::Error::InvalidPath);
57-
}
58-
if directory_path.contains(Self::DRIVE_SEP) {
59-
// No colons in directory path
60-
return Err(crate::Error::InvalidPath);
61-
}
62-
if !directory_path.is_empty() && !directory_path.starts_with(Self::PATH_SEP) {
63-
// No relative paths if drive is specified. An empty path is OK (it means "/")
64-
return Err(crate::Error::InvalidPath);
65-
}
66-
} else if path_str.starts_with(Self::PATH_SEP) {
67-
// No absolute paths if drive is not specified
68-
return Err(crate::Error::InvalidPath);
69-
}
70-
for ch in path_str.chars() {
71-
if ch.is_control() {
72-
// No control characters allowed
73-
return Err(crate::Error::InvalidPath);
74-
}
75-
}
76-
Ok(Path(FfiString::new(path_str)))
77-
}
78-
79-
/// Is this an absolute path?
80-
///
81-
/// Absolute paths have drive specifiers. Relative paths do not.
82-
pub fn is_absolute_path(&self) -> bool {
83-
self.drive_specifier().is_some()
84-
}
3+
// ============================================================================
4+
// Imports
5+
// ============================================================================
856

86-
/// Get the drive specifier for this path.
87-
///
88-
/// * A path like `DS0:/FOO/BAR.TXT` has a drive specifier of `DS0`.
89-
/// * A path like `BAR.TXT` has no drive specifier.
90-
pub fn drive_specifier(&self) -> Option<&str> {
91-
let path_str = self.0.as_str();
92-
if let Some((drive_specifier, _directory_path)) = path_str.split_once(Self::DRIVE_SEP) {
93-
Some(drive_specifier)
94-
} else {
95-
None
96-
}
97-
}
98-
99-
/// Get the drive path portion.
100-
///
101-
/// That is, everything after the directory specifier.
102-
pub fn drive_path(&self) -> Option<&str> {
103-
let path_str = self.0.as_str();
104-
if let Some((_drive_specifier, drive_path)) = path_str.split_once(Self::DRIVE_SEP) {
105-
if drive_path.is_empty() {
106-
Some("/")
107-
} else {
108-
Some(drive_path)
109-
}
110-
} else {
111-
Some(path_str)
112-
}
113-
}
7+
use bitflags::bitflags;
1148

115-
/// Get the directory portion of this path.
116-
///
117-
/// * A path like `DS0:/FOO/BAR.TXT` has a directory portion of `/FOO`.
118-
/// * A path like `DS0:/FOO/BAR/` has a directory portion of `/FOO/BAR`.
119-
/// * A path like `BAR.TXT` has no directory portion.
120-
pub fn directory(&self) -> Option<&str> {
121-
let Some(drive_path) = self.drive_path() else {
122-
return None;
123-
};
124-
if let Some((directory, _filename)) = drive_path.rsplit_once(Self::PATH_SEP) {
125-
if directory.is_empty() {
126-
None
127-
} else {
128-
Some(directory)
129-
}
130-
} else {
131-
Some(drive_path)
132-
}
133-
}
9+
// ============================================================================
10+
// Constants
11+
// ============================================================================
13412

135-
/// Get the filename portion of this path. This filename will include the file extension, if any.
136-
///
137-
/// * A path like `DS0:/FOO/BAR.TXT` has a filename portion of `/BAR.TXT`.
138-
/// * A path like `DS0:/FOO` has a filename portion of `/FOO`.
139-
/// * A path like `DS0:/FOO/` has no filename portion (so it's important directories have a trailing `/`)
140-
pub fn filename(&self) -> Option<&str> {
141-
let Some(drive_path) = self.drive_path() else {
142-
return None;
143-
};
144-
if let Some((_directory, filename)) = drive_path.rsplit_once(Self::PATH_SEP) {
145-
if filename.is_empty() {
146-
None
147-
} else {
148-
Some(filename)
149-
}
150-
} else {
151-
Some(drive_path)
152-
}
153-
}
13+
// None
15414

155-
/// Get the filename extension portion of this path.
156-
///
157-
/// A path like `DS0:/FOO/BAR.TXT` has a filename extension portion of `TXT`.
158-
/// A path like `DS0:/FOO/BAR` has no filename extension portion.
159-
pub fn extension(&self) -> Option<&str> {
160-
let Some(filename) = self.filename() else {
161-
return None;
162-
};
163-
if let Some((_basename, extension)) = filename.rsplit_once('.') {
164-
Some(extension)
165-
} else {
166-
None
167-
}
168-
}
169-
}
15+
// ============================================================================
16+
// Types
17+
// ============================================================================
17018

17119
/// Represents an open file
17220
#[repr(C)]
@@ -285,55 +133,18 @@ pub struct Time {
285133
pub seconds: u8,
286134
}
287135

288-
#[cfg(test)]
289-
mod tests {
290-
use super::*;
136+
// ============================================================================
137+
// Functions
138+
// ============================================================================
291139

292-
#[test]
293-
fn full_path() {
294-
let path_str = "HD0:/DOCUMENTS/JUNE/SALES.TXT";
295-
let path = Path::new(path_str).unwrap();
296-
assert!(path.is_absolute_path());
297-
assert_eq!(path.drive_specifier(), Some("HD0"));
298-
assert_eq!(path.drive_path(), Some("/DOCUMENTS/JUNE/SALES.TXT"));
299-
assert_eq!(path.directory(), Some("/DOCUMENTS/JUNE"));
300-
assert_eq!(path.filename(), Some("SALES.TXT"));
301-
assert_eq!(path.extension(), Some("TXT"));
302-
}
140+
// None
303141

304-
#[test]
305-
fn relative_path() {
306-
let path_str = "DOCUMENTS/JUNE/SALES.TXT";
307-
let path = Path::new(path_str).unwrap();
308-
assert!(!path.is_absolute_path());
309-
assert_eq!(path.drive_specifier(), None);
310-
assert_eq!(path.drive_path(), Some("DOCUMENTS/JUNE/SALES.TXT"));
311-
assert_eq!(path.directory(), Some("DOCUMENTS/JUNE"));
312-
assert_eq!(path.filename(), Some("SALES.TXT"));
313-
assert_eq!(path.extension(), Some("TXT"));
314-
}
142+
// ============================================================================
143+
// Tests
144+
// ============================================================================
315145

316-
#[test]
317-
fn full_dir() {
318-
let path_str = "HD0:/DOCUMENTS/JUNE/";
319-
let path = Path::new(path_str).unwrap();
320-
assert!(path.is_absolute_path());
321-
assert_eq!(path.drive_specifier(), Some("HD0"));
322-
assert_eq!(path.drive_path(), Some("/DOCUMENTS/JUNE/"));
323-
assert_eq!(path.directory(), Some("/DOCUMENTS/JUNE"));
324-
assert_eq!(path.filename(), None);
325-
assert_eq!(path.extension(), None);
326-
}
146+
// None
327147

328-
#[test]
329-
fn relative_dir() {
330-
let path_str = "DOCUMENTS/";
331-
let path = Path::new(path_str).unwrap();
332-
assert!(!path.is_absolute_path());
333-
assert_eq!(path.drive_specifier(), None);
334-
assert_eq!(path.drive_path(), Some("DOCUMENTS/"));
335-
assert_eq!(path.directory(), Some("DOCUMENTS"));
336-
assert_eq!(path.filename(), None);
337-
assert_eq!(path.extension(), None);
338-
}
339-
}
148+
// ============================================================================
149+
// End of File
150+
// ============================================================================

0 commit comments

Comments
 (0)