diff --git a/README.md b/README.md new file mode 100644 index 0000000..d405ef9 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Soda +Soda is an ultrafast, light, and **sane** frontend framework, written in Rust. + +It shares many similarities with React, in that everything is done with declarative components. However, it represents several marked improvements: + * Not JavaScript. Soda allows you to write everything in Rust, without the horrors of the entire JavaScript ecosystem. If you use React with TypeScript, you'll feel pretty much at home, although the significantly improved speed might be a shock. + * Single-binary. Soda projects compile to a single binary containing an HTTP server and all of your pages. You don't have to juggle a messy filesystem tree. + * *Fast*. Soda is built around a zero-cost abstraction model that means every Soda app has a distinct Rust type without ever constructing vtables. This means the optimizing compiler can inline all your worries away. + +Soda is related to but is not a replacement for [sitix](https://swaous.asuscomm.com/sitix). Sitix has an entirely different set of capabilities: while both can, at a high level, perform similar templating tasks, Sitix is designed around building static file trees to be hosted on an external webserver, while Soda is designed to produce dynamic single-binary webapps. + +Documentation is a WIP. \ No newline at end of file diff --git a/examples/hello.rs b/examples/hello.rs index 4c621ee..044a8a2 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -3,18 +3,45 @@ extern crate soda; use soda::prelude::*; +use css::*; #[tokio::main] async fn main() { - let page = PageBase("Test Page", + let page = PageBase("Test Page", ( + Tag::Style(( + Rule( + Select::Tag("div").and(Select::Class("red")).or(Select::Tag("span").and(Select::Class("red"))), + Property::BackgroundColor("red") + ), + Rule( + Select::Class("bigger"), + Property::FontSize(|st : &mut u32| { + format!("{}px", *st + 10) + }) + ) + )), Tag::Heading1("Hello, World!"), - Tag::Paragraph("This is a test of Soda, an ultrafast composable frontend framework built in Rust!") + Tag::Paragraph("This is a test of Soda, an ultrafast composable frontend framework built in Rust!"), + Tag::Paragraph(( + "This page has been visited ", + |st : &mut u32| { + *st += 1; + st.to_string() + }, + " times." + )), + Tag::Div( + "This has a red background!" + ).class("red"), + Tag::Span("it's coming straight at us!").class("bigger"), + Tag::Br, + Tag::Span("leon's getting laaaaaaaaaaaaaaaaaaaaaarger").class("red bigger") ) ); let app = page.route(); - Server::new(([0, 0, 0, 0], 3000)).await.unwrap().serve(app, ()).await.unwrap(); + Server::new(([0, 0, 0, 0], 3000)).await.unwrap().serve(app, 0).await.unwrap(); // weird rustc bug: if the second argument is omitted, rustc gets stuck in a recursion trying to figure out what type State is // *before* exiting on the error produced by the missing argument. this consumes cpu and slowly eats ram until it OOMs or you kill it. } \ No newline at end of file diff --git a/src/css.rs b/src/css.rs new file mode 100644 index 0000000..c1bcdbd --- /dev/null +++ b/src/css.rs @@ -0,0 +1,143 @@ +// composers for CSS + +use crate::prelude::*; +use std::marker::PhantomData; + + + +pub struct Rule (pub Selector, pub Properties); + + + +impl, Properties : Compose> Compose for Rule { + fn render(&self, output : &mut impl output::Output, state : &mut State) { + self.0.render(output, state); + output.write("{"); + self.1.render(output, state); + output.write("}"); + } +} + + +pub enum Select { + Tag(T), + Class(T), + Id(T) +} + + +impl> Compose for Select { + fn render(&self, output : &mut impl output::Output, state : &mut State) { + match self { + Self::Tag(name) => { + name.render(output, state); + }, + Self::Class(name) => { + output.write("."); + name.render(output, state); + }, + Self::Id(name) => { + output.write("#"); + name.render(output, state); + } + } + } +} + +/* +pub trait Selector { +} + + +impl> CssFunctions for Select {} +impl, U : Compose> CssFunctions for CssOr {} +impl, U : Compose> CssFunctions for CssAnd {} +*/ + + +pub trait Selector : Compose { + fn and>(self, other : O) -> CssAnd where Self : Sized { + CssAnd { + one : self, + two : other, + _phantom : Default::default() + } + } + + fn or>(self, other : O) -> CssOr where Self : Sized { + CssOr { + one : self, + two : other, + _phantom : Default::default() + } + } +} + +impl> Selector for Select {} +impl, U : Compose> Selector for CssOr {} +impl, U : Compose> Selector for CssAnd {} + + +pub struct CssAnd, Two : Compose> { + one : One, + two : Two, + _phantom : PhantomData +} + + +impl, Two : Compose> Compose for CssAnd { + fn render(&self, output : &mut impl output::Output, state : &mut State) { + self.one.render(output, state); + self.two.render(output, state); + } +} + + +pub struct CssOr, Two : Compose> { + one : One, + two : Two, + _phantom : PhantomData +} + + + +impl, Two : Compose> Compose for CssOr { + fn render(&self, output : &mut impl output::Output, state : &mut State) { + self.one.render(output, state); + output.write(", "); + self.two.render(output, state); + } +} + + +pub enum Property { + BackgroundColor(T), + FontSize(T) +} + + +impl Property { + fn get_name(&self) -> &'static str { + match self { + Self::BackgroundColor(_) => "background-color", + Self::FontSize(_) => "font-size" + } + } + + fn get_inner<'a>(&'a self) -> &'a T { + match self { + Self::BackgroundColor(c) => c, + Self::FontSize(c) => c + } + } +} + + +impl> Compose for Property { + fn render(&self, output : &mut impl output::Output, state : &mut State) { + output.write(self.get_name()); + output.write(":"); + self.get_inner().render(output, state); + output.write(";"); + } +} \ No newline at end of file diff --git a/src/html.rs b/src/html.rs index c5bf97e..7ca288c 100644 --- a/src/html.rs +++ b/src/html.rs @@ -22,6 +22,10 @@ pub trait HTMLTag : Compose { self.with_property("charset", v) } + fn class(self, v : &'static str) -> WithProperty where Self: Sized { + self.with_property("class", v) + } + fn helpful_provided_render_function(&self, out : &mut impl Output, state : &mut State) { out.write("<"); out.write(self.get_tagname()); @@ -100,19 +104,23 @@ pub enum StandardTags { Title(T), Paragraph(T), Div(T), - Heading1(T) + Heading1(T), + Style(T), + Span(T) } pub enum SelfClosingTags { - Meta + Meta, + Br } impl HTMLTag for SelfClosingTags { fn get_tagname(&self) -> &'static str { match self { - Self::Meta => "meta" + Self::Meta => "meta", + Self::Br => "br" } } @@ -131,7 +139,9 @@ impl StandardTags { Self::Title(c) => c, Self::Paragraph(c) => c, Self::Div(c) => c, - Self::Heading1(c) => c + Self::Heading1(c) => c, + Self::Style(c) => c, + Self::Span(c) => c } } } @@ -145,7 +155,9 @@ impl> HTMLTag for StandardTags Self::Title(_) => "title", Self::Paragraph(_) => "p", Self::Div(_) => "div", - Self::Heading1(_) => "h1" + Self::Heading1(_) => "h1", + Self::Style(_) => "style", + Self::Span(_) => "span" } } diff --git a/src/lib.rs b/src/lib.rs index 98ed579..39f291b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ pub mod tuples; pub mod router; pub mod prelude; pub mod server; +pub mod css; #[cfg(test)] diff --git a/src/prelude.rs b/src/prelude.rs index 0cfa106..c34f243 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,5 +4,6 @@ pub use crate::{ compose::Compose, output, router::{ Router, self }, - server::* + server::*, + css };