<commit_before>use event::{Event, Rule, State};
use std::borrow::Cow::Borrowed;

fn is_ascii_whitespace(c: u8) -> bool {
   is_newline(c) || is_ascii_whitespace_no_nl(c)
}

fn is_ascii_whitespace_no_nl(c: u8) -> bool {
    c == b'\t' || c == 0x0b || c == 0x0c || c == b' '
}

fn is_newline(c: u8) -> bool {
    c == b'\n' || c == b'\r'
}

fn isnt_newline(c: u8) -> bool {
    !is_newline(c)
}

// unusual among "scan" functions in that it scans from the _back_ of the string
// TODO: should also scan unicode whitespace?
fn scan_trailing_whitespace(data: &str) -> usize {
    match data.as_bytes().iter().rev().position(|&c| !is_ascii_whitespace_no_nl(c)) {
        Some(i) => i,
        None => data.len()
    }
}

#[derive(Debug)]
pub struct Tokenizer<'a> {
    sass: &'a str,
    bytes: &'a [u8],
    offset: usize,
    stack: Vec<Rule>,
    state: State,
}

impl<'a> Tokenizer<'a> {
    pub fn new(sass: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            sass: &sass,
            bytes: &sass.as_bytes(),
            offset: 0,
            stack: Vec::new(),
            state: State::StartRule,
        }
    }

    fn start_rule(&mut self) -> Option<Event<'a>> {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            return None
        }

        let c = self.bytes[self.offset];
        if c == b'}' {
            self.offset += 1;
            return Some(self.end())
        }
        if c == b'$' {
            return Some(self.next_variable())
        }

        let d = self.bytes[self.offset + 1];
        if c == b'/' && d == b'*' {
            return Some(self.next_comment())
        }

        self.state = State::InSelectors;
        self.stack.push(Rule::SassRule);

        Some(Event::Start(Rule::SassRule))
    }

    fn end(&mut self) -> Event<'a> {
        let rule = match self.stack.pop() {
            Some(r) => r,
            None => {
                println!("Unexpected empty stack!");
                return Event::End(Rule::SassRule)
            },
        };

        if self.stack.len() == 0 {
            self.state = State::StartRule;
        } else {
            self.state = State::InProperties;
        }

        Event::End(rule)
    }

    fn scan_while<F>(&mut self, data: &str, f: F) -> usize
            where F: Fn(u8) -> bool {
        match data.as_bytes().iter().position(|&c| !f(c)) {
            Some(i) => i,
            None => data.len()
        }
    }

    fn skip_leading_whitespace(&mut self) {
        let mut i = self.offset;
        let limit = self.sass.len();

        while i < limit {
            let c = self.bytes[i];
            if is_ascii_whitespace(c) {
                i += self.scan_while(&self.sass[i..self.sass.len()], is_ascii_whitespace);
            } else if c == b'/' && i + 1 < limit && self.bytes[i + 1] == b'/' {
                i += self.scan_while(&self.sass[i..self.sass.len()], isnt_newline);
            } else {
                self.offset = i;
                return
            }
        }
        self.offset = limit;
    }

    fn next_comment(&mut self) -> Event<'a> {
        let comment_body_beginning = self.offset;
        let mut i = comment_body_beginning + 2;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b'*' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            if self.bytes[i+1] == b'/' {
                self.offset = i + 2;
                return Event::Comment(Borrowed(&self.sass[comment_body_beginning..i + 2]))
            } else {
                i += 1;
            }
        }
        unreachable!()
    }

    fn next_variable(&mut self) -> Event<'a> {
        // TODO: can parts of this be deduplicated with properties?
        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Event::Variable(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                )
            }
        }
        self.offset = self.sass.len();
        Event::Property(Borrowed(""), Borrowed(""))
    }

    fn next_property(&mut self) -> Event<'a> {
        self.skip_leading_whitespace();

        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        let c = self.bytes[i];
        if c == b'}' {
            self.offset += 1;
            return self.end()
        }

        let d = self.bytes[i + 1];
        if c == b'/' && d == b'*' {
            return self.next_comment()
        }

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' || c == b'{') {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.bytes[i];
            if c == b'{' {
                self.state = State::InSelectors;
                self.stack.push(Rule::SassRule);
                return Event::Start(Rule::SassRule)
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Event::Property(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                )

            }
        }
        self.offset = self.sass.len();
        Event::Property(Borrowed(""), Borrowed(""))
    }

    fn next_selector(&mut self) -> Event<'a> {
        let beginning = self.offset;
        let mut i = beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b',' || c == b'{') {
                Some(pos) => { i += pos; },
                None => { i = limit; break; },
            }

            let c = self.bytes[i];

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.sass[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        self.state = State::InProperties;
                    }
                    self.offset = i + 1;
                    return Event::Selector(Borrowed(&self.sass[beginning..end]));
                }
            }

            self.offset = i;
            if i > beginning {
                return Event::Selector(Borrowed(&self.sass[beginning..i]))
            }
            i += 1;
        }

        if i > beginning {
            self.offset = i;
            Event::Selector(Borrowed(&self.sass[beginning..i]))
        } else {
            self.end()
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Event<'a>;

    fn next(&mut self) -> Option<Event<'a>> {
        if self.offset < self.sass.len() {
            match self.state {
                State::StartRule => {
                    let ret = self.start_rule();
                    if ret.is_some() {
                        return ret
                    }
                },
                State::InSelectors => return Some(self.next_selector()),
                State::InProperties => return Some(self.next_property()),
            }
        }
        None
    }
}
<commit_msg>Be able to transition from expecting selectors to properties<commit_after>use event::{Event, Rule, State};
use std::borrow::Cow::Borrowed;

fn is_ascii_whitespace(c: u8) -> bool {
   is_newline(c) || is_ascii_whitespace_no_nl(c)
}

fn is_ascii_whitespace_no_nl(c: u8) -> bool {
    c == b'\t' || c == 0x0b || c == 0x0c || c == b' '
}

fn is_newline(c: u8) -> bool {
    c == b'\n' || c == b'\r'
}

fn isnt_newline(c: u8) -> bool {
    !is_newline(c)
}

// unusual among "scan" functions in that it scans from the _back_ of the string
// TODO: should also scan unicode whitespace?
fn scan_trailing_whitespace(data: &str) -> usize {
    match data.as_bytes().iter().rev().position(|&c| !is_ascii_whitespace_no_nl(c)) {
        Some(i) => i,
        None => data.len()
    }
}

#[derive(Debug)]
pub struct Tokenizer<'a> {
    sass: &'a str,
    bytes: &'a [u8],
    offset: usize,
    stack: Vec<Rule>,
    state: State,
}

impl<'a> Tokenizer<'a> {
    pub fn new(sass: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            sass: &sass,
            bytes: &sass.as_bytes(),
            offset: 0,
            stack: Vec::new(),
            state: State::StartRule,
        }
    }

    fn start_rule(&mut self) -> Option<Event<'a>> {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            return None
        }

        let c = self.bytes[self.offset];
        if c == b'}' {
            self.offset += 1;
            return Some(self.end())
        }
        if c == b'$' {
            return Some(self.next_variable())
        }

        let d = self.bytes[self.offset + 1];
        if c == b'/' && d == b'*' {
            return Some(self.next_comment())
        }

        self.state = State::InSelectors;
        self.stack.push(Rule::SassRule);

        Some(Event::Start(Rule::SassRule))
    }

    fn end(&mut self) -> Event<'a> {
        let rule = match self.stack.pop() {
            Some(r) => r,
            None => {
                println!("Unexpected empty stack!");
                return Event::End(Rule::SassRule)
            },
        };

        if self.stack.len() == 0 {
            self.state = State::StartRule;
        } else {
            self.state = State::InProperties;
        }

        Event::End(rule)
    }

    fn scan_while<F>(&mut self, data: &str, f: F) -> usize
            where F: Fn(u8) -> bool {
        match data.as_bytes().iter().position(|&c| !f(c)) {
            Some(i) => i,
            None => data.len()
        }
    }

    fn skip_leading_whitespace(&mut self) {
        let mut i = self.offset;
        let limit = self.sass.len();

        while i < limit {
            let c = self.bytes[i];
            if is_ascii_whitespace(c) {
                i += self.scan_while(&self.sass[i..self.sass.len()], is_ascii_whitespace);
            } else if c == b'/' && i + 1 < limit && self.bytes[i + 1] == b'/' {
                i += self.scan_while(&self.sass[i..self.sass.len()], isnt_newline);
            } else {
                self.offset = i;
                return
            }
        }
        self.offset = limit;
    }

    fn next_comment(&mut self) -> Event<'a> {
        let comment_body_beginning = self.offset;
        let mut i = comment_body_beginning + 2;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b'*' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            if self.bytes[i+1] == b'/' {
                self.offset = i + 2;
                return Event::Comment(Borrowed(&self.sass[comment_body_beginning..i + 2]))
            } else {
                i += 1;
            }
        }
        unreachable!()
    }

    fn next_variable(&mut self) -> Event<'a> {
        // TODO: can parts of this be deduplicated with properties?
        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Event::Variable(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                )
            }
        }
        self.offset = self.sass.len();
        Event::Property(Borrowed(""), Borrowed(""))
    }

    fn next_property(&mut self) -> Event<'a> {
        self.skip_leading_whitespace();

        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        let c = self.bytes[i];
        if c == b'}' {
            self.offset += 1;
            return self.end()
        }

        let d = self.bytes[i + 1];
        if c == b'/' && d == b'*' {
            return self.next_comment()
        }

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' || c == b'{') {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.bytes[i];
            if c == b'{' {
                self.state = State::InSelectors;
                self.stack.push(Rule::SassRule);
                return Event::Start(Rule::SassRule)
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Event::Property(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                )

            }
        }
        self.offset = self.sass.len();
        Event::Property(Borrowed(""), Borrowed(""))
    }

    fn next_selector(&mut self) -> Event<'a> {
        let beginning = self.offset;
        let mut i = beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b',' || c == b'{' || c == b':') {
                Some(pos) => { i += pos; },
                None => { i = limit; break; },
            }

            let c = self.bytes[i];

            if c == b':' {
                self.state = State::InProperties;
                return self.next_property()
            }

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.sass[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        self.state = State::InProperties;
                    }
                    self.offset = i + 1;
                    return Event::Selector(Borrowed(&self.sass[beginning..end]));
                }
            }

            self.offset = i;
            if i > beginning {
                return Event::Selector(Borrowed(&self.sass[beginning..i]))
            }
            i += 1;
        }

        if i > beginning {
            self.offset = i;
            Event::Selector(Borrowed(&self.sass[beginning..i]))
        } else {
            self.end()
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Event<'a>;

    fn next(&mut self) -> Option<Event<'a>> {
        if self.offset < self.sass.len() {
            match self.state {
                State::StartRule => {
                    let ret = self.start_rule();
                    if ret.is_some() {
                        return ret
                    }
                },
                State::InSelectors => return Some(self.next_selector()),
                State::InProperties => return Some(self.next_property()),
            }
        }
        None
    }
}
<|endoftext|>


<commit_before>use error::{SassError, ErrorKind, Result};
use event::Event;
use sass::comment::SassComment;
use sass::rule::SassRule;
use sass::selector::SassSelector;
use sass::variable::SassVariable;
use top_level_event::TopLevelEvent;
use tokenizer_utils::*;

use std::borrow::Cow::Borrowed;

#[derive(Debug)]
pub struct Tokenizer<'a> {
    toker: Toker<'a>,
    state: State,
    sass_rule_stack: Vec<SassRule<'a>>,
    current_sass_rule_selectors_done: bool,
}

#[derive(PartialEq, Debug, Copy, Clone)]
enum State {
    OutsideRules,
    InVariable,
    InComment,
    InRule,
    InSelectors,
    InProperties,
    EndRule,
    Eof,
}

impl<'a> Tokenizer<'a> {
    pub fn new(inner_str: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            toker: Toker {
                inner_str: &inner_str,
                bytes: &inner_str.as_bytes(),
                offset: 0,
            },
            state: State::OutsideRules,
            sass_rule_stack: Vec::new(),
            current_sass_rule_selectors_done: false,
        }
    }

    fn limit(&self) -> usize {
        self.toker.limit()
    }

    fn start_something(&mut self) -> Result<Option<TopLevelEvent<'a>>> {
        let mut current_sass_rule = SassRule::new();
        self.current_sass_rule_selectors_done = false;

        self.pick_something();
        while self.state != State::OutsideRules {
            if self.state == State::Eof { return Ok(None) }

            // See if this works now that State is Copy
            // self.state = match self.state

            if self.state == State::InSelectors {
                match try!(self.next_selector()) {
                    Some(sel) => current_sass_rule.selectors.push(sel),
                    None => {},
                }
            } else if self.state == State::InProperties {
                match try!(self.next_property()) {
                    Some(prop) => current_sass_rule.children.push(prop),
                    None => {},
                }
            } else if self.state == State::EndRule {
                try!(self.toker.eat("}"));

                match self.sass_rule_stack.pop() {
                    Some(mut rule) => {
                        rule.children.push(Event::ChildRule(current_sass_rule));
                        current_sass_rule = rule;
                        self.pick_something();
                    },
                    None => self.state = State::OutsideRules,
                }
            } else if self.state == State::InRule {
                self.sass_rule_stack.push(current_sass_rule);
                current_sass_rule = SassRule::new();
                self.current_sass_rule_selectors_done = false;
                self.pick_something();
            } else if self.state == State::InVariable {
                return self.next_variable()
            } else if self.state == State::InComment {
                let comment = try!(self.next_comment());
                if comment.is_some() {
                    if self.sass_rule_stack.len() == 0 &&
                       current_sass_rule.selectors.len() == 0 &&
                       current_sass_rule.children.len() == 0 {
                           return Ok(Some(
                               TopLevelEvent::Comment(SassComment { comment: comment.unwrap() })
                           ))
                    } else {
                        current_sass_rule.children.push(comment.unwrap());
                        self.pick_something();
                    }
                } else {
                    return Ok(None)
                }
            } else {
                return Err(SassError {
                    kind: ErrorKind::TokenizerError,
                    message: format!(
                        "Something unexpected happened in tokenization! Current tokenization state: {:?}. Current sass rule = {:?}",
                        self.state,
                        current_sass_rule
                    ),
                })
            }
        }

        Ok(Some(TopLevelEvent::Rule(current_sass_rule)))
    }

    fn pick_something(&mut self) {
        self.toker.skip_leading_whitespace();

        if self.toker.at_eof() {
            self.state = State::Eof;
            return
        }

        let c = self.toker.bytes[self.toker.offset];

        if c == b'}' {
            self.state = State::EndRule;
            return
        }

        if c == b'$' {
            self.state = State::InVariable;
            return
        }

        if c == b'/' && (self.toker.offset + 1) < self.limit() {
            let d = self.toker.bytes[self.toker.offset + 1];
            if d == b'*' {
                self.state = State::InComment;
                return
            }
        }

        if self.current_sass_rule_selectors_done {
            self.state = State::InProperties;
            return
        }

        self.state = State::InSelectors;
    }

    fn next_comment(&mut self) -> Result<Option<Event<'a>>> {
        let comment_body_beginning = self.toker.offset;
        let mut i = comment_body_beginning + 2;

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, isnt_asterisk);
            self.toker.offset = i;

            if self.toker.eat("*/").is_ok() {
                return Ok(Some(
                    Event::Comment(Borrowed(
                        &self.toker.inner_str[comment_body_beginning..self.toker.offset]
                    ))
                ))
            } else {
                i += 1;
            }
        }
        self.toker.offset = self.limit();
        Err(SassError {
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected comment; reached EOF instead."
            ),
        })
    }

    fn next_variable(&mut self) -> Result<Option<TopLevelEvent<'a>>> {
        // TODO: can parts of this be deduplicated with properties?
        let name_beginning = self.toker.offset;
        let mut i = name_beginning;

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, valid_name_char);
            let name_end = i;
            self.toker.offset = i;
            try!(self.toker.eat(":"));
            self.toker.skip_leading_whitespace();

            let value_beginning = self.toker.offset;
            i = value_beginning;

            while i < self.limit() {
                i += self.toker.scan_while_or_end(i, isnt_semicolon);
                let value_end = i;
                self.toker.offset = i;
                try!(self.toker.eat(";"));
                self.toker.skip_leading_whitespace();

                return Ok(Some(TopLevelEvent::Variable(SassVariable{
                    name: Borrowed(&self.toker.inner_str[name_beginning..name_end]),
                    value: Borrowed(&self.toker.inner_str[value_beginning..value_end]),
                })))
            }
        }
        self.toker.offset = self.limit();
        Err(SassError {
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected variable declaration and value; reached EOF instead."
            ),
        })
    }

    fn next_property(&mut self) -> Result<Option<Event<'a>>> {
        self.toker.skip_leading_whitespace();

        if self.toker.at_eof() {
            self.state = State::Eof;
            return Ok(None)
        }

        let name_beginning = self.toker.offset;
        let mut i = name_beginning;

        let c = self.toker.bytes[i];
        if c == b'}' {
            self.state = State::EndRule;
            return Ok(None)
        }

        let d = self.toker.bytes[i + 1];
        if c == b'/' && d == b'*' {
            self.state = State::InComment;
            return Ok(None)
        }

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, valid_name_char);

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.toker.bytes[i];
            if c == b'{' {
                self.state = State::InRule;
                return Ok(None)
            }

            let name_end = i;
            self.toker.offset = i;
            try!(self.toker.eat(":"));
            self.toker.skip_leading_whitespace();

            let value_beginning = self.toker.offset;
            i = value_beginning;

            while i < self.limit() {
                i += self.toker.scan_while_or_end(i, isnt_semicolon);
                let value_end = i;
                self.toker.offset = i;
                try!(self.toker.eat(";"));
                self.toker.skip_leading_whitespace();

                if self.toker.bytes[name_beginning] == b'$' {
                    return Ok(Some(Event::Variable(SassVariable {
                        name: Borrowed(&self.toker.inner_str[name_beginning..name_end]),
                        value: Borrowed(&self.toker.inner_str[value_beginning..value_end])
                    })))
                } else {
                    return Ok(Some(Event::UnevaluatedProperty(
                        Borrowed(&self.toker.inner_str[name_beginning..name_end]),
                        Borrowed(&self.toker.inner_str[value_beginning..value_end]),
                    )))
                }
            }
        }
        self.toker.offset = self.limit();
        Ok(None)
    }

    fn next_selector(&mut self) -> Result<Option<SassSelector<'a>>> {
        self.toker.skip_leading_whitespace();

        let beginning = self.toker.offset;
        let mut i = beginning;

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, valid_selector_char);
            let c = self.toker.bytes[i];

            if c == b':' {
                self.state = State::InProperties;
                return Ok(None)
            }

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.toker.inner_str[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        if self.current_sass_rule_selectors_done {
                            self.state = State::InRule;
                            return Ok(None)
                        } else {
                            self.current_sass_rule_selectors_done = true;
                            self.state = State::InProperties;
                        }
                    }
                    self.toker.offset = i + 1;
                    return Ok(Some(SassSelector::new(&self.toker.inner_str[beginning..end])))
                } else {
                    // only whitespace between commas
                    self.toker.offset += 1;
                    return self.next_selector()
                }
            }

            self.toker.offset = i;
            if i > beginning {
                return Ok(Some(SassSelector::new(&self.toker.inner_str[beginning..i])))
            }
            i += 1;
        }

        if i > beginning {
            self.toker.offset = i;
            Ok(Some(SassSelector::new(&self.toker.inner_str[beginning..i])))
        } else {
            self.state = State::Eof;
            Ok(None)
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Result<TopLevelEvent<'a>>;

    fn next(&mut self) -> Option<Result<TopLevelEvent<'a>>> {
        if !self.toker.at_eof() {
            return match self.start_something() {
                Ok(Some(t)) => Some(Ok(t)),
                Ok(None) => None,
                Err(e) => Some(Err(e)),
            }
        }
        None
    }
}
<commit_msg>Extract methods for getting variable name and variable value<commit_after>use error::{SassError, ErrorKind, Result};
use event::Event;
use sass::comment::SassComment;
use sass::rule::SassRule;
use sass::selector::SassSelector;
use sass::variable::SassVariable;
use top_level_event::TopLevelEvent;
use tokenizer_utils::*;

use std::borrow::Cow::Borrowed;
use std::borrow::Cow;

#[derive(Debug)]
pub struct Tokenizer<'a> {
    toker: Toker<'a>,
    state: State,
    sass_rule_stack: Vec<SassRule<'a>>,
    current_sass_rule_selectors_done: bool,
}

#[derive(PartialEq, Debug, Copy, Clone)]
enum State {
    OutsideRules,
    InVariable,
    InComment,
    InRule,
    InSelectors,
    InProperties,
    EndRule,
    Eof,
}

impl<'a> Tokenizer<'a> {
    pub fn new(inner_str: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            toker: Toker {
                inner_str: &inner_str,
                bytes: &inner_str.as_bytes(),
                offset: 0,
            },
            state: State::OutsideRules,
            sass_rule_stack: Vec::new(),
            current_sass_rule_selectors_done: false,
        }
    }

    fn limit(&self) -> usize {
        self.toker.limit()
    }

    fn start_something(&mut self) -> Result<Option<TopLevelEvent<'a>>> {
        let mut current_sass_rule = SassRule::new();
        self.current_sass_rule_selectors_done = false;

        self.pick_something();
        while self.state != State::OutsideRules {
            if self.state == State::Eof { return Ok(None) }

            // See if this works now that State is Copy
            // self.state = match self.state

            if self.state == State::InSelectors {
                match try!(self.next_selector()) {
                    Some(sel) => current_sass_rule.selectors.push(sel),
                    None => {},
                }
            } else if self.state == State::InProperties {
                match try!(self.next_property()) {
                    Some(prop) => current_sass_rule.children.push(prop),
                    None => {},
                }
            } else if self.state == State::EndRule {
                try!(self.toker.eat("}"));

                match self.sass_rule_stack.pop() {
                    Some(mut rule) => {
                        rule.children.push(Event::ChildRule(current_sass_rule));
                        current_sass_rule = rule;
                        self.pick_something();
                    },
                    None => self.state = State::OutsideRules,
                }
            } else if self.state == State::InRule {
                self.sass_rule_stack.push(current_sass_rule);
                current_sass_rule = SassRule::new();
                self.current_sass_rule_selectors_done = false;
                self.pick_something();
            } else if self.state == State::InVariable {
                return self.next_variable()
            } else if self.state == State::InComment {
                let comment = try!(self.next_comment());
                if comment.is_some() {
                    if self.sass_rule_stack.len() == 0 &&
                       current_sass_rule.selectors.len() == 0 &&
                       current_sass_rule.children.len() == 0 {
                           return Ok(Some(
                               TopLevelEvent::Comment(SassComment { comment: comment.unwrap() })
                           ))
                    } else {
                        current_sass_rule.children.push(comment.unwrap());
                        self.pick_something();
                    }
                } else {
                    return Ok(None)
                }
            } else {
                return Err(SassError {
                    kind: ErrorKind::TokenizerError,
                    message: format!(
                        "Something unexpected happened in tokenization! Current tokenization state: {:?}. Current sass rule = {:?}",
                        self.state,
                        current_sass_rule
                    ),
                })
            }
        }

        Ok(Some(TopLevelEvent::Rule(current_sass_rule)))
    }

    fn pick_something(&mut self) {
        self.toker.skip_leading_whitespace();

        if self.toker.at_eof() {
            self.state = State::Eof;
            return
        }

        let c = self.toker.bytes[self.toker.offset];

        if c == b'}' {
            self.state = State::EndRule;
            return
        }

        if c == b'$' {
            self.state = State::InVariable;
            return
        }

        if c == b'/' && (self.toker.offset + 1) < self.limit() {
            let d = self.toker.bytes[self.toker.offset + 1];
            if d == b'*' {
                self.state = State::InComment;
                return
            }
        }

        if self.current_sass_rule_selectors_done {
            self.state = State::InProperties;
            return
        }

        self.state = State::InSelectors;
    }

    fn next_comment(&mut self) -> Result<Option<Event<'a>>> {
        let comment_body_beginning = self.toker.offset;
        let mut i = comment_body_beginning + 2;

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, isnt_asterisk);
            self.toker.offset = i;

            if self.toker.eat("*/").is_ok() {
                return Ok(Some(
                    Event::Comment(Borrowed(
                        &self.toker.inner_str[comment_body_beginning..self.toker.offset]
                    ))
                ))
            } else {
                i += 1;
            }
        }
        self.toker.offset = self.limit();
        Err(SassError {
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected comment; reached EOF instead."
            ),
        })
    }

    fn next_name(&mut self) -> Result<Cow<'a, str>> {
        let name_beginning = self.toker.offset;
        let mut i = name_beginning;

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, valid_name_char);
            let name_end = i;
            self.toker.offset = i;
            return Ok(Borrowed(&self.toker.inner_str[name_beginning..name_end]))
        }
        self.toker.offset = self.limit();
        Err(SassError {
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected a valid name; reached EOF instead."
            ),
        })
    }

    fn next_value(&mut self) -> Result<Cow<'a, str>> {
        let value_beginning = self.toker.offset;
        let mut i = value_beginning;

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, isnt_semicolon);
            let value_end = i;
            self.toker.offset = i;
            return Ok(Borrowed(&self.toker.inner_str[value_beginning..value_end]))
        }
        self.toker.offset = self.limit();
        Err(SassError {
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected a valid value; reached EOF instead."
            ),
        })
    }

    fn next_variable(&mut self) -> Result<Option<TopLevelEvent<'a>>> {
        let var_name = try!(self.next_name());

        try!(self.toker.eat(":"));
        self.toker.skip_leading_whitespace();

        let var_value = try!(self.next_value());

        try!(self.toker.eat(";"));
        self.toker.skip_leading_whitespace();

        Ok(Some(TopLevelEvent::Variable(SassVariable {
            name:  var_name,
            value: var_value,
        })))
    }

    fn next_property(&mut self) -> Result<Option<Event<'a>>> {
        self.toker.skip_leading_whitespace();

        if self.toker.at_eof() {
            self.state = State::Eof;
            return Ok(None)
        }

        let name_beginning = self.toker.offset;
        let mut i = name_beginning;

        let c = self.toker.bytes[i];
        if c == b'}' {
            self.state = State::EndRule;
            return Ok(None)
        }

        let d = self.toker.bytes[i + 1];
        if c == b'/' && d == b'*' {
            self.state = State::InComment;
            return Ok(None)
        }

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, valid_name_char);

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.toker.bytes[i];
            if c == b'{' {
                self.state = State::InRule;
                return Ok(None)
            }

            let name_end = i;
            self.toker.offset = i;
            try!(self.toker.eat(":"));
            self.toker.skip_leading_whitespace();

            let value_beginning = self.toker.offset;
            i = value_beginning;

            while i < self.limit() {
                i += self.toker.scan_while_or_end(i, isnt_semicolon);
                let value_end = i;
                self.toker.offset = i;
                try!(self.toker.eat(";"));
                self.toker.skip_leading_whitespace();

                if self.toker.bytes[name_beginning] == b'$' {
                    return Ok(Some(Event::Variable(SassVariable {
                        name: Borrowed(&self.toker.inner_str[name_beginning..name_end]),
                        value: Borrowed(&self.toker.inner_str[value_beginning..value_end])
                    })))
                } else {
                    return Ok(Some(Event::UnevaluatedProperty(
                        Borrowed(&self.toker.inner_str[name_beginning..name_end]),
                        Borrowed(&self.toker.inner_str[value_beginning..value_end]),
                    )))
                }
            }
        }
        self.toker.offset = self.limit();
        Ok(None)
    }

    fn next_selector(&mut self) -> Result<Option<SassSelector<'a>>> {
        self.toker.skip_leading_whitespace();

        let beginning = self.toker.offset;
        let mut i = beginning;

        while i < self.limit() {
            i += self.toker.scan_while_or_end(i, valid_selector_char);
            let c = self.toker.bytes[i];

            if c == b':' {
                self.state = State::InProperties;
                return Ok(None)
            }

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.toker.inner_str[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        if self.current_sass_rule_selectors_done {
                            self.state = State::InRule;
                            return Ok(None)
                        } else {
                            self.current_sass_rule_selectors_done = true;
                            self.state = State::InProperties;
                        }
                    }
                    self.toker.offset = i + 1;
                    return Ok(Some(SassSelector::new(&self.toker.inner_str[beginning..end])))
                } else {
                    // only whitespace between commas
                    self.toker.offset += 1;
                    return self.next_selector()
                }
            }

            self.toker.offset = i;
            if i > beginning {
                return Ok(Some(SassSelector::new(&self.toker.inner_str[beginning..i])))
            }
            i += 1;
        }

        if i > beginning {
            self.toker.offset = i;
            Ok(Some(SassSelector::new(&self.toker.inner_str[beginning..i])))
        } else {
            self.state = State::Eof;
            Ok(None)
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Result<TopLevelEvent<'a>>;

    fn next(&mut self) -> Option<Result<TopLevelEvent<'a>>> {
        if !self.toker.at_eof() {
            return match self.start_something() {
                Ok(Some(t)) => Some(Ok(t)),
                Ok(None) => None,
                Err(e) => Some(Err(e)),
            }
        }
        None
    }
}
<|endoftext|>


<commit_before>// Copyright 2013-2015, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::fmt::{Error, Debug};
use std::ffi::CStr;

#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Status {
    Success = 0,

    NoMemory,
    InvalidRestore,
    InvalidPopGroup,
    NoCurrentPoint,
    InvalidMatrix,
    InvalidStatus,
    NullPointer,
    InvalidString,
    InvalidPathData,
    ReadError,
    WriteError,
    SurfaceFinished,
    SurfaceTypeMismatch,
    PatternTypeMismatch,
    InvalidContent,
    InvalidFormat,
    InvalidVisual,
    FileNotFound,
    InvalidDash,
    InvalidDscComment,
    InvalidIndex,
    ClipNotRepresentable,
    TempFileError,
    InvalidStride,
    FontTypeMismatch,
    UserFontImmutable,
    UserFontError,
    NegativeCount,
    InvalidClusters,
    InvalidSlant,
    InvalidWeight,
    InvalidSize,
    UserFontNotImplemented,
    DeviceTypeMismatch,
    DeviceError,
    InvalidMeshConstruction,
    DeviceFinished,
    // CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID has been used on at least one image but no
    // image provided `JBig2Global` (Since 1.14)
    // JBig2GlobalMissing,
    LastStatus
}

impl Debug for Status {
    fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> Result<(), Error> {
        unsafe {
            let char_ptr = super::cairo_status_to_string(*self);
            let tmp = String::from_utf8_lossy(CStr::from_ptr(char_ptr).to_bytes()).into_owned();

            tmp.fmt(formatter)
        }
    }
}

impl Status {
    pub fn ensure_valid(&self) {
        if *self != Status::Success {
            panic!("Cairo error {:?}", *self)
        }
    }
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Antialias {
    Default,

    /* method */
    None,
    Gray,
    Subpixel,

    /* hints */
    Fast,
    Good,
    Best
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FillRule {
    Winding,
    EvenOdd
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineCap {
    Butt,
    Round,
    Square
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineJoin {
    Miter,
    Round,
    nBevel
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Operator {
    Clear,

    Source,
    Over,
    In,
    Out,
    Atop,

    Dest,
    DestOver,
    DestIn,
    DestOut,
    DestAtop,

    Xor,
    Add,
    Saturate,

    Multiply,
    Screen,
    Overlay,
    Darken,
    Lighten,
    ColorDodge,
    ColorBurn,
    HardLight,
    SoftLight,
    Difference,
    Exclusion,
    HslHue,
    HslSaturation,
    HslColor,
    HslLuminosity
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PathDataType {
    MoveTo,
    LineTo,
    CurveTo,
    ClosePath
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Content {
    Color      = 0x1000,
    Alpha      = 0x2000,
    ColorAlpha = 0x3000
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Extend {
    None,
    Repeat,
    Reflect,
    Pad
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Filter {
    Fast,
    Good,
    Best,
    Nearest,
    Bilinear,
    Gaussian
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PatternType {
    Solid,
    Surface,
    LinearGradient,
    RadialGradient,
    #[cfg(feature = "v1_12")]
    Mesh,
    #[cfg(feature = "v1_12")]
    RasterSource
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontSlant {
    Normal,
    Italic,
    Oblique
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontWeight {
    Normal,
    Bold
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum TextClusterFlags {
    None     = 0x00000000,
    Backward = 0x00000001
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontType {
    FontTypeToy,
    FontTypeFt,
    FontTypeWin32,
    FontTypeQuartz,
    FontTypeUser
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum SubpixelOrder {
    Default,
    Rgb,
    Bgr,
    Vrgb,
    Vbgr
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintStyle {
    Default,
    None,
    Slight,
    Medium,
    Full
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintMetrics {
    Default,
    Off,
    On
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SurfaceType {
    Image,
    Pdf,
    Ps,
    Xlib,
    Xcb,
    Glitz,
    Quartz,
    Win32,
    BeOs,
    DirectFb,
    Svg,
    Os2,
    Win32Printing,
    QuartzImage,
    Script,
    Qt,
    Recording,
    Vg,
    Gl,
    Drm,
    Tee,
    Xml,
    Skia,
    Subsurface,
    Cogl,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Format {
    Invalid = -1,
    ARgb32 = 0,
    Rgb24 = 1,
    A8 = 2,
    A1 = 3,
    Rgb16_565 = 4,
    Rgb30 = 5,
}
<commit_msg>Fixing typo in LineJoin enum<commit_after>// Copyright 2013-2015, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::fmt::{Error, Debug};
use std::ffi::CStr;

#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Status {
    Success = 0,

    NoMemory,
    InvalidRestore,
    InvalidPopGroup,
    NoCurrentPoint,
    InvalidMatrix,
    InvalidStatus,
    NullPointer,
    InvalidString,
    InvalidPathData,
    ReadError,
    WriteError,
    SurfaceFinished,
    SurfaceTypeMismatch,
    PatternTypeMismatch,
    InvalidContent,
    InvalidFormat,
    InvalidVisual,
    FileNotFound,
    InvalidDash,
    InvalidDscComment,
    InvalidIndex,
    ClipNotRepresentable,
    TempFileError,
    InvalidStride,
    FontTypeMismatch,
    UserFontImmutable,
    UserFontError,
    NegativeCount,
    InvalidClusters,
    InvalidSlant,
    InvalidWeight,
    InvalidSize,
    UserFontNotImplemented,
    DeviceTypeMismatch,
    DeviceError,
    InvalidMeshConstruction,
    DeviceFinished,
    // CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID has been used on at least one image but no
    // image provided `JBig2Global` (Since 1.14)
    // JBig2GlobalMissing,
    LastStatus
}

impl Debug for Status {
    fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> Result<(), Error> {
        unsafe {
            let char_ptr = super::cairo_status_to_string(*self);
            let tmp = String::from_utf8_lossy(CStr::from_ptr(char_ptr).to_bytes()).into_owned();

            tmp.fmt(formatter)
        }
    }
}

impl Status {
    pub fn ensure_valid(&self) {
        if *self != Status::Success {
            panic!("Cairo error {:?}", *self)
        }
    }
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Antialias {
    Default,

    /* method */
    None,
    Gray,
    Subpixel,

    /* hints */
    Fast,
    Good,
    Best
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FillRule {
    Winding,
    EvenOdd
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineCap {
    Butt,
    Round,
    Square
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineJoin {
    Miter,
    Round,
    Bevel
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Operator {
    Clear,

    Source,
    Over,
    In,
    Out,
    Atop,

    Dest,
    DestOver,
    DestIn,
    DestOut,
    DestAtop,

    Xor,
    Add,
    Saturate,

    Multiply,
    Screen,
    Overlay,
    Darken,
    Lighten,
    ColorDodge,
    ColorBurn,
    HardLight,
    SoftLight,
    Difference,
    Exclusion,
    HslHue,
    HslSaturation,
    HslColor,
    HslLuminosity
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PathDataType {
    MoveTo,
    LineTo,
    CurveTo,
    ClosePath
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Content {
    Color      = 0x1000,
    Alpha      = 0x2000,
    ColorAlpha = 0x3000
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Extend {
    None,
    Repeat,
    Reflect,
    Pad
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Filter {
    Fast,
    Good,
    Best,
    Nearest,
    Bilinear,
    Gaussian
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PatternType {
    Solid,
    Surface,
    LinearGradient,
    RadialGradient,
    #[cfg(feature = "v1_12")]
    Mesh,
    #[cfg(feature = "v1_12")]
    RasterSource
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontSlant {
    Normal,
    Italic,
    Oblique
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontWeight {
    Normal,
    Bold
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum TextClusterFlags {
    None     = 0x00000000,
    Backward = 0x00000001
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontType {
    FontTypeToy,
    FontTypeFt,
    FontTypeWin32,
    FontTypeQuartz,
    FontTypeUser
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum SubpixelOrder {
    Default,
    Rgb,
    Bgr,
    Vrgb,
    Vbgr
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintStyle {
    Default,
    None,
    Slight,
    Medium,
    Full
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintMetrics {
    Default,
    Off,
    On
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SurfaceType {
    Image,
    Pdf,
    Ps,
    Xlib,
    Xcb,
    Glitz,
    Quartz,
    Win32,
    BeOs,
    DirectFb,
    Svg,
    Os2,
    Win32Printing,
    QuartzImage,
    Script,
    Qt,
    Recording,
    Vg,
    Gl,
    Drm,
    Tee,
    Xml,
    Skia,
    Subsurface,
    Cogl,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Format {
    Invalid = -1,
    ARgb32 = 0,
    Rgb24 = 1,
    A8 = 2,
    A1 = 3,
    Rgb16_565 = 4,
    Rgb30 = 5,
}
<|endoftext|>


<commit_before>//! This module provides the logic necessary to implement a license prompt into
//! the hab CLI. When a user runs hab for the first time, they will be prompted
//! to accept the Chef license agreement. If they choose not to, then hab will
//! exit and they will be unable to use the tool.
//!
//! Choosing to accept the license will write a file to either
//! /hab/accepted-licenses/habitat (if run as root) or
//! ~/.hab/accepted-licenses/habitat. The presence of this file will
//! prevent further license prompts. The file itself contains various metadata
//! that we collect, but it's important to note that the contents of this file
//! are not strictly important. The important thing is the presence of the file.
//!
//! If a user was to read our code and pre-emptively create one of the above
//! files and leave its contents completely empty, that is sufficient for the
//! acceptance of the license. We will not overwrite their empty file, nor
//! will we care that it is empty.
//!
//! Because of this, the metadata that we store is not strictly necessary and
//! is more of a best effort scenario. If we can't collect all of it for
//! whatever reason, it's fine.
//!
//! More detailed information on the license spec is available at
//! https://github.com/chef/license-acceptance

use crate::{common::ui::{self,
                         UIReader,
                         UIWriter,
                         UI},
            error::{Error,
                    Result},
            hcore::{fs::{am_i_root,
                         FS_ROOT_PATH},
                    users::get_current_username}};
use chrono::prelude::*;
use dirs;
use serde_yaml;
use std::{fs::{self,
               File},
          io::Write,
          path::PathBuf};

const LICENSE_FILE_FORMAT_VERSION: &str = "1.0";

#[derive(Debug, Serialize, Deserialize)]
pub struct LicenseData {
    pub date_accepted: DateTime<Utc>,
    pub accepting_product: String,
    pub accepting_product_version: String,
    pub user: Option<String>,
    pub file_format: String,
}

impl LicenseData {
    pub fn new() -> Self {
        LicenseData { date_accepted: Utc::now(),
                      accepting_product: String::from("hab"),
                      accepting_product_version: super::VERSION.to_string(),
                      user: get_current_username(),
                      file_format: String::from(LICENSE_FILE_FORMAT_VERSION), }
    }
}

pub fn check_for_license_acceptance_and_prompt(ui: &mut UI) -> Result<()> {
    if license_exists() {
        return Ok(());
    }

    ui.heading("+---------------------------------------------+")?;
    ui.heading("            Chef License Acceptance")?;
    ui.br()?;
    ui.info("Before you can continue, 1 product license must be accepted.")?;
    ui.info("View the license at https://www.chef.io/end-user-license-agreement")?;
    ui.br()?;
    ui.info("License that needs accepting:")?;
    ui.br()?;
    ui.info("  * Habitat")?;
    ui.br()?;

    if ui.prompt_yes_no("Do you accept the 1 product license?", Some(false))? {
        accept_license(ui)
    } else {
        ui.br()?;
        ui.info("If you do not accept this license you will not be able to use Chef products.")?;
        ui.br()?;

        if ui.prompt_yes_no("Do you accept the 1 product license?", Some(false))? {
            accept_license(ui)
        } else {
            ui.br()?;
            ui.heading("+---------------------------------------------+")?;
            Err(Error::LicenseNotAccepted)
        }
    }
}

pub fn accept_license(ui: &mut UI) -> Result<()> {
    if license_exists() {
        ui.info("You have already accepted the license.")?;
        return Ok(());
    }

    ui.br()?;
    ui.info("Accepting 1 product license...")?;

    let license = LicenseData::new();
    let content = serde_yaml::to_string(&license)?;
    fs::create_dir_all(license_path())?;
    let mut file = File::create(license_file())?;
    file.write_all(content.as_bytes())?;

    ui.status(ui::Status::Custom(ui::Glyph::CheckMark, String::from("")),
              "1 product license accepted.")?;
    ui.br()?;
    ui.heading("+---------------------------------------------+")?;

    Ok(())
}

fn license_path() -> PathBuf {
    let hab_dir = if am_i_root() {
        PathBuf::from(&*FS_ROOT_PATH).join("hab")
    } else if let Some(home) = dirs::home_dir() {
        home.join(".hab")
    } else {
        panic!("No home directory available. Unable to find a suitable place to write a license \
                file.");
    };

    hab_dir.join("accepted-licenses")
}

fn license_file() -> PathBuf { license_path().join("habitat") }

fn license_exists() -> bool { license_file().is_file() }
<commit_msg>Add support for license acceptance via env vars<commit_after>//! This module provides the logic necessary to implement a license prompt into
//! the hab CLI. When a user runs hab for the first time, they will be prompted
//! to accept the Chef license agreement. If they choose not to, then hab will
//! exit and they will be unable to use the tool.
//!
//! Choosing to accept the license will write a file to either
//! /hab/accepted-licenses/habitat (if run as root) or
//! ~/.hab/accepted-licenses/habitat. The presence of this file will
//! prevent further license prompts. The file itself contains various metadata
//! that we collect, but it's important to note that the contents of this file
//! are not strictly important. The important thing is the presence of the file.
//!
//! If a user was to read our code and pre-emptively create one of the above
//! files and leave its contents completely empty, that is sufficient for the
//! acceptance of the license. We will not overwrite their empty file, nor
//! will we care that it is empty.
//!
//! Because of this, the metadata that we store is not strictly necessary and
//! is more of a best effort scenario. If we can't collect all of it for
//! whatever reason, it's fine.
//!
//! Additionally, there are 2 environment variables that are supported to
//! constitute license acceptance. Setting HAB_LICENSE to "accept" will
//! accept the license and persist the file and setting HAB_LICENSE to
//! "accept-no-persist" will accept the license but not persist the
//! acceptance.
//!
//! More detailed information on the license spec is available at
//! https://github.com/chef/license-acceptance

use crate::{common::ui::{self,
                         UIReader,
                         UIWriter,
                         UI},
            error::{Error,
                    Result},
            hcore::{fs::{am_i_root,
                         FS_ROOT_PATH},
                    users::get_current_username}};
use chrono::prelude::*;
use dirs;
use serde_yaml;
use std::{env,
          fs::{self,
               File},
          io::Write,
          path::PathBuf};

const LICENSE_FILE_FORMAT_VERSION: &str = "1.0";
const LICENSE_ACCEPT_ENVVAR: &str = "HAB_LICENSE";

#[derive(Debug, Serialize, Deserialize)]
pub struct LicenseData {
    pub date_accepted: DateTime<Utc>,
    pub accepting_product: String,
    pub accepting_product_version: String,
    pub user: Option<String>,
    pub file_format: String,
}

impl LicenseData {
    pub fn new() -> Self {
        LicenseData { date_accepted: Utc::now(),
                      accepting_product: String::from("hab"),
                      accepting_product_version: super::VERSION.to_string(),
                      user: get_current_username(),
                      file_format: String::from(LICENSE_FILE_FORMAT_VERSION), }
    }
}

pub fn check_for_license_acceptance_and_prompt(ui: &mut UI) -> Result<()> {
    if license_exists() || env_var_present()? {
        return Ok(());
    }

    ui.heading("+---------------------------------------------+")?;
    ui.heading("            Chef License Acceptance")?;
    ui.br()?;
    ui.info("Before you can continue, 1 product license must be accepted.")?;
    ui.info("View the license at https://www.chef.io/end-user-license-agreement")?;
    ui.br()?;
    ui.info("License that needs accepting:")?;
    ui.br()?;
    ui.info("  * Habitat")?;
    ui.br()?;

    if ui.prompt_yes_no("Do you accept the 1 product license?", Some(false))? {
        accept_license(ui)
    } else {
        ui.br()?;
        ui.info("If you do not accept this license you will not be able to use Chef products.")?;
        ui.br()?;

        if ui.prompt_yes_no("Do you accept the 1 product license?", Some(false))? {
            accept_license(ui)
        } else {
            ui.br()?;
            ui.heading("+---------------------------------------------+")?;
            Err(Error::LicenseNotAccepted)
        }
    }
}

pub fn accept_license(ui: &mut UI) -> Result<()> {
    if license_exists() {
        ui.info("You have already accepted the license.")?;
        return Ok(());
    }

    ui.br()?;
    ui.info("Accepting 1 product license...")?;

    write_license_file()?;

    ui.status(ui::Status::Custom(ui::Glyph::CheckMark, String::from("")),
              "1 product license accepted.")?;
    ui.br()?;
    ui.heading("+---------------------------------------------+")?;

    Ok(())
}

fn license_path() -> PathBuf {
    let hab_dir = if am_i_root() {
        PathBuf::from(&*FS_ROOT_PATH).join("hab")
    } else if let Some(home) = dirs::home_dir() {
        home.join(".hab")
    } else {
        panic!("No home directory available. Unable to find a suitable place to write a license \
                file.");
    };

    hab_dir.join("accepted-licenses")
}

fn env_var_present() -> Result<bool> {
    match env::var(LICENSE_ACCEPT_ENVVAR) {
        Ok(val) => {
            if &val == "accept" {
                write_license_file()?;
                Ok(true)
            } else if &val == "accept-no-persist" {
                Ok(true)
            } else {
                Ok(false)
            }
        }
        Err(_) => Ok(false),
    }
}

fn write_license_file() -> Result<()> {
    let license = LicenseData::new();
    let content = serde_yaml::to_string(&license)?;
    fs::create_dir_all(license_path())?;
    let mut file = File::create(license_file())?;
    file.write_all(content.as_bytes())?;
    Ok(())
}

fn license_file() -> PathBuf { license_path().join("habitat") }

fn license_exists() -> bool { license_file().is_file() }
<|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/. */

use resource_task::{ProgressMsg, Metadata, Payload, Done, LoaderTask, start_sending};

use std::io;
use std::io::File;
use servo_util::task::spawn_named;

static READ_SIZE: uint = 8192;

fn read_all(reader: &mut io::Stream, progress_chan: &Sender<ProgressMsg>)
        -> Result<(), String> {
    loop {
        let mut buf = vec!();
        match reader.push_at_least(READ_SIZE, READ_SIZE, &mut buf) {
            Ok(_) => progress_chan.send(Payload(buf)),
            Err(e) => match e.kind {
                io::EndOfFile => {
                    if buf.len() > 0 {
                        progress_chan.send(Payload(buf));
                    }
                    return Ok(());
                }
                _ => return Err(e.desc.to_string()),
            }
        }
    }
}

pub fn factory() -> LoaderTask {
    let f: LoaderTask = proc(load_data, start_chan) {
        let url = load_data.url;
        assert!("file" == url.scheme.as_slice());
        let progress_chan = start_sending(start_chan, Metadata::default(url.clone()));
        spawn_named("file_loader", proc() {
            let file_path: Path = url.to_file_path().unwrap();
            match File::open_mode(&Path::new(file_path), io::Open, io::Read) {
                Ok(ref mut reader) => {
                    let res = read_all(reader as &mut io::Stream, &progress_chan);
                    progress_chan.send(Done(res));
                }
                Err(e) => {
                    progress_chan.send(Done(Err(e.desc.to_string())));
                }
            };
        });
    };
    f
}
<commit_msg>Return error when unable to create a file path from url.<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/. */

use resource_task::{ProgressMsg, Metadata, Payload, Done, LoaderTask, start_sending};

use std::io;
use std::io::File;
use servo_util::task::spawn_named;

static READ_SIZE: uint = 8192;

fn read_all(reader: &mut io::Stream, progress_chan: &Sender<ProgressMsg>)
        -> Result<(), String> {
    loop {
        let mut buf = vec!();
        match reader.push_at_least(READ_SIZE, READ_SIZE, &mut buf) {
            Ok(_) => progress_chan.send(Payload(buf)),
            Err(e) => match e.kind {
                io::EndOfFile => {
                    if buf.len() > 0 {
                        progress_chan.send(Payload(buf));
                    }
                    return Ok(());
                }
                _ => return Err(e.desc.to_string()),
            }
        }
    }
}

pub fn factory() -> LoaderTask {
    let f: LoaderTask = proc(load_data, start_chan) {
        let url = load_data.url;
        assert!("file" == url.scheme.as_slice());
        let progress_chan = start_sending(start_chan, Metadata::default(url.clone()));
        spawn_named("file_loader", proc() {
            let file_path: Result<Path, ()> = url.to_file_path();
            match file_path {
                Ok(file_path) => {
                    match File::open_mode(&Path::new(file_path), io::Open, io::Read) {
                        Ok(ref mut reader) => {
                            let res = read_all(reader as &mut io::Stream, &progress_chan);
                            progress_chan.send(Done(res));
                        }
                        Err(e) => {
                            progress_chan.send(Done(Err(e.desc.to_string())));
                        }
                    }
                }
                Err(_) => {
                    progress_chan.send(Done(Err(url.to_string())));
                }
            }
        });
    };
    f
}
<|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/. */

use devtools_traits::AttrInfo;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AttrBinding::{self, AttrMethods};
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom, RootedReference};
use dom::bindings::str::DOMString;
use dom::customelementregistry::CallbackReaction;
use dom::element::{AttributeMutation, Element};
use dom::mutationobserver::{Mutation, MutationObserver};
use dom::node::Node;
use dom::virtualmethods::vtable_for;
use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{Prefix, LocalName, Namespace};
use script_thread::ScriptThread;
use servo_atoms::Atom;
use std::borrow::ToOwned;
use std::cell::Ref;
use std::mem;
use style::attr::{AttrIdentifier, AttrValue};

// https://dom.spec.whatwg.org/#interface-attr
#[dom_struct]
pub struct Attr {
    reflector_: Reflector,
    identifier: AttrIdentifier,
    value: DomRefCell<AttrValue>,

    /// the element that owns this attribute.
    owner: MutNullableDom<Element>,
}

impl Attr {
    fn new_inherited(local_name: LocalName,
                     value: AttrValue,
                     name: LocalName,
                     namespace: Namespace,
                     prefix: Option<Prefix>,
                     owner: Option<&Element>)
                     -> Attr {
        Attr {
            reflector_: Reflector::new(),
            identifier: AttrIdentifier {
                local_name: local_name,
                name: name,
                namespace: namespace,
                prefix: prefix,
            },
            value: DomRefCell::new(value),
            owner: MutNullableDom::new(owner),
        }
    }

    pub fn new(window: &Window,
               local_name: LocalName,
               value: AttrValue,
               name: LocalName,
               namespace: Namespace,
               prefix: Option<Prefix>,
               owner: Option<&Element>)
               -> DomRoot<Attr> {
        reflect_dom_object(
            Box::new(Attr::new_inherited(
                local_name,
                value,
                name,
                namespace,
                prefix,
                owner
            )),
            window,
            AttrBinding::Wrap
        )
    }

    #[inline]
    pub fn name(&self) -> &LocalName {
        &self.identifier.name
    }

    #[inline]
    pub fn namespace(&self) -> &Namespace {
        &self.identifier.namespace
    }

    #[inline]
    pub fn prefix(&self) -> Option<&Prefix> {
        self.identifier.prefix.as_ref()
    }
}

impl AttrMethods for Attr {
    // https://dom.spec.whatwg.org/#dom-attr-localname
    fn LocalName(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&**self.local_name())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn Value(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from AttrValue to DOMString
        DOMString::from(&**self.value())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn SetValue(&self, value: DOMString) {
        if let Some(owner) = self.owner() {
            let value = owner.parse_attribute(&self.identifier.namespace,
                                              self.local_name(),
                                              value);
            self.set_value(value, &owner);
        } else {
            *self.value.borrow_mut() = AttrValue::String(value.into());
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn TextContent(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn SetTextContent(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn NodeValue(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn SetNodeValue(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-name
    fn Name(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&*self.identifier.name)
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodename
    fn NodeName(&self) -> DOMString {
        self.Name()
    }

    // https://dom.spec.whatwg.org/#dom-attr-namespaceuri
    fn GetNamespaceURI(&self) -> Option<DOMString> {
        match self.identifier.namespace {
            ns!() => None,
            ref url => Some(DOMString::from(&**url)),
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-prefix
    fn GetPrefix(&self) -> Option<DOMString> {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        self.prefix().map(|p| DOMString::from(&**p))
    }

    // https://dom.spec.whatwg.org/#dom-attr-ownerelement
    fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
        self.owner()
    }

    // https://dom.spec.whatwg.org/#dom-attr-specified
    fn Specified(&self) -> bool {
        true // Always returns true
    }
}

impl Attr {
    pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
        let name = self.local_name().clone();
        let namespace = self.namespace().clone();
        let old_value = DOMString::from(&**self.value());
        let new_value = DOMString::from(&*value);
        let mutation = Mutation::Attribute {
            name: name.clone(),
            namespace: namespace.clone(),
            old_value: old_value.clone(),
        };

        MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);

        if owner.get_custom_element_definition().is_some() {
            let reaction = CallbackReaction::AttributeChanged(name, Some(old_value), Some(new_value), namespace);
            ScriptThread::enqueue_callback_reaction(owner, reaction, None);
        }

        assert!(Some(owner) == self.owner().r());
        owner.will_mutate_attr(self);
        self.swap_value(&mut value);
        if self.identifier.namespace == ns!() {
            vtable_for(owner.upcast())
                .attribute_mutated(self, AttributeMutation::Set(Some(&value)));
        }
    }

    /// Used to swap the attribute's value without triggering mutation events
    pub fn swap_value(&self, value: &mut AttrValue) {
        mem::swap(&mut *self.value.borrow_mut(), value);
    }

    pub fn identifier(&self) -> &AttrIdentifier {
        &self.identifier
    }

    pub fn value(&self) -> Ref<AttrValue> {
        self.value.borrow()
    }

    pub fn local_name(&self) -> &LocalName {
        &self.identifier.local_name
    }

    /// Sets the owner element. Should be called after the attribute is added
    /// or removed from its older parent.
    pub fn set_owner(&self, owner: Option<&Element>) {
        let ns = &self.identifier.namespace;
        match (self.owner(), owner) {
            (Some(old), None) => {
                // Already gone from the list of attributes of old owner.
                assert!(old.get_attribute(&ns, &self.identifier.local_name).r() != Some(self))
            }
            (Some(old), Some(new)) => assert!(&*old == new),
            _ => {},
        }
        self.owner.set(owner);
    }

    pub fn owner(&self) -> Option<DomRoot<Element>> {
        self.owner.get()
    }

    pub fn summarize(&self) -> AttrInfo {
        AttrInfo {
            namespace: (*self.identifier.namespace).to_owned(),
            name: String::from(self.Name()),
            value: String::from(self.Value()),
        }
    }
}

#[allow(unsafe_code)]
pub trait AttrHelpersForLayout {
    unsafe fn value_forever(&self) -> &'static AttrValue;
    unsafe fn value_ref_forever(&self) -> &'static str;
    unsafe fn value_atom_forever(&self) -> Option<Atom>;
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]>;
    unsafe fn local_name_atom_forever(&self) -> LocalName;
    unsafe fn value_for_layout(&self) -> &AttrValue;
}

#[allow(unsafe_code)]
impl AttrHelpersForLayout for LayoutDom<Attr> {
    #[inline]
    unsafe fn value_forever(&self) -> &'static AttrValue {
        // This transmute is used to cheat the lifetime restriction.
        mem::transmute::<&AttrValue, &AttrValue>((*self.unsafe_get()).value.borrow_for_layout())
    }

    #[inline]
    unsafe fn value_ref_forever(&self) -> &'static str {
        &**self.value_forever()
    }

    #[inline]
    unsafe fn value_atom_forever(&self) -> Option<Atom> {
        let value = (*self.unsafe_get()).value.borrow_for_layout();
        match *value {
            AttrValue::Atom(ref val) => Some(val.clone()),
            _ => None,
        }
    }

    #[inline]
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]> {
        // This transmute is used to cheat the lifetime restriction.
        match *self.value_forever() {
            AttrValue::TokenList(_, ref tokens) => Some(tokens),
            _ => None,
        }
    }

    #[inline]
    unsafe fn local_name_atom_forever(&self) -> LocalName {
        (*self.unsafe_get()).identifier.local_name.clone()
    }

    #[inline]
    unsafe fn value_for_layout(&self) -> &AttrValue {
        (*self.unsafe_get()).value.borrow_for_layout()
    }
}
<commit_msg>Use specific assertion for DOM attr<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/. */

use devtools_traits::AttrInfo;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AttrBinding::{self, AttrMethods};
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom, RootedReference};
use dom::bindings::str::DOMString;
use dom::customelementregistry::CallbackReaction;
use dom::element::{AttributeMutation, Element};
use dom::mutationobserver::{Mutation, MutationObserver};
use dom::node::Node;
use dom::virtualmethods::vtable_for;
use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{Prefix, LocalName, Namespace};
use script_thread::ScriptThread;
use servo_atoms::Atom;
use std::borrow::ToOwned;
use std::cell::Ref;
use std::mem;
use style::attr::{AttrIdentifier, AttrValue};

// https://dom.spec.whatwg.org/#interface-attr
#[dom_struct]
pub struct Attr {
    reflector_: Reflector,
    identifier: AttrIdentifier,
    value: DomRefCell<AttrValue>,

    /// the element that owns this attribute.
    owner: MutNullableDom<Element>,
}

impl Attr {
    fn new_inherited(local_name: LocalName,
                     value: AttrValue,
                     name: LocalName,
                     namespace: Namespace,
                     prefix: Option<Prefix>,
                     owner: Option<&Element>)
                     -> Attr {
        Attr {
            reflector_: Reflector::new(),
            identifier: AttrIdentifier {
                local_name: local_name,
                name: name,
                namespace: namespace,
                prefix: prefix,
            },
            value: DomRefCell::new(value),
            owner: MutNullableDom::new(owner),
        }
    }

    pub fn new(window: &Window,
               local_name: LocalName,
               value: AttrValue,
               name: LocalName,
               namespace: Namespace,
               prefix: Option<Prefix>,
               owner: Option<&Element>)
               -> DomRoot<Attr> {
        reflect_dom_object(
            Box::new(Attr::new_inherited(
                local_name,
                value,
                name,
                namespace,
                prefix,
                owner
            )),
            window,
            AttrBinding::Wrap
        )
    }

    #[inline]
    pub fn name(&self) -> &LocalName {
        &self.identifier.name
    }

    #[inline]
    pub fn namespace(&self) -> &Namespace {
        &self.identifier.namespace
    }

    #[inline]
    pub fn prefix(&self) -> Option<&Prefix> {
        self.identifier.prefix.as_ref()
    }
}

impl AttrMethods for Attr {
    // https://dom.spec.whatwg.org/#dom-attr-localname
    fn LocalName(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&**self.local_name())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn Value(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from AttrValue to DOMString
        DOMString::from(&**self.value())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn SetValue(&self, value: DOMString) {
        if let Some(owner) = self.owner() {
            let value = owner.parse_attribute(&self.identifier.namespace,
                                              self.local_name(),
                                              value);
            self.set_value(value, &owner);
        } else {
            *self.value.borrow_mut() = AttrValue::String(value.into());
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn TextContent(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn SetTextContent(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn NodeValue(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn SetNodeValue(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-name
    fn Name(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&*self.identifier.name)
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodename
    fn NodeName(&self) -> DOMString {
        self.Name()
    }

    // https://dom.spec.whatwg.org/#dom-attr-namespaceuri
    fn GetNamespaceURI(&self) -> Option<DOMString> {
        match self.identifier.namespace {
            ns!() => None,
            ref url => Some(DOMString::from(&**url)),
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-prefix
    fn GetPrefix(&self) -> Option<DOMString> {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        self.prefix().map(|p| DOMString::from(&**p))
    }

    // https://dom.spec.whatwg.org/#dom-attr-ownerelement
    fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
        self.owner()
    }

    // https://dom.spec.whatwg.org/#dom-attr-specified
    fn Specified(&self) -> bool {
        true // Always returns true
    }
}

impl Attr {
    pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
        let name = self.local_name().clone();
        let namespace = self.namespace().clone();
        let old_value = DOMString::from(&**self.value());
        let new_value = DOMString::from(&*value);
        let mutation = Mutation::Attribute {
            name: name.clone(),
            namespace: namespace.clone(),
            old_value: old_value.clone(),
        };

        MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);

        if owner.get_custom_element_definition().is_some() {
            let reaction = CallbackReaction::AttributeChanged(name, Some(old_value), Some(new_value), namespace);
            ScriptThread::enqueue_callback_reaction(owner, reaction, None);
        }

        assert_eq!(Some(owner), self.owner().r());
        owner.will_mutate_attr(self);
        self.swap_value(&mut value);
        if self.identifier.namespace == ns!() {
            vtable_for(owner.upcast())
                .attribute_mutated(self, AttributeMutation::Set(Some(&value)));
        }
    }

    /// Used to swap the attribute's value without triggering mutation events
    pub fn swap_value(&self, value: &mut AttrValue) {
        mem::swap(&mut *self.value.borrow_mut(), value);
    }

    pub fn identifier(&self) -> &AttrIdentifier {
        &self.identifier
    }

    pub fn value(&self) -> Ref<AttrValue> {
        self.value.borrow()
    }

    pub fn local_name(&self) -> &LocalName {
        &self.identifier.local_name
    }

    /// Sets the owner element. Should be called after the attribute is added
    /// or removed from its older parent.
    pub fn set_owner(&self, owner: Option<&Element>) {
        let ns = &self.identifier.namespace;
        match (self.owner(), owner) {
            (Some(old), None) => {
                // Already gone from the list of attributes of old owner.
                assert!(old.get_attribute(&ns, &self.identifier.local_name).r() != Some(self))
            }
            (Some(old), Some(new)) => assert_eq!(&*old, new),
            _ => {},
        }
        self.owner.set(owner);
    }

    pub fn owner(&self) -> Option<DomRoot<Element>> {
        self.owner.get()
    }

    pub fn summarize(&self) -> AttrInfo {
        AttrInfo {
            namespace: (*self.identifier.namespace).to_owned(),
            name: String::from(self.Name()),
            value: String::from(self.Value()),
        }
    }
}

#[allow(unsafe_code)]
pub trait AttrHelpersForLayout {
    unsafe fn value_forever(&self) -> &'static AttrValue;
    unsafe fn value_ref_forever(&self) -> &'static str;
    unsafe fn value_atom_forever(&self) -> Option<Atom>;
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]>;
    unsafe fn local_name_atom_forever(&self) -> LocalName;
    unsafe fn value_for_layout(&self) -> &AttrValue;
}

#[allow(unsafe_code)]
impl AttrHelpersForLayout for LayoutDom<Attr> {
    #[inline]
    unsafe fn value_forever(&self) -> &'static AttrValue {
        // This transmute is used to cheat the lifetime restriction.
        mem::transmute::<&AttrValue, &AttrValue>((*self.unsafe_get()).value.borrow_for_layout())
    }

    #[inline]
    unsafe fn value_ref_forever(&self) -> &'static str {
        &**self.value_forever()
    }

    #[inline]
    unsafe fn value_atom_forever(&self) -> Option<Atom> {
        let value = (*self.unsafe_get()).value.borrow_for_layout();
        match *value {
            AttrValue::Atom(ref val) => Some(val.clone()),
            _ => None,
        }
    }

    #[inline]
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]> {
        // This transmute is used to cheat the lifetime restriction.
        match *self.value_forever() {
            AttrValue::TokenList(_, ref tokens) => Some(tokens),
            _ => None,
        }
    }

    #[inline]
    unsafe fn local_name_atom_forever(&self) -> LocalName {
        (*self.unsafe_get()).identifier.local_name.clone()
    }

    #[inline]
    unsafe fn value_for_layout(&self) -> &AttrValue {
        (*self.unsafe_get()).value.borrow_for_layout()
    }
}
<|endoftext|>