get a tiny app workin'!

This commit is contained in:
2025-02-13 17:05:33 -05:00
parent 09dc73663d
commit 79ff8caf34
6 changed files with 204 additions and 9 deletions

11
README.md Normal file
View 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.

View File

@@ -3,18 +3,45 @@
extern crate soda;
use soda::prelude::*;
use css::*;
#[tokio::main]
async fn main() {
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
View 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(";");
}
}

View File

@@ -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"
}
}

View File

@@ -18,6 +18,7 @@ pub mod tuples;
pub mod router;
pub mod prelude;
pub mod server;
pub mod css;
#[cfg(test)]

View File

@@ -4,5 +4,6 @@ pub use crate::{
compose::Compose,
output,
router::{ Router, self },
server::*
server::*,
css
};