<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use bit_field::BitField;
use std::fmt::{self, Debug, Display, Formatter};
use thiserror::Error;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FieldInfo {
    pub name: &'static str,
    pub start: usize,
    pub width: usize,
    pub value: u64,
    pub decoded: Option<Decoded>,
}

impl FieldInfo {
    fn get(register: u64, name: &'static str, start: usize, end: usize) -> Self {
        let value = register.get_bits(start..end);
        Self {
            name,
            start,
            width: end - start,
            value,
            decoded: None,
        }
    }

    fn get_bit(register: u64, name: &'static str, bit: usize) -> Self {
        Self::get(register, name, bit, bit + 1)
    }

    fn with_decoded(self, decoded: Decoded) -> Self {
        Self {
            decoded: Some(decoded),
            ..self
        }
    }
}

impl Display for FieldInfo {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        if self.width == 1 {
            write!(
                f,
                "{}: {}",
                self.name,
                if self.value == 1 { "true" } else { "false" }
            )
        } else {
            write!(
                f,
                "{}: {:#02$x} {1:#03$b}",
                self.name,
                self.value,
                (self.width + 3) / 4 + 2,
                self.width + 2
            )
        }
    }
}

#[derive(Debug, Error)]
pub enum DecodeError {
    #[error("Invalid ESR, res0 is {res0}")]
    InvalidRes0 { res0: u64 },
    #[error("Invalid EC {ec}")]
    InvalidEc { ec: u64 },
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum SyndromeAccessSize {
    Byte = 0b00,
    Halfword = 0b01,
    Word = 0b10,
    Doubleword = 0b11,
}

impl Display for SyndromeAccessSize {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let s = match self {
            Self::Byte => "byte",
            Self::Halfword => "halfword",
            Self::Word => "word",
            Self::Doubleword => "doubleword",
        };
        write!(f, "{}", s)
    }
}

fn decode_iss_data_abort(iss: u64) -> Decoded {
    let isv = FieldInfo::get_bit(iss, "ISV", 24);
    let sas = FieldInfo::get(iss, "SAS", 22, 24);
    let sas_value = match sas.value {
        0b00 => SyndromeAccessSize::Byte,
        0b01 => SyndromeAccessSize::Halfword,
        0b10 => SyndromeAccessSize::Word,
        0b11 => SyndromeAccessSize::Doubleword,
        _ => unreachable!(),
    };
    let sas = sas.with_decoded(Decoded {
        description: Some(sas_value.to_string()),
        fields: vec![],
    });
    let sse = FieldInfo::get_bit(iss, "SSE", 21);
    let srt = FieldInfo::get(iss, "SRT", 16, 21);
    let sf = FieldInfo::get_bit(iss, "SF", 15);
    let ar = FieldInfo::get_bit(iss, "AR", 14);
    let vncr = FieldInfo::get_bit(iss, "VNCR", 13);
    let fnv = FieldInfo::get_bit(iss, "FnV", 10);
    let ea = FieldInfo::get_bit(iss, "EA", 9);
    let cm = FieldInfo::get_bit(iss, "CM", 8);
    let s1ptw = FieldInfo::get_bit(iss, "S1PTW", 7);
    let wnr = FieldInfo::get_bit(iss, "WnR", 6);
    let dfsc = FieldInfo::get(iss, "DFSC", 0, 6);

    Decoded {
        description: None,
        fields: vec![
            isv, sas, sse, srt, sf, ar, vncr, fnv, ea, cm, s1ptw, wnr, dfsc,
        ],
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Decoded {
    pub description: Option<String>,
    pub fields: Vec<FieldInfo>,
}

pub fn decode(esr: u64) -> Result<Decoded, DecodeError> {
    let res0 = FieldInfo::get(esr, "RES0", 37, 64);
    let iss2 = FieldInfo::get(esr, "ISS2", 32, 37);
    let ec = FieldInfo::get(esr, "EC", 26, 32);
    let il = FieldInfo::get_bit(esr, "IL", 25);
    let iss = FieldInfo::get(esr, "ISS", 0, 25);
    if res0.value != 0 {
        return Err(DecodeError::InvalidRes0 { res0: res0.value });
    }
    let (class, iss_decoded) = match ec.value {
        0b000000 => ("Unknown reason", None),
        0b000001 => ("Wrapped WF* instruction execution", None),
        0b000011 => ("Trapped MCR or MRC access with coproc=0b1111", None),
        0b000100 => ("Trapped MCRR or MRRC access with coproc=0b1111", None),
        0b000101 => ("Trapped MCR or MRC access with coproc=0b1110", None),
        0b000110 => ("Trapped LDC or STC access", None),
        0b000111 => ("Trapped access to SVE, Advanced SIMD or floating point", None),
        0b001010 => ("Trapped execution of an LD64B, ST64B, ST64BV, or ST64BV0 instruction", None),
        0b001100 => ("Trapped MRRC access with (coproc==0b1110)", None),
        0b001101 => ("Branch Target Exception", None),
        0b001110 => ("Illegal Execution state", None),
        0b010001 => ("SVC instruction execution in AArch32 state", None),
        0b010101 => ("SVC instruction execution in AArch64 state", None),
        0b011000 => ("Trapped MSR, MRS or System instruction execution in AArch64 state", None),
        0b011001 => ("Access to SVE functionality trapped as a result of CPACR_EL1.ZEN, CPTR_EL2.ZEN, CPTR_EL2.TZ, or CPTR_EL3.EZ", None),
        0b011100 => ("Exception from a Pointer Authentication instruction authentication failure", None),
        0b100000 => ("Instruction Abort from a lower Exception level", None),
        0b100001 => ("Instruction Abort taken without a change in Exception level", None),
        0b100010 => ("PC alignment fault exception", None),
        0b100100 => ("Data Abort from a lower Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100101 => ("Data Abort taken without a change in Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100110 => ("SP alignment fault exception", None),
        0b101000 => ("Trapped floating-point exception taken from AArch32 state", None),
        0b101100 => ("Trapped floating-point exception taken from AArch64 state", None),
        0b101111 => ("SError interrupt", None),
        0b110000 => ("Breakpoint exception from a lower Exception level", None),
        0b110001 => ("Breakpoint exception taken without a change in Exception level", None),
        0b110010 => ("Software Step exception from a lower Exception level", None),
        0b110011 => ("Software Step exception taken without a change in Exception level", None),
        0b110100 => ("Watchpoint exception from a lower Exception level", None),
        0b110101 => ("Watchpoint exception taken without a change in Exception level", None),
        0b111000 => ("BKPT instruction execution in AArch32 state", None),
        0b111100 => ("BRK instruction execution in AArch64 state", None),
        _ => return Err(DecodeError::InvalidEc { ec: ec.value }),
    };
    let iss = FieldInfo {
        decoded: iss_decoded,
        ..iss
    };
    let ec = ec.with_decoded(Decoded {
        description: Some(class.to_string()),
        fields: vec![],
    });
    Ok(Decoded {
        description: Some(class.to_string()),
        fields: vec![res0, iss2, ec, il, iss],
    })
}
<commit_msg>Decode some more fields.<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use bit_field::BitField;
use std::fmt::{self, Debug, Display, Formatter};
use thiserror::Error;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FieldInfo {
    pub name: &'static str,
    pub start: usize,
    pub width: usize,
    pub value: u64,
    pub decoded: Option<Decoded>,
}

impl FieldInfo {
    fn get(register: u64, name: &'static str, start: usize, end: usize) -> Self {
        let value = register.get_bits(start..end);
        Self {
            name,
            start,
            width: end - start,
            value,
            decoded: None,
        }
    }

    fn get_bit(register: u64, name: &'static str, bit: usize) -> Self {
        Self::get(register, name, bit, bit + 1)
    }

    fn with_decoded(self, decoded: Decoded) -> Self {
        Self {
            decoded: Some(decoded),
            ..self
        }
    }

    fn as_bit(&self) -> bool {
        assert!(self.width == 1);
        self.value == 1
    }
}

impl Display for FieldInfo {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        if self.width == 1 {
            write!(
                f,
                "{}: {}",
                self.name,
                if self.value == 1 { "true" } else { "false" }
            )
        } else {
            write!(
                f,
                "{}: {:#02$x} {1:#03$b}",
                self.name,
                self.value,
                (self.width + 3) / 4 + 2,
                self.width + 2
            )
        }
    }
}

#[derive(Debug, Error)]
pub enum DecodeError {
    #[error("Invalid ESR, res0 is {res0}")]
    InvalidRes0 { res0: u64 },
    #[error("Invalid EC {ec}")]
    InvalidEc { ec: u64 },
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum SyndromeAccessSize {
    Byte = 0b00,
    Halfword = 0b01,
    Word = 0b10,
    Doubleword = 0b11,
}

impl Display for SyndromeAccessSize {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let s = match self {
            Self::Byte => "byte",
            Self::Halfword => "halfword",
            Self::Word => "word",
            Self::Doubleword => "doubleword",
        };
        write!(f, "{}", s)
    }
}

fn decode_sf(sf: bool) -> Decoded {
    let description = if sf {
        "64-bit wide register"
    } else {
        "32-bit wide register"
    };
    Decoded {
        description: Some(description.to_string()),
        fields: vec![],
    }
}

fn decode_ar(ar: bool) -> Decoded {
    let description = if ar {
        "Acquire/release semantics"
    } else {
        "No acquire/release semantics"
    };
    Decoded {
        description: Some(description.to_string()),
        fields: vec![],
    }
}

fn decode_iss_data_abort(iss: u64) -> Decoded {
    let isv = FieldInfo::get_bit(iss, "ISV", 24);
    let sas = FieldInfo::get(iss, "SAS", 22, 24);
    let sas_value = match sas.value {
        0b00 => SyndromeAccessSize::Byte,
        0b01 => SyndromeAccessSize::Halfword,
        0b10 => SyndromeAccessSize::Word,
        0b11 => SyndromeAccessSize::Doubleword,
        _ => unreachable!(),
    };
    let sas = sas.with_decoded(Decoded {
        description: Some(sas_value.to_string()),
        fields: vec![],
    });
    let sse = FieldInfo::get_bit(iss, "SSE", 21);
    let srt = FieldInfo::get(iss, "SRT", 16, 21);
    let sf = FieldInfo::get_bit(iss, "SF", 15);
    let sf_value = sf.as_bit();
    let sf = sf.with_decoded(decode_sf(sf_value));
    let ar = FieldInfo::get_bit(iss, "AR", 14);
    let ar_value = ar.as_bit();
    let ar = ar.with_decoded(decode_ar(ar_value));
    let vncr = FieldInfo::get_bit(iss, "VNCR", 13);
    let fnv = FieldInfo::get_bit(iss, "FnV", 10);
    let ea = FieldInfo::get_bit(iss, "EA", 9);
    let cm = FieldInfo::get_bit(iss, "CM", 8);
    let s1ptw = FieldInfo::get_bit(iss, "S1PTW", 7);
    let wnr = FieldInfo::get_bit(iss, "WnR", 6);
    let dfsc = FieldInfo::get(iss, "DFSC", 0, 6);

    Decoded {
        description: None,
        fields: vec![
            isv, sas, sse, srt, sf, ar, vncr, fnv, ea, cm, s1ptw, wnr, dfsc,
        ],
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Decoded {
    pub description: Option<String>,
    pub fields: Vec<FieldInfo>,
}

pub fn decode(esr: u64) -> Result<Decoded, DecodeError> {
    let res0 = FieldInfo::get(esr, "RES0", 37, 64);
    let iss2 = FieldInfo::get(esr, "ISS2", 32, 37);
    let ec = FieldInfo::get(esr, "EC", 26, 32);
    let il = FieldInfo::get_bit(esr, "IL", 25);
    let iss = FieldInfo::get(esr, "ISS", 0, 25);
    if res0.value != 0 {
        return Err(DecodeError::InvalidRes0 { res0: res0.value });
    }
    let (class, iss_decoded) = match ec.value {
        0b000000 => ("Unknown reason", None),
        0b000001 => ("Wrapped WF* instruction execution", None),
        0b000011 => ("Trapped MCR or MRC access with coproc=0b1111", None),
        0b000100 => ("Trapped MCRR or MRRC access with coproc=0b1111", None),
        0b000101 => ("Trapped MCR or MRC access with coproc=0b1110", None),
        0b000110 => ("Trapped LDC or STC access", None),
        0b000111 => ("Trapped access to SVE, Advanced SIMD or floating point", None),
        0b001010 => ("Trapped execution of an LD64B, ST64B, ST64BV, or ST64BV0 instruction", None),
        0b001100 => ("Trapped MRRC access with (coproc==0b1110)", None),
        0b001101 => ("Branch Target Exception", None),
        0b001110 => ("Illegal Execution state", None),
        0b010001 => ("SVC instruction execution in AArch32 state", None),
        0b010101 => ("SVC instruction execution in AArch64 state", None),
        0b011000 => ("Trapped MSR, MRS or System instruction execution in AArch64 state", None),
        0b011001 => ("Access to SVE functionality trapped as a result of CPACR_EL1.ZEN, CPTR_EL2.ZEN, CPTR_EL2.TZ, or CPTR_EL3.EZ", None),
        0b011100 => ("Exception from a Pointer Authentication instruction authentication failure", None),
        0b100000 => ("Instruction Abort from a lower Exception level", None),
        0b100001 => ("Instruction Abort taken without a change in Exception level", None),
        0b100010 => ("PC alignment fault exception", None),
        0b100100 => ("Data Abort from a lower Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100101 => ("Data Abort taken without a change in Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100110 => ("SP alignment fault exception", None),
        0b101000 => ("Trapped floating-point exception taken from AArch32 state", None),
        0b101100 => ("Trapped floating-point exception taken from AArch64 state", None),
        0b101111 => ("SError interrupt", None),
        0b110000 => ("Breakpoint exception from a lower Exception level", None),
        0b110001 => ("Breakpoint exception taken without a change in Exception level", None),
        0b110010 => ("Software Step exception from a lower Exception level", None),
        0b110011 => ("Software Step exception taken without a change in Exception level", None),
        0b110100 => ("Watchpoint exception from a lower Exception level", None),
        0b110101 => ("Watchpoint exception taken without a change in Exception level", None),
        0b111000 => ("BKPT instruction execution in AArch32 state", None),
        0b111100 => ("BRK instruction execution in AArch64 state", None),
        _ => return Err(DecodeError::InvalidEc { ec: ec.value }),
    };
    let iss = FieldInfo {
        decoded: iss_decoded,
        ..iss
    };
    let ec = ec.with_decoded(Decoded {
        description: Some(class.to_string()),
        fields: vec![],
    });
    Ok(Decoded {
        description: Some(class.to_string()),
        fields: vec![res0, iss2, ec, il, iss],
    })
}
<|endoftext|>


<commit_before>use syntax::ast::TokenTree;
use syntax::codemap::Span;
use syntax::ptr::P;

use syntax::ast::Expr;
use syntax::ext::base::{ExtCtxt, MacResult, MacEager};
use syntax::parse::parser::Parser;
use syntax::parse::token::Token;

pub fn expand<'cx>(cx: &'cx mut ExtCtxt, _: Span,
               tts: &[TokenTree]) -> Box<MacResult + 'cx> {
    let mut parser = cx.new_parser_from_tts(tts);
    let expr = parse_json(cx, &mut parser);
    if &parser.token != &Token::Eof {
        cx.span_fatal(parser.span, "expected end of `json!` macro invocation");
    }
    MacEager::expr(expr)
}

fn parse_json(cx: &ExtCtxt, parser: &mut Parser) -> P<Expr> {
    use syntax::ext::build::AstBuilder;
    use syntax::parse::token::{DelimToken, IdentStyle};

    macro_rules! comma_sep {
        () =>  {
            ::syntax::parse::common::SeqSep {
                sep: Some(Token::Comma),
                trailing_sep_allowed: true // we could be JSON pedants...
            }
        }
    }

    let orig_span = parser.span;

    match &parser.token {
        &Token::OpenDelim(DelimToken::Bracket) => {
            let _ = parser.bump();
            let r_bracket = Token::CloseDelim(DelimToken::Bracket);
            let exprs = parser.parse_seq_to_end(&r_bracket, comma_sep!(), |p| {
                Ok(parse_json(cx, p))
            }).ok().unwrap();
            let exprs = cx.expr_vec(orig_span, exprs);
            quote_expr!(cx, {
                use ::std::boxed::Box;
                let xs: Box<[_]> = Box::new($exprs);
                ::rustc_serialize::json::Json::Array(xs.into_vec())
            })
        },
        &Token::OpenDelim(DelimToken::Brace) => {
            let _ = parser.bump();
            let r_brace = Token::CloseDelim(DelimToken::Brace);
            let kvs = parser.parse_seq_to_end(&r_brace, comma_sep!(), |p| {
                let (istr, _) = p.parse_str().ok().unwrap();
                let s = &*istr;
                let _ = p.expect(&Token::Colon);
                let key = quote_expr!(cx, {
                    use ::std::borrow::ToOwned;
                    $s.to_owned()
                });
                Ok((key, parse_json(cx, p)))
            }).ok().unwrap();
            let mut insertions = vec![];
            // Can't use `quote_stmt!()` and interpolate a vector of
            // statements, seemingly.  Should consider filing a bug
            // upstream.
            for &(ref key, ref value) in kvs.iter() {
                insertions.push(quote_expr!(cx, {
                    _ob.insert($key, $value);
                }));
            }
            let expr = quote_expr!(cx, {
                let mut _ob = ::std::collections::BTreeMap::new();
                $insertions;
                ::rustc_serialize::json::Json::Object(_ob)
            });
            expr
        },
        &Token::OpenDelim(DelimToken::Paren) => {
            let expr = parser.parse_expr();
            quote_expr!(cx, {{
                use ::rustc_serialize::json::ToJson;
                ($expr).to_json()
            }})
        },
        &Token::Ident(id, IdentStyle::Plain) if id.as_str() == "null" => {
            let _ = parser.bump();
            quote_expr!(cx, { ::rustc_serialize::json::Json::Null })
        },
        _ => { // TODO: investigate can_begin_expr (maybe eliminate need for parens)?
            let expr = parser.parse_literal_maybe_minus().ok().unwrap();
            quote_expr!(cx, {{
                use ::rustc_serialize::json::ToJson;
                ($expr).to_json()
            }})
        }
    }
}
<commit_msg>Update for recent changes in libsyntax<commit_after>use syntax::ast::TokenTree;
use syntax::codemap::Span;
use syntax::ptr::P;

use syntax::ast::Expr;
use syntax::ext::base::{ExtCtxt, MacResult, MacEager};
use syntax::parse::parser::Parser;
use syntax::parse::token::Token;

pub fn expand<'cx>(cx: &'cx mut ExtCtxt, _: Span,
               tts: &[TokenTree]) -> Box<MacResult + 'cx> {
    let mut parser = cx.new_parser_from_tts(tts);
    let expr = parse_json(cx, &mut parser);
    if &parser.token != &Token::Eof {
        cx.span_fatal(parser.span, "expected end of `json!` macro invocation");
    }
    MacEager::expr(expr)
}

fn parse_json(cx: &ExtCtxt, parser: &mut Parser) -> P<Expr> {
    use syntax::ext::build::AstBuilder;
    use syntax::parse::token::{DelimToken, IdentStyle};

    macro_rules! comma_sep {
        () =>  {
            ::syntax::parse::common::SeqSep {
                sep: Some(Token::Comma),
                trailing_sep_allowed: true // we could be JSON pedants...
            }
        }
    }

    let orig_span = parser.span;

    match &parser.token {
        &Token::OpenDelim(DelimToken::Bracket) => {
            let _ = parser.bump();
            let r_bracket = Token::CloseDelim(DelimToken::Bracket);
            let exprs = parser.parse_seq_to_end(&r_bracket, comma_sep!(), |p| {
                Ok(parse_json(cx, p))
            }).ok().unwrap();
            let exprs = cx.expr_vec(orig_span, exprs);
            quote_expr!(cx, {
                use ::std::boxed::Box;
                let xs: Box<[_]> = Box::new($exprs);
                ::rustc_serialize::json::Json::Array(xs.into_vec())
            })
        },
        &Token::OpenDelim(DelimToken::Brace) => {
            let _ = parser.bump();
            let r_brace = Token::CloseDelim(DelimToken::Brace);
            let kvs = parser.parse_seq_to_end(&r_brace, comma_sep!(), |p| {
                let (istr, _) = p.parse_str().ok().unwrap();
                let s = &*istr;
                let _ = p.expect(&Token::Colon);
                let key = quote_expr!(cx, {
                    use ::std::borrow::ToOwned;
                    $s.to_owned()
                });
                Ok((key, parse_json(cx, p)))
            }).ok().unwrap();
            let mut insertions = vec![];
            // Can't use `quote_stmt!()` and interpolate a vector of
            // statements, seemingly.  Should consider filing a bug
            // upstream.
            for &(ref key, ref value) in kvs.iter() {
                insertions.push(quote_expr!(cx, {
                    _ob.insert($key, $value);
                }));
            }
            let expr = quote_expr!(cx, {
                let mut _ob = ::std::collections::BTreeMap::new();
                $insertions;
                ::rustc_serialize::json::Json::Object(_ob)
            });
            expr
        },
        &Token::OpenDelim(DelimToken::Paren) => {
            let expr = parser.parse_expr();
            quote_expr!(cx, {{
                use ::rustc_serialize::json::ToJson;
                ($expr).to_json()
            }})
        },
        &Token::Ident(id, IdentStyle::Plain) if id.name.as_str() == "null" => {
            let _ = parser.bump();
            quote_expr!(cx, { ::rustc_serialize::json::Json::Null })
        },
        _ => { // TODO: investigate can_begin_expr (maybe eliminate need for parens)?
            let expr = parser.parse_literal_maybe_minus().ok().unwrap();
            quote_expr!(cx, {{
                use ::rustc_serialize::json::ToJson;
                ($expr).to_json()
            }})
        }
    }
}
<|endoftext|>


<commit_before>#![crate_type = "rlib"]

//! Macro to create a "view box", a box containing data plus
//! a "view" struct that can have interior references into the data.
//! The view box can be moved around as an atomic unit.
#![feature(macro_rules, globs)]

#[macro_export]
macro_rules! viewbox(
    (struct $name:ident<$d:ty, $v:ident>;) => (
        pub struct $name {
            view: $v<'static>,
            #[allow(dead_code)]
            data: Box<$d>
        }

        #[allow(dead_code)]
        impl $name {
            pub fn new(data: $d, f: for<'a>|&'a mut $d| -> $v<'a>) -> $name {
                let mut d = box data;
                let v = unsafe { ::std::mem::transmute(f(&mut *d)) };

                $name { data: d, view: v }
            }

            pub fn new_result<E>(data: $d,
                                 f: for<'a>|&'a mut $d|
                                    -> ::std::result::Result<$v<'a>,E>)
                                 -> ::std::result::Result<$name,($d,E)> {
                let mut d = box data;
                match f(&mut *d).map(|v| unsafe { ::std::mem::transmute(v) }) {
                    Ok(v) => Ok($name { data: d, view: v }),
                    Err(e) => Err((*d,e))
                }
            }

            pub fn unwrap(self)-> $d {
                let $name { data: box data, .. } = self;
                data
            }

            pub fn view<'a>(&'a self) -> &'a $v<'a> {
                unsafe { ::std::mem::transmute(&self.view) }
            }

            pub fn mut_view<'a>(&'a mut self) -> &'a mut $v<'a> {
                unsafe { ::std::mem::transmute(&mut self.view) }
            }
        }
    );
    (#[deriving(PartialEq $(,$derive:ident)*)] struct $name:ident<$d:ty, $v:ident>;) => (
        impl ::std::cmp::PartialEq for $name {
            fn eq(&self, other: &$name) -> bool { self.view() == other.view(); }
        }
        viewbox!(struct $name<$d,$v>;)
    );
    (#[deriving(Show $(,$derive:ident)*)] struct $name:ident<$d:ty, $v:ident>;) => (
        impl ::std::fmt::Show for $name {
            fn fmt(&self, fmt: &mut ::std::fmt::Formatter)
                   -> ::std::result::Result<(), ::std::fmt::FormatError> {
                self.view().fmt(fmt)
            }
        }
        viewbox!(struct $name<$d,$v>;)
    );
    (#[deriving()] struct $name:ident<$d:ty, $v:ident>;) => (
        viewbox!(struct $name<$d,$v>;)
    );
)

#[cfg(test)]
mod test {
    extern crate arena;
    use self::arena::TypedArena;

    // Test data structure
    #[deriving(PartialEq,Show)]
    struct TestData {
        foo: i32,
        bar: String
    }

    // View structure that has references into the boxed data
    struct TestView<'a> {
        x: &'a i32,
        y: &'a str
    }

    // Create TestBox which combines TestData and TestView
    viewbox! {
        struct TestBox<TestData,TestView>;
    }

    // Mutable view into TestData
    struct MutView<'a> {
        x: &'a mut i32,
        y: &'a mut String
    }

    // MutBox combines TestData and MutView
    viewbox! {
        struct MutBox<TestData, MutView>;
    }

    #[test]
    fn basic() {
        // Create some data
        let t = TestData { foo: 42, bar: "Hello".to_string() };
        // Move it into box, creating a view with interior references
        let v = TestBox::new(t, |d| TestView { x: &d.foo, y: d.bar.as_slice() });

        // We can access the boxed data via the view
        assert_eq!(v.view().x, &42i32);
        assert_eq!(v.view().y, "Hello");
        // We can unwrap the view to get the data back
        let t = v.unwrap();
        assert_eq!(t.foo, 42i32);
        assert_eq!(t.bar, "Hello".to_string());
    }

    #[test]
    fn mutation() {
        let t = TestData { foo: 42, bar: "Hello".to_string() };
        let mut v = MutBox::new(t, |d| MutView { x: &mut d.foo, y: &mut d.bar });

        *v.mut_view().x = 5;
        *v.mut_view().y = "Goodbye".to_string();
        let t = v.unwrap();
        assert_eq!(t, TestData { foo: 5, bar: "Goodbye".to_string() });
    }

    #[test]
    fn error() {
        let t = TestData { foo: 42, bar: "Hello".to_string() };
        let v = MutBox::new_result(t, |_| Err("Nope")).err().unwrap();
        assert_eq!(v, (TestData { foo: 42, bar: "Hello".to_string() }, "Nope"))
    }

    struct ArenaView<'a> {
        arena: &'a TypedArena<int>,
        vec: Vec<&'a int>
    }

    viewbox! {
        struct ArenaBox<TypedArena<int>, ArenaView>;
    }

    #[test]
    fn arenaview() {
        let mut vb = ArenaBox::new(
            TypedArena::new(),
            |a| ArenaView { arena: a, vec: Vec::new()});

        spawn(proc() {
            let v = vb.mut_view();
            let a = v.arena.alloc(1);
            let b = v.arena.alloc(2);
            let c = v.arena.alloc(3);
            v.vec.push(a);
            v.vec.push(b);
            v.vec.push(c);
        });
    }
}
<commit_msg>Rename `unwrap` to `into_inner`<commit_after>#![crate_type = "rlib"]

//! Macro to create a "view box", a box containing data plus
//! a "view" struct that can have interior references into the data.
//! The view box can be moved around as an atomic unit.
#![feature(macro_rules, globs)]

#[macro_export]
macro_rules! viewbox(
    (struct $name:ident<$d:ty, $v:ident>;) => (
        pub struct $name {
            view: $v<'static>,
            #[allow(dead_code)]
            data: Box<$d>
        }

        #[allow(dead_code)]
        impl $name {
            pub fn new(data: $d, f: for<'a>|&'a mut $d| -> $v<'a>) -> $name {
                let mut d = box data;
                let v = unsafe { ::std::mem::transmute(f(&mut *d)) };

                $name { data: d, view: v }
            }

            pub fn new_result<E>(data: $d,
                                 f: for<'a>|&'a mut $d|
                                    -> ::std::result::Result<$v<'a>,E>)
                                 -> ::std::result::Result<$name,($d,E)> {
                let mut d = box data;
                match f(&mut *d).map(|v| unsafe { ::std::mem::transmute(v) }) {
                    Ok(v) => Ok($name { data: d, view: v }),
                    Err(e) => Err((*d,e))
                }
            }

            pub fn into_inner(self)-> $d {
                let $name { data: box data, .. } = self;
                data
            }

            pub fn view<'a>(&'a self) -> &'a $v<'a> {
                unsafe { ::std::mem::transmute(&self.view) }
            }

            pub fn mut_view<'a>(&'a mut self) -> &'a mut $v<'a> {
                unsafe { ::std::mem::transmute(&mut self.view) }
            }
        }
    );
    (#[deriving(PartialEq $(,$derive:ident)*)] struct $name:ident<$d:ty, $v:ident>;) => (
        impl ::std::cmp::PartialEq for $name {
            fn eq(&self, other: &$name) -> bool { self.view() == other.view(); }
        }
        viewbox!(struct $name<$d,$v>;)
    );
    (#[deriving(Show $(,$derive:ident)*)] struct $name:ident<$d:ty, $v:ident>;) => (
        impl ::std::fmt::Show for $name {
            fn fmt(&self, fmt: &mut ::std::fmt::Formatter)
                   -> ::std::result::Result<(), ::std::fmt::FormatError> {
                self.view().fmt(fmt)
            }
        }
        viewbox!(struct $name<$d,$v>;)
    );
    (#[deriving()] struct $name:ident<$d:ty, $v:ident>;) => (
        viewbox!(struct $name<$d,$v>;)
    );
)

#[cfg(test)]
mod test {
    extern crate arena;
    use self::arena::TypedArena;

    // Test data structure
    #[deriving(PartialEq,Show)]
    struct TestData {
        foo: i32,
        bar: String
    }

    // View structure that has references into the boxed data
    struct TestView<'a> {
        x: &'a i32,
        y: &'a str
    }

    // Create TestBox which combines TestData and TestView
    viewbox! {
        struct TestBox<TestData,TestView>;
    }

    // Mutable view into TestData
    struct MutView<'a> {
        x: &'a mut i32,
        y: &'a mut String
    }

    // MutBox combines TestData and MutView
    viewbox! {
        struct MutBox<TestData, MutView>;
    }

    #[test]
    fn basic() {
        // Create some data
        let t = TestData { foo: 42, bar: "Hello".to_string() };
        // Move it into box, creating a view with interior references
        let v = TestBox::new(t, |d| TestView { x: &d.foo, y: d.bar.as_slice() });

        // We can access the boxed data via the view
        assert_eq!(v.view().x, &42i32);
        assert_eq!(v.view().y, "Hello");
        // We can unwrap the view to get the data back
        let t = v.into_inner();
        assert_eq!(t.foo, 42i32);
        assert_eq!(t.bar, "Hello".to_string());
    }

    #[test]
    fn mutation() {
        let t = TestData { foo: 42, bar: "Hello".to_string() };
        let mut v = MutBox::new(t, |d| MutView { x: &mut d.foo, y: &mut d.bar });

        *v.mut_view().x = 5;
        *v.mut_view().y = "Goodbye".to_string();
        let t = v.into_inner();
        assert_eq!(t, TestData { foo: 5, bar: "Goodbye".to_string() });
    }

    #[test]
    fn error() {
        let t = TestData { foo: 42, bar: "Hello".to_string() };
        let v = MutBox::new_result(t, |_| Err("Nope")).err().unwrap();
        assert_eq!(v, (TestData { foo: 42, bar: "Hello".to_string() }, "Nope"))
    }

    struct ArenaView<'a> {
        arena: &'a TypedArena<int>,
        vec: Vec<&'a int>
    }

    viewbox! {
        struct ArenaBox<TypedArena<int>, ArenaView>;
    }

    #[test]
    fn arenaview() {
        let mut vb = ArenaBox::new(
            TypedArena::new(),
            |a| ArenaView { arena: a, vec: Vec::new()});

        spawn(proc() {
            let v = vb.mut_view();
            let a = v.arena.alloc(1);
            let b = v.arena.alloc(2);
            let c = v.arena.alloc(3);
            v.vec.push(a);
            v.vec.push(b);
            v.vec.push(c);
        });
    }
}
<|endoftext|>


<commit_before>/* Copyright (c) 2016
 * Jeff Nettleton
 *
 * Licensed under the MIT license (http://opensource.org/licenses/MIT). This
 * file may not be copied, modified, or distributed except according to those
 * terms
 */

extern crate mio;
extern crate regex;

#[cfg(test)]
mod tests;

pub mod route;
pub mod request;
pub mod response;

use std::io::Result;
use std::io::prelude::*;
use std::net::ToSocketAddrs;
use std::collections::HashMap;
use std::collections::HashSet;
use mio::tcp::{TcpListener, TcpStream};
use mio::util::Slab;
use mio::*;

pub use route::*;
pub use request::*;
pub use response::*;

struct Client {
    sock:   TcpStream,
    token:  Token,
    events: EventSet,
    i_buf:  Vec<u8>,
    o_buf:  Vec<u8>,
}

impl Client {
    fn new(sock: TcpStream, token: Token) -> Client {
        Client {
            sock:   sock,
            token:  token,
            events: EventSet::hup(),
            i_buf:  Vec::with_capacity(2048),
            o_buf:  Vec::with_capacity(2048),
        }
    }

    fn receive(&mut self) -> Result<bool> {
        let mut buf: Vec<u8> = Vec::with_capacity(2048);

        match self.sock.try_read_buf(&mut buf) {
            Ok(size)  => {
                match size {
                    Some(_) => {
                        self.events.remove(EventSet::readable());
                        self.events.insert(EventSet::writable());
                        self.i_buf.extend(buf);
                        return Ok(true);
                    },
                    None    => {
                        return Ok(false);
                    },
                }
            },
            Err(e)  => {
                panic!("failed to read from socket! <token: {:?}> <error: {:?}>", self.token, e);
            },
        }

        Ok(false)
    }

    /* write the client's output buffer to the socket.
     *
     * the following return values mean:
     *  - Ok(true):  we can close the connection
     *  - Ok(false): keep listening for writeable event and continue next time
     *  - Err(e):    something dun fucked up
     */
    fn send(&mut self) -> Result<bool> {
        let out = self.o_buf.clone();

        match self.sock.write(&out.as_slice()) {
            Ok(sz)  => {
                if sz == self.o_buf.len() {
                    /* we did it! */
                    self.events.remove(EventSet::writable());
                    return Ok(true);
                } else {
                    self.o_buf = self.o_buf.split_off(sz);

                    return Ok(false);
                }
            },
            Err(e)  => {
                panic!("failed to write to socket! <token: {:?}> <error: {:?}>", self.token, e);
            }
        }

        Ok(false)
    }

    fn register(&mut self, evl: &mut EventLoop<Canteen>) -> Result<()> {
        self.events.insert(EventSet::readable());

        evl.register(&self.sock, self.token, self.events, PollOpt::edge() | PollOpt::oneshot())
           .or_else(|e| {
               panic!("failed to register client! <token: {:?}> <error: {:?}>", self.token, e);
           })
    }

    fn reregister(&mut self, evl: &mut EventLoop<Canteen>) -> Result<()> {
        evl.reregister(&self.sock, self.token, self.events, PollOpt::edge() | PollOpt::oneshot())
           .or_else(|e| {
               panic!("failed to re-register client! <token: {:?}> <error: {:?}>", self.token, e);
           })
    }
}

/* our primary object. similar interface to Flask, the
 * Python microframework. much faster, however! :)
 */
pub struct Canteen {
    routes:  HashMap<RouteDef, Route>,
    server:  TcpListener,
    token:   Token,
    conns:   Slab<Client>,
    default: fn(&Request) -> Response,
}

impl Handler for Canteen {
    type Timeout = ();
    type Message = u32;

    fn ready(&mut self, evl: &mut EventLoop<Canteen>, token: Token, events: EventSet) {
        if events.is_error() || events.is_hup() {
            self.reset_connection(token);
            return;
        }

        if events.is_readable() {
            if self.token == token {
                self.accept(evl);
            } else {
                self.readable(evl, token)
                    .and_then(|_| self.get_client(token)
                                      .reregister(evl))
                    .unwrap_or_else(|e| {
                        panic!("read event failed! <token: {:?}> <error: {:?}>", token, e);
                    });
            }

            return;
        }

        if events.is_writable() {
            match self.get_client(token).send() {
                Ok(true)    => { self.reset_connection(token); },
                Ok(false)   => { let _ = self.get_client(token).reregister(evl); },
                Err(_)      => { panic!("something really bad happened!"); },
            }
        }
    }
}

impl Canteen {
    pub fn new<A: ToSocketAddrs>(addr: A) -> Canteen {
        Canteen {
            routes:  HashMap::new(),
            server:  TcpListener::bind(&addr.to_socket_addrs().unwrap().next().unwrap()).unwrap(),
            token:   Token(1),
            conns:   Slab::new_starting_at(Token(2), 2048),
            default: Route::err_404,
        }
    }

    pub fn add_route(&mut self, path: &str, mlist: Vec<Method>,
                     handler: fn(&Request) -> Response) -> &mut Canteen {
        let mut methods: HashSet<Method> = HashSet::new();

        for m in mlist {
            methods.insert(m);
        }

        for m in methods {
            let rd = RouteDef {
                pathdef:    String::from(path),
                method:     m,
            };

            if self.routes.contains_key(&rd) {
                panic!("a route handler for {} has already been defined!", path);
            }

            self.routes.insert(rd, Route::new(&path, m, handler));
        }

        self
    }

    pub fn set_default(&mut self, handler: fn(&Request) -> Response) {
        self.default = handler;
    }

    fn get_client<'a>(&'a mut self, token: Token) -> &'a mut Client {
        &mut self.conns[token]
    }

    fn accept(&mut self, evl: &mut EventLoop<Canteen>) {
        let (sock, _) = match self.server.accept() {
            Ok(s)   => {
                match s {
                    Some(sock)  => sock,
                    None        => {
                        panic!("failed to accept new connection!");
                    }
                }
            },
            Err(e)  => {
                panic!("failed to accept new connection! <error: {:?}>", e);
            },
        };

        match self.conns.insert_with(|token| Client::new(sock, token)) {
            Some(token) => {
                match self.get_client(token).register(evl) {
                    Ok(_)   => {},
                    Err(e)  => {
                        panic!("failed to register client! <token: {:?}> <error: {:?}>", token, e);
                    },
                }
            },
            None        => {
                panic!("failed to add client connection!");
            },
        }

        self.reregister(evl);
    }

    fn handle_request(&self, req: &mut Request) -> Vec<u8> {
        let mut handler: fn(&Request) -> Response = self.default;

        for (_, route) in &self.routes {
            match (route).is_match(req) {
                true  => {
                    handler = route.handler;
                    req.params = route.parse(&req.path);
                    break;
                },
                false => continue,
            }
        }

        handler(req).gen_output()
    }

    fn readable(&mut self, evl: &mut EventLoop<Canteen>, token: Token) -> Result<bool> {
        match self.get_client(token).receive() {
            Ok(true)    => {
                let buf = self.get_client(token).i_buf.clone();
                let rqstr = String::from_utf8(buf).unwrap();
                let mut req = Request::from_str(&rqstr);
                let output = self.handle_request(&mut req);

                self.get_client(token).o_buf.extend(output);
            },
            Ok(false)   => {},
            Err(e)      => {
                panic!("message wasn't actually readable! <error: {:?}>", e);
            },
        };

        let _ = self.get_client(token).reregister(evl);

        Ok(true)
    }

    fn reset_connection(&mut self, token: Token) {
        /* kill the connection */
        self.conns.remove(token);
    }

    fn register(&mut self, evl: &mut EventLoop<Canteen>) -> Result<()> {
        evl.register(&self.server, self.token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot())
           .or_else(|e| {
               panic!("failed to register server! <token: {:?}> <error: {:?}>", self.token, e);
           })
    }

    fn reregister(&mut self, evl: &mut EventLoop<Canteen>) {
        match evl.reregister(&self.server, self.token,
                             EventSet::readable(),
                             PollOpt::edge() | PollOpt::oneshot()) {
            Ok(_)   => {},
            Err(e)  => {
               panic!("failed to register server! <token: {:?}> <error: {:?}>", self.token, e);
           }
        };
    }

    pub fn run(&mut self) {
        let mut evl = EventLoop::new().unwrap();
        self.register(&mut evl).ok();
        evl.run(self).unwrap();
    }
}
<commit_msg>make writes a little more sane<commit_after>/* Copyright (c) 2016
 * Jeff Nettleton
 *
 * Licensed under the MIT license (http://opensource.org/licenses/MIT). This
 * file may not be copied, modified, or distributed except according to those
 * terms
 */

extern crate mio;
extern crate regex;

#[cfg(test)]
mod tests;

pub mod route;
pub mod request;
pub mod response;

use std::io::Result;
use std::io::prelude::*;
use std::net::ToSocketAddrs;
use std::collections::HashMap;
use std::collections::HashSet;
use mio::tcp::{TcpListener, TcpStream};
use mio::util::Slab;
use mio::*;

pub use route::*;
pub use request::*;
pub use response::*;

struct Client {
    sock:   TcpStream,
    token:  Token,
    events: EventSet,
    i_buf:  Vec<u8>,
    o_buf:  Vec<u8>,
}

impl Client {
    fn new(sock: TcpStream, token: Token) -> Client {
        Client {
            sock:   sock,
            token:  token,
            events: EventSet::hup(),
            i_buf:  Vec::with_capacity(2048),
            o_buf:  Vec::with_capacity(2048),
        }
    }

    fn receive(&mut self) -> Result<bool> {
        let mut buf: Vec<u8> = Vec::with_capacity(2048);

        match self.sock.try_read_buf(&mut buf) {
            Ok(size)  => {
                match size {
                    Some(_) => {
                        self.events.remove(EventSet::readable());
                        self.events.insert(EventSet::writable());
                        self.i_buf.extend(buf);
                        return Ok(true);
                    },
                    None    => {
                        return Ok(false);
                    },
                }
            },
            Err(e)  => {
                panic!("failed to read from socket! <token: {:?}> <error: {:?}>", self.token, e);
            },
        }

        Ok(false)
    }

    /* write the client's output buffer to the socket.
     *
     * the following return values mean:
     *  - Ok(true):  we can close the connection
     *  - Ok(false): keep listening for writeable event and continue next time
     *  - Err(e):    something dun fucked up
     */
    fn send(&mut self) -> Result<bool> {
        while self.o_buf.len() > 0 {
            match self.sock.write(&self.o_buf.as_slice()) {
                Ok(sz)  => {
                    if sz == self.o_buf.len() {
                        /* we did it! */
                        self.events.remove(EventSet::writable());
                        break;
                    } else {
                        /* keep going */
                        self.o_buf = self.o_buf.split_off(sz);
                    }
                },
                Err(e)  => {
                    panic!("failed to write to socket! <token: {:?}> <error: {:?}>", self.token, e);
                }
            }
        }

        Ok(true)
    }

    fn register(&mut self, evl: &mut EventLoop<Canteen>) -> Result<()> {
        self.events.insert(EventSet::readable());

        evl.register(&self.sock, self.token, self.events, PollOpt::edge() | PollOpt::oneshot())
           .or_else(|e| {
               panic!("failed to register client! <token: {:?}> <error: {:?}>", self.token, e);
           })
    }

    fn reregister(&mut self, evl: &mut EventLoop<Canteen>) -> Result<()> {
        evl.reregister(&self.sock, self.token, self.events, PollOpt::edge() | PollOpt::oneshot())
           .or_else(|e| {
               panic!("failed to re-register client! <token: {:?}> <error: {:?}>", self.token, e);
           })
    }
}

/* our primary object. similar interface to Flask, the
 * Python microframework. much faster, however! :)
 */
pub struct Canteen {
    routes:  HashMap<RouteDef, Route>,
    server:  TcpListener,
    token:   Token,
    conns:   Slab<Client>,
    default: fn(&Request) -> Response,
}

impl Handler for Canteen {
    type Timeout = ();
    type Message = u32;

    fn ready(&mut self, evl: &mut EventLoop<Canteen>, token: Token, events: EventSet) {
        if events.is_error() || events.is_hup() {
            self.reset_connection(token);
            return;
        }

        if events.is_readable() {
            if self.token == token {
                self.accept(evl);
            } else {
                self.readable(evl, token)
                    .and_then(|_| self.get_client(token)
                                      .reregister(evl))
                    .unwrap_or_else(|e| {
                        panic!("read event failed! <token: {:?}> <error: {:?}>", token, e);
                    });
            }

            return;
        }

        if events.is_writable() {
            match self.get_client(token).send() {
                Ok(true)    => { self.reset_connection(token); },
                Ok(false)   => { let _ = self.get_client(token).reregister(evl); },
                Err(_)      => { panic!("something really bad happened!"); },
            }
        }
    }
}

impl Canteen {
    pub fn new<A: ToSocketAddrs>(addr: A) -> Canteen {
        Canteen {
            routes:  HashMap::new(),
            server:  TcpListener::bind(&addr.to_socket_addrs().unwrap().next().unwrap()).unwrap(),
            token:   Token(1),
            conns:   Slab::new_starting_at(Token(2), 2048),
            default: Route::err_404,
        }
    }

    pub fn add_route(&mut self, path: &str, mlist: Vec<Method>,
                     handler: fn(&Request) -> Response) -> &mut Canteen {
        let mut methods: HashSet<Method> = HashSet::new();

        for m in mlist {
            methods.insert(m);
        }

        for m in methods {
            let rd = RouteDef {
                pathdef:    String::from(path),
                method:     m,
            };

            if self.routes.contains_key(&rd) {
                panic!("a route handler for {} has already been defined!", path);
            }

            self.routes.insert(rd, Route::new(&path, m, handler));
        }

        self
    }

    pub fn set_default(&mut self, handler: fn(&Request) -> Response) {
        self.default = handler;
    }

    fn get_client<'a>(&'a mut self, token: Token) -> &'a mut Client {
        &mut self.conns[token]
    }

    fn accept(&mut self, evl: &mut EventLoop<Canteen>) {
        let (sock, _) = match self.server.accept() {
            Ok(s)   => {
                match s {
                    Some(sock)  => sock,
                    None        => {
                        panic!("failed to accept new connection!");
                    }
                }
            },
            Err(e)  => {
                panic!("failed to accept new connection! <error: {:?}>", e);
            },
        };

        match self.conns.insert_with(|token| Client::new(sock, token)) {
            Some(token) => {
                match self.get_client(token).register(evl) {
                    Ok(_)   => {},
                    Err(e)  => {
                        panic!("failed to register client! <token: {:?}> <error: {:?}>", token, e);
                    },
                }
            },
            None        => {
                panic!("failed to add client connection!");
            },
        }

        self.reregister(evl);
    }

    fn handle_request(&self, req: &mut Request) -> Vec<u8> {
        let mut handler: fn(&Request) -> Response = self.default;

        for (_, route) in &self.routes {
            match (route).is_match(req) {
                true  => {
                    handler = route.handler;
                    req.params = route.parse(&req.path);
                    break;
                },
                false => continue,
            }
        }

        handler(req).gen_output()
    }

    fn readable(&mut self, evl: &mut EventLoop<Canteen>, token: Token) -> Result<bool> {
        match self.get_client(token).receive() {
            Ok(true)    => {
                let buf = self.get_client(token).i_buf.clone();
                let rqstr = String::from_utf8(buf).unwrap();
                let mut req = Request::from_str(&rqstr);
                let output = self.handle_request(&mut req);

                self.get_client(token).o_buf.extend(output);
            },
            Ok(false)   => {},
            Err(e)      => {
                panic!("message wasn't actually readable! <error: {:?}>", e);
            },
        };

        let _ = self.get_client(token).reregister(evl);

        Ok(true)
    }

    fn reset_connection(&mut self, token: Token) {
        /* kill the connection */
        self.conns.remove(token);
    }

    fn register(&mut self, evl: &mut EventLoop<Canteen>) -> Result<()> {
        evl.register(&self.server, self.token, EventSet::readable(), PollOpt::edge() | PollOpt::oneshot())
           .or_else(|e| {
               panic!("failed to register server! <token: {:?}> <error: {:?}>", self.token, e);
           })
    }

    fn reregister(&mut self, evl: &mut EventLoop<Canteen>) {
        match evl.reregister(&self.server, self.token,
                             EventSet::readable(),
                             PollOpt::edge() | PollOpt::oneshot()) {
            Ok(_)   => {},
            Err(e)  => {
               panic!("failed to register server! <token: {:?}> <error: {:?}>", self.token, e);
           }
        };
    }

    pub fn run(&mut self) {
        let mut evl = EventLoop::new().unwrap();
        self.register(&mut evl).ok();
        evl.run(self).unwrap();
    }
}
<|endoftext|>


<commit_before>//! [tui](https://github.com/fdehau/tui-rs) is a library used to build rich
//! terminal users interfaces and dashboards.
//!
//! ![](https://raw.githubusercontent.com/fdehau/tui-rs/master/assets/demo.gif)
//!
//! # Get started
//!
//! ## Adding `tui` as a dependency
//!
//! ```toml
//! [dependencies]
//! tui = "0.5"
//! termion = "1.5"
//! ```
//!
//! The crate is using the `termion` backend by default but if for some reason you might want to use
//! the `rustbox` backend instead, you need the to replace your dependency specification by:
//!
//! ```toml
//! [dependencies]
//! rustbox = "0.11"
//!
//! [dependencies.tui]
//! version = "0.5"
//! default-features = false
//! features = ['rustbox']
//! ```
//!
//! The same logic applies for all other available backends.
//!
//! ## Creating a `Terminal`
//!
//! Every application using `tui` should start by instantiating a `Terminal`. It is a light
//! abstraction over available backends that provides basic functionalities such as clearing the
//! screen, hiding the cursor, etc.
//!
//! ```rust,no_run
//! use std::io;
//! use tui::Terminal;
//! use tui::backend::TermionBackend;
//! use termion::raw::IntoRawMode;
//!
//! fn main() -> Result<(), io::Error> {
//!     let stdout = io::stdout().into_raw_mode()?;
//!     let backend = TermionBackend::new(stdout);
//!     let mut terminal = Terminal::new(backend)?;
//!     Ok(())
//! }
//! ```
//!
//! If you had previously chosen `rustbox` as a backend, the terminal can be created in a similar
//! way:
//!
//! ```rust,ignore
//! use tui::Terminal;
//! use tui::backend::RustboxBackend;
//!
//! fn main() -> Result<(), io::Error> {
//!     let backend = RustboxBackend::new()?;
//!     let mut terminal = Terminal::new(backend);
//!     Ok(())
//! }
//! ```
//!
//! You may also refer to the examples to find out how to create a `Terminal` for each available
//! backend.
//!
//! ## Building a User Interface (UI)
//!
//! Every component of your interface will be implementing the `Widget` trait.  The library comes
//! with a predefined set of widgets that should met most of your use cases. You are also free to
//! implement your owns.
//!
//! Each widget follows a builder pattern API providing a default configuration along with methods
//! to customize them. The widget is then registered using its `render` method that take a `Frame`
//! instance and an area to draw to.
//!
//! The following example renders a block of the size of the terminal:
//!
//! ```rust,no_run
//! use std::io;
//! use termion::raw::IntoRawMode;
//! use tui::Terminal;
//! use tui::backend::TermionBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Layout, Constraint, Direction};
//!
//! fn main() -> Result<(), io::Error> {
//!     let stdout = io::stdout().into_raw_mode()?;
//!     let backend = TermionBackend::new(stdout);
//!     let mut terminal = Terminal::new(backend)?;
//!     terminal.draw(|mut f| {
//!         let size = f.size();
//!         Block::default()
//!             .title("Block")
//!             .borders(Borders::ALL)
//!             .render(&mut f, size);
//!     })
//! }
//! ```
//!
//! ## Layout
//!
//! The library comes with a basic yet useful layout management object called `Layout`. As you may
//! see below and in the examples, the library makes heavy use of the builder pattern to provide
//! full customization. And `Layout` is no exception:
//!
//! ```rust,no_run
//! use std::io;
//! use termion::raw::IntoRawMode;
//! use tui::Terminal;
//! use tui::backend::TermionBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Layout, Constraint, Direction};
//!
//! fn main() -> Result<(), io::Error> {
//!     let stdout = io::stdout().into_raw_mode()?;
//!     let backend = TermionBackend::new(stdout);
//!     let mut terminal = Terminal::new(backend)?;
//!     terminal.draw(|mut f| {
//!         let chunks = Layout::default()
//!             .direction(Direction::Vertical)
//!             .margin(1)
//!             .constraints(
//!                 [
//!                     Constraint::Percentage(10),
//!                     Constraint::Percentage(80),
//!                     Constraint::Percentage(10)
//!                 ].as_ref()
//!             )
//!             .split(f.size());
//!         Block::default()
//!              .title("Block")
//!              .borders(Borders::ALL)
//!              .render(&mut f, chunks[0]);
//!         Block::default()
//!              .title("Block 2")
//!              .borders(Borders::ALL)
//!              .render(&mut f, chunks[2]);
//!     })
//! }
//! ```
//!
//! This let you describe responsive terminal UI by nesting layouts. You should note that by
//! default the computed layout tries to fill the available space completely. So if for any reason
//! you might need a blank space somewhere, try to pass an additional constraint and don't use the
//! corresponding area.

pub mod backend;
pub mod buffer;
pub mod layout;
pub mod style;
pub mod symbols;
pub mod terminal;
pub mod widgets;

pub use self::terminal::{Frame, Terminal};
<commit_msg>Enable build failure on compilation warnings<commit_after>//! [tui](https://github.com/fdehau/tui-rs) is a library used to build rich
//! terminal users interfaces and dashboards.
//!
//! ![](https://raw.githubusercontent.com/fdehau/tui-rs/master/assets/demo.gif)
//!
//! # Get started
//!
//! ## Adding `tui` as a dependency
//!
//! ```toml
//! [dependencies]
//! tui = "0.5"
//! termion = "1.5"
//! ```
//!
//! The crate is using the `termion` backend by default but if for some reason you might want to use
//! the `rustbox` backend instead, you need the to replace your dependency specification by:
//!
//! ```toml
//! [dependencies]
//! rustbox = "0.11"
//!
//! [dependencies.tui]
//! version = "0.5"
//! default-features = false
//! features = ['rustbox']
//! ```
//!
//! The same logic applies for all other available backends.
//!
//! ## Creating a `Terminal`
//!
//! Every application using `tui` should start by instantiating a `Terminal`. It is a light
//! abstraction over available backends that provides basic functionalities such as clearing the
//! screen, hiding the cursor, etc.
//!
//! ```rust,no_run
//! use std::io;
//! use tui::Terminal;
//! use tui::backend::TermionBackend;
//! use termion::raw::IntoRawMode;
//!
//! fn main() -> Result<(), io::Error> {
//!     let stdout = io::stdout().into_raw_mode()?;
//!     let backend = TermionBackend::new(stdout);
//!     let mut terminal = Terminal::new(backend)?;
//!     Ok(())
//! }
//! ```
//!
//! If you had previously chosen `rustbox` as a backend, the terminal can be created in a similar
//! way:
//!
//! ```rust,ignore
//! use tui::Terminal;
//! use tui::backend::RustboxBackend;
//!
//! fn main() -> Result<(), io::Error> {
//!     let backend = RustboxBackend::new()?;
//!     let mut terminal = Terminal::new(backend);
//!     Ok(())
//! }
//! ```
//!
//! You may also refer to the examples to find out how to create a `Terminal` for each available
//! backend.
//!
//! ## Building a User Interface (UI)
//!
//! Every component of your interface will be implementing the `Widget` trait.  The library comes
//! with a predefined set of widgets that should met most of your use cases. You are also free to
//! implement your owns.
//!
//! Each widget follows a builder pattern API providing a default configuration along with methods
//! to customize them. The widget is then registered using its `render` method that take a `Frame`
//! instance and an area to draw to.
//!
//! The following example renders a block of the size of the terminal:
//!
//! ```rust,no_run
//! use std::io;
//! use termion::raw::IntoRawMode;
//! use tui::Terminal;
//! use tui::backend::TermionBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Layout, Constraint, Direction};
//!
//! fn main() -> Result<(), io::Error> {
//!     let stdout = io::stdout().into_raw_mode()?;
//!     let backend = TermionBackend::new(stdout);
//!     let mut terminal = Terminal::new(backend)?;
//!     terminal.draw(|mut f| {
//!         let size = f.size();
//!         Block::default()
//!             .title("Block")
//!             .borders(Borders::ALL)
//!             .render(&mut f, size);
//!     })
//! }
//! ```
//!
//! ## Layout
//!
//! The library comes with a basic yet useful layout management object called `Layout`. As you may
//! see below and in the examples, the library makes heavy use of the builder pattern to provide
//! full customization. And `Layout` is no exception:
//!
//! ```rust,no_run
//! use std::io;
//! use termion::raw::IntoRawMode;
//! use tui::Terminal;
//! use tui::backend::TermionBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Layout, Constraint, Direction};
//!
//! fn main() -> Result<(), io::Error> {
//!     let stdout = io::stdout().into_raw_mode()?;
//!     let backend = TermionBackend::new(stdout);
//!     let mut terminal = Terminal::new(backend)?;
//!     terminal.draw(|mut f| {
//!         let chunks = Layout::default()
//!             .direction(Direction::Vertical)
//!             .margin(1)
//!             .constraints(
//!                 [
//!                     Constraint::Percentage(10),
//!                     Constraint::Percentage(80),
//!                     Constraint::Percentage(10)
//!                 ].as_ref()
//!             )
//!             .split(f.size());
//!         Block::default()
//!              .title("Block")
//!              .borders(Borders::ALL)
//!              .render(&mut f, chunks[0]);
//!         Block::default()
//!              .title("Block 2")
//!              .borders(Borders::ALL)
//!              .render(&mut f, chunks[2]);
//!     })
//! }
//! ```
//!
//! This let you describe responsive terminal UI by nesting layouts. You should note that by
//! default the computed layout tries to fill the available space completely. So if for any reason
//! you might need a blank space somewhere, try to pass an additional constraint and don't use the
//! corresponding area.

#![deny(warnings)]

pub mod backend;
pub mod buffer;
pub mod layout;
pub mod style;
pub mod symbols;
pub mod terminal;
pub mod widgets;

pub use self::terminal::{Frame, Terminal};
<|endoftext|>


<commit_before>//! [aurelius](https://github.com/euclio/aurelius) is a complete solution for live-previewing
//! markdown as HTML.
//!
//! This crate provides a server that can render and update an HTML preview of markdown without a
//! client-side refresh. Upon receiving an HTTP request, the server responds with an HTML page
//! containing a rendering of supplied markdown. Client-side JavaScript then initiates a WebSocket
//! connection which allows the server to push changes to the client.
//!
//! This crate was designed to power [vim-markdown-composer], a markdown preview plugin for
//! [Neovim](http://neovim.io), but it may be used to implement similar plugins for any editor.
//! See [vim-markdown-composer] for a real-world usage example.
//!
//! # Example
//!
//! ```no_run
//! use std::net::SocketAddr;
//! use aurelius::Server;
//!
//! # tokio_test::block_on(async {
//! let addr = "127.0.0.1:1337".parse::<SocketAddr>()?;
//! let mut server = Server::bind(&addr).await?;
//!
//! server.open_browser()?;
//!
//! server.send("# Hello, world!");
//! #   Ok::<_, Box<dyn std::error::Error>>(())
//! # });
//! ```
//!
//! # Acknowledgments
//! This crate is inspired by suan's
//! [instant-markdown-d](https://github.com/suan/instant-markdown-d).
//!
//! # Why the name?
//! "Aurelius" is a Roman *gens* (family name) shared by many famous Romans, including emperor
//! Marcus Aurelius, one of the "Five Good Emperors." The gens itself originates from the Latin
//! *aureus* meaning "golden." Also, tell me that "Markdown Aurelius" isn't a great pun.
//!
//! [Aurelia (gens) on Wikipedia](https://en.wikipedia.org/wiki/Aurelia_(gens)).
//!
//! [vim-markdown-composer]: https://github.com/euclio/vim-markdown-composer

#![warn(missing_debug_implementations)]
#![warn(missing_docs)]

use std::cell::RefCell;
use std::fs;
use std::io;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::{Arc, RwLock};

use axum::{extract::Extension, http::Uri, routing::get, Router};
use pulldown_cmark::{Options, Parser};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::process::Command;
use tokio::sync::oneshot;
use tokio::sync::watch::{self, Sender};
use tower_http::trace::TraceLayer;
use tracing::log::*;

mod service;

/// Markdown preview server.
///
/// Listens for HTTP connections and serves a page containing a live markdown preview. The page
/// contains JavaScript to open a websocket connection back to the server for rendering updates.
#[derive(Debug)]
pub struct Server {
    addr: SocketAddr,
    config: Arc<RwLock<Config>>,
    external_renderer: Option<RefCell<Command>>,
    tx: Sender<String>,
    _shutdown_tx: oneshot::Sender<()>,
}

impl Server {
    /// Binds the server to a specified address.
    ///
    /// Binding to port 0 will request a port assignment from the OS. Use [`addr()`][Self::addr]
    /// to determine what port was assigned.
    ///
    /// The server must be bound using a Tokio runtime.
    pub async fn bind(addr: &SocketAddr) -> io::Result<Self> {
        let (tx, rx) = watch::channel(String::new());
        let (shutdown_tx, shutdown_rx) = oneshot::channel();

        let config = Arc::default();

        let app = Router::new()
            .route("/", get(service::websocket_handler))
            .route("/__/*path", get(service::serve_asset))
            .fallback(get(service::serve_static_file))
            .layer(Extension(Arc::clone(&config)))
            .layer(Extension(rx))
            .layer(TraceLayer::new_for_http());

        let http_server = axum::Server::bind(addr).serve(app.into_make_service());

        let addr = http_server.local_addr();
        info!("listening on {:?}", addr);

        let http_server = http_server.with_graceful_shutdown(async move {
            let _ = shutdown_rx.await;
        });

        tokio::spawn(http_server);

        Ok(Server {
            addr,
            config,
            external_renderer: None,
            tx,
            _shutdown_tx: shutdown_tx,
        })
    }

    /// Returns the socket address that the server is listening on.
    pub fn addr(&self) -> SocketAddr {
        self.addr
    }

    /// Publish new markdown to be rendered by the server.
    ///
    /// The new HTML will be sent to all connected websocket clients.
    ///
    /// # Errors
    ///
    /// This method forwards errors from an external renderer, if set. Otherwise, the method is
    /// infallible.
    pub async fn send(&self, markdown: &str) -> io::Result<()> {
        let html = if let Some(renderer) = &self.external_renderer {
            let child = renderer.borrow_mut().spawn()?;

            child.stdin.unwrap().write_all(markdown.as_bytes()).await?;

            let mut html = String::with_capacity(markdown.len());
            child.stdout.unwrap().read_to_string(&mut html).await?;

            html
        } else {
            let mut html = String::with_capacity(markdown.len());
            let parser = Parser::new_ext(
                markdown,
                Options::ENABLE_FOOTNOTES
                    | Options::ENABLE_TABLES
                    | Options::ENABLE_STRIKETHROUGH
                    | Options::ENABLE_TASKLISTS,
            );

            pulldown_cmark::html::push_html(&mut html, parser);

            html
        };

        self.tx.send_replace(html);

        Ok(())
    }

    /// Set the directory that static files will be served from.
    ///
    /// This can be thought of as the "working directory" of the server. Any HTTP requests with
    /// non-root paths will be joined to this folder and used to serve files from the filesystem.
    /// Typically this is used to serve image links relative to the markdown file.
    ///
    /// By default, the server will not serve static files.
    pub fn set_static_root(&mut self, root: impl Into<PathBuf>) {
        self.config.write().unwrap().static_root = Some(root.into());
    }

    /// Set the highlight.js theme used for code blocks.
    ///
    /// Defaults to "github".
    pub fn set_highlight_theme(&mut self, theme: String) {
        self.config.write().unwrap().highlight_theme = theme;
    }

    /// Set custom CSS links and files to be served with the rendered HTML.
    ///
    /// Accepts URLs and absolute paths. URLs will be inserted as `<link>` tags. The contents of
    /// the paths will be read from disk and served in `<style>` tags.
    pub fn set_custom_css(&mut self, stylesheets: Vec<String>) -> io::Result<()> {
        let mut files = vec![];
        let mut links = vec![];

        for stylesheet in &stylesheets {
            // NB: Absolute paths on Windows will parse as URLs.
            match stylesheet.parse::<Uri>() {
                Ok(url)
                    if url.scheme_str() == Some("http") || url.scheme_str() == Some("https") =>
                {
                    links.push(url)
                }
                _ => files.push(Path::new(stylesheet.trim_start_matches("file://"))),
            }
        }

        let mut config = self.config.write().unwrap();

        config.custom_styles = files
            .into_iter()
            .map(fs::read_to_string)
            .collect::<Result<Vec<_>, _>>()?;
        config.css_links = links;

        Ok(())
    }

    /// Set an external program to use for rendering the markdown.
    ///
    /// By default, aurelius uses [`pulldown_cmark`] to render markdown in-process.
    /// `pulldown-cmark` is an extremely fast, [CommonMark]-compliant parser that is sufficient
    /// for most use-cases. However, other markdown renderers may provide additional features.
    ///
    /// The `Command` supplied to this function should expect markdown on stdin and print HTML on
    /// stdout.
    ///
    /// # Example
    ///
    /// To use [`pandoc`] to render markdown:
    ///
    ///
    /// ```no_run
    /// # async fn dox() -> Result<(), Box<dyn std::error::Error>> {
    /// use std::net::SocketAddr;
    /// use tokio::process::Command;
    /// use aurelius::Server;
    ///
    /// let addr = "127.0.0.1:1337".parse::<SocketAddr>()?;
    /// let mut server = Server::bind(&addr).await?;
    ///
    /// let mut pandoc = Command::new("pandoc");
    /// pandoc.args(&["-f", "markdown", "-t", "html"]);
    ///
    /// server.set_external_renderer(pandoc);
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [`pulldown_cmark`]: https://github.com/raphlinus/pulldown-cmark
    /// [CommonMark]: https://commonmark.org/
    /// [`pandoc`]: https://pandoc.org/
    pub fn set_external_renderer(&mut self, mut command: Command) {
        command
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::null());
        self.external_renderer = Some(RefCell::new(command));
    }

    /// Opens the user's default browser with the server's URL in the background.
    ///
    /// This function uses platform-specific utilities to determine the browser. The following
    /// platforms are supported:
    ///
    /// | Platform | Program    |
    /// | -------- | ---------- |
    /// | Linux    | `xdg-open` |
    /// | OS X     | `open -g`  |
    /// | Windows  | `explorer` |
    pub fn open_browser(&self) -> io::Result<()> {
        let command = if cfg!(target_os = "macos") {
            let mut command = Command::new("open");
            command.arg("-g");
            command
        } else if cfg!(target_os = "windows") {
            Command::new("explorer")
        } else {
            Command::new("xdg-open")
        };

        self.open_specific_browser(command)
    }

    /// Opens a browser with a specified command. The HTTP address of the server will be appended
    /// to the command as an argument.
    pub fn open_specific_browser(&self, mut command: Command) -> io::Result<()> {
        command.arg(&format!("http://{}", self.addr()));

        command.stdout(Stdio::null()).stderr(Stdio::null());

        info!("spawning browser: {:?}", command);
        command.spawn()?;
        Ok(())
    }
}

#[derive(Debug)]
pub(crate) struct Config {
    static_root: Option<PathBuf>,
    highlight_theme: String,
    css_links: Vec<Uri>,
    custom_styles: Vec<String>,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            static_root: None,
            highlight_theme: String::from("github"),
            css_links: vec![],
            custom_styles: vec![],
        }
    }
}

#[cfg(test)]
mod tests {
    use std::error::Error;

    use async_tungstenite::tungstenite::{self, error::ProtocolError, Message};
    use async_tungstenite::WebSocketStream;
    use futures::{AsyncRead, AsyncWrite};
    use futures::{SinkExt, StreamExt, TryStreamExt};
    use matches::assert_matches;
    use tokio::net::lookup_host;
    use tokio::time::{timeout, Duration};

    use super::Server;

    async fn new_server() -> anyhow::Result<Server> {
        let addr = lookup_host("localhost:0").await?.next().unwrap();
        Ok(Server::bind(&addr).await?)
    }

    async fn assert_websocket_closed<S: AsyncRead + AsyncWrite + Unpin>(
        websocket: &mut WebSocketStream<S>,
    ) {
        assert_matches!(
            websocket.send(Message::Text(String::new())).await,
            Err(tungstenite::Error::AlreadyClosed
                | tungstenite::Error::Protocol(ProtocolError::SendAfterClosing))
        );
    }

    #[tokio::test]
    async fn connect_http() -> anyhow::Result<()> {
        let server = new_server().await?;

        let res = reqwest::get(&format!("http://{}", server.addr())).await?;

        assert!(res.headers()["Content-Type"]
            .to_str()
            .unwrap()
            .contains("text/html"));

        let body = res.text().await?;

        assert!(body.contains(""));

        Ok(())
    }

    #[tokio::test]
    async fn connect_websocket() -> anyhow::Result<()> {
        let server = new_server().await?;

        async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        Ok(())
    }

    #[tokio::test]
    async fn send_with_no_clients() -> anyhow::Result<()> {
        let server = new_server().await?;

        server.send("This shouldn't hang").await?;

        Ok(())
    }

    #[tokio::test]
    async fn send_html() -> anyhow::Result<()> {
        let server = new_server().await.unwrap();

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr()))
                .await
                .unwrap();

        server.send("
Hello, world!
").await.unwrap();
        let message = websocket.next().await.unwrap().unwrap();
        assert_eq!(message.to_text().unwrap(), "
Hello, world!
");

        server.send("
Goodbye, world!
").await.unwrap();
        let message = websocket.next().await.unwrap().unwrap();
        assert_eq!(message.to_text().unwrap(), "
Goodbye, world!
");

        Ok(())
    }

    #[tokio::test]
    async fn send_markdown() -> anyhow::Result<()> {
        let server = new_server().await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        server.send("*Hello*").await?;
        let message = websocket.next().await.unwrap()?;
        assert_eq!(message.to_text()?.trim(), "
Hello
");

        Ok(())
    }

    #[tokio::test]
    async fn close_websockets_on_drop() -> Result<(), Box<dyn Error>> {
        let server = new_server().await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        drop(server);

        assert_matches!(websocket.next().await, Some(Ok(Message::Close(None))));

        assert_websocket_closed(&mut websocket).await;

        Ok(())
    }

    #[tokio::test]
    async fn queue_html_if_no_clients() -> Result<(), Box<dyn Error>> {
        let server = new_server().await?;

        server.send("# Markdown").await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        let message = timeout(Duration::from_secs(5), websocket.try_next())
            .await??
            .unwrap();
        assert!(message.is_text(), "message was not text: {:?}", message);
        assert_eq!(message.to_text().unwrap().trim(), "
Markdown
");

        Ok(())
    }

    #[tokio::test]
    async fn closed_websocket_removed_from_clients() -> Result<(), Box<dyn Error>> {
        let server = new_server().await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        websocket.close(None).await?;

        assert_websocket_closed(&mut websocket).await;

        server.send("# Markdown").await?;

        Ok(())
    }
}
<commit_msg>reuse output allocation<commit_after>//! [aurelius](https://github.com/euclio/aurelius) is a complete solution for live-previewing
//! markdown as HTML.
//!
//! This crate provides a server that can render and update an HTML preview of markdown without a
//! client-side refresh. Upon receiving an HTTP request, the server responds with an HTML page
//! containing a rendering of supplied markdown. Client-side JavaScript then initiates a WebSocket
//! connection which allows the server to push changes to the client.
//!
//! This crate was designed to power [vim-markdown-composer], a markdown preview plugin for
//! [Neovim](http://neovim.io), but it may be used to implement similar plugins for any editor.
//! See [vim-markdown-composer] for a real-world usage example.
//!
//! # Example
//!
//! ```no_run
//! use std::net::SocketAddr;
//! use aurelius::Server;
//!
//! # tokio_test::block_on(async {
//! let addr = "127.0.0.1:1337".parse::<SocketAddr>()?;
//! let mut server = Server::bind(&addr).await?;
//!
//! server.open_browser()?;
//!
//! server.send("# Hello, world!");
//! #   Ok::<_, Box<dyn std::error::Error>>(())
//! # });
//! ```
//!
//! # Acknowledgments
//! This crate is inspired by suan's
//! [instant-markdown-d](https://github.com/suan/instant-markdown-d).
//!
//! # Why the name?
//! "Aurelius" is a Roman *gens* (family name) shared by many famous Romans, including emperor
//! Marcus Aurelius, one of the "Five Good Emperors." The gens itself originates from the Latin
//! *aureus* meaning "golden." Also, tell me that "Markdown Aurelius" isn't a great pun.
//!
//! [Aurelia (gens) on Wikipedia](https://en.wikipedia.org/wiki/Aurelia_(gens)).
//!
//! [vim-markdown-composer]: https://github.com/euclio/vim-markdown-composer

#![warn(missing_debug_implementations)]
#![warn(missing_docs)]

use std::cell::RefCell;
use std::fs;
use std::io;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::{Arc, RwLock};

use axum::{extract::Extension, http::Uri, routing::get, Router};
use pulldown_cmark::{Options, Parser};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::process::Command;
use tokio::sync::oneshot;
use tokio::sync::watch::{self, Sender};
use tower_http::trace::TraceLayer;
use tracing::log::*;

mod service;

/// Markdown preview server.
///
/// Listens for HTTP connections and serves a page containing a live markdown preview. The page
/// contains JavaScript to open a websocket connection back to the server for rendering updates.
#[derive(Debug)]
pub struct Server {
    addr: SocketAddr,
    config: Arc<RwLock<Config>>,
    external_renderer: Option<RefCell<Command>>,
    output: RefCell<String>,
    tx: Sender<String>,
    _shutdown_tx: oneshot::Sender<()>,
}

impl Server {
    /// Binds the server to a specified address.
    ///
    /// Binding to port 0 will request a port assignment from the OS. Use [`addr()`][Self::addr]
    /// to determine what port was assigned.
    ///
    /// The server must be bound using a Tokio runtime.
    pub async fn bind(addr: &SocketAddr) -> io::Result<Self> {
        let (tx, rx) = watch::channel(String::new());
        let (shutdown_tx, shutdown_rx) = oneshot::channel();

        let config = Arc::default();

        let app = Router::new()
            .route("/", get(service::websocket_handler))
            .route("/__/*path", get(service::serve_asset))
            .fallback(get(service::serve_static_file))
            .layer(Extension(Arc::clone(&config)))
            .layer(Extension(rx))
            .layer(TraceLayer::new_for_http());

        let http_server = axum::Server::bind(addr).serve(app.into_make_service());

        let addr = http_server.local_addr();
        info!("listening on {:?}", addr);

        let http_server = http_server.with_graceful_shutdown(async move {
            let _ = shutdown_rx.await;
        });

        tokio::spawn(http_server);

        Ok(Server {
            addr,
            config,
            external_renderer: None,
            tx,
            output: RefCell::new(String::new()),
            _shutdown_tx: shutdown_tx,
        })
    }

    /// Returns the socket address that the server is listening on.
    pub fn addr(&self) -> SocketAddr {
        self.addr
    }

    /// Publish new markdown to be rendered by the server.
    ///
    /// The new HTML will be sent to all connected websocket clients.
    ///
    /// # Errors
    ///
    /// This method forwards errors from an external renderer, if set. Otherwise, the method is
    /// infallible.
    pub async fn send(&self, markdown: &str) -> io::Result<()> {
        let mut output = self.output.take();
        output.clear();

        // Heuristic taken from rustdoc
        output.reserve(markdown.len() * 3 / 2);

        if let Some(renderer) = &self.external_renderer {
            let child = renderer.borrow_mut().spawn()?;

            child.stdin.unwrap().write_all(markdown.as_bytes()).await?;

            child.stdout.unwrap().read_to_string(&mut output).await?;
        } else {
            let parser = Parser::new_ext(
                markdown,
                Options::ENABLE_FOOTNOTES
                    | Options::ENABLE_TABLES
                    | Options::ENABLE_STRIKETHROUGH
                    | Options::ENABLE_TASKLISTS,
            );

            pulldown_cmark::html::push_html(&mut output, parser);
        };

        self.output.replace(self.tx.send_replace(output));

        Ok(())
    }

    /// Set the directory that static files will be served from.
    ///
    /// This can be thought of as the "working directory" of the server. Any HTTP requests with
    /// non-root paths will be joined to this folder and used to serve files from the filesystem.
    /// Typically this is used to serve image links relative to the markdown file.
    ///
    /// By default, the server will not serve static files.
    pub fn set_static_root(&mut self, root: impl Into<PathBuf>) {
        self.config.write().unwrap().static_root = Some(root.into());
    }

    /// Set the highlight.js theme used for code blocks.
    ///
    /// Defaults to "github".
    pub fn set_highlight_theme(&mut self, theme: String) {
        self.config.write().unwrap().highlight_theme = theme;
    }

    /// Set custom CSS links and files to be served with the rendered HTML.
    ///
    /// Accepts URLs and absolute paths. URLs will be inserted as `<link>` tags. The contents of
    /// the paths will be read from disk and served in `<style>` tags.
    pub fn set_custom_css(&mut self, stylesheets: Vec<String>) -> io::Result<()> {
        let mut files = vec![];
        let mut links = vec![];

        for stylesheet in &stylesheets {
            // NB: Absolute paths on Windows will parse as URLs.
            match stylesheet.parse::<Uri>() {
                Ok(url)
                    if url.scheme_str() == Some("http") || url.scheme_str() == Some("https") =>
                {
                    links.push(url)
                }
                _ => files.push(Path::new(stylesheet.trim_start_matches("file://"))),
            }
        }

        let mut config = self.config.write().unwrap();

        config.custom_styles = files
            .into_iter()
            .map(fs::read_to_string)
            .collect::<Result<Vec<_>, _>>()?;
        config.css_links = links;

        Ok(())
    }

    /// Set an external program to use for rendering the markdown.
    ///
    /// By default, aurelius uses [`pulldown_cmark`] to render markdown in-process.
    /// `pulldown-cmark` is an extremely fast, [CommonMark]-compliant parser that is sufficient
    /// for most use-cases. However, other markdown renderers may provide additional features.
    ///
    /// The `Command` supplied to this function should expect markdown on stdin and print HTML on
    /// stdout.
    ///
    /// # Example
    ///
    /// To use [`pandoc`] to render markdown:
    ///
    ///
    /// ```no_run
    /// # async fn dox() -> Result<(), Box<dyn std::error::Error>> {
    /// use std::net::SocketAddr;
    /// use tokio::process::Command;
    /// use aurelius::Server;
    ///
    /// let addr = "127.0.0.1:1337".parse::<SocketAddr>()?;
    /// let mut server = Server::bind(&addr).await?;
    ///
    /// let mut pandoc = Command::new("pandoc");
    /// pandoc.args(&["-f", "markdown", "-t", "html"]);
    ///
    /// server.set_external_renderer(pandoc);
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [`pulldown_cmark`]: https://github.com/raphlinus/pulldown-cmark
    /// [CommonMark]: https://commonmark.org/
    /// [`pandoc`]: https://pandoc.org/
    pub fn set_external_renderer(&mut self, mut command: Command) {
        command
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::null());
        self.external_renderer = Some(RefCell::new(command));
    }

    /// Opens the user's default browser with the server's URL in the background.
    ///
    /// This function uses platform-specific utilities to determine the browser. The following
    /// platforms are supported:
    ///
    /// | Platform | Program    |
    /// | -------- | ---------- |
    /// | Linux    | `xdg-open` |
    /// | OS X     | `open -g`  |
    /// | Windows  | `explorer` |
    pub fn open_browser(&self) -> io::Result<()> {
        let command = if cfg!(target_os = "macos") {
            let mut command = Command::new("open");
            command.arg("-g");
            command
        } else if cfg!(target_os = "windows") {
            Command::new("explorer")
        } else {
            Command::new("xdg-open")
        };

        self.open_specific_browser(command)
    }

    /// Opens a browser with a specified command. The HTTP address of the server will be appended
    /// to the command as an argument.
    pub fn open_specific_browser(&self, mut command: Command) -> io::Result<()> {
        command.arg(&format!("http://{}", self.addr()));

        command.stdout(Stdio::null()).stderr(Stdio::null());

        info!("spawning browser: {:?}", command);
        command.spawn()?;
        Ok(())
    }
}

#[derive(Debug)]
pub(crate) struct Config {
    static_root: Option<PathBuf>,
    highlight_theme: String,
    css_links: Vec<Uri>,
    custom_styles: Vec<String>,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            static_root: None,
            highlight_theme: String::from("github"),
            css_links: vec![],
            custom_styles: vec![],
        }
    }
}

#[cfg(test)]
mod tests {
    use std::error::Error;

    use async_tungstenite::tungstenite::{self, error::ProtocolError, Message};
    use async_tungstenite::WebSocketStream;
    use futures::{AsyncRead, AsyncWrite};
    use futures::{SinkExt, StreamExt, TryStreamExt};
    use matches::assert_matches;
    use tokio::net::lookup_host;
    use tokio::time::{timeout, Duration};

    use super::Server;

    async fn new_server() -> anyhow::Result<Server> {
        let addr = lookup_host("localhost:0").await?.next().unwrap();
        Ok(Server::bind(&addr).await?)
    }

    async fn assert_websocket_closed<S: AsyncRead + AsyncWrite + Unpin>(
        websocket: &mut WebSocketStream<S>,
    ) {
        assert_matches!(
            websocket.send(Message::Text(String::new())).await,
            Err(tungstenite::Error::AlreadyClosed
                | tungstenite::Error::Protocol(ProtocolError::SendAfterClosing))
        );
    }

    #[tokio::test]
    async fn connect_http() -> anyhow::Result<()> {
        let server = new_server().await?;

        let res = reqwest::get(&format!("http://{}", server.addr())).await?;

        assert!(res.headers()["Content-Type"]
            .to_str()
            .unwrap()
            .contains("text/html"));

        let body = res.text().await?;

        assert!(body.contains(""));

        Ok(())
    }

    #[tokio::test]
    async fn connect_websocket() -> anyhow::Result<()> {
        let server = new_server().await?;

        async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        Ok(())
    }

    #[tokio::test]
    async fn send_with_no_clients() -> anyhow::Result<()> {
        let server = new_server().await?;

        server.send("This shouldn't hang").await?;

        Ok(())
    }

    #[tokio::test]
    async fn send_html() -> anyhow::Result<()> {
        let server = new_server().await.unwrap();

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr()))
                .await
                .unwrap();

        server.send("
Hello, world!
").await.unwrap();
        let message = websocket.next().await.unwrap().unwrap();
        assert_eq!(message.to_text().unwrap(), "
Hello, world!
");

        server.send("
Goodbye, world!
").await.unwrap();
        let message = websocket.next().await.unwrap().unwrap();
        assert_eq!(message.to_text().unwrap(), "
Goodbye, world!
");

        Ok(())
    }

    #[tokio::test]
    async fn send_markdown() -> anyhow::Result<()> {
        let server = new_server().await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        server.send("*Hello*").await?;
        let message = websocket.next().await.unwrap()?;
        assert_eq!(message.to_text()?.trim(), "
Hello
");

        Ok(())
    }

    #[tokio::test]
    async fn close_websockets_on_drop() -> Result<(), Box<dyn Error>> {
        let server = new_server().await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        drop(server);

        assert_matches!(websocket.next().await, Some(Ok(Message::Close(None))));

        assert_websocket_closed(&mut websocket).await;

        Ok(())
    }

    #[tokio::test]
    async fn queue_html_if_no_clients() -> Result<(), Box<dyn Error>> {
        let server = new_server().await?;

        server.send("# Markdown").await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        let message = timeout(Duration::from_secs(5), websocket.try_next())
            .await??
            .unwrap();
        assert!(message.is_text(), "message was not text: {:?}", message);
        assert_eq!(message.to_text().unwrap().trim(), "
Markdown
");

        Ok(())
    }

    #[tokio::test]
    async fn closed_websocket_removed_from_clients() -> Result<(), Box<dyn Error>> {
        let server = new_server().await?;

        let (mut websocket, _) =
            async_tungstenite::tokio::connect_async(format!("ws://{}", server.addr())).await?;

        websocket.close(None).await?;

        assert_websocket_closed(&mut websocket).await;

        server.send("# Markdown").await?;

        Ok(())
    }
}
<|endoftext|>


<commit_before>#![crate_name = "neovim"]
#![crate_type = "lib"]
#![crate_type = "rlib"]

extern crate libc;

use std::c_str::CString;
use std::string::String;
use std::vec::Vec;

#[cfg(target_os="macos")]
mod platform {
    #[link(name = "nvim")]
    #[link(name = "uv")]
    #[link(name = "msgpack")]
    #[link(name = "curses")]
    #[link(name = "iconv")]
    extern{}
}

#[cfg(target_os="linux")]
mod platform {
    #[link(name = "nvim")]
    #[link(name = "uv")]
    #[link(name = "msgpack")]
    #[link(name = "curses")]
    extern{}
}

#[cfg(target_os="windows")]
mod platform {
    #[link(name = "nvim")]
    #[link(name = "uv")]
    #[link(name = "msgpack")]
    #[link(name = "curses")]
    extern{}
}

mod ffi {
    pub use libc::{c_char, c_int, c_void, uint64_t};

    extern "C" {
        pub fn channel_from_fds (read_fd: c_int, write_fd: c_int) -> uint64_t;
        pub fn channel_subscribe (id: uint64_t, event: *const c_char);
        pub fn nvim_main (argc: c_int, argv: *const *const c_char,
                          callback: Option<extern "C" fn(c_int, c_int)>,
                          read_fs: c_int, write_fs: c_int) -> c_int;
    }
}

pub struct Channel {
    id: ffi::uint64_t
}

impl Channel {
    pub fn new_with_fds(read_fd: i32, write_fd: i32) -> Channel {
        Channel {
            id: unsafe { ffi::channel_from_fds(read_fd, write_fd) }
        }
    }

    pub fn subscribe(&mut self, event: &str) {
        unsafe { ffi::channel_subscribe(self.id, event.to_c_str().as_ptr()) }
    }
}

pub fn run_with_callback(
    args: Vec<String>,
    callback: Option<extern "C" fn(i32, i32)>,
    read_fs: i32,
    write_fs: i32) -> i32
{
    let v: Vec<CString> = args.iter().map(|s| s.as_slice().to_c_str()).collect();
    let vp: Vec<*const ffi::c_char> = v.iter().map(|s| s.as_ptr()).collect();
    let p_vp: *const *const ffi::c_char = vp.as_ptr();

    unsafe { ffi::nvim_main(vp.len() as i32, p_vp, callback, read_fs, write_fs) }
}

pub fn run(args: Vec<String>) -> i32 {
    run_with_callback(args, None, -1, -1)
}
<commit_msg>Explicitly derive Copy<commit_after>#![crate_name = "neovim"]
#![crate_type = "lib"]
#![crate_type = "rlib"]

extern crate libc;

use std::c_str::CString;
use std::string::String;
use std::vec::Vec;

#[cfg(target_os="macos")]
mod platform {
    #[link(name = "nvim")]
    #[link(name = "uv")]
    #[link(name = "msgpack")]
    #[link(name = "curses")]
    #[link(name = "iconv")]
    extern{}
}

#[cfg(target_os="linux")]
mod platform {
    #[link(name = "nvim")]
    #[link(name = "uv")]
    #[link(name = "msgpack")]
    #[link(name = "curses")]
    extern{}
}

#[cfg(target_os="windows")]
mod platform {
    #[link(name = "nvim")]
    #[link(name = "uv")]
    #[link(name = "msgpack")]
    #[link(name = "curses")]
    extern{}
}

mod ffi {
    pub use libc::{c_char, c_int, c_void, uint64_t};

    extern "C" {
        pub fn channel_from_fds (read_fd: c_int, write_fd: c_int) -> uint64_t;
        pub fn channel_subscribe (id: uint64_t, event: *const c_char);
        pub fn nvim_main (argc: c_int, argv: *const *const c_char,
                          callback: Option<extern "C" fn(c_int, c_int)>,
                          read_fs: c_int, write_fs: c_int) -> c_int;
    }
}

#[deriving(Copy)]
pub struct Channel {
    id: ffi::uint64_t
}

impl Channel {
    pub fn new_with_fds(read_fd: i32, write_fd: i32) -> Channel {
        Channel {
            id: unsafe { ffi::channel_from_fds(read_fd, write_fd) }
        }
    }

    pub fn subscribe(&mut self, event: &str) {
        unsafe { ffi::channel_subscribe(self.id, event.to_c_str().as_ptr()) }
    }
}

pub fn run_with_callback(
    args: Vec<String>,
    callback: Option<extern "C" fn(i32, i32)>,
    read_fs: i32,
    write_fs: i32) -> i32
{
    let v: Vec<CString> = args.iter().map(|s| s.as_slice().to_c_str()).collect();
    let vp: Vec<*const ffi::c_char> = v.iter().map(|s| s.as_ptr()).collect();
    let p_vp: *const *const ffi::c_char = vp.as_ptr();

    unsafe { ffi::nvim_main(vp.len() as i32, p_vp, callback, read_fs, write_fs) }
}

pub fn run(args: Vec<String>) -> i32 {
    run_with_callback(args, None, -1, -1)
}
<|endoftext|>