├── .codecov.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── feature-request.md │ ├── function-request.md │ ├── maintenance-request.md │ ├── metadata-request.md │ └── property-request.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── KNOWN_ISSUES.md ├── LICENSE ├── README.md ├── TODOs.md ├── codegen ├── Cargo.toml └── src │ ├── base.rs │ ├── declaration.rs │ ├── lib.rs │ └── ruleset.rs ├── csstype ├── Cargo.toml ├── src │ ├── base.rs │ ├── color │ │ ├── base.rs │ │ ├── hslcolor.rs │ │ ├── mod.rs │ │ └── rgbcolor.rs │ ├── keyword.rs │ ├── lib.rs │ └── unit.rs └── tests │ └── parse_color.rs ├── examples └── simple.rs ├── metadata ├── Cargo.toml └── src │ ├── lib.rs │ ├── processor.rs │ ├── root │ ├── filename.rs │ └── mod.rs │ ├── rule │ ├── mod.rs │ └── no_warn.rs │ └── util.rs ├── node ├── Cargo.toml └── src │ ├── base.rs │ ├── declaration.rs │ ├── lib.rs │ ├── metadata.rs │ ├── ruleset.rs │ └── selector.rs ├── parse ├── Cargo.toml └── src │ ├── color.rs │ ├── declaration.rs │ ├── expression.rs │ ├── keyword.rs │ ├── lib.rs │ ├── metadata.rs │ ├── root.rs │ ├── ruleset.rs │ ├── selector.rs │ ├── unit.rs │ └── variable.rs ├── property ├── Cargo.toml └── src │ ├── condition.rs │ ├── implementation │ ├── background │ │ ├── background_attachment.rs │ │ ├── background_color.rs │ │ ├── background_repeat.rs │ │ ├── color.rs │ │ └── mod.rs │ ├── box │ │ ├── border │ │ │ ├── border_bottom_width.rs │ │ │ ├── border_left_width.rs │ │ │ ├── border_right_width.rs │ │ │ ├── border_style.rs │ │ │ ├── border_top_width.rs │ │ │ └── mod.rs │ │ ├── clear.rs │ │ ├── display.rs │ │ ├── float.rs │ │ ├── height.rs │ │ ├── list_style_position.rs │ │ ├── list_style_type.rs │ │ ├── margin │ │ │ ├── margin_bottom.rs │ │ │ ├── margin_left.rs │ │ │ ├── margin_right.rs │ │ │ ├── margin_top.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── padding │ │ │ ├── mod.rs │ │ │ ├── padding_bottom.rs │ │ │ ├── padding_left.rs │ │ │ ├── padding_right.rs │ │ │ └── padding_top.rs │ │ ├── white_space.rs │ │ └── width.rs │ ├── direction.rs │ ├── empty_cells.rs │ ├── font │ │ ├── font_kerning.rs │ │ ├── font_size.rs │ │ ├── font_stretch.rs │ │ ├── font_style.rs │ │ ├── font_variant.rs │ │ ├── font_variant_caps.rs │ │ ├── font_variant_position.rs │ │ ├── font_weight.rs │ │ └── mod.rs │ ├── min_height.rs │ ├── mod.rs │ ├── text │ │ ├── letter_spacing.rs │ │ ├── line_height.rs │ │ ├── mod.rs │ │ ├── text_align.rs │ │ ├── text_decoration.rs │ │ ├── text_indent.rs │ │ ├── text_transform.rs │ │ ├── vertical_align.rs │ │ └── word_spacing.rs │ └── user_select.rs │ ├── keyword.rs │ ├── lib.rs │ └── property.rs ├── runtime ├── Cargo.toml └── src │ ├── global.rs │ └── lib.rs ├── runtime_facade ├── Cargo.toml └── src │ ├── compile_context.rs │ └── lib.rs ├── rust-toolchain └── src ├── core ├── mod.rs └── name_mangler.rs ├── css_files_impl.rs ├── css_use_impl.rs ├── lib.rs └── rustyle_impl.rs /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | notify: 3 | gitter: 4 | default: 5 | url: "https://webhooks.gitter.im/e/492c6e170126e4353181" 6 | threshold: 1% -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Share a bug to us to fixing issues 4 | title: 'bug: ' 5 | labels: "❣️ Priority: High, \U0001F197 Status: Available, \U0001F41B Type: Bug" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | - There is no issue about this. 13 | - I'm running the latest version. 14 | - I've tried the nightly version of rust. 15 | 16 | ## Description 17 | 18 | A clear and concise description of what the bug is. 19 | 20 | ## To Reproduce 21 | 22 | Steps to reproduce the behavior. 23 | 24 | ## Expected Behavior 25 | 26 | A clear and concise description of what you expected to happen. 27 | 28 | ## Environment 29 | 30 | - Rust Version: 31 | - Rustyle Version: 32 | - Cargo Version: 33 | 34 | ## Possible Solution 35 | 36 | If you don't have any solution, just delete this. 37 | 38 | ## Additional Information 39 | 40 | e.g. error logs, related issues, etc. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: 'feat: ' 5 | labels: "\U0001F197 Status: Available, \U0001F6E0 Type: Enhancement" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | - There is no issue about this. 13 | - I'm running the latest version. 14 | - It's not related to functions and metadata. 15 | - I understand that my issue won't be accepted, or won't be focused. 16 | 17 | ## Description 18 | 19 | Write the description of a feature wants to request. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/function-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Function Request 3 | about: Suggest a funcion idea for this project 4 | title: 'feat(funcion): ' 5 | labels: "\U0001F197 Status: Available, \U0001F6E0 Type: Enhancement" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | - There is no issue about this. 13 | - I'm running the latest version. 14 | - It's related to functions. 15 | - I understand that my issue won't be accepted, or won't be focused. 16 | 17 | ## Specification 18 | 19 | ```rust 20 | // Write specification in rust function declaration using these template, 21 | // Delete these comments because it's not required. 22 | fn function_name(parameter: Type); 23 | ``` 24 | 25 | ## Behavior 26 | 27 | Write a behavior of the function as prose. It can be quoted in the documentation. 28 | 29 | ## Example 30 | 31 | ### Input 32 | 33 | ```rust 34 | let (Example, ExampleFile) = css! { 35 | // write example here and delete this comment. 36 | } 37 | ``` 38 | 39 | ### Output 40 | 41 | ```css 42 | .auto-generated-classname { 43 | /* write example here and delete this comment */ 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/maintenance-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Maintenance Request 3 | about: Request us to fix phrase, word, etc. 4 | title: 'chore: ' 5 | labels: "\U0001F49A Priority: Low, \U0001F197 Status: Available, \U0001F4DC Type: Maintenance" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | - There is no issue about this. 13 | - I've found the issue from the latest version. 14 | - I understand that my issue won't be accepted, or won't be focused. 15 | 16 | ## Description 17 | 18 | Write the description of a maintenance work wants to be fixed. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/metadata-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Metadata Request 3 | about: Suggest a metadata idea for this project 4 | title: 'feat(metadata): ' 5 | labels: "\U0001F197 Status: Available, \U0001F6E0 Type: Enhancement" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | - There is no issue about this. 13 | - I'm running the latest version. 14 | - It's related to metadatas. 15 | - I understand that my issue won't be accepted, or won't be focused. 16 | 17 | ## Specification 18 | 19 | Choose one and delete another one. 20 | 21 | - Ruleset Metadata 22 | - Rule Metadata 23 | 24 | ```rust 25 | // Write specification in rust function declaration using these template, 26 | // Delete these comments because it's not required. 27 | fn metadata_name(parameter: Type); 28 | ``` 29 | 30 | ## Behavior 31 | 32 | Write a behavior of the function as prose. It can be quoted in the documentation. 33 | 34 | ## Example 35 | 36 | ### Input 37 | 38 | ```rust 39 | let (Example, ExampleFile) = css! { 40 | // write example here and delete this comment. 41 | } 42 | ``` 43 | 44 | ### Output 45 | 46 | ```css 47 | .auto-generated-classname { 48 | /* write example here and delete this comment */ 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/property-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Property Request 3 | about: Notice we to add property 4 | title: 'feat(property): ' 5 | labels: "\U0001F197 Status: Available, \U0001F6E0 Type: Enhancement" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | - There is no issue about this. 13 | - I'm running the latest version. 14 | - It is add request of property. 15 | - I understand that my issue won't be accepted, or won't be focused. 16 | 17 | ## Specification 18 | 19 | - Name : (name) 20 | - Value : (value pattern) 21 | - Document : w3.org or MDN link 22 | 23 | ## Example 24 | 25 | ### Input 26 | 27 | ```rust 28 | let (Example, ExampleFile) = css! { 29 | // write example here and delete this comment. 30 | } 31 | ``` 32 | 33 | ### Output 34 | 35 | ```css 36 | .auto-generated-classname { 37 | /* write example here and delete this comment */ 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | ## Prerequisites 4 | 5 | - [ ] I request to the correct repository. 6 | - [ ] There is no pull request same with this pull request. 7 | - [ ] Someone created an issue before creating this pull request. 8 | - [ ] I Agree that any change needs to be discussed before proceeding, And sometimes pull request can be rejected. 9 | - [ ] I provided enough information which makes easier to review my pull request. 10 | - [ ] I followed the [commit guideline](https://conventionalcommits.org) and code style guideline(rustfmt). 11 | 12 | ## Extra Information 13 | 14 | - [ ] My change requires to update the documentation. 15 | - [ ] I've updated the documentation accordingly. 16 | - [ ] I've added test cases to cover my changes. 17 | 18 | ## Description of Change 19 | 20 | Write the change of this pull request here, adds, removes, and more. 21 | 22 | ## Closing Issues 23 | 24 | Write here issue code prefixed with 'closes #' what to close (e.g. closes #11), and delete this sentence. 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/rust 2 | # Edit at https://www.gitignore.io/?templates=rust 3 | 4 | ### Rust ### 5 | # Generated by Cargo 6 | # will have compiled files and executables 7 | **/target/ 8 | 9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 11 | Cargo.lock 12 | 13 | # These are backup files generated by rustfmt 14 | **/*.rs.bk 15 | 16 | # End of https://www.gitignore.io/api/rust 17 | 18 | # Generated for testing 19 | rustyle/ 20 | 21 | # IDE configs 22 | .vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | cache: cargo 5 | 6 | # required for apt package install 7 | sudo: required 8 | # required for kcov install 9 | addons: 10 | apt: 11 | packages: 12 | - libcurl4-openssl-dev 13 | - libelf-dev 14 | - libdw-dev 15 | - cmake 16 | - gcc 17 | - binutils-dev 18 | - libiberty-dev 19 | 20 | after_success: | 21 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 22 | tar xzf master.tar.gz && 23 | cd kcov-master && 24 | mkdir build && 25 | cd build && 26 | cmake .. && 27 | make && 28 | make install DESTDIR=../../kcov-build && 29 | cd ../.. && 30 | rm -rf kcov-master && 31 | for file in target/debug/rustyle-*; do [ -x "${file}" ] || continue; mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 32 | bash <(curl -s https://codecov.io/bash) && 33 | echo "Uploaded code coverage" 34 | 35 | notifications: 36 | webhooks: 37 | urls: 38 | - https://webhooks.gitter.im/e/c598d780c3767df74f9d 39 | on_success: change 40 | on_failure: always 41 | on_start: never -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustyle" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | repository = "https://github.com/RanolP/rustyle" 6 | description = "A new way to represent the CSS stylesheet in Rust" 7 | license = "MIT" 8 | readme = "README.md" 9 | edition = "2018" 10 | 11 | [badges] 12 | travis-ci = { repository = "RanolP/rustyle" } 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | lazy_static = "1.3.0" 19 | quote = "0.6.12" 20 | fasthash = "0.4.0" 21 | proc-macro2 = "0.4.30" 22 | codegen = { path = "./codegen" } 23 | csstype = { path = "./csstype" } 24 | metadata = { path = "./metadata" } 25 | node = { path = "./node" } 26 | runtime = { path = "./runtime" } 27 | property = { path = "./property" } 28 | runtime_facade = { path = "./runtime_facade" } 29 | parse = { path = "./parse" } -------------------------------------------------------------------------------- /KNOWN_ISSUES.md: -------------------------------------------------------------------------------- 1 | # Known Issues 2 | 3 | ## Literal Value 4 | 5 | ### Hex Color Literal 6 | 7 | Hex literal cannot be compiled because the literal conflict with the integer literal of Rust. 8 | 9 | #### Example 10 | 11 | - `#0e0e0e` (conflict with exponentiation syntax; 1.234E+567) 12 | - `#0b0b0b` (conflict with binary syntax; 0b00001010) 13 | 14 | #### Solution 15 | 16 | You can bypass that by using our hex-string-color literal like this: `#"0e0e0e"`, `#"0b0b0b"` 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present RanolP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustyle 2 | 3 | ![rust-nightly](https://img.shields.io/badge/rust-nightly-important.svg) 4 | [![Travis](https://img.shields.io/travis/com/RanolP/rustyle.svg)](https://travis-ci.com/RanolP/rustyle) 5 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/RanolP/rustyle.svg) 6 | ![Codecov coverage status](https://img.shields.io/codecov/c/github/RanolP/rustyle.svg) 7 | ![crates.io version](https://img.shields.io/crates/v/rustyle.svg) 8 | ![crates.io downloads](https://img.shields.io/crates/d/rustyle.svg) 9 | ![crates.io license](https://img.shields.io/crates/l/rustyle.svg) 10 | 11 | A new way to represent the CSS stylesheet in Rust 12 | 13 | ## Basic Information 14 | 15 | Read like \[rough style\]. It seems like styled-components, emotion, glamor, and other CSS-in-JS libraries. It's basically inspired by their concepts. But more friendly with rust. 16 | 17 | ## Syntax 18 | 19 | Write CSS-in-Rust like this! (We call it rusty css syntax) 20 | 21 | ```rust 22 | let (Class, File) = css! { 23 | background-color: gray; 24 | 25 | &:hover { 26 | background-color: lighten!(15%, gray); 27 | } 28 | 29 | &:not(:hover) > p { 30 | display: none; 31 | } 32 | } 33 | ``` 34 | 35 | ## Special features which not exists on CSS 36 | 37 | ### Root Metadata 38 | 39 | Root Metadata syntax is inspired by the metadata syntax in Rust. 40 | It appends the metadata of root ruleset which controls the behavior of code generator. 41 | For the cleaner code, We decided it should not appear after other rule appeared. 42 | 43 | Syntax: 44 | 45 | ```rust 46 | let (Ruleset, RulesetFile) = rustyle! { 47 | #![ruleset_metadata] 48 | #![ruleset_metadata(with_param)] 49 | } 50 | ``` 51 | 52 | rustyle embedded those ruleset attributes: 53 | 54 | - `#![inject_global]` (todo) 55 | - `#![no_optimize]` (todo) 56 | - `#![no_collapse]` (todo) 57 | - `#![include(NAME)]` (todo) 58 | - `#![filename(filename)]` 59 | 60 | ### Rule Metadata 61 | 62 | Rule Metadata syntax is inspired by the attribute syntax in Rust. 63 | It appends the metadata of rule which gives hint to the code generator. 64 | Affect on the rule on the next line. 65 | 66 | Syntax: 67 | 68 | ```rust 69 | let (Ruleset, RulesetFile) = rustyle! { 70 | #[rule_metadata] 71 | the: rule; 72 | 73 | #[rule_metadata] 74 | the rule { 75 | #[rule_metadata(with_param)] 76 | the: rule; 77 | } 78 | } 79 | ``` 80 | 81 | rustyle embedded those ruleset attributes: 82 | 83 | - `#[no_warn(vendor_prefix)]` 84 | - `#[no_optimize]` (todo) 85 | 86 | ### Function 87 | 88 | Function seems like Sass, Less, etc's. But more likey with macro on Rust. 89 | Function evaluated at the compile time. And our custom function ends with exclamation(!) character. 90 | You can declare custom function if we can :p. 91 | 92 | Syntax: 93 | 94 | ```rust 95 | let (Ruleset, RulesetFile) = rustyle! { 96 | expression_position: function!(); 97 | 98 | statement_position!(); 99 | } 100 | ``` 101 | 102 | rustyle embedded those functions: 103 | 104 | - `adjust_hue!(color, angle)` (todo) 105 | - `lighten!(color, amount)` (todo) 106 | - `darken!(color, amount)` (todo) 107 | - `saturate!(color, amount)` (todo) 108 | - `desaturate!(color, amount)` (todo) 109 | - `transparentize!(color, amount)` (todo) 110 | - `grayscale!(color)` (todo) 111 | - `complement!(color)` (todo) 112 | - `invert!(color)` (todo) 113 | 114 | ## How's it works 115 | 116 | It's written in proc macro. The css codes checked and wrote at compile time. macro calls replaced to randomly generated class names. 117 | 118 | ## Links 119 | 120 | - [Known Issues](https://github.com/RanolP/rustyle/blob/master/KNOWN_ISSUES.md) -------------------------------------------------------------------------------- /TODOs.md: -------------------------------------------------------------------------------- 1 | # TODOs 2 | 3 | ## 💔 Critical 4 | 5 | ## ❣️ High 6 | 7 | - CSS Level 1 Implementation 8 | - Multiple Property Value Parsing 9 | - !important Parser 10 | - URL Parser 11 | - Properties 12 | - Font Properties 13 | - font-family : [[\ | \],]* [\ | \] 14 | - font : [\ || \ || \ ]? \ [ / \ ]? \ 15 | - background-image : \ | none 16 | - Color & Background Properties 17 | - background-position : [\ | \]{1,2} | [top | center | bottom] || [left | center | right] 18 | - background : \ || \ || \ || \ || \ 19 | - Box Properties 20 | - margin : [\ | \ | auto ]{1,4} 21 | - padding : [\ | \ ]{1,4} 22 | - border-width : [thin | medium | thick | \]{1,4} 23 | - border-color : \{1,4} 24 | - border-top : \ || \ || \ 25 | - border-right : \ || \ || \ 26 | - border-bottom : \ || \ || \ 27 | - border-left : \ || \ || \ 28 | - border : \ || \ || \ 29 | - list-style-image : \ | none 30 | - list-style : [disc | circle | square | decimal | lower-roman | upper-roman | lower-alpha | upper-alpha | none] || [inside | outside] || [\ | none] 31 | 32 | ## 💛 Medium 33 | 34 | - Functions Parser 35 | - Media Query Parser 36 | 37 | ## 💚 Low 38 | 39 | - Documentate them 40 | - Improve Code Readibility (Always) 41 | - Optimize Code (Always) -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | metadata = { path = "../metadata" } 9 | node = { path = "../node" } 10 | runtime = { path = "../runtime" } 11 | property = { path = "../property" } 12 | csstype = { path = "../csstype" } 13 | runtime_facade = { path = "../runtime_facade" } -------------------------------------------------------------------------------- /codegen/src/base.rs: -------------------------------------------------------------------------------- 1 | use node::Node; 2 | use proc_macro::Span; 3 | use runtime::CompileContext; 4 | 5 | /// A trait which generates code from itself with some contextual informations. 6 | /// 7 | /// It separated from [`Node`] because of cycle dependency. 8 | /// 9 | /// [`Node`]: node::Node 10 | pub trait CodeGenerator: Node { 11 | /// Generate code with passed contextual informations. 12 | /// 13 | /// # Arguments 14 | /// - `base_class` - The base selector. 15 | /// - `context` - The compilation context. 16 | #[allow(unused)] 17 | fn generate_code(&self, base_class: &str, context: &mut CompileContext) -> String { 18 | self.span() 19 | .unwrap_or(Span::call_site()) 20 | .error(format!( 21 | "CodeGenerator not implemented for '{}', at css class '{}'", 22 | self.name(), 23 | base_class 24 | )) 25 | .emit(); 26 | 27 | String::new() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /codegen/src/declaration.rs: -------------------------------------------------------------------------------- 1 | use crate::CodeGenerator; 2 | use csstype::{CssKeyword, CssKeywordType, CssUnit, Cssifiable, HslColor, RgbColor}; 3 | use metadata::RuleMetadataProcessor; 4 | use node::{DeclarationNode, MetadataNode}; 5 | use property::ConditionType; 6 | use runtime::global::{KEYWORDS, PROPERTIES, RULE_METADATA_PROCESSORS}; 7 | use runtime::CompileContext; 8 | use std::collections::HashMap; 9 | 10 | impl CodeGenerator for DeclarationNode { 11 | /// Generate code from [`DeclarationNode`]. Does not depends on any contextual values. 12 | /// 13 | /// [`DeclarationNode`]: node::DeclarationNode 14 | fn generate_code(&self, _: &str, _: &mut CompileContext) -> String { 15 | let rule_metadata_processors = RULE_METADATA_PROCESSORS.lock().unwrap(); 16 | 17 | let mut processors = 18 | HashMap::, Vec)>::new(); 19 | 20 | for processor in rule_metadata_processors.values() { 21 | processors.insert(processor.name().to_string(), (processor, Vec::new())); 22 | } 23 | 24 | for metadata in self.metadatas.clone() { 25 | if !processors.contains_key(&metadata.method_name) { 26 | metadata.range.error("Unknown metadata").emit(); 27 | continue; 28 | } 29 | 30 | processors 31 | .get_mut(&metadata.method_name.clone()) 32 | .expect("Guaranteed by before if") 33 | .1 34 | .push(metadata); 35 | } 36 | 37 | for (processor, metadatas) in processors.values() { 38 | (*processor).process(&self, metadatas.to_vec()); 39 | } 40 | 41 | let properties = PROPERTIES.lock().unwrap(); 42 | let keywords = KEYWORDS.lock().unwrap(); 43 | 44 | match properties.get(&self.name) { 45 | Some(property) => { 46 | let condition = property.condition(); 47 | let join_value = |value: &Vec>| { 48 | value 49 | .iter() 50 | .map(|value| value.origin()) 51 | .collect::>() 52 | .join(" ") 53 | }; 54 | if self.value.len() != condition.len() { 55 | self.range 56 | .error(format!( 57 | "Expected parameter count: {}, Received parameter count: {}", 58 | condition.len(), 59 | self.value.len() 60 | )) 61 | .emit(); 62 | } else { 63 | for (condition, value) in condition.into_iter().zip((&self.value).into_iter()) { 64 | let any = value.as_any(); 65 | if (any.is::() || any.is::()) 66 | && condition.types_variant.contains(&ConditionType::Color) 67 | { 68 | continue; 69 | } 70 | if let Some(keyword) = any.downcast_ref::() { 71 | if condition.types_variant.contains(&ConditionType::Keyword) { 72 | match &keyword.keyword_type { 73 | CssKeywordType::NotWide(s) => { 74 | if keywords 75 | .get(s) 76 | .map(|set| { 77 | set.contains(&format!( 78 | "{}{}", 79 | self.prefix, self.name 80 | )) 81 | }) 82 | .unwrap_or(false) 83 | { 84 | continue; 85 | } else { 86 | self.range 87 | .error(format!( 88 | "Unacceptable keyword `{}` on {}{}`", 89 | s, self.prefix, self.name 90 | )) 91 | .emit(); 92 | break; 93 | } 94 | } 95 | _ => { 96 | continue; 97 | } 98 | } 99 | } 100 | } 101 | if let Some(unit) = any.downcast_ref::() { 102 | if condition.types_variant.iter().any(|t| { 103 | if let ConditionType::Unit(groups) = t { 104 | groups.contains(&unit.group) 105 | } else { 106 | false 107 | } 108 | }) { 109 | continue; 110 | } 111 | } 112 | 113 | self.range 114 | .error(format!( 115 | "Unacceptable data `{}` on `{}{}`", 116 | join_value(&self.value), 117 | self.prefix, 118 | self.name 119 | )) 120 | .emit(); 121 | break; 122 | } 123 | } 124 | } 125 | None => { 126 | self.range 127 | .warning(format!("Unknown property {}", self.name)) 128 | .emit(); 129 | } 130 | } 131 | 132 | let value = &*self.value; 133 | return format!( 134 | "{prefix}{key}: {value};", 135 | prefix = self.prefix, 136 | key = self.name, 137 | value = value 138 | .iter() 139 | .map(|value| value.optimized_cssify()) 140 | .collect::>() 141 | .join(" ") 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides code generator for [`node`]. 2 | //! 3 | //! [`node`]: node 4 | 5 | #![feature(proc_macro_diagnostic)] 6 | #![feature(slice_concat_ext)] 7 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 8 | 9 | extern crate proc_macro; 10 | 11 | mod base; 12 | mod declaration; 13 | mod ruleset; 14 | 15 | #[doc(inline)] 16 | pub use base::*; 17 | #[doc(inline)] 18 | pub use declaration::*; 19 | #[doc(inline)] 20 | pub use ruleset::*; 21 | -------------------------------------------------------------------------------- /codegen/src/ruleset.rs: -------------------------------------------------------------------------------- 1 | use crate::CodeGenerator; 2 | use metadata::RootMetadataProcessor; 3 | use node::{DeclarationNode, MetadataNode, RulesetNode, RulesetType}; 4 | use runtime::global::ROOT_METADATA_PROCESSORS; 5 | use runtime_facade::CompileContext; 6 | use std::collections::HashMap; 7 | use std::slice::SliceConcatExt; 8 | 9 | impl CodeGenerator for RulesetNode { 10 | /// Generate code from [`RulesetNode`]. 11 | /// 12 | /// [`DeclarationNode`]: node::RulesetNode 13 | fn generate_code(&self, base_class: &str, context: &mut CompileContext) -> String { 14 | if self.ruleset_type == RulesetType::Root { 15 | let root_metadata_processors = ROOT_METADATA_PROCESSORS.lock().unwrap(); 16 | 17 | let mut processors = 18 | HashMap::, Vec)>::new(); 19 | 20 | for processor in root_metadata_processors.values() { 21 | processors.insert(processor.name().to_string(), (processor, Vec::new())); 22 | } 23 | 24 | for metadata in self.metadatas.clone() { 25 | if !processors.contains_key(&metadata.method_name) { 26 | metadata.range.error("Unknown metadata").emit(); 27 | continue; 28 | } 29 | 30 | processors 31 | .get_mut(&metadata.method_name.clone()) 32 | .expect("Guaranteed by before if") 33 | .1 34 | .push(metadata); 35 | } 36 | 37 | for (processor, metadatas) in processors.values() { 38 | (*processor).process(context, metadatas.to_vec()); 39 | } 40 | } 41 | 42 | let mut result = String::new(); 43 | 44 | let base = match &self.ruleset_type { 45 | RulesetType::Root => format!(".{}", base_class), 46 | RulesetType::Selector(group) => format!( 47 | "\n{}", 48 | group 49 | .iter() 50 | .map(|selector| selector.stringify(base_class.to_string())) 51 | .collect::>() 52 | .join(",") 53 | ), 54 | }; 55 | 56 | result.push_str(&base); 57 | 58 | result.push_str(" {\n"); 59 | 60 | let mut appeared_nodes = HashMap::::new(); 61 | 62 | let alert_duplicated = |node: &DeclarationNode| { 63 | node.range 64 | .warning(format!( 65 | "Consider removing duplicated property {}", 66 | node.name 67 | )) 68 | .emit(); 69 | }; 70 | 71 | for declaration in &self.declarations { 72 | let is_duplicated = if let Some(before) = appeared_nodes.get(&declaration.name) { 73 | alert_duplicated(before.0); 74 | true 75 | } else { 76 | false 77 | }; 78 | 79 | appeared_nodes.insert(declaration.name.clone(), (declaration, is_duplicated)); 80 | } 81 | 82 | for (node, is_duplicated) in appeared_nodes.values() { 83 | if *is_duplicated { 84 | alert_duplicated(node); 85 | } 86 | result.push_str(" "); 87 | result.push_str(&node.generate_code(base_class, context)); 88 | result.push_str("\n"); 89 | } 90 | 91 | result.push_str("}"); 92 | 93 | for ruleset in &self.nested_rulesets { 94 | result.push_str(&ruleset.generate_code(base_class, context)); 95 | } 96 | 97 | result 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /csstype/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "csstype" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | 9 | [dev-dependencies] 10 | proptest = "0.9.3" -------------------------------------------------------------------------------- /csstype/src/base.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::fmt::Debug; 3 | 4 | /// A trait can convert self as an [`Any`] type. 5 | /// 6 | /// [`Any`]: std::any::Any 7 | pub trait AsAny { 8 | /// Convert self to [`Any`] 9 | fn as_any(&self) -> &dyn Any; 10 | } 11 | 12 | /// A trait can be converted to a valid css value. 13 | pub trait Cssifiable: AsAny + Any + Debug { 14 | /// Original text. 15 | fn origin(&self) -> String; 16 | 17 | /// Cssified value with less converting. 18 | fn cssify(&self) -> String; 19 | 20 | /// Cssified & Optimized value, maybe loses original meaning. 21 | fn optimized_cssify(&self) -> String { 22 | self.cssify() 23 | } 24 | } 25 | 26 | impl AsAny for T { 27 | fn as_any(&self) -> &dyn Any { 28 | self 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /csstype/src/color/base.rs: -------------------------------------------------------------------------------- 1 | use crate::Cssifiable; 2 | use crate::{HslColor, RgbColor}; 3 | use std::fmt::Debug; 4 | 5 | /// A trait which refers color type of CSS. 6 | pub trait Color: Debug { 7 | /// Original text. 8 | fn origin(&self) -> String; 9 | 10 | /// Color alpha value. 11 | fn alpha(&self) -> u8; 12 | 13 | /// Convert self as a RGB Color. 14 | fn as_rgb(&self) -> RgbColor; 15 | 16 | /// Convert self as a HSL Color. 17 | fn as_hsl(&self) -> HslColor; 18 | } 19 | 20 | impl Cssifiable for T { 21 | fn origin(&self) -> String { 22 | self.origin() 23 | } 24 | 25 | fn cssify(&self) -> String { 26 | let rgb = self.as_rgb(); 27 | let mut result = String::new(); 28 | result.push_str("#"); 29 | result.push_str(&format!("{:02x}", rgb.red)); 30 | result.push_str(&format!("{:02x}", rgb.green)); 31 | result.push_str(&format!("{:02x}", rgb.blue)); 32 | 33 | if rgb.alpha != 0xff { 34 | result.push_str(&format!("{:02x}", rgb.alpha)); 35 | } 36 | 37 | result 38 | } 39 | 40 | fn optimized_cssify(&self) -> String { 41 | if self.alpha() == 0 { 42 | return "#0000".into(); 43 | } 44 | let cssified = self.cssify(); 45 | let cssified_without_sharp = &cssified[1..]; 46 | // we knew the cssified result(does not including #) has a length 6 or 8 47 | let (even, odd) = cssified_without_sharp 48 | .char_indices() 49 | .partition::<(Vec<(usize, char)>), _>(|(index, _)| index % 2 == 0); 50 | 51 | for ((_, a), (_, b)) in odd.iter().zip(even.iter()) { 52 | if a != b { 53 | return cssified; 54 | } 55 | } 56 | 57 | let mut result = String::new(); 58 | 59 | result.push_str("#"); 60 | result.push_str(&odd.into_iter().map(|(_, c)| c).collect::()); 61 | 62 | result 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /csstype/src/color/hslcolor.rs: -------------------------------------------------------------------------------- 1 | use crate::{Color, RgbColor}; 2 | 3 | /// CSS [`Color`] which stores color by HSL system. 4 | /// 5 | /// [`Color`]: crate::color::Color 6 | #[derive(Debug)] 7 | pub struct HslColor { 8 | /// Original text. 9 | pub origin: String, 10 | /// Hue. 11 | pub hue: f32, 12 | /// Saturation. 13 | pub saturation: f32, 14 | /// Lightness. 15 | pub lightness: f32, 16 | /// Alpha. 17 | pub alpha: u8, 18 | } 19 | 20 | impl Color for HslColor { 21 | fn origin(&self) -> String { 22 | self.origin.clone() 23 | } 24 | 25 | fn alpha(&self) -> u8 { 26 | self.alpha 27 | } 28 | 29 | fn as_rgb(&self) -> RgbColor { 30 | panic!("Not implemented") 31 | } 32 | 33 | fn as_hsl(&self) -> HslColor { 34 | HslColor { 35 | origin: self.origin(), 36 | hue: self.hue, 37 | saturation: self.saturation, 38 | lightness: self.lightness, 39 | alpha: self.alpha, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /csstype/src/color/mod.rs: -------------------------------------------------------------------------------- 1 | mod base; 2 | mod hslcolor; 3 | mod rgbcolor; 4 | 5 | #[doc(inline)] 6 | pub use base::*; 7 | #[doc(inline)] 8 | pub use hslcolor::*; 9 | #[doc(inline)] 10 | pub use rgbcolor::*; 11 | -------------------------------------------------------------------------------- /csstype/src/color/rgbcolor.rs: -------------------------------------------------------------------------------- 1 | use crate::{Color, HslColor}; 2 | use std::cmp::Ordering::Equal; 3 | 4 | /// CSS [`Color`] which stores color by RGB system. 5 | /// 6 | /// [`Color`]: crate::color::Color 7 | #[derive(Debug)] 8 | pub struct RgbColor { 9 | /// Original text. 10 | pub origin: String, 11 | /// Red. 12 | pub red: u8, 13 | /// Green. 14 | pub green: u8, 15 | /// Blue. 16 | pub blue: u8, 17 | /// Alpha 18 | pub alpha: u8, 19 | } 20 | 21 | // `origin` should not be compared. 22 | impl PartialEq for RgbColor { 23 | fn eq(&self, other: &Self) -> bool { 24 | self.red == other.red 25 | && self.green == other.green 26 | && self.blue == other.blue 27 | && self.alpha == other.alpha 28 | } 29 | } 30 | 31 | impl Color for RgbColor { 32 | fn origin(&self) -> String { 33 | self.origin.clone() 34 | } 35 | 36 | fn alpha(&self) -> u8 { 37 | self.alpha 38 | } 39 | 40 | fn as_rgb(&self) -> RgbColor { 41 | RgbColor { 42 | origin: self.origin.clone(), 43 | 44 | red: self.red, 45 | green: self.green, 46 | blue: self.blue, 47 | alpha: self.alpha, 48 | } 49 | } 50 | 51 | fn as_hsl(&self) -> HslColor { 52 | let red_ranged = f32::from(self.red) / 255.0; 53 | let green_ranged = f32::from(self.green) / 255.0; 54 | let blue_ranged = f32::from(self.blue) / 255.0; 55 | 56 | let mut vec = vec![red_ranged, green_ranged, blue_ranged]; 57 | vec.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Equal)); 58 | let (min, max) = (vec[0], vec[2]); 59 | let delta = max - min; 60 | 61 | let hue = if (max - min).abs() < std::f32::EPSILON { 62 | 0.0 63 | } else if (red_ranged - max).abs() < std::f32::EPSILON { 64 | (green_ranged - blue_ranged) / delta 65 | } else if (green_ranged - max).abs() < std::f32::EPSILON { 66 | 2.0 + (blue_ranged - red_ranged) / delta 67 | } else { 68 | 4.0 + (red_ranged - green_ranged) / delta 69 | }; 70 | 71 | let hue = hue * 60.0; 72 | let hue = if hue > 360.0 { 360.0 } else { hue }; 73 | let hue = if hue < 0.0 { hue + 360.0 } else { hue }; 74 | 75 | let lightness = (min + max) / 2.0; 76 | 77 | let saturation = if (max - min).abs() < std::f32::EPSILON { 78 | 0.0 79 | } else if lightness <= 0.5 { 80 | delta / (max + min) 81 | } else { 82 | delta / (2.0 - max - min) 83 | }; 84 | 85 | HslColor { 86 | origin: self.origin.clone(), 87 | hue, 88 | saturation, 89 | lightness, 90 | alpha: self.alpha, 91 | } 92 | } 93 | } 94 | 95 | /// The error type which can be occured when parse hex color. 96 | #[derive(Debug, PartialEq)] 97 | pub enum ColorParseError { 98 | /// When the string is empty 99 | StringEmpty, 100 | /// When the string does not starts with `#` character. 101 | NotAHexColor, 102 | /// When the string starts with `#` character but trailing letters are invalid. 103 | InvalidHexColor, 104 | } 105 | 106 | impl RgbColor { 107 | /// Parses color from the string by hex notation. 108 | /// 109 | /// # Arguments 110 | /// - `input` - The string to parse. 111 | pub fn parse_hex(input: &str) -> Result { 112 | if input.is_empty() { 113 | Err(ColorParseError::StringEmpty)? 114 | } 115 | if input.chars().next().expect("guaranteed by before if") != '#' { 116 | Err(ColorParseError::NotAHexColor)? 117 | } 118 | 119 | let color = &input[1..]; 120 | 121 | if color.chars().any(|ch| match ch { 122 | '0'..='9' | 'a'..='f' | 'A'..='F' => false, 123 | _ => true, 124 | }) { 125 | Err(ColorParseError::InvalidHexColor)? 126 | } 127 | 128 | let mut color_chars = color.chars(); 129 | 130 | let mut read = |count: usize| { 131 | let mut s = String::new(); 132 | 133 | for _ in 0..count { 134 | s.push(color_chars.next().expect("guaranteed by caller")); 135 | } 136 | 137 | u8::from_str_radix(&s, 16).expect("guaranteed by caller") 138 | }; 139 | 140 | let dup = |v: u8| v + (v << 4); 141 | 142 | match color.len() { 143 | 3 => Ok(RgbColor { 144 | origin: input.to_string(), 145 | red: dup(read(1)), 146 | green: dup(read(1)), 147 | blue: dup(read(1)), 148 | alpha: 0xff, 149 | }), 150 | 4 => Ok(RgbColor { 151 | origin: input.to_string(), 152 | red: dup(read(1)), 153 | green: dup(read(1)), 154 | blue: dup(read(1)), 155 | alpha: dup(read(1)), 156 | }), 157 | 6 => Ok(RgbColor { 158 | origin: input.to_string(), 159 | red: read(2), 160 | green: read(2), 161 | blue: read(2), 162 | alpha: 0xff, 163 | }), 164 | 8 => Ok(RgbColor { 165 | origin: input.to_string(), 166 | red: read(2), 167 | green: read(2), 168 | blue: read(2), 169 | alpha: read(2), 170 | }), 171 | _ => Err(ColorParseError::InvalidHexColor)?, 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /csstype/src/keyword.rs: -------------------------------------------------------------------------------- 1 | use super::Cssifiable; 2 | 3 | /// A type of CSS keywords. 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub enum CssKeywordType { 6 | /// Wide CSS keyword `initial`. 7 | Initial, 8 | /// Wide CSS keyword `inherit`. 9 | Inherit, 10 | /// Wide CSS keyword `unset`. 11 | Unset, 12 | /// Not wide CSS keywords. 13 | NotWide(String), 14 | } 15 | 16 | /// CSS keyword value. 17 | #[derive(Clone, Debug)] 18 | pub struct CssKeyword { 19 | /// Original text. 20 | pub origin: String, 21 | /// The type of keyword. 22 | pub keyword_type: CssKeywordType, 23 | } 24 | 25 | impl Cssifiable for CssKeyword { 26 | fn origin(&self) -> String { 27 | self.origin.clone() 28 | } 29 | 30 | fn cssify(&self) -> String { 31 | match &self.keyword_type { 32 | CssKeywordType::Inherit => "inherit".into(), 33 | CssKeywordType::Initial => "initial".into(), 34 | CssKeywordType::Unset => "unset".into(), 35 | CssKeywordType::NotWide(s) => s.clone(), 36 | } 37 | } 38 | } 39 | 40 | impl From for CssKeyword { 41 | fn from(input: String) -> CssKeyword { 42 | let keyword_type = match input.to_lowercase().as_str() { 43 | "inherit" => CssKeywordType::Inherit, 44 | "initial" => CssKeywordType::Initial, 45 | "unset" => CssKeywordType::Unset, 46 | _ => CssKeywordType::NotWide(input.clone()), 47 | }; 48 | 49 | CssKeyword { 50 | origin: input.clone(), 51 | keyword_type, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /csstype/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a type which can store CSS values. 2 | 3 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 4 | 5 | mod base; 6 | mod color; 7 | mod keyword; 8 | mod unit; 9 | 10 | #[doc(inline)] 11 | pub use base::*; 12 | #[doc(inline)] 13 | pub use color::*; 14 | #[doc(inline)] 15 | pub use keyword::*; 16 | #[doc(inline)] 17 | pub use unit::*; 18 | -------------------------------------------------------------------------------- /csstype/src/unit.rs: -------------------------------------------------------------------------------- 1 | use super::Cssifiable; 2 | 3 | /// The group of CSS units. 4 | #[derive(Debug, PartialEq, Clone)] 5 | pub enum CssUnitGroup { 6 | /// Integer like `0`. 7 | Integer, 8 | /// Number like `0.1`. 9 | Number, 10 | /// Font relative length like `1em`. 11 | FontRelativeLength, 12 | /// Viewport relative length like `1vw`. 13 | ViewportRelativeLength, 14 | /// Absolute length like `1in`. 15 | AbsoluteLength, 16 | /// Angle like `100deg`. 17 | Angle, 18 | /// Time like `1s`. 19 | Time, 20 | /// Frequency like `1Hz`. 21 | Frequency, 22 | /// Resolution like `1ppi`. 23 | Resolution, 24 | /// Percentage like `1%`. 25 | Percentage, 26 | } 27 | 28 | // ? I just waste a little time; 29 | /* 30 | pub enum CssUnitType { 31 | Integer, 32 | Number, 33 | Em, 34 | Ex, 35 | Cap, 36 | Ch, 37 | Ic, 38 | Rem, 39 | Lh, 40 | Rlh, 41 | Vw, 42 | Vh, 43 | Vi, 44 | Vb, 45 | Vmin, 46 | Vmax, 47 | Cm, 48 | Mm, 49 | Q, 50 | In, 51 | Pc, 52 | Pt, 53 | Px, 54 | Deg, 55 | Grad, 56 | Rad, 57 | Turn, 58 | S, 59 | Ms, 60 | Hz, 61 | Khz, 62 | Dpi, 63 | Dpcm, 64 | Dppx, 65 | } 66 | */ 67 | 68 | /// CSS unit value. 69 | #[derive(Debug)] 70 | pub struct CssUnit { 71 | /// Original text. 72 | pub origin: String, 73 | /// The group of this unit. 74 | pub group: CssUnitGroup, 75 | /// The number part value. 76 | pub number: f64, 77 | /// The unit name. 78 | pub unit: String, 79 | } 80 | 81 | impl Cssifiable for CssUnit { 82 | fn origin(&self) -> String { 83 | self.origin.clone() 84 | } 85 | 86 | fn cssify(&self) -> String { 87 | format!("{}{}", self.number, self.unit) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /csstype/tests/parse_color.rs: -------------------------------------------------------------------------------- 1 | extern crate csstype; 2 | 3 | #[cfg(test)] 4 | mod parse_color { 5 | use csstype::RgbColor; 6 | #[test] 7 | fn must_success() { 8 | assert_eq!( 9 | RgbColor::parse_hex("#000"), 10 | Ok(RgbColor { 11 | origin: "#000".to_string(), 12 | red: 0x00, 13 | green: 0x00, 14 | blue: 0x00, 15 | alpha: 0xff 16 | }) 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_hygiene)] 2 | use rustyle::{css_files, css_use, rustyle}; 3 | 4 | fn main() { 5 | #[css_use] 6 | let red = "#c0ffee"; 7 | 8 | let (test, test_file) = rustyle! { 9 | min-height: 10.5rem; 10 | background-color: #00aabb; 11 | 12 | &:hover::first-letter *.head { 13 | color: #c00; 14 | } 15 | 16 | #[no_warn(vendor_prefix)] 17 | -moz-user-select: none; 18 | 19 | // todo: css_use 20 | // background-color: ${red}; 21 | }; 22 | 23 | println!("test is {} at {}", test, test_file); 24 | 25 | println!("All files are listed here:\n{:?}", css_files!()); 26 | } 27 | -------------------------------------------------------------------------------- /metadata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "metadata" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | node = { path = "../node" } 9 | runtime_facade = { path = "../runtime_facade" } -------------------------------------------------------------------------------- /metadata/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_diagnostic)] 2 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 3 | 4 | mod processor; 5 | 6 | pub mod root; 7 | pub mod rule; 8 | pub mod util; 9 | pub use processor::*; 10 | 11 | pub trait MetadataRegisterer { 12 | fn register_rule_metadata(&self, metadata_processor: M) 13 | where 14 | M: RuleMetadataProcessor, 15 | M: Sized, 16 | M: 'static; 17 | 18 | fn register_root_metadata(&self, metadata_processor: M) 19 | where 20 | M: RootMetadataProcessor, 21 | M: Sized, 22 | M: 'static; 23 | } 24 | 25 | pub fn register_all_metadatas(registerer: &R) 26 | where 27 | R: MetadataRegisterer, 28 | { 29 | rule::register_rule_metadatas(registerer); 30 | root::register_root_metadatas(registerer); 31 | } 32 | -------------------------------------------------------------------------------- /metadata/src/processor.rs: -------------------------------------------------------------------------------- 1 | use node::{DeclarationNode, MetadataNode}; 2 | use runtime_facade::CompileContext; 3 | use std::fmt::Debug; 4 | 5 | pub trait RuleMetadataProcessor: Sync + Send + Debug { 6 | fn name(&self) -> &str; 7 | 8 | fn process(&self, node: &DeclarationNode, metadatas: Vec); 9 | } 10 | 11 | pub trait RootMetadataProcessor: Sync + Send + Debug { 12 | fn name(&self) -> &str; 13 | 14 | fn process(&self, context: &mut CompileContext, metadatas: Vec); 15 | } 16 | -------------------------------------------------------------------------------- /metadata/src/root/filename.rs: -------------------------------------------------------------------------------- 1 | use crate::{util, RootMetadataProcessor}; 2 | use node::MetadataNode; 3 | use runtime_facade::CompileContext; 4 | 5 | #[derive(Debug)] 6 | pub struct Filename; 7 | 8 | impl RootMetadataProcessor for Filename { 9 | fn name(&self) -> &str { 10 | "filename" 11 | } 12 | fn process(&self, context: &mut CompileContext, metadatas: Vec) { 13 | if metadatas.is_empty() { 14 | return; 15 | } 16 | for metadata in (&metadatas).into_iter().take(metadatas.len() - 1) { 17 | util::no_duplicate(&metadata); 18 | } 19 | let last = metadatas.last().expect("Guaranteed by caller"); 20 | let param = match util::check_param_exact(1, &last, false) { 21 | util::ParameterType::Less => return, 22 | util::ParameterType::Matched | util::ParameterType::Over => &last.parameters[0], 23 | }; 24 | context.filename = param.clone(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /metadata/src/root/mod.rs: -------------------------------------------------------------------------------- 1 | mod filename; 2 | 3 | pub use filename::*; 4 | 5 | use crate::MetadataRegisterer; 6 | 7 | pub fn register_root_metadatas(registerer: &R) 8 | where 9 | R: MetadataRegisterer, 10 | { 11 | registerer.register_root_metadata(Filename); 12 | } 13 | -------------------------------------------------------------------------------- /metadata/src/rule/mod.rs: -------------------------------------------------------------------------------- 1 | mod no_warn; 2 | 3 | pub use no_warn::*; 4 | 5 | use crate::MetadataRegisterer; 6 | 7 | pub fn register_rule_metadatas(registerer: &R) 8 | where 9 | R: MetadataRegisterer, 10 | { 11 | registerer.register_rule_metadata(NoWarn); 12 | } 13 | -------------------------------------------------------------------------------- /metadata/src/rule/no_warn.rs: -------------------------------------------------------------------------------- 1 | use crate::{util, RuleMetadataProcessor}; 2 | use node::{DeclarationNode, MetadataNode}; 3 | 4 | #[derive(Debug)] 5 | pub struct NoWarn; 6 | 7 | struct ShouldWarn { 8 | vendor_prefix: bool, 9 | } 10 | 11 | impl RuleMetadataProcessor for NoWarn { 12 | fn name(&self) -> &str { 13 | "no_warn" 14 | } 15 | fn process(&self, node: &DeclarationNode, metadatas: Vec) { 16 | let mut warn = ShouldWarn { 17 | vendor_prefix: true, 18 | }; 19 | 20 | for metadata in metadatas { 21 | match util::check_param_exact(1, &metadata, false) { 22 | util::ParameterType::Less => continue, 23 | _ => {} 24 | } 25 | 26 | match metadata.parameters[0].as_str() { 27 | "vendor_prefix" => { 28 | if warn.vendor_prefix { 29 | warn.vendor_prefix = false; 30 | } else { 31 | util::no_duplicate(&metadata); 32 | } 33 | } 34 | param @ _ => { 35 | metadata 36 | .range 37 | .error(format!("Unexpected parameter {}", param)) 38 | .emit(); 39 | } 40 | } 41 | } 42 | 43 | if node.prefix.len() > 0 && warn.vendor_prefix { 44 | node.range 45 | .warning("Consider removing the vendor prefix") 46 | .emit(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /metadata/src/util.rs: -------------------------------------------------------------------------------- 1 | use node::MetadataNode; 2 | use std::ops::{Bound, RangeBounds}; 3 | 4 | pub enum ParameterType { 5 | Less, 6 | Matched, 7 | Over, 8 | } 9 | 10 | fn pluralize(i: u32, unit: &str) -> String { 11 | match i { 12 | 0 => format!("no {}s", unit), 13 | 1 => format!("1 {}", unit), 14 | _ => format!("{} {}s", i, unit), 15 | } 16 | } 17 | 18 | fn pluralize_range(range: impl RangeBounds, unit: &str) -> String { 19 | if range.end_bound() == Bound::Unbounded { 20 | match range.start_bound() { 21 | Bound::Unbounded => format!("any {}", unit), 22 | Bound::Included(start) => pluralize(*start as u32, &*format!("or more {}", unit)), 23 | Bound::Excluded(start) => pluralize(*start as u32 + 1, &*format!("or more {}", unit)), 24 | } 25 | } else if range.start_bound() == Bound::Unbounded { 26 | match range.end_bound() { 27 | Bound::Unbounded => format!("any {}", unit), 28 | Bound::Included(end) => pluralize(*end as u32, &*format!("or less {}", unit)), 29 | Bound::Excluded(end) => pluralize((end - 1) as u32, &*format!("or less {}", unit)), 30 | } 31 | } else if range.start_bound() == range.end_bound() { 32 | match range.end_bound() { 33 | Bound::Unbounded => panic!("Never happen"), 34 | Bound::Included(end) | Bound::Excluded(end) => pluralize(*end as u32, unit), 35 | } 36 | } else { 37 | let start = match range.start_bound() { 38 | Bound::Unbounded => panic!("Never happen"), 39 | Bound::Included(value) | Bound::Excluded(value) => value, 40 | }; 41 | match range.start_bound() { 42 | Bound::Unbounded => panic!("Never happen"), 43 | Bound::Included(end) | Bound::Excluded(end) => { 44 | format!("{} ~ {}", start, pluralize(*end as u32, unit)) 45 | } 46 | } 47 | } 48 | } 49 | 50 | pub fn check_param_range( 51 | expected: impl RangeBounds, 52 | node: &MetadataNode, 53 | error_when_over: bool, 54 | ) -> ParameterType { 55 | let param_count = node.parameters.len() as i32; 56 | if expected.contains(&(param_count)) { 57 | return ParameterType::Matched; 58 | } 59 | if param_count 60 | < (match expected.start_bound() { 61 | Bound::Unbounded => 0, 62 | Bound::Included(val) => *val, 63 | Bound::Excluded(val) => (*val - 1), 64 | }) 65 | { 66 | node.range 67 | .error(format!( 68 | "{} expected but {} received", 69 | pluralize_range(expected, "parameter"), 70 | pluralize(param_count as u32, "parameter") 71 | )) 72 | .emit(); 73 | return ParameterType::Less; 74 | } else { 75 | if error_when_over { 76 | node.range 77 | .error(format!( 78 | "{} expected but {} received", 79 | pluralize_range(expected, "parameter"), 80 | pluralize(param_count as u32, "parameter") 81 | )) 82 | .emit(); 83 | } else { 84 | node.range 85 | .warning(format!( 86 | "{} expected but {} received", 87 | pluralize_range(expected, "parameter"), 88 | pluralize(param_count as u32, "parameter") 89 | )) 90 | .emit(); 91 | } 92 | return ParameterType::Over; 93 | } 94 | } 95 | 96 | pub fn check_param_exact( 97 | expected: i32, 98 | node: &MetadataNode, 99 | error_when_over: bool, 100 | ) -> ParameterType { 101 | check_param_range(expected..=expected, node, error_when_over) 102 | } 103 | 104 | pub fn no_duplicate(node: &MetadataNode) { 105 | node.range.warning("Consider removing duplicates").emit(); 106 | } 107 | -------------------------------------------------------------------------------- /node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "node" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | csstype = { path = "../csstype" } -------------------------------------------------------------------------------- /node/src/base.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::Span; 2 | 3 | pub trait Node { 4 | fn name(&self) -> &str; 5 | 6 | fn span(&self) -> Option { 7 | None 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /node/src/declaration.rs: -------------------------------------------------------------------------------- 1 | use super::MetadataNode; 2 | use super::Node; 3 | use csstype::Cssifiable; 4 | use proc_macro::Span; 5 | 6 | #[derive(Debug)] 7 | pub struct DeclarationNode { 8 | pub range: Span, 9 | pub prefix: String, 10 | pub name: String, 11 | pub value: Vec>, 12 | pub metadatas: Vec, 13 | } 14 | 15 | impl Node for DeclarationNode { 16 | fn name(&self) -> &str { 17 | "Declaration" 18 | } 19 | 20 | fn span(&self) -> Option { 21 | Some(self.range) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /node/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_span)] 2 | #![feature(proc_macro_diagnostic)] 3 | #![feature(slice_concat_ext)] 4 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 5 | 6 | extern crate proc_macro; 7 | 8 | mod base; 9 | mod declaration; 10 | mod metadata; 11 | mod ruleset; 12 | mod selector; 13 | 14 | pub use base::*; 15 | pub use declaration::*; 16 | pub use metadata::*; 17 | pub use ruleset::*; 18 | pub use selector::*; 19 | -------------------------------------------------------------------------------- /node/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use super::Node; 2 | use proc_macro::Span; 3 | 4 | #[derive(Debug, Clone)] 5 | pub enum MetadataType { 6 | Root, 7 | Rule, 8 | } 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct MetadataNode { 12 | pub range: Span, 13 | pub metadata_type: MetadataType, 14 | pub method_name: String, 15 | pub parameters: Vec, 16 | } 17 | 18 | impl Node for MetadataNode { 19 | fn name(&self) -> &str { 20 | match self.metadata_type { 21 | MetadataType::Root => "RootMetadata", 22 | MetadataType::Rule => "RuleMetadata", 23 | } 24 | } 25 | 26 | fn span(&self) -> Option { 27 | Some(self.range) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /node/src/ruleset.rs: -------------------------------------------------------------------------------- 1 | use super::{DeclarationNode, MetadataNode, Node, SelectorGroup}; 2 | use proc_macro::Span; 3 | 4 | #[derive(Debug, PartialEq)] 5 | pub enum RulesetType { 6 | Selector(SelectorGroup), 7 | Root, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub struct RulesetNode { 12 | pub range: Option, 13 | pub metadatas: Vec, 14 | pub declarations: Vec, 15 | pub nested_rulesets: Vec, 16 | pub ruleset_type: RulesetType, 17 | } 18 | 19 | impl Node for RulesetNode { 20 | fn name(&self) -> &str { 21 | match self.ruleset_type { 22 | RulesetType::Selector(_) => "Ruleset (Nested)", 23 | RulesetType::Root => "Ruleset", 24 | } 25 | } 26 | 27 | fn span(&self) -> Option { 28 | self.range 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /node/src/selector.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::Span; 2 | 3 | #[derive(Debug, Clone, PartialEq)] 4 | pub enum SelectorPartType { 5 | Itself, 6 | Spacing, 7 | Class { 8 | name: String, 9 | }, 10 | Id { 11 | name: String, 12 | }, 13 | Element { 14 | namespace: Option, 15 | name: String, 16 | }, 17 | Universal { 18 | namespace: Option, 19 | }, 20 | PseudoClass { 21 | name: String, 22 | // todo: parameter validation required? 23 | parameter: Option, 24 | }, 25 | PseudoElement { 26 | name: String, 27 | }, 28 | Child { 29 | selector: Selector, 30 | }, 31 | NextSibling { 32 | selector: Selector, 33 | }, 34 | SubsequentSibling { 35 | selector: Selector, 36 | }, 37 | } 38 | 39 | pub type SelectorPart = (SelectorPartType, Option); 40 | 41 | fn stringify(part: &SelectorPartType, class_name: String) -> String { 42 | #[allow(unreachable_patterns)] 43 | match part { 44 | SelectorPartType::Itself => format!(".{}", class_name), 45 | SelectorPartType::Spacing => " ".to_string(), 46 | SelectorPartType::Class { name } => format!(".{}", name), 47 | SelectorPartType::Id { name } => format!("#{}", name), 48 | SelectorPartType::Element { namespace, name } => format!( 49 | "{}{}", 50 | namespace 51 | .clone() 52 | .map_or("".to_string(), |namespace| format!("{}|", namespace)), 53 | name 54 | ), 55 | SelectorPartType::Universal { namespace } => format!( 56 | "{}*", 57 | namespace 58 | .clone() 59 | .map_or("".to_string(), |namespace| format!("{}|", namespace)), 60 | ), 61 | SelectorPartType::PseudoElement { name } => format!("::{}", name), 62 | SelectorPartType::PseudoClass { name, parameter } => format!( 63 | ":{}{}", 64 | name, 65 | if let Some(parameter) = parameter { 66 | format!("({})", parameter) 67 | } else { 68 | "".to_string() 69 | } 70 | ), 71 | SelectorPartType::Child { selector } => format!(">{}", selector.stringify(class_name)), 72 | 73 | _ => { 74 | Span::call_site() 75 | .error(format!("Not stringifiable selector part: {:?}", part)) 76 | .emit(); 77 | String::new() 78 | } 79 | } 80 | } 81 | 82 | #[derive(Debug, Clone)] 83 | pub struct Selector { 84 | pub parts: Vec, 85 | } 86 | 87 | impl PartialEq for Selector { 88 | fn eq(&self, other: &Self) -> bool { 89 | for (left, right) in self.parts.iter().zip(other.parts.iter()) { 90 | if left.0 != right.0 { 91 | return false; 92 | } 93 | } 94 | true 95 | } 96 | } 97 | 98 | impl Selector { 99 | pub fn stringify(&self, class_name: String) -> String { 100 | let mut result = String::new(); 101 | 102 | for part in &self.parts { 103 | result.push_str(&stringify(&part.0, class_name.clone())); 104 | } 105 | 106 | result 107 | } 108 | 109 | pub fn span(&self) -> Option { 110 | if let (Some(first), Some(last)) = (self.parts.first(), self.parts.last()) { 111 | first.1.map_or(last.1, |first| { 112 | last.1 113 | .map(|last| first.join(last).expect("In the same file")) 114 | .or(Some(first)) 115 | }) 116 | } else { 117 | None 118 | } 119 | } 120 | } 121 | 122 | pub type SelectorGroup = Vec; 123 | -------------------------------------------------------------------------------- /parse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | quote = "0.6.12" 9 | codegen = { path = "../codegen" } 10 | csstype = { path = "../csstype" } 11 | metadata = { path = "../metadata" } 12 | node = { path = "../node" } 13 | runtime = { path = "../runtime" } 14 | property = { path = "../property" } 15 | runtime_facade = { path = "../runtime_facade" } -------------------------------------------------------------------------------- /parse/src/color.rs: -------------------------------------------------------------------------------- 1 | use csstype::{ColorParseError, Cssifiable, RgbColor}; 2 | use proc_macro::{Span, TokenTree}; 3 | 4 | pub fn parse_color_hex( 5 | sharp: TokenTree, 6 | tokens: &mut I, 7 | ) -> (Option>, Option) 8 | where 9 | I: Iterator, 10 | { 11 | let invalid_hex = |span: Span| span.error("Invalid hex color").emit(); 12 | if let Some(token) = tokens.next() { 13 | let stringified = token.to_string(); 14 | let parsed_color = RgbColor::parse_hex(&format!( 15 | "#{}", 16 | if stringified.starts_with("\"") { 17 | &stringified[1..stringified.len() - 1] 18 | } else { 19 | stringified.as_str() 20 | } 21 | )); 22 | match parsed_color { 23 | Ok(color) => (Some(Box::new(color)), Some(token.span())), 24 | Err(cause) => match cause { 25 | ColorParseError::StringEmpty | ColorParseError::NotAHexColor => { 26 | panic!("guaranteed by if let") 27 | } 28 | ColorParseError::InvalidHexColor => { 29 | invalid_hex(sharp.span().join(token.span()).expect("In the same file")); 30 | ( 31 | None, 32 | Some(sharp.span().join(token.span()).expect("In the same file")), 33 | ) 34 | } 35 | }, 36 | } 37 | } else { 38 | invalid_hex(sharp.span()); 39 | (None, Some(sharp.span())) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /parse/src/declaration.rs: -------------------------------------------------------------------------------- 1 | use crate::parse_expression; 2 | use node::{DeclarationNode, MetadataNode}; 3 | use proc_macro::{Span, TokenTree}; 4 | use runtime::global::VENDOR_PREFIXES; 5 | use std::iter::Peekable; 6 | 7 | pub fn parse_declaration( 8 | metadatas: Vec, 9 | tokens: &mut Peekable, 10 | ) -> Option 11 | where 12 | I: Iterator, 13 | { 14 | let mut first = None; 15 | let mut expr_span: Option = None; 16 | let mut key = Vec::::new(); 17 | let mut ignore_tails = false; 18 | 19 | while let Some(token) = tokens.next().clone() { 20 | if first.is_none() { 21 | first = Some(token.clone()); 22 | } 23 | 24 | if ignore_tails { 25 | if let TokenTree::Punct(ref punct) = token { 26 | if punct.as_char() == ';' { 27 | expr_span 28 | .unwrap_or( 29 | first 30 | .expect("Always exists") 31 | .span() 32 | .join(punct.span()) 33 | .expect("In the same file"), 34 | ) 35 | .error("Invalid expression assigned") 36 | .emit(); 37 | 38 | break; 39 | } 40 | } else { 41 | expr_span = if let Some(expr_span) = expr_span { 42 | expr_span.join(token.span()) 43 | } else { 44 | Some(token.span()) 45 | }; 46 | } 47 | continue; 48 | } 49 | 50 | match token { 51 | TokenTree::Punct(ref punct) if punct.as_char() == ';' => { 52 | if key.is_empty() { 53 | return None; 54 | } 55 | } 56 | TokenTree::Punct(ref punct) if punct.as_char() == ':' => { 57 | let mut key_str = String::new(); 58 | let mut last_span: Option = None; 59 | let mut spaced = false; 60 | 61 | let report = |span: Span| span.help("Consider removing space").emit(); 62 | 63 | let mut iter = key.iter(); 64 | while let Some(token) = iter.next() { 65 | if let Some(end_span) = last_span { 66 | let start_span = token.span(); 67 | if end_span.end() != start_span.start() { 68 | spaced = true; 69 | last_span = end_span.join(start_span); 70 | } else { 71 | if spaced { 72 | spaced = false; 73 | report(last_span.unwrap()); 74 | } 75 | 76 | last_span = Some(token.span()); 77 | } 78 | } else { 79 | last_span = Some(token.span()); 80 | } 81 | 82 | key_str.push_str(&token.to_string()); 83 | } 84 | 85 | if spaced { 86 | if let Some(span) = last_span { 87 | report(span); 88 | } 89 | } 90 | 91 | let (expr, span) = parse_expression(tokens); 92 | 93 | expr_span = span; 94 | 95 | if let Some(expr) = expr { 96 | let prefix = VENDOR_PREFIXES 97 | .iter() 98 | .find(|prefix| key_str.starts_with(*prefix)) 99 | .map(|prefix| *prefix) 100 | .unwrap_or(""); 101 | let name = if prefix.is_empty() { 102 | key_str 103 | } else { 104 | key_str[prefix.len()..].to_string() 105 | }; 106 | 107 | let result = DeclarationNode { 108 | range: first 109 | .expect("Always exists") 110 | .span() 111 | .join(span.expect("Always exists")) 112 | .expect("In the same file"), 113 | prefix: prefix.to_string(), 114 | name: name, 115 | // todo: multiple expression support (e.g. border: 1px solid black) 116 | value: vec![expr], 117 | metadatas: metadatas, 118 | }; 119 | return Some(result); 120 | } else { 121 | ignore_tails = true; 122 | } 123 | } 124 | _ => { 125 | key.push(token); 126 | } 127 | } 128 | } 129 | 130 | None 131 | } 132 | -------------------------------------------------------------------------------- /parse/src/expression.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | parse_color_hex, parse_css_keyword, parse_css_wide_keyword, parse_unit, parse_variable, 3 | }; 4 | use csstype::Cssifiable; 5 | use proc_macro::{Span, TokenTree}; 6 | use std::iter::Peekable; 7 | 8 | pub fn parse_expression(tokens: &mut Peekable) -> (Option>, Option) 9 | where 10 | I: Iterator, 11 | { 12 | let token = tokens.peek(); 13 | if token.is_none() { 14 | return (None, None); 15 | } 16 | let token = token.expect("guaranteed by if").clone(); 17 | 18 | match token { 19 | TokenTree::Punct(ref punct) if punct.as_char() == '#' => { 20 | tokens.next(); 21 | parse_color_hex(token, tokens) 22 | } 23 | TokenTree::Punct(ref punct) if punct.as_char() == '$' => { 24 | tokens.next(); 25 | parse_variable(token, tokens) 26 | } 27 | TokenTree::Ident(ref ident) => { 28 | let keyword = parse_css_wide_keyword(ident).or_else(|| parse_css_keyword(ident)); 29 | 30 | if keyword.is_some() { 31 | tokens.next(); 32 | (keyword, Some(ident.span())) 33 | } else { 34 | parse_unit(tokens) 35 | } 36 | } 37 | _ => parse_unit(tokens), 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /parse/src/keyword.rs: -------------------------------------------------------------------------------- 1 | use csstype::{CssKeyword, Cssifiable}; 2 | use proc_macro::Ident; 3 | use runtime::global::KEYWORDS; 4 | 5 | pub fn parse_css_wide_keyword(ident: &Ident) -> Option> { 6 | let ident_str = ident.to_string(); 7 | let keyword_validity = match ident_str.to_lowercase().as_str() { 8 | "initial" | "inherit" | "unset" => true, 9 | _ => false, 10 | }; 11 | 12 | if keyword_validity { 13 | Some(Box::new(CssKeyword::from(ident_str))) 14 | } else { 15 | None 16 | } 17 | } 18 | 19 | pub fn parse_css_keyword(ident: &Ident) -> Option> { 20 | let keywords = KEYWORDS.lock().unwrap(); 21 | 22 | let ident_str = ident.to_string(); 23 | 24 | if keywords.contains_key(&ident_str) { 25 | Some(Box::new(CssKeyword::from(ident_str))) 26 | } else { 27 | None 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /parse/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_diagnostic)] 2 | #![feature(proc_macro_span)] 3 | 4 | extern crate proc_macro; 5 | 6 | mod color; 7 | mod declaration; 8 | mod expression; 9 | mod keyword; 10 | mod metadata; 11 | mod root; 12 | mod ruleset; 13 | mod selector; 14 | mod unit; 15 | mod variable; 16 | 17 | pub use self::metadata::*; 18 | pub use color::*; 19 | pub use declaration::*; 20 | pub use expression::*; 21 | pub use keyword::*; 22 | pub use root::*; 23 | pub use ruleset::*; 24 | pub use selector::*; 25 | pub use unit::*; 26 | pub use variable::*; 27 | -------------------------------------------------------------------------------- /parse/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use node::{MetadataNode, MetadataType}; 2 | use proc_macro::{Delimiter, TokenTree}; 3 | use std::iter::Peekable; 4 | 5 | pub fn parse_metadata( 6 | sharp: TokenTree, 7 | tokens: &mut Peekable, 8 | ) -> Option 9 | where 10 | I: Iterator, 11 | { 12 | parse_metadata_common(sharp, false, tokens, MetadataType::Rule) 13 | } 14 | 15 | pub fn parse_ruleset_metadata( 16 | sharp: TokenTree, 17 | exclamation: bool, 18 | tokens: &mut Peekable, 19 | ) -> Option 20 | where 21 | I: Iterator, 22 | { 23 | parse_metadata_common(sharp, exclamation, tokens, MetadataType::Root) 24 | } 25 | 26 | fn parse_metadata_common( 27 | sharp: TokenTree, 28 | exclamation: bool, 29 | tokens: &mut Peekable, 30 | metadata_type: MetadataType, 31 | ) -> Option 32 | where 33 | I: Iterator, 34 | { 35 | match tokens.next() { 36 | Some(TokenTree::Punct(ref punct)) if !exclamation && punct.as_char() == '!' => { 37 | parse_ruleset_metadata(sharp, true, tokens) 38 | } 39 | Some(TokenTree::Group(ref group)) => { 40 | if group.delimiter() != Delimiter::Bracket { 41 | group 42 | .span() 43 | .error("Metadata should be wrapped with []") 44 | .emit(); 45 | return None; 46 | } 47 | let mut tokens = group.stream().into_iter(); 48 | let current = tokens.next(); 49 | let name = match current { 50 | Some(TokenTree::Ident(ref token)) => token, 51 | _ => { 52 | group.span().error("Metadata name is not valid").emit(); 53 | return None; 54 | } 55 | }; 56 | 57 | let group_tokens = match tokens.next() { 58 | Some(TokenTree::Group(ref token)) => { 59 | if token.delimiter() != Delimiter::Parenthesis { 60 | token 61 | .span() 62 | .error("Metadata should be wrapped with ()") 63 | .emit(); 64 | return None; 65 | } 66 | Some(token.stream()) 67 | } 68 | None => None, 69 | 70 | Some(token) => { 71 | token 72 | .span() 73 | .join(tokens.last().unwrap_or(token).span()) 74 | .expect("In the same file") 75 | .error("Metadata parameters are not valid") 76 | .emit(); 77 | return None; 78 | } 79 | }; 80 | 81 | let parameters = if let Some(group_tokens) = group_tokens { 82 | let mut group_tokens = group_tokens.into_iter(); 83 | let mut token_buffer = Vec::::new(); 84 | let mut parameters = Vec::::new(); 85 | 86 | let emit_buffer = 87 | |parameters: &mut Vec, token_buffer: &mut Vec| { 88 | if token_buffer.is_empty() { 89 | return; 90 | } 91 | parameters.push( 92 | token_buffer 93 | .iter() 94 | .map(|t| t.to_string()) 95 | .collect::(), 96 | ); 97 | token_buffer.clear(); 98 | }; 99 | 100 | while let Some(token) = group_tokens.next() { 101 | match token { 102 | TokenTree::Punct(ref punct) if punct.as_char() == ',' => { 103 | emit_buffer(&mut parameters, &mut token_buffer); 104 | } 105 | _ => { 106 | token_buffer.push(token); 107 | } 108 | } 109 | } 110 | 111 | emit_buffer(&mut parameters, &mut token_buffer); 112 | 113 | parameters 114 | } else { 115 | Vec::new() 116 | }; 117 | 118 | Some(MetadataNode { 119 | metadata_type: metadata_type, 120 | method_name: name.to_string(), 121 | parameters: parameters, 122 | range: sharp.span().join(group.span()).expect("In the same file"), 123 | }) 124 | } 125 | token @ _ => { 126 | let mut span = if let Some(token) = token { 127 | sharp.span().join(token.span()).expect("In the same file") 128 | } else { 129 | sharp.span() 130 | }; 131 | let line = sharp.span().start().line; 132 | while let Some(token) = tokens.peek() { 133 | span = span.join(token.span()).expect("In the same file"); 134 | if token.span().end().line >= line { 135 | break; 136 | } 137 | } 138 | span.error("Invalid metadata detected").emit(); 139 | None 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /parse/src/root.rs: -------------------------------------------------------------------------------- 1 | use crate::parse_ruleset; 2 | use ::metadata::{ 3 | register_all_metadatas, MetadataRegisterer, RootMetadataProcessor, RuleMetadataProcessor, 4 | }; 5 | use node::RulesetNode; 6 | use proc_macro::TokenStream; 7 | use property::implementation::register_all_properties; 8 | use property::Registerer; 9 | use runtime::global::{ 10 | IS_STDLIB_INITIALIZED, KEYWORDS, PROPERTIES, ROOT_METADATA_PROCESSORS, RULE_METADATA_PROCESSORS, 11 | }; 12 | use std::collections::HashMap; 13 | 14 | struct RealMetadataRegisterer; 15 | 16 | impl MetadataRegisterer for RealMetadataRegisterer { 17 | fn register_rule_metadata(&self, metadata_processor: M) 18 | where 19 | M: RuleMetadataProcessor, 20 | M: Sized, 21 | M: 'static, 22 | { 23 | RULE_METADATA_PROCESSORS.lock().unwrap().insert( 24 | metadata_processor.name().to_string(), 25 | Box::new(metadata_processor), 26 | ); 27 | } 28 | 29 | fn register_root_metadata(&self, metadata_processor: M) 30 | where 31 | M: RootMetadataProcessor, 32 | M: Sized, 33 | M: 'static, 34 | { 35 | ROOT_METADATA_PROCESSORS.lock().unwrap().insert( 36 | metadata_processor.name().to_string(), 37 | Box::new(metadata_processor), 38 | ); 39 | } 40 | } 41 | 42 | pub fn parse_rustyle(stream: TokenStream) -> Option { 43 | let mut is_stdlib_initialized = IS_STDLIB_INITIALIZED.lock().unwrap(); 44 | 45 | if !*is_stdlib_initialized { 46 | let mut registerer = Registerer { 47 | properties: HashMap::new(), 48 | keywords: HashMap::new(), 49 | }; 50 | register_all_properties(&mut registerer); 51 | let mut global_properties = PROPERTIES.lock().unwrap(); 52 | for (key, value) in registerer.properties.into_iter() { 53 | global_properties.insert(key, value); 54 | } 55 | let mut global_keywords = KEYWORDS.lock().unwrap(); 56 | for (key, value) in registerer.keywords.into_iter() { 57 | global_keywords.insert(key, value); 58 | } 59 | register_all_metadatas(&RealMetadataRegisterer); 60 | *is_stdlib_initialized = true; 61 | } 62 | 63 | let stream = &mut stream.into_iter().peekable(); 64 | 65 | parse_ruleset(stream, None) 66 | } 67 | -------------------------------------------------------------------------------- /parse/src/ruleset.rs: -------------------------------------------------------------------------------- 1 | use crate::{parse_declaration, parse_metadata, parse_selector_group}; 2 | use node::{ 3 | DeclarationNode, MetadataNode, MetadataType, RulesetNode, RulesetType, Selector, SelectorGroup, 4 | }; 5 | use proc_macro::{Delimiter, TokenTree}; 6 | use std::iter::Peekable; 7 | 8 | pub fn parse_ruleset( 9 | tokens: &mut Peekable, 10 | selector_group: Option<&SelectorGroup>, 11 | ) -> Option 12 | where 13 | I: Iterator, 14 | { 15 | let mut declarations = Vec::::new(); 16 | let mut nested_rulesets = Vec::::new(); 17 | let mut root_metadatas = Vec::::new(); 18 | let mut rule_metadatas = Vec::::new(); 19 | let mut first = None; 20 | let mut last = None; 21 | 22 | let mut parse_declaration = |rule_metadatas: &mut Vec, 23 | declarations: &mut Vec, 24 | tokens: &mut Peekable| { 25 | let parsed = parse_declaration(rule_metadatas.to_vec(), tokens); 26 | 27 | if let Some(node) = parsed { 28 | if first.is_none() { 29 | first = Some(node.range); 30 | } 31 | last = Some(node.range); 32 | 33 | declarations.push(node); 34 | } 35 | }; 36 | 37 | loop { 38 | let token = tokens.peek().cloned(); 39 | match token { 40 | Some(TokenTree::Punct(ref token)) if token.as_char() == '#' => { 41 | let sharp = tokens.next().expect("Guaranteed by match"); 42 | 43 | let parsed = parse_metadata(sharp, tokens); 44 | 45 | match parsed { 46 | Some(node @ MetadataNode { 47 | metadata_type: MetadataType::Root, 48 | .. 49 | }) => { 50 | if !rule_metadatas.is_empty() || !declarations.is_empty() { 51 | node.range.warning("Put root metadata on the first of ruleset").emit(); 52 | } 53 | if selector_group.is_some() { 54 | node.range.error("Put root metadata on the root of ruleset").emit(); 55 | continue; 56 | } 57 | root_metadatas.push(node); 58 | }, 59 | Some(node @ MetadataNode { 60 | metadata_type: MetadataType::Rule, 61 | .. 62 | }) => { 63 | rule_metadatas.push(node); 64 | } 65 | _ => { 66 | // todo: unwrap_or(parse_selector()) 67 | panic!("Not Implemented") 68 | } 69 | } 70 | continue; 71 | } 72 | Some(TokenTree::Punct(ref punct)) 73 | // class selector 74 | if punct.as_char() == '.' 75 | // itself selector 76 | || punct.as_char() == '&' 77 | // universal selector 78 | || punct.as_char() == '*' 79 | // state selector 80 | || punct.as_char() == ':' 81 | // adjacent sibling selector 82 | || punct.as_char() == '+' 83 | // general sibling selector 84 | || punct.as_char() == '~' 85 | // child selector 86 | || punct.as_char() == '>' => 87 | { 88 | if let Some((parsed_selector_group, stream)) = parse_selector_group(vec!(), tokens) { 89 | let mut joined = Vec::::new(); 90 | if let Some(selector_group) = selector_group.cloned() { 91 | for selector in selector_group { 92 | joined.push(selector); 93 | } 94 | } 95 | for selector in parsed_selector_group { 96 | joined.push(selector); 97 | } 98 | 99 | if let Some(ruleset) = parse_ruleset(&mut stream.into_iter().peekable(), Some(&joined)) { 100 | nested_rulesets.push(ruleset); 101 | } else { 102 | break; 103 | } 104 | } else { 105 | break; 106 | } 107 | } 108 | Some(TokenTree::Group(ref token)) if token.delimiter() == Delimiter::Bracket => { 109 | // todo: parse_selector() 110 | break; 111 | } 112 | Some(TokenTree::Ident(_)) => { 113 | parse_declaration(&mut rule_metadatas, &mut declarations, tokens); 114 | } 115 | Some(TokenTree::Punct(ref token)) if token.as_char() == '-' => { 116 | parse_declaration(&mut rule_metadatas,&mut declarations, tokens); 117 | } 118 | Some(TokenTree::Punct(ref token)) if token.as_char() == ';' => { 119 | parse_declaration(&mut rule_metadatas, &mut declarations,tokens); 120 | } 121 | None => { 122 | break; 123 | } 124 | Some(token) => { 125 | token.span().error(format!("Unacceptable token {:?}", token.to_string())).emit(); 126 | return None; 127 | } 128 | } 129 | } 130 | 131 | if declarations.is_empty() { 132 | None 133 | } else { 134 | Some(RulesetNode { 135 | range: first.map(|first| first.join(last.unwrap_or(first)).expect("In the same file")), 136 | declarations: declarations, 137 | metadatas: root_metadatas, 138 | nested_rulesets: nested_rulesets, 139 | ruleset_type: selector_group.map_or(RulesetType::Root, |selector_group| { 140 | RulesetType::Selector(selector_group.to_vec()) 141 | }), 142 | }) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /parse/src/selector.rs: -------------------------------------------------------------------------------- 1 | use node::{Selector, SelectorGroup, SelectorPart, SelectorPartType}; 2 | use proc_macro::{Delimiter, Span, TokenStream, TokenTree}; 3 | 4 | use std::collections::HashMap; 5 | use std::iter::Peekable; 6 | pub fn parse_selector_group( 7 | read_tokens: Vec, 8 | tokens: &mut Peekable, 9 | ) -> Option<(SelectorGroup, TokenStream)> 10 | where 11 | I: Iterator, 12 | { 13 | let mut tokens = read_tokens.into_iter().chain(tokens).peekable(); 14 | 15 | let mut selectors = Vec::::new(); 16 | 17 | while let Some(current) = tokens.peek().cloned() { 18 | match current { 19 | TokenTree::Group(ref group) if group.delimiter() == Delimiter::Brace => { 20 | return Some((selectors, group.stream())); 21 | } 22 | TokenTree::Punct(ref punct) if punct.as_char() == ',' => { 23 | tokens.next(); 24 | } 25 | _ => { 26 | if let Some(selector) = parse_selector(&mut tokens) { 27 | selectors.push(selector); 28 | } 29 | } 30 | }; 31 | } 32 | 33 | None 34 | } 35 | 36 | fn parse_selector(tokens: &mut Peekable) -> Option 37 | where 38 | I: Iterator, 39 | { 40 | let mut selector_parts = Vec::::new(); 41 | let mut last_part_span: Option = None; 42 | let mut ignore_token = false; 43 | 44 | let construct_selector = |selector_parts: Vec| { 45 | let filters = { 46 | let mut map = HashMap::<&str, Box bool>>::with_capacity(2); 47 | map.insert( 48 | "pseudo element", 49 | Box::new(|part| match part.0 { 50 | SelectorPartType::PseudoElement { .. } => true, 51 | _ => false, 52 | }), 53 | ); 54 | map.insert( 55 | "element", 56 | Box::new(|part| match part.0 { 57 | SelectorPartType::Element { .. } => true, 58 | _ => false, 59 | }), 60 | ); 61 | map 62 | }; 63 | if !selector_parts.is_empty() { 64 | let mut duplicate_detected = false; 65 | for (name, filter) in filters { 66 | let filtered = selector_parts 67 | .iter() 68 | .filter(filter) 69 | .collect::>(); 70 | 71 | if filtered.len() >= 2 { 72 | for part in filtered { 73 | if let Some(span) = part.1 { 74 | span.error(format!("Use only one {}", name)).emit(); 75 | duplicate_detected = true; 76 | } 77 | } 78 | } 79 | } 80 | 81 | if duplicate_detected { 82 | None 83 | } else { 84 | Some(Selector { 85 | parts: selector_parts 86 | .into_iter() 87 | .rev() 88 | .skip_while(|part| part.0 == SelectorPartType::Spacing) 89 | .map(|part| part.clone()) 90 | .collect::>() 91 | .into_iter() 92 | .rev() 93 | .collect::>(), 94 | }) 95 | } 96 | } else { 97 | None 98 | } 99 | }; 100 | 101 | while let Some(current) = tokens.peek().cloned() { 102 | if ignore_token { 103 | match current { 104 | TokenTree::Group(ref group) if group.delimiter() == Delimiter::Brace => { 105 | last_part_span 106 | .unwrap_or(group.span()) 107 | .warning("Parse failed because of before error(s)") 108 | .emit(); 109 | return None; 110 | } 111 | TokenTree::Punct(ref punct) if punct.as_char() == ',' => { 112 | if let Some(last_part_span) = last_part_span { 113 | last_part_span.error("Not parsable selectors").emit(); 114 | } 115 | return None; 116 | } 117 | _ => { 118 | last_part_span = last_part_span 119 | .unwrap_or(current.span()) 120 | .join(current.span()); 121 | } 122 | } 123 | 124 | tokens.next(); 125 | 126 | continue; 127 | } 128 | match current { 129 | TokenTree::Group(ref token) if token.delimiter() == Delimiter::Brace => { 130 | return construct_selector(selector_parts); 131 | } 132 | TokenTree::Punct(ref token) if token.as_char() == ',' => { 133 | return construct_selector(selector_parts); 134 | } 135 | _ => { 136 | if let Some(result) = parse_selector_part(¤t, tokens) { 137 | if let (Some(last_part_span), Some(span)) = (last_part_span, result.1) { 138 | if last_part_span.end() != span.start() { 139 | selector_parts.push((SelectorPartType::Spacing, None)); 140 | } 141 | } 142 | last_part_span = result.1.or(last_part_span); 143 | 144 | selector_parts.push(result); 145 | } else { 146 | current.span().error("Not parsable selector").emit(); 147 | ignore_token = true; 148 | last_part_span = Some(current.span()); 149 | } 150 | } 151 | }; 152 | } 153 | 154 | None 155 | } 156 | 157 | fn parse_selector_part(current: &TokenTree, tokens: &mut Peekable) -> Option 158 | where 159 | I: Iterator, 160 | { 161 | match current { 162 | TokenTree::Punct(ref punct) if punct.as_char() == '&' => { 163 | tokens.next(); 164 | Some((SelectorPartType::Itself, Some(current.span()))) 165 | } 166 | TokenTree::Punct(ref punct) if punct.as_char() == '>' => { 167 | tokens.next(); 168 | parse_selector(tokens).map(|selector| { 169 | let span = selector.span(); 170 | ( 171 | SelectorPartType::Child { selector }, 172 | Some( 173 | current 174 | .span() 175 | .join(span.expect("Must have")) 176 | .expect("In the same file"), 177 | ), 178 | ) 179 | }) 180 | } 181 | TokenTree::Punct(ref punct) if punct.as_char() == '+' => { 182 | tokens.next(); 183 | parse_selector(tokens).map(|selector| { 184 | let span = selector.span(); 185 | ( 186 | SelectorPartType::NextSibling { selector }, 187 | Some( 188 | current 189 | .span() 190 | .join(span.expect("Must have")) 191 | .expect("In the same file"), 192 | ), 193 | ) 194 | }) 195 | } 196 | TokenTree::Punct(ref punct) if punct.as_char() == '~' => { 197 | tokens.next(); 198 | parse_selector(tokens).map(|selector| { 199 | let span = selector.span(); 200 | ( 201 | SelectorPartType::SubsequentSibling { selector }, 202 | Some( 203 | current 204 | .span() 205 | .join(span.expect("Must have")) 206 | .expect("In the same file"), 207 | ), 208 | ) 209 | }) 210 | } 211 | TokenTree::Punct(ref punct) if punct.as_char() == '.' => { 212 | tokens.next(); 213 | let result = parse_identifier(Some(current.span()), tokens); 214 | if let Some((ident, span)) = result { 215 | let span = current.span().join(span).expect("In the same file"); 216 | Some((SelectorPartType::Class { name: ident }, Some(span))) 217 | } else { 218 | current 219 | .span() 220 | .error("Expected identifier but no identifier received") 221 | .emit(); 222 | None 223 | } 224 | } 225 | TokenTree::Punct(ref punct) if punct.as_char() == ':' => { 226 | tokens.next(); 227 | let is_pseudo_element = if let Some(TokenTree::Punct(ref punct)) = tokens.peek() { 228 | if punct.as_char() == ':' { 229 | tokens.next(); 230 | true 231 | } else { 232 | false 233 | } 234 | } else { 235 | false 236 | }; 237 | 238 | let result = parse_identifier(Some(current.span()), tokens); 239 | if let Some((ident, span)) = result { 240 | let span = current.span().join(span).expect("In the same file"); 241 | // ? :first-line, :first-letter, :before, and :after is pseudo element but looks like pseudo class 242 | if is_pseudo_element 243 | || vec!["first-line", "first-letter", "before", "after"] 244 | .contains(&ident.as_str()) 245 | { 246 | Some((SelectorPartType::PseudoElement { name: ident }, Some(span))) 247 | } else { 248 | // todo: parse parameter 249 | Some(( 250 | SelectorPartType::PseudoClass { 251 | name: ident, 252 | parameter: None, 253 | }, 254 | Some(span), 255 | )) 256 | } 257 | } else { 258 | current 259 | .span() 260 | .error("Expected identifier but no identifier received") 261 | .emit(); 262 | None 263 | } 264 | } 265 | TokenTree::Punct(ref punct) if punct.as_char() == '#' => { 266 | tokens.next(); 267 | let result = parse_identifier(Some(current.span()), tokens); 268 | if let Some((ident, span)) = result { 269 | let span = current.span().join(span).expect("In the same file"); 270 | Some((SelectorPartType::Id { name: ident }, Some(span))) 271 | } else { 272 | current 273 | .span() 274 | .error("Expected identifier but no identifier received") 275 | .emit(); 276 | None 277 | } 278 | } 279 | 280 | TokenTree::Punct(ref punct) if punct.as_char() == '*' => { 281 | tokens.next(); 282 | Some(( 283 | SelectorPartType::Universal { namespace: None }, 284 | Some(current.span()), 285 | )) 286 | } 287 | _ => { 288 | let result = parse_identifier(None, tokens); 289 | if let Some((ident, span)) = result { 290 | let span = current.span().join(span).expect("In the same file"); 291 | // todo: css namespace support (e.g. `svg|a`, `|a`, `*|a`) 292 | // ? check required: should we filter identifier by html-element-set? 293 | Some(( 294 | SelectorPartType::Element { 295 | namespace: None, 296 | name: ident, 297 | }, 298 | Some(span), 299 | )) 300 | } else { 301 | None 302 | } 303 | } 304 | } 305 | //? S = (a|p|n)* 306 | //? a = '[' T ident (('^=' | '$=' | '*=' | '=' | '~=' | '|=') (ident | String))? ']' 307 | //? p = ':'{1,2} ident ('('expr')')? 308 | //? n = ':not(' (t|u|h|c|a|p) ')' 309 | } 310 | 311 | fn parse_identifier(span: Option, tokens: &mut Peekable) -> Option<(String, Span)> 312 | where 313 | I: Iterator, 314 | { 315 | let mut result = String::new(); 316 | let mut span = span; 317 | 318 | while let Some(token) = tokens.peek().cloned() { 319 | if let Some(span) = span { 320 | if span.end() != token.span().start() { 321 | break; 322 | } 323 | } 324 | match token { 325 | TokenTree::Group(_) => { 326 | break; 327 | } 328 | TokenTree::Punct(ref punct) 329 | if punct.as_char() == '.' || punct.as_char() == '#' || punct.as_char() == ':' => 330 | { 331 | break; 332 | } 333 | _ => { 334 | result.push_str(&token.to_string()); 335 | span = span.map_or(Some(token.span()), |span| span.join(token.span())); 336 | tokens.next(); 337 | } 338 | } 339 | } 340 | 341 | if result.is_empty() { 342 | None 343 | } else { 344 | span.map(|span| (result, span)) 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /parse/src/unit.rs: -------------------------------------------------------------------------------- 1 | use csstype::{CssUnit, CssUnitGroup, Cssifiable}; 2 | use proc_macro::{Span, TokenTree}; 3 | use runtime::global::{UnitTree, UNIT_TREE}; 4 | use std::iter::Peekable; 5 | 6 | pub fn parse_unit(tokens: &mut Peekable) -> (Option>, Option) 7 | where 8 | I: Iterator, 9 | { 10 | let (origin, number, unit, span) = if let Some(token) = tokens.next() { 11 | let stringified = token.to_string(); 12 | let span = token.span(); 13 | 14 | // Case 1. If it is number it does not have unit, or else not a number. 15 | if stringified.len() == 1 { 16 | if let Some(number) = stringified.parse::().ok() { 17 | (stringified, number, None, span) 18 | } else { 19 | return (None, Some(span)); 20 | } 21 | } else { 22 | let mut chars = stringified.chars().rev().peekable(); 23 | let mut ch = *chars.peek().expect("Token does not empty"); 24 | 25 | // Case 2. Ends with number means that not ends with unit. 26 | if ('0'..='9').contains(&ch) { 27 | if let Some(number) = stringified.parse::().ok() { 28 | (stringified, number, None, span) 29 | } else { 30 | return (None, Some(span)); 31 | } 32 | } else { 33 | let mut tree: &UnitTree = &UNIT_TREE; 34 | let mut unit: String = String::new(); 35 | 36 | while { 37 | if let Some(branch) = tree.children().get(&ch) { 38 | chars.next(); 39 | unit.insert(0, ch); 40 | tree = branch; 41 | 42 | if let Some(c) = chars.peek().cloned() { 43 | ch = c; 44 | true 45 | } else { 46 | false 47 | } 48 | } else { 49 | false 50 | } 51 | } {} 52 | 53 | if let (Ok(number), UnitTree::Part { end: true, .. }) = ( 54 | chars 55 | .collect::>() 56 | .into_iter() 57 | .rev() 58 | .collect::() 59 | .parse::(), 60 | tree, 61 | ) { 62 | (stringified, number, Some(unit), span) 63 | } else { 64 | return (None, Some(span)); 65 | } 66 | } 67 | } 68 | } else { 69 | return (None, None); 70 | }; 71 | 72 | let (origin, unit) = match tokens.peek().cloned() { 73 | Some(TokenTree::Punct(ref punct)) if punct.as_char() == '%' => { 74 | tokens.next(); 75 | ( 76 | format!("{}{}", origin, punct.to_string()), 77 | Some("%".to_string()), 78 | ) 79 | } 80 | _ => (origin, unit), 81 | }; 82 | 83 | if let Some(unit) = unit { 84 | let group = match unit.to_lowercase().as_str() { 85 | "%" => CssUnitGroup::Percentage, 86 | "em" | "ex" | "cap" | "ch" | "ic" | "rem" | "lh" | "rlh" => { 87 | CssUnitGroup::FontRelativeLength 88 | } 89 | "vw" | "vh" | "vi" | "vb" | "vmin" | "vmax" => CssUnitGroup::ViewportRelativeLength, 90 | "cm" | "mm" | "q" | "in" | "pt" | "pc" | "px" => CssUnitGroup::AbsoluteLength, 91 | "deg" | "grad" | "rad" | "turn" => CssUnitGroup::Angle, 92 | "s" | "ms" => CssUnitGroup::Time, 93 | "hz" | "khz" => CssUnitGroup::Frequency, 94 | "dpi" | "dpcm" | "dppx" => CssUnitGroup::Resolution, 95 | _ => { 96 | span.error(format!("Unit {} is not implemented", unit)); 97 | return (None, Some(span)); 98 | } 99 | }; 100 | ( 101 | Some(Box::new(CssUnit { 102 | group, 103 | origin, 104 | number, 105 | unit, 106 | })), 107 | Some(span), 108 | ) 109 | } else { 110 | ( 111 | Some(Box::new(CssUnit { 112 | group: if number.round() == number { 113 | CssUnitGroup::Integer 114 | } else { 115 | CssUnitGroup::Number 116 | }, 117 | origin, 118 | number, 119 | unit: "".to_string(), 120 | })), 121 | Some(span), 122 | ) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /parse/src/variable.rs: -------------------------------------------------------------------------------- 1 | use crate::parse_expression; 2 | use csstype::Cssifiable; 3 | use proc_macro::{Delimiter, Span, TokenTree}; 4 | 5 | pub fn parse_variable( 6 | dollar: TokenTree, 7 | tokens: &mut I, 8 | ) -> (Option>, Option) 9 | where 10 | I: Iterator, 11 | { 12 | match tokens.next() { 13 | Some(TokenTree::Group(ref token)) => { 14 | if token.delimiter() != Delimiter::Brace { 15 | let span = dollar.span().join(token.span()).expect("In the same file"); 16 | span.error(format!( 17 | "Expected curly brace but {} received", 18 | if token.delimiter() == Delimiter::Bracket { 19 | "square bracket" 20 | } else { 21 | "Round parenthesis" 22 | } 23 | )) 24 | .emit(); 25 | return (None, Some(span)); 26 | } 27 | let mut tokens = token.stream().into_iter().peekable(); 28 | parse_expression(&mut tokens) 29 | } 30 | Some(token) => { 31 | let span = dollar.span().join(token.span()).expect("In the same file"); 32 | span.error(format!("Unexpected token {} received", token)) 33 | .emit(); 34 | (None, Some(span)) 35 | } 36 | None => { 37 | dollar 38 | .span() 39 | .error("Token expected but no token received") 40 | .emit(); 41 | (None, Some(dollar.span())) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /property/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "property" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | csstype = { path = "../csstype" } -------------------------------------------------------------------------------- /property/src/condition.rs: -------------------------------------------------------------------------------- 1 | use csstype::CssUnitGroup; 2 | 3 | #[derive(Clone, PartialEq)] 4 | pub enum ConditionType { 5 | Keyword, 6 | Color, 7 | Unit(Vec), 8 | ValueAllocatedUnit(CssUnitGroup, f32), 9 | } 10 | 11 | impl ConditionType { 12 | fn into_condition(self) -> Condition { 13 | Condition { 14 | types_variant: vec![self], 15 | } 16 | } 17 | } 18 | 19 | pub struct Condition { 20 | pub types_variant: Vec, 21 | } 22 | 23 | impl Condition { 24 | pub fn keyword() -> Condition { 25 | ConditionType::Keyword.into_condition() 26 | } 27 | pub fn color() -> Condition { 28 | ConditionType::Color.into_condition() 29 | } 30 | pub fn length_unit() -> Condition { 31 | ConditionType::Unit(vec![ 32 | CssUnitGroup::Integer, 33 | CssUnitGroup::Number, 34 | CssUnitGroup::AbsoluteLength, 35 | CssUnitGroup::FontRelativeLength, 36 | CssUnitGroup::ViewportRelativeLength, 37 | ]) 38 | .into_condition() 39 | } 40 | pub fn percentage_unit() -> Condition { 41 | ConditionType::Unit(vec![CssUnitGroup::Percentage]).into_condition() 42 | } 43 | pub fn integer_exact(number: i32) -> Condition { 44 | ConditionType::ValueAllocatedUnit(CssUnitGroup::Integer, number as f32).into_condition() 45 | } 46 | pub fn number_exact(number: f32) -> Condition { 47 | ConditionType::ValueAllocatedUnit(CssUnitGroup::Number, number).into_condition() 48 | } 49 | pub fn or(self, cond: Condition) -> Self { 50 | Condition { 51 | types_variant: [self.types_variant, cond.types_variant].concat(), 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /property/src/implementation/background/background_attachment.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["scroll", "fixed"]) 9 | } 10 | fn name(&self) -> &str { 11 | "background-attachment" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/background/background_color.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["transparent", "currentcolor"]) 9 | } 10 | fn name(&self) -> &str { 11 | "background-color" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword().or(Condition::color())] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/background/background_repeat.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["repeat", "repeat-x", "repeat-y", "no-repeat"]) 9 | } 10 | fn name(&self) -> &str { 11 | "background-repeat" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/background/color.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn name(&self) -> &str { 8 | "color" 9 | } 10 | 11 | fn condition(&self) -> Vec { 12 | vec![Condition::keyword().or(Condition::color())] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /property/src/implementation/background/mod.rs: -------------------------------------------------------------------------------- 1 | mod background_color; 2 | mod background_repeat; 3 | mod color; 4 | mod background_attachment; 5 | 6 | use crate::Registerer; 7 | 8 | pub fn register_properties(registerer: &mut Registerer) { 9 | registerer.register(background_color::Instance); 10 | registerer.register(background_repeat::Instance); 11 | registerer.register(color::Instance); 12 | registerer.register(background_attachment::Instance); 13 | } 14 | -------------------------------------------------------------------------------- /property/src/implementation/box/border/border_bottom_width.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["thin", "medium", "thick"]) 9 | } 10 | fn name(&self) -> &str { 11 | "border-bottom-width" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit())] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /property/src/implementation/box/border/border_left_width.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["thin", "medium", "thick"]) 9 | } 10 | fn name(&self) -> &str { 11 | "border-left-width" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit())] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /property/src/implementation/box/border/border_right_width.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["thin", "medium", "thick"]) 9 | } 10 | fn name(&self) -> &str { 11 | "border-right-width" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit())] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /property/src/implementation/box/border/border_style.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["none","dotted","dashed","solid","double","groove","ridge","inset","outset"]) 9 | } 10 | fn name(&self) -> &str { 11 | "border-style" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/box/border/border_top_width.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["thin", "medium", "thick"]) 9 | } 10 | fn name(&self) -> &str { 11 | "border-top-width" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit())] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /property/src/implementation/box/border/mod.rs: -------------------------------------------------------------------------------- 1 | mod border_bottom_width; 2 | mod border_left_width; 3 | mod border_right_width; 4 | mod border_top_width; 5 | mod border_style; 6 | 7 | use crate::Registerer; 8 | 9 | pub fn register_properties(registerer: &mut Registerer) { 10 | registerer.register(border_bottom_width::Instance); 11 | registerer.register(border_left_width::Instance); 12 | registerer.register(border_right_width::Instance); 13 | registerer.register(border_top_width::Instance); 14 | registerer.register(border_style::Instance); 15 | } 16 | -------------------------------------------------------------------------------- /property/src/implementation/box/clear.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["none", "left", "right", "both"]) 9 | } 10 | fn name(&self) -> &str { 11 | "clear" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/box/display.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "inline", 10 | "block", 11 | "list-item", 12 | "inline-block", 13 | "table", 14 | "inline-table", 15 | "table-row-group", 16 | "table-header-group", 17 | "table-footer-group", 18 | "table-row", 19 | "table-column-group", 20 | "table-column", 21 | "table-cell", 22 | "table-caption", 23 | "none", 24 | ]) 25 | } 26 | fn name(&self) -> &str { 27 | "display" 28 | } 29 | 30 | fn condition(&self) -> Vec { 31 | vec![Condition::keyword()] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /property/src/implementation/box/float.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["left", "right", "none"]) 9 | } 10 | fn name(&self) -> &str { 11 | "float" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/box/height.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["auto"]) 9 | } 10 | fn name(&self) -> &str { 11 | "height" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit()) 17 | .or(Condition::percentage_unit())] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /property/src/implementation/box/list_style_position.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["inside", "outside"]) 9 | } 10 | fn name(&self) -> &str { 11 | "list-style-position" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/box/list_style_type.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "disc", 10 | "circle", 11 | "square", 12 | "decimal", 13 | "decimal-leading-zero", 14 | "lower-roman", 15 | "upper-roman", 16 | "lower-greek", 17 | "lower-latin", 18 | "upper-latin", 19 | "armenian", 20 | "georgian", 21 | "lower-alpha", 22 | "upper-alpha", 23 | "none", 24 | ]) 25 | } 26 | fn name(&self) -> &str { 27 | "list-style-type" 28 | } 29 | 30 | fn condition(&self) -> Vec { 31 | vec![Condition::keyword()] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /property/src/implementation/box/margin/margin_bottom.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["auto"]) 9 | } 10 | fn name(&self) -> &str { 11 | "margin-bottom" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit()) 17 | .or(Condition::percentage_unit())] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /property/src/implementation/box/margin/margin_left.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["auto"]) 9 | } 10 | fn name(&self) -> &str { 11 | "margin-left" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit()) 17 | .or(Condition::percentage_unit())] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /property/src/implementation/box/margin/margin_right.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["auto"]) 9 | } 10 | fn name(&self) -> &str { 11 | "margin-right" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit()) 17 | .or(Condition::percentage_unit())] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /property/src/implementation/box/margin/margin_top.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["auto"]) 9 | } 10 | fn name(&self) -> &str { 11 | "margin-top" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit()) 17 | .or(Condition::percentage_unit())] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /property/src/implementation/box/margin/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod margin_bottom; 3 | mod margin_left; 4 | mod margin_right; 5 | mod margin_top; 6 | 7 | use crate::Registerer; 8 | 9 | pub fn register_properties(registerer: &mut Registerer) { 10 | registerer.register(margin_top::Instance); 11 | registerer.register(margin_bottom::Instance); 12 | registerer.register(margin_left::Instance); 13 | registerer.register(margin_right::Instance); 14 | } 15 | -------------------------------------------------------------------------------- /property/src/implementation/box/mod.rs: -------------------------------------------------------------------------------- 1 | mod border; 2 | mod margin; 3 | mod padding; 4 | mod width; 5 | mod float; 6 | mod height; 7 | mod clear; 8 | mod display; 9 | mod white_space; 10 | mod list_style_type; 11 | mod list_style_position; 12 | 13 | use crate::Registerer; 14 | 15 | pub fn register_properties(registerer: &mut Registerer) { 16 | registerer.register(width::Instance); 17 | registerer.register(height::Instance); 18 | registerer.register(float::Instance); 19 | registerer.register(clear::Instance); 20 | registerer.register(display::Instance); 21 | registerer.register(white_space::Instance); 22 | registerer.register(list_style_type::Instance); 23 | registerer.register(list_style_position::Instance); 24 | 25 | margin::register_properties(registerer); 26 | border::register_properties(registerer); 27 | padding::register_properties(registerer); 28 | } 29 | -------------------------------------------------------------------------------- /property/src/implementation/box/padding/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod padding_bottom; 3 | mod padding_left; 4 | mod padding_right; 5 | mod padding_top; 6 | 7 | use crate::Registerer; 8 | 9 | pub fn register_properties(registerer: &mut Registerer) { 10 | registerer.register(padding_bottom::Instance); 11 | registerer.register(padding_left::Instance); 12 | registerer.register(padding_right::Instance); 13 | registerer.register(padding_top::Instance); 14 | } 15 | -------------------------------------------------------------------------------- /property/src/implementation/box/padding/padding_bottom.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn name(&self) -> &str { 8 | "padding-bottom" 9 | } 10 | 11 | fn condition(&self) -> Vec { 12 | vec![Condition::length_unit() 13 | .or(Condition::percentage_unit())] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /property/src/implementation/box/padding/padding_left.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn name(&self) -> &str { 8 | "padding-top" 9 | } 10 | 11 | fn condition(&self) -> Vec { 12 | vec![Condition::length_unit() 13 | .or(Condition::percentage_unit())] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /property/src/implementation/box/padding/padding_right.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn name(&self) -> &str { 8 | "padding-right" 9 | } 10 | 11 | fn condition(&self) -> Vec { 12 | vec![Condition::length_unit() 13 | .or(Condition::percentage_unit())] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /property/src/implementation/box/padding/padding_top.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn name(&self) -> &str { 8 | "padding-top" 9 | } 10 | 11 | fn condition(&self) -> Vec { 12 | vec![Condition::length_unit() 13 | .or(Condition::percentage_unit())] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /property/src/implementation/box/white_space.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "normal", 10 | "pre", 11 | "nowrap", 12 | ]) 13 | } 14 | fn name(&self) -> &str { 15 | "white-space" 16 | } 17 | 18 | fn condition(&self) -> Vec { 19 | vec![Condition::keyword()] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /property/src/implementation/box/width.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["auto"]) 9 | } 10 | fn name(&self) -> &str { 11 | "width" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::length_unit()) 17 | .or(Condition::percentage_unit())] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /property/src/implementation/direction.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["ltr", "rtl"]) 9 | } 10 | fn name(&self) -> &str { 11 | "direction" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/empty_cells.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["show", "hide"]) 9 | } 10 | fn name(&self) -> &str { 11 | "empty-cells" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_kerning.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["auto", "normal", "none"]) 9 | } 10 | fn name(&self) -> &str { 11 | "font-kerning" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_size.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | [ 9 | // absolute size 10 | Keyword::simple_vec(vec![ 11 | "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", 12 | ]), 13 | // relative size 14 | Keyword::simple_vec(vec!["larger", "smaller"]), 15 | ] 16 | .concat() 17 | } 18 | fn name(&self) -> &str { 19 | "font-size" 20 | } 21 | 22 | fn condition(&self) -> Vec { 23 | vec![Condition::keyword() 24 | .or(Condition::length_unit()) 25 | .or(Condition::percentage_unit())] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_stretch.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "normal", 10 | "ultra-condensed", 11 | "extra-condensed", 12 | "condensed", 13 | "semi-condensed", 14 | "semi-expanded", 15 | "expanded", 16 | "extra-expanded", 17 | "ultra-expanded", 18 | ]) 19 | } 20 | fn name(&self) -> &str { 21 | "font-stretch" 22 | } 23 | 24 | fn condition(&self) -> Vec { 25 | vec![Condition::keyword()] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_style.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["normal", "italic", "oblique"]) 9 | } 10 | fn name(&self) -> &str { 11 | "font-style" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_variant.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["normal", "small-caps"]) 9 | } 10 | fn name(&self) -> &str { 11 | "font-variant" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_variant_caps.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "normal", 10 | "small-caps", 11 | "all-small-caps", 12 | "petite-caps", 13 | "all-petite-caps", 14 | "unicase", 15 | "titling-caps", 16 | ]) 17 | } 18 | fn name(&self) -> &str { 19 | "font-variant-caps" 20 | } 21 | 22 | fn condition(&self) -> Vec { 23 | vec![Condition::keyword()] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_variant_position.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["normal", "sub", "super"]) 9 | } 10 | fn name(&self) -> &str { 11 | "font-variant-position" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword()] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/font/font_weight.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["normal", "bold", "bolder", "lighter"]) 9 | } 10 | fn name(&self) -> &str { 11 | "font-weight" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword() 16 | .or(Condition::integer_exact(100)) 17 | .or(Condition::integer_exact(200)) 18 | .or(Condition::integer_exact(300)) 19 | .or(Condition::integer_exact(400)) 20 | .or(Condition::integer_exact(500)) 21 | .or(Condition::integer_exact(600)) 22 | .or(Condition::integer_exact(700)) 23 | .or(Condition::integer_exact(800)) 24 | .or(Condition::integer_exact(900))] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /property/src/implementation/font/mod.rs: -------------------------------------------------------------------------------- 1 | mod font_kerning; 2 | mod font_size; 3 | mod font_stretch; 4 | mod font_style; 5 | mod font_variant; 6 | mod font_variant_caps; 7 | mod font_variant_position; 8 | mod font_weight; 9 | 10 | use crate::Registerer; 11 | 12 | pub fn register_properties(registerer: &mut Registerer) { 13 | registerer.register(font_kerning::Instance); 14 | registerer.register(font_stretch::Instance); 15 | registerer.register(font_style::Instance); 16 | registerer.register(font_variant_caps::Instance); 17 | registerer.register(font_variant_position::Instance); 18 | registerer.register(font_size::Instance); 19 | registerer.register(font_variant::Instance); 20 | registerer.register(font_weight::Instance); 21 | } 22 | -------------------------------------------------------------------------------- /property/src/implementation/min_height.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn name(&self) -> &str { 8 | "min-height" 9 | } 10 | 11 | fn condition(&self) -> Vec { 12 | vec![Condition::keyword() 13 | .or(Condition::length_unit()) 14 | .or(Condition::percentage_unit())] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /property/src/implementation/mod.rs: -------------------------------------------------------------------------------- 1 | mod background; 2 | mod r#box; 3 | mod direction; 4 | mod empty_cells; 5 | mod font; 6 | mod min_height; 7 | mod text; 8 | mod user_select; 9 | 10 | use crate::Registerer; 11 | 12 | pub fn register_all_properties(registerer: &mut Registerer) { 13 | registerer.register(user_select::Instance); 14 | registerer.register(direction::Instance); 15 | registerer.register(empty_cells::Instance); 16 | registerer.register(min_height::Instance); 17 | 18 | background::register_properties(registerer); 19 | font::register_properties(registerer); 20 | text::register_properties(registerer); 21 | r#box::register_properties(registerer); 22 | } 23 | -------------------------------------------------------------------------------- /property/src/implementation/text/letter_spacing.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["normal"]) 9 | } 10 | fn name(&self) -> &str { 11 | "letter-spacing" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword().or(Condition::length_unit())] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/text/line_height.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords() -> Vec { 8 | Keyword::simple_vec(vec!["normal"]) 9 | } 10 | fn name(&self) -> &str { 11 | "line-height" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::length_unit().or(Condition::percentage_unit())] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/text/mod.rs: -------------------------------------------------------------------------------- 1 | mod letter_spacing; 2 | mod text_align; 3 | mod text_decoration; 4 | mod text_indent; 5 | mod text_transform; 6 | mod vertical_align; 7 | mod word_spacing; 8 | 9 | use crate::Registerer; 10 | 11 | pub fn register_properties(registerer: &mut Registerer) { 12 | registerer.register(letter_spacing::Instance); 13 | registerer.register(word_spacing::Instance); 14 | registerer.register(text_decoration::Instance); 15 | registerer.register(vertical_align::Instance); 16 | registerer.register(text_transform::Instance); 17 | registerer.register(text_align::Instance); 18 | registerer.register(text_indent::Instance); 19 | } 20 | -------------------------------------------------------------------------------- /property/src/implementation/text/text_align.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "left", 10 | "right", 11 | "center", 12 | "justify", 13 | ]) 14 | } 15 | fn name(&self) -> &str { 16 | "text-align" 17 | } 18 | 19 | fn condition(&self) -> Vec { 20 | vec![Condition::keyword()] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /property/src/implementation/text/text_decoration.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "none", 10 | "underline", 11 | "overline", 12 | "line-through", 13 | "blink", 14 | ]) 15 | } 16 | fn name(&self) -> &str { 17 | "text-decoration" 18 | } 19 | 20 | fn condition(&self) -> Vec { 21 | vec![Condition::keyword()] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /property/src/implementation/text/text_indent.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn name(&self) -> &str { 8 | "text-indent" 9 | } 10 | 11 | fn condition(&self) -> Vec { 12 | vec![Condition::length_unit().or(Condition::percentage_unit())] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /property/src/implementation/text/text_transform.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "capitalize", 10 | "uppercase", 11 | "lowercase", 12 | "none", 13 | ]) 14 | } 15 | fn name(&self) -> &str { 16 | "text-transform" 17 | } 18 | 19 | fn condition(&self) -> Vec { 20 | vec![Condition::keyword()] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /property/src/implementation/text/vertical_align.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec![ 9 | "baseline", 10 | "sub", 11 | "super", 12 | "top", 13 | "text-top", 14 | "middle", 15 | "bottom", 16 | "text-bottom", 17 | ]) 18 | } 19 | fn name(&self) -> &str { 20 | "vertical-align" 21 | } 22 | 23 | fn condition(&self) -> Vec { 24 | vec![Condition::keyword().or(Condition::percentage_unit())] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /property/src/implementation/text/word_spacing.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | Keyword::simple_vec(vec!["normal"]) 9 | } 10 | fn name(&self) -> &str { 11 | "word-spacing" 12 | } 13 | 14 | fn condition(&self) -> Vec { 15 | vec![Condition::keyword().or(Condition::length_unit())] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /property/src/implementation/user_select.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword, Property}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub struct Instance; 5 | 6 | impl Property for Instance { 7 | fn keywords(&self) -> Vec { 8 | [ 9 | Keyword::simple_vec(vec!["none", "auto", "text", "contain", "all"]), 10 | Keyword::prefixed_vec("-moz-", vec!["none", "text", "all"]), 11 | // "all" Doesn't work in Safari; use only "none" or "text", or else it will allow typing in the container 12 | Keyword::prefixed_vec("-webkit-", vec!["none", "text", "all"]), 13 | Keyword::prefixed_vec("-ms-", vec!["none", "text", "element"]), 14 | ] 15 | .concat() 16 | } 17 | fn name(&self) -> &str { 18 | "user-select" 19 | } 20 | 21 | fn condition(&self) -> Vec { 22 | vec![Condition::keyword()] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /property/src/keyword.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone)] 2 | pub struct Keyword { 3 | pub vendor_prefix: String, 4 | pub name: String, 5 | } 6 | 7 | impl Keyword { 8 | pub fn simple(name: &str) -> Keyword { 9 | Keyword { 10 | vendor_prefix: "".to_string(), 11 | name: name.to_string(), 12 | } 13 | } 14 | pub fn simple_vec(names: Vec<&str>) -> Vec { 15 | names.into_iter().map(Keyword::simple).collect() 16 | } 17 | pub fn prefixed(vendor_prefix: &str, name: &str) -> Keyword { 18 | Keyword { 19 | vendor_prefix: vendor_prefix.to_string(), 20 | name: name.to_string(), 21 | } 22 | } 23 | pub fn prefixed_vec(vendor_prefix: &str, names: Vec<&str>) -> Vec { 24 | names 25 | .into_iter() 26 | .map(|name| Keyword::prefixed(vendor_prefix, name)) 27 | .collect() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /property/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 2 | 3 | mod condition; 4 | mod keyword; 5 | mod property; 6 | 7 | pub mod implementation; 8 | pub use condition::*; 9 | pub use keyword::*; 10 | pub use property::*; 11 | -------------------------------------------------------------------------------- /property/src/property.rs: -------------------------------------------------------------------------------- 1 | use crate::{Condition, Keyword}; 2 | use csstype::Cssifiable; 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | pub type Parameter = Box; 6 | 7 | pub trait Property: Send + Sync { 8 | fn keywords(&self) -> Vec { 9 | vec![] 10 | } 11 | fn name(&self) -> &str; 12 | fn condition(&self) -> Vec; 13 | } 14 | 15 | pub struct Registerer { 16 | pub properties: HashMap>, 17 | pub keywords: HashMap>, 18 | } 19 | 20 | impl Registerer { 21 | pub fn register

(&mut self, property: P) 22 | where 23 | P: Property, 24 | P: 'static, 25 | P: Copy, 26 | { 27 | for keyword in property.keywords() { 28 | if !self.keywords.contains_key(&keyword.name) { 29 | self.keywords.insert(keyword.name.clone(), HashSet::new()); 30 | } 31 | 32 | self.keywords 33 | .get_mut(&keyword.name) 34 | .expect("Guaranteed by before insert") 35 | .insert(format!("{}{}", keyword.vendor_prefix, property.name())); 36 | } 37 | 38 | self.properties 39 | .insert(property.name().to_string(), Box::new(property)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | lazy_static = "1.3.0" 9 | metadata = { path = "../metadata" } 10 | runtime_facade = { path = "../runtime_facade" } 11 | property = { path = "../property" } -------------------------------------------------------------------------------- /runtime/src/global.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use metadata::{RootMetadataProcessor, RuleMetadataProcessor}; 3 | use property::Property; 4 | use std::collections::{HashMap, HashSet}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | pub type UnitTreeChildren = HashMap; 8 | 9 | #[derive(Debug)] 10 | pub enum UnitTree { 11 | Root { 12 | children: UnitTreeChildren, 13 | }, 14 | Part { 15 | children: UnitTreeChildren, 16 | end: bool, 17 | }, 18 | } 19 | 20 | impl UnitTree { 21 | pub fn children(&self) -> &UnitTreeChildren { 22 | match self { 23 | UnitTree::Root { children } => children, 24 | UnitTree::Part { children, .. } => children, 25 | } 26 | } 27 | } 28 | 29 | impl UnitTree { 30 | pub fn mid(children: UnitTreeChildren) -> UnitTree { 31 | UnitTree::Part { 32 | children, 33 | end: false, 34 | } 35 | } 36 | 37 | pub fn end(children: UnitTreeChildren) -> UnitTree { 38 | UnitTree::Part { 39 | children, 40 | end: true, 41 | } 42 | } 43 | } 44 | 45 | macro_rules! map { 46 | ($($k:expr => $v: expr),*) => { 47 | { 48 | #[allow(unused_mut)] 49 | let mut map = HashMap::new(); 50 | $( 51 | map.insert($k, $v); 52 | )* 53 | map 54 | } 55 | }; 56 | } 57 | 58 | lazy_static! { 59 | pub static ref CSS_ID: Arc> = Arc::new(Mutex::new(0)); 60 | pub static ref CSS_FILES_MAP: Arc>>> = 61 | Arc::new(Mutex::new(HashMap::new())); 62 | pub static ref KEYWORDS: Arc>>> = 63 | Arc::new(Mutex::new(HashMap::new())); 64 | pub static ref IS_STDLIB_INITIALIZED: Arc> = Arc::new(Mutex::new(false)); 65 | pub static ref PROPERTIES: Arc>>> = 66 | Arc::new(Mutex::new(HashMap::new())); 67 | pub static ref RULE_METADATA_PROCESSORS: Arc< 68 | Mutex>> 69 | > = Arc::new(Mutex::new(HashMap::new())); 70 | pub static ref ROOT_METADATA_PROCESSORS: Arc< 71 | Mutex>> 72 | > = Arc::new(Mutex::new(HashMap::new())); 73 | 74 | pub static ref OUTPUT: String = 75 | std::env::var("RUSTYLE_OUTPUT").unwrap_or(String::from("./rustyle")); 76 | 77 | pub static ref UNIT_TREE: UnitTree = UnitTree::Root { 78 | children: map!( 79 | 'b' => UnitTree::mid(map!( 80 | // vb: 1% of viewport’s size in the root element’s block axis 81 | 'v' => UnitTree::end(map!()) 82 | )), 83 | 'c' => UnitTree::mid(map!( 84 | // ic: average character advance of a fullwidth glyph in the element’s font, as represented by the “水” (CJK water ideograph, U+6C34) glyph 85 | 'i' => UnitTree::end(map!()), 86 | // pc: picas; 1pc = 1/6th of 1in 87 | 'p' => UnitTree::end(map!()) 88 | )), 89 | 'd' => UnitTree::mid(map!( 90 | 'a' => UnitTree::mid(map!( 91 | // rad: Radians. There are 2π radians in a full circle. 92 | 'r' => UnitTree::end(map!( 93 | // grad: Gradians, also known as "gons" or "grades". There are 400 gradians in a full circle. 94 | 'g' => UnitTree::end(map!()) 95 | )) 96 | )) 97 | )), 98 | 'g' => UnitTree::mid(map!( 99 | 'e' => UnitTree::mid(map!( 100 | // deg: Degrees. There are 360 degrees in a full circle. 101 | 'd' => UnitTree::end(map!()) 102 | )) 103 | )), 104 | 'h' => UnitTree::mid(map!( 105 | // ch: average character advance of a narrow glyph in the element’s font, as represented by the “0” (ZERO, U+0030) glyph 106 | 'c' => UnitTree::end(map!()), 107 | // lh: line height of the element 108 | 'l' => UnitTree::end(map!( 109 | // rlh: line height of the root element 110 | 'r' => UnitTree::end(map!()) 111 | )), 112 | // vh: 1% of viewport’s height 113 | 'v' => UnitTree::end(map!()) 114 | )), 115 | 'i' => UnitTree::mid(map!( 116 | 'p' => UnitTree::mid(map!( 117 | // dpi: Dots per inch. 118 | 'd' => UnitTree::end(map!()) 119 | )), 120 | // vi: 1% of viewport’s size in the root element’s inline axis 121 | 'v' => UnitTree::end(map!()) 122 | )), 123 | 'm' => UnitTree::mid(map!( 124 | // em: relative to font size of the element 125 | 'e' => UnitTree::end(map!( 126 | // rem: relative to font size of the root element 127 | 'r' => UnitTree::end(map!()) 128 | )), 129 | // cm: centimeters; 1cm = 96px/2.54 130 | 'c' => UnitTree::end(map!( 131 | 'p' => UnitTree::mid(map!( 132 | // dpcm: Dots per centimeter. 133 | 'd' => UnitTree::end(map!()) 134 | )) 135 | )), 136 | // mm: millimeters; 1mm = 1/10th of 1cm 137 | 'm' => UnitTree::end(map!()) 138 | )), 139 | 'n' => UnitTree::mid(map!( 140 | // in: inches; 1in = 2.54cm = 96px 141 | 'i' => UnitTree::end(map!()), 142 | 'r' => UnitTree::mid(map!( 143 | 'u' => UnitTree::mid(map!( 144 | // turn: Turns. There is 1 turn in a full circle. 145 | 't' => UnitTree::end(map!()) 146 | )) 147 | )) 148 | )), 149 | 'p' => UnitTree::mid(map!( 150 | 'a' => UnitTree::mid(map!( 151 | // cap: cap height (the nominal height of capital letters) of the element’s font 152 | 'c' => UnitTree::end(map!()) 153 | )) 154 | )), 155 | // Q: quarter-millimeters; 1Q = 1/40th of 1cm 156 | 'q' => UnitTree::end(map!()), 157 | // s: Seconds. 158 | 's' => UnitTree::end(map!( 159 | // ms: Milliseconds. There are 1000 milliseconds in a second. 160 | 'm' => UnitTree::end(map!()) 161 | )), 162 | 't' => UnitTree::mid(map!( 163 | // pt: points; 1pt = 1/72th of 1in 164 | 'p' => UnitTree::end(map!()) 165 | )), 166 | 'x' => UnitTree::mid(map!( 167 | // ex: x-height of the element’s font 168 | 'e' => UnitTree::end(map!()), 169 | // px: pixels; 1px = 1/96th of 1in 170 | 'p' => UnitTree::end(map!( 171 | 'p' => UnitTree::mid(map!( 172 | // dppx: Dots per px unit. 173 | 'd' => UnitTree::end(map!()) 174 | )) 175 | )) 176 | )), 177 | 'w' => UnitTree::mid(map!( 178 | // vw: 1% of viewport’s width 179 | 'v' => UnitTree::end(map!()) 180 | )), 181 | 'z' => UnitTree::mid(map!( 182 | // ? well, Hz and kHz is standard but not used by any property. 183 | // Hz: Hertz. It represents the number of occurrences per second. 184 | 'h' => UnitTree::end(map!( 185 | // kHz: KiloHertz. A kiloHertz is 1000 Hertz. 186 | 'k' => UnitTree::end(map!()) 187 | )) 188 | )) 189 | ) 190 | }; 191 | 192 | pub static ref KNOWN_PSEUDO_CLASSES: Vec<&'static str> = vec![]; 193 | pub static ref KNOWN_PSEUDO_ELEMENTS: Vec<&'static str> = vec![]; 194 | // Listed at https://stackoverflow.com/questions/5411026/list-of-css-vendor-prefixes 195 | pub static ref VENDOR_PREFIXES: Vec<&'static str> = vec![ 196 | "-ms-", // Microsoft 197 | "mso-", // Microsoft Office 198 | "-moz-", // Mozilla Foundation 199 | "-o-", // Opera Software 200 | "-xv-", // Opera Software 201 | "-atsc-", // Advanced Television Standards Committee 202 | "-wap-", // The WAP Forum 203 | "-webkit-", // Safari, Chrome (and other WebKit-based browsers) 204 | "-khtml-", // Konqueror Browser 205 | "-konq-", // Konqueror Browser 206 | "-apple-", // Webkit supports properties using the -apple- prefixes as well 207 | "prince-", // YesLogic 208 | "-ah-", // AntennaHouse 209 | "-hp-", // Hewlett Packard 210 | "-ro-", // Real Objects 211 | "-rim-", // Research In Motion 212 | "-tc-", // Tall Components 213 | ]; 214 | } 215 | -------------------------------------------------------------------------------- /runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 2 | 3 | pub mod global; 4 | pub use runtime_facade::*; 5 | -------------------------------------------------------------------------------- /runtime_facade/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime_facade" 3 | version = "0.1.0" 4 | authors = ["RanolP "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /runtime_facade/src/compile_context.rs: -------------------------------------------------------------------------------- 1 | pub struct CompileContext { 2 | pub filename: String, 3 | } 4 | -------------------------------------------------------------------------------- /runtime_facade/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 2 | 3 | mod compile_context; 4 | 5 | pub use compile_context::*; 6 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod name_mangler; 2 | -------------------------------------------------------------------------------- /src/core/name_mangler.rs: -------------------------------------------------------------------------------- 1 | use fasthash::murmur2; 2 | use lazy_static::lazy_static; 3 | use std::char; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | lazy_static! { 7 | static ref MANGLED_NAMES: Arc>> = Arc::new(Mutex::new(Vec::new())); 8 | } 9 | 10 | pub fn mangle(source: &str) -> String { 11 | let mut names = MANGLED_NAMES.lock().unwrap(); 12 | 13 | let mut name = String::new(); 14 | let mut unique_identifier = 0; 15 | 16 | let append = |name: &mut String, number: u32| { 17 | let mut number = number; 18 | 19 | if number == 0 { 20 | name.push('0'); 21 | return; 22 | } 23 | 24 | // reverse order representation of hash, but don't care 25 | // hash exists to make it more unique, so the order is not important 26 | while number > 0 { 27 | name.push(char::from_digit(number % 36, 36).expect("guaranteed by mod")); 28 | number /= 36; 29 | } 30 | }; 31 | 32 | loop { 33 | name.push_str("rs-"); 34 | append(&mut name, unique_identifier); 35 | append(&mut name, murmur2::hash32(source)); 36 | 37 | if !names.contains(&name) { 38 | break; 39 | } 40 | 41 | unique_identifier += 1; 42 | 43 | name.clear(); 44 | } 45 | 46 | names.push(name.clone()); 47 | 48 | name 49 | } 50 | -------------------------------------------------------------------------------- /src/css_files_impl.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; 2 | use runtime::global::CSS_FILES_MAP; 3 | use std::collections::HashSet; 4 | use std::iter::FromIterator; 5 | 6 | pub fn css_files() -> proc_macro::TokenStream { 7 | let files = CSS_FILES_MAP.lock().unwrap(); 8 | let files: HashSet<_> = files.values().flatten().collect(); 9 | 10 | let temp = TokenStream::from_iter(vec![ 11 | TokenTree::from(Ident::new("vec", Span::call_site())), 12 | TokenTree::from(Punct::new('!', Spacing::Alone)), 13 | TokenTree::from(Group::new( 14 | Delimiter::Parenthesis, 15 | TokenStream::from_iter(files.iter().map(|s| TokenTree::from(Literal::string(s)))), 16 | )), 17 | ]); 18 | 19 | let temp: proc_macro::TokenStream = temp.into(); 20 | 21 | temp 22 | } 23 | -------------------------------------------------------------------------------- /src/css_use_impl.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use std::iter::FromIterator; 4 | 5 | pub fn css_use(item: TokenStream) -> TokenStream { 6 | // todo: value inlining 7 | // println!("CSS USE\n{:?}\n", item); 8 | 9 | // ? The variable maybe used by rustyle, so should ignore the unused warning 10 | let preamble_tokens = TokenStream::from(quote! { 11 | #[allow(unused)] 12 | }); 13 | 14 | TokenStream::from_iter(preamble_tokens.into_iter().chain(item).into_iter()) 15 | } 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! This crate provides the [`rustyle!`] macro that allows using CSS-in-Rust for Rust frontend application. 3 | //! This crate uses some experimental features, so you should use nightly toolchain to use this crate. 4 | //! 5 | //! Note: This crate absolutely unstable, I recommend you to don't use this crate on a production project. 6 | //! 7 | //! [`rustyle!`]: macro.rustyle.html 8 | 9 | #![feature(proc_macro_diagnostic)] 10 | #![feature(proc_macro_span)] 11 | #![feature(slice_concat_ext)] 12 | #![warn(missing_docs, bare_trait_objects, elided_lifetimes_in_paths)] 13 | #![deny(anonymous_parameters)] 14 | 15 | extern crate fasthash; 16 | extern crate proc_macro; 17 | 18 | mod core; 19 | mod css_files_impl; 20 | mod css_use_impl; 21 | mod rustyle_impl; 22 | 23 | use proc_macro::{Span, TokenStream}; 24 | 25 | /// Create a style class which contains the rusty css code. 26 | /// Returns the name of the class. 27 | /// 28 | /// ```ignore 29 | /// let GlobalFile = css! { 30 | /// #![inject_global] 31 | /// body { 32 | /// padding: 0; 33 | /// } 34 | /// }; 35 | /// 36 | /// let (Button, ButtonFile) = css! { 37 | /// border: 1px solid black; 38 | /// #[allow(vendor_prefix)] 39 | /// ::-webkit-scrollbar { 40 | /// width: 10px; 41 | /// } 42 | /// }; 43 | /// 44 | /// html! { 45 | /// ... 46 | /// 49 | /// ... 50 | /// }; 51 | /// ``` 52 | #[proc_macro] 53 | pub fn rustyle(input: TokenStream) -> TokenStream { 54 | rustyle_impl::rustyle(input) 55 | } 56 | 57 | /// Alias of [`rustyle!`] macro. 58 | /// 59 | /// [`rustyle!`]: macro.rustyle.html 60 | /// 61 | /// ```ignore 62 | /// let (Button, ButtonFile) = css! { 63 | /// border: 1px solid black; 64 | /// }; 65 | /// 66 | /// html! { 67 | /// ... 68 | /// 71 | /// ... 72 | /// }; 73 | /// ``` 74 | #[proc_macro] 75 | pub fn css(input: TokenStream) -> TokenStream { 76 | rustyle_impl::rustyle(input) 77 | } 78 | 79 | /// Returns the created css files by rustyle as `Vec<&str>`. 80 | /// It is useful when you don't want to separate css file by route, 81 | /// just want to see whether the styles are approved. 82 | #[proc_macro] 83 | pub fn css_files(input: TokenStream) -> TokenStream { 84 | if !input.is_empty() { 85 | Span::call_site().warning("Input will be ignored").emit(); 86 | } 87 | 88 | css_files_impl::css_files() 89 | } 90 | 91 | /// Allows using an outer variable on [`rustyle!`] macro. 92 | /// Only constantly evaluable some expression allowed. 93 | /// 94 | /// [`rustyle!`]: macro.rustyle.html 95 | /// 96 | /// ```ignore 97 | /// #[css_use] 98 | /// let (Parent, ParentFile) = css! { 99 | /// color: red; 100 | /// }; 101 | /// 102 | /// let (Child, ChildFile) = css! { 103 | /// ${Parent} > & { 104 | /// color: white; 105 | /// } 106 | /// }; 107 | /// ``` 108 | #[proc_macro_attribute] 109 | pub fn css_use(attr: TokenStream, item: TokenStream) -> TokenStream { 110 | if !attr.is_empty() { 111 | Span::call_site() 112 | .warning("Parameters will be ignored") 113 | .emit(); 114 | } 115 | 116 | css_use_impl::css_use(item) 117 | } 118 | -------------------------------------------------------------------------------- /src/rustyle_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::core::name_mangler::mangle; 2 | use codegen::CodeGenerator; 3 | use parse::parse_rustyle; 4 | use proc_macro::{Span, TokenStream}; 5 | use quote::quote; 6 | use runtime::global::{CSS_FILES_MAP, CSS_ID, OUTPUT}; 7 | use runtime_facade::CompileContext; 8 | use std::error::Error; 9 | use std::fs::OpenOptions; 10 | use std::io::Write; 11 | 12 | pub fn rustyle(input: TokenStream) -> TokenStream { 13 | let mut css_files = CSS_FILES_MAP.lock().unwrap(); 14 | 15 | let mut id = CSS_ID.lock().unwrap(); 16 | 17 | let class_name = mangle( 18 | &input 19 | .clone() 20 | .into_iter() 21 | .map(|token| token.to_string()) 22 | .collect::(), 23 | ); 24 | 25 | let mut context = CompileContext { 26 | filename: format!("rustyle.{}.css", *id), 27 | }; 28 | 29 | let node = match parse_rustyle(input) { 30 | None => return (quote! {}).into(), 31 | Some(node) => node, 32 | }; 33 | 34 | let mut result = node.generate_code(&class_name, &mut context); 35 | result.push('\n'); 36 | 37 | let string_path = format!("{}/{}", OUTPUT.as_str(), context.filename); 38 | let path = std::path::Path::new(&string_path); 39 | 40 | if *id == 0 && std::fs::metadata(path.parent().unwrap()).is_ok() { 41 | if let Err(err) = std::fs::remove_dir_all(path.parent().unwrap()) { 42 | Span::call_site() 43 | .warning(format!("couldn't empty the folder: {}", err)) 44 | .emit(); 45 | } 46 | } 47 | 48 | if let Err(err) = std::fs::create_dir_all(path.parent().unwrap()) { 49 | Span::call_site() 50 | .error(format!("couldn't create the folder: {}", err)) 51 | .emit(); 52 | return (quote! {}).into(); 53 | } 54 | 55 | let mut file = match OpenOptions::new() 56 | .create(true) 57 | .write(true) 58 | .append(true) 59 | .open(path) 60 | { 61 | Err(err) => { 62 | Span::call_site() 63 | .error(format!("couldn't create the file: {}", err)) 64 | .emit(); 65 | return (quote! {}).into(); 66 | } 67 | Ok(file) => file, 68 | }; 69 | 70 | css_files.insert(class_name.clone(), vec![context.filename.clone()]); 71 | 72 | match file.write_all(result.as_bytes()) { 73 | Err(err) => { 74 | Span::call_site() 75 | .error(format!( 76 | "couldn't write to {}: {}", 77 | path.to_str().unwrap(), 78 | err.description() 79 | )) 80 | .emit(); 81 | } 82 | Ok(_) => {} 83 | } 84 | 85 | *id += 1; 86 | 87 | let filename = context.filename; 88 | 89 | let expanded = quote! { (#class_name, #filename) }; 90 | 91 | expanded.into() 92 | } 93 | --------------------------------------------------------------------------------