~damien/gtk-webby

6b1d7615869015ef3ec37ea22ed94fc7c4fb581b — Damien Radtke 5 months ago dafa7b8
Add support for empty style or script tags
M examples/full/src/main.rs => examples/full/src/main.rs +4 -1
@@ 1,6 1,7 @@
#[macro_use] extern crate rocket;
use rocket_dyn_templates::Template;
use rocket::config::TlsConfig;
use rocket::fs::FileServer;
use rocket::http::ContentType;
use rocket::serde::Serialize;



@@ 23,7 24,9 @@ fn index() -> (ContentType, Template) {
fn rocket() -> _ {
    let tls_config = TlsConfig::from_paths("localhost.crt", "localhost.key");

    rocket::build().mount("/", routes![index])
    rocket::build()
        .mount("/", routes![index])
        .mount("/static", FileServer::from("static"))
        .attach(Template::fairing())
        .configure(rocket::Config{
            port: 8005,

A examples/full/static/style.css => examples/full/static/style.css +1 -0
@@ 0,0 1,1 @@
/* styles can go here */

M examples/full/templates/index.ui.hbs => examples/full/templates/index.ui.hbs +14 -0
@@ 1,6 1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
	<web:page title="Home"/>
	<web:style src="/static/style.css"/>
	<object class="GtkBox" id="body">
		<property name="orientation">horizontal</property>
		<property name="halign">start</property>


@@ 11,12 12,17 @@
		</child>
		<child>
			<object class="GtkStack" id="stack">
				<property name="margin-start">12</property>
				<child>
					<object class="GtkStackPage">
						<property name="name">home</property>
						<property name="title">Home</property>
						<property name="child">
							<object class="GtkLabel">
								<style>
									<class name="page"/>
								</style>
								<property name="halign">start</property>
								<property name="label">Welcome to my home page!</property>
							</object>
						</property>


@@ 33,6 39,10 @@
								{{#each posts}}
								<child>
									<object class="GtkLabel">
										<style>
											<class name="page"/>
										</style>
										<property name="halign">start</property>
										<property name="label">{{this}}</property>
									</object>
								</child>


@@ 48,6 58,10 @@
						<property name="title">About Me</property>
						<property name="child">
							<object class="GtkLabel">
								<style>
									<class name="page"/>
								</style>
								<property name="halign">start</property>
								<property name="label">Here is more information about me.</property>
							</object>
						</property>

M src/ui.rs => src/ui.rs +48 -39
@@ 23,12 23,20 @@ pub struct Definition {
    pub title: Option<String>,
}

impl Definition {
    pub fn new(
        http_client: &reqwest::blocking::Client,
        location: &String,
        source: String,
    ) -> super::Result<Definition> {
pub struct Builder<'a> {
    http_client: &'a reqwest::blocking::Client,
    location: &'a String,
}

impl<'a> Builder<'a> {
    pub fn new(http_client: &'a reqwest::blocking::Client, location: &'a String) -> Builder<'a> {
        Builder {
            http_client,
            location,
        }
    }

    pub fn build(&self, source: String) -> super::Result<Definition> {
        let mut hrefs = HashMap::new();
        let mut scripts = Vec::new();
        let mut styles = String::new();


@@ 138,43 146,16 @@ impl Definition {

                        current_script = String::new();
                        reading_script = true;
                        if let Some(target) = attrs.get("src") {
                            let url = crate::util::absolutize_url(location, target);
                            match http_client.get(url).send() {
                                Ok(resp) if resp.status().is_success() => match resp.text() {
                                    Ok(text) => current_script.push_str(&text),
                                    Err(err) => println!(
                                        "error parsing script src response as text: {}",
                                        err
                                    ),
                                },
                                Ok(resp) => println!(
                                    "received error code fetching script src: {}",
                                    resp.status()
                                ),
                                Err(err) => println!("error fetching script src: {}", err),
                            }

                        if let Some(script) = self.fetch_src_attr(&attrs) {
                            current_script.push_str(&script);
                        }
                    }
                    Some(STYLE_TAG) => {
                        let attrs = attrs_map(bs)?;
                        reading_style = true;
                        if let Some(target) = attrs.get("src") {
                            let url = crate::util::absolutize_url(location, target);
                            match http_client.get(url).send() {
                                Ok(resp) if resp.status().is_success() => match resp.text() {
                                    Ok(text) => styles.push_str(&text),
                                    Err(err) => println!(
                                        "error parsing style src response as text: {}",
                                        err
                                    ),
                                },
                                Ok(resp) => println!(
                                    "received error code fetching style src: {}",
                                    resp.status()
                                ),
                                Err(err) => println!("error fetching style src: {}", err),
                            }
                        if let Some(style) = self.fetch_src_attr(&attrs) {
                            styles.push_str(&style);
                        }
                    }
                    _ => writer.write_event(Event::Start(trim_bytes_start(bs)?))?,


@@ 209,13 190,24 @@ impl Definition {
                    _ => writer.write_event(Event::End(be))?,
                },
                Event::Empty(ref bs) => match parse_web_tag(&bs.name()) {
                    // TODO: support empty script tags with a "src" attribute
                    Some(PAGE_TAG) => {
                        let attrs = attrs_map(bs)?;
                        if let Some(v) = attrs.get("title") {
                            title = Some(v.clone());
                        }
                    }
                    Some(SCRIPT_TAG) => {
                        let attrs = attrs_map(bs)?;
                        if let Some(script) = self.fetch_src_attr(&attrs) {
                            current_script.push_str(&script);
                        }
                    }
                    Some(STYLE_TAG) => {
                        let attrs = attrs_map(bs)?;
                        if let Some(style) = self.fetch_src_attr(&attrs) {
                            styles.push_str(&style);
                        }
                    }
                    _ => writer.write_event(Event::Empty(trim_bytes_start(bs)?))?,
                },
                e => writer.write_event(&e)?,


@@ 232,6 224,21 @@ impl Definition {
        };
        Ok(def)
    }

    fn fetch_src_attr(&self, attrs: &HashMap<String, String>) -> Option<String> {
        if let Some(src) = attrs.get("src") {
            let url = crate::util::absolutize_url(self.location, src);
            match self.http_client.get(url).send() {
                Ok(resp) if resp.status().is_success() => match resp.text() {
                    Ok(text) => return Some(text),
                    Err(err) => println!("error parsing src response as text: {}", err),
                },
                Ok(resp) => println!("received error code fetching src: {}", resp.status()),
                Err(err) => println!("error fetching src: {}", err),
            }
        }
        None
    }
}

fn parse_web_tag<'a>(name: &'a QName) -> Option<&'a [u8]> {


@@ 255,6 262,7 @@ impl IdAutogenerator {
    }
}

/*
#[cfg(test)]
mod test {
    use super::*;


@@ 309,3 317,4 @@ mod test {
        assert_eq!(parse_web_tag(&QName(b"object")), None);
    }
}
*/

M src/util.rs => src/util.rs +3 -3
@@ 1,9 1,9 @@
pub fn absolutize_url(current_location: &String, target: &String) -> String {
pub fn absolutize_url(current_location: &str, target: &str) -> String {
    if target.is_empty() {
        return current_location.clone();
        return current_location.to_string();
    }
    if target.contains("://") {
        return target.clone();
        return target.to_string();
    }
    if !target.starts_with("/") {
        let mut result = String::new();

M src/window.rs => src/window.rs +1 -1
@@ 294,7 294,7 @@ impl Window {
        std::thread::spawn(move || {
            let ui_definition = {
                let state = window.state.lock().unwrap();
                match crate::ui::Definition::new(&state.http_client, &state.location, s) {
                match crate::ui::Builder::new(&state.http_client, &state.location).build(s) {
                    Ok(def) => def,
                    Err(err) => {
                        println!("failed to build UI definition: {}", err);