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

use crate::common::types::{
    CliError, CliTypedResult, MovePackageDir, PoolAddressArgs, PromptOptions, TransactionOptions,
    TransactionSummary,
};
use crate::common::utils::prompt_yes_with_override;
#[cfg(feature = "no-upload-proposal")]
use crate::common::utils::read_from_file;
use crate::move_tool::{init_move_dir, IncludedArtifacts};
use crate::{CliCommand, CliResult};
use aptos_crypto::HashValue;
use aptos_logger::warn;
use aptos_rest_client::aptos_api_types::U64;
use aptos_rest_client::Transaction;
use aptos_types::{
    account_address::AccountAddress,
    transaction::{Script, TransactionPayload},
};
use async_trait::async_trait;
use cached_packages::aptos_stdlib;
use clap::Parser;
use framework::{BuildOptions, BuiltPackage, ReleasePackage};
use move_deps::move_core_types::transaction_argument::TransactionArgument;
use reqwest::Url;
use serde::Deserialize;
use serde::Serialize;
use std::path::Path;
use std::{collections::BTreeMap, fmt::Formatter, fs, path::PathBuf};
use tempfile::TempDir;

/// 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),
    ExecuteProposal(ExecuteProposal),
    GenerateUpgradeProposal(GenerateUpgradeProposal),
}

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

/// Submit proposal to other validators to be proposed on
#[derive(Parser)]
pub struct SubmitProposal {
    /// Location of the JSON metadata of the proposal
    #[clap(long, group = "proposal-metadata")]
    pub(crate) metadata_url: Url,

    #[cfg(feature = "no-upload-proposal")]
    /// A JSON file to be uploaded later at the metadata URL
    #[clap(long, group = "proposal-metadata")]
    pub(crate) metadata_path: Option<PathBuf>,

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

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

    async fn execute(mut self) -> CliTypedResult<ProposalSubmissionSummary> {
        let (_bytecode, script_hash) = self
            .compile_proposal_args
            .compile(self.txn_options.prompt_options)?;

        // Validate the proposal metadata
        let (metadata, metadata_hash) = self.get_metadata().await?;

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

        let txn = self
            .txn_options
            .submit_transaction(
                aptos_stdlib::aptos_governance_create_proposal(
                    self.pool_address_args.pool_address,
                    script_hash.to_vec(),
                    self.metadata_url.to_string().as_bytes().to_vec(),
                    metadata_hash.to_hex().as_bytes().to_vec(),
                ),
                None,
            )
            .await?;
        let txn_summary = TransactionSummary::from(&txn);
        if let Transaction::UserTransaction(inner) = txn {
            // Find event with proposal id
            let proposal_id = if let Some(event) = inner.events.into_iter().find(|event| {
                event.typ.to_string().as_str() == "0x1::aptos_governance::CreateProposalEvent"
            }) {
                let data: CreateProposalEvent =
                    serde_json::from_value(event.data).map_err(|_| {
                        CliError::UnexpectedError(
                            "Failed to parse Proposal event to get ProposalId".to_string(),
                        )
                    })?;
                Some(data.proposal_id.0)
            } else {
                warn!("No proposal event found to find proposal id");
                None
            };

            return Ok(ProposalSubmissionSummary {
                proposal_id,
                transaction: txn_summary,
            });
        }
        Err(CliError::UnexpectedError(
            "Unable to find parse proposal transaction output".to_string(),
        ))
    }
}

impl SubmitProposal {
    /// Retrieve metadata and validate it
    async fn get_metadata(&self) -> CliTypedResult<(ProposalMetadata, HashValue)> {
        #[cfg(feature = "no-upload-proposal")]
        let bytes = if let Some(ref path) = self.metadata_path {
            read_from_file(path)?
        } else {
            get_metadata_from_url(&self.metadata_url).await?
        };
        #[cfg(not(feature = "no-upload-proposal"))]
        let bytes = get_metadata_from_url(&self.metadata_url).await?;

        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))
    }
}

async fn get_metadata_from_url(metadata_url: &Url) -> CliTypedResult<Vec<u8>> {
    let client = reqwest::ClientBuilder::default()
        .tls_built_in_root_certs(true)
        .build()
        .map_err(|err| CliError::UnexpectedError(format!("Failed to build HTTP client {}", err)))?;
    client
        .get(metadata_url.clone())
        .send()
        .await
        .map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Failed to fetch metadata url {}: {}",
                metadata_url, err
            ))
        })?
        .bytes()
        .await
        .map(|b| b.to_vec())
        .map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Failed to fetch metadata url {}: {}",
                metadata_url, err
            ))
        })
}

#[derive(Debug, Deserialize, Serialize)]
struct CreateProposalEvent {
    proposal_id: U64,
}

#[derive(Debug, Deserialize, Serialize)]
struct ProposalSubmissionSummary {
    proposal_id: Option<u64>,
    #[serde(flatten)]
    transaction: TransactionSummary,
}

/// Submit a vote on a current proposal
#[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) txn_options: TransactionOptions,
    #[clap(flatten)]
    pub(crate) pool_address_args: PoolAddressArgs,
}

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

    async fn execute(mut self) -> CliTypedResult<TransactionSummary> {
        let (vote_str, vote) = match (self.yes, self.no) {
            (true, false) => ("Yes", true),
            (false, true) => ("No", false),
            (_, _) => {
                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_str),
            self.txn_options.prompt_options,
        )?;

        self.txn_options
            .submit_transaction(
                aptos_stdlib::aptos_governance_vote(
                    self.pool_address_args.pool_address,
                    self.proposal_id,
                    vote,
                ),
                None,
            )
            .await
            .map(TransactionSummary::from)
    }
}

#[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
        )
    }
}

fn compile_in_temp_dir(
    script_path: &Path,
    framework_rev: Option<String>,
    prompt_options: PromptOptions,
) -> CliTypedResult<(Vec<u8>, HashValue)> {
    // Make a temporary directory for compilation
    let temp_dir = TempDir::new().map_err(|err| {
        CliError::UnexpectedError(format!("Failed to create temporary directory {}", err))
    })?;

    // Initialize a move directory
    let package_dir = temp_dir.path();
    init_move_dir(
        package_dir,
        "Proposal",
        framework_rev,
        BTreeMap::new(),
        prompt_options,
        None,
    )?;

    // Insert the new script
    let sources_dir = package_dir.join("sources");
    let new_script_path = if let Some(file_name) = script_path.file_name() {
        sources_dir.join(file_name)
    } else {
        // If for some reason we can't get the move file
        sources_dir.join("script.move")
    };
    fs::copy(script_path, new_script_path.as_path()).map_err(|err| {
        CliError::IO(
            format!(
                "Failed to copy {} to {}",
                script_path.display(),
                new_script_path.display()
            ),
            err,
        )
    })?;

    // Compile the script
    compile_script(package_dir)
}

fn compile_script(package_dir: &Path) -> CliTypedResult<(Vec<u8>, HashValue)> {
    let build_options = BuildOptions {
        with_srcs: false,
        with_abis: false,
        with_source_maps: false,
        with_error_map: false,
        install_dir: None,
        named_addresses: Default::default(),
    };

    let pack = BuiltPackage::build(package_dir.to_path_buf(), build_options)?;

    let scripts_count = pack.script_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 bytes = pack.extract_script_code().pop().unwrap();
    let hash = HashValue::sha3_256_of(bytes.as_slice());
    Ok((bytes, hash))
}

/// Execute a proposal that has passed voting requirements
#[derive(Parser)]
pub struct ExecuteProposal {
    /// Proposal Id being executed
    #[clap(long)]
    pub(crate) proposal_id: u64,

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

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

    async fn execute(mut self) -> CliTypedResult<TransactionSummary> {
        let (bytecode, _script_hash) = self
            .compile_proposal_args
            .compile(self.txn_options.prompt_options)?;
        // TODO: Check hash so we don't do a failed roundtrip?

        let args = vec![TransactionArgument::U64(self.proposal_id)];
        let txn = TransactionPayload::Script(Script::new(bytecode, vec![], args));

        self.txn_options
            .submit_transaction(txn, None)
            .await
            .map(TransactionSummary::from)
    }
}

/// Execute a proposal that has passed voting requirements
#[derive(Parser)]
pub struct CompileScriptFunction {
    /// Path to the Move script for the proposal
    #[clap(long, parse(from_os_str))]
    pub script_path: PathBuf,

    /// Git revision or branch for the Aptos framework
    ///
    /// If not provided, it won't be using the AptosFramework.  Keep in mind that this
    /// will only build correctly without the framework git revision when doing a full
    /// framework upgrade
    #[clap(long)]
    pub(crate) framework_git_rev: Option<String>,
}

impl CompileScriptFunction {
    pub(crate) fn compile(
        &self,
        prompt_options: PromptOptions,
    ) -> CliTypedResult<(Vec<u8>, HashValue)> {
        // Check script file
        let script_path = self.script_path.as_path();
        if !self.script_path.exists() {
            return Err(CliError::CommandArgumentError(format!(
                "{} does not exist",
                script_path.display()
            )));
        } else if self.script_path.is_dir() {
            return Err(CliError::CommandArgumentError(format!(
                "{} is a directory",
                script_path.display()
            )));
        }

        // Compile script
        compile_in_temp_dir(script_path, self.framework_git_rev.clone(), prompt_options)
    }
}

/// Generates a package upgrade proposal script.
#[derive(Parser)]
pub struct GenerateUpgradeProposal {
    /// Address of the account which the proposal addresses.
    #[clap(long, parse(try_from_str=crate::common::types::load_account_arg))]
    pub(crate) account: AccountAddress,

    /// Where to store the generated proposal
    #[clap(long, parse(from_os_str), default_value = "proposal.move")]
    pub(crate) output: PathBuf,

    /// What artifacts to include in the package. This can be one of `none`, `sparse`, and
    /// `all`. `none` is the most compact form and does not allow to reconstruct a source
    /// package from chain; `sparse` is the minimal set of artifacts needed to reconstruct
    /// a source package; `all` includes all available artifacts. The choice of included
    /// artifacts heavily influences the size and therefore gas cost of publishing: `none`
    /// is the size of bytecode alone; `sparse` is roughly 2 times as much; and `all` 3-4
    /// as much.
    #[clap(long, default_value_t = IncludedArtifacts::Sparse)]
    pub(crate) included_artifacts: IncludedArtifacts,

    #[clap(flatten)]
    pub(crate) move_options: MovePackageDir,
}

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

    async fn execute(self) -> CliTypedResult<()> {
        let GenerateUpgradeProposal {
            move_options,
            account,
            included_artifacts,
            output,
        } = self;
        let package_path = move_options.get_package_path()?;
        let options = included_artifacts.build_options(move_options.named_addresses());
        let package = BuiltPackage::build(package_path, options)?;
        let release = ReleasePackage::new(package)?;
        release.generate_script_proposal(account, output)?;
        Ok(())
    }
}
<commit_msg>[cli] remove groups from metadata<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::types::{
    CliError, CliTypedResult, MovePackageDir, PoolAddressArgs, PromptOptions, TransactionOptions,
    TransactionSummary,
};
use crate::common::utils::prompt_yes_with_override;
#[cfg(feature = "no-upload-proposal")]
use crate::common::utils::read_from_file;
use crate::move_tool::{init_move_dir, IncludedArtifacts};
use crate::{CliCommand, CliResult};
use aptos_crypto::HashValue;
use aptos_logger::warn;
use aptos_rest_client::aptos_api_types::U64;
use aptos_rest_client::Transaction;
use aptos_types::{
    account_address::AccountAddress,
    transaction::{Script, TransactionPayload},
};
use async_trait::async_trait;
use cached_packages::aptos_stdlib;
use clap::Parser;
use framework::{BuildOptions, BuiltPackage, ReleasePackage};
use move_deps::move_core_types::transaction_argument::TransactionArgument;
use reqwest::Url;
use serde::Deserialize;
use serde::Serialize;
use std::path::Path;
use std::{collections::BTreeMap, fmt::Formatter, fs, path::PathBuf};
use tempfile::TempDir;

/// 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),
    ExecuteProposal(ExecuteProposal),
    GenerateUpgradeProposal(GenerateUpgradeProposal),
}

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

/// Submit proposal to other validators to be proposed on
#[derive(Parser)]
pub struct SubmitProposal {
    /// Location of the JSON metadata of the proposal
    #[clap(long)]
    pub(crate) metadata_url: Url,

    #[cfg(feature = "no-upload-proposal")]
    /// A JSON file to be uploaded later at the metadata URL
    #[clap(long)]
    pub(crate) metadata_path: Option<PathBuf>,

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

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

    async fn execute(mut self) -> CliTypedResult<ProposalSubmissionSummary> {
        let (_bytecode, script_hash) = self
            .compile_proposal_args
            .compile(self.txn_options.prompt_options)?;

        // Validate the proposal metadata
        let (metadata, metadata_hash) = self.get_metadata().await?;

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

        let txn = self
            .txn_options
            .submit_transaction(
                aptos_stdlib::aptos_governance_create_proposal(
                    self.pool_address_args.pool_address,
                    script_hash.to_vec(),
                    self.metadata_url.to_string().as_bytes().to_vec(),
                    metadata_hash.to_hex().as_bytes().to_vec(),
                ),
                None,
            )
            .await?;
        let txn_summary = TransactionSummary::from(&txn);
        if let Transaction::UserTransaction(inner) = txn {
            // Find event with proposal id
            let proposal_id = if let Some(event) = inner.events.into_iter().find(|event| {
                event.typ.to_string().as_str() == "0x1::aptos_governance::CreateProposalEvent"
            }) {
                let data: CreateProposalEvent =
                    serde_json::from_value(event.data).map_err(|_| {
                        CliError::UnexpectedError(
                            "Failed to parse Proposal event to get ProposalId".to_string(),
                        )
                    })?;
                Some(data.proposal_id.0)
            } else {
                warn!("No proposal event found to find proposal id");
                None
            };

            return Ok(ProposalSubmissionSummary {
                proposal_id,
                transaction: txn_summary,
            });
        }
        Err(CliError::UnexpectedError(
            "Unable to find parse proposal transaction output".to_string(),
        ))
    }
}

impl SubmitProposal {
    /// Retrieve metadata and validate it
    async fn get_metadata(&self) -> CliTypedResult<(ProposalMetadata, HashValue)> {
        #[cfg(feature = "no-upload-proposal")]
        let bytes = if let Some(ref path) = self.metadata_path {
            read_from_file(path)?
        } else {
            get_metadata_from_url(&self.metadata_url).await?
        };
        #[cfg(not(feature = "no-upload-proposal"))]
        let bytes = get_metadata_from_url(&self.metadata_url).await?;

        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))
    }
}

async fn get_metadata_from_url(metadata_url: &Url) -> CliTypedResult<Vec<u8>> {
    let client = reqwest::ClientBuilder::default()
        .tls_built_in_root_certs(true)
        .build()
        .map_err(|err| CliError::UnexpectedError(format!("Failed to build HTTP client {}", err)))?;
    client
        .get(metadata_url.clone())
        .send()
        .await
        .map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Failed to fetch metadata url {}: {}",
                metadata_url, err
            ))
        })?
        .bytes()
        .await
        .map(|b| b.to_vec())
        .map_err(|err| {
            CliError::CommandArgumentError(format!(
                "Failed to fetch metadata url {}: {}",
                metadata_url, err
            ))
        })
}

#[derive(Debug, Deserialize, Serialize)]
struct CreateProposalEvent {
    proposal_id: U64,
}

#[derive(Debug, Deserialize, Serialize)]
struct ProposalSubmissionSummary {
    proposal_id: Option<u64>,
    #[serde(flatten)]
    transaction: TransactionSummary,
}

/// Submit a vote on a current proposal
#[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) txn_options: TransactionOptions,
    #[clap(flatten)]
    pub(crate) pool_address_args: PoolAddressArgs,
}

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

    async fn execute(mut self) -> CliTypedResult<TransactionSummary> {
        let (vote_str, vote) = match (self.yes, self.no) {
            (true, false) => ("Yes", true),
            (false, true) => ("No", false),
            (_, _) => {
                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_str),
            self.txn_options.prompt_options,
        )?;

        self.txn_options
            .submit_transaction(
                aptos_stdlib::aptos_governance_vote(
                    self.pool_address_args.pool_address,
                    self.proposal_id,
                    vote,
                ),
                None,
            )
            .await
            .map(TransactionSummary::from)
    }
}

#[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
        )
    }
}

fn compile_in_temp_dir(
    script_path: &Path,
    framework_rev: Option<String>,
    prompt_options: PromptOptions,
) -> CliTypedResult<(Vec<u8>, HashValue)> {
    // Make a temporary directory for compilation
    let temp_dir = TempDir::new().map_err(|err| {
        CliError::UnexpectedError(format!("Failed to create temporary directory {}", err))
    })?;

    // Initialize a move directory
    let package_dir = temp_dir.path();
    init_move_dir(
        package_dir,
        "Proposal",
        framework_rev,
        BTreeMap::new(),
        prompt_options,
        None,
    )?;

    // Insert the new script
    let sources_dir = package_dir.join("sources");
    let new_script_path = if let Some(file_name) = script_path.file_name() {
        sources_dir.join(file_name)
    } else {
        // If for some reason we can't get the move file
        sources_dir.join("script.move")
    };
    fs::copy(script_path, new_script_path.as_path()).map_err(|err| {
        CliError::IO(
            format!(
                "Failed to copy {} to {}",
                script_path.display(),
                new_script_path.display()
            ),
            err,
        )
    })?;

    // Compile the script
    compile_script(package_dir)
}

fn compile_script(package_dir: &Path) -> CliTypedResult<(Vec<u8>, HashValue)> {
    let build_options = BuildOptions {
        with_srcs: false,
        with_abis: false,
        with_source_maps: false,
        with_error_map: false,
        install_dir: None,
        named_addresses: Default::default(),
    };

    let pack = BuiltPackage::build(package_dir.to_path_buf(), build_options)?;

    let scripts_count = pack.script_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 bytes = pack.extract_script_code().pop().unwrap();
    let hash = HashValue::sha3_256_of(bytes.as_slice());
    Ok((bytes, hash))
}

/// Execute a proposal that has passed voting requirements
#[derive(Parser)]
pub struct ExecuteProposal {
    /// Proposal Id being executed
    #[clap(long)]
    pub(crate) proposal_id: u64,

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

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

    async fn execute(mut self) -> CliTypedResult<TransactionSummary> {
        let (bytecode, _script_hash) = self
            .compile_proposal_args
            .compile(self.txn_options.prompt_options)?;
        // TODO: Check hash so we don't do a failed roundtrip?

        let args = vec![TransactionArgument::U64(self.proposal_id)];
        let txn = TransactionPayload::Script(Script::new(bytecode, vec![], args));

        self.txn_options
            .submit_transaction(txn, None)
            .await
            .map(TransactionSummary::from)
    }
}

/// Execute a proposal that has passed voting requirements
#[derive(Parser)]
pub struct CompileScriptFunction {
    /// Path to the Move script for the proposal
    #[clap(long, parse(from_os_str))]
    pub script_path: PathBuf,

    /// Git revision or branch for the Aptos framework
    ///
    /// If not provided, it won't be using the AptosFramework.  Keep in mind that this
    /// will only build correctly without the framework git revision when doing a full
    /// framework upgrade
    #[clap(long)]
    pub(crate) framework_git_rev: Option<String>,
}

impl CompileScriptFunction {
    pub(crate) fn compile(
        &self,
        prompt_options: PromptOptions,
    ) -> CliTypedResult<(Vec<u8>, HashValue)> {
        // Check script file
        let script_path = self.script_path.as_path();
        if !self.script_path.exists() {
            return Err(CliError::CommandArgumentError(format!(
                "{} does not exist",
                script_path.display()
            )));
        } else if self.script_path.is_dir() {
            return Err(CliError::CommandArgumentError(format!(
                "{} is a directory",
                script_path.display()
            )));
        }

        // Compile script
        compile_in_temp_dir(script_path, self.framework_git_rev.clone(), prompt_options)
    }
}

/// Generates a package upgrade proposal script.
#[derive(Parser)]
pub struct GenerateUpgradeProposal {
    /// Address of the account which the proposal addresses.
    #[clap(long, parse(try_from_str=crate::common::types::load_account_arg))]
    pub(crate) account: AccountAddress,

    /// Where to store the generated proposal
    #[clap(long, parse(from_os_str), default_value = "proposal.move")]
    pub(crate) output: PathBuf,

    /// What artifacts to include in the package. This can be one of `none`, `sparse`, and
    /// `all`. `none` is the most compact form and does not allow to reconstruct a source
    /// package from chain; `sparse` is the minimal set of artifacts needed to reconstruct
    /// a source package; `all` includes all available artifacts. The choice of included
    /// artifacts heavily influences the size and therefore gas cost of publishing: `none`
    /// is the size of bytecode alone; `sparse` is roughly 2 times as much; and `all` 3-4
    /// as much.
    #[clap(long, default_value_t = IncludedArtifacts::Sparse)]
    pub(crate) included_artifacts: IncludedArtifacts,

    #[clap(flatten)]
    pub(crate) move_options: MovePackageDir,
}

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

    async fn execute(self) -> CliTypedResult<()> {
        let GenerateUpgradeProposal {
            move_options,
            account,
            included_artifacts,
            output,
        } = self;
        let package_path = move_options.get_package_path()?;
        let options = included_artifacts.build_options(move_options.named_addresses());
        let package = BuiltPackage::build(package_path, options)?;
        let release = ReleasePackage::new(package)?;
        release.generate_script_proposal(account, output)?;
        Ok(())
    }
}
<|endoftext|>


<commit_before>use hir::{AsAssocItem, Semantics};
use ide_db::{
    defs::{Definition, NameClass, NameRefClass},
    RootDatabase,
};
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};

use crate::{
    goto_definition::goto_definition, navigation_target::TryToNav, FilePosition, NavigationTarget,
    RangeInfo,
};

// Feature: Go to Declaration
//
// Navigates to the declaration of an identifier.
//
// This is the same as `Go to Definition` with the following exceptions:
// - outline modules will navigate to the `mod name;` item declaration
// - trait assoc items will navigate to the assoc item of the trait declaration opposed to the trait impl
pub(crate) fn goto_declaration(
    db: &RootDatabase,
    position: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
    let sema = Semantics::new(db);
    let file = sema.parse(position.file_id).syntax().clone();
    let original_token = file
        .token_at_offset(position.offset)
        .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
    let range = original_token.text_range();
    let info: Vec<NavigationTarget> = sema
        .descend_into_macros(original_token)
        .iter()
        .filter_map(|token| {
            let parent = token.parent()?;
            let def = match_ast! {
                match parent {
                    ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
                        NameRefClass::Definition(it) => Some(it),
                        _ => None
                    },
                    ast::Name(name) => match NameClass::classify(&sema, &name)? {
                        NameClass::Definition(it) => Some(it),
                        _ => None
                    },
                    _ => None
                }
            };
            let assoc = match def? {
                Definition::Module(module) => {
                    return Some(NavigationTarget::from_module_to_decl(db, module))
                }
                Definition::Const(c) => c.as_assoc_item(db),
                Definition::TypeAlias(ta) => ta.as_assoc_item(db),
                Definition::Function(f) => f.as_assoc_item(db),
                _ => None,
            }?;

            let trait_ = assoc.containing_trait_impl(db)?;
            let name = Some(assoc.name(db)?);
            let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
            item.try_to_nav(db)
        })
        .collect();

    if info.is_empty() {
        goto_definition(db, position)
    } else {
        Some(RangeInfo::new(range, info))
    }
}

#[cfg(test)]
mod tests {
    use ide_db::base_db::FileRange;
    use itertools::Itertools;

    use crate::fixture;

    fn check(ra_fixture: &str) {
        let (analysis, position, expected) = fixture::annotations(ra_fixture);
        let navs = analysis
            .goto_declaration(position)
            .unwrap()
            .expect("no declaration or definition found")
            .info;
        if navs.is_empty() {
            panic!("unresolved reference")
        }

        let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
        let navs = navs
            .into_iter()
            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        let expected = expected
            .into_iter()
            .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        assert_eq!(expected, navs);
    }

    #[test]
    fn goto_decl_module_outline() {
        check(
            r#"
//- /main.rs
mod foo;
 // ^^^
//- /foo.rs
use self$0;
"#,
        )
    }

    #[test]
    fn goto_decl_module_inline() {
        check(
            r#"
mod foo {
 // ^^^
    use self$0;
}
"#,
        )
    }

    #[test]
    fn goto_decl_goto_def_fallback() {
        check(
            r#"
struct Foo;
    // ^^^
impl Foo$0 {}
"#,
        );
    }

    #[test]
    fn goto_decl_assoc_item_no_impl_item() {
        check(
            r#"
trait Trait {
    const C: () = ();
       // ^
}
impl Trait for () {}

fn main() {
    <()>::C$0;
}
"#,
        );
    }

    #[test]
    fn goto_decl_assoc_item() {
        check(
            r#"
trait Trait {
    const C: () = ();
       // ^
}
impl Trait for () {
    const C: () = ();
}

fn main() {
    <()>::C$0;
}
"#,
        );
        check(
            r#"
trait Trait {
    const C: () = ();
       // ^
}
impl Trait for () {
    const C$0: () = ();
}
"#,
        );
    }
}
<commit_msg>Go to declaration goes to field declaration in pattern and expression shorthands<commit_after>use hir::{AsAssocItem, Semantics};
use ide_db::{
    defs::{Definition, NameClass, NameRefClass},
    RootDatabase,
};
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};

use crate::{
    goto_definition::goto_definition, navigation_target::TryToNav, FilePosition, NavigationTarget,
    RangeInfo,
};

// Feature: Go to Declaration
//
// Navigates to the declaration of an identifier.
//
// This is the same as `Go to Definition` with the following exceptions:
// - outline modules will navigate to the `mod name;` item declaration
// - trait assoc items will navigate to the assoc item of the trait declaration opposed to the trait impl
pub(crate) fn goto_declaration(
    db: &RootDatabase,
    position: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
    let sema = Semantics::new(db);
    let file = sema.parse(position.file_id).syntax().clone();
    let original_token = file
        .token_at_offset(position.offset)
        .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
    let range = original_token.text_range();
    let info: Vec<NavigationTarget> = sema
        .descend_into_macros(original_token)
        .iter()
        .filter_map(|token| {
            let parent = token.parent()?;
            let def = match_ast! {
                match parent {
                    ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
                        NameRefClass::Definition(it) => Some(it),
                        NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
                    },
                    ast::Name(name) => match NameClass::classify(&sema, &name)? {
                        NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
                        NameClass::PatFieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
                    },
                    _ => None
                }
            };
            let assoc = match def? {
                Definition::Module(module) => {
                    return Some(NavigationTarget::from_module_to_decl(db, module))
                }
                Definition::Const(c) => c.as_assoc_item(db),
                Definition::TypeAlias(ta) => ta.as_assoc_item(db),
                Definition::Function(f) => f.as_assoc_item(db),
                _ => None,
            }?;

            let trait_ = assoc.containing_trait_impl(db)?;
            let name = Some(assoc.name(db)?);
            let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
            item.try_to_nav(db)
        })
        .collect();

    if info.is_empty() {
        goto_definition(db, position)
    } else {
        Some(RangeInfo::new(range, info))
    }
}

#[cfg(test)]
mod tests {
    use ide_db::base_db::FileRange;
    use itertools::Itertools;

    use crate::fixture;

    fn check(ra_fixture: &str) {
        let (analysis, position, expected) = fixture::annotations(ra_fixture);
        let navs = analysis
            .goto_declaration(position)
            .unwrap()
            .expect("no declaration or definition found")
            .info;
        if navs.is_empty() {
            panic!("unresolved reference")
        }

        let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
        let navs = navs
            .into_iter()
            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        let expected = expected
            .into_iter()
            .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        assert_eq!(expected, navs);
    }

    #[test]
    fn goto_decl_module_outline() {
        check(
            r#"
//- /main.rs
mod foo;
 // ^^^
//- /foo.rs
use self$0;
"#,
        )
    }

    #[test]
    fn goto_decl_module_inline() {
        check(
            r#"
mod foo {
 // ^^^
    use self$0;
}
"#,
        )
    }

    #[test]
    fn goto_decl_goto_def_fallback() {
        check(
            r#"
struct Foo;
    // ^^^
impl Foo$0 {}
"#,
        );
    }

    #[test]
    fn goto_decl_assoc_item_no_impl_item() {
        check(
            r#"
trait Trait {
    const C: () = ();
       // ^
}
impl Trait for () {}

fn main() {
    <()>::C$0;
}
"#,
        );
    }

    #[test]
    fn goto_decl_assoc_item() {
        check(
            r#"
trait Trait {
    const C: () = ();
       // ^
}
impl Trait for () {
    const C: () = ();
}

fn main() {
    <()>::C$0;
}
"#,
        );
        check(
            r#"
trait Trait {
    const C: () = ();
       // ^
}
impl Trait for () {
    const C$0: () = ();
}
"#,
        );
    }

    #[test]
    fn goto_decl_field_pat_shorthand() {
        check(
            r#"
struct Foo { field: u32 }
           //^^^^^
fn main() {
    let Foo { field$0 };
}
"#,
        );
    }

    #[test]
    fn goto_decl_constructor_shorthand() {
        check(
            r#"
struct Foo { field: u32 }
           //^^^^^
fn main() {
    let field = 0;
    Foo { field$0 };
}
"#,
        );
    }
}
<|endoftext|>


<commit_before>use itertools::Itertools;

use crate::{
    syntax_node::{SyntaxNodeChildren, SyntaxElementChildren},
    ast::{self, child_opt, children, AstNode, AstToken, AstChildren},
};

pub trait TypeAscriptionOwner: AstNode {
    fn ascribed_type(&self) -> Option<&ast::TypeRef> {
        child_opt(self)
    }
}

pub trait NameOwner: AstNode {
    fn name(&self) -> Option<&ast::Name> {
        child_opt(self)
    }
}

pub trait VisibilityOwner: AstNode {
    fn visibility(&self) -> Option<&ast::Visibility> {
        child_opt(self)
    }
}

pub trait LoopBodyOwner: AstNode {
    fn loop_body(&self) -> Option<&ast::Block> {
        child_opt(self)
    }
}

pub trait ArgListOwner: AstNode {
    fn arg_list(&self) -> Option<&ast::ArgList> {
        child_opt(self)
    }
}

pub trait FnDefOwner: AstNode {
    fn functions(&self) -> AstChildren<ast::FnDef> {
        children(self)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ItemOrMacro<'a> {
    Item(&'a ast::ModuleItem),
    Macro(&'a ast::MacroCall),
}

pub trait ModuleItemOwner: AstNode {
    fn items(&self) -> AstChildren<ast::ModuleItem> {
        children(self)
    }
    fn items_with_macros(&self) -> ItemOrMacroIter {
        ItemOrMacroIter(self.syntax().children())
    }
}

#[derive(Debug)]
pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>);

impl<'a> Iterator for ItemOrMacroIter<'a> {
    type Item = ItemOrMacro<'a>;
    fn next(&mut self) -> Option<ItemOrMacro<'a>> {
        loop {
            let n = self.0.next()?;
            if let Some(item) = ast::ModuleItem::cast(n) {
                return Some(ItemOrMacro::Item(item));
            }
            if let Some(call) = ast::MacroCall::cast(n) {
                return Some(ItemOrMacro::Macro(call));
            }
        }
    }
}

pub trait TypeParamsOwner: AstNode {
    fn type_param_list(&self) -> Option<&ast::TypeParamList> {
        child_opt(self)
    }

    fn where_clause(&self) -> Option<&ast::WhereClause> {
        child_opt(self)
    }
}

pub trait TypeBoundsOwner: AstNode {
    fn type_bound_list(&self) -> Option<&ast::TypeBoundList> {
        child_opt(self)
    }
}

pub trait AttrsOwner: AstNode {
    fn attrs(&self) -> AstChildren<ast::Attr> {
        children(self)
    }
    fn has_atom_attr(&self, atom: &str) -> bool {
        self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
    }
}

pub trait DocCommentsOwner: AstNode {
    fn doc_comments(&self) -> CommentIter {
        CommentIter { iter: self.syntax().children_with_tokens() }
    }

    /// Returns the textual content of a doc comment block as a single string.
    /// That is, strips leading `///` (+ optional 1 character of whitespace)
    /// and joins lines.
    fn doc_comment_text(&self) -> Option<std::string::String> {
        let docs = self
            .doc_comments()
            .filter(|comment| comment.is_doc_comment())
            .map(|comment| {
                let prefix_len = comment.prefix().len();

                let line = comment.text().as_str();

                // Determine if the prefix or prefix + 1 char is stripped
                let pos =
                    if line.chars().nth(prefix_len).map(|c| c.is_whitespace()).unwrap_or(false) {
                        prefix_len + 1
                    } else {
                        prefix_len
                    };

                line[pos..].to_owned()
            })
            .join("\n");

        if docs.is_empty() {
            None
        } else {
            Some(docs)
        }
    }
}

pub struct CommentIter<'a> {
    iter: SyntaxElementChildren<'a>,
}

impl<'a> Iterator for CommentIter<'a> {
    type Item = ast::Comment<'a>;
    fn next(&mut self) -> Option<ast::Comment<'a>> {
        self.iter.by_ref().find_map(|el| el.as_token().and_then(ast::Comment::cast))
    }
}
<commit_msg>allow empty doc comments<commit_after>use itertools::Itertools;

use crate::{
    syntax_node::{SyntaxNodeChildren, SyntaxElementChildren},
    ast::{self, child_opt, children, AstNode, AstToken, AstChildren},
};

pub trait TypeAscriptionOwner: AstNode {
    fn ascribed_type(&self) -> Option<&ast::TypeRef> {
        child_opt(self)
    }
}

pub trait NameOwner: AstNode {
    fn name(&self) -> Option<&ast::Name> {
        child_opt(self)
    }
}

pub trait VisibilityOwner: AstNode {
    fn visibility(&self) -> Option<&ast::Visibility> {
        child_opt(self)
    }
}

pub trait LoopBodyOwner: AstNode {
    fn loop_body(&self) -> Option<&ast::Block> {
        child_opt(self)
    }
}

pub trait ArgListOwner: AstNode {
    fn arg_list(&self) -> Option<&ast::ArgList> {
        child_opt(self)
    }
}

pub trait FnDefOwner: AstNode {
    fn functions(&self) -> AstChildren<ast::FnDef> {
        children(self)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ItemOrMacro<'a> {
    Item(&'a ast::ModuleItem),
    Macro(&'a ast::MacroCall),
}

pub trait ModuleItemOwner: AstNode {
    fn items(&self) -> AstChildren<ast::ModuleItem> {
        children(self)
    }
    fn items_with_macros(&self) -> ItemOrMacroIter {
        ItemOrMacroIter(self.syntax().children())
    }
}

#[derive(Debug)]
pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>);

impl<'a> Iterator for ItemOrMacroIter<'a> {
    type Item = ItemOrMacro<'a>;
    fn next(&mut self) -> Option<ItemOrMacro<'a>> {
        loop {
            let n = self.0.next()?;
            if let Some(item) = ast::ModuleItem::cast(n) {
                return Some(ItemOrMacro::Item(item));
            }
            if let Some(call) = ast::MacroCall::cast(n) {
                return Some(ItemOrMacro::Macro(call));
            }
        }
    }
}

pub trait TypeParamsOwner: AstNode {
    fn type_param_list(&self) -> Option<&ast::TypeParamList> {
        child_opt(self)
    }

    fn where_clause(&self) -> Option<&ast::WhereClause> {
        child_opt(self)
    }
}

pub trait TypeBoundsOwner: AstNode {
    fn type_bound_list(&self) -> Option<&ast::TypeBoundList> {
        child_opt(self)
    }
}

pub trait AttrsOwner: AstNode {
    fn attrs(&self) -> AstChildren<ast::Attr> {
        children(self)
    }
    fn has_atom_attr(&self, atom: &str) -> bool {
        self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
    }
}

pub trait DocCommentsOwner: AstNode {
    fn doc_comments(&self) -> CommentIter {
        CommentIter { iter: self.syntax().children_with_tokens() }
    }

    /// Returns the textual content of a doc comment block as a single string.
    /// That is, strips leading `///` (+ optional 1 character of whitespace)
    /// and joins lines.
    fn doc_comment_text(&self) -> Option<String> {
        let mut has_comments = false;
        let docs = self
            .doc_comments()
            .filter(|comment| comment.is_doc_comment())
            .map(|comment| {
                has_comments = true;
                let prefix_len = comment.prefix().len();

                let line = comment.text().as_str();

                // Determine if the prefix or prefix + 1 char is stripped
                let pos =
                    if line.chars().nth(prefix_len).map(|c| c.is_whitespace()).unwrap_or(false) {
                        prefix_len + 1
                    } else {
                        prefix_len
                    };

                line[pos..].to_owned()
            })
            .join("\n");

        if has_comments {
            Some(docs)
        } else {
            None
        }
    }
}

pub struct CommentIter<'a> {
    iter: SyntaxElementChildren<'a>,
}

impl<'a> Iterator for CommentIter<'a> {
    type Item = ast::Comment<'a>;
    fn next(&mut self) -> Option<ast::Comment<'a>> {
        self.iter.by_ref().find_map(|el| el.as_token().and_then(ast::Comment::cast))
    }
}
<|endoftext|>


<commit_before>// Copyright (c) Facebook, Inc. and its affiliates
// SPDX-License-Identifier: MIT OR Apache-2.0

//! # Serde code generator
//!
//! '''bash
//! cargo run --bin serdegen -- --help
//! '''

use serde_generate::{
    cpp, csharp, dart, golang, java, python3, rust, typescript, CodeGeneratorConfig, Encoding,
    SourceInstaller,
};
use serde_reflection::Registry;
use std::path::PathBuf;
use structopt::{clap::arg_enum, StructOpt};

arg_enum! {
#[derive(Debug, StructOpt)]
enum Language {
    Python3,
    Cpp,
    Rust,
    Java,
    Go,
    TypeScript,
    CSharp,
}
}

arg_enum! {
#[derive(Debug, StructOpt, PartialEq, Eq, PartialOrd, Ord)]
enum Runtime {
    Serde,
    Bincode,
    Bcs,
}
}

#[derive(Debug, StructOpt)]
#[structopt(
    name = "Serde code generator",
    about = "Generate code for Serde containers"
)]
struct Options {
    /// Path to the YAML-encoded Serde formats.
    #[structopt(parse(from_os_str))]
    input: Option<PathBuf>,

    /// Language for code generation.
    #[structopt(long, possible_values = &Language::variants(), case_insensitive = true, default_value = "Python3")]
    language: Language,

    /// Directory where to write generated modules (otherwise print code on stdout).
    #[structopt(long)]
    target_source_dir: Option<PathBuf>,

    /// Optional runtimes to install in the `target_source_dir` (if applicable).
    /// Also triggers the generation of specialized methods for each runtime.
    #[structopt(long, possible_values = &Runtime::variants(), case_insensitive = true)]
    with_runtimes: Vec<Runtime>,

    /// Module name for the Serde formats installed in the `target_source_dir`.
    /// Rust crates may contain a version number separated with a colon, e.g. "test:1.2.0".
    /// (By default, the installer will use version "0.1.0".)
    #[structopt(long)]
    module_name: Option<String>,

    /// Optional package name (Python) or module path (Go) where to find Serde runtime dependencies.
    #[structopt(long)]
    serde_package_name: Option<String>,

    /// Translate enums without variant data (c-style enums) into their equivalent in the target language,
    /// if the target language and the generator code support them.
    #[structopt(long)]
    use_c_style_enums: bool,
}

fn get_codegen_config<'a, I>(name: String, runtimes: I, c_style_enums: bool) -> CodeGeneratorConfig
where
    I: IntoIterator<Item = &'a Runtime>,
{
    let mut encodings = Vec::new();
    for runtime in runtimes {
        match runtime {
            Runtime::Bincode => {
                encodings.push(Encoding::Bincode);
            }
            Runtime::Bcs => {
                encodings.push(Encoding::Bcs);
            }
            _ => (),
        }
    }
    CodeGeneratorConfig::new(name)
        .with_encodings(encodings)
        .with_c_style_enums(c_style_enums)
}

fn main() {
    let options = Options::from_args();
    let serde_package_name_opt = options.serde_package_name.clone();
    let named_registry_opt = match &options.input {
        None => None,
        Some(input) => {
            let name = options.module_name.clone().unwrap_or_else(|| {
                input
                    .file_stem()
                    .expect("failed to deduce module name from input path")
                    .to_string_lossy()
                    .into_owned()
            });
            let content = std::fs::read_to_string(input).expect("input file must be readable");
            let registry = serde_yaml::from_str::<Registry>(content.as_str()).unwrap();
            Some((registry, name))
        }
    };
    let runtimes: std::collections::BTreeSet<_> = options.with_runtimes.into_iter().collect();

    match options.target_source_dir {
        None => {
            if let Some((registry, name)) = named_registry_opt {
                let config = get_codegen_config(name, &runtimes, options.use_c_style_enums);

                let stdout = std::io::stdout();
                let mut out = stdout.lock();
                match options.language {
                    Language::Python3 => python3::CodeGenerator::new(&config)
                        .with_serde_package_name(serde_package_name_opt)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Rust => rust::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Cpp => cpp::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Go => golang::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Java => {
                        panic!("Code generation in Java requires `--target-source-dir`")
                    }
                    Language::TypeScript => typescript::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::CSharp => {
                        panic!("Code generation in C# requires `--target-source-dir`")
                    }
                }
            }
        }

        Some(install_dir) => {
            let installer: Box<dyn SourceInstaller<Error = Box<dyn std::error::Error>>> =
                match options.language {
                    Language::Python3 => {
                        Box::new(python3::Installer::new(install_dir, serde_package_name_opt))
                    }
                    Language::Rust => Box::new(rust::Installer::new(install_dir)),
                    Language::Cpp => Box::new(cpp::Installer::new(install_dir)),
                    Language::Java => Box::new(java::Installer::new(install_dir)),
                    Language::Go => {
                        Box::new(golang::Installer::new(install_dir, serde_package_name_opt))
                    }
                    Language::TypeScript => Box::new(typescript::Installer::new(install_dir)),
                    Language::CSharp => Box::new(csharp::Installer::new(install_dir)),
                };

            if let Some((registry, name)) = named_registry_opt {
                let config = get_codegen_config(name, &runtimes, options.use_c_style_enums);
                installer.install_module(&config, &registry).unwrap();
            }

            for runtime in runtimes {
                match runtime {
                    Runtime::Serde => installer.install_serde_runtime().unwrap(),
                    Runtime::Bincode => installer.install_bincode_runtime().unwrap(),
                    Runtime::Bcs => installer.install_bcs_runtime().unwrap(),
                }
            }
        }
    }
}
<commit_msg>fix lint problem<commit_after>// Copyright (c) Facebook, Inc. and its affiliates
// SPDX-License-Identifier: MIT OR Apache-2.0

//! # Serde code generator
//!
//! '''bash
//! cargo run --bin serdegen -- --help
//! '''

use serde_generate::{
    cpp, dart, golang, java, python3, rust, typescript, CodeGeneratorConfig, Encoding,
    SourceInstaller,
};
use serde_reflection::Registry;
use std::path::PathBuf;
use structopt::{clap::arg_enum, StructOpt};

arg_enum! {
#[derive(Debug, StructOpt)]
enum Language {
    Python3,
    Cpp,
    Rust,
    Java,
    Go,
    TypeScript,
    CSharp,
}
}

arg_enum! {
#[derive(Debug, StructOpt, PartialEq, Eq, PartialOrd, Ord)]
enum Runtime {
    Serde,
    Bincode,
    Bcs,
}
}

#[derive(Debug, StructOpt)]
#[structopt(
    name = "Serde code generator",
    about = "Generate code for Serde containers"
)]
struct Options {
    /// Path to the YAML-encoded Serde formats.
    #[structopt(parse(from_os_str))]
    input: Option<PathBuf>,

    /// Language for code generation.
    #[structopt(long, possible_values = &Language::variants(), case_insensitive = true, default_value = "Python3")]
    language: Language,

    /// Directory where to write generated modules (otherwise print code on stdout).
    #[structopt(long)]
    target_source_dir: Option<PathBuf>,

    /// Optional runtimes to install in the `target_source_dir` (if applicable).
    /// Also triggers the generation of specialized methods for each runtime.
    #[structopt(long, possible_values = &Runtime::variants(), case_insensitive = true)]
    with_runtimes: Vec<Runtime>,

    /// Module name for the Serde formats installed in the `target_source_dir`.
    /// Rust crates may contain a version number separated with a colon, e.g. "test:1.2.0".
    /// (By default, the installer will use version "0.1.0".)
    #[structopt(long)]
    module_name: Option<String>,

    /// Optional package name (Python) or module path (Go) where to find Serde runtime dependencies.
    #[structopt(long)]
    serde_package_name: Option<String>,

    /// Translate enums without variant data (c-style enums) into their equivalent in the target language,
    /// if the target language and the generator code support them.
    #[structopt(long)]
    use_c_style_enums: bool,
}

fn get_codegen_config<'a, I>(name: String, runtimes: I, c_style_enums: bool) -> CodeGeneratorConfig
where
    I: IntoIterator<Item = &'a Runtime>,
{
    let mut encodings = Vec::new();
    for runtime in runtimes {
        match runtime {
            Runtime::Bincode => {
                encodings.push(Encoding::Bincode);
            }
            Runtime::Bcs => {
                encodings.push(Encoding::Bcs);
            }
            _ => (),
        }
    }
    CodeGeneratorConfig::new(name)
        .with_encodings(encodings)
        .with_c_style_enums(c_style_enums)
}

fn main() {
    let options = Options::from_args();
    let serde_package_name_opt = options.serde_package_name.clone();
    let named_registry_opt = match &options.input {
        None => None,
        Some(input) => {
            let name = options.module_name.clone().unwrap_or_else(|| {
                input
                    .file_stem()
                    .expect("failed to deduce module name from input path")
                    .to_string_lossy()
                    .into_owned()
            });
            let content = std::fs::read_to_string(input).expect("input file must be readable");
            let registry = serde_yaml::from_str::<Registry>(content.as_str()).unwrap();
            Some((registry, name))
        }
    };
    let runtimes: std::collections::BTreeSet<_> = options.with_runtimes.into_iter().collect();

    match options.target_source_dir {
        None => {
            if let Some((registry, name)) = named_registry_opt {
                let config = get_codegen_config(name, &runtimes, options.use_c_style_enums);

                let stdout = std::io::stdout();
                let mut out = stdout.lock();
                match options.language {
                    Language::Python3 => python3::CodeGenerator::new(&config)
                        .with_serde_package_name(serde_package_name_opt)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Rust => rust::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Cpp => cpp::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Go => golang::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::Java => {
                        panic!("Code generation in Java requires `--target-source-dir`")
                    }
                    Language::TypeScript => typescript::CodeGenerator::new(&config)
                        .output(&mut out, &registry)
                        .unwrap(),
                    Language::CSharp => {
                        panic!("Code generation in C# requires `--target-source-dir`")
                    }
                }
            }
        }

        Some(install_dir) => {
            let installer: Box<dyn SourceInstaller<Error = Box<dyn std::error::Error>>> =
                match options.language {
                    Language::Python3 => {
                        Box::new(python3::Installer::new(install_dir, serde_package_name_opt))
                    }
                    Language::Rust => Box::new(rust::Installer::new(install_dir)),
                    Language::Cpp => Box::new(cpp::Installer::new(install_dir)),
                    Language::Java => Box::new(java::Installer::new(install_dir)),
                    Language::Go => {
                        Box::new(golang::Installer::new(install_dir, serde_package_name_opt))
                    }
                    Language::Dart => Box::new(dart::Installer::new(install_dir)),
                    Language::TypeScript => Box::new(typescript::Installer::new(install_dir)),
                    Language::CSharp => Box::new(csharp::Installer::new(install_dir)),
                };

            if let Some((registry, name)) = named_registry_opt {
                let config = get_codegen_config(name, &runtimes, options.use_c_style_enums);
                installer.install_module(&config, &registry).unwrap();
            }

            for runtime in runtimes {
                match runtime {
                    Runtime::Serde => installer.install_serde_runtime().unwrap(),
                    Runtime::Bincode => installer.install_bincode_runtime().unwrap(),
                    Runtime::Bcs => installer.install_bcs_runtime().unwrap(),
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>fn main() {
    // This binding lives in the main function
    let long_lived_binding = 1;

    // This is a block, and has a smaller scope than the main function
    {
        // This binding only exists in this block
        let short_lived_binding = 2;

        println!("inner short: {}", short_lived_binding);

        // This binding *shadows* the outer one
        let long_lived_binding = 5_f32;

        println!("inner long: {}", long_lived_binding);
    }
    // End of the block

    // Error! `short_lived_binding` doesn't exist in this scope
    println!("outer short: {}", short_lived_binding);
    // FIXME ^ Comment out this line

    println!("outer long: {}", long_lived_binding);
}
<commit_msg>Add same scope binding shadowing<commit_after>fn main() {
    // This binding lives in the main function
    let long_lived_binding = 1;

    // This is a block, and has a smaller scope than the main function
    {
        // This binding only exists in this block
        let short_lived_binding = 2;

        println!("inner short: {}", short_lived_binding);

        // This binding *shadows* the outer one
        let long_lived_binding = 5_f32;

        println!("inner long: {}", long_lived_binding);
    }
    // End of the block

    // Error! `short_lived_binding` doesn't exist in this scope
    println!("outer short: {}", short_lived_binding);
    // FIXME ^ Comment out this line

    println!("outer long: {}", long_lived_binding);

    // This binding also *shadows* the previous binding
    let long_lived_binding = 'a';

    println!("outer long: {}", long_lived_binding);
}
<|endoftext|>


<commit_before>#![feature(test, proc_macro_hygiene)]

extern crate test;

mod map {
    use std::collections::{BTreeMap, HashMap};
    use test::Bencher;
    use phf_macros::phf_map;
    use phf;

    macro_rules! map_and_match {
        ($map:ident, $f:ident, $($key:expr => $value:expr,)+) => {
            static $map: phf::Map<&'static str, usize> = phf_map! {
                $($key => $value),+
            };

            fn $f(key: &str) -> Option<usize> {
                match key {
                    $($key => Some($value),)+
                    _ => None
                }
            }
        }
    }

    map_and_match! { MAP, match_get,
        "apple" => 0,
        "banana" => 1,
        "carrot" => 2,
        "doughnut" => 3,
        "eggplant" => 4,
        "frankincene" => 5,
        "grapes" => 6,
        "haggis" => 7,
        "ice cream" => 8,
        "jelly beans" => 9,
        "kaffir lime leaves" => 10,
        "lemonade" => 11,
        "mashmallows" => 12,
        "nectarines" => 13,
        "oranges" => 14,
        "pineapples" => 15,
        "quinoa" => 16,
        "rosemary" => 17,
        "sourdough" => 18,
        "tomatoes" => 19,
        "unleavened bread" => 20,
        "vanilla" => 21,
        "watermelon" => 22,
        "xinomavro grapes" => 23,
        "yogurt" => 24,
        "zucchini" => 25,
    }

    #[bench]
    fn bench_match_some(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(match_get("zucchini").unwrap(), 25);
        })
    }

    #[bench]
    fn bench_match_none(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(match_get("potato"), None);
        })
    }

    #[bench]
    fn bench_btreemap_some(b: &mut Bencher) {
        let mut map = BTreeMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("zucchini").unwrap(), &25);
        })
    }

    #[bench]
    fn bench_hashmap_some(b: &mut Bencher) {
        let mut map = HashMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("zucchini").unwrap(), &25);
        })
    }

    #[bench]
    fn bench_phf_some(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(MAP.get("zucchini").unwrap(), &25);
        })
    }

    #[bench]
    fn bench_btreemap_none(b: &mut Bencher) {
        let mut map = BTreeMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("potato"), None);
        })
    }

    #[bench]
    fn bench_hashmap_none(b: &mut Bencher) {
        let mut map = BTreeMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("potato"), None);
        })
    }

    #[bench]
    fn bench_phf_none(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(MAP.get("potato"), None);
        })
    }
}
<commit_msg>Fixed typo in benchmark<commit_after>#![feature(test, proc_macro_hygiene)]

extern crate test;

mod map {
    use std::collections::{BTreeMap, HashMap};
    use test::Bencher;
    use phf_macros::phf_map;
    use phf;

    macro_rules! map_and_match {
        ($map:ident, $f:ident, $($key:expr => $value:expr,)+) => {
            static $map: phf::Map<&'static str, usize> = phf_map! {
                $($key => $value),+
            };

            fn $f(key: &str) -> Option<usize> {
                match key {
                    $($key => Some($value),)+
                    _ => None
                }
            }
        }
    }

    map_and_match! { MAP, match_get,
        "apple" => 0,
        "banana" => 1,
        "carrot" => 2,
        "doughnut" => 3,
        "eggplant" => 4,
        "frankincene" => 5,
        "grapes" => 6,
        "haggis" => 7,
        "ice cream" => 8,
        "jelly beans" => 9,
        "kaffir lime leaves" => 10,
        "lemonade" => 11,
        "mashmallows" => 12,
        "nectarines" => 13,
        "oranges" => 14,
        "pineapples" => 15,
        "quinoa" => 16,
        "rosemary" => 17,
        "sourdough" => 18,
        "tomatoes" => 19,
        "unleavened bread" => 20,
        "vanilla" => 21,
        "watermelon" => 22,
        "xinomavro grapes" => 23,
        "yogurt" => 24,
        "zucchini" => 25,
    }

    #[bench]
    fn bench_match_some(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(match_get("zucchini").unwrap(), 25);
        })
    }

    #[bench]
    fn bench_match_none(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(match_get("potato"), None);
        })
    }

    #[bench]
    fn bench_btreemap_some(b: &mut Bencher) {
        let mut map = BTreeMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("zucchini").unwrap(), &25);
        })
    }

    #[bench]
    fn bench_hashmap_some(b: &mut Bencher) {
        let mut map = HashMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("zucchini").unwrap(), &25);
        })
    }

    #[bench]
    fn bench_phf_some(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(MAP.get("zucchini").unwrap(), &25);
        })
    }

    #[bench]
    fn bench_btreemap_none(b: &mut Bencher) {
        let mut map = BTreeMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("potato"), None);
        })
    }

    #[bench]
    fn bench_hashmap_none(b: &mut Bencher) {
        let mut map = HashMap::new();
        for (key, value) in MAP.entries() {
            map.insert(*key, *value);
        }

        b.iter(|| {
            assert_eq!(map.get("potato"), None);
        })
    }

    #[bench]
    fn bench_phf_none(b: &mut Bencher) {
        b.iter(|| {
            assert_eq!(MAP.get("potato"), None);
        })
    }
}
<|endoftext|>


<commit_before>extern crate rand;
extern crate djinn;
extern crate redis;
extern crate rustc_serialize;

use rand::Rng;
use redis::Client;
use djinn::{Agent, Manager, Simulation, Population, Updates, Redis, run};
use rand::distributions::{Weighted, WeightedChoice, IndependentSample};

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Opinion {
    polarity: f64,
    priority: f64,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Person {
    opinions: Vec<Opinion>,
    medias: Vec<Edge>,
    friends: Vec<Edge>,
}

impl Person {
    pub fn new(opinions: Vec<Opinion>) -> Person {
        Person {
            opinions: opinions,
            medias: Vec::new(),
            friends: Vec::new(),
        }
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Edge {
    to: u64,
    weight: u32,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum EdgeType {
    Media,
    Friend,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Media {
    opinions: Vec<Opinion>,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum State {
    Person(Person),
    Media(Media),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum PersonUpdate {
    OpinionShift {
        idx: usize,
        polarity: f64,
        priority: f64,
    },
    TrustShift {
        id: u64,
        shift: f64,
        edgeType: EdgeType,
    },
    Meet { id: u64, trust: f64 },
    Discover { id: u64, trust: f64 },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum MediaUpdate {
    OpinionShift {
        idx: usize,
        polarity: f64,
        priority: f64,
    },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum Update {
    Person(PersonUpdate),
    Media(MediaUpdate),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct World {
    weather: String,
}

#[derive(Clone)]
struct OpinionDynamicsSim;

impl OpinionDynamicsSim {
    fn decide_person<R: Redis>(&self,
                               id: u64,
                               person: &Person,
                               pop: &Population<Self, R>,
                               updates: &mut Updates<Self>)
                               -> () {
        let mut rng = rand::weak_rng();

        // talk to a person or consume media?
        if rng.gen::<f64>() < 0.5 {
            // choose a media.
            // the less media this person is familiar with,
            // the more likely they will encounter a random one.
            // otherwise, they choose one with probability based on how much they trust it.
            let p_rand_media = (person.medias.len() as f64) / 2.; // TODO denom should be a config val
            let media = if rng.gen::<f64>() < p_rand_media {
                let m = pop.random("media");

                // create an edge
                // TODO check if already has edge to this one
                updates.queue(id,
                              Update::Person(PersonUpdate::Discover {
                                  id: id,
                                  trust: 0., // TODO bootstrap trust in some way
                              }));
                m
            } else {
                let mut items: Vec<Weighted<u64>> = person.medias
                    .iter()
                    .map(|e| {
                        Weighted {
                            item: e.to,
                            weight: e.weight,
                        }
                    })
                    .collect();
                let wc = WeightedChoice::new(&mut items);
                let id = wc.ind_sample(&mut rng);
                pop.get_agent(id).unwrap()
            };
        } else {
            // choose a person to talk to.
            let p_rand_person = (person.friends.len() as f64) / 2.; // TODO denom should be a config val
            let person = if rng.gen::<f64>() < p_rand_person {
                let p = pop.random("person"); // TODO prob shouldnt be themselves
                // create an edge
                // TODO check if already has edge to this one
                updates.queue(id,
                              Update::Person(PersonUpdate::Meet {
                                  id: id,
                                  trust: 0., // TODO bootstrap trust in some way
                              }));
                p
            } else {
                let mut items: Vec<Weighted<u64>> = person.friends
                    .iter()
                    .map(|e| {
                        Weighted {
                            item: e.to,
                            weight: e.weight,
                        }
                    })
                    .collect();
                let wc = WeightedChoice::new(&mut items);
                let id = wc.ind_sample(&mut rng);
                pop.get_agent(id).unwrap()
            };
        }
    }
}

impl Simulation for OpinionDynamicsSim {
    type State = State;
    type Update = Update;
    type World = World;

    fn decide<R: Redis>(&self,
                        agent: &Agent<Self::State>,
                        world: &Self::World,
                        pop: &Population<Self, R>,
                        updates: &mut Updates<Self>)
                        -> () {
        match agent.state {
            State::Person(ref p) => {
                self.decide_person(agent.id, p, pop, updates);
            }
            State::Media(ref m) => {
                // TODO
            }
        }
    }

    fn update(&self, mut state: &mut Self::State, updates: Vec<Self::Update>) -> bool {
        // TODO
        false
    }

    fn on_spawns<R: Redis>(&self,
                           agents: Vec<Agent<Self::State>>,
                           population: &Population<Self, R>)
                           -> () {
        // index newly created media/people
        // so we can sample them later
        let mut media = Vec::new();
        let mut people = Vec::new();
        let _: Vec<()> = agents.iter()
            .map(|a| {
                match a.state {
                    State::Person(_) => people.push(a.id),
                    State::Media(_) => media.push(a.id),
                }
            })
            .collect();
        let _: () = population.indexes("media", media);
        let _: () = population.indexes("people", people);
    }
}

// TODO
// person step:
//  - sample a person to talk to, based on trust. or encounter random.
//  - read a story from media, based on opinion alignment and importance
//  - talk to person
//  - change opinion based on media
//  - change opinion based on talking to person
//

fn main() {
    // TODO
    // - bootstrap social network
    // - people more likely talk to likeminded friends
    // - randomly meet new people

    let sim = OpinionDynamicsSim {};

    // TODO
    let world = World { weather: "sunny".to_string() };

    // Setup the manager
    let addr = "redis://127.0.0.1/";
    let pop_client = Client::open(addr).unwrap();
    let mut manager = Manager::new(addr, pop_client, sim.clone());

    let mut medias = vec![Media {
                              opinions: vec![Opinion {
                                                 polarity: -1.,
                                                 priority: 0.5,
                                             },
                                             Opinion {
                                                 polarity: -0.5,
                                                 priority: 1.,
                                             }],
                          },
                          Media {
                              opinions: vec![Opinion {
                                                 polarity: 1.,
                                                 priority: 0.8,
                                             },
                                             Opinion {
                                                 polarity: -0.2,
                                                 priority: 0.4,
                                             }],
                          }];
    let media_ids = manager.spawns(medias.drain(..).map(|m| State::Media(m)).collect());

    let mut people = vec![Person::new(vec![Opinion {
                                               polarity: 1.,
                                               priority: 1.,
                                           },
                                           Opinion {
                                               polarity: 0.,
                                               priority: 0.,
                                           }]),
                          Person::new(vec![Opinion {
                                               polarity: -1.,
                                               priority: 1.,
                                           },
                                           Opinion {
                                               polarity: 1.,
                                               priority: 0.,
                                           }])];
    let people_ids = manager.spawns(people.drain(..).map(|m| State::Person(m)).collect());

    // run(sim, world, manager, 4, 10);
}
<commit_msg>sketch of person's decide method<commit_after>extern crate rand;
extern crate djinn;
extern crate redis;
extern crate rustc_serialize;

use rand::Rng;
use redis::Client;
use djinn::{Agent, Manager, Simulation, Population, Updates, Redis, run};
use rand::distributions::{Weighted, WeightedChoice, IndependentSample};
use std::collections::HashMap;

// when two people share the exact same polarity on an opinion,
// this is how much their trust improves by
const MAX_POS_TRUST_SHIFT: f64 = 1.;

// if two people have a polarity within +/- this value,
// their trust improves
const MAX_POS_TRUST_SHIFT_RANGE: f64 = 4.;

// when an opinion shifts, it shifts by this proportion of
// the opinion difference
const OPINION_SHIFT_PROPORTION: f64 = 0.1;

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Opinion {
    polarity: i32,
    priority: u32,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Person {
    opinions: Vec<Opinion>,
    medias: HashMap<u64, u32>,
    friends: HashMap<u64, u32>,
}

impl Person {
    pub fn new(opinions: Vec<Opinion>) -> Person {
        Person {
            opinions: opinions,
            medias: HashMap::new(),
            friends: HashMap::new(),
        }
    }

    /// Select a random opinion weighted by priority.
    fn rand_opinion_idx(&self, mut rng: &mut Rng, opinions: &Vec<Opinion>) -> usize {
        let mut items: Vec<Weighted<usize>> = opinions.iter()
            .enumerate()
            .map(|(i, o)| {
                Weighted {
                    item: i,
                    weight: o.priority,
                }
            })
            .collect();
        let wc = WeightedChoice::new(&mut items);
        wc.ind_sample(&mut rng).clone()
    }

    /// Select a random edge by weight.
    fn rand_edge(&self, mut rng: &mut Rng, edges: &HashMap<u64, u32>) -> u64 {
        let mut items: Vec<Weighted<u64>> = edges.iter()
            .map(|(id, weight)| {
                Weighted {
                    item: *id,
                    weight: *weight,
                }
            })
            .collect();
        let wc = WeightedChoice::new(&mut items);
        wc.ind_sample(&mut rng)
    }

    /// Trust is computed from alignment of opinion polarities,
    /// weighted by the importance/priority of those opinions
    /// from the perspective of the subject.
    fn trust_from_opinions(&self, opinions: &Vec<Opinion>) -> u32 {
        self.opinions
            .iter()
            .zip(opinions.iter())
            .fold(0, |acc, (o1, o2)| acc + self.trust_from_opinion(o1, o2))
    }

    fn trust_from_opinion(&self, o1: &Opinion, o2: &Opinion) -> u32 {
        let dist = (o1.polarity - o2.polarity).abs();
        let x = ((2 - dist) as f64) / 1.;
        ((x - 1.) * (o1.priority as f64)).round() as u32
    }

    /// TODO atm only considering a shift _towards_ the other person's opinion,
    /// but totally possible that encountering a divergent opinion
    /// causes one to double-down on their own opinion.
    /// TODO atm this is only a one-way exchange, totally possible to influence the
    /// other person's opinion too.
    fn be_influenced(&self, op_idx: usize, op1: &Opinion, op2: &Opinion) -> PersonUpdate {
        let diff = op2.polarity - op1.polarity;
        let shift = ((diff as f64) * OPINION_SHIFT_PROPORTION).round() as i32;
        PersonUpdate::OpinionShift {
            idx: op_idx,
            polarity: shift,
        }
    }

    /// Based on hearing this person's opinion, adjust trust.
    /// The lower distance between their opinions, the more they trust each other.
    fn trust_shift(&self, op1: &Opinion, op2: &Opinion) -> i32 {
        let dist = (op1.polarity - op2.polarity).abs();
        let trust_shift = -((dist as f64) / MAX_POS_TRUST_SHIFT_RANGE).powi(2) +
                          MAX_POS_TRUST_SHIFT;
        trust_shift.round() as i32
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum EdgeType {
    Media,
    Friend,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Media {
    opinions: Vec<Opinion>,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum State {
    Person(Person),
    Media(Media),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum PersonUpdate {
    OpinionShift { idx: usize, polarity: i32 },
    TrustShift {
        id: u64,
        shift: i32,
        edgeType: EdgeType,
    },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum MediaUpdate {
    OpinionShift {
        idx: usize,
        polarity: i32,
        priority: i32,
    },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum Update {
    Person(PersonUpdate),
    Media(MediaUpdate),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct World {
    weather: String,
}

#[derive(Clone)]
struct OpinionDynamicsSim;

impl OpinionDynamicsSim {
    fn decide_person<R: Redis>(&self,
                               id: u64,
                               person: &Person,
                               pop: &Population<Self, R>,
                               updates: &mut Updates<Self>)
                               -> () {
        let mut rng = rand::weak_rng();

        // talk to a person or consume media?
        let other = if rng.gen::<f64>() < 0.5 {
            // choose a media.
            // the less media this person is familiar with,
            // the more likely they will encounter a random one.
            // otherwise, they choose one with probability based on how much they trust it.
            let p_rand_media = (person.medias.len() as f64) / 2.; // TODO denom should be a config val
            if rng.gen::<f64>() < p_rand_media {
                pop.random("media")
            } else {
                let id = person.rand_edge(&mut rng, &person.medias);
                pop.get_agent(id).unwrap()
            }
        } else {
            // choose a person to talk to.
            let p_rand_person = (person.friends.len() as f64) / 2.; // TODO denom should be a config val
            if rng.gen::<f64>() < p_rand_person {
                pop.random("person") // TODO prob shouldnt be themselves
            } else {
                let id = person.rand_edge(&mut rng, &person.friends);
                pop.get_agent(id).unwrap()
            }
        };
        match other.state {
            State::Person(ref p) => {
                let op_idx = person.rand_opinion_idx(&mut rng, &p.opinions);
                let ref op1 = person.opinions[op_idx];
                let ref op2 = p.opinions[op_idx];

                // naively bootstrap trust for new person as 0
                let trust = match person.friends.get(&other.id) {
                    Some(t) => *t,
                    None => 0,
                };
                let p_opinion_shift = ((trust as f64) + 0.01) / 100.;
                if rng.gen::<f64>() < p_opinion_shift {
                    updates.queue(id, Update::Person(person.be_influenced(op_idx, op1, op2)));
                }

                updates.queue(id,
                              Update::Person(PersonUpdate::TrustShift {
                                  id: other.id,
                                  shift: person.trust_shift(op1, op2),
                                  edgeType: EdgeType::Friend,
                              }))
            }
            State::Media(ref m) => {
                let op_idx = person.rand_opinion_idx(&mut rng, &m.opinions);
                let ref op1 = person.opinions[op_idx];
                let ref op2 = m.opinions[op_idx];

                // naively bootstrap trust for new media as 0
                let trust = match person.medias.get(&other.id) {
                    Some(t) => *t,
                    None => 0,
                };

                let p_opinion_shift = ((trust as f64) + 0.01) / 100.;
                if rng.gen::<f64>() < p_opinion_shift {
                    updates.queue(id, Update::Person(person.be_influenced(op_idx, op1, op2)));
                }

                updates.queue(id,
                              Update::Person(PersonUpdate::TrustShift {
                                  id: other.id,
                                  shift: person.trust_shift(op1, op2),
                                  edgeType: EdgeType::Media,
                              }))
            }
        }
    }
}

impl Simulation for OpinionDynamicsSim {
    type State = State;
    type Update = Update;
    type World = World;

    fn decide<R: Redis>(&self,
                        agent: &Agent<Self::State>,
                        world: &Self::World,
                        pop: &Population<Self, R>,
                        updates: &mut Updates<Self>)
                        -> () {
        match agent.state {
            State::Person(ref p) => {
                self.decide_person(agent.id, p, pop, updates);
            }
            State::Media(ref m) => {
                // TODO
            }
        }
    }

    fn update(&self, mut state: &mut Self::State, updates: Vec<Self::Update>) -> bool {
        // TODO
        false
    }

    fn on_spawns<R: Redis>(&self,
                           agents: Vec<Agent<Self::State>>,
                           population: &Population<Self, R>)
                           -> () {
        // index newly created media/people
        // so we can sample them later
        let mut media = Vec::new();
        let mut people = Vec::new();
        let _: Vec<()> = agents.iter()
            .map(|a| {
                match a.state {
                    State::Person(_) => people.push(a.id),
                    State::Media(_) => media.push(a.id),
                }
            })
            .collect();
        let _: () = population.indexes("media", media);
        let _: () = population.indexes("people", people);
    }
}

// TODO
// person step:
//  - sample a person to talk to, based on trust. or encounter random.
//  - read a story from media, based on opinion alignment and importance
//  - talk to person
//  - change opinion based on media
//  - change opinion based on talking to person
//

fn main() {
    // TODO
    // - bootstrap social network
    // - people more likely talk to likeminded friends
    // - randomly meet new people

    let sim = OpinionDynamicsSim {};

    // TODO
    let world = World { weather: "sunny".to_string() };

    // Setup the manager
    let addr = "redis://127.0.0.1/";
    let pop_client = Client::open(addr).unwrap();
    let mut manager = Manager::new(addr, pop_client, sim.clone());

    let mut medias = vec![Media {
                              opinions: vec![Opinion {
                                                 polarity: -1,
                                                 priority: 5,
                                             },
                                             Opinion {
                                                 polarity: -5,
                                                 priority: 1,
                                             }],
                          },
                          Media {
                              opinions: vec![Opinion {
                                                 polarity: 1,
                                                 priority: 8,
                                             },
                                             Opinion {
                                                 polarity: 2,
                                                 priority: 4,
                                             }],
                          }];
    let media_ids = manager.spawns(medias.drain(..).map(|m| State::Media(m)).collect());

    let mut people = vec![Person::new(vec![Opinion {
                                               polarity: 1,
                                               priority: 1,
                                           },
                                           Opinion {
                                               polarity: 0,
                                               priority: 0,
                                           }]),
                          Person::new(vec![Opinion {
                                               polarity: -1,
                                               priority: 1,
                                           },
                                           Opinion {
                                               polarity: 1,
                                               priority: 0,
                                           }])];
    let people_ids = manager.spawns(people.drain(..).map(|m| State::Person(m)).collect());

    // run(sim, world, manager, 4, 10);
}
<|endoftext|>