<commit_before>pub use self::tokens::{Doctype, Attributes, TagKind, StartTag, EndTag, Tag, Token};
pub use self::tokens::{DoctypeToken, TagToken, CommentToken, CharacterToken};

use std::util::replace;

mod tokens;
mod states;

pub trait TokenSink {
    fn process_token(&mut self, token: Token);
}

fn ascii_letter(c: char) -> Option<char> {
    c.to_ascii_opt()
        .filtered(|a| a.is_alpha())
        .map(|a| a.to_lower().to_char())
}

pub struct Tokenizer<'sink, Sink> {
    priv sink: &'sink mut Sink,
    priv state: states::State,

    // FIXME: The state machine guarantees the tag exists when
    // we need it, so we could eliminate the Option overhead.
    // Leaving it as Option for now, to find bugs.
    priv current_tag: Option<Tag>,

    /// Last start tag name, for use in checking "appropriate end tag".
    priv last_start_tag_name: Option<~str>,

    /// The "temporary buffer" mentioned in the spec.
    priv temp_buf: ~str
}

#[deriving(Eq)]
enum ConsumeCharResult {
    Reconsume,
    Finished,
}

impl<'sink, Sink: TokenSink> Tokenizer<'sink, Sink> {
    pub fn new(sink: &'sink mut Sink) -> Tokenizer<'sink, Sink> {
        Tokenizer {
            sink: sink,
            state: states::Data,
            current_tag: None,
            last_start_tag_name: None,
            temp_buf: ~"",
        }
    }

    pub fn feed(&mut self, input: &str) {
        debug!("feeding {:s}", input);
        let mut it = input.chars(); //.peekable();
        loop {
            match self.state {
                // These states do something other than consume a single character.
                states::CharacterReferenceInData | states::CharacterReferenceInRcdata
                | states::CharacterReferenceInAttributeValue | states::BogusComment
                | states::MarkupDeclarationOpen | states::CdataSection => {
                    fail!("FIXME: state {:?} not implemented", self.state);
                }

                _ => {
                    match it.next() {
                        None => return,
                        Some(c) => {
                            while self.process_char(c) == Reconsume {
                                // reconsume
                            }
                        }
                    }
                }
            }
        }
    }

    fn emit_char(&mut self, c: char) {
        self.sink.process_token(CharacterToken(c));
    }

    fn emit_current_tag(&mut self) {
        let tag = self.current_tag.take().unwrap();
        match tag.kind {
            StartTag => self.last_start_tag_name = Some(tag.name.clone()),
            _ => ()
        }
        self.sink.process_token(TagToken(tag));
    }

    fn emit_temp_buf(&mut self) {
        let buf = replace(&mut self.temp_buf, ~""); // FIXME

        // FIXME: add a multiple-character token
        for c in buf.chars() {
            self.emit_char(c);
        }
    }

    fn create_tag(&mut self, kind: TagKind, c: char) {
        assert!(self.current_tag.is_none());
        let mut t = Tag::new(kind);
        t.name.push_char(c);
        self.current_tag = Some(t);
    }

    fn append_to_tag_name(&mut self, c: char) {
        self.current_tag.get_mut_ref().name.push_char(c);
    }

    fn have_appropriate_end_tag(&self) -> bool {
        match (self.last_start_tag_name.as_ref(), self.current_tag.as_ref()) {
            (Some(last), Some(tag)) =>
                (tag.kind == EndTag) && (tag.name.as_slice() == last.as_slice()),
            _ => false
        }
    }
}

// A little DSL for common state machine behaviors.
macro_rules! go (
    ( to $state:ident $($rest:tt)* ) => ({
        self.state = states::$state;
        go!($($rest)*);
    });

    ( emit $c:expr $($rest:tt)* ) => ({
        self.emit_char($c);
        go!($($rest)*);
    });

    ( error $($rest:tt)* ) => ({
        error!("Parse error: saw {:?} in state {:?}", c, self.state)
        go!($($rest)*);
    });

    ( create_tag $kind:expr $c:expr $($rest:tt)* ) => ({
        self.create_tag($kind, $c);
        go!($($rest)*);
    });

    ( append_tag $c:expr $($rest:tt)* ) => ({
        self.append_to_tag_name($c);
        go!($($rest)*);
    });

    ( emit_tag $($rest:tt)* ) => ({
        self.emit_current_tag();
        go!($($rest)*);
    });

    ( clear_temp $($rest:tt)* ) => ({
        self.temp_buf = ~""; // FIXME: don't allocate if already empty
        go!($($rest)*);
    });

    ( append_temp $c:expr $($rest:tt)* ) => ({
        self.temp_buf.push_char($c);
        go!($($rest)*);
    });

    ( emit_temp $($rest:tt)* ) => ({
        self.emit_temp_buf();
        go!($($rest)*);
    });

    ( reconsume ) => ({
        return Reconsume;
    });

    // FIXME: better name
    ( finish ) => ({
        return Finished;
    });

    () => ({});
)

impl<'sink, Sink: TokenSink> Tokenizer<'sink, Sink> {
    // FIXME: explicitly represent the EOF character?
    // For now the plan is to handle EOF in a separate match.
    fn process_char(&mut self, c: char) -> ConsumeCharResult {
        debug!("Processing {:?} in state {:?}", c, self.state);
        match self.state {
            states::Data => match c {
                '&'  => go!(to CharacterReferenceInData),
                '<'  => go!(to TagOpen),
                '\0' => go!(error emit '\0'),
                _    => go!(emit c),
            },

            states::Rcdata => match c {
                '&'  => go!(to CharacterReferenceInRcdata),
                '<'  => go!(to RcdataLessThanSign),
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::Rawtext => match c {
                '<'  => go!(to RawtextLessThanSign),
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::ScriptData => match c {
                '<'  => go!(to ScriptDataLessThanSign),
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::Plaintext => match c {
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::TagOpen => match c {
                '!' => go!(to MarkupDeclarationOpen),
                '/' => go!(to EndTagOpen),
                '?' => go!(error to BogusComment),
                _ => match ascii_letter(c) {
                    Some(cl) => go!(create_tag StartTag cl to TagName),
                    None     => go!(error emit '<' to Data reconsume)
                }
            },

            states::EndTagOpen => match c {
                '>' => go!(error to Data),
                _ => match ascii_letter(c) {
                    Some(cl) => {
                        self.create_tag(EndTag, cl);
                        go!(to TagName);
                    }
                    None => go!(error to BogusComment)
                }
            },

            states::TagName => match c {
                '\t' | '\n' | '\x0C' | ' '
                     => go!(to BeforeAttributeName),
                '/'  => go!(to SelfClosingStartTag),
                '>'  => go!(emit_tag to Data),
                '\0' => go!(error append_tag '\ufffd'),
                _    => go!(append_tag (ascii_letter(c).unwrap_or(c))),
            },

            states::RcdataLessThanSign => match c {
                '/' => go!(clear_temp to RcdataEndTagOpen),
                _   => go!(to Rcdata emit '<' reconsume),
            },

            states::RcdataEndTagOpen => match ascii_letter(c) {
                Some(cl) => go!(create_tag EndTag cl append_temp c to RcdataEndTagName),
                None     => go!(to Rcdata emit '<' emit '/' reconsume)
            },

            states::RcdataEndTagName => {
                if self.have_appropriate_end_tag() {
                    match c {
                        '\t' | '\n' | '\x0C' | ' '
                            => go!(to BeforeAttributeName finish),
                        '/' => go!(to SelfClosingStartTag finish),
                        '>' => go!(emit_tag to Data finish),
                        _ => (),
                    }
                }

                match ascii_letter(c) {
                    Some(cl) => go!(append_tag cl append_temp c),
                    None     => go!(emit '<' emit '/' emit_temp to Rcdata reconsume),
                }
            },

            states::RawtextLessThanSign => match c {
                '/' => go!(clear_temp to RawtextEndTagOpen),
                _   => go!(to Rawtext emit '<' reconsume),
            },

            states::RawtextEndTagOpen => match ascii_letter(c) {
                Some(cl) => go!(create_tag EndTag cl append_temp c to RawtextEndTagName),
                None     => go!(to Rawtext emit '<' emit '/' reconsume),
            },

            s => fail!("FIXME: state {:?} not implemented", s),
        }

        Finished

    }
}
<commit_msg>more cleanup<commit_after>pub use self::tokens::{Doctype, Attributes, TagKind, StartTag, EndTag, Tag, Token};
pub use self::tokens::{DoctypeToken, TagToken, CommentToken, CharacterToken};

use std::util::replace;

mod tokens;
mod states;

pub trait TokenSink {
    fn process_token(&mut self, token: Token);
}

fn ascii_letter(c: char) -> Option<char> {
    c.to_ascii_opt()
        .filtered(|a| a.is_alpha())
        .map(|a| a.to_lower().to_char())
}

pub struct Tokenizer<'sink, Sink> {
    priv sink: &'sink mut Sink,
    priv state: states::State,

    // FIXME: The state machine guarantees the tag exists when
    // we need it, so we could eliminate the Option overhead.
    // Leaving it as Option for now, to find bugs.
    priv current_tag: Option<Tag>,

    /// Last start tag name, for use in checking "appropriate end tag".
    priv last_start_tag_name: Option<~str>,

    /// The "temporary buffer" mentioned in the spec.
    priv temp_buf: ~str
}

#[deriving(Eq)]
enum ConsumeCharResult {
    Reconsume,
    Finished,
}

impl<'sink, Sink: TokenSink> Tokenizer<'sink, Sink> {
    pub fn new(sink: &'sink mut Sink) -> Tokenizer<'sink, Sink> {
        Tokenizer {
            sink: sink,
            state: states::Data,
            current_tag: None,
            last_start_tag_name: None,
            temp_buf: ~"",
        }
    }

    pub fn feed(&mut self, input: &str) {
        debug!("feeding {:s}", input);
        let mut it = input.chars(); //.peekable();
        loop {
            match self.state {
                // These states do something other than consume a single character.
                states::CharacterReferenceInData | states::CharacterReferenceInRcdata
                | states::CharacterReferenceInAttributeValue | states::BogusComment
                | states::MarkupDeclarationOpen | states::CdataSection => {
                    fail!("FIXME: state {:?} not implemented", self.state);
                }

                _ => match it.next() {
                    None => return,
                    Some(c) => {
                        while self.process_char(c) == Reconsume {
                            // reconsume
                        }
                    }
                }
            }
        }
    }

    fn parse_error(&self, c: char) {
        error!("Parse error: saw {:?} in state {:?}", c, self.state);
    }

    fn emit_char(&mut self, c: char) {
        self.sink.process_token(CharacterToken(c));
    }

    fn emit_current_tag(&mut self) {
        let tag = self.current_tag.take().unwrap();
        match tag.kind {
            StartTag => self.last_start_tag_name = Some(tag.name.clone()),
            _ => ()
        }
        self.sink.process_token(TagToken(tag));
    }

    fn emit_temp_buf(&mut self) {
        let buf = replace(&mut self.temp_buf, ~""); // FIXME

        // FIXME: add a multiple-character token
        for c in buf.chars() {
            self.emit_char(c);
        }
    }

    fn create_tag(&mut self, kind: TagKind, c: char) {
        assert!(self.current_tag.is_none());
        let mut t = Tag::new(kind);
        t.name.push_char(c);
        self.current_tag = Some(t);
    }

    fn append_to_tag_name(&mut self, c: char) {
        self.current_tag.get_mut_ref().name.push_char(c);
    }

    fn have_appropriate_end_tag(&self) -> bool {
        match (self.last_start_tag_name.as_ref(), self.current_tag.as_ref()) {
            (Some(last), Some(tag)) =>
                (tag.kind == EndTag) && (tag.name.as_slice() == last.as_slice()),
            _ => false
        }
    }
}

// A little DSL for common state machine behaviors.
macro_rules! go (
    ( to $state:ident $($rest:tt)* ) => ({
        self.state = states::$state;
        go!($($rest)*);
    });

    ( emit $c:expr $($rest:tt)* ) => ({
        self.emit_char($c);
        go!($($rest)*);
    });

    ( error $($rest:tt)* ) => ({
        self.parse_error(c); // CAPTURE
        go!($($rest)*);
    });

    ( create_tag $kind:expr $c:expr $($rest:tt)* ) => ({
        self.create_tag($kind, $c);
        go!($($rest)*);
    });

    ( append_tag $c:expr $($rest:tt)* ) => ({
        self.append_to_tag_name($c);
        go!($($rest)*);
    });

    ( emit_tag $($rest:tt)* ) => ({
        self.emit_current_tag();
        go!($($rest)*);
    });

    ( clear_temp $($rest:tt)* ) => ({
        self.temp_buf = ~""; // FIXME: don't allocate if already empty
        go!($($rest)*);
    });

    ( append_temp $c:expr $($rest:tt)* ) => ({
        self.temp_buf.push_char($c);
        go!($($rest)*);
    });

    ( emit_temp $($rest:tt)* ) => ({
        self.emit_temp_buf();
        go!($($rest)*);
    });

    ( reconsume ) => ({
        return Reconsume;
    });

    // FIXME: better name
    ( finish ) => ({
        return Finished;
    });

    () => ({});
)

impl<'sink, Sink: TokenSink> Tokenizer<'sink, Sink> {
    // FIXME: explicitly represent the EOF character?
    // For now the plan is to handle EOF in a separate match.
    fn process_char(&mut self, c: char) -> ConsumeCharResult {
        debug!("Processing {:?} in state {:?}", c, self.state);
        match self.state {
            states::Data => match c {
                '&'  => go!(to CharacterReferenceInData),
                '<'  => go!(to TagOpen),
                '\0' => go!(error emit '\0'),
                _    => go!(emit c),
            },

            states::Rcdata => match c {
                '&'  => go!(to CharacterReferenceInRcdata),
                '<'  => go!(to RcdataLessThanSign),
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::Rawtext => match c {
                '<'  => go!(to RawtextLessThanSign),
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::ScriptData => match c {
                '<'  => go!(to ScriptDataLessThanSign),
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::Plaintext => match c {
                '\0' => go!(error emit '\ufffd'),
                _    => go!(emit c),
            },

            states::TagOpen => match c {
                '!' => go!(to MarkupDeclarationOpen),
                '/' => go!(to EndTagOpen),
                '?' => go!(error to BogusComment),
                _ => match ascii_letter(c) {
                    Some(cl) => go!(create_tag StartTag cl to TagName),
                    None     => go!(error emit '<' to Data reconsume)
                }
            },

            states::EndTagOpen => match c {
                '>' => go!(error to Data),
                _ => match ascii_letter(c) {
                    Some(cl) => go!(create_tag EndTag cl to TagName),
                    None     => go!(error to BogusComment),
                }
            },

            states::TagName => match c {
                '\t' | '\n' | '\x0C' | ' '
                     => go!(to BeforeAttributeName),
                '/'  => go!(to SelfClosingStartTag),
                '>'  => go!(emit_tag to Data),
                '\0' => go!(error append_tag '\ufffd'),
                _    => go!(append_tag (ascii_letter(c).unwrap_or(c))),
            },

            states::RcdataLessThanSign => match c {
                '/' => go!(clear_temp to RcdataEndTagOpen),
                _   => go!(to Rcdata emit '<' reconsume),
            },

            states::RcdataEndTagOpen => match ascii_letter(c) {
                Some(cl) => go!(create_tag EndTag cl append_temp c to RcdataEndTagName),
                None     => go!(to Rcdata emit '<' emit '/' reconsume)
            },

            states::RcdataEndTagName => {
                if self.have_appropriate_end_tag() {
                    match c {
                        '\t' | '\n' | '\x0C' | ' '
                            => go!(to BeforeAttributeName finish),
                        '/' => go!(to SelfClosingStartTag finish),
                        '>' => go!(emit_tag to Data finish),
                        _ => (),
                    }
                }

                match ascii_letter(c) {
                    Some(cl) => go!(append_tag cl append_temp c),
                    None     => go!(emit '<' emit '/' emit_temp to Rcdata reconsume),
                }
            },

            states::RawtextLessThanSign => match c {
                '/' => go!(clear_temp to RawtextEndTagOpen),
                _   => go!(to Rawtext emit '<' reconsume),
            },

            states::RawtextEndTagOpen => match ascii_letter(c) {
                Some(cl) => go!(create_tag EndTag cl append_temp c to RawtextEndTagName),
                None     => go!(to Rawtext emit '<' emit '/' reconsume),
            },

            s => fail!("FIXME: state {:?} not implemented", s),
        }

        Finished

    }
}
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::collections::HashMap;
use std::fmt;

use {Args, Format, Message, Value};

/// Using a value, select the appropriate message and format it.
pub struct SelectFormat {
    /// The name of the variable whose value should be formatted.
    variable_name: String,
    /// Given a value of a variable, this maps that to a message format.
    mappings: HashMap<String, Message>,
    /// The message format to use if no valid mapping is found for
    /// the variable value.
    default: Message,
}

impl SelectFormat {
    /// Construct a `SelectFormat`.
    pub fn new(variable_name: &str, default: Message) -> Self {
        SelectFormat {
            variable_name: variable_name.to_string(),
            mappings: HashMap::<String, Message>::new(),
            default: default,
        }
    }

    /// Map a value for a particular message.
    pub fn map(&mut self, value: &str, message: Message) {
        self.mappings.insert(value.to_string(), message);
    }

    /// Given a value, determine which `Message` to use.
    pub fn lookup_message(&self, value: &str) -> &Message {
        self.mappings.get(value).unwrap_or(&self.default)
    }
}

impl Format for SelectFormat {
    fn apply_format<'f>(&'f self, stream: &mut fmt::Write, args: &'f Args<'f>) -> fmt::Result {
        if let Some(arg) = args.get(&self.variable_name) {
            let value = match *arg.value() {
                Value::Str(str) => str,
                _ => panic!("Wrong variable type."),
            };
            let message = self.lookup_message(&value);
            try!(message.write_message(stream, args));
            Ok(())
        } else {
            Ok(())
        }
    }
}

#[cfg(test)]
mod tests {
    use icu::parse;
    use super::SelectFormat;
    use {arg, Format};

    #[test]
    fn it_works() {
        let mut fmt = SelectFormat::new("type", parse("Default").unwrap());
        fmt.map("block", parse("Block").unwrap());

        let mut output = String::new();
        fmt.apply_format(&mut output, &arg("type", "block")).unwrap();
        assert_eq!("Block", output);

        let mut output = String::new();
        fmt.apply_format(&mut output, &arg("type", "span")).unwrap();
        assert_eq!("Default", output);
    }
}
<commit_msg>SelectFormat: Fix clippy warning.<commit_after>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::collections::HashMap;
use std::fmt;

use {Args, Format, Message, Value};

/// Using a value, select the appropriate message and format it.
pub struct SelectFormat {
    /// The name of the variable whose value should be formatted.
    variable_name: String,
    /// Given a value of a variable, this maps that to a message format.
    mappings: HashMap<String, Message>,
    /// The message format to use if no valid mapping is found for
    /// the variable value.
    default: Message,
}

impl SelectFormat {
    /// Construct a `SelectFormat`.
    pub fn new(variable_name: &str, default: Message) -> Self {
        SelectFormat {
            variable_name: variable_name.to_string(),
            mappings: HashMap::<String, Message>::new(),
            default: default,
        }
    }

    /// Map a value for a particular message.
    pub fn map(&mut self, value: &str, message: Message) {
        self.mappings.insert(value.to_string(), message);
    }

    /// Given a value, determine which `Message` to use.
    pub fn lookup_message(&self, value: &str) -> &Message {
        self.mappings.get(value).unwrap_or(&self.default)
    }
}

impl Format for SelectFormat {
    fn apply_format<'f>(&'f self, stream: &mut fmt::Write, args: &'f Args<'f>) -> fmt::Result {
        if let Some(arg) = args.get(&self.variable_name) {
            let value = match *arg.value() {
                Value::Str(str) => str,
                _ => panic!("Wrong variable type."),
            };
            let message = self.lookup_message(value);
            try!(message.write_message(stream, args));
            Ok(())
        } else {
            Ok(())
        }
    }
}

#[cfg(test)]
mod tests {
    use icu::parse;
    use super::SelectFormat;
    use {arg, Format};

    #[test]
    fn it_works() {
        let mut fmt = SelectFormat::new("type", parse("Default").unwrap());
        fmt.map("block", parse("Block").unwrap());

        let mut output = String::new();
        fmt.apply_format(&mut output, &arg("type", "block")).unwrap();
        assert_eq!("Block", output);

        let mut output = String::new();
        fmt.apply_format(&mut output, &arg("type", "span")).unwrap();
        assert_eq!("Default", output);
    }
}
<|endoftext|>


<commit_before>

#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum State {
    #[serde(rename = "opened")]
    Opened,
    #[serde(rename = "closed")]
    Closed,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ListingOrderBy {
    #[serde(rename = "created_at")]
    CreatedAt,
    #[serde(rename = "updated_at")]
    UpdatedAt,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
struct IssuesListerInternal {
    /// State of issues to return.
    state: Option<State>,
    /// Labels of issues to return.
    labels: Option<Vec<String>>,
    /// Return requests ordered by. Default is `ListingOrderBy::CreatedAt`.
    order_by: Option<ListingOrderBy>,
    /// Return requests sorted. Default is `::ListingSort::Desc`.
    sort: Option<::ListingSort>,
}
<commit_msg>Cleanup<commit_after>

#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum State {
    #[serde(rename = "opened")]
    Opened,
    #[serde(rename = "closed")]
    Closed,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ListingOrderBy {
    #[serde(rename = "created_at")]
    CreatedAt,
    #[serde(rename = "updated_at")]
    UpdatedAt,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
struct IssuesListerInternal {
    /// State of issues to return.
    state: Option<State>,
    /// Labels of issues to return.
    labels: Option<Vec<String>>,
    /// Return requests ordered by. Default is `ListingOrderBy::CreatedAt`.
    order_by: Option<ListingOrderBy>,
    /// Return requests sorted. Default is `::ListingSort::Desc`.
    sort: Option<::ListingSort>,
}
<|endoftext|>


<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::types::{
    CliError, CliTypedResult, PoolAddressArgs, PromptOptions, TransactionOptions,
    TransactionSummary,
};
use crate::common::utils::prompt_yes_with_override;
use crate::move_tool::{compile_move, ArgWithType, FunctionArgType};
use crate::{CliCommand, CliResult};
use aptos_crypto::HashValue;
use aptos_rest_client::{aptos_api_types::MoveType, Transaction};
use aptos_types::{
    account_address::AccountAddress,
    transaction::{Script, TransactionPayload},
};
use async_trait::async_trait;
use clap::Parser;
use move_deps::{
    move_compiler::compiled_unit::CompiledUnitEnum,
    move_core_types::{language_storage::TypeTag, transaction_argument::TransactionArgument},
    move_package::BuildConfig,
};
use reqwest::Url;
use serde::Deserialize;
use serde::Serialize;
use std::{
    collections::BTreeMap,
    convert::{TryFrom, TryInto},
    fmt::Formatter,
    fs,
    path::PathBuf,
};

/// Tool for on-chain governance
///
/// This tool allows voters that have stake to vote the ability to
/// propose changes to the chain, as well as vote and execute these
/// proposals.
#[derive(Parser)]
pub enum GovernanceTool {
    Propose(SubmitProposal),
    Vote(SubmitVote),
    PrepareProposal(PrepareProposal),
    ExecuteProposal(ExecuteProposal),
}

impl GovernanceTool {
    pub async fn execute(self) -> CliResult {
        use GovernanceTool::*;
        match self {
            Propose(tool) => tool.execute_serialized().await,
            Vote(tool) => tool.execute_serialized().await,
            PrepareProposal(tool) => tool.execute_serialized().await,
            ExecuteProposal(tool) => tool.execute_serialized().await,
        }
    }
}

/// Submit proposal to other validators to be proposed on
#[derive(Parser)]
pub struct SubmitProposal {
    /// Execution hash of the script to be voted on
    #[clap(long, parse(try_from_str = read_hex_hash))]
    pub(crate) execution_hash: HashValue,

    /// Code location of the script to be voted on
    #[clap(long)]
    pub(crate) metadata_url: Url,

    #[clap(flatten)]
    pub(crate) prompt_options: PromptOptions,
    #[clap(flatten)]
    pub(crate) txn_options: TransactionOptions,
    #[clap(flatten)]
    pub(crate) pool_address_args: PoolAddressArgs,
}

#[async_trait]
impl CliCommand<Transaction> for SubmitProposal {
    fn command_name(&self) -> &'static str {
        "SubmitProposal"
    }

    async fn execute(mut self) -> CliTypedResult<Transaction> {
        // Validate the proposal metadata
        let client = reqwest::ClientBuilder::default()
            .tls_built_in_root_certs(true)
            .build()
            .map_err(|err| {
                CliError::UnexpectedError(format!("Failed to build HTTP client {}", err))
            })?;
        let bytes = client
            .get(self.metadata_url.clone())
            .send()
            .await
            .map_err(|err| {
                CliError::CommandArgumentError(format!(
                    "Failed to fetch metadata url {}: {}",
                    self.metadata_url, err
                ))
            })?
            .bytes()
            .await
            .map_err(|err| {
                CliError::CommandArgumentError(format!(
                    "Failed to fetch metadata url {}: {}",
                    self.metadata_url, err
                ))
            })?;
        let metadata: ProposalMetadata = serde_json::from_slice(&bytes).map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Metadata is not in a proper JSON format: {}",
                err
            ))
        })?;
        Url::parse(&metadata.source_code_url).map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Source code URL {} is invalid {}",
                metadata.source_code_url, err
            ))
        })?;
        Url::parse(&metadata.discussion_url).map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Discussion URL {} is invalid {}",
                metadata.discussion_url, err
            ))
        })?;

        let metadata_hash = HashValue::sha3_256_of(&bytes);

        println!("{}, Hash: {}", metadata, metadata_hash);
        prompt_yes_with_override("Do you want to submit this proposal?", self.prompt_options)?;

        self.txn_options
            .submit_script_function(
                AccountAddress::ONE,
                "aptos_governance",
                "create_proposal",
                vec![],
                vec![
                    bcs::to_bytes(&self.pool_address_args.pool_address)?,
                    bcs::to_bytes(&self.execution_hash.to_hex())?,
                    bcs::to_bytes(&self.metadata_url.to_string())?,
                    bcs::to_bytes(&metadata_hash.to_hex())?,
                ],
            )
            .await
    }
}

fn read_hex_hash(str: &str) -> CliTypedResult<HashValue> {
    let hex = str.strip_prefix("0x").unwrap_or(str);
    HashValue::from_hex(hex).map_err(|err| CliError::CommandArgumentError(err.to_string()))
}

#[derive(Parser)]
pub struct SubmitVote {
    /// Id of proposal to vote on
    #[clap(long)]
    pub(crate) proposal_id: u64,

    /// Vote choice. True for yes. False for no.
    #[clap(long)]
    pub(crate) should_pass: bool,

    #[clap(flatten)]
    pub(crate) prompt_options: PromptOptions,
    #[clap(flatten)]
    pub(crate) txn_options: TransactionOptions,
    #[clap(flatten)]
    pub(crate) pool_address_args: PoolAddressArgs,
}

#[async_trait]
impl CliCommand<Transaction> for SubmitVote {
    fn command_name(&self) -> &'static str {
        "SubmitVote"
    }

    async fn execute(mut self) -> CliTypedResult<Transaction> {
        // TODO: Display details of proposal
        let vote = if self.should_pass { "Yes" } else { "No" };
        prompt_yes_with_override(
            &format!("Are you sure you want to vote {}", vote),
            self.prompt_options,
        )?;

        self.txn_options
            .submit_script_function(
                AccountAddress::ONE,
                "aptos_governance",
                "vote",
                vec![],
                vec![
                    bcs::to_bytes(&self.pool_address_args.pool_address)?,
                    bcs::to_bytes(&self.proposal_id)?,
                    bcs::to_bytes(&self.should_pass)?,
                ],
            )
            .await
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProposalMetadata {
    title: String,
    description: String,
    source_code_url: String,
    discussion_url: String,
}

impl std::fmt::Display for ProposalMetadata {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Proposal:\n\tTitle:{}\n\tDescription:{}\n\tSource code URL:{}\n\tDiscussion URL:{}",
            self.title, self.description, self.source_code_url, self.discussion_url
        )
    }
}

#[derive(Clone, Debug, Default, Serialize)]
pub struct ScriptHash {
    hash: String,
    bytecode: String,
}

#[derive(Parser)]
pub struct PrepareProposal {
    /// Path to the Move package that contains the execution script
    #[clap(long, parse(from_os_str))]
    pub path: PathBuf,
}

#[async_trait]
impl CliCommand<ScriptHash> for PrepareProposal {
    fn command_name(&self) -> &'static str {
        "PrepareProposal"
    }

    async fn execute(mut self) -> CliTypedResult<ScriptHash> {
        let build_config = BuildConfig {
            additional_named_addresses: BTreeMap::new(),
            generate_abis: false,
            generate_docs: false,
            ..Default::default()
        };

        let compiled_package = compile_move(build_config, self.path.as_path())?;
        let scripts_count = compiled_package.scripts().count();

        if scripts_count != 1 {
            return Err(CliError::UnexpectedError(format!(
                "Only one script can be prepared a time. Make sure one and only one script file \
                is included in the Move package. Found {} scripts.",
                scripts_count
            )));
        }

        let script = *compiled_package
            .scripts()
            .collect::<Vec<_>>()
            .get(0)
            .unwrap();

        match script.unit {
            CompiledUnitEnum::Script(ref s) => {
                let mut bytes = vec![];

                s.script.serialize(&mut bytes).map_err(|err| {
                    CliError::UnexpectedError(format!("Unexpected error: {}", err))
                })?;

                Ok(ScriptHash {
                    hash: hex::encode(HashValue::sha3_256_of(bytes.as_slice()).to_vec()),
                    bytecode: hex::encode(bytes),
                })
            }
            CompiledUnitEnum::Module(_) => Err(CliError::UnexpectedError(
                "You can only execute a script, a module is not supported.".to_string(),
            )),
        }
    }
}

#[derive(Parser)]
pub struct ExecuteProposal {
    /// Path to the compiled script file
    #[clap(long, parse(from_os_str))]
    pub path: PathBuf,

    /// Hex encoded arguments separated by spaces.
    ///
    /// Example: `0x01 0x02 0x03`
    #[clap(long, multiple_values = true)]
    pub(crate) args: Vec<ArgWithType>,

    /// TypeTag arguments separated by spaces.
    ///
    /// Example: `u8 u64 u128 bool address vector true false signer`
    #[clap(long, multiple_values = true)]
    pub(crate) type_args: Vec<MoveType>,

    #[clap(flatten)]
    pub(crate) txn_options: TransactionOptions,
}

impl TryFrom<&ArgWithType> for TransactionArgument {
    type Error = CliError;

    fn try_from(arg: &ArgWithType) -> Result<Self, Self::Error> {
        let txn_arg = match arg._ty {
            FunctionArgType::Address => TransactionArgument::Address(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("address", err.to_string()))?,
            ),
            FunctionArgType::Bool => TransactionArgument::Bool(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("bool", err.to_string()))?,
            ),
            FunctionArgType::Hex => TransactionArgument::U8Vector(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("hex", err.to_string()))?,
            ),
            FunctionArgType::String => TransactionArgument::U8Vector(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("string", err.to_string()))?,
            ),
            FunctionArgType::U128 => TransactionArgument::U128(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("u128", err.to_string()))?,
            ),
            FunctionArgType::U64 => TransactionArgument::U64(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("u64", err.to_string()))?,
            ),
            FunctionArgType::U8 => TransactionArgument::U8(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("u8", err.to_string()))?,
            ),
        };

        Ok(txn_arg)
    }
}

#[async_trait]
impl CliCommand<TransactionSummary> for ExecuteProposal {
    fn command_name(&self) -> &'static str {
        "ExecuteProposal"
    }

    async fn execute(mut self) -> CliTypedResult<TransactionSummary> {
        if !self.path.exists() {
            return Err(CliError::UnableToReadFile(
                self.path.display().to_string(),
                "Path doesn't exist".to_string(),
            ));
        }

        let code = fs::read(self.path.as_path()).map_err(|err| {
            CliError::UnableToReadFile(self.path.display().to_string(), err.to_string())
        })?;

        let args = self
            .args
            .iter()
            .map(|arg_with_type| arg_with_type.try_into())
            .collect::<Result<Vec<TransactionArgument>, CliError>>()?;

        let mut type_args: Vec<TypeTag> = Vec::new();

        // These TypeArgs are used for generics
        for type_arg in self.type_args.iter().cloned() {
            let type_tag = TypeTag::try_from(type_arg)
                .map_err(|err| CliError::UnableToParse("--type-args", err.to_string()))?;
            type_args.push(type_tag)
        }

        let txn = TransactionPayload::Script(Script::new(code, type_args, args));

        self.txn_options
            .submit_transaction(txn)
            .await
            .map(TransactionSummary::from)
    }
}
<commit_msg>[aptos-cli] Add vote yes and no options<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::types::{
    CliError, CliTypedResult, PoolAddressArgs, PromptOptions, TransactionOptions,
    TransactionSummary,
};
use crate::common::utils::prompt_yes_with_override;
use crate::move_tool::{compile_move, ArgWithType, FunctionArgType};
use crate::{CliCommand, CliResult};
use aptos_crypto::HashValue;
use aptos_rest_client::{aptos_api_types::MoveType, Transaction};
use aptos_types::{
    account_address::AccountAddress,
    transaction::{Script, TransactionPayload},
};
use async_trait::async_trait;
use clap::Parser;
use move_deps::{
    move_compiler::compiled_unit::CompiledUnitEnum,
    move_core_types::{language_storage::TypeTag, transaction_argument::TransactionArgument},
    move_package::BuildConfig,
};
use reqwest::Url;
use serde::Deserialize;
use serde::Serialize;
use std::{
    collections::BTreeMap,
    convert::{TryFrom, TryInto},
    fmt::Formatter,
    fs,
    path::PathBuf,
};

/// Tool for on-chain governance
///
/// This tool allows voters that have stake to vote the ability to
/// propose changes to the chain, as well as vote and execute these
/// proposals.
#[derive(Parser)]
pub enum GovernanceTool {
    Propose(SubmitProposal),
    Vote(SubmitVote),
    PrepareProposal(PrepareProposal),
    ExecuteProposal(ExecuteProposal),
}

impl GovernanceTool {
    pub async fn execute(self) -> CliResult {
        use GovernanceTool::*;
        match self {
            Propose(tool) => tool.execute_serialized().await,
            Vote(tool) => tool.execute_serialized().await,
            PrepareProposal(tool) => tool.execute_serialized().await,
            ExecuteProposal(tool) => tool.execute_serialized().await,
        }
    }
}

/// Submit proposal to other validators to be proposed on
#[derive(Parser)]
pub struct SubmitProposal {
    /// Execution hash of the script to be voted on
    #[clap(long, parse(try_from_str = read_hex_hash))]
    pub(crate) execution_hash: HashValue,

    /// Code location of the script to be voted on
    #[clap(long)]
    pub(crate) metadata_url: Url,

    #[clap(flatten)]
    pub(crate) prompt_options: PromptOptions,
    #[clap(flatten)]
    pub(crate) txn_options: TransactionOptions,
    #[clap(flatten)]
    pub(crate) pool_address_args: PoolAddressArgs,
}

#[async_trait]
impl CliCommand<Transaction> for SubmitProposal {
    fn command_name(&self) -> &'static str {
        "SubmitProposal"
    }

    async fn execute(mut self) -> CliTypedResult<Transaction> {
        // Validate the proposal metadata
        let (metadata, metadata_hash) = get_metadata(&self.metadata_url)?;

        println!("{}, Hash: {}", metadata, metadata_hash);
        prompt_yes_with_override("Do you want to submit this proposal?", self.prompt_options)?;

        self.txn_options
            .submit_script_function(
                AccountAddress::ONE,
                "aptos_governance",
                "create_proposal",
                vec![],
                vec![
                    bcs::to_bytes(&self.pool_address_args.pool_address)?,
                    bcs::to_bytes(&self.execution_hash.to_hex())?,
                    bcs::to_bytes(&self.metadata_url.to_string())?,
                    bcs::to_bytes(&metadata_hash.to_hex())?,
                ],
            )
            .await
    }
}

/// Retrieve metadata and validate it
fn get_metadata(metadata_url: &Url) -> CliTypedResult<(ProposalMetadata, HashValue)> {
    let client = reqwest::ClientBuilder::default()
        .tls_built_in_root_certs(true)
        .build()
        .map_err(|err| CliError::UnexpectedError(format!("Failed to build HTTP client {}", err)))?;
    let bytes = client
        .get(metadata_url)
        .send()
        .await
        .map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Failed to fetch metadata url {}: {}",
                metadata_url, err
            ))
        })?
        .bytes()
        .await
        .map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Failed to fetch metadata url {}: {}",
                metadata_url, err
            ))
        })?;
    let metadata: ProposalMetadata = serde_json::from_slice(&bytes).map_err(|err| {
        CliError::CommandArgumentError(format!("Metadata is not in a proper JSON format: {}", err))
    })?;
    Url::parse(&metadata.source_code_url).map_err(|err| {
        CliError::CommandArgumentError(format!(
            "Source code URL {} is invalid {}",
            metadata.source_code_url, err
        ))
    })?;
    Url::parse(&metadata.discussion_url).map_err(|err| {
        CliError::CommandArgumentError(format!(
            "Discussion URL {} is invalid {}",
            metadata.discussion_url, err
        ))
    })?;
    let metadata_hash = HashValue::sha3_256_of(&bytes);
    Ok((metadata, metadata_hash))
}

fn read_hex_hash(str: &str) -> CliTypedResult<HashValue> {
    let hex = str.strip_prefix("0x").unwrap_or(str);
    HashValue::from_hex(hex).map_err(|err| CliError::CommandArgumentError(err.to_string()))
}

#[derive(Parser)]
pub struct SubmitVote {
    /// Id of proposal to vote on
    #[clap(long)]
    pub(crate) proposal_id: u64,

    /// Vote yes on the proposal
    #[clap(long, group = "vote")]
    pub(crate) yes: bool,

    /// Vote no on the proposal
    #[clap(long, group = "vote")]
    pub(crate) no: bool,

    #[clap(flatten)]
    pub(crate) prompt_options: PromptOptions,
    #[clap(flatten)]
    pub(crate) txn_options: TransactionOptions,
    #[clap(flatten)]
    pub(crate) pool_address_args: PoolAddressArgs,
}

#[async_trait]
impl CliCommand<Transaction> for SubmitVote {
    fn command_name(&self) -> &'static str {
        "SubmitVote"
    }

    async fn execute(mut self) -> CliTypedResult<Transaction> {
        let vote = match (vote_yes, vote_no) {
            (true, false) => "Yes",
            (false, true) => "No",
            (_, _) => {
                return Err(CliError::CommandArgumentError(
                    "Must choose either --yes or --no".to_string(),
                ))
            }
        };

        // TODO: Display details of proposal

        prompt_yes_with_override(
            &format!("Are you sure you want to vote {}", vote),
            self.prompt_options,
        )?;

        self.txn_options
            .submit_script_function(
                AccountAddress::ONE,
                "aptos_governance",
                "vote",
                vec![],
                vec![
                    bcs::to_bytes(&self.pool_address_args.pool_address)?,
                    bcs::to_bytes(&self.proposal_id)?,
                    bcs::to_bytes(&self.should_pass)?,
                ],
            )
            .await
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProposalMetadata {
    title: String,
    description: String,
    source_code_url: String,
    discussion_url: String,
}

impl std::fmt::Display for ProposalMetadata {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Proposal:\n\tTitle:{}\n\tDescription:{}\n\tSource code URL:{}\n\tDiscussion URL:{}",
            self.title, self.description, self.source_code_url, self.discussion_url
        )
    }
}

#[derive(Clone, Debug, Default, Serialize)]
pub struct ScriptHash {
    hash: String,
    bytecode: String,
}

#[derive(Parser)]
pub struct PrepareProposal {
    /// Path to the Move package that contains the execution script
    #[clap(long, parse(from_os_str))]
    pub path: PathBuf,
}

#[async_trait]
impl CliCommand<ScriptHash> for PrepareProposal {
    fn command_name(&self) -> &'static str {
        "PrepareProposal"
    }

    async fn execute(mut self) -> CliTypedResult<ScriptHash> {
        let build_config = BuildConfig {
            additional_named_addresses: BTreeMap::new(),
            generate_abis: false,
            generate_docs: false,
            ..Default::default()
        };

        let compiled_package = compile_move(build_config, self.path.as_path())?;
        let scripts_count = compiled_package.scripts().count();

        if scripts_count != 1 {
            return Err(CliError::UnexpectedError(format!(
                "Only one script can be prepared a time. Make sure one and only one script file \
                is included in the Move package. Found {} scripts.",
                scripts_count
            )));
        }

        let script = *compiled_package
            .scripts()
            .collect::<Vec<_>>()
            .get(0)
            .unwrap();

        match script.unit {
            CompiledUnitEnum::Script(ref s) => {
                let mut bytes = vec![];

                s.script.serialize(&mut bytes).map_err(|err| {
                    CliError::UnexpectedError(format!("Unexpected error: {}", err))
                })?;

                Ok(ScriptHash {
                    hash: hex::encode(HashValue::sha3_256_of(bytes.as_slice()).to_vec()),
                    bytecode: hex::encode(bytes),
                })
            }
            CompiledUnitEnum::Module(_) => Err(CliError::UnexpectedError(
                "You can only execute a script, a module is not supported.".to_string(),
            )),
        }
    }
}

#[derive(Parser)]
pub struct ExecuteProposal {
    /// Path to the compiled script file
    #[clap(long, parse(from_os_str))]
    pub path: PathBuf,

    /// Hex encoded arguments separated by spaces.
    ///
    /// Example: `0x01 0x02 0x03`
    #[clap(long, multiple_values = true)]
    pub(crate) args: Vec<ArgWithType>,

    /// TypeTag arguments separated by spaces.
    ///
    /// Example: `u8 u64 u128 bool address vector true false signer`
    #[clap(long, multiple_values = true)]
    pub(crate) type_args: Vec<MoveType>,

    #[clap(flatten)]
    pub(crate) txn_options: TransactionOptions,
}

impl TryFrom<&ArgWithType> for TransactionArgument {
    type Error = CliError;

    fn try_from(arg: &ArgWithType) -> Result<Self, Self::Error> {
        let txn_arg = match arg._ty {
            FunctionArgType::Address => TransactionArgument::Address(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("address", err.to_string()))?,
            ),
            FunctionArgType::Bool => TransactionArgument::Bool(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("bool", err.to_string()))?,
            ),
            FunctionArgType::Hex => TransactionArgument::U8Vector(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("hex", err.to_string()))?,
            ),
            FunctionArgType::String => TransactionArgument::U8Vector(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("string", err.to_string()))?,
            ),
            FunctionArgType::U128 => TransactionArgument::U128(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("u128", err.to_string()))?,
            ),
            FunctionArgType::U64 => TransactionArgument::U64(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("u64", err.to_string()))?,
            ),
            FunctionArgType::U8 => TransactionArgument::U8(
                bcs::from_bytes(&arg.arg)
                    .map_err(|err| CliError::UnableToParse("u8", err.to_string()))?,
            ),
        };

        Ok(txn_arg)
    }
}

#[async_trait]
impl CliCommand<TransactionSummary> for ExecuteProposal {
    fn command_name(&self) -> &'static str {
        "ExecuteProposal"
    }

    async fn execute(mut self) -> CliTypedResult<TransactionSummary> {
        if !self.path.exists() {
            return Err(CliError::UnableToReadFile(
                self.path.display().to_string(),
                "Path doesn't exist".to_string(),
            ));
        }

        let code = fs::read(self.path.as_path()).map_err(|err| {
            CliError::UnableToReadFile(self.path.display().to_string(), err.to_string())
        })?;

        let args = self
            .args
            .iter()
            .map(|arg_with_type| arg_with_type.try_into())
            .collect::<Result<Vec<TransactionArgument>, CliError>>()?;

        let mut type_args: Vec<TypeTag> = Vec::new();

        // These TypeArgs are used for generics
        for type_arg in self.type_args.iter().cloned() {
            let type_tag = TypeTag::try_from(type_arg)
                .map_err(|err| CliError::UnableToParse("--type-args", err.to_string()))?;
            type_args.push(type_tag)
        }

        let txn = TransactionPayload::Script(Script::new(code, type_args, args));

        self.txn_options
            .submit_transaction(txn)
            .await
            .map(TransactionSummary::from)
    }
}
<|endoftext|>


<commit_before>extern crate proc_macro;

use proc_macro::*;
use std::process::Command;
use std::sync::Once;

#[proc_macro_attribute]
pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
    // Ideally these options would be embedded in the test itself. However, I
    // find it very helpful to have the test clearly state whether or not it
    // is ignored. It would be nice to have some kind of runtime ignore
    // support (such as
    // https://internals.rust-lang.org/t/pre-rfc-skippable-tests/14611).
    //
    // Unfortunately a big drawback here is that if the environment changes
    // (such as the existence of the `git` CLI), this will not trigger a
    // rebuild and the test will still be ignored. In theory, something like
    // `tracked_env` or `tracked_path`
    // (https://github.com/rust-lang/rust/issues/99515) could help with this,
    // but they don't really handle the absence of files well.
    let mut ignore = false;
    let mut requires_reason = false;
    let mut found_reason = false;
    let is_not_nightly = !version().1;
    for rule in split_rules(attr) {
        match rule.as_str() {
            "build_std_real" => {
                // Only run the "real" build-std tests on nightly and with an
                // explicit opt-in (these generally only work on linux, and
                // have some extra requirements, and are slow, and can pollute
                // the environment since it downloads dependencies).
                ignore |= is_not_nightly;
                ignore |= option_env!("CARGO_RUN_BUILD_STD_TESTS").is_none();
            }
            "build_std_mock" => {
                // Only run the "mock" build-std tests on nightly and disable
                // for windows-gnu which is missing object files (see
                // https://github.com/rust-lang/wg-cargo-std-aware/issues/46).
                ignore |= is_not_nightly;
                ignore |= cfg!(all(target_os = "windows", target_env = "gnu"));
            }
            "nightly" => {
                requires_reason = true;
                ignore |= is_not_nightly;
            }
            s if s.starts_with("requires_") => {
                let command = &s[9..];
                ignore |= !has_command(command);
            }
            s if s.starts_with(">=1.") => {
                requires_reason = true;
                let min_minor = s[4..].parse().unwrap();
                ignore |= version().0 < min_minor;
            }
            s if s.starts_with("reason=") => {
                found_reason = true;
            }
            _ => panic!("unknown rule {:?}", rule),
        }
    }
    if requires_reason && !found_reason {
        panic!(
            "#[cargo_test] with a rule also requires a reason, \
            such as #[cargo_test(nightly, reason = \"needs -Z unstable-thing\")]"
        );
    }

    let span = Span::call_site();
    let mut ret = TokenStream::new();
    let add_attr = |ret: &mut TokenStream, attr_name| {
        ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone))));
        let attr = TokenTree::from(Ident::new(attr_name, span));
        ret.extend(Some(TokenTree::from(Group::new(
            Delimiter::Bracket,
            attr.into(),
        ))));
    };
    add_attr(&mut ret, "test");
    if ignore {
        add_attr(&mut ret, "ignore");
    }

    for token in item {
        let group = match token {
            TokenTree::Group(g) => {
                if g.delimiter() == Delimiter::Brace {
                    g
                } else {
                    ret.extend(Some(TokenTree::Group(g)));
                    continue;
                }
            }
            other => {
                ret.extend(Some(other));
                continue;
            }
        };

        let mut new_body = to_token_stream(
            r#"let _test_guard = {
                let tmp_dir = option_env!("CARGO_TARGET_TMPDIR");
                cargo_test_support::paths::init_root(tmp_dir)
            };"#,
        );

        new_body.extend(group.stream());
        ret.extend(Some(TokenTree::from(Group::new(
            group.delimiter(),
            new_body,
        ))));
    }

    ret
}

fn split_rules(t: TokenStream) -> Vec<String> {
    let tts: Vec<_> = t.into_iter().collect();
    tts.split(|tt| match tt {
        TokenTree::Punct(p) => p.as_char() == ',',
        _ => false,
    })
    .filter(|parts| !parts.is_empty())
    .map(|parts| {
        parts
            .into_iter()
            .map(|part| part.to_string())
            .collect::<String>()
    })
    .collect()
}

fn to_token_stream(code: &str) -> TokenStream {
    code.parse().unwrap()
}

static mut VERSION: (u32, bool) = (0, false);

fn version() -> &'static (u32, bool) {
    static INIT: Once = Once::new();
    INIT.call_once(|| {
        let output = Command::new("rustc")
            .arg("-V")
            .output()
            .expect("rustc should run");
        let stdout = std::str::from_utf8(&output.stdout).expect("utf8");
        let vers = stdout.split_whitespace().skip(1).next().unwrap();
        let is_nightly = option_env!("CARGO_TEST_DISABLE_NIGHTLY").is_none()
            && (vers.contains("-nightly") || vers.contains("-dev"));
        let minor = vers.split('.').skip(1).next().unwrap().parse().unwrap();
        unsafe { VERSION = (minor, is_nightly) }
    });
    unsafe { &VERSION }
}

fn has_command(command: &str) -> bool {
    let output = match Command::new(command).arg("--version").output() {
        Ok(output) => output,
        Err(e) => {
            // hg is not installed on GitHub macos.
            // Consider installing it if Cargo gains more hg support, but
            // otherwise it isn't critical.
            if is_ci() && !(cfg!(target_os = "macos") && command == "hg") {
                panic!(
                    "expected command `{}` to be somewhere in PATH: {}",
                    command, e
                );
            }
            return false;
        }
    };
    if !output.status.success() {
        panic!(
            "expected command `{}` to be runnable, got error {}:\n\
            stderr:{}\n\
            stdout:{}\n",
            command,
            output.status,
            String::from_utf8_lossy(&output.stderr),
            String::from_utf8_lossy(&output.stdout)
        );
    }
    true
}

/// Whether or not this running in a Continuous Integration environment.
fn is_ci() -> bool {
    // Consider using `tracked_env` instead of option_env! when it is stabilized.
    // `tracked_env` will handle changes, but not require rebuilding the macro
    // itself like option_env does.
    option_env!("CI").is_some() || option_env!("TF_BUILD").is_some()
}
<commit_msg>Always allow hg to be missing on CI.<commit_after>extern crate proc_macro;

use proc_macro::*;
use std::process::Command;
use std::sync::Once;

#[proc_macro_attribute]
pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
    // Ideally these options would be embedded in the test itself. However, I
    // find it very helpful to have the test clearly state whether or not it
    // is ignored. It would be nice to have some kind of runtime ignore
    // support (such as
    // https://internals.rust-lang.org/t/pre-rfc-skippable-tests/14611).
    //
    // Unfortunately a big drawback here is that if the environment changes
    // (such as the existence of the `git` CLI), this will not trigger a
    // rebuild and the test will still be ignored. In theory, something like
    // `tracked_env` or `tracked_path`
    // (https://github.com/rust-lang/rust/issues/99515) could help with this,
    // but they don't really handle the absence of files well.
    let mut ignore = false;
    let mut requires_reason = false;
    let mut found_reason = false;
    let is_not_nightly = !version().1;
    for rule in split_rules(attr) {
        match rule.as_str() {
            "build_std_real" => {
                // Only run the "real" build-std tests on nightly and with an
                // explicit opt-in (these generally only work on linux, and
                // have some extra requirements, and are slow, and can pollute
                // the environment since it downloads dependencies).
                ignore |= is_not_nightly;
                ignore |= option_env!("CARGO_RUN_BUILD_STD_TESTS").is_none();
            }
            "build_std_mock" => {
                // Only run the "mock" build-std tests on nightly and disable
                // for windows-gnu which is missing object files (see
                // https://github.com/rust-lang/wg-cargo-std-aware/issues/46).
                ignore |= is_not_nightly;
                ignore |= cfg!(all(target_os = "windows", target_env = "gnu"));
            }
            "nightly" => {
                requires_reason = true;
                ignore |= is_not_nightly;
            }
            s if s.starts_with("requires_") => {
                let command = &s[9..];
                ignore |= !has_command(command);
            }
            s if s.starts_with(">=1.") => {
                requires_reason = true;
                let min_minor = s[4..].parse().unwrap();
                ignore |= version().0 < min_minor;
            }
            s if s.starts_with("reason=") => {
                found_reason = true;
            }
            _ => panic!("unknown rule {:?}", rule),
        }
    }
    if requires_reason && !found_reason {
        panic!(
            "#[cargo_test] with a rule also requires a reason, \
            such as #[cargo_test(nightly, reason = \"needs -Z unstable-thing\")]"
        );
    }

    let span = Span::call_site();
    let mut ret = TokenStream::new();
    let add_attr = |ret: &mut TokenStream, attr_name| {
        ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone))));
        let attr = TokenTree::from(Ident::new(attr_name, span));
        ret.extend(Some(TokenTree::from(Group::new(
            Delimiter::Bracket,
            attr.into(),
        ))));
    };
    add_attr(&mut ret, "test");
    if ignore {
        add_attr(&mut ret, "ignore");
    }

    for token in item {
        let group = match token {
            TokenTree::Group(g) => {
                if g.delimiter() == Delimiter::Brace {
                    g
                } else {
                    ret.extend(Some(TokenTree::Group(g)));
                    continue;
                }
            }
            other => {
                ret.extend(Some(other));
                continue;
            }
        };

        let mut new_body = to_token_stream(
            r#"let _test_guard = {
                let tmp_dir = option_env!("CARGO_TARGET_TMPDIR");
                cargo_test_support::paths::init_root(tmp_dir)
            };"#,
        );

        new_body.extend(group.stream());
        ret.extend(Some(TokenTree::from(Group::new(
            group.delimiter(),
            new_body,
        ))));
    }

    ret
}

fn split_rules(t: TokenStream) -> Vec<String> {
    let tts: Vec<_> = t.into_iter().collect();
    tts.split(|tt| match tt {
        TokenTree::Punct(p) => p.as_char() == ',',
        _ => false,
    })
    .filter(|parts| !parts.is_empty())
    .map(|parts| {
        parts
            .into_iter()
            .map(|part| part.to_string())
            .collect::<String>()
    })
    .collect()
}

fn to_token_stream(code: &str) -> TokenStream {
    code.parse().unwrap()
}

static mut VERSION: (u32, bool) = (0, false);

fn version() -> &'static (u32, bool) {
    static INIT: Once = Once::new();
    INIT.call_once(|| {
        let output = Command::new("rustc")
            .arg("-V")
            .output()
            .expect("rustc should run");
        let stdout = std::str::from_utf8(&output.stdout).expect("utf8");
        let vers = stdout.split_whitespace().skip(1).next().unwrap();
        let is_nightly = option_env!("CARGO_TEST_DISABLE_NIGHTLY").is_none()
            && (vers.contains("-nightly") || vers.contains("-dev"));
        let minor = vers.split('.').skip(1).next().unwrap().parse().unwrap();
        unsafe { VERSION = (minor, is_nightly) }
    });
    unsafe { &VERSION }
}

fn has_command(command: &str) -> bool {
    let output = match Command::new(command).arg("--version").output() {
        Ok(output) => output,
        Err(e) => {
            // hg is not installed on GitHub macOS or certain constrained
            // environments like Docker. Consider installing it if Cargo gains
            // more hg support, but otherwise it isn't critical.
            if is_ci() && command != "hg" {
                panic!(
                    "expected command `{}` to be somewhere in PATH: {}",
                    command, e
                );
            }
            return false;
        }
    };
    if !output.status.success() {
        panic!(
            "expected command `{}` to be runnable, got error {}:\n\
            stderr:{}\n\
            stdout:{}\n",
            command,
            output.status,
            String::from_utf8_lossy(&output.stderr),
            String::from_utf8_lossy(&output.stdout)
        );
    }
    true
}

/// Whether or not this running in a Continuous Integration environment.
fn is_ci() -> bool {
    // Consider using `tracked_env` instead of option_env! when it is stabilized.
    // `tracked_env` will handle changes, but not require rebuilding the macro
    // itself like option_env does.
    option_env!("CI").is_some() || option_env!("TF_BUILD").is_some()
}
<|endoftext|>


<commit_before>extern crate roboime_next;
#[macro_use] extern crate glium;
extern crate clap;
extern crate clock_ticks;
//extern crate image;
extern crate env_logger;
#[macro_use] extern crate log;
extern crate rusttype;
//extern crate arrayvec;
extern crate unicode_normalization;

use std::error::Error;

mod draw;

fn main_loop() -> Result<(), Box<Error>> {
    use std::env;
    use std::thread;
    //use std::io::Cursor;
    use std::time::Duration;
    use std::process::Command;
    use roboime_next::prelude::*;
    use roboime_next::{sim, ai};
    use clap::{Arg, App, AppSettings};
    use glium::{glutin, DisplayBuild};
    use log::LogLevelFilter;
    use env_logger::LogBuilder;
    //use image::image::GenericImage;
    use ::draw::*;

    let matches = App::new("robime-next-cli")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .setting(AppSettings::TrailingVarArg)
        .about("Connect an AI to a Robocup SSL game through an stdio interface (like codingame).")
        .arg(Arg::with_name("blue")
             .short("b")
             .long("blue")
             .value_name("BLUE COMMAND")
             .help("Command to start the blue AI, accepts arguments."))
        .arg(Arg::with_name("yellow")
             .short("y")
             .long("yellow")
             .value_name("YELLOW COMMAND")
             .help("Command to start the yellow AI, accepts arguments."))
        .arg(Arg::with_name("pause")
             .short("p")
             .long("pause")
             .help("Start the simulator paused."))
        .arg(Arg::with_name("v")
             .short("v")
             .multiple(true)
             .help("Sets the level of verbosity."))
        .arg(Arg::with_name("q")
             .conflicts_with("v")
             .long("quiet")
             .short("q")
             .help("Don't log warnings or errors."))
        //.arg(Arg::with_name("d")
        //     .long("debug")
        //     .short("d")
        //     .help("Enable debug logs."))
        .get_matches();

    {
        let mut builder = LogBuilder::new();
        builder.format(|record| {
            format!("{} [{}] {}", record.level(), record.location().module_path(), record.args())
        });
        let verbosity = matches.occurrences_of("v");
        builder.filter(Some("roboime"), match verbosity {
            0 => LogLevelFilter::Warn,
            1 => LogLevelFilter::Info,
            2 => LogLevelFilter::Debug,
            _ => LogLevelFilter::Trace,
        });
        if matches.is_present("q") {
            builder.filter(Some("roboime"), LogLevelFilter::Off);
        }
        if let Ok(ref log) = env::var("RUST_LOG") {
           builder.parse(log);
        }
        try!(builder.init());
    }

    // building the display, ie. the main object
    let display = try!(glutin::WindowBuilder::new()
        .with_title(format!("RoboIME Next"))
        // 1 extra pixel to align the middle lines to the screen
        .with_dimensions(1041, 741)
        .with_depth_buffer(24)
        .with_gl(glutin::GlRequest::Latest)
        .with_multisampling(4)
        .with_vsync()
        .build_glium());

    let team_side = {
        let team_side = Default::default();
        team_side
    };

    let mut is_paused = {
        let mut pause = false;
        if matches.is_present("pause") { pause = true; };
        pause
    };

    // Configs

    let ai_blue_cfg = if matches.is_present("blue") {
        let ai_cmd: Vec<&str> = matches.values_of("blue").unwrap().collect();
        let (ai_program, ai_args) = (ai_cmd[0], &ai_cmd[1..]);
        let mut ai_command = Command::new(ai_program);
        ai_command.args(ai_args);
        let mut ai_cfg = ai::Builder::new(ai_command);
        ai_cfg.color(Color::Blue);
        Some(ai_cfg)
    } else {
        None
    };

    let ai_yellow_cfg = if matches.is_present("yellow") {
        let ai_cmd: Vec<&str> = matches.values_of("yellow").unwrap().collect();
        let (ai_program, ai_args) = (ai_cmd[0], &ai_cmd[1..]);
        let mut ai_command = Command::new(ai_program);
        ai_command.args(ai_args);
        let mut ai_cfg = ai::Builder::new(ai_command);
        ai_cfg.color(Color::Yellow);
        Some(ai_cfg)
    } else {
        None
    };

    // States

    let mut sim_state = sim::Builder::new()
        .initial_formation(true)
        .team_side(team_side)
        .build();
    let mut draw_game = try!(Game::new(&display)); draw_game.team_side(&display, team_side);

    let mut ai_blue = match ai_blue_cfg {
        Some(mut ai_cfg) => Some(try!(ai_cfg.build())),
        None => None,
    };

    let mut ai_yellow = match ai_yellow_cfg {
        Some(mut ai_cfg) => Some(try!(ai_cfg.build())),
        None => None,
    };

    let mut accumulator = 0;
    let mut previous_clock = clock_ticks::precise_time_ns();
    let mut step_clock = previous_clock;
    let mut step_counter = 0;

    let mut key_meta = false;
    let mut key_ctrl = false;

    // debugger thread
    if let Some(ref mut ai) = ai_blue {
        let debug = ai.debug.take().unwrap();
        thread::spawn(move || {
            for line in debug {
                println!("Blue> {}", line.unwrap());
            }
        });
    }

    if let Some(ref mut ai) = ai_yellow {
        let debug = ai.debug.take().unwrap();
        thread::spawn(move || {
            for line in debug {
                println!("Blue> {}", line.unwrap());
            }
        });
    }

    // init the AI
    debug!("Wait for AI to start...");
    let mut ai_blue_state = match ai_blue {
        Some(ref mut ai) => Some(try!(ai.init(&sim_state))),
        None => None,
    };
    let mut ai_yellow_state = match ai_yellow {
        Some(ref mut ai) => Some(try!(ai.init(&sim_state))),
        None => None,
    };

    //drop(try!(rx.recv()));
    debug!("AI started, going on...");

    // the main loop
    'main: loop {
        #[derive(Copy, Clone, PartialEq, Eq)] enum Shot { No, Fwd, Bkw, }
        let mut single_shot = Shot::No;

        draw_game.update(&display, &sim_state);

        // render the game state
        let view = view_matrix(&[0.0, 0.0, 10.0], &[0.0, 0.0, -1.0], &[0.0, 1.0, 0.0]);
        //let view = view_matrix(&[-4.0, -4.0, 4.0], &[1.0, 1.0, -1.0], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[4.0, -4.0, 4.0], &[-1.0, 1.0, -1.0], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[-0.2, -0.2, 0.6], &[0.2, 0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[1.0, 2.0, 3.0], &[1.0, 1.0, -3.0], &[0.0, 1.0, 0.0]);
        //let view = view_matrix(&[-4.0, 0.7, 1.6], &[-0.2, -0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[-5.0, -0.7, 1.6], &[0.2, 0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[4.0, 0.7, 1.6], &[0.2, -0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[5.0, -0.7, 1.6], &[-0.2, 0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[0.0, 0.0, 0.6], &[0.0, 0.0, -1.0], &[0.0, 1.0, 0.0]);
        let mut target = display.draw();
        let view_port = display.get_window().unwrap().get_inner_size_points().unwrap();
        try!(draw_game.draw_to(&mut target, &sim_state, view_port, view));
        try!(target.finish());

        // polling and handling the events received by the window
        for event in display.poll_events() {
            use glium::glutin::Event::*;
            use glium::glutin::ElementState::*;
            use glium::glutin::VirtualKeyCode::*;

            match event {

                // close on Ctrl+Q, Cmd+Q, Win+Q, Esc, window X button
                KeyboardInput(Pressed, _, Some(Q)) if key_meta || key_ctrl => break 'main,
                KeyboardInput(Pressed, _, Some(Escape)) | Closed => break 'main,

                // modifier keys
                KeyboardInput(Pressed,  _, Some(LWin)) |
                KeyboardInput(Pressed,  _, Some(RWin)) => { key_meta = true; }
                KeyboardInput(Released, _, Some(LWin)) |
                KeyboardInput(Released, _, Some(RWin)) => { key_meta = false; }
                KeyboardInput(Pressed,  _, Some(LControl)) |
                KeyboardInput(Pressed,  _, Some(RControl)) => { key_ctrl = true; }
                KeyboardInput(Released, _, Some(LControl)) |
                KeyboardInput(Released, _, Some(RControl)) => { key_ctrl = false; }

                // some actions
                KeyboardInput(Pressed, _, Some(P)) => {
                    is_paused = !is_paused;
                }
                KeyboardInput(Pressed, _, Some(RBracket)) => {
                    single_shot = Shot::Fwd;
                }
                KeyboardInput(Pressed, _, Some(LBracket)) => {
                    single_shot = Shot::Bkw;
                }
                KeyboardInput(Pressed, _, Some(R)) => {
                    // reset ball position and speed
                    sim_state.ball.pos = Vec2d(0.0, 0.0);
                    sim_state.ball.vel = Vec2d(0.0, 0.0);
                }
                KeyboardInput(..) => {
                    debug!("{:?}", event);
                }
                _ => ()
            }
        }

        const FIXED_TIME_STEP: u64 = 16_666_667;

        let now = clock_ticks::precise_time_ns();
        if !is_paused {
            accumulator += now - previous_clock;

            while accumulator >= FIXED_TIME_STEP {
                accumulator -= FIXED_TIME_STEP;
                let now = clock_ticks::precise_time_ns();
                let delta = now - step_clock;
                if delta >= 1_000_000_000 {
                    // expect ~60 steps per second
                    info!("{:2} steps in {:.03} seconds", step_counter, delta as f32 * 1.0e-9);
                    step_counter = 0;
                    step_clock = now;
                }

                // game step
                let mut cmds = vec![];

                if let Some(ref mut ai_state) = ai_blue_state {
                    cmds.push(try!(ai_state.update(&sim_state)));
                }

                if let Some(ref mut ai_state) = ai_yellow_state {
                    cmds.push(try!(ai_state.update(&sim_state)));
                }

                sim_state.step(&cmds, FIXED_TIME_STEP as f32 * 1.0e-9);
                step_counter += 1;
            }
        }
        previous_clock = now;

        if single_shot != Shot::No {
            // XXX: is there a simple way to no duplicade this?
            // game step
            let mut cmds = vec![];

            if let Some(ref mut ai_state) = ai_blue_state {
                cmds.push(try!(ai_state.update(&sim_state)));
            }

            if let Some(ref mut ai_state) = ai_yellow_state {
                cmds.push(try!(ai_state.update(&sim_state)));
            }

            let mut timestep = FIXED_TIME_STEP as f32 * 1.0e-9;
            if single_shot == Shot::Bkw { timestep = -timestep; }
            sim_state.step(&cmds, timestep);
        }

        thread::sleep(Duration::from_millis(((FIXED_TIME_STEP - accumulator) / 1_000_000) as u64));
    }

    info!("Bye!");
    Ok(())
}

fn main() {
    use std::process::exit;

    if let Err(err) = main_loop() {
        println!("Error: {}.", err.description());

        let mut err: &Error = &*err;
        while let Some(cause) = err.cause() {
            println!("- caused by: {}", cause.description());
            err = cause;
        }

        exit(1);
    }
}
<commit_msg>Argument to simulate faster than realtime.<commit_after>extern crate roboime_next;
#[macro_use] extern crate glium;
extern crate clap;
extern crate clock_ticks;
//extern crate image;
extern crate env_logger;
#[macro_use] extern crate log;
extern crate rusttype;
//extern crate arrayvec;
extern crate unicode_normalization;

use std::error::Error;

mod draw;

fn main_loop() -> Result<(), Box<Error>> {
    use std::env;
    use std::thread;
    //use std::io::Cursor;
    use std::time::Duration;
    use std::process::Command;
    use roboime_next::prelude::*;
    use roboime_next::{sim, ai};
    use clap::{Arg, App, AppSettings};
    use glium::{glutin, DisplayBuild};
    use log::LogLevelFilter;
    use env_logger::LogBuilder;
    //use image::image::GenericImage;
    use ::draw::*;

    let matches = App::new("robime-next-cli")
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .setting(AppSettings::TrailingVarArg)
        .about("Connect an AI to a Robocup SSL game through an stdio interface (like codingame).")
        .arg(Arg::with_name("blue")
             .short("b")
             .long("blue")
             .value_name("BLUE COMMAND")
             .help("Command to start the blue AI, accepts arguments."))
        .arg(Arg::with_name("yellow")
             .short("y")
             .long("yellow")
             .value_name("YELLOW COMMAND")
             .help("Command to start the yellow AI, accepts arguments."))
        .arg(Arg::with_name("pause")
             .short("p")
             .long("pause")
             .help("Start the simulator paused."))
        .arg(Arg::with_name("v")
             .short("v")
             .multiple(true)
             .help("Sets the level of verbosity."))
        .arg(Arg::with_name("fast")
             .short("f")
             .multiple(true)
             .help("Each f doubles the speed."))
        .arg(Arg::with_name("q")
             .conflicts_with("v")
             .long("quiet")
             .short("q")
             .help("Don't log warnings or errors."))
        //.arg(Arg::with_name("d")
        //     .long("debug")
        //     .short("d")
        //     .help("Enable debug logs."))
        .get_matches();

    {
        let mut builder = LogBuilder::new();
        builder.format(|record| {
            format!("{} [{}] {}", record.level(), record.location().module_path(), record.args())
        });
        let verbosity = matches.occurrences_of("v");
        builder.filter(Some("roboime"), match verbosity {
            0 => LogLevelFilter::Warn,
            1 => LogLevelFilter::Info,
            2 => LogLevelFilter::Debug,
            _ => LogLevelFilter::Trace,
        });
        if matches.is_present("q") {
            builder.filter(Some("roboime"), LogLevelFilter::Off);
        }
        if let Ok(ref log) = env::var("RUST_LOG") {
           builder.parse(log);
        }
        try!(builder.init());
    }

    // building the display, ie. the main object
    let display = try!(glutin::WindowBuilder::new()
        .with_title(format!("RoboIME Next"))
        // 1 extra pixel to align the middle lines to the screen
        .with_dimensions(1041, 741)
        .with_depth_buffer(24)
        .with_gl(glutin::GlRequest::Latest)
        .with_multisampling(4)
        .with_vsync()
        .build_glium());

    let team_side = {
        let team_side = Default::default();
        team_side
    };

    let mut is_paused = {
        let mut pause = false;
        if matches.is_present("pause") { pause = true; };
        pause
    };

    let multiplier = 2.0f32.powi(matches.occurrences_of("fast") as i32);

    // Configs

    let ai_blue_cfg = if matches.is_present("blue") {
        let ai_cmd: Vec<&str> = matches.values_of("blue").unwrap().collect();
        let (ai_program, ai_args) = (ai_cmd[0], &ai_cmd[1..]);
        let mut ai_command = Command::new(ai_program);
        ai_command.args(ai_args);
        let mut ai_cfg = ai::Builder::new(ai_command);
        ai_cfg.color(Color::Blue);
        Some(ai_cfg)
    } else {
        None
    };

    let ai_yellow_cfg = if matches.is_present("yellow") {
        let ai_cmd: Vec<&str> = matches.values_of("yellow").unwrap().collect();
        let (ai_program, ai_args) = (ai_cmd[0], &ai_cmd[1..]);
        let mut ai_command = Command::new(ai_program);
        ai_command.args(ai_args);
        let mut ai_cfg = ai::Builder::new(ai_command);
        ai_cfg.color(Color::Yellow);
        Some(ai_cfg)
    } else {
        None
    };

    // States

    let mut sim_state = sim::Builder::new()
        .initial_formation(true)
        .team_side(team_side)
        .build();
    let mut draw_game = try!(Game::new(&display)); draw_game.team_side(&display, team_side);

    let mut ai_blue = match ai_blue_cfg {
        Some(mut ai_cfg) => Some(try!(ai_cfg.build())),
        None => None,
    };

    let mut ai_yellow = match ai_yellow_cfg {
        Some(mut ai_cfg) => Some(try!(ai_cfg.build())),
        None => None,
    };

    let mut accumulator = 0.0;
    let mut previous_clock = clock_ticks::precise_time_ns();
    let mut step_clock = previous_clock;
    let mut step_counter = 0;

    let mut key_meta = false;
    let mut key_ctrl = false;

    // debugger thread
    if let Some(ref mut ai) = ai_blue {
        let debug = ai.debug.take().unwrap();
        thread::spawn(move || {
            for line in debug {
                println!("Blue> {}", line.unwrap());
            }
        });
    }

    if let Some(ref mut ai) = ai_yellow {
        let debug = ai.debug.take().unwrap();
        thread::spawn(move || {
            for line in debug {
                println!("Blue> {}", line.unwrap());
            }
        });
    }

    // init the AI
    debug!("Wait for AI to start...");
    let mut ai_blue_state = match ai_blue {
        Some(ref mut ai) => Some(try!(ai.init(&sim_state))),
        None => None,
    };
    let mut ai_yellow_state = match ai_yellow {
        Some(ref mut ai) => Some(try!(ai.init(&sim_state))),
        None => None,
    };

    //drop(try!(rx.recv()));
    debug!("AI started, going on...");

    // the main loop
    'main: loop {
        #[derive(Copy, Clone, PartialEq, Eq)] enum Shot { No, Fwd, Bkw, }
        let mut single_shot = Shot::No;

        draw_game.update(&display, &sim_state);

        // render the game state
        let view = view_matrix(&[0.0, 0.0, 10.0], &[0.0, 0.0, -1.0], &[0.0, 1.0, 0.0]);
        //let view = view_matrix(&[-4.0, -4.0, 4.0], &[1.0, 1.0, -1.0], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[4.0, -4.0, 4.0], &[-1.0, 1.0, -1.0], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[-0.2, -0.2, 0.6], &[0.2, 0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[1.0, 2.0, 3.0], &[1.0, 1.0, -3.0], &[0.0, 1.0, 0.0]);
        //let view = view_matrix(&[-4.0, 0.7, 1.6], &[-0.2, -0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[-5.0, -0.7, 1.6], &[0.2, 0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[4.0, 0.7, 1.6], &[0.2, -0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[5.0, -0.7, 1.6], &[-0.2, 0.2, -0.6], &[0.0, 0.0, 1.0]);
        //let view = view_matrix(&[0.0, 0.0, 0.6], &[0.0, 0.0, -1.0], &[0.0, 1.0, 0.0]);
        let mut target = display.draw();
        let view_port = display.get_window().unwrap().get_inner_size_points().unwrap();
        try!(draw_game.draw_to(&mut target, &sim_state, view_port, view));
        try!(target.finish());

        // polling and handling the events received by the window
        for event in display.poll_events() {
            use glium::glutin::Event::*;
            use glium::glutin::ElementState::*;
            use glium::glutin::VirtualKeyCode::*;

            match event {

                // close on Ctrl+Q, Cmd+Q, Win+Q, Esc, window X button
                KeyboardInput(Pressed, _, Some(Q)) if key_meta || key_ctrl => break 'main,
                KeyboardInput(Pressed, _, Some(Escape)) | Closed => break 'main,

                // modifier keys
                KeyboardInput(Pressed,  _, Some(LWin)) |
                KeyboardInput(Pressed,  _, Some(RWin)) => { key_meta = true; }
                KeyboardInput(Released, _, Some(LWin)) |
                KeyboardInput(Released, _, Some(RWin)) => { key_meta = false; }
                KeyboardInput(Pressed,  _, Some(LControl)) |
                KeyboardInput(Pressed,  _, Some(RControl)) => { key_ctrl = true; }
                KeyboardInput(Released, _, Some(LControl)) |
                KeyboardInput(Released, _, Some(RControl)) => { key_ctrl = false; }

                // some actions
                KeyboardInput(Pressed, _, Some(P)) => {
                    is_paused = !is_paused;
                }
                KeyboardInput(Pressed, _, Some(RBracket)) => {
                    single_shot = Shot::Fwd;
                }
                KeyboardInput(Pressed, _, Some(LBracket)) => {
                    single_shot = Shot::Bkw;
                }
                KeyboardInput(Pressed, _, Some(R)) => {
                    // reset ball position and speed
                    sim_state.ball.pos = Vec2d(0.0, 0.0);
                    sim_state.ball.vel = Vec2d(0.0, 0.0);
                }
                KeyboardInput(..) => {
                    debug!("{:?}", event);
                }
                _ => ()
            }
        }

        //const FIXED_TIME_STEP: u64 = 16_666_667;
        const FIXED_TIME_STEP: f32 = 1_000_000_000.0 / 60.0;

        let now = clock_ticks::precise_time_ns();
        if !is_paused {
            accumulator += (now - previous_clock) as f32 * multiplier;

            while accumulator >= FIXED_TIME_STEP {
                accumulator -= FIXED_TIME_STEP;
                let now = clock_ticks::precise_time_ns();
                let delta = now - step_clock;
                if delta >= 1_000_000_000 {
                    // expect ~60 steps per second
                    info!("{:2} steps in {:.03} seconds", step_counter, delta as f32 * 1.0e-9);
                    step_counter = 0;
                    step_clock = now;
                }

                // game step
                let mut cmds = vec![];

                if let Some(ref mut ai_state) = ai_blue_state {
                    cmds.push(try!(ai_state.update(&sim_state)));
                }

                if let Some(ref mut ai_state) = ai_yellow_state {
                    cmds.push(try!(ai_state.update(&sim_state)));
                }

                sim_state.step(&cmds, FIXED_TIME_STEP * 1.0e-9);
                step_counter += 1;
            }
        }
        previous_clock = now;

        if single_shot != Shot::No {
            // XXX: is there a simple way to no duplicade this?
            // game step
            let mut cmds = vec![];

            if let Some(ref mut ai_state) = ai_blue_state {
                cmds.push(try!(ai_state.update(&sim_state)));
            }

            if let Some(ref mut ai_state) = ai_yellow_state {
                cmds.push(try!(ai_state.update(&sim_state)));
            }

            let mut timestep = FIXED_TIME_STEP * 1.0e-9;
            if single_shot == Shot::Bkw { timestep = -timestep; }
            sim_state.step(&cmds, timestep);
        }

        thread::sleep(Duration::from_millis(((FIXED_TIME_STEP - accumulator) / 1_000_000.0) as u64));
    }

    info!("Bye!");
    Ok(())
}

fn main() {
    use std::process::exit;

    if let Err(err) = main_loop() {
        println!("Error: {}.", err.description());

        let mut err: &Error = &*err;
        while let Some(cause) = err.cause() {
            println!("- caused by: {}", cause.description());
            err = cause;
        }

        exit(1);
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::collections::BTreeMap;

use crate::virtio::queue::DescriptorChain;
use crate::virtio::video::command::QueueType;
use crate::virtio::video::device::{AsyncCmdResponse, AsyncCmdTag};
use crate::virtio::video::error::VideoError;
use crate::virtio::video::protocol;
use crate::virtio::video::response::CmdResponse;

/// AsyncCmdDescMap is a BTreeMap which stores descriptor chains in which asynchronous
/// responses will be written.
#[derive(Default)]
pub struct AsyncCmdDescMap(BTreeMap<AsyncCmdTag, DescriptorChain>);

impl AsyncCmdDescMap {
    pub fn insert(&mut self, tag: AsyncCmdTag, descriptor_chain: DescriptorChain) {
        self.0.insert(tag, descriptor_chain);
    }

    pub fn remove(&mut self, tag: &AsyncCmdTag) -> Option<DescriptorChain> {
        self.0.remove(tag)
    }

    /// Returns a list of `AsyncCmdResponse`s to cancel pending commands that target
    /// stream `target_stream_id`.
    /// If `target_queue_type` is specified, then only create the requests for the specified queue.
    /// Otherwise, create the requests for both input and output queue.
    /// If `processing_tag` is specified, a cancellation request for that tag will
    /// not be created.
    pub fn create_cancellation_responses(
        &self,
        target_stream_id: &u32,
        target_queue_type: Option<QueueType>,
        processing_tag: Option<AsyncCmdTag>,
    ) -> Vec<AsyncCmdResponse> {
        let mut responses = vec![];
        for tag in self.0.keys().filter(|&&k| Some(k) != processing_tag) {
            match tag {
                AsyncCmdTag::Queue {
                    stream_id,
                    queue_type,
                    ..
                } if stream_id == target_stream_id
                    && target_queue_type.as_ref().unwrap_or(&queue_type) == queue_type =>
                {
                    responses.push(AsyncCmdResponse::from_response(
                        tag.clone(),
                        CmdResponse::ResourceQueue {
                            timestamp: 0,
                            flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_ERR,
                            size: 0,
                        },
                    ));
                }
                AsyncCmdTag::Drain { stream_id } | AsyncCmdTag::Clear { stream_id, .. }
                    if stream_id == target_stream_id =>
                {
                    // TODO(b/1518105): Use more appropriate error code if a new protocol supports
                    // one.
                    responses.push(AsyncCmdResponse::from_error(
                        tag.clone(),
                        VideoError::InvalidOperation,
                    ));
                }
                _ => {
                    // Keep commands for other streams.
                }
            }
        }
        responses
    }
}
<commit_msg>virtio: video: Check queue type when cancelling Clear tag<commit_after>// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::collections::BTreeMap;

use crate::virtio::queue::DescriptorChain;
use crate::virtio::video::command::QueueType;
use crate::virtio::video::device::{AsyncCmdResponse, AsyncCmdTag};
use crate::virtio::video::error::VideoError;
use crate::virtio::video::protocol;
use crate::virtio::video::response::CmdResponse;

/// AsyncCmdDescMap is a BTreeMap which stores descriptor chains in which asynchronous
/// responses will be written.
#[derive(Default)]
pub struct AsyncCmdDescMap(BTreeMap<AsyncCmdTag, DescriptorChain>);

impl AsyncCmdDescMap {
    pub fn insert(&mut self, tag: AsyncCmdTag, descriptor_chain: DescriptorChain) {
        self.0.insert(tag, descriptor_chain);
    }

    pub fn remove(&mut self, tag: &AsyncCmdTag) -> Option<DescriptorChain> {
        self.0.remove(tag)
    }

    /// Returns a list of `AsyncCmdResponse`s to cancel pending commands that target
    /// stream `target_stream_id`.
    /// If `target_queue_type` is specified, then only create the requests for the specified queue.
    /// Otherwise, create the requests for both input and output queue.
    /// If `processing_tag` is specified, a cancellation request for that tag will
    /// not be created.
    pub fn create_cancellation_responses(
        &self,
        target_stream_id: &u32,
        target_queue_type: Option<QueueType>,
        processing_tag: Option<AsyncCmdTag>,
    ) -> Vec<AsyncCmdResponse> {
        let mut responses = vec![];
        for tag in self.0.keys().filter(|&&k| Some(k) != processing_tag) {
            match tag {
                AsyncCmdTag::Queue {
                    stream_id,
                    queue_type,
                    ..
                } if stream_id == target_stream_id
                    && target_queue_type.as_ref().unwrap_or(&queue_type) == queue_type =>
                {
                    responses.push(AsyncCmdResponse::from_response(
                        tag.clone(),
                        CmdResponse::ResourceQueue {
                            timestamp: 0,
                            flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_ERR,
                            size: 0,
                        },
                    ));
                }
                AsyncCmdTag::Drain { stream_id } if stream_id == target_stream_id => {
                    // TODO(b/1518105): Use more appropriate error code if a new protocol supports
                    // one.
                    responses.push(AsyncCmdResponse::from_error(
                        tag.clone(),
                        VideoError::InvalidOperation,
                    ));
                }
                AsyncCmdTag::Clear {
                    stream_id,
                    queue_type,
                } if stream_id == target_stream_id
                    && target_queue_type.as_ref().unwrap_or(&queue_type) == queue_type =>
                {
                    // TODO(b/1518105): Use more appropriate error code if a new protocol supports
                    // one.
                    responses.push(AsyncCmdResponse::from_error(
                        tag.clone(),
                        VideoError::InvalidOperation,
                    ));
                }
                _ => {
                    // Keep commands for other streams.
                }
            }
        }
        responses
    }
}
<|endoftext|>