browserslist/lib.rs
1#![allow(clippy::float_cmp)]
2#![deny(clippy::if_not_else)]
3#![deny(clippy::needless_borrow)]
4#![deny(clippy::unimplemented)]
5#![warn(missing_docs)]
6
7//! **browserslist-rs** is a Rust-based implementation of [Browserslist](https://github.com/browserslist/browserslist).
8//!
9//! ## Introduction
10//!
11//! This library bundles Can I Use data, Electron versions list and Node.js releases list,
12//! so it won't and doesn't need to access any data files.
13//!
14//! Except several non-widely/non-frequently used features,
15//! this library works as same as the JavaScript-based
16//! implementation [Browserslist](https://github.com/browserslist/browserslist).
17//!
18//! ## Usage
19//!
20//! It provides a simple API for querying which accepts a sequence of strings and options [`Opts`],
21//! then returns the result.
22//!
23//! ```
24//! use browserslist::{Distrib, Opts, resolve, Error};
25//!
26//! let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
27//! assert_eq!(distribs[0].name(), "ie");
28//! assert_eq!(distribs[0].version(), "6");
29//! assert_eq!(distribs[1].name(), "ie");
30//! assert_eq!(distribs[1].version(), "5.5");
31//!
32//! assert_eq!(
33//! resolve(["yuru 1.0"], &Opts::default()),
34//! Err(Error::BrowserNotFound(String::from("yuru")))
35//! );
36//! ```
37//!
38//! The result isn't a list of strings, instead, it's a tuple struct called [`Distrib`].
39//! If you need to retrieve something like JavaScript-based implementation of
40//! [Browserslist](https://github.com/browserslist/browserslist),
41//! you can convert them to strings:
42//!
43//! ```
44//! use browserslist::{Distrib, Opts, resolve, Error};
45//!
46//! let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
47//! assert_eq!(
48//! distribs.into_iter().map(|d| d.to_string()).collect::<Vec<_>>(),
49//! vec![String::from("ie 6"), String::from("ie 5.5")]
50//! );
51//! ```
52//!
53//! ## WebAssembly
54//!
55//! This crate can be compiled as WebAssembly, without configuring any features manually.
56//!
57//! Please note that browser and Deno can run WebAssembly,
58//! but those environments aren't Node.js,
59//! so you will receive an error when querying `current node` in those environments.
60
61use parser::parse_browserslist_query;
62use std::cmp::Ordering;
63pub use {error::Error, opts::Opts, queries::Distrib};
64
65#[cfg(not(target_arch = "wasm32"))]
66mod config;
67mod error;
68mod opts;
69mod parser;
70mod queries;
71mod semver;
72#[cfg(test)]
73mod test;
74
75/// Resolve browserslist queries.
76///
77/// This is a low-level API.
78/// If you want to load queries from configuration file and
79/// resolve them automatically,
80/// use the higher-level API [`execute`] instead.
81///
82/// ```
83/// use browserslist::{Distrib, Opts, resolve};
84///
85/// let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
86/// assert_eq!(distribs[0].name(), "ie");
87/// assert_eq!(distribs[0].version(), "6");
88/// assert_eq!(distribs[1].name(), "ie");
89/// assert_eq!(distribs[1].version(), "5.5");
90/// ```
91pub fn resolve<I, S>(queries: I, opts: &Opts) -> Result<Vec<Distrib>, Error>
92where
93 S: AsRef<str>,
94 I: IntoIterator<Item = S>,
95{
96 let query = queries
97 .into_iter()
98 .enumerate()
99 .fold(String::new(), |mut s, (i, query)| {
100 if i > 0 {
101 s.push_str(", ");
102 }
103 s.push_str(query.as_ref());
104 s
105 });
106
107 let mut distribs = parse_browserslist_query(&query)?
108 .1
109 .into_iter()
110 .enumerate()
111 .try_fold(vec![], |mut distribs, (i, current)| {
112 if i == 0 && current.negated {
113 return Err(Error::NotAtFirst(current.raw.to_string()));
114 }
115
116 let mut dist = queries::query(current.atom, opts)?;
117 if current.negated {
118 distribs.retain(|distrib| !dist.contains(distrib));
119 } else if current.is_and {
120 distribs.retain(|distrib| dist.contains(distrib));
121 } else {
122 distribs.append(&mut dist);
123 }
124
125 Ok::<_, Error>(distribs)
126 })?;
127
128 distribs.sort_by(|a, b| match a.name().cmp(b.name()) {
129 Ordering::Equal => {
130 let version_a = a.version().split('-').next().unwrap();
131 let version_b = b.version().split('-').next().unwrap();
132 version_b
133 .parse::<semver::Version>()
134 .unwrap_or_default()
135 .cmp(&version_a.parse().unwrap_or_default())
136 }
137 ord => ord,
138 });
139 distribs.dedup();
140
141 Ok(distribs)
142}
143
144#[cfg(not(target_arch = "wasm32"))]
145/// Load queries from configuration with environment information,
146/// then resolve those queries.
147///
148/// If you want to resolve custom queries (not from configuration file),
149/// use the lower-level API [`resolve`] instead.
150///
151/// ```
152/// use browserslist::{Opts, execute};
153///
154/// // when no config found, it use `defaults` query
155/// assert!(!execute(&Opts::default()).unwrap().is_empty());
156/// ```
157pub fn execute(opts: &Opts) -> Result<Vec<Distrib>, Error> {
158 resolve(config::load(opts)?, opts)
159}