Full Code of rail44/squark for AI

master 3658acd25258 cached
47 files
62.5 KB
17.5k tokens
139 symbols
1 requests
Download .txt
Repository: rail44/squark
Branch: master
Commit: 3658acd25258
Files: 47
Total size: 62.5 KB

Directory structure:
gitextract_c8b3y0t7/

├── .gitignore
├── Cargo.toml
├── LICENCE
├── README.md
├── examples/
│   ├── counter/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── crate/
│   │   │   ├── .crates.toml
│   │   │   ├── .gitignore
│   │   │   ├── Cargo.toml
│   │   │   └── src/
│   │   │       └── lib.rs
│   │   ├── index.html
│   │   ├── js/
│   │   │   └── index.js
│   │   ├── package.json
│   │   └── webpack.config.js
│   ├── todomvc/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── crate/
│   │   │   ├── .crates.toml
│   │   │   ├── .gitignore
│   │   │   ├── Cargo.toml
│   │   │   └── src/
│   │   │       └── lib.rs
│   │   ├── index.html
│   │   ├── js/
│   │   │   └── index.js
│   │   ├── package.json
│   │   ├── styles.css
│   │   └── webpack.config.js
│   └── with_task/
│       ├── .gitignore
│       ├── README.md
│       ├── crate/
│       │   ├── .gitignore
│       │   ├── Cargo.toml
│       │   └── src/
│       │       └── lib.rs
│       ├── index.html
│       ├── js/
│       │   └── index.js
│       ├── package.json
│       └── webpack.config.js
├── renovate.json
├── squark/
│   ├── .gitignore
│   ├── Cargo.toml
│   └── src/
│       ├── lib.rs
│       └── vdom.rs
├── squark-macros/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── src/
│   │   ├── lib.rs
│   │   └── view.pest
│   └── tests/
│       └── test.rs
└── squark-web/
    ├── .gitignore
    ├── Cargo.toml
    └── src/
        └── lib.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/target
/Cargo.lock


================================================
FILE: Cargo.toml
================================================
[workspace]
members = [
    "squark",
    "squark-macros",
    "squark-web",
]


================================================
FILE: LICENCE
================================================
            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
                    Version 2, December 2004

 Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>

 Everyone is permitted to copy and distribute verbatim or modified
 copies of this license document, and changing it is allowed as long
 as the name is changed.

            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. You just DO WHAT THE FUCK YOU WANT TO.


================================================
FILE: README.md
================================================
# squark

Rust frontend framework, for web browser and more.

**Currently, we depend on `nightly` channel**

## Design

* Separating runtime definition and implemention
  + `squark` crate has no dependency for specific platform
* Architecture inspired from [Elm](https://elm-lang.org/) and [HyperApp](https://github.com/hyperapp/hyperapp/)
  + Simplicy
  + Elegant
* Supporting futures-0.1
  + reducer can emit task for async work such as fetch resource

## crates

### squark

[![crates.io](https://img.shields.io/crates/v/squark.svg)](https://crates.io/crates/squark)
[![docs.rs](https://docs.rs/squark/badge.svg)](https://docs.rs/squark/*/squark/)

Core crate.

* Pure Rust virtual DOM implemention
* Definition of GUI application
* Definition of runtime to handle diffirence of virtual DOM

### squark-macros

[![crates.io](https://img.shields.io/crates/v/squark-macros.svg)](https://crates.io/crates/squark-macros)
[![docs.rs](https://docs.rs/squark-macros/badge.svg)](https://docs.rs/squark-macros/*/squark_macros/)

It provides macro like JSX for helping writing view.  
Very thanks to [pest](https://github.com/pest-parser/pest) parser.

#### Syntax

```   
view! {
    <button class="some-class" onclick={ |_| Some(Action::Submit) }>
        Button!
    </button>
}
```

We can generate native Rust expression at compile-time.


### squark-web

[![crates.io](https://img.shields.io/crates/v/squark-web.svg)](https://crates.io/crates/squark-web)
[![docs.rs](https://docs.rs/squark-web/badge.svg)](https://docs.rs/squark-web/*/squark_web/)

Runtime implemention for web browser with usinng [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen/).

Here is full example of counter app!

```rust
#![feature(proc_macro_hygiene)]

extern crate squark;
extern crate squark_macros;
extern crate squark_web;
extern crate wasm_bindgen;
extern crate web_sys;

use squark::{App, Runtime, View, Task};
use squark_macros::view;
use squark_web::WebRuntime;
use wasm_bindgen::prelude::*;
use web_sys::window;

#[derive(Clone, Debug, PartialEq)]
struct State {
    count: isize,
}

impl State {
    pub fn new() -> State {
        State { count: 0 }
    }
}

#[derive(Clone, Debug)]
enum Action {
    ChangeCount(isize),
}

#[derive(Clone, Debug)]
struct CounterApp;
impl App for CounterApp {
    type State = State;
    type Action = Action;

    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {
        match action {
            Action::ChangeCount(c) => {
                state.count = c;
            }
        };
        (state, Task::empty())
    }

    fn view(&self, state: State) -> View<Action> {
        let count = state.count;
        view! {
            <div>
                { count.to_string() }
                <button onclick={ move |_| Some(Action::ChangeCount(count.clone() + 1)) }>
                    increment
                </button>
                <button onclick={ move |_| Some(Action::ChangeCount(count - 1)) }>
                    decrement
                </button>
            </div>
        }
    }
}

impl Default for CounterApp {
    fn default() -> CounterApp {
        CounterApp
    }
}

#[wasm_bindgen]
pub fn run() {
    WebRuntime::<CounterApp>::new(
        window()
            .unwrap()
            .document()
            .expect("Failed to get document")
            .query_selector("body")
            .unwrap()
            .unwrap(),
        State::new(),
    )
    .run();
}
```

Project dir is located at [examples/counter](./examples/counter).

There are some other examples available on [examples](./examples), most of them use [rust-webpack-template](https://github.com/rustwasm/rust-webpack-template).  
TodoMVC is working on [https://rail44.github.io/squark/](https://rail44.github.io/squark/).


================================================
FILE: examples/counter/.gitignore
================================================
dist
node_modules
crate/pkg
crate/target
crate/wasm-pack.log


================================================
FILE: examples/counter/README.md
================================================
Just run

```bash
npm run start
```

dev server will come with hot reloading.


================================================
FILE: examples/counter/crate/.crates.toml
================================================
[v1]
"wasm-bindgen-cli 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = ["wasm-bindgen", "wasm-bindgen-test-runner", "wasm2es6js"]


================================================
FILE: examples/counter/crate/.gitignore
================================================

/target/
**/*.rs.bk
Cargo.lock


================================================
FILE: examples/counter/crate/Cargo.toml
================================================
[package]
name = "counter"
version = "0.1.0"
authors = ["Satoshi Amemiya <amemiya@protonmail.com>"]

[workspace]

[lib]
crate-type = ["cdylib"]

[dependencies]
squark = { "path" = "../../../squark" }
squark-macros = { "path" = "../../../squark-macros" }
squark-web = { "path" = "../../../squark-web" }
serde_json = "1.0.13"
wasm-bindgen = "0.2.19"
futures = "0.1.25"
js-sys = "0.3.6"
wasm-bindgen-futures = "0.3.6"

[dependencies.web-sys]
version = "0.3.2"
features = [
  'Document',
  'Window',
]


================================================
FILE: examples/counter/crate/src/lib.rs
================================================
#![feature(proc_macro_hygiene)]

extern crate squark;
extern crate squark_macros;
extern crate squark_web;
extern crate wasm_bindgen;
extern crate web_sys;

use squark::{App, Runtime, View, Task};
use squark_macros::view;
use squark_web::WebRuntime;
use wasm_bindgen::prelude::*;
use web_sys::window;

#[derive(Clone, Debug, PartialEq)]
struct State {
    count: isize,
}

impl State {
    pub fn new() -> State {
        State { count: 0 }
    }
}

#[derive(Clone, Debug)]
enum Action {
    ChangeCount(isize),
}

#[derive(Clone, Debug)]
struct CounterApp;
impl App for CounterApp {
    type State = State;
    type Action = Action;

    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {
        match action {
            Action::ChangeCount(c) => {
                state.count = c;
            }
        };
        (state, Task::empty())
    }

    fn view(&self, state: State) -> View<Action> {
        let count = state.count;
        view! {
            <div>
                { count.to_string() }
                <button onclick={ move |_| Some(Action::ChangeCount(count.clone() + 1)) }>
                    increment
                </button>
                <button onclick={ move |_| Some(Action::ChangeCount(count - 1)) }>
                    decrement
                </button>
            </div>
        }
    }
}

impl Default for CounterApp {
    fn default() -> CounterApp {
        CounterApp
    }
}

#[wasm_bindgen]
pub fn run() {
    WebRuntime::<CounterApp>::new(
        window()
            .unwrap()
            .document()
            .expect("Failed to get document")
            .query_selector("body")
            .unwrap()
            .unwrap(),
        State::new(),
    )
    .run();
}


================================================
FILE: examples/counter/index.html
================================================
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <script src='./dist/bundle.js'></script>
    </body>
</html>


================================================
FILE: examples/counter/js/index.js
================================================
import("../crate/pkg").then(module => {
  module.run();
});


================================================
FILE: examples/counter/package.json
================================================
{
  "scripts": {
    "start": "webpack-dev-server -d",
    "build": "webpack --mode none"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "^0.2.0",
    "webpack": "^4.28.1",
    "webpack-cli": "^3.2.1",
    "webpack-dev-server": "^3.1.14"
  }
}


================================================
FILE: examples/counter/webpack.config.js
================================================
const path = require("path");
const dist = path.resolve(__dirname, "dist");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
  entry: "./js/index.js",
  output: {
    publicPath: "dist/",
    path: dist,
    filename: "bundle.js"
  },
  plugins: [
    new WasmPackPlugin({
      crateDirectory: path.resolve(__dirname, "crate"),
    }),
  ],
  devServer: {
    host: '0.0.0.0',
  },
};


================================================
FILE: examples/todomvc/.gitignore
================================================
dist
node_modules
crate/pkg
crate/target
crate/wasm-pack.log


================================================
FILE: examples/todomvc/README.md
================================================
Just run

```bash
npm run start
```

dev server will come with hot reloading.


================================================
FILE: examples/todomvc/crate/.crates.toml
================================================
[v1]
"wasm-bindgen-cli 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = ["wasm-bindgen", "wasm-bindgen-test-runner", "wasm2es6js"]


================================================
FILE: examples/todomvc/crate/.gitignore
================================================

/target/
**/*.rs.bk
Cargo.lock


================================================
FILE: examples/todomvc/crate/Cargo.toml
================================================
[package]
name = "todomvc"
version = "0.1.0"
authors = ["Satoshi Amemiya <amemiya@protonmail.com>"]

[workspace]

[lib]
crate-type = ["cdylib"]

[dependencies]
squark = { "path" = "../../../squark" }
squark-macros = { "path" = "../../../squark-macros" }
squark-web = { "path" = "../../../squark-web" }
serde_json = "1.0.13"
wasm-bindgen = "0.2.29"

[dependencies.web-sys]
version = "0.3.2"
features = [
  'Document',
  'Window',
]


================================================
FILE: examples/todomvc/crate/src/lib.rs
================================================
#![feature(proc_macro_hygiene)]

extern crate serde_json;
extern crate squark;
extern crate squark_macros;
extern crate squark_web;
extern crate wasm_bindgen;
extern crate web_sys;

use squark::{uuid, App, Child, HandlerArg, Runtime, View, Task};
use squark_macros::view;
use squark_web::WebRuntime;
use std::iter::FromIterator;
use wasm_bindgen::prelude::*;
use web_sys::window;

#[derive(Clone, Hash, Debug, PartialEq)]
enum Visibility {
    All,
    Active,
    Completed,
}

impl ToString for Visibility {
    fn to_string(&self) -> String {
        match self {
            Visibility::All => "All".to_string(),
            Visibility::Active => "Active".to_string(),
            Visibility::Completed => "Completed".to_string(),
        }
    }
}

impl Visibility {
    pub fn view(&self, selected: bool) -> View<Action> {
        let this = self.clone();
        let class = if selected { "selected" } else { "" };
        view! {
            <li>
                <a class={ class } style="cursor: pointer" onclick={ move |_| { Some(Action::ChangeVisibility(this.clone())) } }>
                    { self.to_string() }
                </a>
            </li>
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
struct Entry {
    id: String,
    description: String,
    completed: bool,
}

impl Entry {
    pub fn new(description: String) -> Entry {
        Entry {
            id: uuid(),
            description,
            completed: false,
        }
    }

    pub fn should_display(&self, visibility: &Visibility) -> bool {
        match visibility {
            Visibility::All => true,
            Visibility::Active => !self.completed,
            Visibility::Completed => self.completed,
        }
    }

    pub fn view(&self, i: usize, editing: bool) -> View<Action> {
        let completed = self.completed;
        let mut class = vec![];
        if completed {
            class.push("completed");
        }
        if editing {
            class.push("editing");
        }

        view! {
            <li key={ self.id.clone() } class={ class.join(" ") }>
                {
                    if editing {
                        let id = format!("edit-{}", i);
                        view! {
                            <input
                                id={ id.clone() }
                                class="edit"
                                type="text"
                                value={ self.description.clone() }
                                oninput={ |v| match v {
                                    HandlerArg::String(v) => Some(Action::UpdateEntry(v)),
                                    _ => None,
                                } }
                                onkeydown={ |v| match v {
                                    HandlerArg::String(ref v) if v.as_str() == "Enter" => {
                                        Some(Action::EndEditing)
                                    }
                                    _ => None,
                                } }
                                onblur={ move |_| Some(Action::EndEditing) } />
                        }
                    } else {
                        view! {
                            <div class="view">
                                <input
                                    class="toggle"
                                    type="checkbox"
                                    checked={ completed }
                                    onclick={
                                        move |_| {
                                            Some(Action::Check(i, !completed))
                                        }
                                    }/>
                                <label ondblclick={
                                    move |_| { Some(Action::EditEntry(i))
                                    }
                                }>
                                    { self.description.clone() }
                                </label>
                                <button class="destroy" onclick={ move |_| { Some(Action::Remove(i)) } } />
                            </div>
                        }
                    }
                }
            </li>
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
struct State {
    entries: Vec<Entry>,
    field: String,
    editing: Option<usize>,
    visibility: Visibility,
}

impl State {
    pub fn new() -> State {
        State {
            entries: vec![],
            field: "".to_string(),
            editing: None,
            visibility: Visibility::All,
        }
    }

    pub fn has_completed(&self) -> bool {
        self.entries.iter().any(|e| e.completed)
    }

    pub fn not_completed_count(&self) -> usize {
        self.entries.len() - self.completed_count()
    }

    pub fn completed_count(&self) -> usize {
        self.entries.iter().filter(|e| e.completed).count()
    }

    pub fn is_all_completed(&self) -> bool {
        self.completed_count() == self.entries.len()
    }
}

#[derive(Clone, Debug)]
enum Action {
    UpdateField(String),
    EditEntry(usize),
    UpdateEntry(String),
    EndEditing,
    Add,
    Remove(usize),
    RemoveComplete,
    Check(usize, bool),
    CheckAll(bool),
    ChangeVisibility(Visibility),
}

fn header_view(state: &State) -> View<Action> {
    view! {
        <header class="header">
            <h1>todos</h1>
            <input
                class="new-todo"
                placeholder="What needs to be done?"
                value={ state.field.clone() }
                oninput={ |v| match v {
                    HandlerArg::String(v) => Some(Action::UpdateField(v)),
                    _ => None,
                } }
                onkeydown={ |v| match v {
                    HandlerArg::String(ref v) if v.as_str() == "Enter" => Some(Action::Add),
                    _ => None,
                } } />
        </header>
    }
}

fn main_view(state: &State) -> View<Action> {
    let is_all_completed = state.is_all_completed();
    view! {
        <section class="main">
            {
                if state.entries.len() > 0 {
                    view! {
                        <span>
                            <input
                                class="toggle-all"
                                type="checkbox"
                                checked={ is_all_completed }
                                onclick={
                                    move |_| {
                                        Some(Action::CheckAll(!is_all_completed))
                                    }
                                } />
                        </span>
                    }
                } else {
                    ().into()
                }

            }
            <ul class="todo-list" type="checkbox">
                {
                    Child::from_iter(state
                        .entries
                        .iter()
                        .enumerate()
                        .filter(|&(_, e)| e.should_display(&state.visibility))
                        .map(|(i, e)| {
                            let is_editing = state.editing.as_ref().map_or(false, |at| &i == at);
                            e.view(i, is_editing)
                        }))
                }
            </ul>
        </section>
    }
}

fn footer_view(state: &State) -> View<Action> {
    if state.entries.is_empty() {
        return ().into();
    }
    view! {
        <footer class="footer">
            <span class="todo-count">
                <strong>
                    { state.not_completed_count().to_string() }
                </strong>
                item(s) left
            </span>
            <ul class="filters">
                {
                    Child::from_iter(
                        vec![Visibility::All, Visibility::Active, Visibility::Completed]
                            .into_iter()
                            .map(|v| v.view(v == state.visibility))
                    )
                }
            </ul>
            {
                if state.has_completed() {
                    view! {
                        <button class="clear-completed" onclick={ move |_| { Some(Action::RemoveComplete) } }>
                            Clear completed
                        </button>
                    }
                } else {
                    ().into()
                }
            }
        </footer>
    }
}

#[derive(Clone, Debug)]
struct TodoApp;
impl App for TodoApp {
    type State = State;
    type Action = Action;

    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {
        match action {
            Action::Add => {
                if state.field.as_str() != "" {
                    let entry = Entry::new(state.field);
                    state.entries.push(entry);
                    state.field = "".to_string();
                }
            }
            Action::UpdateField(s) => {
                state.field = s;
            }
            Action::EndEditing => {
                if let Some(i) = state.editing {
                    if state.entries[i].description.as_str() == "" {
                        state.entries.remove(i);
                    }
                    state.editing = None;
                }
            }
            Action::UpdateEntry(s) => {
                if let Some(i) = state.editing {
                    state.entries[i].description = s;
                }
            }
            Action::CheckAll(b) => {
                for mut entry in &mut state.entries {
                    entry.completed = b;
                }
            }
            Action::Check(at, b) => {
                state.entries[at].completed = b;
            }
            Action::Remove(at) => {
                state.entries.remove(at);
            }
            Action::RemoveComplete => {
                let entries = state.entries.drain(..).filter(|e| !e.completed).collect();
                state.entries = entries;
            }
            Action::EditEntry(i) => {
                state.editing = Some(i);
            }
            Action::ChangeVisibility(v) => {
                state.visibility = v;
            }
        };
        (state, Task::empty())
    }

    fn view(&self, state: State) -> View<Action> {
        view! {
            <div>
                { header_view(&state) }
                { main_view(&state) }
                { footer_view(&state) }
            </div>
        }
    }
}

impl Default for TodoApp {
    fn default() -> TodoApp {
        TodoApp
    }
}

#[wasm_bindgen]
pub fn run() {
    WebRuntime::<TodoApp>::new(
        window()
            .unwrap()
            .document()
            .unwrap()
            .query_selector("#container")
            .unwrap()
            .unwrap(),
        State::new(),
    )
    .run();
}


================================================
FILE: examples/todomvc/index.html
================================================
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>squark-web • TodoMVC</title>
        <link rel="stylesheet" href="./styles.css">
    </head>
    <body>
        <div class="todomvc-wrapper">
            <section id="container" class="todoapp">
            </section>
            <footer class="info">
                <p>Double-click to edit a todo</p>
                <p>Written by <a href="https://github.com/rail44/" target="_blank">Satoshi Amemiya</a></p>
                <p>Part of <a href="http://todomvc.com/" target="_blank">TodoMVC</a></p>
            </footer>
        </div>
        <script src='./dist/bundle.js'></script>
    </body>
</html>


================================================
FILE: examples/todomvc/js/index.js
================================================
import("../crate/pkg").then(module => {
  module.run();
});


================================================
FILE: examples/todomvc/package.json
================================================
{
  "scripts": {
    "start": "webpack-dev-server -d",
    "build": "webpack --mode none"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "^0.2.0",
    "webpack": "^4.28.1",
    "webpack-cli": "^3.2.1",
    "webpack-dev-server": "^3.1.14"
  }
}


================================================
FILE: examples/todomvc/styles.css
================================================
/* Source: https://github.com/evancz/elm-todomvc/blob/master/style.css */

html,
body {
    margin: 0;
    padding: 0;
}

.todomvc-wrapper {
    visibility: visible !important;
}

button {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font-size: 100%;
    vertical-align: baseline;
    font-family: inherit;
    font-weight: inherit;
    color: inherit;
    -webkit-appearance: none;
    appearance: none;
    -webkit-font-smoothing: antialiased;
    -moz-font-smoothing: antialiased;
    font-smoothing: antialiased;
}

body {
    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
    line-height: 1.4em;
    background: #f5f5f5;
    color: #4d4d4d;
    min-width: 230px;
    max-width: 550px;
    margin: 0 auto;
    -webkit-font-smoothing: antialiased;
    -moz-font-smoothing: antialiased;
    font-smoothing: antialiased;
    font-weight: 300;
}

button,
input[type="checkbox"] {
    outline: none;
}

.hidden {
    display: none;
}

.todoapp {
    background: #fff;
    margin: 130px 0 40px 0;
    position: relative;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
                0 25px 50px 0 rgba(0, 0, 0, 0.1);
}

.todoapp input::-webkit-input-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}

.todoapp input::-moz-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}

.todoapp input::input-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}

.todoapp h1 {
    position: absolute;
    top: -155px;
    width: 100%;
    font-size: 100px;
    font-weight: 100;
    text-align: center;
    color: rgba(175, 47, 47, 0.15);
    -webkit-text-rendering: optimizeLegibility;
    -moz-text-rendering: optimizeLegibility;
    text-rendering: optimizeLegibility;
}

.new-todo,
.edit {
    position: relative;
    margin: 0;
    width: 100%;
    font-size: 24px;
    font-family: inherit;
    font-weight: inherit;
    line-height: 1.4em;
    border: 0;
    outline: none;
    color: inherit;
    padding: 6px;
    border: 1px solid #999;
    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    -moz-font-smoothing: antialiased;
    font-smoothing: antialiased;
}

.new-todo {
    padding: 16px 16px 16px 60px;
    border: none;
    background: rgba(0, 0, 0, 0.003);
    box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}

.main {
    position: relative;
    z-index: 2;
    border-top: 1px solid #e6e6e6;
}

label[for='toggle-all'] {
    display: none;
}

.toggle-all {
    position: absolute;
    top: -55px;
    left: -12px;
    width: 60px;
    height: 34px;
    text-align: center;
    border: none; /* Mobile Safari */
}

.toggle-all:before {
    content: '❯';
    font-size: 22px;
    color: #e6e6e6;
    padding: 10px 27px 10px 27px;
}

.toggle-all:checked:before {
    color: #737373;
}

.todo-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.todo-list li {
    position: relative;
    font-size: 24px;
    border-bottom: 1px solid #ededed;
}

.todo-list li:last-child {
    border-bottom: none;
}

.todo-list li.editing {
    border-bottom: none;
    padding: 0;
}

.todo-list li.editing .edit {
    display: block;
    width: 506px;
    padding: 13px 17px 12px 17px;
    margin: 0 0 0 43px;
}

.todo-list li.editing .view {
    display: none;
}

.todo-list li .toggle {
    text-align: center;
    width: 40px;
    /* auto, since non-WebKit browsers doesn't support input styling */
    height: auto;
    position: absolute;
    top: 0;
    bottom: 0;
    margin: auto 0;
    border: none; /* Mobile Safari */
    -webkit-appearance: none;
    appearance: none;
}

.todo-list li .toggle:after {
    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}

.todo-list li .toggle:checked:after {
    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}

.todo-list li label {
    white-space: pre-line;
    word-break: break-all;
    padding: 15px 60px 15px 15px;
    margin-left: 45px;
    display: block;
    line-height: 1.2;
    transition: color 0.4s;
}

.todo-list li.completed label {
    color: #d9d9d9;
    text-decoration: line-through;
}

.todo-list li .destroy {
    display: none;
    position: absolute;
    top: 0;
    right: 10px;
    bottom: 0;
    width: 40px;
    height: 40px;
    margin: auto 0;
    font-size: 30px;
    color: #cc9a9a;
    margin-bottom: 11px;
    transition: color 0.2s ease-out;
}

.todo-list li .destroy:hover {
    color: #af5b5e;
}

.todo-list li .destroy:after {
    content: '×';
}

.todo-list li:hover .destroy {
    display: block;
}

.todo-list li .edit {
    display: none;
}

.todo-list li.editing:last-child {
    margin-bottom: -1px;
}

.footer {
    color: #777;
    padding: 10px 15px;
    height: 20px;
    text-align: center;
    border-top: 1px solid #e6e6e6;
}

.footer:before {
    content: '';
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 50px;
    overflow: hidden;
    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
                0 8px 0 -3px #f6f6f6,
                0 9px 1px -3px rgba(0, 0, 0, 0.2),
                0 16px 0 -6px #f6f6f6,
                0 17px 2px -6px rgba(0, 0, 0, 0.2);
}

.todo-count {
    float: left;
    text-align: left;
}

.todo-count strong {
    font-weight: 300;
}

.filters {
    margin: 0;
    padding: 0;
    list-style: none;
    position: absolute;
    right: 0;
    left: 0;
}

.filters li {
    display: inline;
}

.filters li a {
    color: inherit;
    margin: 3px;
    padding: 3px 7px;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: 3px;
}

.filters li a.selected,
.filters li a:hover {
    border-color: rgba(175, 47, 47, 0.1);
}

.filters li a.selected {
    border-color: rgba(175, 47, 47, 0.2);
}

.clear-completed,
html .clear-completed:active {
    float: right;
    position: relative;
    line-height: 20px;
    text-decoration: none;
    cursor: pointer;
    position: relative;
}

.clear-completed:hover {
    text-decoration: underline;
}

.info {
    margin: 65px auto 0;
    color: #bfbfbf;
    font-size: 10px;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
    text-align: center;
}

.info p {
    line-height: 1;
}

.info a {
    color: inherit;
    text-decoration: none;
    font-weight: 400;
}

.info a:hover {
    text-decoration: underline;
}

/*
    Hack to remove background from Mobile Safari.
    Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
    .toggle-all,
    .todo-list li .toggle {
        background: none;
    }

    .todo-list li .toggle {
        height: 40px;
    }

    .toggle-all {
        -webkit-transform: rotate(90deg);
        transform: rotate(90deg);
        -webkit-appearance: none;
        appearance: none;
    }
}

@media (max-width: 430px) {
    .footer {
        height: 50px;
    }

    .filters {
        bottom: 10px;
    }
}



================================================
FILE: examples/todomvc/webpack.config.js
================================================
const path = require("path");
const dist = path.resolve(__dirname, "dist");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
  entry: "./js/index.js",
  output: {
    publicPath: "dist/",
    path: dist,
    filename: "bundle.js"
  },
  plugins: [
    new WasmPackPlugin({
      crateDirectory: path.resolve(__dirname, "crate"),
    }),
  ],
  devServer: {
    host: '0.0.0.0',
  },
};


================================================
FILE: examples/with_task/.gitignore
================================================
dist
node_modules
crate/pkg
crate/target
crate/wasm-pack.log


================================================
FILE: examples/with_task/README.md
================================================
Just run

```bash
npm run start
```

dev server will come with hot reloading.


================================================
FILE: examples/with_task/crate/.gitignore
================================================

/target/
**/*.rs.bk
Cargo.lock


================================================
FILE: examples/with_task/crate/Cargo.toml
================================================
[package]
name = "with_task"
version = "0.1.0"
authors = ["Satoshi Amemiya <amemiya@protonmail.com>"]

[workspace]

[lib]
crate-type = ["cdylib"]

[dependencies]
squark = { "path" = "../../../squark" }
squark-macros = { "path" = "../../../squark-macros" }
squark-web = { "path" = "../../../squark-web" }
serde_json = "1.0.13"
wasm-bindgen = "0.2.19"
futures = "0.1.25"
js-sys = "0.3.6"
wasm-bindgen-futures = "0.3.6"

[dependencies.web-sys]
version = "0.3.2"
features = [
  'Document',
  'Window',
]


================================================
FILE: examples/with_task/crate/src/lib.rs
================================================
#![feature(proc_macro_hygiene)]
extern crate squark;
extern crate squark_macros;
extern crate squark_web;
extern crate wasm_bindgen;
extern crate wasm_bindgen_futures;
extern crate web_sys;
extern crate js_sys;
extern crate futures;

use squark::{App, Runtime, View, Task};
use squark_macros::view;
use squark_web::WebRuntime;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::window;
use js_sys::Promise;
use futures::prelude::*;

#[derive(Clone, Debug, PartialEq)]
struct State {
    count: isize,
}

impl State {
    pub fn new() -> State {
        State { count: 0 }
    }
}

#[derive(Clone, Debug)]
enum Action {
    Increment,
    Decrement,
    Timeout,
}

#[derive(Clone, Debug)]
struct CounterApp;
impl App for CounterApp {
    type State = State;
    type Action = Action;

    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {
        let mut task = Task::empty();
        match action {
            Action::Increment => {
                state.count += 1;
            }
            Action::Decrement => {
                state.count -= 1;
            }
            Action::Timeout => {
                let p = Promise::new(&mut move |resolve, _| {
                    let closure = Closure::wrap(Box::new(move |_: JsValue| {
                        resolve.call0(&JsValue::null()).unwrap();
                    }) as Box<FnMut(_)>);
                    window().unwrap().set_timeout_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 1000).unwrap();
                    closure.forget();
                });
                let future = JsFuture::from(p)
                    .map(move |_| {
                        Action::Increment
                    })
                    .map_err(|e| panic!("delay errored; err={:?}", e));
                task.push(Box::new(future));
            }
        };
        (state, task)
    }

    fn view(&self, state: State) -> View<Action> {
        let count = state.count;
        view! {
            <div>
                { count.to_string() }
                <button onclick={ move |_| Some(Action::Increment) }>
                    increment
                </button>
                <button onclick={ move |_| Some(Action::Decrement) }>
                    decrement
                </button>
                <button onclick={ move |_| Some(Action::Timeout) }>
                    timeout
                </button>
            </div>
        }
    }
}

impl Default for CounterApp {
    fn default() -> CounterApp {
        CounterApp
    }
}

#[wasm_bindgen]
pub fn run() {
    WebRuntime::<CounterApp>::new(
        window()
            .unwrap()
            .document()
            .expect("Failed to get document")
            .query_selector("body")
            .unwrap()
            .unwrap(),
        State::new(),
    )
    .run();
}


================================================
FILE: examples/with_task/index.html
================================================
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <script src='./dist/bundle.js'></script>
    </body>
</html>


================================================
FILE: examples/with_task/js/index.js
================================================
import("../crate/pkg").then(module => {
  module.run();
});


================================================
FILE: examples/with_task/package.json
================================================
{
  "scripts": {
    "start": "webpack-dev-server -d",
    "build": "webpack --mode none"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "^0.2.0",
    "webpack": "^4.28.1",
    "webpack-cli": "^3.2.1",
    "webpack-dev-server": "^3.1.14"
  }
}


================================================
FILE: examples/with_task/webpack.config.js
================================================
const path = require("path");
const dist = path.resolve(__dirname, "dist");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
  entry: "./js/index.js",
  output: {
    publicPath: "dist/",
    path: dist,
    filename: "bundle.js"
  },
  plugins: [
    new WasmPackPlugin({
      crateDirectory: path.resolve(__dirname, "crate"),
    }),
  ],
  devServer: {
    host: '0.0.0.0',
  },
};


================================================
FILE: renovate.json
================================================
{
  "extends": [
    "config:base"
  ]
}


================================================
FILE: squark/.gitignore
================================================

/target/
**/*.rs.bk
Cargo.lock


================================================
FILE: squark/Cargo.toml
================================================
[package]
name = "squark"
version = "0.7.1"
authors = ["Satoshi Amemiya <amemiya@protonmail.com>"]
repository = "https://github.com/rail44/squark"
homepage = "https://github.com/rail44/squark"
license = "WTFPL"
readme = "README.md"
keywords = ["web", "asmjs", "webasm", "javascript"]
categories = ["gui", "web-programming"]
description = "Virtual DOM implemention and application definition inspired from HyperApp"
edition = "2018"

[dependencies]
uuid = "0.7.4"
serde_json = "1.0.41"
rand = { version = "0.7.2", features = [ "wasm-bindgen" ] }
rustc-hash = "1.0.1"
futures = "0.1.29"
serde = "1.0.101"


================================================
FILE: squark/src/lib.rs
================================================
use rand::prelude::*;
use std::cell::{Cell, RefCell};
use std::fmt::Debug;
use rustc_hash::FxHashMap;
use std::rc::Rc;
use futures::Future;
use serde::Serialize;

mod vdom;

pub use crate::vdom::{Node, Element, Diff, View, HandlerArg, AttributeValue, Child};
use crate::vdom::{HandlerFunction, HandlerMap};

thread_local! {
    static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
}

pub trait App: 'static + Clone + Default {
    type State: Clone + Debug + PartialEq + 'static;
    type Action: Clone + Debug + 'static;

    fn reducer(&self, state: Self::State, action: Self::Action) -> (Self::State, Task<Self::Action>);

    fn view(&self, state: Self::State) -> View<Self::Action>;
}

pub fn handler<A, F>(f: F) -> (String, HandlerFunction<A>)
where
    F: Fn(HandlerArg) -> Option<A> + 'static,
{
    (uuid(), Box::new(f))
}

#[derive(Clone)]
pub struct Env<A: App> {
    app: A,
    state: Rc<RefCell<A::State>>,
    node: Rc<RefCell<Node>>,
    handler_map: Rc<RefCell<HandlerMap<A::Action>>>,
    scheduled: Rc<Cell<bool>>,
}

impl<A: App> Env<A> {
    pub fn new(state: A::State) -> Env<A> {
        Env {
            app: A::default(),
            state: Rc::new(RefCell::new(state)),
            node: Rc::new(RefCell::new(Node::Null)),
            handler_map: Rc::new(RefCell::new(FxHashMap::default())),
            scheduled: Rc::new(Cell::new(false)),
        }
    }

    fn get_state(&self) -> A::State {
        self.state.borrow().to_owned()
    }

    fn set_state(&self, state: A::State) {
        *self.state.borrow_mut() = state;
    }

    fn get_node(&self) -> Node {
        self.node.borrow().to_owned()
    }

    fn set_node(&self, node: Node) {
        *self.node.borrow_mut() = node;
    }

    fn pop_handler(&self, id: &str) -> Option<HandlerFunction<A::Action>> {
        self.handler_map.borrow_mut().remove(id)
    }
}

pub struct Task<A>(Vec<Box<Future<Item = A, Error = ()>>>);

impl<A> Default for Task<A> {
    fn default() -> Self {
        Task(vec![])
    }
}

impl<A> Task<A> {
    pub fn empty() -> Self {
        Self::default()
    }

    pub fn into_futures(self) -> Vec<Box<Future<Item = A, Error = ()>>> {
        self.0
    }

    pub fn push(&mut self, future: Box<Future<Item = A, Error = ()>>) {
        self.0.push(future);
    }
}

pub trait Runtime<A: App>: Clone + 'static {
    fn get_env<'a>(&'a self) -> &'a Env<A>;

    fn handle_diff(&self, diff: Diff);

    fn handle_future<T: Serialize + 'static, E: Serialize + 'static>(&self, future: Box<Future<Item = T, Error = E>>);

    fn schedule_render(&self);

    fn run(&self) {
        self.run_with_task(Task::empty());
    }

    fn run_with_task(&self, task: Task<A::Action>) {
        for future in task.into_futures() {
            self.emit_future(future);
        }

        let env = self.get_env();
        env.scheduled.set(false);
        let mut old_node = env.get_node();
        let view = env.app.view(env.get_state());
        *env.handler_map.borrow_mut() = view.handler_map;
        if let Some(diff) = Node::diff(&mut old_node, &view.node, &mut 0) {
            env.set_node(view.node);
            self.handle_diff(diff);
        }
    }

    fn on_action(&self, action: A::Action) {
        let env = self.get_env();

        let old_state = env.get_state();
        let (new_state, task) = env.app.reducer(old_state.to_owned(), action);
        for future in task.into_futures() {
            self.emit_future(future);
        }
        self.set_state(new_state);
    }

    fn set_state(&self, new_state: A::State) {
        let env = self.get_env();
        let old_state = env.get_state();
        if old_state == new_state {
            return;
        }
        env.set_state(new_state);
        if env.scheduled.get() {
            return;
        }
        env.scheduled.set(true);
        self.schedule_render();
    }

    fn emit_future(&self, task: Box<Future<Item = A::Action, Error = ()>>) {
        let this = self.clone();
        self.handle_future(Box::new(task.map(move |a| {
            this.on_action(a);
        })));
    }

    fn pop_handler(&self, id: &str) -> Option<Box<Fn(HandlerArg)>> {
        let env = self.get_env();
        let handler = env.pop_handler(id)?;
        let this = self.to_owned();
        let f = move |arg: HandlerArg| {
            match handler(arg) {
                Some(a) => this.on_action(a),
                None => return,
            };
        };
        Some(Box::new(f))
    }
}

pub fn uuid() -> String {
    RNG.with(|rng| uuid::Builder::from_bytes(rng.borrow_mut().gen()))
        .set_variant(uuid::Variant::RFC4122)
        .set_version(uuid::Version::Random)
        .build()
        .to_string()
}


================================================
FILE: squark/src/vdom.rs
================================================
use rustc_hash::{FxHashMap, FxHashSet};
use std::iter::FromIterator;

pub use serde_json::Value as HandlerArg;

type Attribute = (String, AttributeValue);

fn diff_attributes(a: &mut Vec<Attribute>, b: &[Attribute]) -> Vec<Diff> {
    let mut result = vec![];

    let mut old_map = FxHashMap::<String, AttributeValue>::from_iter(a.drain(..));
    for &(ref new_key, ref new_val) in b {
        match old_map.remove(new_key) {
            Some(old_val) => {
                if &old_val != new_val {
                    result.push(Diff::SetAttribute(new_key.to_owned(), new_val.to_owned()))
                }
            }
            None => result.push(Diff::SetAttribute(new_key.to_owned(), new_val.to_owned())),
        }
    }

    for (old_key, _) in old_map.drain() {
        result.push(Diff::RemoveAttribute(old_key));
    }

    result
}

pub(crate) type HandlerFunction<A> = Box<Fn(HandlerArg) -> Option<A>>;
type Handler = (String, String);

fn diff_handlers(a: &mut Vec<Handler>, b: &[Handler]) -> Vec<Diff> {
    let mut result = vec![];

    let mut old_map = FxHashMap::<String, String>::from_iter(a.drain(..));
    for &(ref new_key, ref new_id) in b {
        old_map.remove(new_key);
        result.push(Diff::SetHandler(new_key.to_owned(), new_id.to_owned()));
    }

    for (old_key, old_id) in old_map.drain() {
        result.push(Diff::RemoveHandler(old_key, old_id));
    }

    result
}

#[derive(Clone, Debug)]
pub enum Node {
    Text(String),
    Element(Element),
    Null,
}

impl Node {
    pub(crate) fn diff(a: &mut Node, b: &Node, i: &mut usize) -> Option<Diff> {
        match (a, b) {
            (&mut Node::Element(ref mut a), &Node::Element(ref b)) => {
                match Element::diff(a, b, *i) {
                    Some(diff) => Some(diff),
                    None => None,
                }
            }
            (&mut Node::Text(ref mut text_a), &Node::Text(ref text_b)) => {
                if text_a == text_b {
                    return None;
                }
                Some(Diff::ReplaceChild(*i, b.to_owned()))
            }
            (&mut Node::Null, &Node::Null) => None,
            (&mut Node::Null, _) => Some(Diff::AddChild(*i, b.to_owned())),
            (_, &Node::Null) => Some(Diff::RemoveChild(*i)),
            _ => Some(Diff::ReplaceChild(*i, b.to_owned())),
        }
    }

    fn get_key(&self) -> Option<String> {
        match self {
            Node::Element(ref el) => el.get_key(),
            _ => None,
        }
    }
}

fn get_nodelist_key_set(nodelist: &[Node]) -> FxHashSet<String> {
    FxHashSet::from_iter(nodelist.iter().filter_map(|c| c.get_key()))
}

fn diff_children(a: &mut Vec<Node>, b: &[Node], i: &mut usize) -> Vec<Diff> {
    let mut result = vec![];
    let b_key_set = get_nodelist_key_set(b);
    let survived = a
        .drain(..)
        .filter(|c| match c.get_key() {
            Some(k) => {
                let is_survived = b_key_set.contains(&k);
                if !is_survived {
                    result.push(Diff::RemoveChild(*i));
                    return false;
                }
                *i += 1;
                true
            }
            None => {
                *i += 1;
                true
            }
        })
        .collect();
    *a = survived;

    let mut i = 0;
    a.reverse();
    for new_child in b.iter() {
        match a.pop() {
            None => {
                result.push(Diff::AddChild(i, new_child.to_owned()));
                i += 1;
            }
            Some(mut old_child) => {
                if let Some(diff) = Node::diff(&mut old_child, new_child, &mut i) {
                    result.push(diff.to_owned());
                    if let Diff::RemoveChild(_) = diff {
                        continue;
                    }
                }
            }
        }
        i += 1;
    }

    for _ in a.iter() {
        result.push(Diff::RemoveChild(i));
    }

    result
}

#[derive(Clone, Debug)]
pub struct Element {
    name: String,
    attributes: Vec<Attribute>,
    handlers: Vec<Handler>,
    children: Vec<Node>,
}

impl Element {
    fn new(
        name: String,
        attributes: Vec<Attribute>,
        handlers: Vec<Handler>,
        children: Vec<Node>,
    ) -> Element {
        Element {
            name,
            attributes,
            handlers,
            children,
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn attributes(&self) -> &[Attribute] {
        &self.attributes
    }

    pub fn handlers(&self) -> &[Handler] {
        &self.handlers
    }

    pub fn children(&self) -> &[Node] {
        &self.children
    }

    fn diff(a: &mut Element, b: &Element, i: usize) -> Option<Diff> {
        if let (Some(a_key), Some(b_key)) = (a.get_key(), b.get_key()) {
            if a_key != b_key {
                return Some(Diff::ReplaceChild(i, Node::Element(b.to_owned())));
            }
        }

        if a.name != b.name {
            return Some(Diff::ReplaceChild(i, Node::Element(b.to_owned())));
        }

        let mut result = vec![];

        result.append(&mut diff_attributes(&mut a.attributes, &b.attributes));
        result.append(&mut diff_handlers(&mut a.handlers, &b.handlers));
        result.append(&mut diff_children(&mut a.children, &b.children, &mut 0));

        if result.is_empty() {
            return None;
        }
        Some(Diff::PatchChild(i, result))
    }

    fn get_key(&self) -> Option<String> {
        self.attributes
            .iter()
            .find(|&&(ref k, _)| k == "key")
            .and_then(|&(_, ref v)| match v {
                AttributeValue::String(ref s) => Some(s.to_owned()),
                AttributeValue::Bool(_) => None,
            })
    }
}

#[derive(Debug, Clone)]
pub enum Diff {
    SetAttribute(String, AttributeValue),
    RemoveAttribute(String),
    AddChild(usize, Node),
    ReplaceChild(usize, Node),
    RemoveChild(usize),
    PatchChild(usize, Vec<Diff>),
    SetHandler(String, String),
    RemoveHandler(String, String),
}

#[derive(Clone, Debug, PartialEq)]
pub enum AttributeValue {
    String(String),
    Bool(bool),
}

impl From<String> for AttributeValue {
    fn from(s: String) -> AttributeValue {
        AttributeValue::String(s)
    }
}

impl<'a> From<&'a str> for AttributeValue {
    fn from(s: &'a str) -> AttributeValue {
        AttributeValue::String(s.to_owned())
    }
}

impl From<bool> for AttributeValue {
    fn from(b: bool) -> AttributeValue {
        AttributeValue::Bool(b)
    }
}

pub(crate) type HandlerMap<A> = FxHashMap<String, HandlerFunction<A>>;

pub struct View<A> {
    pub(crate) node: Node,
    pub(crate) handler_map: HandlerMap<A>,
}

pub enum Child<A> {
    View(View<A>),
    ViewList(Vec<View<A>>),
}

impl<A, T> From<T> for Child<A>
where
    T: Into<View<A>> + Sized,
{
    fn from(v: T) -> Child<A> {
        Child::View(v.into())
    }
}

impl<A> FromIterator<View<A>> for Child<A> {
    fn from_iter<I>(iter: I) -> Child<A>
    where
        I: IntoIterator<Item = View<A>>,
    {
        Child::ViewList(iter.into_iter().collect())
    }
}

impl<A> View<A> {
    pub fn new(
        name: String,
        attributes: Vec<Attribute>,
        handlers: Vec<(String, (String, HandlerFunction<A>))>,
        children: Vec<Child<A>>,
    ) -> View<A> {
        let mut handler_map = FxHashMap::default();
        let handlers = handlers
            .into_iter()
            .map(|(kind, (id, f))| {
                let handler = (kind, id.to_owned());
                handler_map.insert(id, f);
                handler
            })
            .collect();

        let mut children_vec = vec![];
        for child in children {
            match child {
                Child::View(v) => {
                    handler_map.extend(v.handler_map);
                    children_vec.push(v.node);
                }
                Child::ViewList(child_vec) => {
                    for v in child_vec {
                        handler_map.extend(v.handler_map);
                        children_vec.push(v.node);
                    }
                }
            }
        }

        View {
            node: Node::Element(Element::new(name, attributes, handlers, children_vec)),
            handler_map,
        }
    }

    pub fn text(s: String) -> View<A> {
        View {
            node: Node::Text(s),
            handler_map: FxHashMap::default(),
        }
    }

    pub fn null() -> View<A> {
        View {
            node: Node::Null,
            handler_map: FxHashMap::default(),
        }
    }
}

impl<A> From<()> for View<A> {
    fn from(_: ()) -> View<A> {
        View::null()
    }
}

impl<A> From<String> for View<A> {
    fn from(s: String) -> View<A> {
        View::text(s)
    }
}

impl<'a, A> From<&'a str> for View<A> {
    fn from(s: &'a str) -> View<A> {
        View::text(s.to_owned())
    }
}

impl<A, T> From<Option<T>> for View<A>
where
    T: Into<View<A>>,
{
    fn from(option: Option<T>) -> View<A> {
        option.map_or_else(View::null, |v| v.into())
    }
}


================================================
FILE: squark-macros/.gitignore
================================================

/target/
**/*.rs.bk
Cargo.lock


================================================
FILE: squark-macros/Cargo.toml
================================================
[package]
name = "squark-macros"
version = "0.7.0"
authors = ["Satoshi Amemiya <amemiya@protonmail.com>"]
repository = "https://github.com/rail44/squark"
homepage = "https://github.com/rail44/squark"
license = "WTFPL"
readme = "README.md"
keywords = ["web", "asmjs", "webasm", "javascript"]
categories = ["gui", "web-programming"]
description = "Macros like JSX to help building Squark application"

[lib]
proc-macro = true

[dependencies]
pest = "2.1.2"
pest_derive = "2.1.0"

[dev-dependencies]
squark = { path = "../squark", version = "0.7.0" }


================================================
FILE: squark-macros/src/lib.rs
================================================
#![crate_type = "proc-macro"]
#![feature(proc_macro_hygiene, proc_macro_quote)]

extern crate pest;
#[macro_use]
extern crate pest_derive;
extern crate proc_macro;

use parser::{Parser as ViewParser, Rule};
use pest::iterators::{Pair, Pairs};
use pest::Parser;
use proc_macro::{quote, Literal, TokenStream, TokenTree};
use std::iter::FromIterator;
use std::str::FromStr;

mod parser {
    #[derive(Parser)]
    #[grammar = "view.pest"]
    pub struct Parser;
}

fn get_token_stream(mut tag_pairs: Pairs<Rule>) -> TokenStream {
    let name = tag_pairs.next().expect("name").as_str();
    let _name = TokenTree::Literal(Literal::string(name));

    let mut attributes = vec![];
    let mut handlers = vec![];

    let vec: Vec<Pair<Rule>> = tag_pairs.next().expect("attributes").into_inner().collect();
    for i in 0..(vec.len() / 2) {
        let j = i * 2;
        let k = &vec[j].as_str();
        let v = &vec[j + 1];

        let _v = match v.as_rule() {
            Rule::embedded => {
                let mut _embedded = TokenStream::from_str(v.as_str()).unwrap();
                quote!($_embedded.into())
            }
            Rule::string => {
                let _v = TokenTree::Literal(Literal::string(v.as_str()));
                quote! { $_v.into() }
            }
            Rule::bool => {
                let _v = TokenStream::from_str(v.as_str()).unwrap();
                quote! { $_v.into() }
            }
            _ => unreachable!(),
        };

        if k.starts_with("on") {
            let (_, k) = k.split_at(2);
            let _k = TokenTree::Literal(Literal::string(k));
            handlers.push(quote! {
                ($_k.to_string(), _squark::handler($_v)),
            });
            continue;
        }

        let _k = TokenTree::Literal(Literal::string(k));
        attributes.push(quote! {
            ($_k.to_string(), $_v),
        });
    }
    let _attributes = TokenStream::from_iter(attributes);
    let _handlers = TokenStream::from_iter(handlers);

    let mut children = vec![];
    if let Some(children_pair) = tag_pairs.next() {
        for p in children_pair.into_inner() {
            let token = match p.as_rule() {
                Rule::tag => {
                    let _tag = get_token_stream(p.into_inner());
                    quote! {
                        _squark::Child::from($_tag),
                    }
                }
                Rule::text => {
                    let _text = TokenTree::Literal(Literal::string(p.as_str()));
                    quote! {
                        $_text.into(),
                    }
                }
                Rule::embedded => {
                    let _embedded = TokenStream::from_str(p.as_str()).unwrap();
                    quote! {
                        {$_embedded}.into(),
                    }
                }
                _ => unreachable!(),
            };
            children.push(token);
        }
    }
    let _children = TokenStream::from_iter(children);

    quote! {
        _squark::View::new(
            $_name.to_string(),
            vec![
                $_attributes
            ],
            vec![
                $_handlers
            ],
            vec![
                $_children
            ]
        )
    }
}

#[proc_macro]
pub fn view(arg: TokenStream) -> TokenStream {
    let s = arg.to_string();
    let mut pairs = ViewParser::parse(Rule::view, &s).unwrap();
    let _token = get_token_stream(pairs.next().unwrap().into_inner());

    quote! {
        {
            extern crate squark as _squark;
            $_token
        }
    }
}


================================================
FILE: squark-macros/src/view.pest
================================================
view = _{ SOI ~ tag ~ EOI }

identifier = _{ ('a'..'z' | 'A'..'Z' | '0'..'9' | "_" | "-")+ }

tag_name = @{ identifier }
tag = { single_tag | pair_tag }

pair_tag = _{ open_tag ~ children ~ close_tag }
single_tag = _{ "<" ~ tag_name ~ attributes ~ "/" ~ ">" }

children = {(tag | embedded_outer | text)*}

open_tag = _{ "<" ~ tag_name ~ attributes ~ ">" }
close_tag = _{ "<" ~ "/" ~ (!">" ~ ANY)+ ~ ">" }

attributes = { attribute* }
attribute = _{ key ~ "=" ~ (string_literal | bool | embedded_outer) }

key = @{ identifier }

string_literal = _{ "\"" ~ string ~ "\"" }
string = @{ (!"\"" ~ ANY)* }

embedded_outer = _{ "{" ~ embedded ~ "}" }
embedded = { (bracketed | (!"}" ~ ANY))* }
bracketed = _{ "{" ~ bracketed_inner ~ "}" }
bracketed_inner = _{ (bracketed | (!"}" ~ ANY))* }

bool = { "true" | "false" }

text = @{ (!("<" | "{") ~ ANY)+ }

newline    = _{ "\n" | "\r\n" }
WHITESPACE = _{ " " | newline }


================================================
FILE: squark-macros/tests/test.rs
================================================
#![feature(test, use_extern_macros, proc_macro_non_items)]

extern crate squark;
extern crate squark_macros;

use squark::View;
use squark_macros::view;

fn v() -> View<()> {
    let not_completed_count = 1234;
    let has_completed = true;
    view! {
        <footer class="footer">
            <h1 class="todo-count">
                <strong>{ not_completed_count.to_string() }</strong>
                 item(s) left
            </h1>
            <br />
            {
                if has_completed {
                    view! {
                        <button class="clear-completed" onclick={ move |_| { Some(()) } } />
                    }
                } else {
                    ().into()
                }
            }
        </footer>
    }
}

#[test]
fn it_works() {
    let v = v();
}


================================================
FILE: squark-web/.gitignore
================================================

/target/
**/*.rs.bk
Cargo.lock


================================================
FILE: squark-web/Cargo.toml
================================================
[package]
name = "squark-web"
version = "0.3.0"
authors = ["Satoshi Amemiya <amemiya@protonmail.com>"]
repository = "https://github.com/rail44/squark"
homepage = "https://github.com/rail44/squark"
license = "WTFPL"
readme = "README.md"
categories = ["gui", "web-programming", "wasm"]
description = "Squark runtime implemiontion for web browser with using wasm-bindgen"
edition = "2018"

[dependencies]
serde_json = "1.0.41"
serde = "1.0.101"
squark = { path = "../squark", version = "0.7.0" }
wasm-bindgen = { version = "0.2.51", features = [ "nightly", "serde-serialize" ] }
js-sys = "0.3.28"
futures = "0.1.29"
wasm-bindgen-futures = "0.4.1"

[dependencies.web-sys]
version = "0.3.28"
features = [
  'Window',
  'Document',
  'DomStringMap',
  'Element',
  'EventTarget',
  'HtmlElement',
  'HtmlInputElement',
  'Node',
  'NodeList',
  'Text',
  'Event',
  'InputEvent',
  'KeyboardEvent',
]


================================================
FILE: squark-web/src/lib.rs
================================================
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use futures::Future;
use wasm_bindgen_futures::future_to_promise;
use squark::{
    uuid,
    App, AttributeValue, Diff, Element as SquarkElement, Env, HandlerArg, Node as SquarkNode,
    Runtime,
};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{window, Document, Element, EventTarget, HtmlElement, Node};
use serde::Serialize;
use serde_json::json;

trait ToHandlerArg: JsCast {
    fn to_handler_arg(self) -> HandlerArg;
}

impl ToHandlerArg for web_sys::Event {
    fn to_handler_arg(self) -> HandlerArg {
        json!{null}
    }
}

impl ToHandlerArg for web_sys::InputEvent {
    fn to_handler_arg(self) -> HandlerArg {
        let ev: web_sys::Event = self.into();
        let target = ev.target().unwrap();
        let js_val: &JsValue = target.as_ref();
        if js_val.is_null() {
            return json!{""};
        }
        let input_el: &web_sys::HtmlInputElement = target.unchecked_ref();
        json!{input_el.value()}
    }
}

impl ToHandlerArg for web_sys::KeyboardEvent {
    fn to_handler_arg(self) -> HandlerArg {
        json!{self.key()}
    }
}

type AttachedMap = HashMap<String, HashMap<String, Closure<Fn(JsValue)>>>;

fn document() -> Document {
    window().unwrap().document().unwrap()
}

fn get_handler_id(el: &HtmlElement) -> Option<String> {
    let id = js_sys::Reflect::get(el.dataset().as_ref(), &"handlerId".into()).unwrap();

    if id.is_undefined() {
        return None;
    }
    Some(id.as_string().unwrap())
}

fn set_handler_id(el: &HtmlElement, id: &str) {
    js_sys::Reflect::set(el.dataset().as_ref(), &"handlerId".into(), &id.into()).unwrap();
}

#[derive(Clone)]
pub struct WebRuntime<A: App> {
    env: Env<A>,
    root: Rc<Element>,
    attached_map: Rc<RefCell<AttachedMap>>,
}

fn insert_at(parent: &Node, i: usize, node: &Node) {
    let ref_node = parent.child_nodes().item(i as u32);
    if ref_node.is_none() {
        parent.append_child(&node).unwrap();
        return;
    }
    parent.insert_before(&node, ref_node.as_ref()).unwrap();
}

fn set_attribute(el: &Element, name: &str, value: &AttributeValue) {
    match value {
        AttributeValue::Bool(b) => {
            js_sys::Reflect::set(el.as_ref(), &name.into(), &(*b).into()).unwrap();
            el.set_attribute(name, &b.to_string()).unwrap();
        }
        AttributeValue::String(s) => {
            js_sys::Reflect::set(el.as_ref(), &name.into(), &s.into()).unwrap();
            el.set_attribute(name, s).unwrap();
        }
    }
}

impl<A: App> WebRuntime<A> {
    pub fn new(root: Element, state: A::State) -> WebRuntime<A> {
        WebRuntime {
            env: Env::new(state),
            root: Rc::new(root),
            attached_map: Rc::new(RefCell::new(AttachedMap::new())),
        }
    }

    fn handle_diff_inner(&self, el: &Element, diff: Diff) {
        match diff {
            Diff::AddChild(i, node) => self.add_child(el, i, node),
            Diff::PatchChild(i, diffs) => {
                let as_node: &Node = el.as_ref();
                let child = as_node.child_nodes().item(i as u32).unwrap();
                for diff in diffs {
                    self.handle_diff_inner(child.unchecked_ref(), diff);
                }
            }
            Diff::ReplaceChild(i, node) => self.replace_child(el, i, node),
            Diff::SetAttribute(name, value) => set_attribute(el, &name, &value),
            Diff::RemoveAttribute(name) => {
                el.remove_attribute(&name).unwrap();
            }
            Diff::RemoveChild(i) => self.remove_child(el.as_ref(), i),
            Diff::SetHandler(name, id) => self.set_handler(el.unchecked_ref(), &name, &id),
            Diff::RemoveHandler(name, _) => {
                let attached = self
                    .attached_map
                    .borrow_mut()
                    .get_mut(&get_handler_id(el.unchecked_ref()).unwrap())
                    .and_then(|inner| inner.remove(&name))
                    .unwrap();
                let html_el: &EventTarget = el.unchecked_ref();
                html_el
                    .remove_event_listener_with_callback(&name, attached.as_ref().unchecked_ref())
                    .unwrap();
            }
        }
    }

    fn replace_at(&self, parent: &Node, i: usize, node: &Node) {
        let current = parent.child_nodes().item(i as u32).unwrap();
        self.remove_attached(&current);
        parent.replace_child(&node, &current).unwrap();
    }

    fn create_element(&self, el: &SquarkElement) -> Element {
        let web_el: Element = document().create_element(el.name()).unwrap();
        for (ref name, ref value) in el.attributes() {
            set_attribute(&web_el, name, value);
        }

        for (ref name, id) in el.handlers() {
            self.set_handler(web_el.unchecked_ref(), name, &id);
        }

        {
            let node: &Node = web_el.as_ref();
            for child in el.children() {
                match child {
                    SquarkNode::Element(el) => {
                        let child = self.create_element(el);
                        node.append_child(child.as_ref()).unwrap();
                    }
                    SquarkNode::Text(s) => {
                        let child = document().create_text_node(s.as_str());
                        node.append_child(child.as_ref()).unwrap();
                    }
                    _ => (),
                };
            }
        }

        web_el
    }

    fn add_child(&self, parent: &Element, i: usize, node: SquarkNode) {
        match node {
            SquarkNode::Element(el) => {
                let child = self.create_element(&el);
                insert_at(parent.as_ref(), i, child.as_ref());
            }
            SquarkNode::Text(s) => {
                let child = document().create_text_node(s.as_str());
                insert_at(parent.as_ref(), i, child.as_ref());
            }
            _ => (),
        };
    }

    fn replace_child(&self, parent: &Element, i: usize, node: SquarkNode) {
        match node {
            SquarkNode::Element(el) => {
                let child = self.create_element(&el);
                self.replace_at(parent.as_ref(), i, child.as_ref());
            }
            SquarkNode::Text(s) => {
                let child = document().create_text_node(s.as_str());
                self.replace_at(parent.as_ref(), i, child.as_ref());
            }
            _ => (),
        };
    }

    fn remove_child(&self, parent: &Node, i: usize) {
        let current = parent.child_nodes().item(i as u32).unwrap();

        self.remove_attached(current.unchecked_ref());
        parent.remove_child(&current).unwrap();
    }

    fn set_handler(&self, el: &Element, name: &str, id: &str) {
        let closure = match name {
            "keydown" => self._set_handler::<web_sys::KeyboardEvent>(el.as_ref(), "keydown", id),
            "input" => self._set_handler::<web_sys::InputEvent>(el.as_ref(), "input", id),
            name => self._set_handler::<web_sys::Event>(el.as_ref(), name, id),
        };

        let handler_id = get_handler_id(el.unchecked_ref()).unwrap_or_else(|| {
            let uuid = uuid();
            set_handler_id(el.unchecked_ref(), &uuid);
            uuid
        });

        let mut map = self.attached_map.borrow_mut();
        let inner = map.entry(handler_id).or_insert_with(HashMap::new);
        if let Some(attached) = inner.remove(name) {
            let target: &EventTarget = el.as_ref();
            target
                .remove_event_listener_with_callback(&name, attached.as_ref().unchecked_ref())
                .unwrap();
        }
        inner.insert(name.to_owned(), closure);
    }

    fn _set_handler<T: ToHandlerArg>(
        &self,
        el: &EventTarget,
        name: &str,
        id: &str,
    ) -> Closure<Fn(JsValue)> {
        let handler = self.pop_handler(id).unwrap();
        let closure = Closure::new(move |ev: JsValue| {
            let ev: T = ev.unchecked_into();
            handler(ev.to_handler_arg());
        });
        el.add_event_listener_with_callback(name, closure.as_ref().unchecked_ref())
            .unwrap();
        closure
    }

    fn remove_attached(&self, el: &Node) {
        if !el.is_instance_of::<Element>() {
            return;
        }

        let el: &Element = el.unchecked_ref();

        let mut map = self.attached_map.borrow_mut();
        if let Some(id) = get_handler_id(el.unchecked_ref()) {
            map.remove(&id);
        }

        let children = el.query_selector_all("[data-has-handler]").unwrap();
        for i in 0..children.length() {
            let child = children.item(i).unwrap();
            if let Some(id) = get_handler_id(child.unchecked_ref()) {
                map.remove(&id);
            }
        }
    }
}

fn nop<T>(_: T) {}

impl<A: App> Runtime<A> for WebRuntime<A> {
    fn get_env<'a>(&'a self) -> &'a Env<A> {
        &self.env
    }

    fn schedule_render(&self) {
        let this = self.clone();
        let closure = Closure::wrap(Box::new(move |_: JsValue| {
            this.run();
        }) as Box<FnMut(_)>);
        window()
            .unwrap()
            .request_animation_frame(closure.as_ref().unchecked_ref())
            .unwrap();
        closure.forget();
    }

    fn handle_diff(&self, diff: Diff) {
        self.handle_diff_inner(&self.root, diff);
    }

    fn handle_future<T: Serialize + 'static, E: Serialize + 'static>(&self, future: Box<Future<Item = T, Error = E>>) {
        let p = future_to_promise(
            future
                .map(|v| JsValue::from_serde(&v).unwrap())
                .map_err(|e| JsValue::from_serde(&e).unwrap())
        );
        let closure = Closure::new(nop);
        p.then(&closure);
        closure.forget();
    }
}
Download .txt
gitextract_c8b3y0t7/

├── .gitignore
├── Cargo.toml
├── LICENCE
├── README.md
├── examples/
│   ├── counter/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── crate/
│   │   │   ├── .crates.toml
│   │   │   ├── .gitignore
│   │   │   ├── Cargo.toml
│   │   │   └── src/
│   │   │       └── lib.rs
│   │   ├── index.html
│   │   ├── js/
│   │   │   └── index.js
│   │   ├── package.json
│   │   └── webpack.config.js
│   ├── todomvc/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── crate/
│   │   │   ├── .crates.toml
│   │   │   ├── .gitignore
│   │   │   ├── Cargo.toml
│   │   │   └── src/
│   │   │       └── lib.rs
│   │   ├── index.html
│   │   ├── js/
│   │   │   └── index.js
│   │   ├── package.json
│   │   ├── styles.css
│   │   └── webpack.config.js
│   └── with_task/
│       ├── .gitignore
│       ├── README.md
│       ├── crate/
│       │   ├── .gitignore
│       │   ├── Cargo.toml
│       │   └── src/
│       │       └── lib.rs
│       ├── index.html
│       ├── js/
│       │   └── index.js
│       ├── package.json
│       └── webpack.config.js
├── renovate.json
├── squark/
│   ├── .gitignore
│   ├── Cargo.toml
│   └── src/
│       ├── lib.rs
│       └── vdom.rs
├── squark-macros/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── src/
│   │   ├── lib.rs
│   │   └── view.pest
│   └── tests/
│       └── test.rs
└── squark-web/
    ├── .gitignore
    ├── Cargo.toml
    └── src/
        └── lib.rs
Download .txt
SYMBOL INDEX (139 symbols across 8 files)

FILE: examples/counter/crate/src/lib.rs
  type State (line 16) | struct State {
    method new (line 21) | pub fn new() -> State {
  type Action (line 27) | enum Action {
  type CounterApp (line 32) | struct CounterApp;
  type State (line 34) | type State = State;
  type Action (line 35) | type Action = Action;
  method reducer (line 37) | fn reducer(&self, mut state: State, action: Action) -> (State, Task<Acti...
  method view (line 46) | fn view(&self, state: State) -> View<Action> {
  method default (line 63) | fn default() -> CounterApp {
  function run (line 69) | pub fn run() {

FILE: examples/todomvc/crate/src/lib.rs
  type Visibility (line 18) | enum Visibility {
    method view (line 35) | pub fn view(&self, selected: bool) -> View<Action> {
  method to_string (line 25) | fn to_string(&self) -> String {
  type Entry (line 49) | struct Entry {
    method new (line 56) | pub fn new(description: String) -> Entry {
    method should_display (line 64) | pub fn should_display(&self, visibility: &Visibility) -> bool {
    method view (line 72) | pub fn view(&self, i: usize, editing: bool) -> View<Action> {
  type State (line 134) | struct State {
    method new (line 142) | pub fn new() -> State {
    method has_completed (line 151) | pub fn has_completed(&self) -> bool {
    method not_completed_count (line 155) | pub fn not_completed_count(&self) -> usize {
    method completed_count (line 159) | pub fn completed_count(&self) -> usize {
    method is_all_completed (line 163) | pub fn is_all_completed(&self) -> bool {
  type Action (line 169) | enum Action {
  function header_view (line 182) | fn header_view(state: &State) -> View<Action> {
  function main_view (line 202) | fn main_view(state: &State) -> View<Action> {
  function footer_view (line 243) | fn footer_view(state: &State) -> View<Action> {
  type TodoApp (line 280) | struct TodoApp;
  type State (line 282) | type State = State;
  type Action (line 283) | type Action = Action;
  method reducer (line 285) | fn reducer(&self, mut state: State, action: Action) -> (State, Task<Acti...
  method view (line 335) | fn view(&self, state: State) -> View<Action> {
  method default (line 347) | fn default() -> TodoApp {
  function run (line 353) | pub fn run() {

FILE: examples/with_task/crate/src/lib.rs
  type State (line 22) | struct State {
    method new (line 27) | pub fn new() -> State {
  type Action (line 33) | enum Action {
  type CounterApp (line 40) | struct CounterApp;
  type State (line 42) | type State = State;
  type Action (line 43) | type Action = Action;
  method reducer (line 45) | fn reducer(&self, mut state: State, action: Action) -> (State, Task<Acti...
  method view (line 73) | fn view(&self, state: State) -> View<Action> {
  method default (line 93) | fn default() -> CounterApp {
  function run (line 99) | pub fn run() {

FILE: squark-macros/src/lib.rs
  type Parser (line 19) | pub struct Parser;
  function get_token_stream (line 22) | fn get_token_stream(mut tag_pairs: Pairs<Rule>) -> TokenStream {
  function view (line 114) | pub fn view(arg: TokenStream) -> TokenStream {

FILE: squark-macros/tests/test.rs
  function v (line 9) | fn v() -> View<()> {
  function it_works (line 33) | fn it_works() {

FILE: squark-web/src/lib.rs
  type ToHandlerArg (line 17) | trait ToHandlerArg: JsCast {
    method to_handler_arg (line 18) | fn to_handler_arg(self) -> HandlerArg;
    method to_handler_arg (line 22) | fn to_handler_arg(self) -> HandlerArg {
    method to_handler_arg (line 28) | fn to_handler_arg(self) -> HandlerArg {
    method to_handler_arg (line 41) | fn to_handler_arg(self) -> HandlerArg {
  type AttachedMap (line 46) | type AttachedMap = HashMap<String, HashMap<String, Closure<Fn(JsValue)>>>;
  function document (line 48) | fn document() -> Document {
  function get_handler_id (line 52) | fn get_handler_id(el: &HtmlElement) -> Option<String> {
  function set_handler_id (line 61) | fn set_handler_id(el: &HtmlElement, id: &str) {
  type WebRuntime (line 66) | pub struct WebRuntime<A: App> {
  function insert_at (line 72) | fn insert_at(parent: &Node, i: usize, node: &Node) {
  function set_attribute (line 81) | fn set_attribute(el: &Element, name: &str, value: &AttributeValue) {
  function new (line 95) | pub fn new(root: Element, state: A::State) -> WebRuntime<A> {
  function handle_diff_inner (line 103) | fn handle_diff_inner(&self, el: &Element, diff: Diff) {
  function replace_at (line 135) | fn replace_at(&self, parent: &Node, i: usize, node: &Node) {
  function create_element (line 141) | fn create_element(&self, el: &SquarkElement) -> Element {
  function add_child (line 171) | fn add_child(&self, parent: &Element, i: usize, node: SquarkNode) {
  function replace_child (line 185) | fn replace_child(&self, parent: &Element, i: usize, node: SquarkNode) {
  function remove_child (line 199) | fn remove_child(&self, parent: &Node, i: usize) {
  function set_handler (line 206) | fn set_handler(&self, el: &Element, name: &str, id: &str) {
  function _set_handler (line 230) | fn _set_handler<T: ToHandlerArg>(
  function remove_attached (line 246) | fn remove_attached(&self, el: &Node) {
  function nop (line 268) | fn nop<T>(_: T) {}
  function get_env (line 271) | fn get_env<'a>(&'a self) -> &'a Env<A> {
  function schedule_render (line 275) | fn schedule_render(&self) {
  function handle_diff (line 287) | fn handle_diff(&self, diff: Diff) {
  function handle_future (line 291) | fn handle_future<T: Serialize + 'static, E: Serialize + 'static>(&self, ...

FILE: squark/src/lib.rs
  type App (line 18) | pub trait App: 'static + Clone + Default {
    method reducer (line 22) | fn reducer(&self, state: Self::State, action: Self::Action) -> (Self::...
    method view (line 24) | fn view(&self, state: Self::State) -> View<Self::Action>;
  function handler (line 27) | pub fn handler<A, F>(f: F) -> (String, HandlerFunction<A>)
  type Env (line 35) | pub struct Env<A: App> {
  function new (line 44) | pub fn new(state: A::State) -> Env<A> {
  function get_state (line 54) | fn get_state(&self) -> A::State {
  function set_state (line 58) | fn set_state(&self, state: A::State) {
  function get_node (line 62) | fn get_node(&self) -> Node {
  function set_node (line 66) | fn set_node(&self, node: Node) {
  function pop_handler (line 70) | fn pop_handler(&self, id: &str) -> Option<HandlerFunction<A::Action>> {
  type Task (line 75) | pub struct Task<A>(Vec<Box<Future<Item = A, Error = ()>>>);
  method default (line 78) | fn default() -> Self {
  function empty (line 84) | pub fn empty() -> Self {
  function into_futures (line 88) | pub fn into_futures(self) -> Vec<Box<Future<Item = A, Error = ()>>> {
  function push (line 92) | pub fn push(&mut self, future: Box<Future<Item = A, Error = ()>>) {
  type Runtime (line 97) | pub trait Runtime<A: App>: Clone + 'static {
    method get_env (line 98) | fn get_env<'a>(&'a self) -> &'a Env<A>;
    method handle_diff (line 100) | fn handle_diff(&self, diff: Diff);
    method handle_future (line 102) | fn handle_future<T: Serialize + 'static, E: Serialize + 'static>(&self...
    method schedule_render (line 104) | fn schedule_render(&self);
    method run (line 106) | fn run(&self) {
    method run_with_task (line 110) | fn run_with_task(&self, task: Task<A::Action>) {
    method on_action (line 126) | fn on_action(&self, action: A::Action) {
    method set_state (line 137) | fn set_state(&self, new_state: A::State) {
    method emit_future (line 151) | fn emit_future(&self, task: Box<Future<Item = A::Action, Error = ()>>) {
    method pop_handler (line 158) | fn pop_handler(&self, id: &str) -> Option<Box<Fn(HandlerArg)>> {
  function uuid (line 172) | pub fn uuid() -> String {

FILE: squark/src/vdom.rs
  type Attribute (line 6) | type Attribute = (String, AttributeValue);
  function diff_attributes (line 8) | fn diff_attributes(a: &mut Vec<Attribute>, b: &[Attribute]) -> Vec<Diff> {
  type HandlerFunction (line 30) | pub(crate) type HandlerFunction<A> = Box<Fn(HandlerArg) -> Option<A>>;
  type Handler (line 31) | type Handler = (String, String);
  function diff_handlers (line 33) | fn diff_handlers(a: &mut Vec<Handler>, b: &[Handler]) -> Vec<Diff> {
  type Node (line 50) | pub enum Node {
    method diff (line 57) | pub(crate) fn diff(a: &mut Node, b: &Node, i: &mut usize) -> Option<Di...
    method get_key (line 78) | fn get_key(&self) -> Option<String> {
  function get_nodelist_key_set (line 86) | fn get_nodelist_key_set(nodelist: &[Node]) -> FxHashSet<String> {
  function diff_children (line 90) | fn diff_children(a: &mut Vec<Node>, b: &[Node], i: &mut usize) -> Vec<Di...
  type Element (line 141) | pub struct Element {
    method new (line 149) | fn new(
    method name (line 163) | pub fn name(&self) -> &str {
    method attributes (line 167) | pub fn attributes(&self) -> &[Attribute] {
    method handlers (line 171) | pub fn handlers(&self) -> &[Handler] {
    method children (line 175) | pub fn children(&self) -> &[Node] {
    method diff (line 179) | fn diff(a: &mut Element, b: &Element, i: usize) -> Option<Diff> {
    method get_key (line 202) | fn get_key(&self) -> Option<String> {
  type Diff (line 214) | pub enum Diff {
  type AttributeValue (line 226) | pub enum AttributeValue {
    method from (line 232) | fn from(s: String) -> AttributeValue {
    method from (line 238) | fn from(s: &'a str) -> AttributeValue {
    method from (line 244) | fn from(b: bool) -> AttributeValue {
  type HandlerMap (line 249) | pub(crate) type HandlerMap<A> = FxHashMap<String, HandlerFunction<A>>;
  type View (line 251) | pub struct View<A> {
  type Child (line 256) | pub enum Child<A> {
  function from (line 265) | fn from(v: T) -> Child<A> {
  function from_iter (line 271) | fn from_iter<I>(iter: I) -> Child<A>
  function new (line 280) | pub fn new(
  function text (line 318) | pub fn text(s: String) -> View<A> {
  function null (line 325) | pub fn null() -> View<A> {
  function from (line 334) | fn from(_: ()) -> View<A> {
  function from (line 340) | fn from(s: String) -> View<A> {
  function from (line 346) | fn from(s: &'a str) -> View<A> {
  function from (line 355) | fn from(option: Option<T>) -> View<A> {
Condensed preview — 47 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (70K chars).
[
  {
    "path": ".gitignore",
    "chars": 20,
    "preview": "/target\n/Cargo.lock\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 79,
    "preview": "[workspace]\nmembers = [\n    \"squark\",\n    \"squark-macros\",\n    \"squark-web\",\n]\n"
  },
  {
    "path": "LICENCE",
    "chars": 483,
    "preview": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 200"
  },
  {
    "path": "README.md",
    "chars": 3775,
    "preview": "# squark\n\nRust frontend framework, for web browser and more.\n\n**Currently, we depend on `nightly` channel**\n\n## Design\n\n"
  },
  {
    "path": "examples/counter/.gitignore",
    "chars": 61,
    "preview": "dist\nnode_modules\ncrate/pkg\ncrate/target\ncrate/wasm-pack.log\n"
  },
  {
    "path": "examples/counter/README.md",
    "chars": 78,
    "preview": "Just run\n\n```bash\nnpm run start\n```\n\ndev server will come with hot reloading.\n"
  },
  {
    "path": "examples/counter/crate/.crates.toml",
    "chars": 148,
    "preview": "[v1]\n\"wasm-bindgen-cli 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)\" = [\"wasm-bindgen\", \"wasm-bindgen-"
  },
  {
    "path": "examples/counter/crate/.gitignore",
    "chars": 32,
    "preview": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "examples/counter/crate/Cargo.toml",
    "chars": 498,
    "preview": "[package]\nname = \"counter\"\nversion = \"0.1.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\n\n[workspace]\n\n[lib]\n"
  },
  {
    "path": "examples/counter/crate/src/lib.rs",
    "chars": 1746,
    "preview": "#![feature(proc_macro_hygiene)]\n\nextern crate squark;\nextern crate squark_macros;\nextern crate squark_web;\nextern crate "
  },
  {
    "path": "examples/counter/index.html",
    "chars": 167,
    "preview": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        <script src='."
  },
  {
    "path": "examples/counter/js/index.js",
    "chars": 60,
    "preview": "import(\"../crate/pkg\").then(module => {\n  module.run();\n});\n"
  },
  {
    "path": "examples/counter/package.json",
    "chars": 260,
    "preview": "{\n  \"scripts\": {\n    \"start\": \"webpack-dev-server -d\",\n    \"build\": \"webpack --mode none\"\n  },\n  \"devDependencies\": {\n  "
  },
  {
    "path": "examples/counter/webpack.config.js",
    "chars": 420,
    "preview": "const path = require(\"path\");\nconst dist = path.resolve(__dirname, \"dist\");\nconst WasmPackPlugin = require(\"@wasm-tool/w"
  },
  {
    "path": "examples/todomvc/.gitignore",
    "chars": 61,
    "preview": "dist\nnode_modules\ncrate/pkg\ncrate/target\ncrate/wasm-pack.log\n"
  },
  {
    "path": "examples/todomvc/README.md",
    "chars": 78,
    "preview": "Just run\n\n```bash\nnpm run start\n```\n\ndev server will come with hot reloading.\n"
  },
  {
    "path": "examples/todomvc/crate/.crates.toml",
    "chars": 148,
    "preview": "[v1]\n\"wasm-bindgen-cli 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)\" = [\"wasm-bindgen\", \"wasm-bindgen-"
  },
  {
    "path": "examples/todomvc/crate/.gitignore",
    "chars": 32,
    "preview": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "examples/todomvc/crate/Cargo.toml",
    "chars": 431,
    "preview": "[package]\nname = \"todomvc\"\nversion = \"0.1.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\n\n[workspace]\n\n[lib]\n"
  },
  {
    "path": "examples/todomvc/crate/src/lib.rs",
    "chars": 10898,
    "preview": "#![feature(proc_macro_hygiene)]\n\nextern crate serde_json;\nextern crate squark;\nextern crate squark_macros;\nextern crate "
  },
  {
    "path": "examples/todomvc/index.html",
    "chars": 695,
    "preview": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>squark-web • TodoMVC</title>\n "
  },
  {
    "path": "examples/todomvc/js/index.js",
    "chars": 60,
    "preview": "import(\"../crate/pkg\").then(module => {\n  module.run();\n});\n"
  },
  {
    "path": "examples/todomvc/package.json",
    "chars": 260,
    "preview": "{\n  \"scripts\": {\n    \"start\": \"webpack-dev-server -d\",\n    \"build\": \"webpack --mode none\"\n  },\n  \"devDependencies\": {\n  "
  },
  {
    "path": "examples/todomvc/styles.css",
    "chars": 7341,
    "preview": "/* Source: https://github.com/evancz/elm-todomvc/blob/master/style.css */\n\nhtml,\nbody {\n    margin: 0;\n    padding: 0;\n}"
  },
  {
    "path": "examples/todomvc/webpack.config.js",
    "chars": 420,
    "preview": "const path = require(\"path\");\nconst dist = path.resolve(__dirname, \"dist\");\nconst WasmPackPlugin = require(\"@wasm-tool/w"
  },
  {
    "path": "examples/with_task/.gitignore",
    "chars": 61,
    "preview": "dist\nnode_modules\ncrate/pkg\ncrate/target\ncrate/wasm-pack.log\n"
  },
  {
    "path": "examples/with_task/README.md",
    "chars": 78,
    "preview": "Just run\n\n```bash\nnpm run start\n```\n\ndev server will come with hot reloading.\n"
  },
  {
    "path": "examples/with_task/crate/.gitignore",
    "chars": 32,
    "preview": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "examples/with_task/crate/Cargo.toml",
    "chars": 500,
    "preview": "[package]\nname = \"with_task\"\nversion = \"0.1.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\n\n[workspace]\n\n[lib"
  },
  {
    "path": "examples/with_task/crate/src/lib.rs",
    "chars": 2905,
    "preview": "#![feature(proc_macro_hygiene)]\nextern crate squark;\nextern crate squark_macros;\nextern crate squark_web;\nextern crate w"
  },
  {
    "path": "examples/with_task/index.html",
    "chars": 167,
    "preview": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        <script src='."
  },
  {
    "path": "examples/with_task/js/index.js",
    "chars": 60,
    "preview": "import(\"../crate/pkg\").then(module => {\n  module.run();\n});\n"
  },
  {
    "path": "examples/with_task/package.json",
    "chars": 260,
    "preview": "{\n  \"scripts\": {\n    \"start\": \"webpack-dev-server -d\",\n    \"build\": \"webpack --mode none\"\n  },\n  \"devDependencies\": {\n  "
  },
  {
    "path": "examples/with_task/webpack.config.js",
    "chars": 420,
    "preview": "const path = require(\"path\");\nconst dist = path.resolve(__dirname, \"dist\");\nconst WasmPackPlugin = require(\"@wasm-tool/w"
  },
  {
    "path": "renovate.json",
    "chars": 41,
    "preview": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "squark/.gitignore",
    "chars": 32,
    "preview": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "squark/Cargo.toml",
    "chars": 603,
    "preview": "[package]\nname = \"squark\"\nversion = \"0.7.1\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\nrepository = \"https:/"
  },
  {
    "path": "squark/src/lib.rs",
    "chars": 4723,
    "preview": "use rand::prelude::*;\nuse std::cell::{Cell, RefCell};\nuse std::fmt::Debug;\nuse rustc_hash::FxHashMap;\nuse std::rc::Rc;\nu"
  },
  {
    "path": "squark/src/vdom.rs",
    "chars": 9105,
    "preview": "use rustc_hash::{FxHashMap, FxHashSet};\nuse std::iter::FromIterator;\n\npub use serde_json::Value as HandlerArg;\n\ntype Att"
  },
  {
    "path": "squark-macros/.gitignore",
    "chars": 32,
    "preview": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "squark-macros/Cargo.toml",
    "chars": 548,
    "preview": "[package]\nname = \"squark-macros\"\nversion = \"0.7.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\nrepository = \""
  },
  {
    "path": "squark-macros/src/lib.rs",
    "chars": 3614,
    "preview": "#![crate_type = \"proc-macro\"]\n#![feature(proc_macro_hygiene, proc_macro_quote)]\n\nextern crate pest;\n#[macro_use]\nextern "
  },
  {
    "path": "squark-macros/src/view.pest",
    "chars": 912,
    "preview": "view = _{ SOI ~ tag ~ EOI }\n\nidentifier = _{ ('a'..'z' | 'A'..'Z' | '0'..'9' | \"_\" | \"-\")+ }\n\ntag_name = @{ identifier }"
  },
  {
    "path": "squark-macros/tests/test.rs",
    "chars": 806,
    "preview": "#![feature(test, use_extern_macros, proc_macro_non_items)]\n\nextern crate squark;\nextern crate squark_macros;\n\nuse squark"
  },
  {
    "path": "squark-web/.gitignore",
    "chars": 32,
    "preview": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "squark-web/Cargo.toml",
    "chars": 895,
    "preview": "[package]\nname = \"squark-web\"\nversion = \"0.3.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\nrepository = \"htt"
  },
  {
    "path": "squark-web/src/lib.rs",
    "chars": 9879,
    "preview": "use std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::rc::Rc;\nuse futures::Future;\nuse wasm_bindgen_futures::f"
  }
]

About this extraction

This page contains the full source code of the rail44/squark GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 47 files (62.5 KB), approximately 17.5k tokens, and a symbol index with 139 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!