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
//! The [CSS Modules] project defines CSS Modules as:
//!
//! > A **CSS Module** is a CSS file in which all class names and animation names are scoped locally by default.
//!
//! This implementation is however currently immature and what parsing we do have is very naively implemented. As a result, currently only class names are locally scoped and the following work is in progress:
//!
//! - Locally scoped animation names
//! - Inlining `url()` and `@import` statements
//!
//! ## Usage
//!
//! A [`Stylesheet`] can be constructed manually:
//!
//! ```
//! use css_modules::stylesheet::*;
//!
//! let css = Stylesheet::new("my_module", ".myStyles {}");
//! ```
//!
//! Or if you would prefer to use automatic module naming based on source code, through a macro:
//!
//! ```
//! use css_modules::*;
//!
//! let css = css_module!(".myStyles {}");
//! ```
//!
//! The same as above, but from a file relative to the current source file:
//!
//! ```
//! use css_modules::*;
//!
//! let css = include_css_module!("test.css");
//! ```
//!
//! Which is the equivelent of doing:
//!
//! ```
//! use css_modules::*;
//!
//! let css = css_module!(include_str!("test.css"));
//! ```
//!
//! [CSS Modules]: https://github.com/css-modules/css-modules

pub mod grammar;
pub mod stylesheet;

/// Generate a css module name based on the location from where it was called.
pub fn css_module_name() -> String {
    use backtrace::{Backtrace, BacktraceFrame, BacktraceSymbol};
    use lazy_static::lazy_static;
    use regex::Regex;

    lazy_static! {
        static ref NAME_FORMATTER: Regex = Regex::new(r"[^\w]+").unwrap();
    }

    fn previous_symbol(level: u32) -> Option<BacktraceSymbol> {
        let (trace, curr_file, curr_line) = (Backtrace::new(), file!(), line!());
        let frames = trace.frames();

        frames
            .iter()
            .flat_map(BacktraceFrame::symbols)
            .skip_while(|s| {
                s.filename()
                    .map(|p| !p.ends_with(curr_file))
                    .unwrap_or(true)
                    || s.lineno() != Some(curr_line)
            })
            .nth(1 + level as usize)
            .cloned()
    }

    let symbol = previous_symbol(1);
    let name = symbol.as_ref().and_then(BacktraceSymbol::name);

    if let Some(name) = name {
        NAME_FORMATTER
            .replace_all(&format!("{:?}", name), "_")
            .to_string()
    } else {
        "unknown_module".to_string()
    }
}

/// Make a [`Stylesheet`] from a string.
///
/// ```
/// use css_modules::*;
///
/// let css = css_module!(".myClass { font-weight: bold; color: red; }");
///
/// // Get the localised class name:
/// css.id("myClass").unwrap();
///
/// // Get the module stylesheet as a string:
/// format!("{}", css);
/// ```
#[macro_export]
macro_rules! css_module {
    ($stylesheet:expr) => {{
        use $crate::stylesheet::*;

        match Stylesheet::new(&css_module_name(), $stylesheet) {
            Ok(css) => css,
            Err(error) => panic!("{}", error),
        }
    }};
    ($name:expr, $stylesheet:expr) => {{
        use $crate::stylesheet::*;

        match Stylesheet::new($name, $stylesheet) {
            Ok(css) => css,
            Err(error) => panic!("{}", error),
        }
    }};
}

/// Make a [`Stylesheet`] from a file relative to the current source file.
///
/// ```
/// use css_modules::*;
///
/// let css = include_css_module!("test.css");
///
/// // Get the localised class name:
/// css.id("myClass").unwrap();
///
/// // Get the module stylesheet as a string:
/// format!("{}", css);
/// ```
#[macro_export]
macro_rules! include_css_module {
    ($file:expr) => {{
        css_module!(include_str!($file))
    }};
    ($name:expr, $file:expr) => {{
        css_module!($name, include_str!($file))
    }};
}