use core::fmt;
use bstr::ByteSlice;
use crate::Flags;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum RegexpOption {
Disabled,
Enabled,
}
impl RegexpOption {
#[must_use]
pub const fn new() -> Self {
Self::Disabled
}
#[must_use]
pub const fn is_enabled(self) -> bool {
matches!(self, Self::Enabled)
}
}
impl Default for RegexpOption {
fn default() -> Self {
Self::Disabled
}
}
impl From<bool> for RegexpOption {
fn from(value: bool) -> Self {
if value {
Self::Enabled
} else {
Self::Disabled
}
}
}
impl From<RegexpOption> for bool {
fn from(value: RegexpOption) -> Self {
matches!(value, RegexpOption::Enabled)
}
}
#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Options {
flags: Flags,
}
impl From<Options> for Flags {
fn from(opts: Options) -> Self {
opts.flags
}
}
impl From<Options> for u8 {
fn from(opts: Options) -> Self {
opts.flags.bits()
}
}
impl From<Options> for i64 {
fn from(opts: Options) -> Self {
opts.flags.bits().into()
}
}
impl From<Flags> for Options {
fn from(mut flags: Flags) -> Self {
flags.remove(Flags::FIXEDENCODING | Flags::NOENCODING);
Self { flags }
}
}
impl From<u8> for Options {
fn from(flags: u8) -> Self {
let flags = Flags::from_bits_truncate(flags);
Self::from(flags)
}
}
impl From<i64> for Options {
fn from(flags: i64) -> Self {
let [byte, ..] = flags.to_le_bytes();
Self::from(byte)
}
}
impl From<Option<bool>> for Options {
fn from(options: Option<bool>) -> Self {
match options {
Some(false) | None => Self::new(),
Some(true) => Self::with_ignore_case(),
}
}
}
impl From<&str> for Options {
fn from(options: &str) -> Self {
Self::from(options.as_bytes())
}
}
impl From<&[u8]> for Options {
fn from(options: &[u8]) -> Self {
let mut flags = Flags::empty();
flags.set(Flags::MULTILINE, options.find_byte(b'm').is_some());
flags.set(Flags::IGNORECASE, options.find_byte(b'i').is_some());
flags.set(Flags::EXTENDED, options.find_byte(b'x').is_some());
flags.set(Flags::LITERAL, options.find_byte(b'l').is_some());
Self { flags }
}
}
impl From<String> for Options {
fn from(options: String) -> Self {
Self::from(options.as_str())
}
}
impl From<Vec<u8>> for Options {
fn from(options: Vec<u8>) -> Self {
Self::from(options.as_slice())
}
}
impl fmt::Display for Options {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_display_modifier())
}
}
impl Options {
#[must_use]
pub const fn new() -> Self {
Self { flags: Flags::empty() }
}
#[must_use]
pub const fn with_ignore_case() -> Self {
Self {
flags: Flags::IGNORECASE,
}
}
#[must_use]
pub fn try_from_int(options: i64) -> Option<Self> {
let options = u8::try_from(options).ok()?;
Some(Self::from(options))
}
#[must_use]
pub fn flags(self) -> Flags {
let mut flags = self.flags;
flags.remove(Flags::LITERAL);
flags
}
#[must_use]
pub const fn into_bits(self) -> u8 {
self.flags.bits()
}
#[must_use]
pub const fn multiline(self) -> RegexpOption {
if self.flags.intersects(Flags::MULTILINE) {
RegexpOption::Enabled
} else {
RegexpOption::Disabled
}
}
#[must_use]
pub const fn ignore_case(self) -> RegexpOption {
if self.flags.intersects(Flags::IGNORECASE) {
RegexpOption::Enabled
} else {
RegexpOption::Disabled
}
}
#[must_use]
pub const fn extended(self) -> RegexpOption {
if self.flags.intersects(Flags::EXTENDED) {
RegexpOption::Enabled
} else {
RegexpOption::Disabled
}
}
#[must_use]
pub const fn is_literal(self) -> bool {
self.flags.intersects(Flags::LITERAL)
}
#[must_use]
pub const fn as_display_modifier(self) -> &'static str {
use RegexpOption::{Disabled, Enabled};
match (self.multiline(), self.ignore_case(), self.extended()) {
(Enabled, Enabled, Enabled) => "mix",
(Enabled, Enabled, Disabled) => "mi",
(Enabled, Disabled, Enabled) => "mx",
(Enabled, Disabled, Disabled) => "m",
(Disabled, Enabled, Enabled) => "ix",
(Disabled, Enabled, Disabled) => "i",
(Disabled, Disabled, Enabled) => "x",
(Disabled, Disabled, Disabled) => "",
}
}
#[must_use]
pub const fn as_inline_modifier(self) -> &'static str {
use RegexpOption::{Disabled, Enabled};
match (self.multiline(), self.ignore_case(), self.extended()) {
(Enabled, Enabled, Enabled) => "mix",
(Enabled, Enabled, Disabled) => "mi-x",
(Enabled, Disabled, Enabled) => "mx-i",
(Enabled, Disabled, Disabled) => "m-ix",
(Disabled, Enabled, Enabled) => "ix-m",
(Disabled, Enabled, Disabled) => "i-mx",
(Disabled, Disabled, Enabled) => "x-mi",
(Disabled, Disabled, Disabled) => "-mix",
}
}
pub fn set(&mut self, other: Flags, value: bool) {
self.flags.set(other, value);
}
}
#[cfg(test)]
mod tests {
use super::{Options, RegexpOption};
use crate::Flags;
#[test]
fn new_is_empty_flags() {
assert_eq!(Options::new(), Options::from(Flags::empty()));
}
#[test]
fn from_all_flags_ignores_encoding_and_literal() {
assert_eq!(
Options::from(Flags::all()),
Options::from(Flags::ALL_REGEXP_OPTS | Flags::LITERAL)
);
}
#[test]
fn parse_options_from_option_bool() {
assert_eq!(Options::from(None), Options::new());
assert_eq!(Options::from(Some(false)), Options::new());
assert_eq!(Options::from(Some(true)), Options::with_ignore_case());
}
#[test]
fn new_is_ignore_case() {
let mut opts = Options::new();
opts.flags |= Flags::IGNORECASE;
assert_eq!(Options::with_ignore_case(), opts);
}
#[test]
fn make_options_extended() {
let mut opts = Options::new();
opts.flags |= Flags::EXTENDED;
assert_eq!(Options::from(Flags::EXTENDED), opts);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::MAX),
None
);
assert_eq!(Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | 4096), None);
assert_eq!(Options::from(Flags::EXTENDED.bits() | 64), opts);
assert_ne!(Options::from(Flags::EXTENDED | Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::EXTENDED | Flags::MULTILINE), opts);
assert_ne!(
Options::from(Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE),
opts
);
assert_eq!(opts.ignore_case(), RegexpOption::Disabled);
assert_eq!(opts.extended(), RegexpOption::Enabled);
assert_eq!(opts.multiline(), RegexpOption::Disabled);
}
#[test]
fn make_options_ignore_case() {
let mut opts = Options::new();
opts.flags |= Flags::IGNORECASE;
assert_eq!(Options::from(Flags::IGNORECASE), opts);
assert_eq!(
Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | i64::MAX),
None
);
assert_eq!(Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | 4096), None);
assert_eq!(Options::from(Flags::IGNORECASE.bits() | 64), opts);
assert_ne!(Options::from(Flags::IGNORECASE | Flags::EXTENDED), opts);
assert_ne!(Options::from(Flags::IGNORECASE | Flags::MULTILINE), opts);
assert_ne!(
Options::from(Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE),
opts
);
assert_eq!(opts.ignore_case(), RegexpOption::Enabled);
assert_eq!(opts.extended(), RegexpOption::Disabled);
assert_eq!(opts.multiline(), RegexpOption::Disabled);
}
#[test]
fn make_options_multiline() {
let mut opts = Options::new();
opts.flags |= Flags::MULTILINE;
assert_eq!(Options::from(Flags::MULTILINE), opts);
assert_eq!(
Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | i64::MAX),
None
);
assert_eq!(Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | 4096), None);
assert_eq!(Options::from(Flags::MULTILINE.bits() | 64), opts);
assert_ne!(Options::from(Flags::MULTILINE | Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::MULTILINE | Flags::EXTENDED), opts);
assert_ne!(
Options::from(Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE),
opts
);
assert_eq!(opts.ignore_case(), RegexpOption::Disabled);
assert_eq!(opts.extended(), RegexpOption::Disabled);
assert_eq!(opts.multiline(), RegexpOption::Enabled);
}
#[test]
fn make_options_extended_ignore_case() {
let mut opts = Options::new();
opts.flags |= Flags::EXTENDED | Flags::IGNORECASE;
assert_ne!(Options::from(Flags::EXTENDED), opts);
assert_ne!(Options::from(Flags::IGNORECASE), opts);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::from(Flags::IGNORECASE.bits()) | i64::MAX),
None
);
assert_eq!(Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | 4096), None);
assert_eq!(Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | 4096), None);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::from(Flags::MULTILINE.bits()) | 4096),
None
);
assert_eq!(
Options::from(Flags::EXTENDED.bits() | Flags::IGNORECASE.bits() | 64),
opts
);
assert_eq!(Options::from(Flags::EXTENDED | Flags::IGNORECASE), opts);
assert_ne!(
Options::from(Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE),
opts
);
assert_eq!(opts.ignore_case(), RegexpOption::Enabled);
assert_eq!(opts.extended(), RegexpOption::Enabled);
assert_eq!(opts.multiline(), RegexpOption::Disabled);
}
#[test]
fn make_options_extended_ignore_case_multiline() {
let mut opts = Options::new();
opts.flags |= Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE;
assert_ne!(Options::from(Flags::EXTENDED), opts);
assert_ne!(Options::from(Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::MULTILINE), opts);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | i64::MAX),
None
);
assert_eq!(Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | 4096), None);
assert_eq!(Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | 4096), None);
assert_eq!(Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | 4096), None);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::from(Flags::MULTILINE.bits()) | 4096),
None
);
assert_ne!(Options::from(Flags::EXTENDED.bits() | 64), opts);
assert_ne!(Options::from(Flags::IGNORECASE.bits() | 64), opts);
assert_ne!(Options::from(Flags::MULTILINE.bits() | 64), opts);
assert_ne!(
Options::from(Flags::EXTENDED.bits() | Flags::MULTILINE.bits() | 64),
opts
);
assert_ne!(Options::from(Flags::EXTENDED | Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::MULTILINE | Flags::IGNORECASE), opts);
assert_eq!(
Options::from(Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE),
opts
);
assert_eq!(Options::from(Flags::ALL_REGEXP_OPTS), opts);
assert_eq!(opts.ignore_case(), RegexpOption::Enabled);
assert_eq!(opts.extended(), RegexpOption::Enabled);
assert_eq!(opts.multiline(), RegexpOption::Enabled);
}
#[test]
fn make_options_all_opts() {
let mut opts = Options::new();
opts.flags |= Flags::ALL_REGEXP_OPTS;
assert_ne!(Options::from(Flags::EXTENDED), opts);
assert_ne!(Options::from(Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::MULTILINE), opts);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | i64::MAX),
None
);
assert_eq!(Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | 4096), None);
assert_eq!(Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | 4096), None);
assert_eq!(Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | 4096), None);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::from(Flags::MULTILINE.bits()) | 4096),
None
);
assert_ne!(Options::from(Flags::EXTENDED.bits() | 64), opts);
assert_ne!(Options::from(Flags::IGNORECASE.bits() | 64), opts);
assert_ne!(Options::from(Flags::MULTILINE.bits() | 64), opts);
assert_ne!(
Options::from(Flags::EXTENDED.bits() | Flags::MULTILINE.bits() | 64),
opts
);
assert_ne!(Options::from(Flags::EXTENDED | Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::MULTILINE | Flags::IGNORECASE), opts);
assert_eq!(
Options::from(Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE),
opts
);
assert_eq!(Options::from(Flags::ALL_REGEXP_OPTS), opts);
assert_eq!(opts.ignore_case(), RegexpOption::Enabled);
assert_eq!(opts.extended(), RegexpOption::Enabled);
assert_eq!(opts.multiline(), RegexpOption::Enabled);
}
#[test]
fn make_options_flags_all() {
let opts = Options::from(Flags::all());
assert_ne!(Options::from(Flags::EXTENDED), opts);
assert_ne!(Options::from(Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::MULTILINE), opts);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | i64::MAX),
None
);
assert_eq!(
Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | i64::MAX),
None
);
assert_eq!(Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | 4096), None);
assert_eq!(Options::try_from_int(i64::from(Flags::IGNORECASE.bits()) | 4096), None);
assert_eq!(Options::try_from_int(i64::from(Flags::MULTILINE.bits()) | 4096), None);
assert_eq!(
Options::try_from_int(i64::from(Flags::EXTENDED.bits()) | i64::from(Flags::MULTILINE.bits()) | 4096),
None
);
assert_ne!(Options::from(Flags::EXTENDED.bits() | 64), opts);
assert_ne!(Options::from(Flags::IGNORECASE.bits() | 64), opts);
assert_ne!(Options::from(Flags::MULTILINE.bits() | 64), opts);
assert_ne!(
Options::from(Flags::EXTENDED.bits() | Flags::MULTILINE.bits() | 64),
opts
);
assert_ne!(Options::from(Flags::EXTENDED | Flags::IGNORECASE), opts);
assert_ne!(Options::from(Flags::MULTILINE | Flags::IGNORECASE), opts);
assert_eq!(
Options::from(Flags::EXTENDED | Flags::IGNORECASE | Flags::MULTILINE | Flags::LITERAL),
opts
);
let mut flags = opts.flags;
flags.remove(Flags::LITERAL);
assert_eq!(Options::from(Flags::ALL_REGEXP_OPTS).flags, flags);
assert_eq!(opts.ignore_case(), RegexpOption::Enabled);
assert_eq!(opts.extended(), RegexpOption::Enabled);
assert_eq!(opts.multiline(), RegexpOption::Enabled);
}
}