Skip to content

Commit

Permalink
dependency: replace lazy_static with OnceLock
Browse files Browse the repository at this point in the history
Remove dependency on lazy_static and replace it with std::cell::OnceLock.

Feature request #24
  • Loading branch information
jmcnamara committed Jun 14, 2024
1 parent 822862e commit a796a24
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 134 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ rust-version = "1.73.0" # For zip.rs compatibility.
chrono = {version = "0.4.31", default-features = false, features = ["clock", "wasmbind", "serde"], optional = true}
zip = {version = "2.1.3 ", default-features = false, features = ["deflate"]}
regex = "1.7.3"
lazy_static = "1.4.0"
polars= {version = "0.38.3", default-features = false, features = [], optional = true}
js-sys = {version = "0.3.64", optional = true}
wasm-bindgen = {version = "0.2.87", optional = true}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ frequently.

## Features

- `default`: Includes all the standard functionality. Has dependencies on `zip`,
`regex` and `lazy_static`.
- `default`: Includes all the standard functionality. Has dependencies on `zip`
and `regex` only.


- `serde`: Adds supports for Serde serialization. This is off by default.
Expand Down
23 changes: 10 additions & 13 deletions src/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,11 +753,11 @@

mod tests;

use regex::Regex;
use std::{fmt, mem};
use std::{fmt, mem, sync::OnceLock};

use crate::{
drawing::{DrawingObject, DrawingType},
static_regex,
utility::{self, ToXmlBoolean},
xmlwriter::XMLWriter,
ColNum, Color, IntoColor, IntoExcelDateTime, ObjectMovement, RowNum, XlsxError, COL_MAX,
Expand Down Expand Up @@ -8334,25 +8334,22 @@ impl ChartRange {
/// ```
///
pub fn new_from_string(range_string: &str) -> ChartRange {
lazy_static! {
static ref CHART_CELL: Regex = Regex::new(r"^=?([^!]+)'?!\$?(\w+)\$?(\d+)").unwrap();
static ref CHART_RANGE: Regex =
Regex::new(r"^=?([^!]+)'?!\$?(\w+)\$?(\d+):\$?(\w+)\$?(\d+)").unwrap();
}
let chart_cell = static_regex!(r"^=?([^!]+)'?!\$?(\w+)\$?(\d+)");
let chart_range = static_regex!(r"^=?([^!]+)'?!\$?(\w+)\$?(\d+):\$?(\w+)\$?(\d+)");

let mut sheet_name = "";
let mut first_row = 0;
let mut first_col = 0;
let mut last_row = 0;
let mut last_col = 0;

if let Some(caps) = CHART_RANGE.captures(range_string) {
if let Some(caps) = chart_range.captures(range_string) {
sheet_name = caps.get(1).unwrap().as_str();
first_row = caps.get(3).unwrap().as_str().parse::<u32>().unwrap() - 1;
last_row = caps.get(5).unwrap().as_str().parse::<u32>().unwrap() - 1;
first_col = utility::column_name_to_number(caps.get(2).unwrap().as_str());
last_col = utility::column_name_to_number(caps.get(4).unwrap().as_str());
} else if let Some(caps) = CHART_CELL.captures(range_string) {
} else if let Some(caps) = chart_cell.captures(range_string) {
sheet_name = caps.get(1).unwrap().as_str();
first_row = caps.get(3).unwrap().as_str().parse::<u32>().unwrap() - 1;
first_col = utility::column_name_to_number(caps.get(2).unwrap().as_str());
Expand Down Expand Up @@ -10190,10 +10187,10 @@ impl ChartDataLabel {

// Check if the data label is in the default/unmodified condition.
pub(crate) fn is_default(&self) -> bool {
lazy_static! {
static ref DEFAULT_STATE: ChartDataLabel = ChartDataLabel::default();
};
self == &*DEFAULT_STATE
static DEFAULT_STATE: OnceLock<ChartDataLabel> = OnceLock::new();
let default_state = DEFAULT_STATE.get_or_init(ChartDataLabel::default);

self == default_state
}
}

Expand Down
14 changes: 6 additions & 8 deletions src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
#![warn(missing_docs)]
mod tests;

use regex::Regex;

#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};

Expand All @@ -22,6 +20,7 @@ use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime};
)))]
use std::time::SystemTime;

use crate::static_regex;
use crate::XlsxError;

const DAY_SECONDS: u64 = 24 * 60 * 60;
Expand Down Expand Up @@ -243,13 +242,12 @@ impl ExcelDateTime {
/// src="https://rustxlsxwriter.github.io/images/datetime_parse_from_str.png">
///
pub fn parse_from_str(datetime: &str) -> Result<ExcelDateTime, XlsxError> {
lazy_static! {
static ref DATE: Regex = Regex::new(r"\b(\d\d\d\d)-(\d\d)-(\d\d)").unwrap();
static ref TIME: Regex = Regex::new(r"(\d+):(\d\d)(:(\d\d(\.\d+)?))?").unwrap();
}
let date_regex = static_regex!(r"\b(\d\d\d\d)-(\d\d)-(\d\d)");
let time_regex = static_regex!(r"(\d+):(\d\d)(:(\d\d(\.\d+)?))?");

let mut matched = false;

let mut dt = match DATE.captures(datetime) {
let mut dt = match date_regex.captures(datetime) {
Some(caps) => {
let year = caps.get(1).unwrap().as_str().parse::<u16>().unwrap();
let month = caps.get(2).unwrap().as_str().parse::<u8>().unwrap();
Expand All @@ -261,7 +259,7 @@ impl ExcelDateTime {
None => Ok(ExcelDateTime::default()),
};

if let Some(caps) = TIME.captures(datetime) {
if let Some(caps) = time_regex.captures(datetime) {
let hour = caps.get(1).unwrap().as_str().parse::<u16>().unwrap();
let min = caps.get(2).unwrap().as_str().parse::<u8>().unwrap();

Expand Down
18 changes: 9 additions & 9 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

mod tests;

use std::{collections::HashMap, fmt, hash::Hash};
use std::{collections::HashMap, fmt, hash::Hash, sync::OnceLock};

/// The `Format` struct is used to define cell formatting for data in a worksheet.
///
Expand Down Expand Up @@ -601,10 +601,10 @@ impl Format {

// Check if the format is in the default/unmodified condition.
pub(crate) fn is_default(&self) -> bool {
lazy_static! {
static ref DEFAULT_STATE: Format = Format::default();
};
self == &*DEFAULT_STATE
static DEFAULT_STATE: OnceLock<Format> = OnceLock::new();
let default_state = DEFAULT_STATE.get_or_init(Format::default);

self == default_state
}

// -----------------------------------------------------------------------
Expand Down Expand Up @@ -2306,10 +2306,10 @@ pub(crate) struct Border {
impl Border {
// Check if the border is in the default/unmodified condition.
pub(crate) fn is_default(&self) -> bool {
lazy_static! {
static ref DEFAULT_STATE: Border = Border::default();
};
self == &*DEFAULT_STATE
static DEFAULT_STATE: OnceLock<Border> = OnceLock::new();
let default_state = DEFAULT_STATE.get_or_init(Border::default);

self == default_state
}
}

Expand Down
53 changes: 23 additions & 30 deletions src/formula.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

mod tests;

use regex::Regex;
use std::borrow::Cow;

use crate::static_regex;

/// The `Formula` struct is used to define a worksheet formula.
///
/// The `Formula` struct creates a formula type that can be used to write
Expand Down Expand Up @@ -941,13 +942,11 @@ impl Formula {

// Check of a dynamic function/formula.
pub(crate) fn is_dynamic_function(&self) -> bool {
lazy_static! {
static ref DYNAMIC_FUNCTION: Regex = Regex::new(
r"\b(ANCHORARRAY|BYCOL|BYROW|CHOOSECOLS|CHOOSEROWS|DROP|EXPAND|FILTER|HSTACK|LAMBDA|MAKEARRAY|MAP|RANDARRAY|REDUCE|SCAN|SEQUENCE|SINGLE|SORT|SORTBY|SWITCH|TAKE|TEXTSPLIT|TOCOL|TOROW|UNIQUE|VSTACK|WRAPCOLS|WRAPROWS|XLOOKUP)\("
)
.unwrap();
}
DYNAMIC_FUNCTION.is_match(&self.formula_string)
let dynamic_function = static_regex!(
r"\b(ANCHORARRAY|BYCOL|BYROW|CHOOSECOLS|CHOOSEROWS|DROP|EXPAND|FILTER|HSTACK|LAMBDA|MAKEARRAY|MAP|RANDARRAY|REDUCE|SCAN|SEQUENCE|SINGLE|SORT|SORTBY|SWITCH|TAKE|TEXTSPLIT|TOCOL|TOROW|UNIQUE|VSTACK|WRAPCOLS|WRAPROWS|XLOOKUP)\("
);

dynamic_function.is_match(&self.formula_string)
}

// Utility method to optionally strip equal sign and array braces from a
Expand Down Expand Up @@ -992,41 +991,35 @@ impl Formula {

// Escape/expand the dynamic formula _xlfn functions.
fn escape_dynamic_formulas1(formula: &str) -> Cow<str> {
lazy_static! {
static ref XLFN: Regex = Regex::new(
r"\b(ANCHORARRAY|BYCOL|BYROW|CHOOSECOLS|CHOOSEROWS|DROP|EXPAND|HSTACK|LAMBDA|MAKEARRAY|MAP|RANDARRAY|REDUCE|SCAN|SEQUENCE|SINGLE|SORTBY|SWITCH|TAKE|TEXTSPLIT|TOCOL|TOROW|UNIQUE|VSTACK|WRAPCOLS|WRAPROWS|XLOOKUP)\("
)
.unwrap();
}
XLFN.replace_all(formula, "_xlfn.$1(")
let xlfn = static_regex!(
r"\b(ANCHORARRAY|BYCOL|BYROW|CHOOSECOLS|CHOOSEROWS|DROP|EXPAND|HSTACK|LAMBDA|MAKEARRAY|MAP|RANDARRAY|REDUCE|SCAN|SEQUENCE|SINGLE|SORTBY|SWITCH|TAKE|TEXTSPLIT|TOCOL|TOROW|UNIQUE|VSTACK|WRAPCOLS|WRAPROWS|XLOOKUP)\("
);

xlfn.replace_all(formula, "_xlfn.$1(")
}

// Escape/expand the dynamic formula _xlfn._xlws. functions.
fn escape_dynamic_formulas2(formula: &str) -> Cow<str> {
lazy_static! {
static ref XLWS: Regex = Regex::new(r"\b(FILTER|SORT)\(").unwrap();
}
XLWS.replace_all(formula, "_xlfn._xlws.$1(")
let xlws = static_regex!(r"\b(FILTER|SORT)\(");

xlws.replace_all(formula, "_xlfn._xlws.$1(")
}

// Escape/expand future/_xlfn functions.
fn escape_future_functions(formula: &str) -> Cow<str> {
lazy_static! {
static ref FUTURE: Regex = Regex::new(
r"\b(ACOTH|ACOT|AGGREGATE|ARABIC|ARRAYTOTEXT|BASE|BETA.DIST|BETA.INV|BINOM.DIST.RANGE|BINOM.DIST|BINOM.INV|BITAND|BITLSHIFT|BITOR|BITRSHIFT|BITXOR|CEILING.MATH|CEILING.PRECISE|CHISQ.DIST.RT|CHISQ.DIST|CHISQ.INV.RT|CHISQ.INV|CHISQ.TEST|COMBINA|CONCAT|CONFIDENCE.NORM|CONFIDENCE.T|COTH|COT|COVARIANCE.P|COVARIANCE.S|CSCH|CSC|DAYS|DECIMAL|ERF.PRECISE|ERFC.PRECISE|EXPON.DIST|F.DIST.RT|F.DIST|F.INV.RT|F.INV|F.TEST|FILTERXML|FLOOR.MATH|FLOOR.PRECISE|FORECAST.ETS.CONFINT|FORECAST.ETS.SEASONALITY|FORECAST.ETS.STAT|FORECAST.ETS|FORECAST.LINEAR|FORMULATEXT|GAMMA.DIST|GAMMA.INV|GAMMALN.PRECISE|GAMMA|GAUSS|HYPGEOM.DIST|IFNA|IFS|IMAGE|IMCOSH|IMCOT|IMCSCH|IMCSC|IMSECH|IMSEC|IMSINH|IMTAN|ISFORMULA|ISOMITTED|ISOWEEKNUM|LET|LOGNORM.DIST|LOGNORM.INV|MAXIFS|MINIFS|MODE.MULT|MODE.SNGL|MUNIT|NEGBINOM.DIST|NORM.DIST|NORM.INV|NORM.S.DIST|NORM.S.INV|NUMBERVALUE|PDURATION|PERCENTILE.EXC|PERCENTILE.INC|PERCENTRANK.EXC|PERCENTRANK.INC|PERMUTATIONA|PHI|POISSON.DIST|QUARTILE.EXC|QUARTILE.INC|QUERYSTRING|RANK.AVG|RANK.EQ|RRI|SECH|SEC|SHEETS|SHEET|SKEW.P|STDEV.P|STDEV.S|T.DIST.2T|T.DIST.RT|T.DIST|T.INV.2T|T.INV|T.TEST|TEXTAFTER|TEXTBEFORE|TEXTJOIN|UNICHAR|UNICODE|VALUETOTEXT|VAR.P|VAR.S|WEBSERVICE|WEIBULL.DIST|XMATCH|XOR|Z.TEST)\("
)
.unwrap();
}
FUTURE.replace_all(formula, "_xlfn.$1(")
let future = static_regex!(
r"\b(ACOTH|ACOT|AGGREGATE|ARABIC|ARRAYTOTEXT|BASE|BETA.DIST|BETA.INV|BINOM.DIST.RANGE|BINOM.DIST|BINOM.INV|BITAND|BITLSHIFT|BITOR|BITRSHIFT|BITXOR|CEILING.MATH|CEILING.PRECISE|CHISQ.DIST.RT|CHISQ.DIST|CHISQ.INV.RT|CHISQ.INV|CHISQ.TEST|COMBINA|CONCAT|CONFIDENCE.NORM|CONFIDENCE.T|COTH|COT|COVARIANCE.P|COVARIANCE.S|CSCH|CSC|DAYS|DECIMAL|ERF.PRECISE|ERFC.PRECISE|EXPON.DIST|F.DIST.RT|F.DIST|F.INV.RT|F.INV|F.TEST|FILTERXML|FLOOR.MATH|FLOOR.PRECISE|FORECAST.ETS.CONFINT|FORECAST.ETS.SEASONALITY|FORECAST.ETS.STAT|FORECAST.ETS|FORECAST.LINEAR|FORMULATEXT|GAMMA.DIST|GAMMA.INV|GAMMALN.PRECISE|GAMMA|GAUSS|HYPGEOM.DIST|IFNA|IFS|IMAGE|IMCOSH|IMCOT|IMCSCH|IMCSC|IMSECH|IMSEC|IMSINH|IMTAN|ISFORMULA|ISOMITTED|ISOWEEKNUM|LET|LOGNORM.DIST|LOGNORM.INV|MAXIFS|MINIFS|MODE.MULT|MODE.SNGL|MUNIT|NEGBINOM.DIST|NORM.DIST|NORM.INV|NORM.S.DIST|NORM.S.INV|NUMBERVALUE|PDURATION|PERCENTILE.EXC|PERCENTILE.INC|PERCENTRANK.EXC|PERCENTRANK.INC|PERMUTATIONA|PHI|POISSON.DIST|QUARTILE.EXC|QUARTILE.INC|QUERYSTRING|RANK.AVG|RANK.EQ|RRI|SECH|SEC|SHEETS|SHEET|SKEW.P|STDEV.P|STDEV.S|T.DIST.2T|T.DIST.RT|T.DIST|T.INV.2T|T.INV|T.TEST|TEXTAFTER|TEXTBEFORE|TEXTJOIN|UNICHAR|UNICODE|VALUETOTEXT|VAR.P|VAR.S|WEBSERVICE|WEIBULL.DIST|XMATCH|XOR|Z.TEST)\("
);

future.replace_all(formula, "_xlfn.$1(")
}

// Escape/expand table functions.
fn escape_table_functions(formula: &str) -> Cow<str> {
// Convert Excel 2010 "@" table ref to 2007 "#This Row".
lazy_static! {
static ref TABLE: Regex = Regex::new(r"@").unwrap();
}
TABLE.replace_all(formula, "[#This Row],")
let table = static_regex!(r"@");

table.replace_all(formula, "[#This Row],")
}
}

Expand Down
11 changes: 8 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
//! crate:
//!
//! - `default`: Includes all the standard functionality. Has dependencies on
//! `zip`, `regex` and `lazy_static`.
//! `zip` and `regex` only.
//! - `serde`: Adds supports for Serde serialization. This is off by default.
//! - `chrono`: Adds supports for Chrono date/time types to the API. This is off
//! by default.
Expand Down Expand Up @@ -265,5 +265,10 @@ extern crate rust_xlsxwriter_derive;
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub use rust_xlsxwriter_derive::XlsxSerialize;

#[macro_use]
extern crate lazy_static;
macro_rules! static_regex {
($re:literal) => {{
static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
RE.get_or_init(|| regex::Regex::new($re).unwrap())
}};
}
pub(crate) use static_regex;
14 changes: 5 additions & 9 deletions src/test_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@
//
// Copyright 2022-2024, John McNamara, [email protected]

use regex::Regex;
use crate::static_regex;

// Convert XML string/doc into a vector for comparison testing.
pub(crate) fn xml_to_vec(xml_string: &str) -> Vec<String> {
lazy_static! {
static ref ELEMENT_DIVIDES: Regex = Regex::new(r">\s*<").unwrap();
}
let element_dividers = static_regex!(r">\s*<");

let mut xml_elements: Vec<String> = Vec::new();
let tokens: Vec<&str> = ELEMENT_DIVIDES.split(xml_string).collect();
let tokens: Vec<&str> = element_dividers.split(xml_string).collect();

for token in &tokens {
let mut element = token.trim().to_string();
Expand All @@ -35,12 +33,10 @@ pub(crate) fn xml_to_vec(xml_string: &str) -> Vec<String> {
// Convert VML string/doc into a vector for comparison testing. Excel VML tends
// to be less structured than other XML so it needs more massaging.
pub(crate) fn vml_to_vec(vml_string: &str) -> Vec<String> {
lazy_static! {
static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
}
let whitespace = static_regex!(r"\s+");

let mut vml_string = vml_string.replace(['\r', '\n'], "");
vml_string = WHITESPACE.replace_all(&vml_string, " ").into();
vml_string = whitespace.replace_all(&vml_string, " ").into();

vml_string = vml_string
.replace("; ", ";")
Expand Down
38 changes: 16 additions & 22 deletions src/worksheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,8 +1057,6 @@ use std::sync::Arc;
#[cfg(feature = "chrono")]
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};

use regex::Regex;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

Expand All @@ -1077,10 +1075,10 @@ use crate::styles::Styles;
use crate::vml::VmlInfo;
use crate::xmlwriter::{XMLWriter, XML_WRITE_ERROR};
use crate::{
utility, Chart, ChartEmptyCells, ChartRangeCacheData, ChartRangeCacheDataType, Color,
ConditionalFormat, ExcelDateTime, FilterCondition, FilterCriteria, FilterData, FilterDataType,
HeaderImagePosition, Image, IntoColor, IntoExcelDateTime, ObjectMovement, ProtectionOptions,
Sparkline, SparklineType, Table, TableFunction, Url,
static_regex, utility, Chart, ChartEmptyCells, ChartRangeCacheData, ChartRangeCacheDataType,
Color, ConditionalFormat, ExcelDateTime, FilterCondition, FilterCriteria, FilterData,
FilterDataType, HeaderImagePosition, Image, IntoColor, IntoExcelDateTime, ObjectMovement,
ProtectionOptions, Sparkline, SparklineType, Table, TableFunction, Url,
};

/// Integer type to represent a zero indexed row number. Excel's limit for rows
Expand Down Expand Up @@ -12567,16 +12565,14 @@ impl Worksheet {
// Check that there is a header/footer &[Picture] variable in the correct
// position to match the corresponding image object.
fn verify_header_footer_image(string: &str, position: &HeaderImagePosition) -> bool {
lazy_static! {
static ref LEFT: Regex = Regex::new(r"(&[L].*)(:?&[CR])?").unwrap();
static ref RIGHT: Regex = Regex::new(r"(&[R].*)(:?&[LC])?").unwrap();
static ref CENTER: Regex = Regex::new(r"(&[C].*)(:?&[LR])?").unwrap();
}
let left = static_regex!(r"(&[L].*)(:?&[CR])?");
let right = static_regex!(r"(&[R].*)(:?&[LC])?");
let center = static_regex!(r"(&[C].*)(:?&[LR])?");

let caps = match position {
HeaderImagePosition::Left => LEFT.captures(string),
HeaderImagePosition::Right => RIGHT.captures(string),
HeaderImagePosition::Center => CENTER.captures(string),
HeaderImagePosition::Left => left.captures(string),
HeaderImagePosition::Right => right.captures(string),
HeaderImagePosition::Center => center.captures(string),
};

match caps {
Expand Down Expand Up @@ -15094,13 +15090,11 @@ impl Hyperlink {
// This method handles a variety of different string processing that needs
// to be done for links and targets associated with Excel hyperlinks.
fn initialize(&mut self) {
lazy_static! {
static ref URL: Regex = Regex::new(r"^(ftp|http)s?://").unwrap();
static ref URL_ESCAPE: Regex = Regex::new(r"%[0-9a-fA-F]{2}").unwrap();
static ref REMOTE_FILE: Regex = Regex::new(r"^(\\\\|\w:)").unwrap();
}
let url_escape = static_regex!(r"%[0-9a-fA-F]{2}");
let remote_file = static_regex!(r"^(\\\\|\w:)");
let url_protocol = static_regex!(r"^(ftp|http)s?://");

if URL.is_match(&self.url) {
if url_protocol.is_match(&self.url) {
// Handle web links like http://.
self.link_type = HyperlinkType::Url;

Expand Down Expand Up @@ -15136,7 +15130,7 @@ impl Hyperlink {
let bare_link = bare_link.replacen("file://", "", 1);

// Links to local files aren't prefixed with file:///.
if !REMOTE_FILE.is_match(&bare_link) {
if !remote_file.is_match(&bare_link) {
self.url.clone_from(&bare_link);
}

Expand All @@ -15153,7 +15147,7 @@ impl Hyperlink {
}

// Escape any url characters in the url string.
if !URL_ESCAPE.is_match(&self.url) {
if !url_escape.is_match(&self.url) {
self.url = crate::xmlwriter::escape_url(&self.url).into();
}
}
Expand Down
Loading

1 comment on commit a796a24

@adriandelgado
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Please sign in to comment.