Enum dowser::Extension

source ·
pub enum Extension {
    Ext2(u16),
    Ext3(u32),
    Ext4(u32),
}
Expand description

§Extension.

This enum can be used to efficiently check a file path’s extension case- insensitively against a hard-coded reference extension. It is likely overkill in most situations, but if you’re looking to optimize the filtering of large path lists, this can turn those painful nanosecond operations into pleasant picosecond ones!

The magic is largely down to storing values as u16 or u32 integers and comparing those (rather than byte slices or OsStr), and not messing around with the path Components iterator. (Note, this is done using the safe u*::from_le_bytes() methods rather than casting chicanery.)

At the moment, only extensions sized between 2-4 bytes are supported as those sizes are the most common and also translate perfectly to primitives, but larger values may be added in the future.

§Reference Constructors.

A “reference” extension is one known to you ahead of time, i.e. what you’re looking for. These can be constructed using the constant Extension::new2, Extension::new3, and Extension::new4 methods.

Because these are “known” values, no logical validation is performed. If you do something silly like mix case or type them incorrectly, equality tests will fail. You’d only be hurting yourself!

use dowser::Extension;

const EXT2: Extension = Extension::new2(*b"gz");
const EXT3: Extension = Extension::new3(*b"png");
const EXT4: Extension = Extension::new4(*b"html");

The main idea is you’ll pre-compute these values and compare unknown runtime values against them later.

§Runtime Constructors.

A “runtime” extension, for lack of a better adjective, is a value you don’t know ahead of time, e.g. from a user-supplied path. These can be constructed using the Extension::try_from2, Extension::try_from3, and Extension::try_from4 methods, which accept any AsRef<Path> argument.

The method you choose should match the length you’re looking for. For example, if you’re hoping for a PNG, use Extension::try_from3.

use dowser::Extension;

const EXT3: Extension = Extension::new3(*b"png");
assert_eq!(Extension::try_from3("/path/to/IMAGE.PNG"), Some(EXT3));
assert_eq!(Extension::try_from3("/path/to/doc.html"), None);

§Examples

To filter a list of image paths with the standard library — say, matching PNGs — you would do something like:

use std::ffi::OsStr;
use std::path::PathBuf;

// Imagine this is much longer…
let paths = vec![PathBuf::from("/path/to/image.png")];

paths.iter()
    .filter(|p| p.extension()
        .map_or(false, |e| e.eq_ignore_ascii_case(OsStr::new("png")))
    )
    .for_each(|p| todo!());

Using Extension instead, the same operation looks like:

use dowser::Extension;
use std::path::PathBuf;

// Imagine this is much longer…
let paths = vec![PathBuf::from("/path/to/image.png")];

// The reference extension.
const EXT: Extension = Extension::new3(*b"png");

paths.iter()
    .filter(|p| Extension::try_from3(p).map_or(false, |e| e == EXT))
    .for_each(|p| todo!());

Variants§

§

Ext2(u16)

§2-char Extension.

Like .gz.

§

Ext3(u32)

§3-char Extension.

Like .png.

§

Ext4(u32)

§4-char Extension.

Like .html.

Implementations§

source§

impl Extension

§Unchecked Instantiation.

source

pub const fn new2(src: [u8; 2]) -> Self

§New Unchecked (2).

Create a new Extension, unchecked, from two bytes, e.g. *b"gz". This should be lowercase and not include a period.

This method is intended for known values that you want to check unknown values against. Sanity-checking is traded for performance, but you’re only hurting yourself if you misuse it.

For compile-time generation, see Extension::codegen.

§Examples
use dowser::Extension;
const MY_EXT: Extension = Extension::new2(*b"gz");
source

pub const fn new3(src: [u8; 3]) -> Self

§New Unchecked (3).

Create a new Extension, unchecked, from three bytes, e.g. *b"gif". This should be lowercase and not include a period.

This method is intended for known values that you want to check unknown values against. Sanity-checking is traded for performance, but you’re only hurting yourself if you misuse it.

For compile-time generation, see Extension::codegen.

§Examples
use dowser::Extension;
const MY_EXT: Extension = Extension::new3(*b"gif");
source

pub const fn new4(src: [u8; 4]) -> Self

§New Unchecked (4).

Create a new Extension, unchecked, from four bytes, e.g. *b"html". This should be lowercase and not include a period.

This method is intended for known values that you want to check unknown values against. Sanity-checking is traded for performance, but you’re only hurting yourself if you misuse it.

For compile-time generation, see Extension::codegen.

§Examples
use dowser::Extension;
const MY_EXT: Extension = Extension::new4(*b"html");
source§

impl Extension

§From Paths.

source

pub fn try_from2<P>(path: P) -> Option<Self>
where P: AsRef<Path>,

§Try From Path (2).

This method is used to (try to) pull a 2-byte extension from a file path. This requires that the path be at least 4 bytes, with anything but a forward/backward slash at [len - 4] and a dot at [len - 3].

If successful, it will return an Extension::Ext2 that can be compared against your reference Extension. Casing will be fixed automatically.

§Examples
use dowser::Extension;

const MY_EXT: Extension = Extension::new2(*b"gz");
assert_eq!(Extension::try_from2("/path/to/file.gz"), Some(MY_EXT));
assert_eq!(Extension::try_from2("/path/to/file.GZ"), Some(MY_EXT));

assert_eq!(Extension::try_from2("/path/to/file.png"), None);
assert_ne!(Extension::try_from2("/path/to/file.br"), Some(MY_EXT));
source

pub fn try_from3<P>(path: P) -> Option<Self>
where P: AsRef<Path>,

§Try From Path (3).

This method is used to (try to) pull a 3-byte extension from a file path. This requires that the path be at least 5 bytes, with anything but a forward/backward slash at [len - 5] and a dot at [len - 4].

If successful, it will return an Extension::Ext3 that can be compared against your reference Extension. Casing will be fixed automatically.

§Examples
use dowser::Extension;

const MY_EXT: Extension = Extension::new3(*b"png");
assert_eq!(Extension::try_from3("/path/to/file.png"), Some(MY_EXT));
assert_eq!(Extension::try_from3("/path/to/FILE.PNG"), Some(MY_EXT));

assert_eq!(Extension::try_from3("/path/to/file.html"), None);
assert_ne!(Extension::try_from3("/path/to/file.jpg"), Some(MY_EXT));
source

pub fn try_from4<P>(path: P) -> Option<Self>
where P: AsRef<Path>,

§Try From Path (4).

This method is used to (try to) pull a 4-byte extension from a file path. This requires that the path be at least 6 bytes, with anything but a forward/backward slash at [len - 6] and a dot at [len - 5].

If successful, it will return an Extension::Ext4 that can be compared against your reference Extension. Casing will be fixed automatically.

§Examples
use dowser::Extension;

const MY_EXT: Extension = Extension::new4(*b"html");
assert_eq!(Extension::try_from4("/path/to/file.html"), Some(MY_EXT));
assert_eq!(Extension::try_from4("/path/to/FILE.HTML"), Some(MY_EXT));

assert_eq!(Extension::try_from4("/path/to/file.png"), None);
assert_ne!(Extension::try_from4("/path/to/file.xhtm"), Some(MY_EXT));
source§

impl Extension

§From Slices.

source

pub const fn slice_ext2(path: &[u8]) -> Option<Self>

§Extension Slice (2).

This method is used to (try to) pull a 2-byte extension from a file path in slice form. This requires that the path be at least 4 bytes, with anything but a forward/backward slash at [len - 4] and a dot at [len - 3].

If successful, it will return an Extension::Ext2 that can be compared against your reference Extension. Casing will be fixed automatically.

§Examples
use dowser::Extension;

const MY_EXT: Extension = Extension::new2(*b"gz");
assert_eq!(Extension::slice_ext2(b"/path/to/file.gz"), Some(MY_EXT));
assert_eq!(Extension::slice_ext2(b"/path/to/file.GZ"), Some(MY_EXT));

// Non-matches.
assert_eq!(Extension::slice_ext2(b"/path/to/.gz"), None);
assert_eq!(Extension::slice_ext2(b"/path/to\\.gz"), None);
assert_eq!(Extension::slice_ext2(b"/path/to/file.png"), None);
assert_ne!(Extension::slice_ext2(b"/path/to/file.br"), Some(MY_EXT));
source

pub const fn slice_ext3(path: &[u8]) -> Option<Self>

§Extension Slice (3).

This method is used to (try to) pull a 3-byte extension from a file path in slice form. This requires that the path be at least 5 bytes, with anything but a forward/backward slash at [len - 5] and a dot at [len - 4].

If successful, it will return an Extension::Ext3 that can be compared against your reference Extension. Casing will be fixed automatically.

§Examples
use dowser::Extension;

const MY_EXT: Extension = Extension::new3(*b"png");
assert_eq!(Extension::slice_ext3(b"/path/to/file.png"), Some(MY_EXT));
assert_eq!(Extension::slice_ext3(b"/path/to/FILE.PNG"), Some(MY_EXT));

// Non-matches.
assert_eq!(Extension::slice_ext3(b"/path/to/.png"), None);
assert_eq!(Extension::slice_ext3(b"/path/to\\.png"), None);
assert_eq!(Extension::slice_ext3(b"/path/to/file.html"), None);
assert_ne!(Extension::slice_ext3(b"/path/to/file.jpg"), Some(MY_EXT));
source

pub const fn slice_ext4(path: &[u8]) -> Option<Self>

§Extension Slice (4).

This method is used to (try to) pull a 4-byte extension from a file path in slice form. This requires that the path be at least 6 bytes, with anything but a forward/backward slash at [len - 6] and a dot at [len - 5].

If successful, it will return an Extension::Ext4 that can be compared against your reference Extension. Casing will be fixed automatically.

§Examples
use dowser::Extension;

const MY_EXT: Extension = Extension::new4(*b"html");
assert_eq!(Extension::slice_ext4(b"/path/to/file.html"), Some(MY_EXT));
assert_eq!(Extension::slice_ext4(b"/path/to/FILE.HTML"), Some(MY_EXT));

// Non-matches.
assert_eq!(Extension::slice_ext2(b"/path/to/.html"), None);
assert_eq!(Extension::slice_ext2(b"/path/to\\.html"), None);
assert_eq!(Extension::slice_ext4(b"/path/to/file.png"), None);
assert_ne!(Extension::slice_ext4(b"/path/to/file.xhtm"), Some(MY_EXT));
source

pub fn slice_ext(src: &[u8]) -> Option<&[u8]>

§Slice Extension.

This returns the file extension portion of a path as a byte slice, similar to std::path::Path::extension, but faster since it is dealing with straight bytes.

The extension is found by jumping to the last period, ensuring the byte before that period is not a path separator, and that there are one or more bytes after that period (none of which are path separators).

If the above are all good, a slice containing everything after that last period is returned.

§Examples
use dowser::Extension;

// Uppercase in, uppercase out.
assert_eq!(
    Extension::slice_ext(b"/path/to/IMAGE.JPEG"),
    Some(&b"JPEG"[..])
);

// Lowercase in, lowercase out.
assert_eq!(
    Extension::slice_ext(b"/path/to/file.docx"),
    Some(&b"docx"[..])
);

// These are all bad, though:
assert_eq!(
    Extension::slice_ext(b"/path/to/.hide"),
    None
);
assert_eq!(
    Extension::slice_ext(b"/path/to/"),
    None
);
assert_eq!(
    Extension::slice_ext(b"/path/to/file."),
    None
);
source§

impl Extension

§Codegen Helpers.

source

pub fn codegen(src: &[u8]) -> String

§Codegen Helper.

This compile-time method can be used in a build.rs script to generate a pre-computed Extension value of any supported length (2-4 bytes).

Unlike the runtime methods, this will automatically fix case and period inconsistencies, but ideally you should still pass it just the letters, in lowercase, because you have the power to do so. Haha.

§Examples
use dowser::Extension;

// This is what it looks like.
assert_eq!(
    Extension::codegen(b"js"),
    "Extension::Ext2(29_546_u16)"
);
assert_eq!(
    Extension::codegen(b"jpg"),
    "Extension::Ext3(1_735_420_462_u32)"
);
assert_eq!(
    Extension::codegen(b"html"),
    "Extension::Ext4(1_819_112_552_u32)"
);

In a typical build.rs workflow, you’d be building up a string of other code around it, and saving it all to a file, like:

use dowser::Extension;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
    let out = format!(
        "const MY_EXT: Extension = {};",
        Extension::codegen(b"jpg")
    );

    let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap())
        .join("compile-time-vars.rs");
    let mut f = File::create(out_path).unwrap();
    f.write_all(out.as_bytes()).unwrap();
    f.flush().unwrap();
}

Then in your main program, say lib.rs, you’d toss an include!() to that file to import the code as code, like:

use dowser::Extension;

include!(concat!(env!("OUT_DIR"), "/compile-time-vars.rs"));

Et voilà, you’ve saved yourself a nanosecond of runtime effort! Haha.

§Panics

This will panic if the extension (minus punctuation) is not 2-4 bytes or contains whitespace or path separators.

Trait Implementations§

source§

impl Clone for Extension

source§

fn clone(&self) -> Extension

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Extension

source§

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

Formats the value using the given formatter. Read more
source§

impl Hash for Extension

source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl<P> PartialEq<P> for Extension
where P: AsRef<Path>,

source§

fn eq(&self, other: &P) -> bool

§Path Equality.

When there’s just one extension and one path to check, you can compare them directly (extension first).

§Examples
use dowser::Extension;

const MY_EXT: Extension = Extension::new4(*b"html");

assert_eq!(MY_EXT, "/path/to/index.html");
assert_ne!(MY_EXT, "/path/to/image.jpeg");
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl PartialEq for Extension

source§

fn eq(&self, other: &Self) -> bool

This method tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl Copy for Extension

source§

impl Eq for Extension

Auto Trait Implementations§

Blanket Implementations§

source§

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

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

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

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

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

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

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

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

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

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

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

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.