<commit_before>// DOCS

#![feature(plugin,core,collections,std_misc)]
#![plugin(peg_syntax_ext)]

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::borrow::ToOwned;
use std::str::FromStr;
use std::fmt;
use std::error::Error;

pub struct Property {
    /// Parameters.
    pub params: HashMap<String, String>,

    /// Value as unparsed string.
    pub raw_value: String,

    /// Property group. E.g. a contentline like `foo.FN:Markus` would result in the group being
    /// `"foo"`.
    pub prop_group: Option<String>
}

impl Property {
    /// Create property from unescaped string.
    pub fn new(value: &str) -> Property {
        Property {
            params: HashMap::new(),
            raw_value: escape_chars(value),
            prop_group: None
        }
    }

    /// Get value as unescaped string.
    pub fn value_as_string(&self) -> String {
        unescape_chars(self.raw_value.as_slice())
    }
}

pub struct Component {
    /// The name of the component, such as `VCARD` or `VEVENT`.
    pub name: String,

    /// The component's properties.
    pub props: HashMap<String, Vec<Property>>,

    /// The component's child- or sub-components.
    pub subcomponents: Vec<Component>
}

impl Component {
    pub fn new(name: &str) -> Component {
        Component {
            name: name.to_string(),
            props: HashMap::new(),
            subcomponents: vec![]
        }
    }

    /// Retrieve one property (from many) by key. Returns `None` if nothing is found.
    pub fn single_prop(&self, key: &str) -> Option<&Property> {
        match self.props.get(key) {
            Some(x) => {
                match x.len() {
                    1 => Some(&x[0]),
                    _ => None
                }
            },
            None => None
        }
    }

    /// Retrieve a mutable vector of properties for this key. Creates one (and inserts it into the
    /// component) if none exists.
    pub fn all_props_mut(&mut self, key: &str) -> &mut Vec<Property> {
        match self.props.entry(String::from_str(key)) {
            Occupied(values) => values.into_mut(),
            Vacant(values) => values.insert(vec![])
        }
    }

    /// Retrieve properties by key. Returns an empty slice if key doesn't exist.
    pub fn all_props(&self, key: &str) -> &[Property] {
        static EMPTY: &'static [Property] = &[];
        match self.props.get(key) {
            Some(values) => values.as_slice(),
            None => EMPTY
        }
    }
}

impl FromStr for Component {
    type Err = String;

    /// Same as `vobject::parse_component`, but without the error messages.
    fn from_str(s: &str) -> Result<Component, String> {
        match parse_component(s) {
            Ok(x) => Ok(x),
            Err(e) => Err(e.into_string())
        }
    }
}

/// Parse a component. The error value is a human-readable message.
pub fn parse_component(s: &str) -> ParseResult<Component> {
    // XXX: The unfolding should be worked into the PEG
    // See feature request: https://github.com/kevinmehall/rust-peg/issues/26
    let unfolded = unfold_lines(s);
    match parser::component(unfolded.as_slice()) {
        Ok(x) => Ok(x),
        Err(e) => Err(ParseError::from_peg_error(e))
    }
}

/// Write a component. The error value is a human-readable message.
pub fn write_component(c: &Component) -> String {
    fn inner(buf: &mut String, c: &Component) {
        buf.push_str("BEGIN:");
        buf.push_str(c.name.as_slice());&
        buf.push_str("\r\n");

        for (prop_name, props) in c.props.iter() {
            for prop in props.iter() {
                match prop.prop_group {
                    Some(ref x) => { buf.push_str(x.as_slice()); buf.push('.'); },
                    None => ()
                };
                buf.push_str(prop_name.as_slice());
                for (param_key, param_value) in prop.params.iter() {
                    buf.push(';');
                    buf.push_str(param_key.as_slice());
                    buf.push('=');
                    buf.push_str(param_value.as_slice());
                };
                buf.push(':');
                buf.push_str(fold_line(prop.raw_value.as_slice()).as_slice());
                buf.push_str("\r\n");
            };
        };

        for subcomponent in c.subcomponents.iter() {
            inner(buf, subcomponent);
        };

        buf.push_str("END:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");
    }

    let mut buf = String::new();
    inner(&mut buf, c);
    buf
}

/// Escape text for a VObject property value.
pub fn escape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\n")
        .replace("\\", "\\\\")
        .replace(";", "\\;")
        .replace(",", "\\,")
        .replace("\r\n", "\\n")
        .replace("\n", "\\n")
}

/// Unescape text from a VObject property value.
pub fn unescape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\\n")
        .replace("\r\n", "\n")
        .replace("\\n", "\n")
        .replace("\\,", ",")
        .replace("\\;", ";")
        .replace("\\\\", "\\")
}

/// Unfold contentline.
pub fn unfold_lines(s: &str) -> String {
    s
        .replace("\r\n ", "").replace("\r\n\t", "")
        .replace("\n ", "").replace("\n\t", "")
        .replace("\r ", "").replace("\r\t", "")
}

/// Fold contentline to 75 chars. This function assumes the input to be unfolded, which means no
/// '\n' or '\r' in it.
pub fn fold_line(s: &str) -> String {
    let mut rv = String::new();
    for (i, c) in s.chars().enumerate() {
        rv.push(c);
        if i != 0 && i % 75 == 0 {
            rv.push_str("\r\n ");
        };
    };
    rv
}

#[derive(PartialEq, Eq, Debug)]
pub struct ParseError {
    desc: String,
    orig: Option<parser::ParseError>
}

pub type ParseResult<T> = Result<T, ParseError>;

impl Error for ParseError {
    fn description(&self) -> &str {
        self.desc.as_slice()
    }

    fn cause(&self) -> Option<&Error> {
        match self.parser_error() {
            Some(x) => Some(&*x),
            None => None
        }
    }
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.description().fmt(f)
    }
}

impl ParseError {
    pub fn new(desc: String, cause: Option<parser::ParseError>) -> Self {
        ParseError {
            desc: desc,
            orig: cause
        }
    }

    pub fn from_peg_error(e: parser::ParseError) -> Self {
        ParseError {
            desc: format!("{}", e).to_owned(),
            orig: Some(e)
        }
    }

    pub fn into_string(self) -> String {
        self.desc
    }

    /// Access the underlying parser error.
    pub fn parser_error(&self) -> Option<&parser::ParseError> {
        self.orig.as_ref()
    }

    /// The line where the error occured.
    ///
    /// The value might be warped because content lines are unfolded before the parser keeps track
    /// of line numbers.
    pub fn line(&self) -> Option<&usize> {
        match self.parser_error() {
            Some(e) => Some(&e.line),
            None => None
        }
    }

    /// The column where the error occured.
    ///
    /// The value might be warped because content lines are unfolded before the parser keeps track
    /// of line numbers.
    pub fn column(&self) -> Option<&usize> {
        match self.parser_error() {
            Some(e) => Some(&e.column),
            None => None
        }
    }
}

peg! parser(r#"
use super::{Component,Property};
use std::collections::HashMap;

components -> Vec<Component>
    = cs:component ** eols __ { cs }

    #[pub]
    component -> Component
        = name:component_begin
          ps:props
          cs:components
          component_end {
            let mut rv = Component::new(name);
            rv.subcomponents = cs;

            for (k, v) in ps.into_iter() {
                rv.all_props_mut(k).push(v);
            };

            rv
        }

    component_begin -> &'input str
        = "BEGIN:" v:value __ { v }

    component_end -> &'input str
        = "END:" v:value __ { v }

props -> Vec<(&'input str, Property)>
    = ps:prop ++ eols __ { ps }

    prop -> (&'input str, Property)
        = !"BEGIN:" !"END:" g:group? k:name p:params ":" v:value {
            (k, Property { params: p, raw_value: v.to_string(), prop_group: g })
        }

    group -> String
        = g:group_name "." { g.to_string() }

        group_name -> &'input str
            = group_char+ { match_str }

    name -> &'input str
        = iana_token+ { match_str }

    params -> HashMap<String, String>
        = ps:(";" p:param {p})* {
            let mut rv: HashMap<String, String> = HashMap::with_capacity(ps.len());
            rv.extend(ps.into_iter().map(|(k, v)| (k.to_string(), v.to_string())));
            rv
        }

        param -> (&'input str, &'input str)
            // FIXME: Doesn't handle comma-separated values
            = k:param_name v:("=" v:param_value { v })? {
                (k, match v {
                    Some(x) => x,
                    None => ""
                })
            }

        param_name -> &'input str
            = iana_token+ { match_str }

        param_value -> &'input str
            = x:(quoted_string / param_text) { x }

        param_text -> &'input str
            = safe_char* { match_str }

    value -> &'input str
        = value_char+ { match_str }

quoted_string -> &'input str
    = dquote x:quoted_content dquote { x }

quoted_content -> &'input str
    = qsafe_char* { match_str }

iana_token = ([a-zA-Z0-9] / "-")+
group_char = ([a-zA-Z0-9] / "-")
qsafe_char = !dquote !ctl value_char
safe_char = !";" !":" qsafe_char

value_char = !eol .

eol = "\r\n" / "\n" / "\r"
dquote = "\""
eols = eol+

// Taken from vCard. vCalendar's is a subset. Together with the definition of "qsafe_char" this
// might reject a bunch of valid iCalendars, but I can't imagine one.
ctl = [\u{00}-\u{1F}] / "\u{7F}"

whitespace = " " / "\t"
__ = (eol / whitespace)*

"#);
<commit_msg>impl Clone for Property<commit_after>// DOCS

#![feature(plugin,core,collections,std_misc)]
#![plugin(peg_syntax_ext)]

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::borrow::ToOwned;
use std::str::FromStr;
use std::fmt;
use std::error::Error;

pub struct Property {
    /// Parameters.
    pub params: HashMap<String, String>,

    /// Value as unparsed string.
    pub raw_value: String,

    /// Property group. E.g. a contentline like `foo.FN:Markus` would result in the group being
    /// `"foo"`.
    pub prop_group: Option<String>
}

impl Property {
    /// Create property from unescaped string.
    pub fn new(value: &str) -> Property {
        Property {
            params: HashMap::new(),
            raw_value: escape_chars(value),
            prop_group: None
        }
    }

    /// Get value as unescaped string.
    pub fn value_as_string(&self) -> String {
        unescape_chars(self.raw_value.as_slice())
    }
}

impl Clone for Property {
    fn clone(&self) -> Self {
        Property {
            params: self.params.clone(),
            raw_value: self.raw_value.clone(),
            prop_group: self.prop_group.clone()
        }
    }
}

pub struct Component {
    /// The name of the component, such as `VCARD` or `VEVENT`.
    pub name: String,

    /// The component's properties.
    pub props: HashMap<String, Vec<Property>>,

    /// The component's child- or sub-components.
    pub subcomponents: Vec<Component>
}

impl Component {
    pub fn new(name: &str) -> Component {
        Component {
            name: name.to_string(),
            props: HashMap::new(),
            subcomponents: vec![]
        }
    }

    /// Retrieve one property (from many) by key. Returns `None` if nothing is found.
    pub fn single_prop(&self, key: &str) -> Option<&Property> {
        match self.props.get(key) {
            Some(x) => {
                match x.len() {
                    1 => Some(&x[0]),
                    _ => None
                }
            },
            None => None
        }
    }

    /// Retrieve a mutable vector of properties for this key. Creates one (and inserts it into the
    /// component) if none exists.
    pub fn all_props_mut(&mut self, key: &str) -> &mut Vec<Property> {
        match self.props.entry(String::from_str(key)) {
            Occupied(values) => values.into_mut(),
            Vacant(values) => values.insert(vec![])
        }
    }

    /// Retrieve properties by key. Returns an empty slice if key doesn't exist.
    pub fn all_props(&self, key: &str) -> &[Property] {
        static EMPTY: &'static [Property] = &[];
        match self.props.get(key) {
            Some(values) => values.as_slice(),
            None => EMPTY
        }
    }
}

impl FromStr for Component {
    type Err = String;

    /// Same as `vobject::parse_component`, but without the error messages.
    fn from_str(s: &str) -> Result<Component, String> {
        match parse_component(s) {
            Ok(x) => Ok(x),
            Err(e) => Err(e.into_string())
        }
    }
}

/// Parse a component. The error value is a human-readable message.
pub fn parse_component(s: &str) -> ParseResult<Component> {
    // XXX: The unfolding should be worked into the PEG
    // See feature request: https://github.com/kevinmehall/rust-peg/issues/26
    let unfolded = unfold_lines(s);
    match parser::component(unfolded.as_slice()) {
        Ok(x) => Ok(x),
        Err(e) => Err(ParseError::from_peg_error(e))
    }
}

/// Write a component. The error value is a human-readable message.
pub fn write_component(c: &Component) -> String {
    fn inner(buf: &mut String, c: &Component) {
        buf.push_str("BEGIN:");
        buf.push_str(c.name.as_slice());&
        buf.push_str("\r\n");

        for (prop_name, props) in c.props.iter() {
            for prop in props.iter() {
                match prop.prop_group {
                    Some(ref x) => { buf.push_str(x.as_slice()); buf.push('.'); },
                    None => ()
                };
                buf.push_str(prop_name.as_slice());
                for (param_key, param_value) in prop.params.iter() {
                    buf.push(';');
                    buf.push_str(param_key.as_slice());
                    buf.push('=');
                    buf.push_str(param_value.as_slice());
                };
                buf.push(':');
                buf.push_str(fold_line(prop.raw_value.as_slice()).as_slice());
                buf.push_str("\r\n");
            };
        };

        for subcomponent in c.subcomponents.iter() {
            inner(buf, subcomponent);
        };

        buf.push_str("END:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");
    }

    let mut buf = String::new();
    inner(&mut buf, c);
    buf
}

/// Escape text for a VObject property value.
pub fn escape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\n")
        .replace("\\", "\\\\")
        .replace(";", "\\;")
        .replace(",", "\\,")
        .replace("\r\n", "\\n")
        .replace("\n", "\\n")
}

/// Unescape text from a VObject property value.
pub fn unescape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\\n")
        .replace("\r\n", "\n")
        .replace("\\n", "\n")
        .replace("\\,", ",")
        .replace("\\;", ";")
        .replace("\\\\", "\\")
}

/// Unfold contentline.
pub fn unfold_lines(s: &str) -> String {
    s
        .replace("\r\n ", "").replace("\r\n\t", "")
        .replace("\n ", "").replace("\n\t", "")
        .replace("\r ", "").replace("\r\t", "")
}

/// Fold contentline to 75 chars. This function assumes the input to be unfolded, which means no
/// '\n' or '\r' in it.
pub fn fold_line(s: &str) -> String {
    let mut rv = String::new();
    for (i, c) in s.chars().enumerate() {
        rv.push(c);
        if i != 0 && i % 75 == 0 {
            rv.push_str("\r\n ");
        };
    };
    rv
}

#[derive(PartialEq, Eq, Debug)]
pub struct ParseError {
    desc: String,
    orig: Option<parser::ParseError>
}

pub type ParseResult<T> = Result<T, ParseError>;

impl Error for ParseError {
    fn description(&self) -> &str {
        self.desc.as_slice()
    }

    fn cause(&self) -> Option<&Error> {
        match self.parser_error() {
            Some(x) => Some(&*x),
            None => None
        }
    }
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.description().fmt(f)
    }
}

impl ParseError {
    pub fn new(desc: String, cause: Option<parser::ParseError>) -> Self {
        ParseError {
            desc: desc,
            orig: cause
        }
    }

    pub fn from_peg_error(e: parser::ParseError) -> Self {
        ParseError {
            desc: format!("{}", e).to_owned(),
            orig: Some(e)
        }
    }

    pub fn into_string(self) -> String {
        self.desc
    }

    /// Access the underlying parser error.
    pub fn parser_error(&self) -> Option<&parser::ParseError> {
        self.orig.as_ref()
    }

    /// The line where the error occured.
    ///
    /// The value might be warped because content lines are unfolded before the parser keeps track
    /// of line numbers.
    pub fn line(&self) -> Option<&usize> {
        match self.parser_error() {
            Some(e) => Some(&e.line),
            None => None
        }
    }

    /// The column where the error occured.
    ///
    /// The value might be warped because content lines are unfolded before the parser keeps track
    /// of line numbers.
    pub fn column(&self) -> Option<&usize> {
        match self.parser_error() {
            Some(e) => Some(&e.column),
            None => None
        }
    }
}

peg! parser(r#"
use super::{Component,Property};
use std::collections::HashMap;

components -> Vec<Component>
    = cs:component ** eols __ { cs }

    #[pub]
    component -> Component
        = name:component_begin
          ps:props
          cs:components
          component_end {
            let mut rv = Component::new(name);
            rv.subcomponents = cs;

            for (k, v) in ps.into_iter() {
                rv.all_props_mut(k).push(v);
            };

            rv
        }

    component_begin -> &'input str
        = "BEGIN:" v:value __ { v }

    component_end -> &'input str
        = "END:" v:value __ { v }

props -> Vec<(&'input str, Property)>
    = ps:prop ++ eols __ { ps }

    prop -> (&'input str, Property)
        = !"BEGIN:" !"END:" g:group? k:name p:params ":" v:value {
            (k, Property { params: p, raw_value: v.to_string(), prop_group: g })
        }

    group -> String
        = g:group_name "." { g.to_string() }

        group_name -> &'input str
            = group_char+ { match_str }

    name -> &'input str
        = iana_token+ { match_str }

    params -> HashMap<String, String>
        = ps:(";" p:param {p})* {
            let mut rv: HashMap<String, String> = HashMap::with_capacity(ps.len());
            rv.extend(ps.into_iter().map(|(k, v)| (k.to_string(), v.to_string())));
            rv
        }

        param -> (&'input str, &'input str)
            // FIXME: Doesn't handle comma-separated values
            = k:param_name v:("=" v:param_value { v })? {
                (k, match v {
                    Some(x) => x,
                    None => ""
                })
            }

        param_name -> &'input str
            = iana_token+ { match_str }

        param_value -> &'input str
            = x:(quoted_string / param_text) { x }

        param_text -> &'input str
            = safe_char* { match_str }

    value -> &'input str
        = value_char+ { match_str }

quoted_string -> &'input str
    = dquote x:quoted_content dquote { x }

quoted_content -> &'input str
    = qsafe_char* { match_str }

iana_token = ([a-zA-Z0-9] / "-")+
group_char = ([a-zA-Z0-9] / "-")
qsafe_char = !dquote !ctl value_char
safe_char = !";" !":" qsafe_char

value_char = !eol .

eol = "\r\n" / "\n" / "\r"
dquote = "\""
eols = eol+

// Taken from vCard. vCalendar's is a subset. Together with the definition of "qsafe_char" this
// might reject a bunch of valid iCalendars, but I can't imagine one.
ctl = [\u{00}-\u{1F}] / "\u{7F}"

whitespace = " " / "\t"
__ = (eol / whitespace)*

"#);
<|endoftext|>


<commit_before>#![cfg(all(feature = "elastictranscoder", feature = "s3"))]

extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rand;
extern crate rusoto_core;
extern crate rusoto_elastictranscoder;
extern crate rusoto_s3;

use std::clone::Clone;
use std::ops::{Deref, DerefMut};

use rand::Rng;
use rusoto_core::Region;
use rusoto_elastictranscoder::{Ets, EtsClient};
use rusoto_s3::{CreateBucketRequest, DeleteBucketRequest, S3Client, S3};

const AWS_ETS_WEB_PRESET_ID: &'static str = "1351620000001-100070";
const AWS_ETS_WEB_PRESET_NAME: &'static str = "System preset: Web";
const AWS_REGION: Region = Region::UsEast1;
const AWS_SERVICE_RANDOM_SUFFIX_LENGTH: usize = 20;

struct TestEtsClient {
    region: Region,

    client: EtsClient,

    s3_client: Option<S3Client>,
    input_bucket: Option<String>,
    output_bucket: Option<String>,
}

impl TestEtsClient {
    /// Creates a new `EtsClient` for a test.
    fn new(region: Region) -> TestEtsClient {
        TestEtsClient {
            region: region.clone(),
            client: EtsClient::new(region),
            s3_client: None,
            input_bucket: None,
            output_bucket: None,
        }
    }

    fn create_s3_client(&mut self) {
        self.s3_client = Some(S3Client::new(self.region.clone()));
    }

    fn create_bucket(&mut self) -> String {
        let bucket_name = generate_unique_name("ets-bucket-1");

        let create_bucket_req = CreateBucketRequest {
            bucket: bucket_name.to_owned(),
            ..Default::default()
        };

        let result = self
            .s3_client
            .as_ref()
            .unwrap()
            .create_bucket(create_bucket_req)
            .sync();

        let mut location = result.unwrap().location.unwrap();
        // A `Location` is identical to a `BucketName` except that it has a
        // forward slash prepended to it, so we need to remove it.
        location.remove(0);
        info!("Created S3 bucket: {}", location);
        location
    }
}

impl Deref for TestEtsClient {
    type Target = EtsClient;
    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

impl DerefMut for TestEtsClient {
    fn deref_mut<'a>(&'a mut self) -> &'a mut EtsClient {
        &mut self.client
    }
}

impl Drop for TestEtsClient {
    fn drop(&mut self) {
        self.s3_client.take().map(|s3_client| {
            self.input_bucket.take().map(|bucket| {
                let delete_bucket_req = DeleteBucketRequest {
                    bucket: bucket.to_owned(),
                    ..Default::default()
                };

                match s3_client.delete_bucket(delete_bucket_req).sync() {
                    Ok(_) => info!("Deleted S3 bucket: {}", bucket),
                    Err(e) => error!("Failed to delete S3 bucket: {}", e),
                };
            });
            self.output_bucket.take().map(|bucket| {
                let delete_bucket_req = DeleteBucketRequest {
                    bucket: bucket.to_owned(),
                    ..Default::default()
                };

                match s3_client.delete_bucket(delete_bucket_req).sync() {
                    Ok(_) => info!("Deleted S3 bucket: {}", bucket),
                    Err(e) => error!("Failed to delete S3 bucket: {}", e),
                };
            });
        });
    }
}

// TODO: once Rust has proper support for testing frameworks, this code will
// need to be refactored so that it is only called once, instead of per test
// case.
fn initialize() {
    let _ = env_logger::try_init();
}

fn create_client() -> TestEtsClient {
    TestEtsClient::new(AWS_REGION)
}

/// Generates a random name for an AWS service by appending a random sequence of
/// ASCII characters to the specified prefix.
/// Keeps it lower case to work with S3 requirements as of 3/1/2018.
fn generate_unique_name(prefix: &str) -> String {
    format!(
        "{}-{}",
        prefix,
        rand::thread_rng()
            .gen_ascii_chars()
            .take(AWS_SERVICE_RANDOM_SUFFIX_LENGTH)
            .collect::<String>()
    )
    .to_lowercase()
}

#[test]
#[should_panic(expected = "Role cannot be blank")]
fn create_pipeline_without_arn() {
    use rusoto_elastictranscoder::CreatePipelineRequest;

    initialize();

    let mut client = create_client();
    client.create_s3_client();
    client.input_bucket = Some(client.create_bucket());
    client.output_bucket = Some(client.create_bucket());

    let request = CreatePipelineRequest {
        input_bucket: client.input_bucket.as_ref().cloned().unwrap(),
        output_bucket: client.output_bucket.as_ref().cloned(),
        ..CreatePipelineRequest::default()
    };
    let response = client.create_pipeline(request).sync();

    response.unwrap();
}

#[test]
fn create_preset() {
    use rusoto_elastictranscoder::{
        AudioCodecOptions, AudioParameters, CreatePresetRequest, DeletePresetRequest,
    };

    initialize();

    let client = create_client();

    let name = generate_unique_name("ets-preset-1");
    let request = CreatePresetRequest {
        audio: Some(AudioParameters {
            channels: Some("2".to_owned()),
            codec: Some("flac".to_owned()),
            codec_options: Some(AudioCodecOptions {
                bit_depth: Some("24".to_owned()),
                ..AudioCodecOptions::default()
            }),
            sample_rate: Some("96000".to_owned()),
            ..AudioParameters::default()
        }),
        container: "flac".to_owned(),
        description: Some("This is an example FLAC preset".to_owned()),
        name: name.clone(),
        ..CreatePresetRequest::default()
    };
    let response = client.create_preset(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    assert!(response.preset.is_some());

    let preset = response.preset.unwrap();

    assert_eq!(preset.container, Some("flac".to_owned()));
    assert_eq!(
        preset.description,
        Some("This is an example FLAC preset".to_owned())
    );
    assert_eq!(preset.name, Some(name));
    assert!(preset.id.is_some());

    let id = preset.id.unwrap();

    info!("Created preset with id: {:?}", &id);

    // Cleanup

    let request = DeletePresetRequest { id: id };
    client.delete_preset(request).sync().ok();
}

#[test]
fn delete_preset() {
    use rusoto_elastictranscoder::{
        AudioCodecOptions, AudioParameters, CreatePresetRequest, DeletePresetRequest,
    };

    initialize();

    let client = create_client();

    let name = generate_unique_name("ets-preset-1");
    let request = CreatePresetRequest {
        audio: Some(AudioParameters {
            channels: Some("2".to_owned()),
            codec: Some("flac".to_owned()),
            codec_options: Some(AudioCodecOptions {
                bit_depth: Some("24".to_owned()),
                ..AudioCodecOptions::default()
            }),
            sample_rate: Some("96000".to_owned()),
            ..AudioParameters::default()
        }),
        container: "flac".to_owned(),
        description: Some("This is an example FLAC preset".to_owned()),
        name: name.clone(),
        ..CreatePresetRequest::default()
    };
    let response = client.create_preset(request).sync().unwrap();
    let preset = response.preset.unwrap();
    let id = preset.id.unwrap();

    let request = DeletePresetRequest { id: id.clone() };
    let response = client.delete_preset(request).sync();

    assert!(response.is_ok());
    info!("Deleted preset with id: {:?}", &id);
}

#[test]
fn list_jobs_by_status() {
    use rusoto_elastictranscoder::ListJobsByStatusRequest;

    initialize();

    let client = create_client();

    let status = "Submitted".to_owned();
    let request = ListJobsByStatusRequest {
        status: status.clone(),
        ..ListJobsByStatusRequest::default()
    };
    let response = client.list_jobs_by_status(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    info!(
        "Got list of jobs with status \"{}\": {:?}",
        &status, response.jobs
    );
}

#[test]
fn list_pipelines() {
    use rusoto_elastictranscoder::ListPipelinesRequest;

    initialize();

    let client = create_client();

    let request = ListPipelinesRequest::default();
    let response = client.list_pipelines(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    info!("Got list of pipelines: {:?}", response.pipelines);
}

#[test]
fn list_presets() {
    use rusoto_elastictranscoder::ListPresetsRequest;

    initialize();

    let client = create_client();

    let request = ListPresetsRequest::default();
    let response = client.list_presets(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    assert!(response.presets.is_some());

    let presets = response.presets.unwrap();
    info!("Got list of presets.");
    for preset in presets.iter() {
        info!("Preset: {:?}", preset.name);
    }

    let web_preset = presets
        .iter()
        .filter(|x| x.id == Some(AWS_ETS_WEB_PRESET_ID.to_owned()))
        .next();

    assert!(web_preset.is_some());

    let web_preset = web_preset.unwrap();

    assert_eq!(web_preset.id, Some(AWS_ETS_WEB_PRESET_ID.to_owned()));
    assert_eq!(web_preset.name, Some(AWS_ETS_WEB_PRESET_NAME.to_owned()));
}

#[test]
fn read_preset() {
    use rusoto_elastictranscoder::ReadPresetRequest;

    initialize();

    let client = create_client();

    let request = ReadPresetRequest {
        id: AWS_ETS_WEB_PRESET_ID.to_owned(),
    };
    let response = client.read_preset(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    assert!(response.preset.is_some());

    let preset = response.preset.unwrap();
    info!("Got preset: {:?}", preset.name);

    assert_eq!(preset.id, Some(AWS_ETS_WEB_PRESET_ID.to_owned()));
    assert_eq!(preset.name, Some(AWS_ETS_WEB_PRESET_NAME.to_owned()));
}
<commit_msg>Fix up elastictranscoder test.<commit_after>#![cfg(all(feature = "elastictranscoder", feature = "s3"))]

extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rand;
extern crate rusoto_core;
extern crate rusoto_elastictranscoder;
extern crate rusoto_s3;

use std::clone::Clone;
use std::ops::{Deref, DerefMut};

use rand::Rng;
use rusoto_core::Region;
use rusoto_elastictranscoder::{Ets, EtsClient};
use rusoto_s3::{CreateBucketRequest, DeleteBucketRequest, S3Client, S3};

const AWS_ETS_WEB_PRESET_ID: &'static str = "1351620000001-100070";
const AWS_ETS_WEB_PRESET_NAME: &'static str = "System preset: Web";
const AWS_REGION: Region = Region::UsEast1;
const AWS_SERVICE_RANDOM_SUFFIX_LENGTH: usize = 20;

struct TestEtsClient {
    region: Region,

    client: EtsClient,

    s3_client: Option<S3Client>,
    input_bucket: Option<String>,
    output_bucket: Option<String>,
}

impl TestEtsClient {
    /// Creates a new `EtsClient` for a test.
    fn new(region: Region) -> TestEtsClient {
        TestEtsClient {
            region: region.clone(),
            client: EtsClient::new(region),
            s3_client: None,
            input_bucket: None,
            output_bucket: None,
        }
    }

    fn create_s3_client(&mut self) {
        self.s3_client = Some(S3Client::new(self.region.clone()));
    }

    fn create_bucket(&mut self) -> String {
        let bucket_name = generate_unique_name("ets-bucket-1");

        let create_bucket_req = CreateBucketRequest {
            bucket: bucket_name.to_owned(),
            ..Default::default()
        };

        let result = self
            .s3_client
            .as_ref()
            .unwrap()
            .create_bucket(create_bucket_req)
            .sync();

        let mut location = result.unwrap().location.unwrap();
        // A `Location` is identical to a `BucketName` except that it has a
        // forward slash prepended to it, so we need to remove it.
        location.remove(0);
        info!("Created S3 bucket: {}", location);
        location
    }
}

impl Deref for TestEtsClient {
    type Target = EtsClient;
    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

impl DerefMut for TestEtsClient {
    fn deref_mut<'a>(&'a mut self) -> &'a mut EtsClient {
        &mut self.client
    }
}

impl Drop for TestEtsClient {
    fn drop(&mut self) {
        self.s3_client.take().map(|s3_client| {
            self.input_bucket.take().map(|bucket| {
                let delete_bucket_req = DeleteBucketRequest {
                    bucket: bucket.to_owned(),
                    ..Default::default()
                };

                match s3_client.delete_bucket(delete_bucket_req).sync() {
                    Ok(_) => info!("Deleted S3 bucket: {}", bucket),
                    Err(e) => error!("Failed to delete S3 bucket: {}", e),
                };
            });
            self.output_bucket.take().map(|bucket| {
                let delete_bucket_req = DeleteBucketRequest {
                    bucket: bucket.to_owned(),
                    ..Default::default()
                };

                match s3_client.delete_bucket(delete_bucket_req).sync() {
                    Ok(_) => info!("Deleted S3 bucket: {}", bucket),
                    Err(e) => error!("Failed to delete S3 bucket: {}", e),
                };
            });
        });
    }
}

// TODO: once Rust has proper support for testing frameworks, this code will
// need to be refactored so that it is only called once, instead of per test
// case.
fn initialize() {
    let _ = env_logger::try_init();
}

fn create_client() -> TestEtsClient {
    TestEtsClient::new(AWS_REGION)
}

/// Generates a random name for an AWS service by appending a random sequence of
/// ASCII characters to the specified prefix.
/// Keeps it lower case to work with S3 requirements as of 3/1/2018.
fn generate_unique_name(prefix: &str) -> String {
    format!(
        "{}-{}",
        prefix,
        rand::thread_rng()
            .gen_ascii_chars()
            .take(AWS_SERVICE_RANDOM_SUFFIX_LENGTH)
            .collect::<String>()
    )
    .to_lowercase()
}

#[test]
#[should_panic(expected = "Role cannot be blank")]
fn create_pipeline_without_arn() {
    use rusoto_elastictranscoder::CreatePipelineRequest;

    initialize();

    let mut client = create_client();
    client.create_s3_client();
    client.input_bucket = Some(client.create_bucket());
    client.output_bucket = Some(client.create_bucket());

    let request = CreatePipelineRequest {
        input_bucket: client.input_bucket.as_ref().cloned().unwrap(),
        output_bucket: client.output_bucket.as_ref().cloned(),
        ..CreatePipelineRequest::default()
    };
    let response = client.create_pipeline(request).sync();

    response.unwrap();
}

#[test]
fn create_preset() {
    use rusoto_elastictranscoder::{
        AudioCodecOptions, AudioParameters, CreatePresetRequest, DeletePresetRequest,
    };

    initialize();

    let client = create_client();

    let name = generate_unique_name("ets-preset-1");
    let request = CreatePresetRequest {
        audio: Some(AudioParameters {
            channels: Some("2".to_owned()),
            codec: Some("flac".to_owned()),
            codec_options: Some(AudioCodecOptions {
                bit_depth: Some("24".to_owned()),
                ..AudioCodecOptions::default()
            }),
            sample_rate: Some("96000".to_owned()),
            ..AudioParameters::default()
        }),
        container: "flac".to_owned(),
        description: Some("This is an example FLAC preset".to_owned()),
        name: name.clone(),
        ..CreatePresetRequest::default()
    };
    let response = client.create_preset(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    assert!(response.preset.is_some());

    let preset = response.preset.unwrap();

    assert_eq!(preset.container, Some("flac".to_owned()));
    assert_eq!(
        preset.description,
        Some("This is an example FLAC preset".to_owned())
    );
    assert_eq!(preset.name, Some(name));
    assert!(preset.id.is_some());

    let id = preset.id.unwrap();

    info!("Created preset with id: {:?}", &id);

    // Cleanup

    let request = DeletePresetRequest { id: id };
    client.delete_preset(request).sync().ok();
}

#[test]
fn delete_preset() {
    use rusoto_elastictranscoder::{
        AudioCodecOptions, AudioParameters, CreatePresetRequest, DeletePresetRequest,
    };

    initialize();

    let client = create_client();

    let name = generate_unique_name("ets-preset-1");
    let request = CreatePresetRequest {
        audio: Some(AudioParameters {
            channels: Some("2".to_owned()),
            codec: Some("flac".to_owned()),
            codec_options: Some(AudioCodecOptions {
                bit_depth: Some("24".to_owned()),
                ..AudioCodecOptions::default()
            }),
            sample_rate: Some("96000".to_owned()),
            ..AudioParameters::default()
        }),
        container: "flac".to_owned(),
        description: Some("This is an example FLAC preset".to_owned()),
        name: name.clone(),
        ..CreatePresetRequest::default()
    };
    let response = client.create_preset(request).sync().unwrap();
    let preset = response.preset.unwrap();
    let id = preset.id.unwrap();

    let request = DeletePresetRequest { id: id.clone() };
    let response = client.delete_preset(request).sync();

    assert!(response.is_ok());
    info!("Deleted preset with id: {:?}", &id);
}

#[test]
fn list_jobs_by_status() {
    use rusoto_elastictranscoder::ListJobsByStatusRequest;

    initialize();

    let client = create_client();

    let status = "Submitted".to_owned();
    let request = ListJobsByStatusRequest {
        status: status.clone(),
        ..ListJobsByStatusRequest::default()
    };
    let response = client.list_jobs_by_status(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    info!(
        "Got list of jobs with status \"{}\": {:?}",
        &status, response.jobs
    );
}

#[test]
fn list_pipelines() {
    use rusoto_elastictranscoder::ListPipelinesRequest;

    initialize();

    let client = create_client();

    let request = ListPipelinesRequest::default();
    let response = client.list_pipelines(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    info!("Got list of pipelines: {:?}", response.pipelines);
}

#[test]
fn list_presets() {
    use rusoto_elastictranscoder::ListPresetsRequest;

    initialize();
    let client = create_client();

    let request = ListPresetsRequest::default();
    let response = client.list_presets(request).sync();
    assert!(response.is_ok());

    let response = response.unwrap();
    assert!(response.presets.is_some());

    let presets = response.presets.unwrap();
    info!("Got list of presets.");
    for preset in presets.iter() {
        info!("Preset: {:?}", preset.name);
    }

    let web_preset = presets
        .iter()
        .filter(|x| x.id == Some(AWS_ETS_WEB_PRESET_ID.to_owned()))
        .next();

    let found_preset = match web_preset {
        Some(w) => w.clone(),
        None => {
            // get the next page
            assert!(response.next_page_token.is_some());
            let page_two_request = ListPresetsRequest {
                page_token: response.next_page_token,
                ..Default::default()
            };

            let page_two_response = client.list_presets(page_two_request).sync().unwrap();
            let presets_pg_2 = page_two_response.presets.unwrap();
            let web_preset = presets_pg_2
                .iter()
                .filter(|x| x.id == Some(AWS_ETS_WEB_PRESET_ID.to_owned()))
                .next();
            web_preset.unwrap().clone()
        },
    };

    assert_eq!(found_preset.id, Some(AWS_ETS_WEB_PRESET_ID.to_owned()));
    assert_eq!(found_preset.name, Some(AWS_ETS_WEB_PRESET_NAME.to_owned()));
}

#[test]
fn read_preset() {
    use rusoto_elastictranscoder::ReadPresetRequest;

    initialize();

    let client = create_client();

    let request = ReadPresetRequest {
        id: AWS_ETS_WEB_PRESET_ID.to_owned(),
    };
    let response = client.read_preset(request).sync();

    assert!(response.is_ok());

    let response = response.unwrap();

    assert!(response.preset.is_some());

    let preset = response.preset.unwrap();
    info!("Got preset: {:?}", preset.name);

    assert_eq!(preset.id, Some(AWS_ETS_WEB_PRESET_ID.to_owned()));
    assert_eq!(preset.name, Some(AWS_ETS_WEB_PRESET_NAME.to_owned()));
}
<|endoftext|>


<commit_before>use super::error::{Error, Result};
use libloading::{Library, Symbol};
use once_cell::unsync::OnceCell;
use regex::{Regex, RegexBuilder};
use serde_derive::Deserialize;
use std::collections::HashMap;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::SystemTime;
use std::{fs, mem};
use tree_sitter::{Language, PropertySheet};
use tree_sitter_highlight::{load_property_sheet, Properties};

#[cfg(unix)]
const DYLIB_EXTENSION: &'static str = "so";

#[cfg(windows)]
const DYLIB_EXTENSION: &'static str = "dll";

const BUILD_TARGET: &'static str = env!("BUILD_TARGET");

struct LanguageRepo {
    path: PathBuf,
    language: OnceCell<Language>,
    configurations: Vec<LanguageConfiguration>,
}

pub struct LanguageConfiguration {
    scope: Option<String>,
    _content_regex: Option<Regex>,
    _first_line_regex: Option<Regex>,
    injection_regex: Option<Regex>,
    file_types: Vec<String>,
    highlight_property_sheet_path: Option<PathBuf>,
    highlight_property_sheet: OnceCell<Option<PropertySheet<Properties>>>,
}

pub struct Loader {
    parser_lib_path: PathBuf,
    language_repos: Vec<LanguageRepo>,
    language_configuration_ids_by_file_type: HashMap<String, Vec<(usize, usize)>>,
}

unsafe impl Send for Loader {}
unsafe impl Sync for Loader {}

impl Loader {
    pub fn new(parser_lib_path: PathBuf) -> Self {
        Loader {
            parser_lib_path,
            language_repos: Vec::new(),
            language_configuration_ids_by_file_type: HashMap::new(),
        }
    }

    pub fn find_all_languages(&mut self, parser_src_paths: &Vec<PathBuf>) -> Result<()> {
        for parser_container_dir in parser_src_paths.iter() {
            if let Ok(entries) = fs::read_dir(parser_container_dir) {
                for entry in entries {
                    let entry = entry?;
                    if let Some(parser_dir_name) = entry.file_name().to_str() {
                        if parser_dir_name.starts_with("tree-sitter-") {
                            self.find_language_at_path(&parser_container_dir.join(parser_dir_name))
                                .ok();
                        }
                    }
                }
            }
        }
        Ok(())
    }

    pub fn language_at_path(&mut self, path: &Path) -> Result<Option<Language>> {
        if let Ok(id) = self.find_language_at_path(path) {
            Ok(Some(self.language_configuration_for_id(id)?.0))
        } else {
            Ok(None)
        }
    }

    pub fn language_configuration_for_scope(
        &self,
        scope: &str,
    ) -> Result<Option<(Language, &LanguageConfiguration)>> {
        for (i, repo) in self.language_repos.iter().enumerate() {
            for configuration in &repo.configurations {
                if configuration.scope.as_ref().map_or(false, |s| s == scope) {
                    let (language, _) = self.language_configuration_for_id(i)?;
                    return Ok(Some((language, &configuration)));
                }
            }
        }
        Ok(None)
    }

    pub fn language_configuration_for_file_name(
        &self,
        path: &Path,
    ) -> Result<Option<(Language, &LanguageConfiguration)>> {
        let ids = path
            .file_name()
            .and_then(|n| n.to_str())
            .and_then(|file_name| self.language_configuration_ids_by_file_type.get(file_name))
            .or_else(|| {
                path.extension()
                    .and_then(|extension| extension.to_str())
                    .and_then(|extension| {
                        self.language_configuration_ids_by_file_type.get(extension)
                    })
            });
        if let Some(ids) = ids {
            // TODO use `content-regex` to pick one
            for (repo_id, configuration_id) in ids.iter().cloned() {
                let (language, configurations) = self.language_configuration_for_id(repo_id)?;
                return Ok(Some((language, &configurations[configuration_id])));
            }
        }
        Ok(None)
    }

    pub fn language_configuration_for_injection_string(
        &self,
        string: &str,
    ) -> Result<Option<(Language, &LanguageConfiguration)>> {
        let mut best_match_length = 0;
        let mut best_match_position = None;
        for (i, repo) in self.language_repos.iter().enumerate() {
            for (j, configuration) in repo.configurations.iter().enumerate() {
                if let Some(injection_regex) = &configuration.injection_regex {
                    if let Some(mat) = injection_regex.find(string) {
                        let length = mat.end() - mat.start();
                        if length > best_match_length {
                            best_match_position = Some((i, j));
                            best_match_length = length;
                        }
                    }
                }
            }
        }
        if let Some((i, j)) = best_match_position {
            let (language, configurations) = self.language_configuration_for_id(i)?;
            Ok(Some((language, &configurations[j])))
        } else {
            Ok(None)
        }
    }

    fn language_configuration_for_id(
        &self,
        id: usize,
    ) -> Result<(Language, &Vec<LanguageConfiguration>)> {
        let repo = &self.language_repos[id];
        let language = repo.language.get_or_try_init(|| {
            let src_path = repo.path.join("src");
            self.load_language_at_path(&src_path, &src_path)
        })?;
        Ok((*language, &self.language_repos[id].configurations))
    }

    pub fn load_language_at_path(&self, src_path: &Path, header_path: &Path) -> Result<Language> {
        let grammar_path = src_path.join("grammar.json");
        let parser_path = src_path.join("parser.c");
        let mut scanner_path = src_path.join("scanner.c");

        #[derive(Deserialize)]
        struct GrammarJSON {
            name: String,
        }
        let mut grammar_file = fs::File::open(grammar_path)?;
        let grammar_json: GrammarJSON = serde_json::from_reader(BufReader::new(&mut grammar_file))?;

        let scanner_path = if scanner_path.exists() {
            Some(scanner_path)
        } else {
            scanner_path.set_extension("cc");
            if scanner_path.exists() {
                Some(scanner_path)
            } else {
                None
            }
        };

        self.load_language_from_sources(
            &grammar_json.name,
            &header_path,
            &parser_path,
            &scanner_path,
        )
    }

    pub fn load_language_from_sources(
        &self,
        name: &str,
        header_path: &Path,
        parser_path: &Path,
        scanner_path: &Option<PathBuf>,
    ) -> Result<Language> {
        let mut library_path = self.parser_lib_path.join(name);
        library_path.set_extension(DYLIB_EXTENSION);

        if needs_recompile(&library_path, &parser_path, &scanner_path)? {
            let mut config = cc::Build::new();
            config
                .cpp(true)
                .opt_level(2)
                .cargo_metadata(false)
                .target(BUILD_TARGET)
                .host(BUILD_TARGET);
            let compiler = config.get_compiler();
            let mut command = Command::new(compiler.path());
            for (key, value) in compiler.env() {
                command.env(key, value);
            }

            if cfg!(windows) {
                command
                    .args(&["/nologo", "/LD", "/I"])
                    .arg(header_path)
                    .arg("/Od")
                    .arg(parser_path);
                if let Some(scanner_path) = scanner_path.as_ref() {
                    command.arg(scanner_path);
                }
                command
                    .arg("/link")
                    .arg(format!("/out:{}", library_path.to_str().unwrap()));
            } else {
                command
                    .arg("-shared")
                    .arg("-fPIC")
                    .arg("-fno-exceptions")
                    .arg("-g")
                    .arg("-I")
                    .arg(header_path)
                    .arg("-o")
                    .arg(&library_path)
                    .arg("-O2");
                if let Some(scanner_path) = scanner_path.as_ref() {
                    if scanner_path.extension() == Some("c".as_ref()) {
                        command.arg("-xc").arg("-std=c99").arg(scanner_path);
                    } else {
                        command.arg(scanner_path);
                    }
                }
                command.arg("-xc").arg(parser_path);
            }

            let output = command.output()?;
            if !output.status.success() {
                return Err(Error(format!(
                    "Parser compilation failed.\nStdout: {}\nStderr: {}",
                    String::from_utf8_lossy(&output.stdout),
                    String::from_utf8_lossy(&output.stderr)
                )));
            }
        }

        let library = Library::new(&library_path).map_err(|e| {
            Error(format!(
                "Error opening dynamic library {:?}: {}",
                &library_path, e
            ))
        })?;
        let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(name));
        let language = unsafe {
            let language_fn: Symbol<unsafe extern "C" fn() -> Language> =
                library.get(language_fn_name.as_bytes())?;
            language_fn()
        };
        mem::forget(library);
        Ok(language)
    }

    fn find_language_at_path<'a>(&'a mut self, parser_path: &Path) -> Result<usize> {
        #[derive(Deserialize)]
        struct LanguageConfigurationJSON {
            scope: Option<String>,
            #[serde(rename = "file-types")]
            file_types: Option<Vec<String>>,
            #[serde(rename = "content-regex")]
            content_regex: Option<String>,
            #[serde(rename = "first-line-regex")]
            first_line_regex: Option<String>,
            #[serde(rename = "injection-regex")]
            injection_regex: Option<String>,
            highlights: Option<String>,
        }

        #[derive(Deserialize)]
        struct PackageJSON {
            #[serde(rename = "tree-sitter")]
            tree_sitter: Option<Vec<LanguageConfigurationJSON>>,
        }

        let package_json_contents = fs::read_to_string(&parser_path.join("package.json"))?;
        let package_json: PackageJSON = serde_json::from_str(&package_json_contents)?;
        let configurations = package_json
            .tree_sitter
            .map_or(Vec::new(), |configurations| {
                configurations
                    .into_iter()
                    .map(|conf| LanguageConfiguration {
                        scope: conf.scope,
                        file_types: conf.file_types.unwrap_or(Vec::new()),
                        _content_regex: conf
                            .content_regex
                            .and_then(|r| RegexBuilder::new(&r).multi_line(true).build().ok()),
                        _first_line_regex: conf
                            .first_line_regex
                            .and_then(|r| RegexBuilder::new(&r).multi_line(true).build().ok()),
                        injection_regex: conf
                            .injection_regex
                            .and_then(|r| RegexBuilder::new(&r).multi_line(true).build().ok()),
                        highlight_property_sheet_path: conf.highlights.map(|h| parser_path.join(h)),
                        highlight_property_sheet: OnceCell::new(),
                    })
                    .collect()
            });

        for (i, configuration) in configurations.iter().enumerate() {
            for file_type in &configuration.file_types {
                self.language_configuration_ids_by_file_type
                    .entry(file_type.to_string())
                    .or_insert(Vec::new())
                    .push((self.language_repos.len(), i));
            }
        }

        self.language_repos.push(LanguageRepo {
            path: parser_path.to_owned(),
            language: OnceCell::new(),
            configurations,
        });

        Ok(self.language_repos.len() - 1)
    }
}

impl LanguageConfiguration {
    pub fn highlight_property_sheet(
        &self,
        language: Language,
    ) -> Result<Option<&PropertySheet<Properties>>> {
        self.highlight_property_sheet
            .get_or_try_init(|| {
                if let Some(path) = &self.highlight_property_sheet_path {
                    let sheet_json = fs::read_to_string(path)?;
                    let sheet = load_property_sheet(language, &sheet_json)?;
                    Ok(Some(sheet))
                } else {
                    Ok(None)
                }
            })
            .map(Option::as_ref)
    }
}

fn needs_recompile(
    lib_path: &Path,
    parser_c_path: &Path,
    scanner_path: &Option<PathBuf>,
) -> Result<bool> {
    if !lib_path.exists() {
        return Ok(true);
    }
    let lib_mtime = mtime(lib_path)?;
    if mtime(parser_c_path)? > lib_mtime {
        return Ok(true);
    }
    if let Some(scanner_path) = scanner_path {
        if mtime(scanner_path)? > lib_mtime {
            return Ok(true);
        }
    }
    Ok(false)
}

fn mtime(path: &Path) -> Result<SystemTime> {
    Ok(fs::metadata(path)?.modified()?)
}

fn replace_dashes_with_underscores(name: &str) -> String {
    let mut result = String::with_capacity(name.len());
    for c in name.chars() {
        if c == '-' {
            result.push('_');
        } else {
            result.push(c);
        }
    }
    result
}
<commit_msg>Allow dynamically loading grammars w/ no package.json file<commit_after>use super::error::{Error, Result};
use libloading::{Library, Symbol};
use once_cell::unsync::OnceCell;
use regex::{Regex, RegexBuilder};
use serde_derive::Deserialize;
use std::collections::HashMap;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::SystemTime;
use std::{fs, mem};
use tree_sitter::{Language, PropertySheet};
use tree_sitter_highlight::{load_property_sheet, Properties};

#[cfg(unix)]
const DYLIB_EXTENSION: &'static str = "so";

#[cfg(windows)]
const DYLIB_EXTENSION: &'static str = "dll";

const BUILD_TARGET: &'static str = env!("BUILD_TARGET");

struct LanguageRepo {
    path: PathBuf,
    language: OnceCell<Language>,
    configurations: Vec<LanguageConfiguration>,
}

#[derive(Default)]
pub struct LanguageConfiguration {
    scope: Option<String>,
    _content_regex: Option<Regex>,
    _first_line_regex: Option<Regex>,
    injection_regex: Option<Regex>,
    file_types: Vec<String>,
    highlight_property_sheet_path: Option<PathBuf>,
    highlight_property_sheet: OnceCell<Option<PropertySheet<Properties>>>,
}

pub struct Loader {
    parser_lib_path: PathBuf,
    language_repos: Vec<LanguageRepo>,
    language_configuration_ids_by_file_type: HashMap<String, Vec<(usize, usize)>>,
}

unsafe impl Send for Loader {}
unsafe impl Sync for Loader {}

impl Loader {
    pub fn new(parser_lib_path: PathBuf) -> Self {
        Loader {
            parser_lib_path,
            language_repos: Vec::new(),
            language_configuration_ids_by_file_type: HashMap::new(),
        }
    }

    pub fn find_all_languages(&mut self, parser_src_paths: &Vec<PathBuf>) -> Result<()> {
        for parser_container_dir in parser_src_paths.iter() {
            if let Ok(entries) = fs::read_dir(parser_container_dir) {
                for entry in entries {
                    let entry = entry?;
                    if let Some(parser_dir_name) = entry.file_name().to_str() {
                        if parser_dir_name.starts_with("tree-sitter-") {
                            self.find_language_at_path(&parser_container_dir.join(parser_dir_name))
                                .ok();
                        }
                    }
                }
            }
        }
        Ok(())
    }

    pub fn language_at_path(&mut self, path: &Path) -> Result<Option<Language>> {
        if let Ok(id) = self.find_language_at_path(path) {
            Ok(Some(self.language_for_id(id)?.0))
        } else {
            Ok(None)
        }
    }

    pub fn language_configuration_for_scope(
        &self,
        scope: &str,
    ) -> Result<Option<(Language, &LanguageConfiguration)>> {
        for (i, repo) in self.language_repos.iter().enumerate() {
            for configuration in &repo.configurations {
                if configuration.scope.as_ref().map_or(false, |s| s == scope) {
                    let (language, _) = self.language_for_id(i)?;
                    return Ok(Some((language, &configuration)));
                }
            }
        }
        Ok(None)
    }

    pub fn language_configuration_for_file_name(
        &self,
        path: &Path,
    ) -> Result<Option<(Language, &LanguageConfiguration)>> {
        let ids = path
            .file_name()
            .and_then(|n| n.to_str())
            .and_then(|file_name| self.language_configuration_ids_by_file_type.get(file_name))
            .or_else(|| {
                path.extension()
                    .and_then(|extension| extension.to_str())
                    .and_then(|extension| {
                        self.language_configuration_ids_by_file_type.get(extension)
                    })
            });
        if let Some(ids) = ids {
            // TODO use `content-regex` to pick one
            for (repo_id, configuration_id) in ids.iter().cloned() {
                let (language, configurations) = self.language_for_id(repo_id)?;
                return Ok(Some((language, &configurations[configuration_id])));
            }
        }
        Ok(None)
    }

    pub fn language_configuration_for_injection_string(
        &self,
        string: &str,
    ) -> Result<Option<(Language, &LanguageConfiguration)>> {
        let mut best_match_length = 0;
        let mut best_match_position = None;
        for (i, repo) in self.language_repos.iter().enumerate() {
            for (j, configuration) in repo.configurations.iter().enumerate() {
                if let Some(injection_regex) = &configuration.injection_regex {
                    if let Some(mat) = injection_regex.find(string) {
                        let length = mat.end() - mat.start();
                        if length > best_match_length {
                            best_match_position = Some((i, j));
                            best_match_length = length;
                        }
                    }
                }
            }
        }
        if let Some((i, j)) = best_match_position {
            let (language, configurations) = self.language_for_id(i)?;
            Ok(Some((language, &configurations[j])))
        } else {
            Ok(None)
        }
    }

    fn language_for_id(&self, id: usize) -> Result<(Language, &Vec<LanguageConfiguration>)> {
        let repo = &self.language_repos[id];
        let language = repo.language.get_or_try_init(|| {
            let src_path = repo.path.join("src");
            self.load_language_at_path(&src_path, &src_path)
        })?;
        Ok((*language, &self.language_repos[id].configurations))
    }

    pub fn load_language_at_path(&self, src_path: &Path, header_path: &Path) -> Result<Language> {
        let grammar_path = src_path.join("grammar.json");
        let parser_path = src_path.join("parser.c");
        let mut scanner_path = src_path.join("scanner.c");

        #[derive(Deserialize)]
        struct GrammarJSON {
            name: String,
        }
        let mut grammar_file = fs::File::open(grammar_path)?;
        let grammar_json: GrammarJSON = serde_json::from_reader(BufReader::new(&mut grammar_file))?;

        let scanner_path = if scanner_path.exists() {
            Some(scanner_path)
        } else {
            scanner_path.set_extension("cc");
            if scanner_path.exists() {
                Some(scanner_path)
            } else {
                None
            }
        };

        self.load_language_from_sources(
            &grammar_json.name,
            &header_path,
            &parser_path,
            &scanner_path,
        )
    }

    pub fn load_language_from_sources(
        &self,
        name: &str,
        header_path: &Path,
        parser_path: &Path,
        scanner_path: &Option<PathBuf>,
    ) -> Result<Language> {
        let mut library_path = self.parser_lib_path.join(name);
        library_path.set_extension(DYLIB_EXTENSION);

        if needs_recompile(&library_path, &parser_path, &scanner_path)? {
            let mut config = cc::Build::new();
            config
                .cpp(true)
                .opt_level(2)
                .cargo_metadata(false)
                .target(BUILD_TARGET)
                .host(BUILD_TARGET);
            let compiler = config.get_compiler();
            let mut command = Command::new(compiler.path());
            for (key, value) in compiler.env() {
                command.env(key, value);
            }

            if cfg!(windows) {
                command
                    .args(&["/nologo", "/LD", "/I"])
                    .arg(header_path)
                    .arg("/Od")
                    .arg(parser_path);
                if let Some(scanner_path) = scanner_path.as_ref() {
                    command.arg(scanner_path);
                }
                command
                    .arg("/link")
                    .arg(format!("/out:{}", library_path.to_str().unwrap()));
            } else {
                command
                    .arg("-shared")
                    .arg("-fPIC")
                    .arg("-fno-exceptions")
                    .arg("-g")
                    .arg("-I")
                    .arg(header_path)
                    .arg("-o")
                    .arg(&library_path)
                    .arg("-O2");
                if let Some(scanner_path) = scanner_path.as_ref() {
                    if scanner_path.extension() == Some("c".as_ref()) {
                        command.arg("-xc").arg("-std=c99").arg(scanner_path);
                    } else {
                        command.arg(scanner_path);
                    }
                }
                command.arg("-xc").arg(parser_path);
            }

            let output = command.output()?;
            if !output.status.success() {
                return Err(Error(format!(
                    "Parser compilation failed.\nStdout: {}\nStderr: {}",
                    String::from_utf8_lossy(&output.stdout),
                    String::from_utf8_lossy(&output.stderr)
                )));
            }
        }

        let library = Library::new(&library_path).map_err(|e| {
            Error(format!(
                "Error opening dynamic library {:?}: {}",
                &library_path, e
            ))
        })?;
        let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(name));
        let language = unsafe {
            let language_fn: Symbol<unsafe extern "C" fn() -> Language> =
                library.get(language_fn_name.as_bytes())?;
            language_fn()
        };
        mem::forget(library);
        Ok(language)
    }

    fn find_language_at_path<'a>(&'a mut self, parser_path: &Path) -> Result<usize> {
        #[derive(Deserialize)]
        struct LanguageConfigurationJSON {
            scope: Option<String>,
            #[serde(rename = "file-types")]
            file_types: Option<Vec<String>>,
            #[serde(rename = "content-regex")]
            content_regex: Option<String>,
            #[serde(rename = "first-line-regex")]
            first_line_regex: Option<String>,
            #[serde(rename = "injection-regex")]
            injection_regex: Option<String>,
            highlights: Option<String>,
        }

        #[derive(Deserialize)]
        struct PackageJSON {
            #[serde(rename = "tree-sitter")]
            tree_sitter: Option<Vec<LanguageConfigurationJSON>>,
        }

        let mut configurations = vec![LanguageConfiguration::default()];
        if let Ok(package_json_contents) = fs::read_to_string(&parser_path.join("package.json")) {
            let package_json = serde_json::from_str::<PackageJSON>(&package_json_contents);
            if let Ok(package_json) = package_json {
                configurations = package_json
                    .tree_sitter
                    .map_or(Vec::new(), |configurations| {
                        configurations
                            .into_iter()
                            .map(|conf| LanguageConfiguration {
                                scope: conf.scope,
                                file_types: conf.file_types.unwrap_or(Vec::new()),
                                _content_regex: conf.content_regex.and_then(|r| {
                                    RegexBuilder::new(&r).multi_line(true).build().ok()
                                }),
                                _first_line_regex: conf.first_line_regex.and_then(|r| {
                                    RegexBuilder::new(&r).multi_line(true).build().ok()
                                }),
                                injection_regex: conf.injection_regex.and_then(|r| {
                                    RegexBuilder::new(&r).multi_line(true).build().ok()
                                }),
                                highlight_property_sheet_path: conf
                                    .highlights
                                    .map(|h| parser_path.join(h)),
                                highlight_property_sheet: OnceCell::new(),
                            })
                            .collect()
                    });

                for (i, configuration) in configurations.iter().enumerate() {
                    for file_type in &configuration.file_types {
                        self.language_configuration_ids_by_file_type
                            .entry(file_type.to_string())
                            .or_insert(Vec::new())
                            .push((self.language_repos.len(), i));
                    }
                }
            }
        }

        self.language_repos.push(LanguageRepo {
            path: parser_path.to_owned(),
            language: OnceCell::new(),
            configurations,
        });

        Ok(self.language_repos.len() - 1)
    }
}

impl LanguageConfiguration {
    pub fn highlight_property_sheet(
        &self,
        language: Language,
    ) -> Result<Option<&PropertySheet<Properties>>> {
        self.highlight_property_sheet
            .get_or_try_init(|| {
                if let Some(path) = &self.highlight_property_sheet_path {
                    let sheet_json = fs::read_to_string(path)?;
                    let sheet = load_property_sheet(language, &sheet_json)?;
                    Ok(Some(sheet))
                } else {
                    Ok(None)
                }
            })
            .map(Option::as_ref)
    }
}

fn needs_recompile(
    lib_path: &Path,
    parser_c_path: &Path,
    scanner_path: &Option<PathBuf>,
) -> Result<bool> {
    if !lib_path.exists() {
        return Ok(true);
    }
    let lib_mtime = mtime(lib_path)?;
    if mtime(parser_c_path)? > lib_mtime {
        return Ok(true);
    }
    if let Some(scanner_path) = scanner_path {
        if mtime(scanner_path)? > lib_mtime {
            return Ok(true);
        }
    }
    Ok(false)
}

fn mtime(path: &Path) -> Result<SystemTime> {
    Ok(fs::metadata(path)?.modified()?)
}

fn replace_dashes_with_underscores(name: &str) -> String {
    let mut result = String::with_capacity(name.len());
    for c in name.chars() {
        if c == '-' {
            result.push('_');
        } else {
            result.push(c);
        }
    }
    result
}
<|endoftext|>


<commit_before>use super::error::Error;
use super::wasm;
use std::fs;
use std::net::TcpListener;
use std::path::Path;
use std::str::FromStr;
use tiny_http::{Header, Response, Server};
use webbrowser;

const HTML: &'static str = include_str!("./web_ui.html");
const PLAYGROUND_JS: &'static [u8] = include_bytes!("../../docs/assets/js/playground.js");

#[cfg(unix)]
const LIB_JS: &'static [u8] = include_bytes!("../../lib/binding_web/tree-sitter.js");
#[cfg(unix)]
const LIB_WASM: &'static [u8] = include_bytes!("../../lib/binding_web/tree-sitter.wasm");

#[cfg(windows)]
const LIB_JS: &'static [u8] = &[];
#[cfg(windows)]
const LIB_WASM: &'static [u8] = &[];

pub fn serve(grammar_path: &Path) {
    let port = get_available_port().expect("Couldn't find an available port");
    let url = format!("127.0.0.1:{}", port);
    let server = Server::http(&url).expect("Failed to start web server");
    let grammar_name = wasm::get_grammar_name(&grammar_path.join("src"))
        .map_err(Error::wrap(|| "Failed to get wasm filename"))
        .unwrap();
    let wasm_filename = format!("tree-sitter-{}.wasm", grammar_name);
    let language_wasm = fs::read(grammar_path.join(&wasm_filename))
        .map_err(Error::wrap(|| {
            format!(
                "Failed to read {}. Run `tree-sitter build-wasm` first.",
                wasm_filename
            )
        }))
        .unwrap();

    webbrowser::open(&format!("http://127.0.0.1:{}", port))
        .map_err(Error::wrap(|| {
            format!("Failed to open '{}' in a web browser", url)
        }))
        .unwrap();

    let html = HTML
        .replace("THE_LANGUAGE_NAME", &grammar_name)
        .into_bytes();
    let html_header = Header::from_str("Content-type: text/html").unwrap();
    let js_header = Header::from_str("Content-type: application/javascript").unwrap();
    let wasm_header = Header::from_str("Content-type: application/wasm").unwrap();

    for request in server.incoming_requests() {
        let res = match request.url() {
            "/" => response(&html, &html_header),
            "/playground.js" => response(PLAYGROUND_JS, &js_header),
            "/tree-sitter-parser.wasm" => response(&language_wasm, &wasm_header),
            "/tree-sitter.js" => {
                if cfg!(windows) {
                    redirect("https://tree-sitter.github.io/tree-sitter.js")
                } else {
                    response(LIB_JS, &js_header)
                }
            }
            "/tree-sitter.wasm" => {
                if cfg!(windows) {
                    redirect("https://tree-sitter.github.io/tree-sitter.wasm")
                } else {
                    response(LIB_WASM, &js_header)
                }
            }
            _ => response(b"Not found", &html_header).with_status_code(404),
        };
        request.respond(res).expect("Failed to write HTTP response");
    }
}

fn redirect<'a>(url: &'a str) -> Response<&'a [u8]> {
    Response::empty(302)
        .with_data("".as_bytes(), Some(0))
        .with_header(Header::from_bytes("Location", url.as_bytes()).unwrap())
}

fn response<'a>(data: &'a [u8], header: &Header) -> Response<&'a [u8]> {
    Response::empty(200)
        .with_data(data, Some(data.len()))
        .with_header(header.clone())
}

fn get_available_port() -> Option<u16> {
    (8000..12000).find(port_is_available)
}

fn port_is_available(port: &u16) -> bool {
    TcpListener::bind(("127.0.0.1", *port)).is_ok()
}
<commit_msg>web-ui: Fix content-type of library wasm<commit_after>use super::error::Error;
use super::wasm;
use std::fs;
use std::net::TcpListener;
use std::path::Path;
use std::str::FromStr;
use tiny_http::{Header, Response, Server};
use webbrowser;

const HTML: &'static str = include_str!("./web_ui.html");
const PLAYGROUND_JS: &'static [u8] = include_bytes!("../../docs/assets/js/playground.js");

#[cfg(unix)]
const LIB_JS: &'static [u8] = include_bytes!("../../lib/binding_web/tree-sitter.js");
#[cfg(unix)]
const LIB_WASM: &'static [u8] = include_bytes!("../../lib/binding_web/tree-sitter.wasm");

#[cfg(windows)]
const LIB_JS: &'static [u8] = &[];
#[cfg(windows)]
const LIB_WASM: &'static [u8] = &[];

pub fn serve(grammar_path: &Path) {
    let port = get_available_port().expect("Couldn't find an available port");
    let url = format!("127.0.0.1:{}", port);
    let server = Server::http(&url).expect("Failed to start web server");
    let grammar_name = wasm::get_grammar_name(&grammar_path.join("src"))
        .map_err(Error::wrap(|| "Failed to get wasm filename"))
        .unwrap();
    let wasm_filename = format!("tree-sitter-{}.wasm", grammar_name);
    let language_wasm = fs::read(grammar_path.join(&wasm_filename))
        .map_err(Error::wrap(|| {
            format!(
                "Failed to read {}. Run `tree-sitter build-wasm` first.",
                wasm_filename
            )
        }))
        .unwrap();

    webbrowser::open(&format!("http://127.0.0.1:{}", port))
        .map_err(Error::wrap(|| {
            format!("Failed to open '{}' in a web browser", url)
        }))
        .unwrap();

    let html = HTML
        .replace("THE_LANGUAGE_NAME", &grammar_name)
        .into_bytes();
    let html_header = Header::from_str("Content-Type: text/html").unwrap();
    let js_header = Header::from_str("Content-Type: application/javascript").unwrap();
    let wasm_header = Header::from_str("Content-Type: application/wasm").unwrap();

    for request in server.incoming_requests() {
        let res = match request.url() {
            "/" => response(&html, &html_header),
            "/playground.js" => response(PLAYGROUND_JS, &js_header),
            "/tree-sitter-parser.wasm" => response(&language_wasm, &wasm_header),
            "/tree-sitter.js" => {
                if cfg!(windows) {
                    redirect("https://tree-sitter.github.io/tree-sitter.js")
                } else {
                    response(LIB_JS, &js_header)
                }
            }
            "/tree-sitter.wasm" => {
                if cfg!(windows) {
                    redirect("https://tree-sitter.github.io/tree-sitter.wasm")
                } else {
                    response(LIB_WASM, &wasm_header)
                }
            }
            _ => response(b"Not found", &html_header).with_status_code(404),
        };
        request.respond(res).expect("Failed to write HTTP response");
    }
}

fn redirect<'a>(url: &'a str) -> Response<&'a [u8]> {
    Response::empty(302)
        .with_data("".as_bytes(), Some(0))
        .with_header(Header::from_bytes("Location", url.as_bytes()).unwrap())
}

fn response<'a>(data: &'a [u8], header: &Header) -> Response<&'a [u8]> {
    Response::empty(200)
        .with_data(data, Some(data.len()))
        .with_header(header.clone())
}

fn get_available_port() -> Option<u16> {
    (8000..12000).find(port_is_available)
}

fn port_is_available(port: &u16) -> bool {
    TcpListener::bind(("127.0.0.1", *port)).is_ok()
}
<|endoftext|>


<commit_before>#![allow(dead_code)]

#[macro_use]
extern crate lazy_static;
extern crate regex;

use std::str::FromStr;
use std::io::Read;

#[derive(Debug)]
enum IngestionState {
    Take,
    Duplicate {
        occ: usize,
        rem: usize,
        buf: String
    }
}

impl IngestionState {
    fn from_marker(s: &str) -> IngestionState {
        let (occ, rep) = s.split_at(s.find('x').unwrap());
        Duplicate {
            rem: usize::from_str(&occ[1..]).unwrap(),
            occ: usize::from_str(&rep[1..(rep.len()-1)]).unwrap(),
            buf: String::new()
        }
    }
}

#[derive(Debug)]
struct Decompressor {
    decompressed: String,
    state: IngestionState,
}

#[derive(Debug)]
enum BlockType {
    Word,
    Marker
}
fn split(compressed: &str) -> Vec<(BlockType, &str)> {
    let mut blocks = Vec::new();
    lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new("(\\w+)?(\\(\\w+\\))?").unwrap();
    }
    for cap in RE.captures_iter(compressed) {
        if let Some(s) = cap.at(1) {
            blocks.push((BlockType::Word, s));
        }
        if let Some(s) = cap.at(2) {
            blocks.push((BlockType::Marker, s));
        }
    }
    blocks
}

use IngestionState::*;
use BlockType::*;

fn ingest_block(decomp:  &mut Decompressor, block: (BlockType, &str)) {
    let mut switch_to_take = false;
    match (&mut decomp.state, block.0) {
        (&mut Take, Word) =>
            decomp.decompressed += block.1,

        (s @ &mut Take, Marker) =>
            *s = IngestionState::from_marker(block.1),

        (&mut Duplicate {ref occ, ref mut rem, ref mut buf}, _) => {
            if block.1.len() >= *rem {
                let (dupe, simple) = block.1.split_at(*rem);
                *buf += dupe;
                for _ in 0..*occ {
                    decomp.decompressed += buf;
                }
                decomp.decompressed += simple;
                switch_to_take = true;
            } else {
                *buf += block.1;
                *rem -= block.1.len();
            }
        }
    }
    if switch_to_take {
        decomp.state = Take;
    }
}

fn decompress(s: &str) -> String {
    let mut decompressor = Decompressor {decompressed: String::new(), state: Take};
    for b in split(s) {
        ingest_block(&mut decompressor, b)
    }
    decompressor.decompressed
}

fn main() {
    let mut f = std::fs::File::open("input").expect("Unable to open input file");
    let mut input = String::new();
    f.read_to_string(&mut input).expect("Unable to read file");

    println!("{:?}", decompress(&input).len());
}
<commit_msg>Solution for day 9 part 2<commit_after>#[macro_use]
extern crate lazy_static;
extern crate regex;

use std::str::FromStr;
use std::io::Read;

#[derive(Debug)]
enum IngestionState {
    Take,
    Duplicate {
        occ: usize,
        rem: usize,
        buf: String
    }
}

fn parse_marker(s: &str) -> (usize, usize) {
    let (occ, rep) = s.split_at(s.find('x').unwrap());
    (usize::from_str(&occ[1..]).unwrap(),
    usize::from_str(&rep[1..(rep.len()-1)]).unwrap())
}

impl IngestionState {
    fn from_marker(s: &str) -> IngestionState {
        let (len, rep) = parse_marker(s);
        Duplicate {
            rem: len,
            occ: rep,
            buf: String::new()
        }
    }
}

#[derive(Debug)]
struct Decompressor {
    decompressed: String,
    state: IngestionState,
}

#[derive(Debug)]
enum BlockType {
    Word,
    Marker
}
fn split(compressed: &str) -> Vec<(BlockType, &str)> {
    let mut blocks = Vec::new();
    lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new("(\\w+)?(\\(\\w+\\))?").unwrap();
    }
    for cap in RE.captures_iter(compressed) {
        if let Some(s) = cap.at(1) {
            blocks.push((BlockType::Word, s));
        }
        if let Some(s) = cap.at(2) {
            blocks.push((BlockType::Marker, s));
        }
    }
    blocks
}

use IngestionState::*;
use BlockType::*;

fn ingest_block(decomp:  &mut Decompressor, block: (BlockType, &str)) {
    let mut switch_to_take = false;
    match (&mut decomp.state, block.0) {
        (&mut Take, Word) =>
            decomp.decompressed += block.1,

        (s @ &mut Take, Marker) =>
            *s = IngestionState::from_marker(block.1),

        (&mut Duplicate {ref occ, ref mut rem, ref mut buf}, _) => {
            if block.1.len() >= *rem {
                let (dupe, simple) = block.1.split_at(*rem);
                *buf += dupe;
                for _ in 0..*occ {
                    decomp.decompressed += buf;
                }
                decomp.decompressed += simple;
                switch_to_take = true;
            } else {
                *buf += block.1;
                *rem -= block.1.len();
            }
        }
    }
    if switch_to_take {
        decomp.state = Take;
    }
}

fn decompress(s: &str) -> String {
    let mut decompressor = Decompressor {decompressed: String::new(), state: Take};
    for b in split(s) {
        ingest_block(&mut decompressor, b)
    }
    decompressor.decompressed
}

fn decompressed_size(s: &str) -> usize {
    match s.find('(') {
        None => s.len(),
        Some(0) => {
            let (marker, rest) = s.split_at(s.find(')').unwrap() + 1);
            let (len, rep) = parse_marker(marker);
            rep * decompressed_size(&rest[..len]) + decompressed_size(&rest[len..])
        },
        Some(i) => i + decompressed_size(&s[i..]),
    }
}

fn main() {
    let mut f = std::fs::File::open("input").expect("Unable to open input file");
    let mut input = String::new();
    f.read_to_string(&mut input).expect("Unable to read file");

    println!("v1 decoding: {:?}", decompress(&input).len());
    println!("v2 decoding: {:?}", decompressed_size(input.trim()));
}
<|endoftext|>


<commit_before>
use super::*;
use std::marker::PhantomData;

/// An incoming COM interface pointer.
///
/// Intercom will implement the various `[com_interface]` traits for the
/// corresponding `ComItf<T>` type.
///
/// This applies only to the pure interfaces.  Implicit interfaces created
/// through `#[com_interface] impl MyStruct` constructs are not supported for
/// `ComItf<T>`.
#[fundamental]
#[repr(C)]
pub struct ComItf<T> where T: ?Sized {
    ptr: RawComPtr,
    phantom: PhantomData<T>,
}

impl<T> ComItf<T> where T: ?Sized {

    /// Creates a `ComItf<T>` from a raw COM interface pointer.
    ///
    /// # Safety
    ///
    /// The `ptr` __must__ be a valid COM interface pointer for an interface
    /// of type `T`.
    pub unsafe fn wrap( ptr : RawComPtr ) -> ComItf<T> {
        ComItf {
            ptr: ptr,
            phantom: PhantomData,
        }
    }

    /// Gets the raw COM pointer from the `ComItf<T>`.
    pub fn ptr( this : &Self ) -> RawComPtr { this.ptr }

    /// Returns a `ComItf<T>` value that references a null pointer.
    ///
    /// # Safety
    ///
    /// The `ComItf<T>` returned by the function will be invalid for any
    /// method calls. Its purpose is to act as a return value from COM
    /// methods in the case of an error result.
    pub unsafe fn null_itf() -> ComItf<T> {
        ComItf {
            ptr: ::std::ptr::null_mut(),
            phantom: PhantomData,
        }
    }

    pub fn try_into< U: IidOf + ?Sized >(
        &self
    ) -> Result< ComItf<U>, ::HRESULT >
    {
        let iunk : &ComItf<IUnknown> = self.as_ref();

        match iunk.query_interface( U::iid() ) {
            Ok( ptr ) => Ok( ComItf::<U> {
                ptr: ptr,
                phantom: PhantomData
            } ),
            Err( e ) => Err( e )
        }
    }
}

impl<T> AsRef<ComItf<IUnknown>> for ComItf<T> where T: ?Sized {

    fn as_ref( &self ) -> &ComItf<IUnknown> {
        unsafe { &*( self as *const _ as *const ComItf<IUnknown> ) }
    }
}
<commit_msg>Implement CoCreateInstance for ComItf<commit_after>
use super::*;
use std::marker::PhantomData;

/// An incoming COM interface pointer.
///
/// Intercom will implement the various `[com_interface]` traits for the
/// corresponding `ComItf<T>` type.
///
/// This applies only to the pure interfaces.  Implicit interfaces created
/// through `#[com_interface] impl MyStruct` constructs are not supported for
/// `ComItf<T>`.
#[fundamental]
#[repr(C)]
pub struct ComItf<T> where T: ?Sized {
    ptr: RawComPtr,
    phantom: PhantomData<T>,
}

impl<T> ComItf<T> where T: ?Sized {

    /// Creates a `ComItf<T>` from a raw COM interface pointer.
    ///
    /// # Safety
    ///
    /// The `ptr` __must__ be a valid COM interface pointer for an interface
    /// of type `T`.
    pub unsafe fn wrap( ptr : RawComPtr ) -> ComItf<T> {
        ComItf {
            ptr: ptr,
            phantom: PhantomData,
        }
    }

    /// Gets the raw COM pointer from the `ComItf<T>`.
    pub fn ptr( this : &Self ) -> RawComPtr { this.ptr }

    /// Returns a `ComItf<T>` value that references a null pointer.
    ///
    /// # Safety
    ///
    /// The `ComItf<T>` returned by the function will be invalid for any
    /// method calls. Its purpose is to act as a return value from COM
    /// methods in the case of an error result.
    pub unsafe fn null_itf() -> ComItf<T> {
        ComItf {
            ptr: ::std::ptr::null_mut(),
            phantom: PhantomData,
        }
    }

    pub fn try_into< U: IidOf + ?Sized >(
        &self
    ) -> Result< ComItf<U>, ::HRESULT >
    {
        let iunk : &ComItf<IUnknown> = self.as_ref();

        match iunk.query_interface( U::iid() ) {
            Ok( ptr ) => Ok( ComItf::<U> {
                ptr: ptr,
                phantom: PhantomData
            } ),
            Err( e ) => Err( e )
        }
    }

    pub unsafe fn out_mut_ptr( &mut self ) -> &mut RawComPtr {
        &mut self.ptr
    }
}

#[cfg(windows)]
#[link(name = "ole32")]
extern "system" {
    #[doc(hidden)]
    pub fn CoCreateInstance(
        clsid : ::guid::GUID,
        outer : RawComPtr,
        cls_context: u32,
        riid : ::REFIID,
        out : &mut RawComPtr,
    ) -> ::HRESULT;
}

#[cfg(windows)]
impl<T: IidOf + ?Sized> ComItf<T>
{
    pub fn create( clsid : GUID ) -> ::ComResult< ComItf<T> > {

        unsafe {
            let mut out = ComItf::null_itf();
            match CoCreateInstance(
                    clsid,
                    std::ptr::null_mut(),
                    1, // in-proc server.
                    T::iid(),
                    &mut ComItf::out_mut_ptr( &mut out ) ) {

                ::S_OK => Ok( out ),
                e => Err( e ),
            }
        }
    }
}

impl<T> AsRef<ComItf<IUnknown>> for ComItf<T> where T: ?Sized {

    fn as_ref( &self ) -> &ComItf<IUnknown> {
        unsafe { &*( self as *const _ as *const ComItf<IUnknown> ) }
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#[doc = "

Handy functions for creating class objects and so forth.

"];

use glue::GetJSClassHookStubPointer;
use glue::{PROPERTY_STUB, STRICT_PROPERTY_STUB, ENUMERATE_STUB,
              RESOLVE_STUB, CONVERT_STUB};
use std::libc::c_uint;
use std::str::raw::from_c_str;
use std::cast::transmute;
use name_pool::*;
use std::ptr::null;
use std::ptr;
use jsapi;
use jsapi::{JSClass, JSContext, JSVal, JSFunctionSpec, JSBool, JSNativeWrapper};
use jsapi::{JS_EncodeString, JS_free, JS_ValueToBoolean, JS_ValueToString};
use jsapi::{JS_ReportError, JS_ValueToSource, JS_GC, JS_GetRuntime};
use jsapi::{JSPropertyOp, JSStrictPropertyOp, JSEnumerateOp, JSResolveOp, JSConvertOp };
use JSCLASS_IS_GLOBAL;
use JSCLASS_HAS_RESERVED_SLOTS;
use JSCLASS_GLOBAL_SLOT_COUNT;
use JS_ARGV;
use JSVAL_VOID;
use JS_SET_RVAL;

#[fixed_stack_segment]
pub fn basic_class(np: @mut NamePool, name: ~str) -> JSClass {
    JSClass {
        name: np.add(name),
        flags: JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + 1),
        addProperty: unsafe { Some(GetJSClassHookStubPointer(PROPERTY_STUB) as JSPropertyOp) },
        delProperty: unsafe { Some(GetJSClassHookStubPointer(PROPERTY_STUB) as JSPropertyOp) },
        getProperty: unsafe { Some(GetJSClassHookStubPointer(PROPERTY_STUB) as JSPropertyOp) },
        setProperty: unsafe { Some(GetJSClassHookStubPointer(STRICT_PROPERTY_STUB) as JSStrictPropertyOp) },
        enumerate: unsafe { Some(GetJSClassHookStubPointer(ENUMERATE_STUB) as JSEnumerateOp) },
        resolve: unsafe { Some(GetJSClassHookStubPointer(RESOLVE_STUB) as JSResolveOp) },
        convert: unsafe { Some(GetJSClassHookStubPointer(CONVERT_STUB) as JSConvertOp) },
        finalize: None,
        checkAccess: None,
        call: None,
        hasInstance: None,
        construct: None,
        trace: None,
        reserved: (null(), null(), null(), null(), null(),  // 05
                   null(), null(), null(), null(), null(),  // 10
                   null(), null(), null(), null(), null(),  // 15
                   null(), null(), null(), null(), null(),  // 20
                   null(), null(), null(), null(), null(),  // 25
                   null(), null(), null(), null(), null(),  // 30
                   null(), null(), null(), null(), null(),  // 35
                   null(), null(), null(), null(), null())  // 40
    }
}

pub fn global_class(np: @mut NamePool) -> JSClass {
    basic_class(np, ~"global")
}

#[fixed_stack_segment]
pub unsafe fn jsval_to_rust_str(cx: *JSContext, vp: *jsapi::JSString) -> ~str {
    if vp.is_null() {
        ~""
    } else {
        let bytes = JS_EncodeString(cx, vp);
        let s = from_c_str(bytes);
        JS_free(cx, transmute(bytes));
        s
    }
}

pub extern fn debug(cx: *JSContext, argc: c_uint, vp: *mut JSVal) -> JSBool {
    unsafe {
        let argv = JS_ARGV(cx, &*vp);
        for i in range(0, argc as int) {
            let jsstr = JS_ValueToString(cx, *ptr::offset(argv, i));
            debug!("%s", jsval_to_rust_str(cx, jsstr));
        }
        JS_SET_RVAL(cx, &*vp, JSVAL_VOID);
        return 1_i32;
    }
}

pub extern fn gc(cx: *JSContext, argc: c_uint, vp: *mut JSVal) -> JSBool {
    unsafe {
        JS_GC(JS_GetRuntime(cx));
        JS_SET_RVAL(cx, &*vp, JSVAL_VOID);
        return 1;
    }
}

pub extern fn assert(cx: *JSContext, argc: c_uint, vp: *mut JSVal) -> JSBool {
    unsafe {
        let argv = JS_ARGV(cx, &*vp);

        let argument = match argc {
            0 => JSVAL_VOID,
            _ => *ptr::offset(argv, 0)
        };

        let result = 0;
        if JS_ValueToBoolean(cx, argument, &result) == 0 {
            return 0_i32;
        }

        if result == 0 {
            // This operation can fail, but that is not critical.
            let source = JS_ValueToSource(cx, argument);
            let msg = fmt!("JavaScript assertion failed: %s is falsy!",
                            jsval_to_rust_str(cx, source));

            debug!(msg);
            do msg.to_c_str().with_ref |buf| {
              JS_ReportError(cx, buf);
            }
            return 0_i32;
        }

        JS_SET_RVAL(cx, &*vp, JSVAL_VOID);
        return 1_i32;
    }
}

pub fn debug_fns(np: @mut NamePool) -> ~[JSFunctionSpec] {
    ~[
        JSFunctionSpec {
            name: np.add(~"debug"),
            call: JSNativeWrapper {
                op: Some(debug),
                info: null()
            },
            nargs: 0,
            flags: 0,
            selfHostedName: null()
        },
        JSFunctionSpec {
            name: np.add(~"assert"),
            call: JSNativeWrapper {
                op: Some(assert),
                info: null()
            },
            nargs: 1,
            flags: 0,
            selfHostedName: null()
        },
        JSFunctionSpec {
            name: np.add(~"gc"),
            call: JSNativeWrapper {
                op: Some(gc),
                info: null()
            },
            nargs: 1,
            flags: 0,
            selfHostedName: null()
        },
        JSFunctionSpec {
            name: null(),
            call: JSNativeWrapper {
                op: None,
                info: null(),
            },
            nargs: 0,
            flags: 0,
            selfHostedName: null()
        }
    ]
}

<commit_msg>Warning police.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#[doc = "

Handy functions for creating class objects and so forth.

"];

use glue::GetJSClassHookStubPointer;
use glue::{PROPERTY_STUB, STRICT_PROPERTY_STUB, ENUMERATE_STUB,
              RESOLVE_STUB, CONVERT_STUB};
use std::libc::c_uint;
use std::str::raw::from_c_str;
use std::cast::transmute;
use name_pool::*;
use std::ptr::null;
use std::ptr;
use jsapi;
use jsapi::{JSClass, JSContext, JSVal, JSFunctionSpec, JSBool, JSNativeWrapper};
use jsapi::{JS_EncodeString, JS_free, JS_ValueToBoolean, JS_ValueToString};
use jsapi::{JS_ReportError, JS_ValueToSource, JS_GC, JS_GetRuntime};
use jsapi::{JSPropertyOp, JSStrictPropertyOp, JSEnumerateOp, JSResolveOp, JSConvertOp };
use JSCLASS_IS_GLOBAL;
use JSCLASS_HAS_RESERVED_SLOTS;
use JSCLASS_GLOBAL_SLOT_COUNT;
use JS_ARGV;
use JSVAL_VOID;
use JS_SET_RVAL;

#[fixed_stack_segment]
pub fn basic_class(np: @mut NamePool, name: ~str) -> JSClass {
    JSClass {
        name: np.add(name),
        flags: JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + 1),
        addProperty: unsafe { Some(GetJSClassHookStubPointer(PROPERTY_STUB) as JSPropertyOp) },
        delProperty: unsafe { Some(GetJSClassHookStubPointer(PROPERTY_STUB) as JSPropertyOp) },
        getProperty: unsafe { Some(GetJSClassHookStubPointer(PROPERTY_STUB) as JSPropertyOp) },
        setProperty: unsafe { Some(GetJSClassHookStubPointer(STRICT_PROPERTY_STUB) as JSStrictPropertyOp) },
        enumerate: unsafe { Some(GetJSClassHookStubPointer(ENUMERATE_STUB) as JSEnumerateOp) },
        resolve: unsafe { Some(GetJSClassHookStubPointer(RESOLVE_STUB) as JSResolveOp) },
        convert: unsafe { Some(GetJSClassHookStubPointer(CONVERT_STUB) as JSConvertOp) },
        finalize: None,
        checkAccess: None,
        call: None,
        hasInstance: None,
        construct: None,
        trace: None,
        reserved: (null(), null(), null(), null(), null(),  // 05
                   null(), null(), null(), null(), null(),  // 10
                   null(), null(), null(), null(), null(),  // 15
                   null(), null(), null(), null(), null(),  // 20
                   null(), null(), null(), null(), null(),  // 25
                   null(), null(), null(), null(), null(),  // 30
                   null(), null(), null(), null(), null(),  // 35
                   null(), null(), null(), null(), null())  // 40
    }
}

pub fn global_class(np: @mut NamePool) -> JSClass {
    basic_class(np, ~"global")
}

#[fixed_stack_segment]
pub unsafe fn jsval_to_rust_str(cx: *JSContext, vp: *jsapi::JSString) -> ~str {
    if vp.is_null() {
        ~""
    } else {
        let bytes = JS_EncodeString(cx, vp);
        let s = from_c_str(bytes);
        JS_free(cx, transmute(bytes));
        s
    }
}

pub extern fn debug(cx: *JSContext, argc: c_uint, vp: *mut JSVal) -> JSBool {
    unsafe {
        let argv = JS_ARGV(cx, &*vp);
        for i in range(0, argc as int) {
            let jsstr = JS_ValueToString(cx, *ptr::offset(argv, i));
            debug!("%s", jsval_to_rust_str(cx, jsstr));
        }
        JS_SET_RVAL(cx, &*vp, JSVAL_VOID);
        return 1_i32;
    }
}

pub extern fn gc(cx: *JSContext, _argc: c_uint, vp: *mut JSVal) -> JSBool {
    unsafe {
        JS_GC(JS_GetRuntime(cx));
        JS_SET_RVAL(cx, &*vp, JSVAL_VOID);
        return 1;
    }
}

pub extern fn assert(cx: *JSContext, argc: c_uint, vp: *mut JSVal) -> JSBool {
    unsafe {
        let argv = JS_ARGV(cx, &*vp);

        let argument = match argc {
            0 => JSVAL_VOID,
            _ => *ptr::offset(argv, 0)
        };

        let result = 0;
        if JS_ValueToBoolean(cx, argument, &result) == 0 {
            return 0_i32;
        }

        if result == 0 {
            // This operation can fail, but that is not critical.
            let source = JS_ValueToSource(cx, argument);
            let msg = fmt!("JavaScript assertion failed: %s is falsy!",
                            jsval_to_rust_str(cx, source));

            debug!(msg);
            do msg.to_c_str().with_ref |buf| {
              JS_ReportError(cx, buf);
            }
            return 0_i32;
        }

        JS_SET_RVAL(cx, &*vp, JSVAL_VOID);
        return 1_i32;
    }
}

pub fn debug_fns(np: @mut NamePool) -> ~[JSFunctionSpec] {
    ~[
        JSFunctionSpec {
            name: np.add(~"debug"),
            call: JSNativeWrapper {
                op: Some(debug),
                info: null()
            },
            nargs: 0,
            flags: 0,
            selfHostedName: null()
        },
        JSFunctionSpec {
            name: np.add(~"assert"),
            call: JSNativeWrapper {
                op: Some(assert),
                info: null()
            },
            nargs: 1,
            flags: 0,
            selfHostedName: null()
        },
        JSFunctionSpec {
            name: np.add(~"gc"),
            call: JSNativeWrapper {
                op: Some(gc),
                info: null()
            },
            nargs: 1,
            flags: 0,
            selfHostedName: null()
        },
        JSFunctionSpec {
            name: null(),
            call: JSNativeWrapper {
                op: None,
                info: null(),
            },
            nargs: 0,
            flags: 0,
            selfHostedName: null()
        }
    ]
}

<|endoftext|>