1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#![allow(clippy::float_cmp)]
#![deny(clippy::if_not_else)]
#![deny(clippy::needless_borrow)]
#![deny(clippy::unimplemented)]
#![warn(missing_docs)]

//! **browserslist-rs** is a Rust-based implementation of [Browserslist](https://github.com/browserslist/browserslist).
//!
//! ## Introduction
//!
//! This library bundles Can I Use data, Electron versions list and Node.js releases list,
//! so it won't and doesn't need to access any data files.
//!
//! Except several non-widely/non-frequently used features,
//! this library works as same as the JavaScript-based
//! implementation [Browserslist](https://github.com/browserslist/browserslist).
//!
//! ## Usage
//!
//! It provides a simple API for querying which accepts a sequence of strings and options [`Opts`],
//! then returns the result.
//!
//! ```
//! use browserslist::{Distrib, Opts, resolve, Error};
//!
//! let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
//! assert_eq!(distribs[0].name(), "ie");
//! assert_eq!(distribs[0].version(), "6");
//! assert_eq!(distribs[1].name(), "ie");
//! assert_eq!(distribs[1].version(), "5.5");
//!
//! assert_eq!(
//!     resolve(["yuru 1.0"], &Opts::default()),
//!     Err(Error::BrowserNotFound(String::from("yuru")))
//! );
//! ```
//!
//! The result isn't a list of strings, instead, it's a tuple struct called [`Distrib`].
//! If you need to retrieve something like JavaScript-based implementation of
//! [Browserslist](https://github.com/browserslist/browserslist),
//! you can convert them to strings:
//!
//! ```
//! use browserslist::{Distrib, Opts, resolve, Error};
//!
//! let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
//! assert_eq!(
//!     distribs.into_iter().map(|d| d.to_string()).collect::<Vec<_>>(),
//!     vec![String::from("ie 6"), String::from("ie 5.5")]
//! );
//! ```
//!
//! ## WebAssembly
//!
//! This crate can be compiled as WebAssembly, without configuring any features manually.
//!
//! Please note that browser and Deno can run WebAssembly,
//! but those environments aren't Node.js,
//! so you will receive an error when querying `current node` in those environments.

use parser::parse_browserslist_query;
use std::cmp::Ordering;
#[cfg(all(feature = "wasm_bindgen", target_arch = "wasm32"))]
pub use wasm::browserslist;
pub use {error::Error, opts::Opts, queries::Distrib};

#[cfg(not(target_arch = "wasm32"))]
mod config;
mod data;
mod error;
mod opts;
mod parser;
mod queries;
mod semver;
#[cfg(test)]
mod test;
#[cfg(all(feature = "wasm_bindgen", target_arch = "wasm32"))]
mod wasm;

/// Resolve browserslist queries.
///
/// This is a low-level API.
/// If you want to load queries from configuration file and
/// resolve them automatically,
/// use the higher-level API [`execute`] instead.
///
/// ```
/// use browserslist::{Distrib, Opts, resolve};
///
/// let distribs = resolve(["ie <= 6"], &Opts::default()).unwrap();
/// assert_eq!(distribs[0].name(), "ie");
/// assert_eq!(distribs[0].version(), "6");
/// assert_eq!(distribs[1].name(), "ie");
/// assert_eq!(distribs[1].version(), "5.5");
/// ```
pub fn resolve<I, S>(queries: I, opts: &Opts) -> Result<Vec<Distrib>, Error>
where
    S: AsRef<str>,
    I: IntoIterator<Item = S>,
{
    let query = queries
        .into_iter()
        .enumerate()
        .fold(String::new(), |mut s, (i, query)| {
            if i > 0 {
                s.push_str(", ");
            }
            s.push_str(query.as_ref());
            s
        });

    let mut distribs = parse_browserslist_query(&query)?
        .1
        .into_iter()
        .enumerate()
        .try_fold(vec![], |mut distribs, (i, current)| {
            if i == 0 && current.negated {
                return Err(Error::NotAtFirst(current.raw.to_string()));
            }

            let mut dist = queries::query(current.atom, opts)?;
            if current.negated {
                distribs.retain(|distrib| !dist.contains(distrib));
            } else if current.is_and {
                distribs.retain(|distrib| dist.contains(distrib));
            } else {
                distribs.append(&mut dist);
            }

            Ok::<_, Error>(distribs)
        })?;

    distribs.sort_by(|a, b| match a.name().cmp(b.name()) {
        Ordering::Equal => {
            let version_a = a.version().split('-').next().unwrap();
            let version_b = b.version().split('-').next().unwrap();
            version_b
                .parse::<semver::Version>()
                .unwrap_or_default()
                .cmp(&version_a.parse().unwrap_or_default())
        }
        ord => ord,
    });
    distribs.dedup();

    Ok(distribs)
}

#[cfg(not(target_arch = "wasm32"))]
/// Load queries from configuration with environment information,
/// then resolve those queries.
///
/// If you want to resolve custom queries (not from configuration file),
/// use the lower-level API [`resolve`] instead.
///
/// ```
/// use browserslist::{Opts, execute};
///
/// // when no config found, it use `defaults` query
/// assert!(!execute(&Opts::default()).unwrap().is_empty());
/// ```
pub fn execute(opts: &Opts) -> Result<Vec<Distrib>, Error> {
    resolve(config::load(opts)?, opts)
}