Skip to content

Commit

Permalink
Upstream yewtil crate (#1119)
Browse files Browse the repository at this point in the history
* Upstream yewtil crate

* cargo fmt

* cargo clippy

* cargo fmt
  • Loading branch information
jstarry committed Apr 25, 2020
1 parent a91e7f6 commit f9a783c
Show file tree
Hide file tree
Showing 58 changed files with 4,492 additions and 0 deletions.
13 changes: 13 additions & 0 deletions Cargo.toml
Expand Up @@ -13,6 +13,19 @@ members = [
"yew-router/examples/router_component",
"yew-router/examples/switch",

# Utilities
"yewtil",
"yewtil-macro",
"yewtil/examples/pure_component",
# "yewtil/examples/dsl",
"yewtil/examples/lrc",
"yewtil/examples/history",
"yewtil/examples/mrc_irc",
"yewtil/examples/effect",
"yewtil/examples/fetch",
"yewtil/examples/futures",
"yewtil/examples/function_component",

# Examples
"examples/counter",
"examples/crm",
Expand Down
15 changes: 15 additions & 0 deletions yewtil-macro/Cargo.toml
@@ -0,0 +1,15 @@
[package]
name = "yewtil-macro"
version = "0.1.0"
authors = ["Henry Zimmerman <zimhen7@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"
description = "Macros to be re-exported from the yewtil crate"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0.6"
quote = "1.0.2"
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
194 changes: 194 additions & 0 deletions yewtil-macro/src/function_component.rs
@@ -0,0 +1,194 @@
use proc_macro::TokenStream as TokenStream1;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::export::ToTokens;
use syn::parse::{Parse, ParseBuffer};
use syn::parse_macro_input;
use syn::punctuated::Punctuated;
use syn::token;
use syn::Token;
use syn::{braced, parenthesized};
use syn::{Block, Error, Field, Stmt, Type, VisPublic, Visibility};

pub fn function_component_handler(attr: TokenStream, item: TokenStream1) -> TokenStream1 {
let component_name = attr.to_string();
assert!(
!component_name.is_empty(),
"you must provide a component name. eg: function_component(MyComponent)"
);
let component_name = Ident::new(&component_name, Span::call_site());
let function = parse_macro_input!(item as Function);
TokenStream1::from(
FunctionComponentInfo {
component_name,
function,
}
.to_token_stream(),
)
}

pub struct FunctionComponentInfo {
component_name: Ident,
function: Function,
}

// TODO, support type parameters

pub struct Function {
pub vis: Visibility,
pub fn_token: Token![fn],
pub name: Ident,
pub paren_token: token::Paren,
pub fields: Punctuated<Field, Token![,]>,
pub returns_token: Token![->],
pub return_ty: Ident,
pub brace_token: token::Brace,
pub body: Vec<Stmt>,
}

impl Parse for Function {
fn parse(input: &ParseBuffer) -> Result<Self, Error> {
let vis = input.parse()?;
let fn_token = input.parse()?;
let name = input.parse()?;
let content;
let paren_token = parenthesized!(content in input);
let returns_token = input.parse()?;
let return_ty = input.parse()?;
let content2;
let brace_token = braced!(content2 in input);
Ok(Function {
vis,
fn_token,
name,
paren_token,
fields: content.parse_terminated(Field::parse_named)?,
returns_token,
return_ty,
brace_token,
body: content2.call(Block::parse_within)?,
})
}
}

impl ToTokens for Function {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Function {
vis,
fn_token,
name,
fields,
returns_token,
return_ty,
body,
..
} = self;
let fields = fields
.iter()
.map(|field: &Field| {
let mut new_field: Field = field.clone();
new_field.attrs = vec![];
new_field
})
.collect::<Punctuated<_, Token![,]>>();

tokens.extend(quote! {
#vis #fn_token #name(#fields) #returns_token #return_ty {
#(#body)*
}
})
}
}

impl ToTokens for FunctionComponentInfo {
fn to_tokens(&self, tokens: &mut TokenStream) {
let FunctionComponentInfo {
component_name,
function,
} = self;
// The function tokens must be re-generated in order to strip the attributes that are not allowed.
let function_token_stream = function.to_token_stream();
let Function {
vis, name, fields, ..
} = function;

let impl_name = format!("FuncComp{}", component_name.to_string());
let impl_name = Ident::new(&impl_name, Span::call_site());

let alias = quote! {
#vis type #component_name = ::yewtil::Pure<#impl_name>;
};

// Set the fields to be public and strips references as necessary.
// This will preserve attributes like #[props(required)], which will appear in the generated struct below.
let new_fields = fields
.iter()
.map(|field: &Field| {
let mut new_field: Field = field.clone();
let visibility = Visibility::Public(VisPublic {
pub_token: syn::token::Pub {
span: Span::call_site(),
},
});
// Strip references so the component can have a static lifetime.
// TODO Handle 'static lifetimes gracefully here - allowing &'static strings instead of erroneously converting them to plain strs.
let ty = match &field.ty {
Type::Reference(x) => {
let elem = x.elem.clone();
Type::Verbatim(quote! {
#elem
})
}
x => x.clone(),
};
new_field.vis = visibility;
new_field.ty = ty;
new_field
})
.collect::<Punctuated<_, Token![,]>>();

let component_struct = quote! {
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
#vis struct #impl_name {
#new_fields
}
};

let arguments = fields
.iter()
.zip(new_fields.iter())
.map(|(field, new_field): (&Field, &Field)| {
let field_name = field.ident.as_ref().expect("Field must have name");

// If the fields differ, then a reference was removed from the function's field's type
// to make it static.
// Otherwise it is assumed that the type is not a reference on the function and it
// implements clone, and that when calling the function, the type should be cloned again.
if field.ty != new_field.ty {
quote! {
&self.#field_name
}
} else {
quote! {
self.#field_name.clone()
}
}
})
.collect::<Punctuated<_, Token![,]>>();

let pure_component_impl = quote! {
impl ::yewtil::PureComponent for #impl_name {
fn render(&self) -> ::yew::Html {
#name(#arguments)
}
}
};

tokens.extend(quote! {
#function_token_stream
#alias
#component_struct
#pure_component_impl
})
}
}
10 changes: 10 additions & 0 deletions yewtil-macro/src/lib.rs
@@ -0,0 +1,10 @@
extern crate proc_macro;
use proc_macro::TokenStream;

use crate::function_component::function_component_handler;

mod function_component;
#[proc_macro_attribute]
pub fn function_component(attr: TokenStream, item: TokenStream) -> TokenStream {
function_component_handler(attr.into(), item)
}
20 changes: 20 additions & 0 deletions yewtil/CHANGELOG.md
@@ -0,0 +1,20 @@
# Changelog

<!-- START TEMPLATE
## ✨ **VERSION** *(DATE)*
- #### ⚡️ Features
- Sample
- #### 🛠 Fixes
- Sample
- #### 🚨 Breaking changes
- Sample
END TEMPLATE-->

## **v0.2.0** *11/18/19*
- #### ⚡️ Features
- Add new `FetchRequest` trait, `fetch_resource()` function, and `FetchState` enum
to simplify making fetch requests using futures.
- Add `Default` implementations to `Irc` and `Mrc`.
68 changes: 68 additions & 0 deletions yewtil/Cargo.toml
@@ -0,0 +1,68 @@
[package]
name = "yewtil"
version = "0.2.0"
authors = ["Henry Zimmerman <zimhen7@gmail.com>"]
edition = "2018"
description = "Utility crate for Yew"
license = "MIT/Apache-2.0"
repository = "https://github.com/yewstack/yewtil"
readme = "Readme.md"

[features]
default = ["stable"] # Only stable is included by default.
all = ["stable", "experimental"]

# Broad features
## All features MUST be stable or experimental
stable = ["neq", "pure", "history", "mrc_irc", "effect", "future"]
experimental = ["dsl", "lrc", "with_callback", "fetch" ]

# Some pointers are stable, some experimental.
# This makes sure you get all the pointers
ptr = ["lrc", "mrc_irc"]

# Misc features
neq = []
pure = ["neq", "yewtil-macro"]
with_callback = []
history = []
dsl = []
effect = []
fetch = ["serde", "serde_json", "neq", "future"]
future = ["wasm-bindgen-futures", "wasm-bindgen", "stdweb", "futures", "web-sys"]

# Ptr features
lrc = []
mrc_irc = []

[dependencies]
futures = {version = "0.3.1", optional = true}
log = "0.4.8"
serde = {version= "1.0.102", optional = true}
serde_json = { version = "1.0.41", optional = true }
wasm-bindgen = {version = "0.2.51", features=["serde-serialize"], optional = true}
wasm-bindgen-futures = {version = "0.4.3", optional = true}
yew = { path = "../yew" }
yewtil-macro = { path = "../yewtil-macro", optional = true }

[dependencies.stdweb]
version = "0.4.20"
optional = true
features = [
"futures-support",
"experimental_features_which_may_break_on_minor_version_bumps",
]

[dependencies.web-sys]
version = "0.3.31"
optional = true
features = [
'Headers',
'Request',
'RequestInit',
'RequestMode',
'Response',
'Window',
'Location',
'Storage',
]

0 comments on commit f9a783c

Please sign in to comment.