get a tiny app workin'!
This commit is contained in:
11
README.md
Normal file
11
README.md
Normal file
@@ -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.
|
||||
@@ -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.
|
||||
}
|
||||
143
src/css.rs
Normal file
143
src/css.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
// composers for CSS
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
|
||||
|
||||
pub struct Rule<Selector, Properties> (pub Selector, pub Properties);
|
||||
|
||||
|
||||
|
||||
impl<State : Send + Sync, Selector : Compose<State>, Properties : Compose<State>> Compose<State> for Rule<Selector, Properties> {
|
||||
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<T> {
|
||||
Tag(T),
|
||||
Class(T),
|
||||
Id(T)
|
||||
}
|
||||
|
||||
|
||||
impl<State : Send + Sync, T : Compose<State>> Compose<State> for Select<T> {
|
||||
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<State : Send + Sync> {
|
||||
}
|
||||
|
||||
|
||||
impl<State : Send + Sync, T : Compose<State>> CssFunctions<State> for Select<T> {}
|
||||
impl<State : Send + Sync, T : Compose<State>, U : Compose<State>> CssFunctions<State> for CssOr<T, U> {}
|
||||
impl<State : Send + Sync, T : Compose<State>, U : Compose<State>> CssFunctions<State> for CssAnd<T, U> {}
|
||||
*/
|
||||
|
||||
|
||||
pub trait Selector<State : Send + Sync> : Compose<State> {
|
||||
fn and<O : Compose<State>>(self, other : O) -> CssAnd<State, Self, O> where Self : Sized {
|
||||
CssAnd {
|
||||
one : self,
|
||||
two : other,
|
||||
_phantom : Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn or<O : Compose<State>>(self, other : O) -> CssOr<State, Self, O> where Self : Sized {
|
||||
CssOr {
|
||||
one : self,
|
||||
two : other,
|
||||
_phantom : Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<State : Send + Sync, T : Compose<State>> Selector<State> for Select<T> {}
|
||||
impl<State : Send + Sync, T : Compose<State>, U : Compose<State>> Selector<State> for CssOr<State, T, U> {}
|
||||
impl<State : Send + Sync, T : Compose<State>, U : Compose<State>> Selector<State> for CssAnd<State, T, U> {}
|
||||
|
||||
|
||||
pub struct CssAnd<State : Send + Sync, One : Compose<State>, Two : Compose<State>> {
|
||||
one : One,
|
||||
two : Two,
|
||||
_phantom : PhantomData<State>
|
||||
}
|
||||
|
||||
|
||||
impl<State : Send + Sync, One : Compose<State>, Two : Compose<State>> Compose<State> for CssAnd<State, One, Two> {
|
||||
fn render(&self, output : &mut impl output::Output, state : &mut State) {
|
||||
self.one.render(output, state);
|
||||
self.two.render(output, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct CssOr<State : Send + Sync, One : Compose<State>, Two : Compose<State>> {
|
||||
one : One,
|
||||
two : Two,
|
||||
_phantom : PhantomData<State>
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl<State : Send + Sync, One : Compose<State>, Two : Compose<State>> Compose<State> for CssOr<State, One, Two> {
|
||||
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<T> {
|
||||
BackgroundColor(T),
|
||||
FontSize(T)
|
||||
}
|
||||
|
||||
|
||||
impl<T> Property<T> {
|
||||
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<State : Send + Sync, T : Compose<State>> Compose<State> for Property<T> {
|
||||
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(";");
|
||||
}
|
||||
}
|
||||
22
src/html.rs
22
src/html.rs
@@ -22,6 +22,10 @@ pub trait HTMLTag<State : Send + Sync> : Compose<State> {
|
||||
self.with_property("charset", v)
|
||||
}
|
||||
|
||||
fn class(self, v : &'static str) -> WithProperty<State, Self> 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<T> {
|
||||
Title(T),
|
||||
Paragraph(T),
|
||||
Div(T),
|
||||
Heading1(T)
|
||||
Heading1(T),
|
||||
Style(T),
|
||||
Span(T)
|
||||
}
|
||||
|
||||
|
||||
pub enum SelfClosingTags {
|
||||
Meta
|
||||
Meta,
|
||||
Br
|
||||
}
|
||||
|
||||
|
||||
impl<State : Send + Sync> HTMLTag<State> for SelfClosingTags {
|
||||
fn get_tagname(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Meta => "meta"
|
||||
Self::Meta => "meta",
|
||||
Self::Br => "br"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +139,9 @@ impl<T> StandardTags<T> {
|
||||
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<State : Send + Sync, T : Compose<State>> HTMLTag<State> for StandardTags<T>
|
||||
Self::Title(_) => "title",
|
||||
Self::Paragraph(_) => "p",
|
||||
Self::Div(_) => "div",
|
||||
Self::Heading1(_) => "h1"
|
||||
Self::Heading1(_) => "h1",
|
||||
Self::Style(_) => "style",
|
||||
Self::Span(_) => "span"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod tuples;
|
||||
pub mod router;
|
||||
pub mod prelude;
|
||||
pub mod server;
|
||||
pub mod css;
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,5 +4,6 @@ pub use crate::{
|
||||
compose::Compose,
|
||||
output,
|
||||
router::{ Router, self },
|
||||
server::*
|
||||
server::*,
|
||||
css
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user