initial os release. I really expect SO much bugs, guys. good luck =)

This commit is contained in:
Yiffy Dev 2024-10-30 10:59:43 +05:00 • committed by spushk
commit d47557dc59
Signed by: spushk
GPG key ID: 7797B761E105E8AA
5 changed files with 7965 additions and 0 deletions

View file

@ -0,0 +1,202 @@
use std::{fmt::Debug, fs};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use sabry_intrnl::compiler::CompilerSyntax;
use syn::{braced, parse::Parse, spanned::Spanned, Ident, LitStr, Token};
pub mod sassy;
pub mod styly;
pub mod usey;
/// Macro input for arbitrary style code
///
/// Also supports string literal token instead of braces,
/// in which mode will look for relative file
///
/// Does not work nice with rust-analyzer though
#[derive(Clone, Debug)]
pub struct ArbitraryStyleBlock {
code: String,
span: Span,
}
impl ArbitraryStyleBlock {
pub fn span(&self) -> Span {
self.span
}
pub fn code(&self) -> &str {
&self.code
}
}
impl Parse for ArbitraryStyleBlock {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let (code, span) = if let Ok(path_tok) = input.parse::<LitStr>() {
let path = path_tok.value();
let iofile = match fs::read(&path) {
Ok(bf) => bf,
Err(_) => return Err(syn::Error::new(path_tok.span(), format!("Could not read file '{path}'. Perhaps its relative? Thats not currently supported :(")))
};
let code = String::from_utf8_lossy(&iofile).to_string();
(code, path_tok.span())
} else {
// Does not work on stable!
// We're on nightly though
// So what?
// Then is works.
let _s;
braced!(_s in input);
let stream = _s.parse::<TokenStream>()?;
let c = match stream.span().source_text() {
Some(stx) => stx,
// TODO: rust-analyzer does fall into this even if all is fine
None => "".to_string(), //return Err(syn::Error::new(stream.span(), "Source code expected")),
};
(c, stream.span())
};
Ok(Self { code, span })
}
}
#[allow(clippy::to_string_trait_impl)]
impl ToString for ArbitraryStyleBlock {
fn to_string(&self) -> String {
self.code.clone()
}
}
#[derive(Clone, Copy)]
pub enum ArbitraryStyleSyntax {
#[cfg(feature = "scss")]
Scss,
#[cfg(feature = "sass")]
Sass,
}
impl From<ArbitraryStyleSyntax> for CompilerSyntax {
fn from(value: ArbitraryStyleSyntax) -> Self {
match value {
#[cfg(feature = "sass")]
ArbitraryStyleSyntax::Sass => Self::Sass,
#[cfg(feature = "scss")]
ArbitraryStyleSyntax::Scss => Self::Scss,
}
}
}
impl ToTokens for ArbitraryStyleSyntax {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
#[cfg(feature = "sass")]
Self::Sass => tokens.append_all(quote! {"sass"}),
#[cfg(feature = "scss")]
Self::Scss => tokens.append_all(quote! {"scss"}),
}
}
}
impl Parse for ArbitraryStyleSyntax {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
match input.parse::<Token![:]>() {
Ok(_) => { /* Syntax is set */ }
Err(_) => {
/* Syntax is not set, falling to default */
return Ok(Self::default());
}
}
let ident = input.parse::<Ident>()?;
match ident.to_string().as_str() {
#[cfg(feature = "sass")]
"sass" => Ok(Self::Sass),
#[cfg(feature = "scss")]
"scss" => Ok(Self::Scss),
_ => Err(syn::Error::new(
ident.span(),
format!(
"Available syntax are `sass` (WIP) and `scss`, each of them requires feature flag. Omit to use default {:?} syntax",
Self::default()
),
)),
}
}
}
impl Debug for ArbitraryStyleSyntax {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
#[cfg(feature = "sass")]
Self::Sass => "sass",
#[cfg(feature = "scss")]
Self::Scss => "scss",
};
write!(f, "{name}")
}
}
impl Default for ArbitraryStyleSyntax {
#[cfg(feature = "scss")]
fn default() -> Self {
Self::Scss
}
#[cfg(all(feature = "sass", not(feature = "scss")))]
fn default() -> Self {
Self::Sass
}
}
impl From<ArbitraryStyleSyntax> for raffia::Syntax {
fn from(value: ArbitraryStyleSyntax) -> Self {
match value {
#[cfg(feature = "sass")]
ArbitraryStyleSyntax::Sass => Self::Sass,
#[cfg(feature = "scss")]
ArbitraryStyleSyntax::Scss => Self::Scss,
}
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "sass")]
#[test]
fn arbitrary_style_block_sass() {
use super::ArbitraryStyleBlock;
let code = "#a
co:d
.sel
di: pi
.sel,div > sel
f: \"into\"";
let braced_code = format!("{{{code}}}");
let block = syn::parse_str::<ArbitraryStyleBlock>(&braced_code).unwrap();
assert_eq!(code, block.code);
}
#[cfg(feature = "scss")]
#[test]
fn arbitrary_style_block_scss() {
use super::ArbitraryStyleBlock;
let code = "#a {
c:r;
}
.b.c{
c:\"into\";
}";
let braced_code = format!("{{{code}}}");
let block = syn::parse_str::<ArbitraryStyleBlock>(&braced_code).unwrap();
assert_eq!(code, block.code)
}
}

View file

@ -0,0 +1,68 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::Parse, Ident};
use super::{ArbitraryStyleBlock, ArbitraryStyleSyntax};
/// Syntax:
/// `#ident(:$syntax)? { $code }`
///
/// `$syntax`: sass/scss
///
/// `$code`: intended to be valid based on $syntax
pub fn sassy_macro_impl(input: TokenStream) -> TokenStream {
let MacroSyntax {
ident,
syntax: mode,
code,
} = match syn::parse2::<MacroSyntax>(input) {
Ok(m) => m,
Err(e) => return e.to_compile_error(),
};
let sourcesass = code.to_string();
let identname = ident.to_string();
let macro_doc = format!("Arbitrary {:?} code declared with `sassy!`. Pretty usable in tandem with `usey!` and `magic()` at build time", mode);
quote! {
#[doc = #macro_doc]
#[macro_export]
macro_rules! #ident {
() => {#sourcesass};
(module) => {#identname};
(syntax) => {#mode};
}
}
}
pub struct MacroSyntax {
ident: Ident,
syntax: ArbitraryStyleSyntax,
code: ArbitraryStyleBlock,
}
impl Parse for MacroSyntax {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
#[cfg(feature = "sass")]
use regex::Regex;
let ident = input.parse::<Ident>()?;
let mode = input.parse::<ArbitraryStyleSyntax>()?;
let mut code = input.parse::<ArbitraryStyleBlock>()?;
// this is because of how parsing works. Need to find more elegant way though
// TODO: find a more elegant way
code.code = Regex::new("\n ").expect("BUG: failed to build regex for indentation SASS").replace_all(&code.code, "\n").to_string();
Ok(Self {
ident,
syntax: mode,
code,
})
}
}
#[cfg(test)]
mod test {
}

View file

@ -0,0 +1,209 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use sabry_intrnl::{
compiler::CompilerAdapter,
config::SabryConfig,
scoper::{ArbitraryScope, ScopedSelector},
};
use syn::{parse::Parse, Ident, Token};
use super::{ArbitraryStyleBlock, ArbitraryStyleSyntax};
/// Syntax:
/// `pub? #ident(:$syntax)? { $code } \#?`
///
/// `$syntax`: sass/scss
///
/// `$code`: intended to be valid based on $syntax
///
/// `pub` modifier makes scope public
///
/// `const` modifier completely changes the behaviour of styling, and
/// makes CSS exposed as constant
///
/// `#` shebang generates machine-readable and well-parsible but invalid rust code for building purposes
/// instead of scope module.
///
/// The machine-readable output may also be forced by use `machine_readable: true` arg on `parse_macro_syntax` function
/// without modifying tokenstream
pub fn styly_macro_impl(input: TokenStream) -> TokenStream {
let config = match SabryConfig::require() {
Ok(c) => c,
Err(e) => {
return syn::Error::new(
Span::call_site(),
format!(
"Could not read sabry configuration required by this macro: {:?}",
e
),
)
.to_compile_error()
}
};
let ms = match parse_macro_syntax(input) {
Ok(ms) => ms,
Err(e) => return e.to_compile_error(),
};
let scope = match ArbitraryScope::from_source(ms.syntax.into(), ms.scope.clone(), &ms.code.code)
{
Ok(s) => match s.hashed(&config.hash) {
Ok(hs) => Ok(hs),
Err(e) => Err(e),
},
Err(e) => Err(e),
};
let scope = match scope {
Ok(s) => s,
Err(e) => {
return syn::Error::new(
Span::call_site(),
format!("Could not generate scope: {:?}", e),
)
.to_compile_error()
}
};
match ms.generator {
ScopeGenerator::Module { public, constant } => {
let scope_hash = scope.hash.as_str();
let scope_ident = scope.original_scope.name;
let scope_wrapper_ident =
syn::parse_str::<Ident>(&scope_ident.to_string().to_uppercase())
.expect("BUG: We just converted valid Ident to string and uppercased");
let scope_vis = if public {
quote! {pub}
} else {
quote! {}
};
let scope_members = scope
.hashed_selectors
.iter()
.filter_map(|hs| {
hs.html_ident.clone().map(|html| {
let name = match hs.sel {
ScopedSelector::Class(_) => "class",
ScopedSelector::Id(_) => "id",
ScopedSelector::Tag(_) => "tagname",
};
(name, hs.sel.gen_rusty_ident(), html, hs.css_ident.clone())
})
})
.map(|(name, ident, html, css)| {
let docs = format!("'{}' {}. CSS selector '{}'", ident, name, css);
quote! {
#[doc = #docs]
#[allow(non_upper_case_globals)]
pub const #ident : &str = #html ;
}
});
let mod_docs = format!(
"'{}' style scope. The wrapper class for scoped tagnames is {}",
&scope_ident, &scope_wrapper_ident
);
let wrp_docs = format!("wrapper class for '{}' scope. If you have any tagname selectors - they should live as children of element with this class applied.", &scope_ident);
if constant {
let compiler = CompilerAdapter::new(config.clone());
let css = match compiler.compile_module(ms.syntax.into(), &ms.code.code) {
Ok(c) => c,
Err(e) => {
return syn::Error::new(
ms.code.span,
format!("Could not compile const inline: {e:?}"),
)
.to_compile_error()
}
};
let const_docs = format!("The compiled CSS style for {} scope", &scope_ident);
let const_wrapper_ident = syn::parse_str::<Ident>(
format!("{}_CSS", &scope_ident.to_string().to_uppercase()).as_str(),
)
.expect("BUG: We just converted valid Ident to string and uppercased");
quote! {
#[doc = #const_docs]
#scope_vis const #const_wrapper_ident: &str = #css ;
#[doc = #wrp_docs]
#scope_vis const #scope_wrapper_ident : &str = #scope_hash ;
#[doc = #mod_docs]
#scope_vis mod #scope_ident {
#(#scope_members)*
}
}
} else {
quote! {
#[doc = #wrp_docs]
#scope_vis const #scope_wrapper_ident : &str = #scope_hash ;
#[doc = #mod_docs]
#scope_vis mod #scope_ident {
#(#scope_members)*
}
}
}
}
ScopeGenerator::MachineReadable => {
quote! {}
}
}
}
// reusable function, that does not return tokenstream for machine-processing at build time
pub fn parse_macro_syntax(input: TokenStream) -> Result<MacroSyntax, syn::Error> {
syn::parse2::<MacroSyntax>(input)
}
#[derive(Debug)]
pub enum ScopeGenerator {
Module { public: bool, constant: bool },
MachineReadable,
}
impl Parse for ScopeGenerator {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let public = input.parse::<Token![pub]>().is_ok();
let constant = input.parse::<Token![const]>().is_ok();
// would be nice to implement compile-time CSS compilation to const
// with `const` modifier, so I'll leave it this way for now
let this = Self::Module { public, constant };
Ok(this)
}
}
#[derive(Debug)]
pub struct MacroSyntax {
/// The way macro should be expanded
pub generator: ScopeGenerator,
/// Scope identifier
pub scope: Ident,
/// Used stytax
pub syntax: ArbitraryStyleSyntax,
/// Style code, either from rust or read from file
pub code: ArbitraryStyleBlock,
}
impl Parse for MacroSyntax {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut generator = input.parse::<ScopeGenerator>()?;
let scope = input.parse::<Ident>()?;
let syntax = input.parse::<ArbitraryStyleSyntax>()?;
let code = input.parse::<ArbitraryStyleBlock>()?;
if input.parse::<Token![#]>().is_ok() {
generator = ScopeGenerator::MachineReadable
}
Ok(Self {
generator,
scope,
syntax,
code,
})
}
}

View file

@ -0,0 +1,71 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::Parse, punctuated::Punctuated, Macro, Token};
/// Syntax:
/// `#(pub? #macro,)*`
pub fn usey_macro_impl(input: TokenStream) -> TokenStream {
let MacroSyntax { stylecalls } = match syn::parse2(input) {
Ok(ms) => ms,
Err(e) => return e.to_compile_error(),
};
let stylecall_pairs = stylecalls
.iter()
.map(|c| c.to_contract_pair("__sabry_pub_module"));
quote! {
vec![
#(#stylecall_pairs,)*
]
}
}
#[derive(Clone)]
pub struct StyleCall {
vis_pub: bool,
call: Macro,
}
impl StyleCall {
pub fn to_contract_pair(&self, pub_module: &str) -> TokenStream {
let call_code = &self.call;
let call_path = &self.call.path;
let call_bang = &self.call.bang_token;
// the following calls are expected by macro genereted by `sassy!`
let call_syntax = quote! {#call_path #call_bang (syntax)};
let call_file = quote! {#call_path #call_bang (module)};
if self.vis_pub {
quote! {
(format!("{}.{}", #pub_module, #call_syntax), #call_code .to_string())
}
} else {
quote! {
(format!("{}.{}", #call_file, #call_syntax), #call_code .to_string())
}
}
}
}
impl Parse for StyleCall {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let vis_pub = input.parse::<Token![pub]>().is_ok();
let call = input.parse::<Macro>()?;
Ok(Self { vis_pub, call })
}
}
pub struct MacroSyntax {
stylecalls: Vec<StyleCall>,
}
impl Parse for MacroSyntax {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let calls = Punctuated::<StyleCall, Token![,]>::parse_separated_nonempty(input)?;
let stylecalls = calls.iter().cloned().collect();
Ok(Self { stylecalls })
}
}

View file

@ -0,0 +1,4 @@
// Moving this to separate crate, cuz I don't wanna to reimplement that parsing again for build magic
// Also, because I can.
pub mod impls;