<commit_before>use quick_xml::events::{BytesStart, Event};
use quick_xml::name::{QName, ResolveResult};
use quick_xml::{Decoder, Reader, Result};
use std::str::from_utf8;

#[test]
fn sample_1_short() {
    test(
        include_str!("documents/sample_1.xml"),
        include_str!("documents/sample_1_short.txt"),
        true,
    );
}

#[test]
fn sample_1_full() {
    test(
        include_str!("documents/sample_1.xml"),
        include_str!("documents/sample_1_full.txt"),
        false,
    );
}

#[test]
fn sample_2_short() {
    test(
        include_str!("documents/sample_2.xml"),
        include_str!("documents/sample_2_short.txt"),
        true,
    );
}

#[test]
fn sample_2_full() {
    test(
        include_str!("documents/sample_2.xml"),
        include_str!("documents/sample_2_full.txt"),
        false,
    );
}

#[cfg(feature = "escape-html")]
#[test]
fn html5() {
    test(
        include_str!("documents/html5.html"),
        include_str!("documents/html5.txt"),
        false,
    );
}

#[test]
fn escaped_characters() {
    test(
        r#"<e attr=""Hello"">'a' < '&'</e>"#,
        r#"
            |StartElement(e [attr=""Hello""])
            |Characters('a' < '&')
            |EndElement(e)
            |EndDocument
        "#,
        true,
    )
}

#[cfg(feature = "escape-html")]
#[test]
fn escaped_characters_html() {
    test(
        r#"<e attr="ℏÈℓ𝕝⨀">╔╗╔╗╔╗</e>"#,
        r#"
            |StartElement(e [attr="ℏÈℓ𝕝⨀"])
            |Characters(╔╗╔╗╔╗)
            |EndElement(e)
            |EndDocument
        "#,
        true,
    )
}

#[cfg(feature = "encoding")]
#[test]
fn encoded_characters() {
    test_bytes(
        b"\
            <?xml version = \"1.0\" encoding = \"Shift_JIS\" ?>\n\
            \x82\xA0\x82\xA2\x82\xA4\
        ",
        "
            |StartDocument(1.0, Shift_JIS)
            |StartElement(a)
            |Characters(あいう)
            |EndElement(a)
            |EndDocument
        "
        .as_bytes(),
        true,
    )
}

// #[test]
// fn sample_3_short() {
//     test(
//         include_str!("documents/sample_3.xml"),
//         include_str!("documents/sample_3_short.txt"),
//         true
//     );
// }

// #[test]
// fn sample_3_full() {
//     test(
//         include_str!("documents/sample_3.xml"),
//         include_str!("documents/sample_3_full.txt"),
//         false
//     );
// }

// #[test]
// fn sample_4_short() {
//     test(
//         include_str!("documents/sample_4.xml"),
//         include_str!("documents/sample_4_short.txt"),
//         true
//     );
// }

// #[test]
// fn sample_4_full() {
//     test(
//         include_str!("documents/sample_4.xml"),
//         include_str!("documents/sample_4_full.txt"),
//         false
//     );
//
// }

#[test]
// FIXME: Trips on the first byte-order-mark byte
// Expected: StartDocument(1.0, utf-16)
// Found: InvalidUtf8([255, 254]; invalid utf-8 sequence of 1 bytes from index 0)
#[ignore]
fn sample_5_short() {
    test_bytes(
        include_bytes!("documents/sample_5_utf16bom.xml"),
        include_bytes!("documents/sample_5_short.txt"),
        true,
    );
}

#[test]
fn sample_ns_short() {
    test(
        include_str!("documents/sample_ns.xml"),
        include_str!("documents/sample_ns_short.txt"),
        true,
    );
}

#[test]
fn eof_1() {
    test(
        r#"<?xml"#,
        r#"Error: Unexpected EOF during reading XmlDecl"#,
        true,
    );
}

#[test]
fn bad_1() {
    test(
        r#"<?xml&.,"#,
        r#"1:6 Error: Unexpected EOF during reading XmlDecl"#,
        true,
    );
}

#[test]
fn dashes_in_comments() {
    test(
        r#"<hello/>"#,
        r#"
        |Error: Unexpected token '--'
        "#,
        true,
    );

    test(
        r#"<hello/>"#,
        r#"
        |Error: Unexpected token '--'
        "#,
        true,
    );

    // Canary test for correct comments
    test(
        r#"<hello/>"#,
        r#"
        |Comment( comment )
        |EmptyElement(hello)
        |EndDocument
        "#,
        true,
    );
}

#[test]
fn tabs_1() {
    test(
        "\t\t",
        r#"
            StartElement(a)
            EmptyElement(b)
            EndElement(a)
            EndDocument
        "#,
        true,
    );
}

#[test]
fn issue_83_duplicate_attributes() {
    // Error when parsing attributes won't stop main event reader
    // as it is a lazy operation => add ending events
    test(
        r#"<hello><some-tag a='10' a="20"/></hello>"#,
        "
            |StartElement(hello)
            |1:30 EmptyElement(some-tag, attr-error: \
                  position 16: duplicated attribute, previous declaration at position 9)
            |EndElement(hello)
            |EndDocument
        ",
        true,
    );
}

#[test]
fn issue_93_large_characters_in_entity_references() {
    test(
        r#"<hello>&𤶼;</hello>"#,
        r#"
            |StartElement(hello)
            |1:10 FailedUnescape([38, 240, 164, 182, 188, 59]; Error while escaping character at range 1..5: Unrecognized escape symbol: Ok("𤶼"))
            |EndElement(hello)
            |EndDocument
        "#,
        true,
    )
}

#[test]
fn issue_98_cdata_ending_with_right_bracket() {
    test(
        r#"<hello><![CDATA[Foo [Bar]]]></hello>"#,
        r#"
            |StartElement(hello)
            |CData(Foo [Bar])
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    )
}

#[test]
fn issue_105_unexpected_double_dash() {
    test(
        r#"<hello>-- </hello>"#,
        r#"
            |StartElement(hello)
            |Characters(-- )
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );

    test(
        r#"<hello>--</hello>"#,
        r#"
            |StartElement(hello)
            |Characters(--)
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );

    test(
        r#"<hello>--></hello>"#,
        r#"
            |StartElement(hello)
            |Characters(-->)
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );

    test(
        r#"<hello><![CDATA[--]]></hello>"#,
        r#"
            |StartElement(hello)
            |CData(--)
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );
}

#[test]
fn issue_attributes_have_no_default_namespace() {
    // At the moment, the 'test' method doesn't render namespaces for attribute names.
    // This test only checks whether the default namespace got applied to the EmptyElement.
    test(
        r#"<hello xmlns="urn:foo" x="y"/>"#,
        r#"
             |EmptyElement({urn:foo}hello [x="y"])
             |EndDocument
         "#,
        true,
    );
}

#[test]
fn issue_default_namespace_on_outermost_element() {
    // Regression test
    test(
        r#"<hello xmlns="urn:foo"/>"#,
        r#"
                |EmptyElement({urn:foo}hello)
                |EndDocument
            "#,
        true,
    );
}

#[test]
fn default_namespace_applies_to_end_elem() {
    test(
        r#"<hello xmlns="urn:foo" x="y">
              <inner/>
            </hello>"#,
        r#"
            |StartElement({urn:foo}hello [x="y"])
            |EmptyElement({urn:foo}inner)
            |EndElement({urn:foo}hello)
            |EndDocument
        "#,
        true,
    );
}

#[track_caller]
fn test(input: &str, output: &str, trim: bool) {
    test_bytes(input.as_bytes(), output.as_bytes(), trim);
}

#[track_caller]
fn test_bytes(input: &[u8], output: &[u8], trim: bool) {
    let mut reader = Reader::from_reader(input);
    reader
        .trim_text(trim)
        .check_comments(true)
        .expand_empty_elements(false);

    let mut spec_lines = SpecIter(output).enumerate();
    let mut buf = Vec::new();
    let mut ns_buffer = Vec::new();

    loop {
        buf.clear();
        let event = reader.read_namespaced_event(&mut buf, &mut ns_buffer);
        let line = xmlrs_display(event, reader.decoder());
        if let Some((n, spec)) = spec_lines.next() {
            if spec.trim() == "EndDocument" {
                break;
            }
            if line.trim() != spec.trim() {
                panic!(
                    "\n-------------------\n\
                     Unexpected event at line {}:\n\
                     Expected: {}\nFound: {}\n\
                     -------------------\n",
                    n + 1,
                    spec,
                    line
                );
            }
        } else {
            if line == "EndDocument" {
                break;
            }
            panic!("Unexpected event: {}", line);
        }
    }
}

fn namespace_name(n: ResolveResult, name: QName, decoder: Decoder) -> String {
    let name = decoder.decode(name.as_ref()).unwrap();
    match n {
        // Produces string '{namespace}prefixed_name'
        ResolveResult::Bound(n) => format!("{{{}}}{}", from_utf8(n.as_ref()).unwrap(), name),
        _ => name.to_string(),
    }
}

fn make_attrs(e: &BytesStart) -> ::std::result::Result<String, String> {
    let mut atts = Vec::new();
    for a in e.attributes() {
        match a {
            Ok(a) => {
                if a.key.as_namespace_binding().is_none() {
                    atts.push(format!(
                        "{}=\"{}\"",
                        from_utf8(a.key.as_ref()).unwrap(),
                        from_utf8(&*a.unescaped_value().unwrap()).unwrap()
                    ));
                }
            }
            Err(e) => return Err(e.to_string()),
        }
    }
    Ok(atts.join(", "))
}

fn xmlrs_display(opt_event: Result<(ResolveResult, Event)>, decoder: Decoder) -> String {
    match opt_event {
        Ok((_, Event::StartText(_))) => "StartText".to_string(),
        Ok((n, Event::Start(ref e))) => {
            let name = namespace_name(n, e.name(), decoder);
            match make_attrs(e) {
                Ok(ref attrs) if attrs.is_empty() => format!("StartElement({})", &name),
                Ok(ref attrs) => format!("StartElement({} [{}])", &name, &attrs),
                Err(e) => format!("StartElement({}, attr-error: {})", &name, &e),
            }
        }
        Ok((n, Event::Empty(ref e))) => {
            let name = namespace_name(n, e.name(), decoder);
            match make_attrs(e) {
                Ok(ref attrs) if attrs.is_empty() => format!("EmptyElement({})", &name),
                Ok(ref attrs) => format!("EmptyElement({} [{}])", &name, &attrs),
                Err(e) => format!("EmptyElement({}, attr-error: {})", &name, &e),
            }
        }
        Ok((n, Event::End(ref e))) => {
            let name = namespace_name(n, e.name(), decoder);
            format!("EndElement({})", name)
        }
        Ok((_, Event::Comment(ref e))) => format!("Comment({})", from_utf8(e).unwrap()),
        Ok((_, Event::CData(ref e))) => format!("CData({})", from_utf8(e).unwrap()),
        Ok((_, Event::Text(ref e))) => match e.unescaped() {
            Ok(c) => format!("Characters({})", decoder.decode(c.as_ref()).unwrap()),
            Err(ref err) => format!("FailedUnescape({:?}; {})", e.escaped(), err),
        },
        Ok((_, Event::Decl(ref e))) => {
            let version_cow = e.version().unwrap();
            let version = from_utf8(version_cow.as_ref()).unwrap();
            let encoding_cow = e.encoding().unwrap().unwrap();
            let encoding = from_utf8(encoding_cow.as_ref()).unwrap();
            format!("StartDocument({}, {})", version, encoding)
        }
        Ok((_, Event::Eof)) => format!("EndDocument"),
        Ok((_, Event::PI(ref e))) => format!("ProcessingInstruction(PI={})", from_utf8(e).unwrap()),
        Err(ref e) => format!("Error: {}", e),
        Ok((_, Event::DocType(ref e))) => format!("DocType({})", from_utf8(e).unwrap()),
    }
}

struct SpecIter<'a>(&'a [u8]);

impl<'a> Iterator for SpecIter<'a> {
    type Item = &'a str;
    fn next(&mut self) -> Option<&'a str> {
        let start = self
            .0
            .iter()
            .position(|b| !matches!(*b, b' ' | b'\r' | b'\n' | b'\t' | b'|' | b':' | b'0'..=b'9'))
            .unwrap_or(0);

        if let Some(p) = self.0.windows(3).position(|w| w == b")\r\n") {
            let (prev, next) = self.0.split_at(p + 1);
            self.0 = &next[1..];
            Some(from_utf8(&prev[start..]).expect("Error decoding to uft8"))
        } else if let Some(p) = self.0.windows(2).position(|w| w == b")\n") {
            let (prev, next) = self.0.split_at(p + 1);
            self.0 = next;
            Some(from_utf8(&prev[start..]).expect("Error decoding to uft8"))
        } else if self.0.is_empty() {
            None
        } else {
            let p = self.0;
            self.0 = &[];
            Some(from_utf8(&p[start..]).unwrap())
        }
    }
}
<commit_msg>Use correct encoding when generating event information for comparison<commit_after>use quick_xml::escape::unescape;
use quick_xml::events::{BytesStart, Event};
use quick_xml::name::{QName, ResolveResult};
use quick_xml::{Decoder, Reader, Result};
use std::str::from_utf8;

#[test]
fn sample_1_short() {
    test(
        include_str!("documents/sample_1.xml"),
        include_str!("documents/sample_1_short.txt"),
        true,
    );
}

#[test]
fn sample_1_full() {
    test(
        include_str!("documents/sample_1.xml"),
        include_str!("documents/sample_1_full.txt"),
        false,
    );
}

#[test]
fn sample_2_short() {
    test(
        include_str!("documents/sample_2.xml"),
        include_str!("documents/sample_2_short.txt"),
        true,
    );
}

#[test]
fn sample_2_full() {
    test(
        include_str!("documents/sample_2.xml"),
        include_str!("documents/sample_2_full.txt"),
        false,
    );
}

#[cfg(feature = "escape-html")]
#[test]
fn html5() {
    test(
        include_str!("documents/html5.html"),
        include_str!("documents/html5.txt"),
        false,
    );
}

#[test]
fn escaped_characters() {
    test(
        r#"<e attr=""Hello"">'a' < '&'</e>"#,
        r#"
            |StartElement(e [attr=""Hello""])
            |Characters('a' < '&')
            |EndElement(e)
            |EndDocument
        "#,
        true,
    )
}

#[cfg(feature = "escape-html")]
#[test]
fn escaped_characters_html() {
    test(
        r#"<e attr="ℏÈℓ𝕝⨀">╔╗╔╗╔╗</e>"#,
        r#"
            |StartElement(e [attr="ℏÈℓ𝕝⨀"])
            |Characters(╔╗╔╗╔╗)
            |EndElement(e)
            |EndDocument
        "#,
        true,
    )
}

#[cfg(feature = "encoding")]
#[test]
fn encoded_characters() {
    test_bytes(
        b"\
            <?xml version = \"1.0\" encoding = \"Shift_JIS\" ?>\n\
            \x82\xA0\x82\xA2\x82\xA4\
        ",
        "
            |StartDocument(1.0, Shift_JIS)
            |StartElement(a)
            |Characters(あいう)
            |EndElement(a)
            |EndDocument
        "
        .as_bytes(),
        true,
    )
}

// #[test]
// fn sample_3_short() {
//     test(
//         include_str!("documents/sample_3.xml"),
//         include_str!("documents/sample_3_short.txt"),
//         true
//     );
// }

// #[test]
// fn sample_3_full() {
//     test(
//         include_str!("documents/sample_3.xml"),
//         include_str!("documents/sample_3_full.txt"),
//         false
//     );
// }

// #[test]
// fn sample_4_short() {
//     test(
//         include_str!("documents/sample_4.xml"),
//         include_str!("documents/sample_4_short.txt"),
//         true
//     );
// }

// #[test]
// fn sample_4_full() {
//     test(
//         include_str!("documents/sample_4.xml"),
//         include_str!("documents/sample_4_full.txt"),
//         false
//     );
//
// }

#[test]
// FIXME: Trips on the first byte-order-mark byte
// Expected: StartDocument(1.0, utf-16)
// Found: InvalidUtf8([255, 254]; invalid utf-8 sequence of 1 bytes from index 0)
#[ignore]
fn sample_5_short() {
    test_bytes(
        include_bytes!("documents/sample_5_utf16bom.xml"),
        include_bytes!("documents/sample_5_short.txt"),
        true,
    );
}

#[test]
fn sample_ns_short() {
    test(
        include_str!("documents/sample_ns.xml"),
        include_str!("documents/sample_ns_short.txt"),
        true,
    );
}

#[test]
fn eof_1() {
    test(
        r#"<?xml"#,
        r#"Error: Unexpected EOF during reading XmlDecl"#,
        true,
    );
}

#[test]
fn bad_1() {
    test(
        r#"<?xml&.,"#,
        r#"1:6 Error: Unexpected EOF during reading XmlDecl"#,
        true,
    );
}

#[test]
fn dashes_in_comments() {
    test(
        r#"<hello/>"#,
        r#"
        |Error: Unexpected token '--'
        "#,
        true,
    );

    test(
        r#"<hello/>"#,
        r#"
        |Error: Unexpected token '--'
        "#,
        true,
    );

    // Canary test for correct comments
    test(
        r#"<hello/>"#,
        r#"
        |Comment( comment )
        |EmptyElement(hello)
        |EndDocument
        "#,
        true,
    );
}

#[test]
fn tabs_1() {
    test(
        "\t\t",
        r#"
            StartElement(a)
            EmptyElement(b)
            EndElement(a)
            EndDocument
        "#,
        true,
    );
}

#[test]
fn issue_83_duplicate_attributes() {
    // Error when parsing attributes won't stop main event reader
    // as it is a lazy operation => add ending events
    test(
        r#"<hello><some-tag a='10' a="20"/></hello>"#,
        "
            |StartElement(hello)
            |1:30 EmptyElement(some-tag, attr-error: \
                  position 16: duplicated attribute, previous declaration at position 9)
            |EndElement(hello)
            |EndDocument
        ",
        true,
    );
}

#[test]
fn issue_93_large_characters_in_entity_references() {
    test(
        r#"<hello>&𤶼;</hello>"#,
        r#"
            |StartElement(hello)
            |1:10 FailedUnescape([38, 240, 164, 182, 188, 59]; Error while escaping character at range 1..5: Unrecognized escape symbol: Ok("𤶼"))
            |EndElement(hello)
            |EndDocument
        "#,
        true,
    )
}

#[test]
fn issue_98_cdata_ending_with_right_bracket() {
    test(
        r#"<hello><![CDATA[Foo [Bar]]]></hello>"#,
        r#"
            |StartElement(hello)
            |CData(Foo [Bar])
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    )
}

#[test]
fn issue_105_unexpected_double_dash() {
    test(
        r#"<hello>-- </hello>"#,
        r#"
            |StartElement(hello)
            |Characters(-- )
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );

    test(
        r#"<hello>--</hello>"#,
        r#"
            |StartElement(hello)
            |Characters(--)
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );

    test(
        r#"<hello>--></hello>"#,
        r#"
            |StartElement(hello)
            |Characters(-->)
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );

    test(
        r#"<hello><![CDATA[--]]></hello>"#,
        r#"
            |StartElement(hello)
            |CData(--)
            |EndElement(hello)
            |EndDocument
        "#,
        false,
    );
}

#[test]
fn issue_attributes_have_no_default_namespace() {
    // At the moment, the 'test' method doesn't render namespaces for attribute names.
    // This test only checks whether the default namespace got applied to the EmptyElement.
    test(
        r#"<hello xmlns="urn:foo" x="y"/>"#,
        r#"
             |EmptyElement({urn:foo}hello [x="y"])
             |EndDocument
         "#,
        true,
    );
}

#[test]
fn issue_default_namespace_on_outermost_element() {
    // Regression test
    test(
        r#"<hello xmlns="urn:foo"/>"#,
        r#"
                |EmptyElement({urn:foo}hello)
                |EndDocument
            "#,
        true,
    );
}

#[test]
fn default_namespace_applies_to_end_elem() {
    test(
        r#"<hello xmlns="urn:foo" x="y">
              <inner/>
            </hello>"#,
        r#"
            |StartElement({urn:foo}hello [x="y"])
            |EmptyElement({urn:foo}inner)
            |EndElement({urn:foo}hello)
            |EndDocument
        "#,
        true,
    );
}

#[track_caller]
fn test(input: &str, output: &str, trim: bool) {
    test_bytes(input.as_bytes(), output.as_bytes(), trim);
}

#[track_caller]
fn test_bytes(input: &[u8], output: &[u8], trim: bool) {
    let mut reader = Reader::from_reader(input);
    reader
        .trim_text(trim)
        .check_comments(true)
        .expand_empty_elements(false);

    let mut spec_lines = SpecIter(output).enumerate();
    let mut buf = Vec::new();
    let mut ns_buffer = Vec::new();

    loop {
        buf.clear();
        let event = reader.read_namespaced_event(&mut buf, &mut ns_buffer);
        let line = xmlrs_display(event, reader.decoder());
        if let Some((n, spec)) = spec_lines.next() {
            if spec.trim() == "EndDocument" {
                break;
            }
            if line.trim() != spec.trim() {
                panic!(
                    "\n-------------------\n\
                     Unexpected event at line {}:\n\
                     Expected: {}\nFound: {}\n\
                     -------------------\n",
                    n + 1,
                    spec,
                    line
                );
            }
        } else {
            if line == "EndDocument" {
                break;
            }
            panic!("Unexpected event: {}", line);
        }
    }
}

fn namespace_name(n: ResolveResult, name: QName, decoder: Decoder) -> String {
    let name = decoder.decode(name.as_ref()).unwrap();
    match n {
        // Produces string '{namespace}prefixed_name'
        ResolveResult::Bound(n) => format!("{{{}}}{}", decoder.decode(n.as_ref()).unwrap(), name),
        _ => name.to_string(),
    }
}

fn make_attrs(e: &BytesStart, decoder: Decoder) -> ::std::result::Result<String, String> {
    let mut atts = Vec::new();
    for a in e.attributes() {
        match a {
            Ok(a) => {
                if a.key.as_namespace_binding().is_none() {
                    let key = decoder.decode(a.key.as_ref()).unwrap();
                    let value = decoder.decode(a.value.as_ref()).unwrap();
                    let unescaped_value = unescape(value.as_bytes()).unwrap();
                    atts.push(format!(
                        "{}=\"{}\"",
                        key,
                        // unescape does not change validity of an UTF-8 string
                        from_utf8(&*unescaped_value).unwrap()
                    ));
                }
            }
            Err(e) => return Err(e.to_string()),
        }
    }
    Ok(atts.join(", "))
}

fn xmlrs_display(opt_event: Result<(ResolveResult, Event)>, decoder: Decoder) -> String {
    match opt_event {
        Ok((_, Event::StartText(_))) => "StartText".to_string(),
        Ok((n, Event::Start(e))) => {
            let name = namespace_name(n, e.name(), decoder);
            match make_attrs(&e, decoder) {
                Ok(attrs) if attrs.is_empty() => format!("StartElement({})", &name),
                Ok(attrs) => format!("StartElement({} [{}])", &name, &attrs),
                Err(e) => format!("StartElement({}, attr-error: {})", &name, &e),
            }
        }
        Ok((n, Event::Empty(e))) => {
            let name = namespace_name(n, e.name(), decoder);
            match make_attrs(&e, decoder) {
                Ok(attrs) if attrs.is_empty() => format!("EmptyElement({})", &name),
                Ok(attrs) => format!("EmptyElement({} [{}])", &name, &attrs),
                Err(e) => format!("EmptyElement({}, attr-error: {})", &name, &e),
            }
        }
        Ok((n, Event::End(e))) => {
            let name = namespace_name(n, e.name(), decoder);
            format!("EndElement({})", name)
        }
        Ok((_, Event::Comment(e))) => format!("Comment({})", decoder.decode(&e).unwrap()),
        Ok((_, Event::CData(e))) => format!("CData({})", decoder.decode(&e).unwrap()),
        Ok((_, Event::Text(e))) => match unescape(decoder.decode(&e).unwrap().as_bytes()) {
            Ok(c) => format!("Characters({})", from_utf8(c.as_ref()).unwrap()),
            Err(err) => format!("FailedUnescape({:?}; {})", e.escaped(), err),
        },
        Ok((_, Event::Decl(e))) => {
            let version_cow = e.version().unwrap();
            let version = decoder.decode(version_cow.as_ref()).unwrap();
            let encoding_cow = e.encoding().unwrap().unwrap();
            let encoding = decoder.decode(encoding_cow.as_ref()).unwrap();
            format!("StartDocument({}, {})", version, encoding)
        }
        Ok((_, Event::Eof)) => format!("EndDocument"),
        Ok((_, Event::PI(e))) => {
            format!("ProcessingInstruction(PI={})", decoder.decode(&e).unwrap())
        }
        Ok((_, Event::DocType(e))) => format!("DocType({})", decoder.decode(&e).unwrap()),
        Err(e) => format!("Error: {}", e),
    }
}

struct SpecIter<'a>(&'a [u8]);

impl<'a> Iterator for SpecIter<'a> {
    type Item = &'a str;
    fn next(&mut self) -> Option<&'a str> {
        let start = self
            .0
            .iter()
            .position(|b| !matches!(*b, b' ' | b'\r' | b'\n' | b'\t' | b'|' | b':' | b'0'..=b'9'))
            .unwrap_or(0);

        if let Some(p) = self.0.windows(3).position(|w| w == b")\r\n") {
            let (prev, next) = self.0.split_at(p + 1);
            self.0 = &next[1..];
            Some(from_utf8(&prev[start..]).expect("Error decoding to uft8"))
        } else if let Some(p) = self.0.windows(2).position(|w| w == b")\n") {
            let (prev, next) = self.0.split_at(p + 1);
            self.0 = next;
            Some(from_utf8(&prev[start..]).expect("Error decoding to uft8"))
        } else if self.0.is_empty() {
            None
        } else {
            let p = self.0;
            self.0 = &[];
            Some(from_utf8(&p[start..]).unwrap())
        }
    }
}
<|endoftext|>


<commit_before>use asm_ops::*;

use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::process::Command;

use std::fmt;
impl fmt::Display for Register{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }

}
impl fmt::Display for AsmOperand {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            AsmOperand::RegisterOperand(ref dest) =>  write!(f, "{}", dest),
            AsmOperand::Value(ref dest) => write!(f, "{}", dest),
            AsmOperand::Memory(ref mem) => write!(f, "[{}]", mem)
        }
    }
}

pub fn compile(ops: &Vec<AsmOp>) -> Vec<u8>{
    let mut str = "use64\n".to_string();
    for op in ops{
        let temp_str = match *op {
            AsmOp::Add(ref dest, ref operand) => format!("add {}, {}\n", dest, operand),
            AsmOp::Sub(ref dest, ref operand) => format!("sub {}, {}\n", dest, operand),

            AsmOp::Mul(ref dest, ref operand) => format!("imul {}, {}\n", dest, operand),
            AsmOp::Div(ref dest, ref operand) => panic!(),
            AsmOp::Mod(ref dest, ref operand) => panic!(),

            AsmOp::Pop(ref dest) => format!("popq {}\n", dest),
            AsmOp::Push(ref dest) => format!("pushq {}\n", dest),
            AsmOp::Mov(ref dest, ref operand) => format!("mov {}, {}\n", dest, operand),
            AsmOp::Out => "ret".to_string() ,
            // _ => {}
        };
        str = str + &temp_str;
    }
    println!("\nOutput assembly is:\n{}\n",str);

    write_asm(&str);
    assemble();
    read_bytes()
}

fn write_asm(str: &String) {
    let path = Path::new("target/temp.asm");
    let display = path.display();

    let mut file = match File::create(&path) {
        Err(why) => panic!("couldn't create {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    match file.write_all(str.as_bytes()) {
        Err(why) => {
            panic!("couldn't write to {}: {}", display, Error::description(&why))
        },
        Ok(_) => println!("Successfully wrote to {}", display),
    }
}

fn assemble() {
    let output = Command::new("fasm").arg("target/temp.asm").output().unwrap_or_else(|e| {
        panic!("failed to execute process: {}", e)
    });

    if !output.status.success() {
        println!("status: {}", output.status);
        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
        panic!("Can't assemble!");
    } else {
        println!("Successfully assembled to temp.bin");
    }
}

fn read_bytes() -> Vec<u8>{
    let path = Path::new("target/temp.bin");
    let display = path.display();

    let mut file = match File::open(&path) {
       Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)),
       Ok(file) => file,
   };

    let mut contents: Vec<u8> = Vec::new();
    // Returns amount of bytes read and append the result to the buffer
    let result = file.read_to_end(&mut contents).unwrap();
    println!("Program consists of {} bytes", result);
    contents
}
<commit_msg>Fixed warnings<commit_after>use asm_ops::*;

use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::process::Command;

use std::fmt;
impl fmt::Display for Register{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }

}
impl fmt::Display for AsmOperand {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            AsmOperand::RegisterOperand(ref dest) =>  write!(f, "{}", dest),
            AsmOperand::Value(ref dest) => write!(f, "{}", dest),
            AsmOperand::Memory(ref mem) => write!(f, "[{}]", mem)
        }
    }
}

pub fn compile(ops: &Vec<AsmOp>) -> Vec<u8>{
    let mut str = "use64\n".to_string();
    for op in ops{
        let temp_str = match *op {
            AsmOp::Add(ref dest, ref operand) => format!("add {}, {}\n", dest, operand),
            AsmOp::Sub(ref dest, ref operand) => format!("sub {}, {}\n", dest, operand),

            AsmOp::Mul(ref dest, ref operand) => format!("imul {}, {}\n", dest, operand),
            AsmOp::Div(_, _) => panic!(),
            AsmOp::Mod(_, _) => panic!(),

            AsmOp::Pop(ref dest) => format!("popq {}\n", dest),
            AsmOp::Push(ref dest) => format!("pushq {}\n", dest),
            AsmOp::Mov(ref dest, ref operand) => format!("mov {}, {}\n", dest, operand),
            AsmOp::Out => "ret".to_string() ,
            // _ => {}
        };
        str = str + &temp_str;
    }
    println!("\nOutput assembly is:\n{}\n",str);

    write_asm(&str);
    assemble();
    read_bytes()
}

fn write_asm(str: &String) {
    let path = Path::new("target/temp.asm");
    let display = path.display();

    let mut file = match File::create(&path) {
        Err(why) => panic!("couldn't create {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    match file.write_all(str.as_bytes()) {
        Err(why) => {
            panic!("couldn't write to {}: {}", display, Error::description(&why))
        },
        Ok(_) => println!("Successfully wrote to {}", display),
    }
}

fn assemble() {
    let output = Command::new("fasm").arg("target/temp.asm").output().unwrap_or_else(|e| {
        panic!("failed to execute process: {}", e)
    });

    if !output.status.success() {
        println!("status: {}", output.status);
        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
        panic!("Can't assemble!");
    } else {
        println!("Successfully assembled to temp.bin");
    }
}

fn read_bytes() -> Vec<u8>{
    let path = Path::new("target/temp.bin");
    let display = path.display();

    let mut file = match File::open(&path) {
       Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)),
       Ok(file) => file,
   };

    let mut contents: Vec<u8> = Vec::new();
    // Returns amount of bytes read and append the result to the buffer
    let result = file.read_to_end(&mut contents).unwrap();
    println!("Program consists of {} bytes", result);
    contents
}
<|endoftext|>


<commit_before>use super::{LzfResult, LzfError};
use std::{cmp, mem};

const HLOG    : usize = 16;
const HSIZE   : u32 = 1 << HLOG;
const MAX_OFF : usize = 1 << 13;
const MAX_REF : usize = ((1 << 8) + (1 << 3));
const MAX_LIT : i32 = (1 << 5);

fn first(p: &[u8], off: usize) -> u32 {
    ((p[off] as u32) << 8) | p[off+1] as u32
}

fn next(v: u32, p: &[u8], off: usize) -> u32 {
    (v << 8) | p[off+2] as u32
}

fn idx(h: u32) -> usize {
    let h = h as u64;
    (
        // 8 = 3*8-HLOG, but HLOG is constant at 16
        (h.wrapping_shr(8).wrapping_sub(h*5))
        & (HSIZE-1) as u64
    ) as usize
}

fn not(i: i32) -> i32 {
    if i == 0 {
        1
    } else {
        0
    }
}

/// Compress the given data, if possible.
/// The return value will be set to the error if compression fails.
///
/// The buffer is always set to the same size as the input buffer.
/// If that is not enough to hold the lzf-compressed data,
/// an error will be returned.
///
/// Example:
///
/// ```rust
/// let data = "foobar";
/// let compressed = lzf::compress(data.as_bytes());
/// ```
pub fn compress(data: &[u8]) -> LzfResult<Vec<u8>> {
    let in_len = data.len();
    let out_buf_len = in_len;
    let mut out = Vec::with_capacity(out_buf_len);
    unsafe { out.set_len(out_buf_len) };

    let mut out_len: i32 = 1; /* start run by default */

    /* This goes against all of Rust's statically verifiable guarantees,
     * but for the below use-case accessing uninitialized memory is ok,
     * as we have other checks to make sure the read memory is not used.
     *
     * The otherwise happening memset slows down the code by a factor of 20-30
     */
    let mut htab: [usize; 1 << HLOG] = unsafe { mem::uninitialized() };

    let mut current_offset = 0;

    if in_len == 0 {
        return Err(LzfError::DataCorrupted);
    }

    let mut lit: i32 = 0;

    let mut hval: u32;
    let mut ref_offset;

    hval = first(data, current_offset);

    while current_offset < in_len - 2 {
        hval = next(hval, data, current_offset);
        let hslot_idx = idx(hval);

        ref_offset = htab[hslot_idx];
        htab[hslot_idx] = current_offset;

        let off = current_offset.wrapping_sub(ref_offset).wrapping_sub(1);
        if off < MAX_OFF && current_offset + 4 < in_len && ref_offset > 0 &&
           data[ref_offset+0] == data[current_offset+0] &&
           data[ref_offset+1] == data[current_offset+1] &&
           data[ref_offset+2] == data[current_offset+2] {

            let mut len = 2;
            let maxlen = cmp::min(in_len - current_offset - len, MAX_REF);

            /* stop run */
            out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
            out_len -= not(lit); /* undo run if length is zero */

            if out_len as i32 + 3 + 1 >= out_buf_len as i32 {
                return Err(LzfError::NoCompressionPossible);
            }

            loop {
                len += 1;
                while len < maxlen && data[ref_offset+len] == data[current_offset+len] {
                    len += 1;
                }
                break;
            }

            len -= 2; /* len is now #octets - 1 */
            current_offset += 1;

            if len < 7 {
                out[out_len as usize] = (off >> 8) as u8 + (len << 5) as u8;
                out_len += 1;
            } else {
                out[out_len as usize] = (off >> 8) as u8 + (7 << 5);
                out[out_len as usize + 1] = (len as u8).wrapping_sub(7);
                out_len += 2;
            }

            out[out_len as usize] = off as u8;
            out_len += 2; /* start run */
            lit = 0;

            /* we add here, because we later substract from the total length */
            current_offset += len - 1;

            if current_offset >= in_len {
                break;
            }

            hval = first(data, current_offset);

            hval = next(hval, data, current_offset);
            htab[idx(hval)] = current_offset;
            current_offset += 1;

            hval = next(hval, data, current_offset);
            htab[idx(hval)] = current_offset;
            current_offset += 1;
        } else {
            /* one more literal byte we must copy */
            if current_offset >= out_buf_len {
                return Err(LzfError::NoCompressionPossible);
            }

            lit += 1;
            out[out_len as usize] = data[current_offset];
            out_len += 1;
            current_offset += 1;

            if lit == MAX_LIT {
                /* stop run */
                out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
                lit = 0;
                out_len += 1; /* start run */
            }
        }
    }

    /* at most 3 bytes can be missing here */
    if out_len + 3 > out_buf_len as i32 {
        return Err(LzfError::NoCompressionPossible);
    }

    while current_offset < in_len {
        lit += 1;
        out[out_len as usize] = data[current_offset];
        out_len += 1;
        current_offset += 1;

        if lit == MAX_LIT {
            /* stop run */
            out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
            lit = 0;
            out_len += 1; /* start run */
        }
    }

    /* end run */
    out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
    out_len -= not(lit); /* undo run if length is zero */

    unsafe { out.set_len(out_len as usize) };

    Ok(out)
}

#[test]
fn test_compress_skips_short() {
    match compress("foo".as_bytes()) {
        Ok(_) => panic!("Compression did _something_, which is wrong for 'foo'"),
        Err(err) => assert_eq!(LzfError::NoCompressionPossible, err),
    }
}

#[test]
fn test_compress_lorem() {
    let lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod \
                 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At \
                 vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, \
                 no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit \
                 amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut \
                 labore et dolore magna aliquyam erat, sed diam voluptua.";

    match compress(lorem.as_bytes()) {
        Ok(compressed) => {
            assert_eq!(272, compressed.len())
        }
        Err(err) => panic!("Compression failed with error {:?}", err),
    }
}

#[test]
fn test_compress_decompress_lorem_round() {
    use super::decompress;

    let lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod \
                 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At \
                 vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, \
                 no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit \
                 amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut \
                 labore et dolore magna aliquyam erat, sed diam voluptua.";

    let compressed = match compress(lorem.as_bytes()) {
        Ok(c) => c,
        Err(err) => panic!("Compression failed with error {:?}", err),
    };

    match decompress(&compressed, lorem.len()) {
        Ok(decompressed) => {
            assert_eq!(lorem.len(), decompressed.len());
            assert_eq!(lorem.as_bytes(), &decompressed[..]);
        }
        Err(err) => panic!("Decompression failed with error {:?}", err),
    };
}

#[test]
fn test_alice_wonderland_both() {
    let alice = "\r\n\r\n\r\n\r\n                ALICE'S ADVENTURES IN WONDERLAND\r\n";

    let compressed = match compress(alice.as_bytes()) {
        Ok(c) => c,
        Err(err) => panic!("Compression failed with error {:?}", err),
    };

    let c_compressed = match super::compress(alice.as_bytes()) {
        Ok(c) => c,
        Err(err) => panic!("Compression failed with error {:?}", err),
    };

    assert_eq!(&compressed[..], &c_compressed[..]);
}
<commit_msg>fix: Remove ineffective plus-0<commit_after>use super::{LzfResult, LzfError};
use std::{cmp, mem};

const HLOG    : usize = 16;
const HSIZE   : u32 = 1 << HLOG;
const MAX_OFF : usize = 1 << 13;
const MAX_REF : usize = ((1 << 8) + (1 << 3));
const MAX_LIT : i32 = (1 << 5);

fn first(p: &[u8], off: usize) -> u32 {
    ((p[off] as u32) << 8) | p[off+1] as u32
}

fn next(v: u32, p: &[u8], off: usize) -> u32 {
    (v << 8) | p[off+2] as u32
}

fn idx(h: u32) -> usize {
    let h = h as u64;
    (
        // 8 = 3*8-HLOG, but HLOG is constant at 16
        (h.wrapping_shr(8).wrapping_sub(h*5))
        & (HSIZE-1) as u64
    ) as usize
}

fn not(i: i32) -> i32 {
    if i == 0 {
        1
    } else {
        0
    }
}

/// Compress the given data, if possible.
/// The return value will be set to the error if compression fails.
///
/// The buffer is always set to the same size as the input buffer.
/// If that is not enough to hold the lzf-compressed data,
/// an error will be returned.
///
/// Example:
///
/// ```rust
/// let data = "foobar";
/// let compressed = lzf::compress(data.as_bytes());
/// ```
pub fn compress(data: &[u8]) -> LzfResult<Vec<u8>> {
    let in_len = data.len();
    let out_buf_len = in_len;
    let mut out = Vec::with_capacity(out_buf_len);
    unsafe { out.set_len(out_buf_len) };

    let mut out_len: i32 = 1; /* start run by default */

    /* This goes against all of Rust's statically verifiable guarantees,
     * but for the below use-case accessing uninitialized memory is ok,
     * as we have other checks to make sure the read memory is not used.
     *
     * The otherwise happening memset slows down the code by a factor of 20-30
     */
    let mut htab: [usize; 1 << HLOG] = unsafe { mem::uninitialized() };

    let mut current_offset = 0;

    if in_len == 0 {
        return Err(LzfError::DataCorrupted);
    }

    let mut lit: i32 = 0;

    let mut hval: u32;
    let mut ref_offset;

    hval = first(data, current_offset);

    while current_offset < in_len - 2 {
        hval = next(hval, data, current_offset);
        let hslot_idx = idx(hval);

        ref_offset = htab[hslot_idx];
        htab[hslot_idx] = current_offset;

        let off = current_offset.wrapping_sub(ref_offset).wrapping_sub(1);
        if off < MAX_OFF && current_offset + 4 < in_len && ref_offset > 0 &&
           data[ref_offset] == data[current_offset] &&
           data[ref_offset+1] == data[current_offset+1] &&
           data[ref_offset+2] == data[current_offset+2] {

            let mut len = 2;
            let maxlen = cmp::min(in_len - current_offset - len, MAX_REF);

            /* stop run */
            out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
            out_len -= not(lit); /* undo run if length is zero */

            if out_len as i32 + 3 + 1 >= out_buf_len as i32 {
                return Err(LzfError::NoCompressionPossible);
            }

            loop {
                len += 1;
                while len < maxlen && data[ref_offset+len] == data[current_offset+len] {
                    len += 1;
                }
                break;
            }

            len -= 2; /* len is now #octets - 1 */
            current_offset += 1;

            if len < 7 {
                out[out_len as usize] = (off >> 8) as u8 + (len << 5) as u8;
                out_len += 1;
            } else {
                out[out_len as usize] = (off >> 8) as u8 + (7 << 5);
                out[out_len as usize + 1] = (len as u8).wrapping_sub(7);
                out_len += 2;
            }

            out[out_len as usize] = off as u8;
            out_len += 2; /* start run */
            lit = 0;

            /* we add here, because we later substract from the total length */
            current_offset += len - 1;

            if current_offset >= in_len {
                break;
            }

            hval = first(data, current_offset);

            hval = next(hval, data, current_offset);
            htab[idx(hval)] = current_offset;
            current_offset += 1;

            hval = next(hval, data, current_offset);
            htab[idx(hval)] = current_offset;
            current_offset += 1;
        } else {
            /* one more literal byte we must copy */
            if current_offset >= out_buf_len {
                return Err(LzfError::NoCompressionPossible);
            }

            lit += 1;
            out[out_len as usize] = data[current_offset];
            out_len += 1;
            current_offset += 1;

            if lit == MAX_LIT {
                /* stop run */
                out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
                lit = 0;
                out_len += 1; /* start run */
            }
        }
    }

    /* at most 3 bytes can be missing here */
    if out_len + 3 > out_buf_len as i32 {
        return Err(LzfError::NoCompressionPossible);
    }

    while current_offset < in_len {
        lit += 1;
        out[out_len as usize] = data[current_offset];
        out_len += 1;
        current_offset += 1;

        if lit == MAX_LIT {
            /* stop run */
            out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
            lit = 0;
            out_len += 1; /* start run */
        }
    }

    /* end run */
    out[(out_len - lit - 1) as usize] = (lit as u8).wrapping_sub(1);
    out_len -= not(lit); /* undo run if length is zero */

    unsafe { out.set_len(out_len as usize) };

    Ok(out)
}

#[test]
fn test_compress_skips_short() {
    match compress("foo".as_bytes()) {
        Ok(_) => panic!("Compression did _something_, which is wrong for 'foo'"),
        Err(err) => assert_eq!(LzfError::NoCompressionPossible, err),
    }
}

#[test]
fn test_compress_lorem() {
    let lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod \
                 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At \
                 vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, \
                 no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit \
                 amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut \
                 labore et dolore magna aliquyam erat, sed diam voluptua.";

    match compress(lorem.as_bytes()) {
        Ok(compressed) => {
            assert_eq!(272, compressed.len())
        }
        Err(err) => panic!("Compression failed with error {:?}", err),
    }
}

#[test]
fn test_compress_decompress_lorem_round() {
    use super::decompress;

    let lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod \
                 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At \
                 vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, \
                 no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit \
                 amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut \
                 labore et dolore magna aliquyam erat, sed diam voluptua.";

    let compressed = match compress(lorem.as_bytes()) {
        Ok(c) => c,
        Err(err) => panic!("Compression failed with error {:?}", err),
    };

    match decompress(&compressed, lorem.len()) {
        Ok(decompressed) => {
            assert_eq!(lorem.len(), decompressed.len());
            assert_eq!(lorem.as_bytes(), &decompressed[..]);
        }
        Err(err) => panic!("Decompression failed with error {:?}", err),
    };
}

#[test]
fn test_alice_wonderland_both() {
    let alice = "\r\n\r\n\r\n\r\n                ALICE'S ADVENTURES IN WONDERLAND\r\n";

    let compressed = match compress(alice.as_bytes()) {
        Ok(c) => c,
        Err(err) => panic!("Compression failed with error {:?}", err),
    };

    let c_compressed = match super::compress(alice.as_bytes()) {
        Ok(c) => c,
        Err(err) => panic!("Compression failed with error {:?}", err),
    };

    assert_eq!(&compressed[..], &c_compressed[..]);
}
<|endoftext|>


<commit_before>extern crate discord;

use std::env;
use discord::{Discord, State};
use discord::model::Event;

// A simple DJ bot example.
// Use by issuing the command "!dj <youtube-link>" in PM or a visible text channel.
// The bot will join the voice channel of the person issuing the command.
// "!dj stop" will stop playing, and "!dj quit" will quit the voice channel.
// The bot will quit any voice channel it is the last user in.

pub fn main() {
	// Log in to Discord using a bot token from the environment
	let discord = Discord::from_bot_token(
		&env::var("DISCORD_TOKEN").expect("Expected token"),
	).expect("login failed");

	// establish websocket and voice connection
	let (mut connection, ready) = discord.connect().expect("connect failed");
	println!("[Ready] {} is serving {} servers", ready.user.username, ready.servers.len());
	let mut state = State::new(ready);
	state.sync_calls(&connection);

	// receive events forever
	loop {
		let event = match connection.recv_event() {
			Ok(event) => event,
			Err(err) => {
				println!("[Warning] Receive error: {:?}", err);
				if let discord::Error::WebSocket(..) = err {
					// Handle the websocket connection being dropped
					let (new_connection, ready) = discord.connect().expect("connect failed");
					connection = new_connection;
					state = State::new(ready);
					println!("[Ready] Reconnected successfully.");
				}
				if let discord::Error::Closed(..) = err {
					break
				}
				continue
			},
		};
		state.update(&event);

		match event {
			Event::MessageCreate(message) => {
				use std::ascii::AsciiExt;
				// safeguard: stop if the message is from us
				if message.author.id == state.user().id {
					continue
				}

				// reply to a command if there was one
				let mut split = message.content.split(' ');
				let first_word = split.next().unwrap_or("");
				let argument = split.next().unwrap_or("");

				if first_word.eq_ignore_ascii_case("!dj") {
					let vchan = state.find_voice_user(message.author.id);
					if argument.eq_ignore_ascii_case("stop") {
						vchan.map(|(sid, _)| connection.voice(sid).stop());
					} else if argument.eq_ignore_ascii_case("quit") {
						vchan.map(|(sid, _)| connection.drop_voice(sid));
					} else {
						let output = if let Some((server_id, channel_id)) = vchan {
							match discord::voice::open_ytdl_stream(argument) {
								Ok(stream) => {
									let voice = connection.voice(server_id);
									voice.set_deaf(true);
									voice.connect(channel_id);
									voice.play(stream);
									String::new()
								},
								Err(error) => format!("Error: {}", error),
							}
						} else {
							"You must be in a voice channel to DJ".to_owned()
						};
						if !output.is_empty() {
							warn(discord.send_message(&message.channel_id, &output, "", false));
						}
					}
				}
			}
			Event::VoiceStateUpdate(server_id, _) => {
				// If someone moves/hangs up, and we are in a voice channel,
				if let Some(cur_channel) = connection.voice(server_id).current_channel() {
					// and our current voice channel is empty, disconnect from voice
					match server_id {
						Some(server_id) => if let Some(srv) = state.servers().iter().find(|srv| srv.id == server_id) {
							if srv.voice_states.iter().filter(|vs| vs.channel_id == Some(cur_channel)).count() <= 1 {
								connection.voice(Some(server_id)).disconnect();
							}
						},
						None => if let Some(call) = state.calls().get(&cur_channel) {
							if call.voice_states.len() <= 1 {
								connection.voice(server_id).disconnect();
							}
						}
					}
				}
			}
			_ => {}, // discard other events
		}
	}
}

fn warn<T, E: ::std::fmt::Debug>(result: Result<T, E>) {
	match result {
		Ok(_) => {},
		Err(err) => println!("[Warning] {:?}", err)
	}
}
<commit_msg>Fix dj example for sync_calls change<commit_after>extern crate discord;

use std::env;
use discord::{Discord, State};
use discord::model::Event;

// A simple DJ bot example.
// Use by issuing the command "!dj <youtube-link>" in PM or a visible text channel.
// The bot will join the voice channel of the person issuing the command.
// "!dj stop" will stop playing, and "!dj quit" will quit the voice channel.
// The bot will quit any voice channel it is the last user in.

pub fn main() {
	// Log in to Discord using a bot token from the environment
	let discord = Discord::from_bot_token(
		&env::var("DISCORD_TOKEN").expect("Expected token"),
	).expect("login failed");

	// establish websocket and voice connection
	let (mut connection, ready) = discord.connect().expect("connect failed");
	println!("[Ready] {} is serving {} servers", ready.user.username, ready.servers.len());
	let mut state = State::new(ready);
	connection.sync_calls(&state.all_private_channels());

	// receive events forever
	loop {
		let event = match connection.recv_event() {
			Ok(event) => event,
			Err(err) => {
				println!("[Warning] Receive error: {:?}", err);
				if let discord::Error::WebSocket(..) = err {
					// Handle the websocket connection being dropped
					let (new_connection, ready) = discord.connect().expect("connect failed");
					connection = new_connection;
					state = State::new(ready);
					println!("[Ready] Reconnected successfully.");
				}
				if let discord::Error::Closed(..) = err {
					break
				}
				continue
			},
		};
		state.update(&event);

		match event {
			Event::MessageCreate(message) => {
				use std::ascii::AsciiExt;
				// safeguard: stop if the message is from us
				if message.author.id == state.user().id {
					continue
				}

				// reply to a command if there was one
				let mut split = message.content.split(' ');
				let first_word = split.next().unwrap_or("");
				let argument = split.next().unwrap_or("");

				if first_word.eq_ignore_ascii_case("!dj") {
					let vchan = state.find_voice_user(message.author.id);
					if argument.eq_ignore_ascii_case("stop") {
						vchan.map(|(sid, _)| connection.voice(sid).stop());
					} else if argument.eq_ignore_ascii_case("quit") {
						vchan.map(|(sid, _)| connection.drop_voice(sid));
					} else {
						let output = if let Some((server_id, channel_id)) = vchan {
							match discord::voice::open_ytdl_stream(argument) {
								Ok(stream) => {
									let voice = connection.voice(server_id);
									voice.set_deaf(true);
									voice.connect(channel_id);
									voice.play(stream);
									String::new()
								},
								Err(error) => format!("Error: {}", error),
							}
						} else {
							"You must be in a voice channel to DJ".to_owned()
						};
						if !output.is_empty() {
							warn(discord.send_message(&message.channel_id, &output, "", false));
						}
					}
				}
			}
			Event::VoiceStateUpdate(server_id, _) => {
				// If someone moves/hangs up, and we are in a voice channel,
				if let Some(cur_channel) = connection.voice(server_id).current_channel() {
					// and our current voice channel is empty, disconnect from voice
					match server_id {
						Some(server_id) => if let Some(srv) = state.servers().iter().find(|srv| srv.id == server_id) {
							if srv.voice_states.iter().filter(|vs| vs.channel_id == Some(cur_channel)).count() <= 1 {
								connection.voice(Some(server_id)).disconnect();
							}
						},
						None => if let Some(call) = state.calls().get(&cur_channel) {
							if call.voice_states.len() <= 1 {
								connection.voice(server_id).disconnect();
							}
						}
					}
				}
			}
			_ => {}, // discard other events
		}
	}
}

fn warn<T, E: ::std::fmt::Debug>(result: Result<T, E>) {
	match result {
		Ok(_) => {},
		Err(err) => println!("[Warning] {:?}", err)
	}
}
<|endoftext|>


<commit_before>use core::{Entity, Vec2};

/// The camera
struct Cam<'a> {
  /// The former camera pos
  former_pos: Vec2<i64>,
  /// The entity in focus
  in_focus: &'a Entity,
  /// The transition state
  trans_state: f64,
}

// TODO: Make the camera locked to the player? (Or is this a bad idea?)

const CAM_VELOCITY: f64 = 0.1;

impl<'a> Cam<'a> {
  /// Creates a new Cam
  fn new(focus: &'a Entity) -> Cam {
    Cam {
      former_pos: focus.get_pos(),
      in_focus: focus,
      trans_state: 0.0,
    }
  }
  /// Updates the Cam
  fn update(&mut self, dt: f64) {
    self.trans_state += dt * CAM_VELOCITY;
    if self.trans_state > 1.0 {
      self.trans_state = 0.0;
      self.former_pos = self.in_focus.get_pos();
    }
  }
  /// Get position
  fn get_pos(&self) -> Vec2<f64> {
    let pos = self.in_focus.get_pos() as Vec2<f64>;
    pos * self.trans_state
    + pos * (1.0 - self.trans_state)
  }
}
<commit_msg>Fix compile error<commit_after>use core::{Entity, Vec2};

/// The camera
struct Cam<'a> {
  /// The former camera pos
  former_pos: Vec2<i64>,
  /// The entity in focus
  in_focus: &'a Entity,
  /// The transition state
  trans_state: f64,
}

// TODO: Make the camera locked to the player? (Or is this a bad idea?)

const CAM_VELOCITY: f64 = 0.1;

impl<'a> Cam<'a> {
  /// Creates a new Cam
  fn new(focus: &'a Entity) -> Cam {
    Cam {
      former_pos: focus.get_pos(),
      in_focus: focus,
      trans_state: 0.0,
    }
  }
  /// Updates the Cam
  fn update(&mut self, dt: f64) {
    self.trans_state += dt * CAM_VELOCITY;
    if self.trans_state > 1.0 {
      self.trans_state = 0.0;
      self.former_pos = self.in_focus.get_pos();
    }
  }
  /// Get position
  fn get_pos(&self) -> Vec2<f64> {
    let pos: Vec2<f64> = Vec2::from(self.in_focus.get_pos());
    pos * self.trans_state
    + pos * (1.0 - self.trans_state)
  }
}
<|endoftext|>


<commit_before>use std::io;
use std::io::prelude::*;
use std::fs::File;

const INLINE_TAGS: [&'static str; 16] = [
    "area", "base", "br", "col", "command", "embed", "hr", "img",
    "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];

const DOCTYPE_HTML: &'static str = "html";
const DOCTYPE_XML: &'static str = "xml";
const DOCTYPE_JSON: &'static str = "json";

pub struct Document {
    path: &'static str,
    contents: String,
    doctype: &'static str
}

impl Document {
    pub fn new(path: &'static str) -> Result<Document, io::Error> {
        let mut f = try!(File::open(path));

        let mut contents = String::new();
        try!(f.read_to_string(&mut contents));

        Ok(Document {
            path: path,
            contents: contents,
            doctype: "unknown"
        })
    }

    pub fn validate(&mut self) -> Option<&'static str> {
        if self.contents.len() == 0 {
            return Some("The file was empty");
        }

        if !self.contents.starts_with("doctype") && !self.contents.starts_with("extends") {
            return Some("The document must start with a 'doctype' or 'extends'");
        }

        // TODO: this isn't future proof, will need fixing
        match self.contents.lines().collect::<Vec<_>>()[0] {
            "doctype html" => self.doctype = DOCTYPE_HTML,
            "doctype xml" => self.doctype = DOCTYPE_XML,
            "doctype json" => self.doctype = DOCTYPE_JSON,
            _ => return Some("Unknown 'doctype' supplied")
        }

        None
    }

    // TODO: move this into the compiler mod
    pub fn compile(self, pretty: bool) -> String {
        let nodes = parse(self);

        let mut parent_stack: Vec<&DocumentNode> = Vec::new();
        let mut output = String::new();
        let len = nodes.len();
        let mut i = 0;

        for n in &nodes {
            if i == 0 {
                if n.tokens[0] == "doctype" {
                    if let Some(doctype) = generate_doctype(&n.content) {
                        output.push_str(&doctype.to_string());

                        if pretty {
                            output.push('\n');
                        }
                    } else {
                        // TODO: proper error handling
                        panic!("Unknown 'doctype' suppied");
                    }
                } /*else if n.tokens[0] == "extends" {
                    // TODO: when extends is added
                }*/

                i += 1;
                continue;
            }

            // TODO: sort this code nesting out, it's too deep
            let has_next = i + 1 < len;
            let has_sub = has_next && nodes[i + 1].depth > n.depth;

            if has_sub {
                output.push_str(&n.render_open(pretty).to_string());
                parent_stack.push(n);
            } else {
                output.push_str(&n.render(pretty).to_string());

                if has_next && nodes[i + 1].depth < n.depth {
                    loop {
                        match parent_stack.pop() {
                            None =>  { break; },
                            Some(p) => {
                                output.push_str(&p.render_end(pretty).to_string());

                                if p.depth == nodes[i + 1].depth {
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            if !has_next && parent_stack.len() > 0 {
                loop {
                    match parent_stack.pop() {
                        None =>  { break; },
                        Some(p) => {
                            output.push_str(&p.render_end(pretty).to_string());
                        }
                    }
                }
            }

            if !pretty && has_next {
                let next = &nodes[i + 1];

                if n.tokens[0] == "|" && next.tokens[0] == "|" {
                    output.push(' ');
                }
            }

            i += 1;
        }

        output
    }
}

pub struct DocumentNode {
    depth: usize,
    tokens: Vec<String>,
    content: String,
    is_self_closing: bool,
    ignore_sub_content: bool
}

impl DocumentNode {
    fn from_content(indent: usize, content: String) -> DocumentNode {
        DocumentNode {
            depth: indent,
            tokens: vec!["|".to_string()],
            content: content,
            is_self_closing: false,
            ignore_sub_content: true
        }
    }

    fn from(line: &str, indent: usize, doctype: &str) -> Option<DocumentNode> {
        let (tokens, content) = match split_tokens(String::from(line.trim())) {
            Some(result) => result,
            None => return None
        };

        let mut is_self_closing = false;
        let mut ignore_sub_content = false;
        let last_token = &*tokens.last().unwrap().to_string();

        if tokens[0] == "//" || tokens[0] == "|" || tokens[0] == "style" || tokens[0] == "script" {
            ignore_sub_content = true;
        } else if last_token == "/" || (doctype == DOCTYPE_HTML && INLINE_TAGS.contains(&&*tokens[0].to_string())) {
            is_self_closing = true;
        }

        Some(DocumentNode {
            depth: indent,
            tokens: tokens,
            content: content,
            is_self_closing: is_self_closing,
            ignore_sub_content: ignore_sub_content
        })
    }

    fn render(&self, pretty: bool) -> String {
        let mut output = String::new();

        if self.tokens[0] == "|" {
            output.push_str(&self.content.to_string());

            if pretty {
                return pretty_print(&output, self.depth);
            } else {
                return output;
            }
        }

        output.push_str(&self.render_open(false).to_string());

        if self.is_self_closing {
            if pretty {
                return pretty_print(&output, self.depth);
            }

            return output;
        }

        if !self.content.is_empty() {
            output.push_str(&self.content.to_string());
        }

        output.push_str(&self.render_end(false).to_string());

        if pretty {
            return pretty_print(&output, self.depth);
        }

        output
    }

    fn render_open(&self, pretty: bool) -> String {
        let tag_tokens = &self.tokens;
        let mut output = String::new();

        if tag_tokens[0] == "//" {
            output.push_str("<!--");
        } else if tag_tokens[0] == "|" {
            output.push_str(&self.content.to_string());
        } else {
            output.push_str(&format!("<{}", tag_tokens[0]).to_string());

            if tag_tokens.len() > 1 {
                let mut classes = String::new();
                let mut attributes = String::new();

                for t in &tag_tokens[1..] {
                    if t.starts_with('#') {
                        output.push_str(&format!(" id=\"{}\"", t[1..].to_string()))
                    } else if t.starts_with('.') {
                        classes.push_str(&format!(" {}", t[1..].to_string()));
                    } else if t.starts_with('(') {
                        let attrs: Vec<_> = t[1..].split(',').collect();

                        attributes.push_str(&attrs.concat().to_string());
                    }
                }

                if !classes.is_empty() {
                    output.push_str(&format!(" class=\"{}\"", classes.trim()));
                }

                if !attributes.is_empty() {
                    output.push_str(&format!(" {}", attributes).to_string());
                }
            }

            if self.is_self_closing {
                output.push('/');
            }

            output.push('>');
        }

        if pretty {
            return pretty_print(&output, self.depth);
        }

        output
    }

    fn render_end(&self, pretty: bool) -> String {
        let output = match self.tokens[0].as_ref() {
            "//" => "-->".to_string(),
            "|" => String::new(),
            _ => format!("</{}>", &self.tokens[0])
        };

        if pretty && !output.is_empty() {
            return pretty_print(&output, self.depth);
        }

        output
    }
}

fn parse(doc: Document) -> Vec<DocumentNode> {
    let mut nodes: Vec<DocumentNode> = Vec::new();
    let mut parsable_indent = 0;
    let mut mode = 0;

    for line in doc.contents.lines() {
        if line.is_empty() {
            continue;
        }

        let mut indent: usize = 0;

        for c in line.chars() {
            if c.is_whitespace() {
                indent += 1;
            } else {
                break;
            }
        }

        if mode == 2 && indent > parsable_indent {
            continue;
        } else if mode == 1 && indent > parsable_indent {
            nodes.push(DocumentNode::from_content(indent, line.trim().to_string()));
            continue;
        }

        if let Some(node) = DocumentNode::from(line, indent, doc.doctype) {
            if node.ignore_sub_content {
                mode = 1;
            } else {
                mode = 0;
            }

            nodes.push(node);
        } else {
            mode = 2;
        }

        parsable_indent = indent;
    }

    nodes
}

fn split_tokens(s: String) -> Option<(Vec<String>, String)> {
    let mut tokens: Vec<String> = Vec::new();

    let mut start = 0;
    let len = s.len();
    let mut mode = 0;
    let mut content = String::new();

    if s.starts_with("|") {
        tokens.push("|".to_string());
        content.push_str(&s[1..].trim().to_string());
    } else if s.starts_with("//-") {
        return None;
    } else if s.starts_with("//") {
        tokens.push("//".to_string());
        content.push_str(&s[2..].trim().to_string());
    }

    if tokens.len() > 0 {
        return Some((tokens, content));
    }

    for (i, c) in s.chars().enumerate() {
        if c.is_whitespace() && mode == 0 {
            tokens.push(s[start..i].to_string());
            content.push_str(&s[i..len].trim().to_string());
            break;
        }

        if c == ')' {
            if mode != 1 {
                // TODO: proper error handling
                panic!("Invalid attribute closing brace");
            }

            tokens.push(s[start..i].to_string());

            mode = 0;
            start = i;
        } else {
            if c == '/' && mode == 0 {
                tokens.push(s[start..i].to_string());
                tokens.push("/".to_string());
                start = i + 1;
            } else if ((c == '#' || c == '.' || c == '(') && mode == 0) || i == len - 1 {
                if i == len - 1 {
                    tokens.push(s[start..].to_string());
                } else if i > start {
                    tokens.push(s[start..i].to_string());
                }

                if c == '(' {
                    mode = 1;
                }

                start = i;
            }
        }
    }

    // TODO: pretty sure this can be done using a pattern in the starts_with
    if tokens.len() > 0 && (tokens[0].starts_with(".") || tokens[0].starts_with("#")) {
        tokens.insert(0, "div".to_string());
    }

    Some((tokens, content))
}

// TODO: need to abstract this stuff to support multiple doctypes
fn generate_doctype(content: &String) -> Option<String> {
    return match content.as_ref() {
        "html" => Some(format!("<!DOCTYPE {}>", content)),
        "xml" => Some("<?xml version=\"1.0\" encoding=\"utf-8\" ?>".to_string()),
        _ => None
    };
}

// TODO: look at making this a macro
// TODO: reduce if statements when using this by applying it always with a flag to indicate which format to use
fn pretty_print(content: &String, depth: usize) -> String {
    let mut output = String::new();

    for _ in 0..depth {
        output.push(' ');
    }

    output.push_str(&content.to_string());
    output.push('\n');
    output
}
<commit_msg>fixed code block render issue for xml<commit_after>use std::io;
use std::io::prelude::*;
use std::fs::File;

const INLINE_TAGS: [&'static str; 16] = [
    "area", "base", "br", "col", "command", "embed", "hr", "img",
    "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];

const DOCTYPE_HTML: &'static str = "html";
const DOCTYPE_XML: &'static str = "xml";
const DOCTYPE_JSON: &'static str = "json";

pub struct Document {
    path: &'static str,
    contents: String,
    doctype: &'static str
}

impl Document {
    pub fn new(path: &'static str) -> Result<Document, io::Error> {
        let mut f = try!(File::open(path));

        let mut contents = String::new();
        try!(f.read_to_string(&mut contents));

        Ok(Document {
            path: path,
            contents: contents,
            doctype: "unknown"
        })
    }

    pub fn validate(&mut self) -> Option<&'static str> {
        if self.contents.len() == 0 {
            return Some("The file was empty");
        }

        if !self.contents.starts_with("doctype") && !self.contents.starts_with("extends") {
            return Some("The document must start with a 'doctype' or 'extends'");
        }

        // TODO: this isn't future proof, will need fixing
        match self.contents.lines().collect::<Vec<_>>()[0] {
            "doctype html" => self.doctype = DOCTYPE_HTML,
            "doctype xml" => self.doctype = DOCTYPE_XML,
            "doctype json" => self.doctype = DOCTYPE_JSON,
            _ => return Some("Unknown 'doctype' supplied")
        }

        None
    }

    // TODO: move this into the compiler mod
    pub fn compile(self, pretty: bool) -> String {
        let nodes = parse(self);

        let mut parent_stack: Vec<&DocumentNode> = Vec::new();
        let mut output = String::new();
        let len = nodes.len();
        let mut i = 0;

        for n in &nodes {
            if i == 0 {
                if n.tokens[0] == "doctype" {
                    if let Some(doctype) = generate_doctype(&n.content) {
                        output.push_str(&doctype.to_string());

                        if pretty {
                            output.push('\n');
                        }
                    } else {
                        // TODO: proper error handling
                        panic!("Unknown 'doctype' suppied");
                    }
                } /*else if n.tokens[0] == "extends" {
                    // TODO: when extends is added
                }*/

                i += 1;
                continue;
            }

            // TODO: sort this code nesting out, it's too deep
            let has_next = i + 1 < len;
            let has_sub = has_next && nodes[i + 1].depth > n.depth;

            if has_sub {
                output.push_str(&n.render_open(pretty).to_string());
                parent_stack.push(n);
            } else {
                output.push_str(&n.render(pretty).to_string());

                if has_next && nodes[i + 1].depth < n.depth {
                    loop {
                        match parent_stack.pop() {
                            None =>  { break; },
                            Some(p) => {
                                output.push_str(&p.render_end(pretty).to_string());

                                if p.depth == nodes[i + 1].depth {
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            if !has_next && parent_stack.len() > 0 {
                loop {
                    match parent_stack.pop() {
                        None =>  { break; },
                        Some(p) => {
                            output.push_str(&p.render_end(pretty).to_string());
                        }
                    }
                }
            }

            if !pretty && has_next {
                let next = &nodes[i + 1];

                if n.tokens[0] == "|" && next.tokens[0] == "|" {
                    output.push(' ');
                }
            }

            i += 1;
        }

        output
    }
}

pub struct DocumentNode {
    depth: usize,
    tokens: Vec<String>,
    content: String,
    is_self_closing: bool,
    ignore_sub_content: bool
}

impl DocumentNode {
    fn from_content(indent: usize, content: String) -> DocumentNode {
        DocumentNode {
            depth: indent,
            tokens: vec!["|".to_string()],
            content: content,
            is_self_closing: false,
            ignore_sub_content: true
        }
    }

    fn from(line: &str, indent: usize, doctype: &str) -> Option<DocumentNode> {
        let (tokens, content) = match split_tokens(String::from(line.trim())) {
            Some(result) => result,
            None => return None
        };

        let mut is_self_closing = false;
        let mut ignore_sub_content = false;
        let last_token = &*tokens.last().unwrap().to_string();

        if tokens[0] == "//" || tokens[0] == "|" {
            ignore_sub_content = true;
        } else if last_token == "/" {
            is_self_closing = true;
        }

        if doctype == DOCTYPE_HTML {
            if tokens[0] == "style" || tokens[0] == "script" {
                ignore_sub_content = true;
            } else if INLINE_TAGS.contains(&&*tokens[0].to_string()) {
                is_self_closing = true;
            }
        }

        Some(DocumentNode {
            depth: indent,
            tokens: tokens,
            content: content,
            is_self_closing: is_self_closing,
            ignore_sub_content: ignore_sub_content
        })
    }

    fn render(&self, pretty: bool) -> String {
        let mut output = String::new();

        if self.tokens[0] == "|" {
            output.push_str(&self.content.to_string());

            if pretty {
                return pretty_print(&output, self.depth);
            } else {
                return output;
            }
        }

        output.push_str(&self.render_open(false).to_string());

        if self.is_self_closing {
            if pretty {
                return pretty_print(&output, self.depth);
            }

            return output;
        }

        if !self.content.is_empty() {
            output.push_str(&self.content.to_string());
        }

        output.push_str(&self.render_end(false).to_string());

        if pretty {
            return pretty_print(&output, self.depth);
        }

        output
    }

    fn render_open(&self, pretty: bool) -> String {
        let tag_tokens = &self.tokens;
        let mut output = String::new();

        if tag_tokens[0] == "//" {
            output.push_str("<!--");
        } else if tag_tokens[0] == "|" {
            output.push_str(&self.content.to_string());
        } else {
            output.push_str(&format!("<{}", tag_tokens[0]).to_string());

            if tag_tokens.len() > 1 {
                let mut classes = String::new();
                let mut attributes = String::new();

                for t in &tag_tokens[1..] {
                    if t.starts_with('#') {
                        output.push_str(&format!(" id=\"{}\"", t[1..].to_string()))
                    } else if t.starts_with('.') {
                        classes.push_str(&format!(" {}", t[1..].to_string()));
                    } else if t.starts_with('(') {
                        let attrs: Vec<_> = t[1..].split(',').collect();

                        attributes.push_str(&attrs.concat().to_string());
                    }
                }

                if !classes.is_empty() {
                    output.push_str(&format!(" class=\"{}\"", classes.trim()));
                }

                if !attributes.is_empty() {
                    output.push_str(&format!(" {}", attributes).to_string());
                }
            }

            if self.is_self_closing {
                output.push('/');
            }

            output.push('>');
        }

        if pretty {
            return pretty_print(&output, self.depth);
        }

        output
    }

    fn render_end(&self, pretty: bool) -> String {
        let output = match self.tokens[0].as_ref() {
            "//" => "-->".to_string(),
            "|" => String::new(),
            _ => format!("</{}>", &self.tokens[0])
        };

        if pretty && !output.is_empty() {
            return pretty_print(&output, self.depth);
        }

        output
    }
}

fn parse(doc: Document) -> Vec<DocumentNode> {
    let mut nodes: Vec<DocumentNode> = Vec::new();
    let mut parsable_indent = 0;
    let mut mode = 0;

    for line in doc.contents.lines() {
        if line.is_empty() {
            continue;
        }

        let mut indent: usize = 0;

        for c in line.chars() {
            if c.is_whitespace() {
                indent += 1;
            } else {
                break;
            }
        }

        if mode == 2 && indent > parsable_indent {
            continue;
        } else if mode == 1 && indent > parsable_indent {
            nodes.push(DocumentNode::from_content(indent, line.trim().to_string()));
            continue;
        }

        if let Some(node) = DocumentNode::from(line, indent, doc.doctype) {
            if node.ignore_sub_content {
                mode = 1;
            } else {
                mode = 0;
            }

            nodes.push(node);
        } else {
            mode = 2;
        }

        parsable_indent = indent;
    }

    nodes
}

fn split_tokens(s: String) -> Option<(Vec<String>, String)> {
    let mut tokens: Vec<String> = Vec::new();

    let mut start = 0;
    let len = s.len();
    let mut mode = 0;
    let mut content = String::new();

    if s.starts_with("|") {
        tokens.push("|".to_string());
        content.push_str(&s[1..].trim().to_string());
    } else if s.starts_with("//-") {
        return None;
    } else if s.starts_with("//") {
        tokens.push("//".to_string());
        content.push_str(&s[2..].trim().to_string());
    }

    if tokens.len() > 0 {
        return Some((tokens, content));
    }

    for (i, c) in s.chars().enumerate() {
        if c.is_whitespace() && mode == 0 {
            tokens.push(s[start..i].to_string());
            content.push_str(&s[i..len].trim().to_string());
            break;
        }

        if c == ')' {
            if mode != 1 {
                // TODO: proper error handling
                panic!("Invalid attribute closing brace");
            }

            tokens.push(s[start..i].to_string());

            mode = 0;
            start = i;
        } else {
            if c == '/' && mode == 0 {
                tokens.push(s[start..i].to_string());
                tokens.push("/".to_string());
                start = i + 1;
            } else if ((c == '#' || c == '.' || c == '(') && mode == 0) || i == len - 1 {
                if i == len - 1 {
                    tokens.push(s[start..].to_string());
                } else if i > start {
                    tokens.push(s[start..i].to_string());
                }

                if c == '(' {
                    mode = 1;
                }

                start = i;
            }
        }
    }

    // TODO: pretty sure this can be done using a pattern in the starts_with
    if tokens.len() > 0 && (tokens[0].starts_with(".") || tokens[0].starts_with("#")) {
        tokens.insert(0, "div".to_string());
    }

    Some((tokens, content))
}

// TODO: need to abstract this stuff to support multiple doctypes
fn generate_doctype(content: &String) -> Option<String> {
    return match content.as_ref() {
        "html" => Some(format!("<!DOCTYPE {}>", content)),
        "xml" => Some("<?xml version=\"1.0\" encoding=\"utf-8\" ?>".to_string()),
        _ => None
    };
}

// TODO: look at making this a macro
// TODO: reduce if statements when using this by applying it always with a flag to indicate which format to use
fn pretty_print(content: &String, depth: usize) -> String {
    let mut output = String::new();

    for _ in 0..depth {
        output.push(' ');
    }

    output.push_str(&content.to_string());
    output.push('\n');
    output
}
<|endoftext|>