<commit_before>use crate::core::{
    db::{EntryGateway, EntryIndex, EntryIndexQuery, EntryIndexer},
    entities::Entry,
    util::geo::{LatCoord, LngCoord},
};

use failure::Fallible;
use std::ops::Bound;
use std::path::Path;
use tantivy::{
    collector::{Count, TopDocs},
    query::{BooleanQuery, Occur, Query, QueryParser, RangeQuery, TermQuery},
    schema::*,
    tokenizer::{LowerCaser, RawTokenizer, Tokenizer},
    DocAddress, Document, Index, IndexWriter, Score,
};

const OVERALL_INDEX_HEAP_SIZE_IN_BYTES: usize = 50_000_000;

struct TantivyEntryFields {
    id: Field,
    title: Field,
    description: Field,
    category: Field,
    lat: Field,
    lng: Field,
    tag: Field,
}

pub(crate) struct TantivyEntryIndex {
    fields: TantivyEntryFields,
    index: Index,
    writer: IndexWriter,
    text_query_parser: QueryParser,
}

const ID_TOKENIZER: &str = "raw";
const TAG_TOKENIZER: &str = "tag";
const TEXT_TOKENIZER: &str = "default";

fn build_schema() -> (Schema, TantivyEntryFields) {
    let id_options = TextOptions::default()
        .set_indexing_options(
            TextFieldIndexing::default()
                .set_tokenizer(ID_TOKENIZER)
                .set_index_option(IndexRecordOption::Basic),
        )
        .set_stored();
    let category_options = TextOptions::default().set_indexing_options(
        TextFieldIndexing::default()
            .set_tokenizer(ID_TOKENIZER)
            .set_index_option(IndexRecordOption::WithFreqs),
    );
    let tag_options = TextOptions::default().set_indexing_options(
        TextFieldIndexing::default()
            .set_tokenizer(TAG_TOKENIZER)
            .set_index_option(IndexRecordOption::WithFreqs),
    );
    let text_options = TextOptions::default().set_indexing_options(
        TextFieldIndexing::default()
            .set_tokenizer(TEXT_TOKENIZER)
            .set_index_option(IndexRecordOption::WithFreqsAndPositions),
    );
    let mut schema_builder = SchemaBuilder::default();
    let id = schema_builder.add_text_field("id", id_options);
    let lat = schema_builder.add_i64_field("lat", INT_INDEXED);
    let lng = schema_builder.add_i64_field("lng", INT_INDEXED);
    let title = schema_builder.add_text_field("title", text_options.clone());
    let description = schema_builder.add_text_field("desc", text_options);
    let category = schema_builder.add_text_field("cat", category_options.clone());
    let tag = schema_builder.add_text_field("tag", tag_options);
    let schema = schema_builder.build();
    let fields = TantivyEntryFields {
        id,
        lat,
        lng,
        title,
        description,
        category,
        tag,
    };
    (schema, fields)
}

fn register_tokenizers(index: &Index) {
    // Predefined tokenizers
    debug_assert!(index.tokenizers().get(ID_TOKENIZER).is_some());
    debug_assert!(index.tokenizers().get(TEXT_TOKENIZER).is_some());
    // Custom tokenizer(s)
    debug_assert!(index.tokenizers().get(TAG_TOKENIZER).is_none());
    index
        .tokenizers()
        .register(TAG_TOKENIZER, RawTokenizer.filter(LowerCaser));
}

impl TantivyEntryIndex {
    pub fn create_in_ram() -> Fallible<Self> {
        let no_path: Option<&Path> = None;
        Self::create(no_path)
    }

    pub fn create<P: AsRef<Path>>(path: Option<P>) -> Fallible<Self> {
        let (schema, fields) = build_schema();

        // TODO: Open index from existing directory
        let index = if let Some(path) = path {
            info!(
                "Creating full-text search index in directory: {}",
                path.as_ref().to_string_lossy()
            );
            Index::create_in_dir(path, schema)?
        } else {
            warn!("Creating full-text search index in RAM");
            Index::create_in_ram(schema)
        };

        register_tokenizers(&index);

        let writer = index.writer(OVERALL_INDEX_HEAP_SIZE_IN_BYTES)?;
        let text_query_parser =
            QueryParser::for_index(&index, vec![fields.title, fields.description]);
        Ok(Self {
            fields,
            index,
            writer,
            text_query_parser,
        })
    }
}

impl EntryIndexer for TantivyEntryIndex {
    fn add_or_update_entry(&mut self, entry: &Entry) -> Fallible<()> {
        let id_term = Term::from_field_text(self.fields.id, &entry.id);
        self.writer.delete_term(id_term);
        let mut doc = Document::default();
        doc.add_text(self.fields.id, &entry.id);
        doc.add_i64(
            self.fields.lat,
            i64::from(LatCoord::from_deg(entry.location.lat).to_raw()),
        );
        doc.add_i64(
            self.fields.lng,
            i64::from(LngCoord::from_deg(entry.location.lng).to_raw()),
        );
        doc.add_text(self.fields.title, &entry.title);
        doc.add_text(self.fields.description, &entry.description);
        for category in &entry.categories {
            doc.add_text(self.fields.category, category);
        }
        for tag in &entry.tags {
            doc.add_text(self.fields.tag, tag);
        }
        self.writer.add_document(doc);
        Ok(())
    }

    fn remove_entry_by_id(&mut self, id: &str) -> Fallible<()> {
        let id_term = Term::from_field_text(self.fields.id, id);
        self.writer.delete_term(id_term);
        Ok(())
    }

    fn flush(&mut self) -> Fallible<()> {
        self.writer.commit()?;
        self.index.load_searchers()?;
        Ok(())
    }
}

// Upper bound for the expected number of results for which
// memory is pre-allocated.
const MAX_LIMIT: usize = 100;

impl EntryIndex for TantivyEntryIndex {
    fn query_entries(
        &self,
        entries: &EntryGateway,
        query: &EntryIndexQuery,
        limit: usize,
    ) -> Fallible<Vec<Entry>> {
        let mut sub_queries: Vec<(Occur, Box<Query>)> =
            Vec::with_capacity(2 + 2 + query.categories.len() + query.tags.len());
        if let Some(ref bbox) = query.bbox {
            debug_assert!(bbox.is_valid());
            debug_assert!(!bbox.is_empty());
            let lat_query = RangeQuery::new_i64_bounds(
                self.fields.lat,
                Bound::Included(i64::from(bbox.south_west().lat().to_raw())),
                Bound::Included(i64::from(bbox.north_east().lat().to_raw())),
            );
            sub_queries.push((Occur::Must, Box::new(lat_query)));
            if bbox.south_west().lng() <= bbox.north_east().lng() {
                // regular (inclusive)
                let lng_query = RangeQuery::new_i64_bounds(
                    self.fields.lng,
                    Bound::Included(i64::from(bbox.south_west().lng().to_raw())),
                    Bound::Included(i64::from(bbox.north_east().lng().to_raw())),
                );
                sub_queries.push((Occur::Must, Box::new(lng_query)));
            } else {
                // inverse (exclusive)
                let lng_query = RangeQuery::new_i64_bounds(
                    self.fields.lng,
                    Bound::Excluded(i64::from(bbox.north_east().lng().to_raw())),
                    Bound::Excluded(i64::from(bbox.south_west().lng().to_raw())),
                );
                sub_queries.push((Occur::MustNot, Box::new(lng_query)));
            }
        }
        if let Some(ref text) = query.text {
            debug_assert!(!text.trim().is_empty());
            match self.text_query_parser.parse_query(&text.to_lowercase()) {
                Ok(query) => {
                    sub_queries.push((Occur::Must, Box::new(query)));
                }
                Err(err) => {
                    warn!("Failed to parse query text '{}': {:?}", text, err);
                }
            }
        }
        for category in &query.categories {
            debug_assert!(!category.trim().is_empty());
            let category_term =
                Term::from_field_text(self.fields.category, &category.to_lowercase());
            let category_query = TermQuery::new(category_term, IndexRecordOption::Basic);
            sub_queries.push((Occur::Should, Box::new(category_query)));
        }
        for tag in &query.tags {
            debug_assert!(!tag.trim().is_empty());
            let tag_term = Term::from_field_text(self.fields.tag, &tag.to_lowercase());
            let tag_query = TermQuery::new(tag_term, IndexRecordOption::Basic);
            sub_queries.push((Occur::Should, Box::new(tag_query)));
        }
        let query = BooleanQuery::from(sub_queries);
        let searcher = self.index.searcher();
        let (_doc_count, top_docs): (usize, Vec<(Score, DocAddress)>) =
            searcher.search(&query, &(Count, TopDocs::with_limit(limit.min(MAX_LIMIT))))?;
        let mut top_entries = Vec::with_capacity(top_docs.len());
        for (_score, doc_addr) in top_docs {
            match searcher.doc(doc_addr) {
                Ok(doc) => {
                    if let Some(id) = doc.get_first(self.fields.id).and_then(Value::text) {
                        match entries.get_entry(id) {
                            Ok(entry) => {
                                top_entries.push(entry);
                            }
                            Err(err) => {
                                warn!("Entry {} not found: {}", id, err);
                            }
                        }
                    } else {
                        warn!("Mising entry id in document {:?}", doc_addr);
                    }
                }
                Err(err) => {
                    warn!("Failed to load document {:?}: {}", doc_addr, err);
                }
            }
        }
        Ok(top_entries)
    }
}
<commit_msg>Refine search criteria<commit_after>use crate::core::{
    db::{EntryGateway, EntryIndex, EntryIndexQuery, EntryIndexer},
    entities::Entry,
    util::geo::{LatCoord, LngCoord},
};

use failure::Fallible;
use std::ops::Bound;
use std::path::Path;
use tantivy::{
    collector::{Count, TopDocs},
    query::{BooleanQuery, Occur, Query, QueryParser, RangeQuery, TermQuery},
    schema::*,
    tokenizer::{LowerCaser, RawTokenizer, Tokenizer},
    DocAddress, Document, Index, IndexWriter, Score,
};

const OVERALL_INDEX_HEAP_SIZE_IN_BYTES: usize = 50_000_000;

struct TantivyEntryFields {
    id: Field,
    title: Field,
    description: Field,
    category: Field,
    lat: Field,
    lng: Field,
    tag: Field,
}

pub(crate) struct TantivyEntryIndex {
    fields: TantivyEntryFields,
    index: Index,
    writer: IndexWriter,
    text_query_parser: QueryParser,
}

const ID_TOKENIZER: &str = "raw";
const TAG_TOKENIZER: &str = "tag";
const TEXT_TOKENIZER: &str = "default";

fn build_schema() -> (Schema, TantivyEntryFields) {
    let id_options = TextOptions::default()
        .set_indexing_options(
            TextFieldIndexing::default()
                .set_tokenizer(ID_TOKENIZER)
                .set_index_option(IndexRecordOption::Basic),
        )
        .set_stored();
    let category_options = TextOptions::default().set_indexing_options(
        TextFieldIndexing::default()
            .set_tokenizer(ID_TOKENIZER)
            .set_index_option(IndexRecordOption::WithFreqs),
    );
    let tag_options = TextOptions::default().set_indexing_options(
        TextFieldIndexing::default()
            .set_tokenizer(TAG_TOKENIZER)
            .set_index_option(IndexRecordOption::WithFreqs),
    );
    let text_options = TextOptions::default().set_indexing_options(
        TextFieldIndexing::default()
            .set_tokenizer(TEXT_TOKENIZER)
            .set_index_option(IndexRecordOption::WithFreqsAndPositions),
    );
    let mut schema_builder = SchemaBuilder::default();
    let id = schema_builder.add_text_field("id", id_options);
    let lat = schema_builder.add_i64_field("lat", INT_INDEXED);
    let lng = schema_builder.add_i64_field("lng", INT_INDEXED);
    let title = schema_builder.add_text_field("title", text_options.clone());
    let description = schema_builder.add_text_field("desc", text_options);
    let category = schema_builder.add_text_field("cat", category_options.clone());
    let tag = schema_builder.add_text_field("tag", tag_options);
    let schema = schema_builder.build();
    let fields = TantivyEntryFields {
        id,
        lat,
        lng,
        title,
        description,
        category,
        tag,
    };
    (schema, fields)
}

fn register_tokenizers(index: &Index) {
    // Predefined tokenizers
    debug_assert!(index.tokenizers().get(ID_TOKENIZER).is_some());
    debug_assert!(index.tokenizers().get(TEXT_TOKENIZER).is_some());
    // Custom tokenizer(s)
    debug_assert!(index.tokenizers().get(TAG_TOKENIZER).is_none());
    index
        .tokenizers()
        .register(TAG_TOKENIZER, RawTokenizer.filter(LowerCaser));
}

impl TantivyEntryIndex {
    pub fn create_in_ram() -> Fallible<Self> {
        let no_path: Option<&Path> = None;
        Self::create(no_path)
    }

    pub fn create<P: AsRef<Path>>(path: Option<P>) -> Fallible<Self> {
        let (schema, fields) = build_schema();

        // TODO: Open index from existing directory
        let index = if let Some(path) = path {
            info!(
                "Creating full-text search index in directory: {}",
                path.as_ref().to_string_lossy()
            );
            Index::create_in_dir(path, schema)?
        } else {
            warn!("Creating full-text search index in RAM");
            Index::create_in_ram(schema)
        };

        register_tokenizers(&index);

        let writer = index.writer(OVERALL_INDEX_HEAP_SIZE_IN_BYTES)?;
        let text_query_parser =
            QueryParser::for_index(&index, vec![fields.title, fields.description]);
        Ok(Self {
            fields,
            index,
            writer,
            text_query_parser,
        })
    }
}

impl EntryIndexer for TantivyEntryIndex {
    fn add_or_update_entry(&mut self, entry: &Entry) -> Fallible<()> {
        let id_term = Term::from_field_text(self.fields.id, &entry.id);
        self.writer.delete_term(id_term);
        let mut doc = Document::default();
        doc.add_text(self.fields.id, &entry.id);
        doc.add_i64(
            self.fields.lat,
            i64::from(LatCoord::from_deg(entry.location.lat).to_raw()),
        );
        doc.add_i64(
            self.fields.lng,
            i64::from(LngCoord::from_deg(entry.location.lng).to_raw()),
        );
        doc.add_text(self.fields.title, &entry.title);
        doc.add_text(self.fields.description, &entry.description);
        for category in &entry.categories {
            doc.add_text(self.fields.category, category);
        }
        for tag in &entry.tags {
            doc.add_text(self.fields.tag, tag);
        }
        self.writer.add_document(doc);
        Ok(())
    }

    fn remove_entry_by_id(&mut self, id: &str) -> Fallible<()> {
        let id_term = Term::from_field_text(self.fields.id, id);
        self.writer.delete_term(id_term);
        Ok(())
    }

    fn flush(&mut self) -> Fallible<()> {
        self.writer.commit()?;
        self.index.load_searchers()?;
        Ok(())
    }
}

// Upper bound for the expected number of results for which
// memory is pre-allocated.
const MAX_LIMIT: usize = 100;

impl EntryIndex for TantivyEntryIndex {
    fn query_entries(
        &self,
        entries: &EntryGateway,
        query: &EntryIndexQuery,
        limit: usize,
    ) -> Fallible<Vec<Entry>> {
        let mut sub_queries: Vec<(Occur, Box<Query>)> =
            Vec::with_capacity(2 + 2 + query.categories.len() + query.tags.len());
        if let Some(ref bbox) = query.bbox {
            debug_assert!(bbox.is_valid());
            debug_assert!(!bbox.is_empty());
            let lat_query = RangeQuery::new_i64_bounds(
                self.fields.lat,
                Bound::Included(i64::from(bbox.south_west().lat().to_raw())),
                Bound::Included(i64::from(bbox.north_east().lat().to_raw())),
            );
            sub_queries.push((Occur::Must, Box::new(lat_query)));
            if bbox.south_west().lng() <= bbox.north_east().lng() {
                // regular (inclusive)
                let lng_query = RangeQuery::new_i64_bounds(
                    self.fields.lng,
                    Bound::Included(i64::from(bbox.south_west().lng().to_raw())),
                    Bound::Included(i64::from(bbox.north_east().lng().to_raw())),
                );
                sub_queries.push((Occur::Must, Box::new(lng_query)));
            } else {
                // inverse (exclusive)
                let lng_query = RangeQuery::new_i64_bounds(
                    self.fields.lng,
                    Bound::Excluded(i64::from(bbox.north_east().lng().to_raw())),
                    Bound::Excluded(i64::from(bbox.south_west().lng().to_raw())),
                );
                sub_queries.push((Occur::MustNot, Box::new(lng_query)));
            }
        }
        if let Some(ref text) = query.text {
            debug_assert!(!text.trim().is_empty());
            match self.text_query_parser.parse_query(&text.to_lowercase()) {
                Ok(query) => {
                    sub_queries.push((Occur::Must, Box::new(query)));
                }
                Err(err) => {
                    warn!("Failed to parse query text '{}': {:?}", text, err);
                }
            }
        }
        // At least one out of multiple categories should match. But this
        // kine of (sub-)query is not supported so we use the following
        // workaround.
        let category_occur = if query.categories.len() == 1 {
            // A single category must match
            Occur::Must
        } else {
            // Some out of multiple categories should match
            Occur::Should
        };
        for category in &query.categories {
            debug_assert!(!category.trim().is_empty());
            let category_term =
                Term::from_field_text(self.fields.category, &category.to_lowercase());
            let category_query = TermQuery::new(category_term, IndexRecordOption::Basic);
            sub_queries.push((category_occur, Box::new(category_query)));
        }
        // At least one out of multiple tags should match. But this
        // kind of (sub-)query is not supported so we use the following
        // workaround.
        let tag_occur = if query.tags.len() == 1 {
            // A single tag must match
            Occur::Must
        } else {
            // Some out of multiple tags should match
            Occur::Should
        };
        for tag in &query.tags {
            debug_assert!(!tag.trim().is_empty());
            let tag_term = Term::from_field_text(self.fields.tag, &tag.to_lowercase());
            let tag_query = TermQuery::new(tag_term, IndexRecordOption::Basic);
            sub_queries.push((tag_occur, Box::new(tag_query)));
        }
        let query = BooleanQuery::from(sub_queries);
        let searcher = self.index.searcher();
        let (_doc_count, top_docs): (usize, Vec<(Score, DocAddress)>) =
            searcher.search(&query, &(Count, TopDocs::with_limit(limit.min(MAX_LIMIT))))?;
        let mut top_entries = Vec::with_capacity(top_docs.len());
        for (_score, doc_addr) in top_docs {
            match searcher.doc(doc_addr) {
                Ok(doc) => {
                    if let Some(id) = doc.get_first(self.fields.id).and_then(Value::text) {
                        match entries.get_entry(id) {
                            Ok(entry) => {
                                top_entries.push(entry);
                            }
                            Err(err) => {
                                warn!("Entry {} not found: {}", id, err);
                            }
                        }
                    } else {
                        warn!("Mising entry id in document {:?}", doc_addr);
                    }
                }
                Err(err) => {
                    warn!("Failed to load document {:?}: {}", doc_addr, err);
                }
            }
        }
        Ok(top_entries)
    }
}
<|endoftext|>


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

use style::atomic_refcell::{AtomicRef, AtomicRefCell};

struct Foo {
    u: u32,
}

struct Bar {
    f: Foo,
}

#[test]
fn map() {
    let a = AtomicRefCell::new(Bar { f: Foo { u: 42 } });
    let b = a.borrow();
    assert_eq!(b.f.u, 42);
    let c = AtomicRef::map(b, |x| &x.f);
    assert_eq!(c.u, 42);
    let d = AtomicRef::map(c, |x| &x.u);
    assert_eq!(*d, 42);
}

/* FIXME(bholley): Enable once we have AtomicRefMut::map(), which is blocked on
 * https://github.com/Kimundi/owning-ref-rs/pull/16
#[test]
fn map_mut() {
    let a = AtomicRefCell::new(Bar { f: Foo { u: 42 } });
    let mut b = a.borrow_mut();
    assert_eq!(b.f.u, 42);
    b.f.u = 43;
    let mut c = AtomicRefMut::map(b, |x| &x.f);
    assert_eq!(c.u, 43);
    c.u = 44;
    let mut d = AtomicRefMut::map(c, |x| &x.u);
    assert_eq!(*d, 44);
    *d. = 45;
    assert_eq!(*d, 45);
}*/
<commit_msg>Add benchmarks for AtomicRefCell borrows.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use style::atomic_refcell::{AtomicRef, AtomicRefCell};
use test::Bencher;

struct Foo {
    u: u32,
}

struct Bar {
    f: Foo,
}

impl Default for Bar {
    fn default() -> Self {
        Bar { f: Foo { u: 42 } }
    }
}

#[test]
fn map() {
    let a = AtomicRefCell::new(Bar::default());
    let b = a.borrow();
    assert_eq!(b.f.u, 42);
    let c = AtomicRef::map(b, |x| &x.f);
    assert_eq!(c.u, 42);
    let d = AtomicRef::map(c, |x| &x.u);
    assert_eq!(*d, 42);
}

#[bench]
fn immutable_borrow(b: &mut Bencher) {
    let a = AtomicRefCell::new(Bar::default());
    b.iter(|| a.borrow());
}

#[bench]
fn immutable_second_borrow(b: &mut Bencher) {
    let a = AtomicRefCell::new(Bar::default());
    let _first = a.borrow();
    b.iter(|| a.borrow());
}

#[bench]
fn immutable_third_borrow(b: &mut Bencher) {
    let a = AtomicRefCell::new(Bar::default());
    let _first = a.borrow();
    let _second = a.borrow();
    b.iter(|| a.borrow());
}

#[bench]
fn mutable_borrow(b: &mut Bencher) {
    let a = AtomicRefCell::new(Bar::default());
    b.iter(|| a.borrow_mut());
}

/* FIXME(bholley): Enable once we have AtomicRefMut::map(), which is blocked on
 * https://github.com/Kimundi/owning-ref-rs/pull/16
#[test]
fn map_mut() {
    let a = AtomicRefCell::new(Bar { f: Foo { u: 42 } });
    let mut b = a.borrow_mut();
    assert_eq!(b.f.u, 42);
    b.f.u = 43;
    let mut c = AtomicRefMut::map(b, |x| &x.f);
    assert_eq!(c.u, 43);
    c.u = 44;
    let mut d = AtomicRefMut::map(c, |x| &x.u);
    assert_eq!(*d, 44);
    *d. = 45;
    assert_eq!(*d, 45);
}*/
<|endoftext|>


<commit_before>// This module defines constants and functions for working with zakocmp
// configuration files.

extern crate yaml_rust;

use std::io::{Error, ErrorKind};
use std::result::Result;
use std::vec::Vec;
use yaml_rust::{Yaml, YamlLoader};

const DEFAULT_POLICY_KEY: &'static str = "default-policy";
const POLICIES_KEY: &'static str = "policies";

// Enumerates the string representations of known policies.
const POLICY_REPR_IGNORE: &'static str = "ignore";
const POLICY_REPR_NOADD: &'static str = "noadd";
const POLICY_REPR_NODELETE: &'static str = "nodelete";
const POLICY_REPR_NOMODIFY: &'static str = "nomodify";
const POLICY_REPR_IMMUTABLE: &'static str = "immutable";

// Represents known policies as integers.
pub const POLICY_IGNORE: i32 = 0;
pub const POLICY_NOADD: i32 = 1 << 0;
pub const POLICY_NODELETE: i32 = 1 << 1;
pub const POLICY_NOMODIFY: i32 = 1 << 2;
pub const POLICY_IMMUTABLE: i32 = POLICY_NOADD | POLICY_NODELETE | POLICY_NOMODIFY;

// Represents the length of the string "./" which as an implementation
// detail currently precedes all paths captured in zakopane snapshots.
// If a path matches only this, the match is trivial and should not be
// have its policy applied.
const TRIVIAL_MATCH_LENGTH: usize = 2;

// Represents a sorted vector of zakopane config rules, each mapping a
// path (prefix) to a policy. This type alias is provided for ease of
// coding.
type ZakopanePolicies = Vec<(String, i32)>;

// Represents a zakopane config. Please consult the documentation.
pub struct ZakopaneConfig {
    default_policy: i32,
    policies: ZakopanePolicies,
}

// Borrows the string representation of one policy `token` and returns
// the equivalent integral representation.
fn policy_token_as_int(token: &str) -> Result<i32, Error> {
    match token {
        POLICY_REPR_IGNORE => Ok(POLICY_IGNORE),
        POLICY_REPR_NOADD => Ok(POLICY_NOADD),
        POLICY_REPR_NODELETE => Ok(POLICY_NODELETE),
        POLICY_REPR_NOMODIFY => Ok(POLICY_NOMODIFY),
        POLICY_REPR_IMMUTABLE => Ok(POLICY_IMMUTABLE),
        _ => Err(Error::new(
            ErrorKind::InvalidInput,
            format!("bad token: ``{}''", token),
        )),
    }
}

// Borrows the string representation of a combined `policy` and returns
// the equivalent integral representation. This function expects
// `policy` to comprise one or more policy tokens separated by commas.
fn policy_tokens_as_int(policy: &str) -> Result<i32, Error> {
    let policy_ints: Vec<i32> = policy
        .split(",")
        .map(|tok| policy_token_as_int(tok))
        .collect::<Result<Vec<i32>, Error>>()?;
    return Ok(policy_ints
        .iter()
        .fold(POLICY_IGNORE, |accum, elem| accum | elem));
}

// Borrows yaml representations of one line of zakopane policy and
// returns the corresponding valid tuple suitable for use in building a
// ZakopanePolicies object.
fn extract_policy(ypath: &Yaml, policy_tokens: &Yaml) -> Result<(String, i32), Error> {
    let path: String = match ypath.as_str() {
        Some(string) => string.to_owned(),
        None => return Err(Error::new(ErrorKind::InvalidData, "malformed path?")),
    };
    let policy: i32 = match policy_tokens.as_str() {
        Some(string) => policy_tokens_as_int(string)?,
        None => return Err(Error::new(ErrorKind::InvalidData, "malformed policy?")),
    };
    Ok((path, policy))
}

// Borrows the YAML representation of a zakopane config and returns the
// corresponding ZakopanePolicies. The return value can be benignly
// empty (e.g. if the present config elects not to specify any rules).
fn policies_from_yaml(doc: &Yaml) -> Result<ZakopanePolicies, Error> {
    let policies_map_yaml = &doc[POLICIES_KEY];
    if policies_map_yaml.is_badvalue() {
        // Assumes the config may be benignly devoid of specific
        // policies, returning Ok.
        return Ok(vec![]);
    }
    // Otherwise, iterates over the policies map. Each entry in the
    // policies map correlates a path prefix to a comma-separated list
    // of policies.
    let policies_map: &yaml_rust::yaml::Hash = match policies_map_yaml.as_hash() {
        Some(map) => map,
        None => return Err(Error::new(ErrorKind::InvalidData, "malformed policies")),
    };
    let mut policies: ZakopanePolicies = policies_map
        .into_iter()
        .map(|pair| extract_policy(&pair.0, &pair.1))
        .collect::<Result<ZakopanePolicies, Error>>()?;
    policies.sort_unstable_by_key(|pair| pair.0.to_owned());
    Ok(policies)
}

// Borrows the YAML representation of a zakopane config and returns the
// integral default-policy defined within.
fn default_policy_from_yaml(doc: &Yaml) -> Result<i32, Error> {
    let default_policy_yaml = &doc[DEFAULT_POLICY_KEY];
    if default_policy_yaml.is_badvalue() {
        return Err(Error::new(ErrorKind::InvalidData, DEFAULT_POLICY_KEY));
    }
    let default_policy: i32 = match default_policy_yaml.as_str() {
        None => return Err(Error::new(ErrorKind::InvalidData, DEFAULT_POLICY_KEY)),
        Some(token) => policy_tokens_as_int(&token),
    }?;
    Ok(default_policy)
}

impl ZakopaneConfig {
    // Borrows the string representation of a zakopane config and
    // returns a corresponding ZakopaneConfig.
    pub fn new(config: &str) -> Result<ZakopaneConfig, Error> {
        let docs: Vec<Yaml> = match YamlLoader::load_from_str(config) {
            Ok(val) => val,
            Err(scan_error) => return Err(Error::new(ErrorKind::InvalidData, scan_error)),
        };
        if docs.len() == 0 {
            return Err(Error::new(ErrorKind::InvalidData, "empty zakopane config"));
        }
        let doc = &docs[0];

        let default_policy = default_policy_from_yaml(&doc)?;
        let policies: ZakopanePolicies = policies_from_yaml(&doc)?;

        Ok(ZakopaneConfig {
            default_policy: default_policy,
            policies: policies,
        })
    }

    // Returns how many rules this config contains.
    // This is never less than 1 as a default-policy is always required.
    pub fn rules(&self) -> usize {
        1 + self.policies.len()
    }

    // Borrows a `path` and returns the best-matched policy that
    // applies. This function returns an owned tuple of the
    // (closest-matched path expression, integral policy).
    //
    // This function represents the default-policy fallback by
    // returning the tuple consisting of an empty &str and the
    // default policy.
    pub fn match_policy(&self, path: &str) -> (&str, i32) {
        let mut best_match_path: &str = "";
        let mut best_match_policy: i32 = 0;
        for (prefix, policy) in self.policies.iter() {
            if path.starts_with(prefix) && prefix.len() > best_match_path.len() {
                best_match_path = prefix;
                best_match_policy = *policy;
            }
        }
        if best_match_path.len() <= TRIVIAL_MATCH_LENGTH {
            return ("", self.default_policy);
        }
        return (best_match_path, best_match_policy);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn policy_token_bare_noadd() {
        let policy: i32 = match policy_tokens_as_int(&"noadd") {
            Ok(value) => value,
            Err(oof) => panic!(oof),
        };
        assert!(policy == POLICY_NOADD);
    }

    #[test]
    fn policy_token_bare_nodelete() {
        let policy: i32 = match policy_tokens_as_int(&"nodelete") {
            Ok(value) => value,
            Err(oof) => panic!(oof),
        };
        assert!(policy == POLICY_NODELETE);
    }

    #[test]
    fn policy_token_bare_nomodify() {
        let policy: i32 = match policy_tokens_as_int(&"nomodify") {
            Ok(value) => value,
            Err(oof) => panic!(oof),
        };
        assert!(policy == POLICY_NOMODIFY);
    }

    #[test]
    fn policy_tokens_can_combo() {
        let policy: i32 = match policy_tokens_as_int(&"noadd,nodelete") {
            Ok(value) => value,
            Err(oof) => panic!(oof),
        };
        assert!(policy == POLICY_NOADD | POLICY_NODELETE);
    }

    #[test]
    fn policy_tokens_can_repeat() {
        let policy: i32 =
            match policy_tokens_as_int(&"noadd,noadd,noadd,noadd,nodelete,nodelete,nodelete,noadd")
            {
                Ok(value) => value,
                Err(oof) => panic!(oof),
            };
        assert!(policy == POLICY_NOADD | POLICY_NODELETE);
    }

    #[test]
    fn config_must_not_be_empty() {
        let config = "";
        assert!(!ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_must_not_be_obviously_malformed() {
        let config = r#"
This is not a zakopane config -
rather, it's two lines of text.
        "#;
        assert!(!ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_requires_default_policy() {
        let config = r#"
policies:
    hello-there: nomodify
        "#;
        assert!(!ZakopaneConfig::new(&config).is_ok());

        let mut config_with_default_policy: String = r#"
default-policy: immutable
        "#
        .to_string();
        config_with_default_policy.push_str(&config);
        assert!(ZakopaneConfig::new(&config_with_default_policy).is_ok())
    }

    #[test]
    fn config_might_not_have_specific_policies() {
        let config = r#"
default-policy: nodelete
one-irrelevant-key: it doesn't matter what we put here
another-irrelevant-key: this doesn't invalidate the YAML
third-irrelevant-key: so long as it contains a default-policy
        "#;
        assert!(ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_policies_must_be_a_map() {
        let config = r#"
default-policy: noadd
policies:
    -   eh?
    -   this ain't a map
        "#;
        assert!(!ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_can_have_several_policies() {
        let config = r#"
default-policy: immutable
policies:
    hello-there: noadd
    general-kenobi: nodelete
        "#;
        assert!(ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn match_default_policy() {
        let config_yaml = r#"
default-policy: noadd
        "#;
        let config = ZakopaneConfig::new(&config_yaml).unwrap();

        // With only a default policy, this config has just 1 rule.
        assert!(config.rules() == 1);

        // Any path prefix we throw at match_policy() shall come up
        // as the default policy.
        let (_path, policy) = config.match_policy("./Documents/hello/there.txt");
        assert!(policy == POLICY_NOADD);
        let (_path, policy) = config.match_policy("./Music/general/kenobi.txt");
        assert!(policy == POLICY_NOADD);
    }

    #[test]
    fn match_nondefault_policies() {
        let config_yaml = r#"
default-policy: immutable
policies:
    ./Pictures/: noadd
    ./Pictures/2019/third-party/: nodelete
    ./Pictures/2020/: nomodify
    ./Pictures/2020/food/: nodelete,nomodify
        "#;
        let config = ZakopaneConfig::new(&config_yaml).unwrap();

        assert!(config.rules() == 5);

        // Falls back on the default-policy absent any specific policy
        // defined for this file.
        let (_path, policy) = config.match_policy("./Documents/catch-me-senpai.txt");
        assert!(policy == POLICY_IMMUTABLE);
        // Matches only ``./Pictures.''
        let (_path, policy) = config.match_policy("./Pictures/2016/yano.jpg");
        assert!(policy == POLICY_NOADD);
        // As above and does _not_ match ``./Pictures/2019/third-party/.''
        let (_path, policy) = config.match_policy("./Pictures/2019/first-party.jpg");
        assert!(policy == POLICY_NOADD);
        // Does match ``./Pictures/2019/third-party/.''
        let (_path, policy) = config.match_policy("./Pictures/2019/third-party/yano.jpg");
        assert!(policy == POLICY_NODELETE);

        // Path prefix matching is done strictly and exactly;
        // ``food.md'' doesn't match ``food/,'' so there's no risk of
        // zakopane confusing cohabiting entities with similar basenames.
        let (path, policy) = config.match_policy("./Pictures/2020/food.md");
        assert!(policy == POLICY_NOMODIFY);
        assert!(path == "./Pictures/2020/");
        let (path, policy) = config.match_policy("./Pictures/2020/food/tacos.jpg");
        assert!(policy == POLICY_NODELETE | POLICY_NOMODIFY);
        assert!(path == "./Pictures/2020/food/");
    }
}
<commit_msg>zakocmp: reorganize config unit tests<commit_after>// This module defines constants and functions for working with zakocmp
// configuration files.

extern crate yaml_rust;

use std::io::{Error, ErrorKind};
use std::result::Result;
use std::vec::Vec;
use yaml_rust::{Yaml, YamlLoader};

const DEFAULT_POLICY_KEY: &'static str = "default-policy";
const POLICIES_KEY: &'static str = "policies";

// Enumerates the string representations of known policies.
const POLICY_REPR_IGNORE: &'static str = "ignore";
const POLICY_REPR_NOADD: &'static str = "noadd";
const POLICY_REPR_NODELETE: &'static str = "nodelete";
const POLICY_REPR_NOMODIFY: &'static str = "nomodify";
const POLICY_REPR_IMMUTABLE: &'static str = "immutable";

// Represents known policies as integers.
pub const POLICY_IGNORE: i32 = 0;
pub const POLICY_NOADD: i32 = 1 << 0;
pub const POLICY_NODELETE: i32 = 1 << 1;
pub const POLICY_NOMODIFY: i32 = 1 << 2;
pub const POLICY_IMMUTABLE: i32 = POLICY_NOADD | POLICY_NODELETE | POLICY_NOMODIFY;

// Represents the length of the string "./" which as an implementation
// detail currently precedes all paths captured in zakopane snapshots.
// If a path matches only this, the match is trivial and should not be
// have its policy applied.
const TRIVIAL_MATCH_LENGTH: usize = 2;

// Represents a sorted vector of zakopane config rules, each mapping a
// path (prefix) to a policy. This type alias is provided for ease of
// coding.
type ZakopanePolicies = Vec<(String, i32)>;

// Represents a zakopane config. Please consult the documentation.
pub struct ZakopaneConfig {
    default_policy: i32,
    policies: ZakopanePolicies,
}

// Borrows the string representation of one policy `token` and returns
// the equivalent integral representation.
fn policy_token_as_int(token: &str) -> Result<i32, Error> {
    match token {
        POLICY_REPR_IGNORE => Ok(POLICY_IGNORE),
        POLICY_REPR_NOADD => Ok(POLICY_NOADD),
        POLICY_REPR_NODELETE => Ok(POLICY_NODELETE),
        POLICY_REPR_NOMODIFY => Ok(POLICY_NOMODIFY),
        POLICY_REPR_IMMUTABLE => Ok(POLICY_IMMUTABLE),
        _ => Err(Error::new(
            ErrorKind::InvalidInput,
            format!("bad token: ``{}''", token),
        )),
    }
}

// Borrows the string representation of a combined `policy` and returns
// the equivalent integral representation. This function expects
// `policy` to comprise one or more policy tokens separated by commas.
fn policy_tokens_as_int(policy: &str) -> Result<i32, Error> {
    let policy_ints: Vec<i32> = policy
        .split(",")
        .map(|tok| policy_token_as_int(tok))
        .collect::<Result<Vec<i32>, Error>>()?;
    return Ok(policy_ints
        .iter()
        .fold(POLICY_IGNORE, |accum, elem| accum | elem));
}

// Borrows yaml representations of one line of zakopane policy and
// returns the corresponding valid tuple suitable for use in building a
// ZakopanePolicies object.
fn extract_policy(ypath: &Yaml, policy_tokens: &Yaml) -> Result<(String, i32), Error> {
    let path: String = match ypath.as_str() {
        Some(string) => string.to_owned(),
        None => return Err(Error::new(ErrorKind::InvalidData, "malformed path?")),
    };
    let policy: i32 = match policy_tokens.as_str() {
        Some(string) => policy_tokens_as_int(string)?,
        None => return Err(Error::new(ErrorKind::InvalidData, "malformed policy?")),
    };
    Ok((path, policy))
}

// Borrows the YAML representation of a zakopane config and returns the
// corresponding ZakopanePolicies. The return value can be benignly
// empty (e.g. if the present config elects not to specify any rules).
fn policies_from_yaml(doc: &Yaml) -> Result<ZakopanePolicies, Error> {
    let policies_map_yaml = &doc[POLICIES_KEY];
    if policies_map_yaml.is_badvalue() {
        // Assumes the config may be benignly devoid of specific
        // policies, returning Ok.
        return Ok(vec![]);
    }
    // Otherwise, iterates over the policies map. Each entry in the
    // policies map correlates a path prefix to a comma-separated list
    // of policies.
    let policies_map: &yaml_rust::yaml::Hash = match policies_map_yaml.as_hash() {
        Some(map) => map,
        None => return Err(Error::new(ErrorKind::InvalidData, "malformed policies")),
    };
    let mut policies: ZakopanePolicies = policies_map
        .into_iter()
        .map(|pair| extract_policy(&pair.0, &pair.1))
        .collect::<Result<ZakopanePolicies, Error>>()?;
    policies.sort_unstable_by_key(|pair| pair.0.to_owned());
    Ok(policies)
}

// Borrows the YAML representation of a zakopane config and returns the
// integral default-policy defined within.
fn default_policy_from_yaml(doc: &Yaml) -> Result<i32, Error> {
    let default_policy_yaml = &doc[DEFAULT_POLICY_KEY];
    if default_policy_yaml.is_badvalue() {
        return Err(Error::new(ErrorKind::InvalidData, DEFAULT_POLICY_KEY));
    }
    let default_policy: i32 = match default_policy_yaml.as_str() {
        None => return Err(Error::new(ErrorKind::InvalidData, DEFAULT_POLICY_KEY)),
        Some(token) => policy_tokens_as_int(&token),
    }?;
    Ok(default_policy)
}

impl ZakopaneConfig {
    // Borrows the string representation of a zakopane config and
    // returns a corresponding ZakopaneConfig.
    pub fn new(config: &str) -> Result<ZakopaneConfig, Error> {
        let docs: Vec<Yaml> = match YamlLoader::load_from_str(config) {
            Ok(val) => val,
            Err(scan_error) => return Err(Error::new(ErrorKind::InvalidData, scan_error)),
        };
        if docs.len() == 0 {
            return Err(Error::new(ErrorKind::InvalidData, "empty zakopane config"));
        }
        let doc = &docs[0];

        let default_policy = default_policy_from_yaml(&doc)?;
        let policies: ZakopanePolicies = policies_from_yaml(&doc)?;

        Ok(ZakopaneConfig {
            default_policy: default_policy,
            policies: policies,
        })
    }

    // Returns how many rules this config contains.
    // This is never less than 1 as a default-policy is always required.
    pub fn rules(&self) -> usize {
        1 + self.policies.len()
    }

    // Borrows a `path` and returns the best-matched policy that
    // applies. This function returns an owned tuple of the
    // (closest-matched path expression, integral policy).
    //
    // This function represents the default-policy fallback by
    // returning the tuple consisting of an empty &str and the
    // default policy.
    pub fn match_policy(&self, path: &str) -> (&str, i32) {
        let mut best_match_path: &str = "";
        let mut best_match_policy: i32 = 0;
        for (prefix, policy) in self.policies.iter() {
            if path.starts_with(prefix) && prefix.len() > best_match_path.len() {
                best_match_path = prefix;
                best_match_policy = *policy;
            }
        }
        if best_match_path.len() <= TRIVIAL_MATCH_LENGTH {
            return ("", self.default_policy);
        }
        return (best_match_path, best_match_policy);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn policy_token_bare() {
        let policy: i32 = policy_tokens_as_int(&"noadd").unwrap();
        assert!(policy == POLICY_NOADD);

        let policy: i32 = policy_tokens_as_int(&"nodelete").unwrap();
        assert!(policy == POLICY_NODELETE);

        let policy: i32 = policy_tokens_as_int(&"nomodify").unwrap();
        assert!(policy == POLICY_NOMODIFY);
    }

    #[test]
    fn policy_tokens_can_combo() {
        let policy: i32 = policy_tokens_as_int(&"noadd,nodelete").unwrap();
        assert!(policy == POLICY_NOADD | POLICY_NODELETE);
    }

    #[test]
    fn policy_tokens_can_repeat() {
        let policy: i32 =
            policy_tokens_as_int(&"noadd,noadd,noadd,noadd,nodelete,nodelete,nodelete,noadd")
                .unwrap();
        assert!(policy == POLICY_NOADD | POLICY_NODELETE);
    }

    #[test]
    fn config_must_not_be_empty() {
        let config = "";
        assert!(!ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_must_not_be_obviously_malformed() {
        let config = r#"
This is not a zakopane config -
rather, it's two lines of text.
        "#;
        assert!(!ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_requires_default_policy() {
        let config = r#"
policies:
    hello-there: nomodify
        "#;
        assert!(!ZakopaneConfig::new(&config).is_ok());

        let mut config_with_default_policy: String = r#"
default-policy: immutable
        "#
        .to_string();
        config_with_default_policy.push_str(&config);
        assert!(ZakopaneConfig::new(&config_with_default_policy).is_ok())
    }

    #[test]
    fn config_might_not_have_specific_policies() {
        let config = r#"
default-policy: nodelete
one-irrelevant-key: it doesn't matter what we put here
another-irrelevant-key: this doesn't invalidate the YAML
third-irrelevant-key: so long as it contains a default-policy
        "#;
        assert!(ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_policies_must_be_a_map() {
        let config = r#"
default-policy: noadd
policies:
    -   eh?
    -   this ain't a map
        "#;
        assert!(!ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn config_can_have_several_policies() {
        let config = r#"
default-policy: immutable
policies:
    hello-there: noadd
    general-kenobi: nodelete
        "#;
        assert!(ZakopaneConfig::new(&config).is_ok());
    }

    #[test]
    fn match_default_policy() {
        let config_yaml = r#"
default-policy: noadd
        "#;
        let config = ZakopaneConfig::new(&config_yaml).unwrap();

        // With only a default policy, this config has just 1 rule.
        assert!(config.rules() == 1);

        // Any path prefix we throw at match_policy() shall come up
        // as the default policy.
        let (_path, policy) = config.match_policy("./Documents/hello/there.txt");
        assert!(policy == POLICY_NOADD);
        let (_path, policy) = config.match_policy("./Music/general/kenobi.txt");
        assert!(policy == POLICY_NOADD);
    }

    #[test]
    fn match_nondefault_policies() {
        let config_yaml = r#"
default-policy: immutable
policies:
    ./Pictures/: noadd
    ./Pictures/2019/third-party/: nodelete
    ./Pictures/2020/: nomodify
    ./Pictures/2020/food/: nodelete,nomodify
        "#;
        let config = ZakopaneConfig::new(&config_yaml).unwrap();

        assert!(config.rules() == 5);

        // Falls back on the default-policy absent any specific policy
        // defined for this file.
        let (_path, policy) = config.match_policy("./Documents/catch-me-senpai.txt");
        assert!(policy == POLICY_IMMUTABLE);
        // Matches only ``./Pictures.''
        let (_path, policy) = config.match_policy("./Pictures/2016/yano.jpg");
        assert!(policy == POLICY_NOADD);
        // As above and does _not_ match ``./Pictures/2019/third-party/.''
        let (_path, policy) = config.match_policy("./Pictures/2019/first-party.jpg");
        assert!(policy == POLICY_NOADD);
        // Does match ``./Pictures/2019/third-party/.''
        let (_path, policy) = config.match_policy("./Pictures/2019/third-party/yano.jpg");
        assert!(policy == POLICY_NODELETE);

        // Path prefix matching is done strictly and exactly;
        // ``food.md'' doesn't match ``food/,'' so there's no risk of
        // zakopane confusing cohabiting entities with similar basenames.
        let (path, policy) = config.match_policy("./Pictures/2020/food.md");
        assert!(policy == POLICY_NOMODIFY);
        assert!(path == "./Pictures/2020/");
        let (path, policy) = config.match_policy("./Pictures/2020/food/tacos.jpg");
        assert!(policy == POLICY_NODELETE | POLICY_NOMODIFY);
        assert!(path == "./Pictures/2020/food/");
    }
}
<|endoftext|>


<commit_before>/* kernel::cstr.rs */
#[allow(unused_imports)];
use core::*;
use core::str::*;
use core::option::{Some, Option, None}; // Match statement
use core::iter::Iterator;
use kernel::*;
use super::super::platform::*;
use kernel::memory::Allocator;

pub static DEFAULT_STRLEN: uint = 256;

pub static mut count:uint = 0;

pub struct cstr {
    p: *mut u8,
    p_cstr_i: uint,
    size: uint
}

impl cstr {
    pub fn new() -> cstr {
        unsafe {
            cstr::news(DEFAULT_STRLEN)
        }
    }

    pub unsafe fn news(size: uint) -> cstr {
        // Sometimes this doesn't allocate enough memory and gets stuck...
        let (z, w) = heap.alloc(size);
        let (x, y) = heap.alloc(size);
        let this = cstr {
            p: x,
            p_cstr_i: 0,
            size: y
        };
        *(((this.p as uint)) as *mut char) = '\0';
        this
    }

    pub fn from_str(s: &str) -> cstr {
        let mut this = cstr::new();
        for c in slice::iter(as_bytes(s)) {
            this.add_u8(*c);
        };
        this
    }

    pub fn map_u8(&mut self, f: |u8|) {
        self.map(|c: char| {
            f(c as u8)
        });
    }

    pub fn map(&self, f: |char|) {
        let mut i = 0;
        while i < self.len() && self.get_char(i) != '\0' {
            f(self.get_char(i));
            i += 1;
        }
    }

    pub unsafe fn join(&self, other: cstr) -> cstr {
        let mut len = DEFAULT_STRLEN;
        while len < self.len() + other.len() {
            len += DEFAULT_STRLEN;
        }
        let mut new = cstr::news(len);
        self.map(|c| { new.add_char(c); } );
        other.map(|c| { new.add_char(c); } );
        new
    }

    pub unsafe fn trim(&mut self) -> cstr {
        let mut new = cstr::new();
        if self.len() == 0 || self.size > new.size {
            return new;
        }
        // Kill beginning whitespace.
        let mut beginwhite = true;
        let mut i = 0;
        while i < self.len() {
            let c = self.get_char(i);
            let iswhite = (c == (' ') || c == ('\n'));
            if !iswhite || !beginwhite {
                beginwhite = false;
                new.add_char(c);
            }
            i += 1;
        }
        // Kill trailing whitespace.
        i = self.len();
        let mut endwhite = true;
        while (endwhite && i > 0) {
            let c = self.get_char(i);
            let iswhite = (c == (' ') || c == ('\n'));
            if !iswhite {
                endwhite = false;
            }
            else {
                new.delete_char();
            }
            i -= 1;
        }
        return new;
    }

    pub unsafe fn clone(&mut self) -> cstr {
        let mut ind: uint = 0;
        let mut s = cstr::news(self.size);
        loop {
            if (self.len() == 0 || ind == self.len()) {
                return s;
            }
            s.add_u8(self.get_char(ind) as u8);
            ind += 1;
            if (ind == self.size) {
                return s;
            }
        }
    }

    #[allow(dead_code)]
    pub fn len(&self) -> uint {
        self.p_cstr_i
    }

    // HELP THIS DOESN'T WORK THERE IS NO GARBAGE COLLECTION!!!
    // -- TODO: exchange_malloc, exchange_free
    #[allow(dead_code)]
    pub unsafe fn destroy(&self) {
        heap.free(self.p);
    }

    pub fn get_char(&self, idx: uint) -> char {
        if idx > self.len() || idx > self.size || self.p_cstr_i > self.size {
            return '\0';
        }
        unsafe {
            *(((self.p as uint) + idx) as *char)
        }
    }

    pub fn add_char(&mut self, c: char) -> bool {
        self.add_u8(c as u8)
    }

    pub fn add_u8(&mut self, x: u8) -> bool {
        if (self.p_cstr_i >= self.size || self.size > DEFAULT_STRLEN) {
            return false;
        }
        unsafe {
            *(((self.p as uint) + self.p_cstr_i) as *mut u8) = x;
            self.p_cstr_i += 1;
            *(((self.p as uint) + self.p_cstr_i) as *mut char) = '\0';
        }
        true
    }

    pub unsafe fn delete_char(&mut self) -> bool {
        if (self.p_cstr_i == 0) {
            return false;
        }
        self.p_cstr_i -= 1;
        *(((self.p as uint)+self.p_cstr_i) as *mut char) = '\0';
        true
    }

    pub unsafe fn reset(&mut self) {
        self.p_cstr_i = 0;
        *(self.p as *mut char) = '\0';
    }

    #[allow(dead_code)]
    pub unsafe fn eq(&self, other: &cstr) -> bool {
        if (self.len() != other.len()) { return false; }
        else {
            let mut x = 0;
            let mut selfp: uint = self.p as uint;
            let mut otherp: uint = other.p as uint;
            while x < self.len() {
                if (*(selfp as *char) != *(otherp as *char)) { return false; }
                selfp += 1;
                otherp += 1;
                x += 1;
            }
            true
        }
    }

    pub unsafe fn streq(&self, other: &str) -> bool {
        let mut selfp: uint = self.p as uint;
        for c in slice::iter(as_bytes(other)) {
            if( *c != *(selfp as *u8) ) {
                return false;
            }
            selfp += 1;
        };
        *(selfp as *char) == '\0'
    }

    pub unsafe fn getarg(&self, delim: char, mut k: uint) -> Option<cstr> {
        let mut ind: uint = 0;
        let mut found = k == 0;
        let mut selfp: uint = self.p as uint;
        let mut s = cstr::new();
        loop {
            if (self.len() == 0) {
                return None;
            }
            if (*(selfp as *char) == '\0') {
                // End of string
                if (found) { return Some(s); }
                else { return None; }
            };
            if (*(selfp as *u8) == delim as u8) {
                if (found) { return Some(s); }
                k -= 1;
            };
            if (found) {
                s.add_u8(*(selfp as *u8));
            };
            found = k == 0;
            selfp += 1;
            ind += 1;
            if (ind == self.size) {
                return None;
            }
        }
    }
    #[allow(dead_code)]
    pub unsafe fn split(&self, delim: char) -> (cstr, cstr) {
        let mut i = 0;
        let mut beg = cstr::new();
        let mut end = cstr::new();
        let mut found = false;
        while i < self.len() && self.get_char(i) != '\0' {
            if (!found && self.get_char(i) as u8 == delim as u8) {
                found = true;
            }
            else if (!found) {
                beg.add_char(self.get_char(i));
            }
            else {
                end.add_char(self.get_char(i));
            };
            i += 1;
        }
        (beg, end)
    }
}
<commit_msg>remove old kernel code; cleanup equality<commit_after>/* kernel::cstr.rs */
#[allow(unused_imports)];
use core::*;
use core::str::*;
use core::option::{Some, Option, None}; // Match statement
use core::iter::Iterator;
use kernel::*;
use super::super::platform::*;
use kernel::memory::Allocator;

pub static DEFAULT_STRLEN: uint = 256;

pub static mut count:uint = 0;

pub struct cstr {
    p: *mut u8,
    p_cstr_i: uint,
    size: uint
}

impl cstr {
    pub fn new() -> cstr {
        unsafe {
            cstr::news(DEFAULT_STRLEN)
        }
    }

    pub unsafe fn news(size: uint) -> cstr {
        // Sometimes this doesn't allocate enough memory and gets stuck...
        let (x, y) = heap.alloc(size);
        let this = cstr {
            p: x,
            p_cstr_i: 0,
            size: y
        };
        *(((this.p as uint)) as *mut char) = '\0';
        this
    }

    pub fn from_str(s: &str) -> cstr {
        let mut this = cstr::new();
        for c in slice::iter(as_bytes(s)) {
            this.add_u8(*c);
        };
        this
    }

    pub fn map_u8(&mut self, f: |u8|) {
        self.map(|c: char| {
            f(c as u8)
        });
    }

    pub fn map(&self, f: |char|) {
        let mut i = 0;
        while i < self.len() && self.get_char(i) != '\0' {
            f(self.get_char(i));
            i += 1;
        }
    }

    pub unsafe fn join(&self, other: cstr) -> cstr {
        let mut len = DEFAULT_STRLEN;
        while len < self.len() + other.len() {
            len += DEFAULT_STRLEN;
        }
        let mut new = cstr::news(len);
        self.map(|c| { new.add_char(c); } );
        other.map(|c| { new.add_char(c); } );
        new
    }

    pub unsafe fn trim(&mut self) -> cstr {
        let mut new = cstr::new();
        if self.len() == 0 || self.size > new.size {
            return new;
        }
        // Kill beginning whitespace.
        let mut beginwhite = true;
        let mut i = 0;
        while i < self.len() {
            let c = self.get_char(i);
            let iswhite = (c == (' ') || c == ('\n'));
            if !iswhite || !beginwhite {
                beginwhite = false;
                new.add_char(c);
            }
            i += 1;
        }
        // Kill trailing whitespace.
        i = self.len();
        let mut endwhite = true;
        while (endwhite && i > 0) {
            let c = self.get_char(i);
            let iswhite = (c == (' ') || c == ('\n'));
            if !iswhite {
                endwhite = false;
            }
            else {
                new.delete_char();
            }
            i -= 1;
        }
        return new;
    }

    pub unsafe fn clone(&mut self) -> cstr {
        let mut ind: uint = 0;
        let mut s = cstr::news(self.size);
        loop {
            if (self.len() == 0 || ind == self.len()) {
                return s;
            }
            s.add_u8(self.get_char(ind) as u8);
            ind += 1;
            if (ind == self.size) {
                return s;
            }
        }
    }

    #[allow(dead_code)]
    pub fn len(&self) -> uint {
        self.p_cstr_i
    }

    // HELP THIS DOESN'T WORK THERE IS NO GARBAGE COLLECTION!!!
    // -- TODO: exchange_malloc, exchange_free
    #[allow(dead_code)]
    pub unsafe fn destroy(&self) {
        heap.free(self.p);
    }

    pub fn get_char(&self, idx: uint) -> char {
        if idx > self.len() || idx > self.size || self.p_cstr_i > self.size {
            return '\0';
        }
        unsafe {
            *(((self.p as uint) + idx) as *char)
        }
    }

    pub fn add_char(&mut self, c: char) -> bool {
        self.add_u8(c as u8)
    }

    pub fn add_u8(&mut self, x: u8) -> bool {
        if (self.p_cstr_i >= self.size || self.size > DEFAULT_STRLEN) {
            return false;
        }
        unsafe {
            *(((self.p as uint) + self.p_cstr_i) as *mut u8) = x;
            self.p_cstr_i += 1;
            *(((self.p as uint) + self.p_cstr_i) as *mut char) = '\0';
        }
        true
    }

    pub unsafe fn delete_char(&mut self) -> bool {
        if (self.p_cstr_i == 0) {
            return false;
        }
        self.p_cstr_i -= 1;
        *(((self.p as uint)+self.p_cstr_i) as *mut char) = '\0';
        true
    }

    pub unsafe fn reset(&mut self) {
        self.p_cstr_i = 0;
        *(self.p as *mut char) = '\0';
    }

    pub unsafe fn eq(&self, other: &cstr) -> bool {
        // lengths not equal -> false
        if (self.len() != other.len()) {
            return false;
        }
        // both lengths are zero -> true
        if self.len() == 0 && other.len() == 0 {
            return true;
        }
        let mut i = 0;
        while i < self.len() {
            if (self.get_char(i) as u8) != (other.get_char(i) as u8) {
                return false;
            }
            i += 1;
        }
        true
    }

    pub unsafe fn streq(&self, other: &str) -> bool {
        let mut i = 0;
        for c in slice::iter(as_bytes(other)) {
            let selfc = self.get_char(i) as u8;
            if( *c != selfc) {
                return false;
            }
            i += 1;
        };
        return true;
    }

    pub unsafe fn split(&self, delim: char) -> (cstr, cstr) {
        let mut i = 0;
        let mut beg = cstr::new();
        let mut end = cstr::new();
        let mut found = false;
        while i < self.len() && self.get_char(i) != '\0' {
            if (!found && self.get_char(i) as u8 == delim as u8) {
                found = true;
            }
            else if (!found) {
                beg.add_char(self.get_char(i));
            }
            else {
                end.add_char(self.get_char(i));
            };
            i += 1;
        }
        (beg, end)
    }
}
<|endoftext|>


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        Ok(txn_arg)
    }
}

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

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

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

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

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

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

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

        self.txn_options
            .submit_transaction(txn)
            .await
            .map(TransactionSummary::from)
    }
}
<commit_msg>[aptos-cli] Update metadata for proposal submission<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        Ok(txn_arg)
    }
}

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

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

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

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

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

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

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

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


<commit_before>/*
 * Copyright (c) 2013, David Renshaw (dwrenshaw@gmail.com)
 *
 * See the LICENSE file in the capnproto-rust root directory.
 */

use std;

use rand::*;
use common::*;
use catrank_capnp::*;

pub type RequestBuilder<'a> = SearchResultList::Builder<'a>;
pub type ResponseBuilder<'a> = SearchResultList::Builder<'a>;
pub type Expectation = int;
pub type RequestReader<'a> = SearchResultList::Reader<'a>;
pub type ResponseReader<'a> = SearchResultList::Reader<'a>;

pub struct ScoredResult<'a> {
    score : f64,
    result : SearchResult::Reader<'a>
}

static URL_PREFIX : &'static str = "http://example.com";

pub fn setup_request(rng : &mut FastRand, request : SearchResultList::Builder) -> int {
    let count = rng.nextLessThan(1000) as uint;
    let mut goodCount : int = 0;

    let list = request.init_results(count);

    for i in range(0, count) {
        let result = list[i];
        result.set_score(1000.0 - i as f64);
        let url_size = rng.nextLessThan(100) as uint;

        let url_prefix_length = URL_PREFIX.as_bytes().len();
        let url = result.init_url(url_size + url_prefix_length);

        let bytes = url.as_mut_bytes();
        std::io::BufWriter::new(bytes).write(URL_PREFIX.as_bytes()).unwrap();

        for j in range(0, url_size) {
            bytes[j + url_prefix_length] = (97 + rng.nextLessThan(26)) as u8;
        }

        let isCat = rng.nextLessThan(8) == 0;
        let isDog = rng.nextLessThan(8) == 0;
        if isCat && !isDog {
            goodCount += 1;
        }

        let mut snippet = StrBuf::from_str(" ");

        let prefix = rng.nextLessThan(20) as uint;
        for _ in range(0, prefix) {
            snippet.push_str(WORDS[rng.nextLessThan(WORDS.len() as u32) as uint]);
        }
        if isCat { snippet.push_str("cat ") }
        if isDog { snippet.push_str("dog ") }

        let suffix = rng.nextLessThan(20) as uint;
        for _ in range(0, suffix) {
            snippet.push_str(WORDS[rng.nextLessThan(WORDS.len() as u32) as uint]);
        }

        result.set_snippet(snippet.as_slice());
    }

    goodCount
}

pub fn handle_request(request : SearchResultList::Reader,
                     response : SearchResultList::Builder) {
    let mut scoredResults : ~[ScoredResult] = ~[];

    let results = request.get_results();
    for i in range(0, results.size()) {
        let result = results[i];
        let mut score = result.get_score();
        if result.get_snippet().contains(" cat ") {
            score *= 10000.0;
        }
        if result.get_snippet().contains(" dog ") {
            score /= 10000.0;
        }
        scoredResults.push(ScoredResult {score : score, result : result});
    }

    scoredResults.sort_by(|v1, v2| { if v1.score < v2.score { std::cmp::Less } else { std::cmp::Greater } });

    let list = response.init_results(scoredResults.len());
    for i in range(0, list.size()) {
        let item = list[i];
        let result = scoredResults[i];
        item.set_score(result.score);
        item.set_url(result.result.get_url());
        item.set_snippet(result.result.get_snippet());
    }
}

pub fn check_response(response : SearchResultList::Reader, expectedGoodCount : int) -> bool {
    let mut goodCount : int = 0;
    let results = response.get_results();
    for i in range(0, results.size()) {
        let result = results[i];
        if result.get_score() > 1001.0 {
            goodCount += 1;
        } else {
            break;
        }
    }
    return goodCount == expectedGoodCount;
}
<commit_msg>~[T] no longer growable<commit_after>/*
 * Copyright (c) 2013, David Renshaw (dwrenshaw@gmail.com)
 *
 * See the LICENSE file in the capnproto-rust root directory.
 */

use std;

use rand::*;
use common::*;
use catrank_capnp::*;

pub type RequestBuilder<'a> = SearchResultList::Builder<'a>;
pub type ResponseBuilder<'a> = SearchResultList::Builder<'a>;
pub type Expectation = int;
pub type RequestReader<'a> = SearchResultList::Reader<'a>;
pub type ResponseReader<'a> = SearchResultList::Reader<'a>;

pub struct ScoredResult<'a> {
    score : f64,
    result : SearchResult::Reader<'a>
}

static URL_PREFIX : &'static str = "http://example.com";

pub fn setup_request(rng : &mut FastRand, request : SearchResultList::Builder) -> int {
    let count = rng.nextLessThan(1000) as uint;
    let mut goodCount : int = 0;

    let list = request.init_results(count);

    for i in range(0, count) {
        let result = list[i];
        result.set_score(1000.0 - i as f64);
        let url_size = rng.nextLessThan(100) as uint;

        let url_prefix_length = URL_PREFIX.as_bytes().len();
        let url = result.init_url(url_size + url_prefix_length);

        let bytes = url.as_mut_bytes();
        std::io::BufWriter::new(bytes).write(URL_PREFIX.as_bytes()).unwrap();

        for j in range(0, url_size) {
            bytes[j + url_prefix_length] = (97 + rng.nextLessThan(26)) as u8;
        }

        let isCat = rng.nextLessThan(8) == 0;
        let isDog = rng.nextLessThan(8) == 0;
        if isCat && !isDog {
            goodCount += 1;
        }

        let mut snippet = StrBuf::from_str(" ");

        let prefix = rng.nextLessThan(20) as uint;
        for _ in range(0, prefix) {
            snippet.push_str(WORDS[rng.nextLessThan(WORDS.len() as u32) as uint]);
        }
        if isCat { snippet.push_str("cat ") }
        if isDog { snippet.push_str("dog ") }

        let suffix = rng.nextLessThan(20) as uint;
        for _ in range(0, suffix) {
            snippet.push_str(WORDS[rng.nextLessThan(WORDS.len() as u32) as uint]);
        }

        result.set_snippet(snippet.as_slice());
    }

    goodCount
}

pub fn handle_request(request : SearchResultList::Reader,
                     response : SearchResultList::Builder) {
    let mut scoredResults : Vec<ScoredResult> = Vec::new();

    let results = request.get_results();
    for i in range(0, results.size()) {
        let result = results[i];
        let mut score = result.get_score();
        if result.get_snippet().contains(" cat ") {
            score *= 10000.0;
        }
        if result.get_snippet().contains(" dog ") {
            score /= 10000.0;
        }
        scoredResults.push(ScoredResult {score : score, result : result});
    }

    scoredResults.sort_by(|v1, v2| { if v1.score < v2.score { std::cmp::Less } else { std::cmp::Greater } });

    let list = response.init_results(scoredResults.len());
    for i in range(0, list.size()) {
        let item = list[i];
        let result = scoredResults.get(i);
        item.set_score(result.score);
        item.set_url(result.result.get_url());
        item.set_snippet(result.result.get_snippet());
    }
}

pub fn check_response(response : SearchResultList::Reader, expectedGoodCount : int) -> bool {
    let mut goodCount : int = 0;
    let results = response.get_results();
    for i in range(0, results.size()) {
        let result = results[i];
        if result.get_score() > 1001.0 {
            goodCount += 1;
        } else {
            break;
        }
    }
    return goodCount == expectedGoodCount;
}
<|endoftext|>


<commit_before>use std::path::PathBuf;
use std::process::Command;

// represents an archive we download from crates.io
#[derive(Debug)]
struct KrateSource {
    version: String,
    name: String,
}

// represents the extracted sourcecode of a crate
#[derive(Debug)]
struct Krate {
    version: String,
    name: String,
    path: PathBuf,
}

impl KrateSource {
    fn new(name: &str, version: &str) -> Self {
        KrateSource {
            version: version.into(),
            name: name.into(),
        }
    }
    fn download_and_extract(&self) -> Krate {
        let extract_dir = PathBuf::from("target/crater/crates");

        // download
        let krate_download_dir = PathBuf::from("target/crater/downloads");

        let url = format!(
            "https://crates.io/api/v1/crates/{}/{}/download",
            self.name, self.version
        );
        println!("Downloading {}, {} / {}", self.name, self.version, url);
        let _ = std::fs::create_dir("target/crater/");

        let _ = std::fs::create_dir(&krate_download_dir);
        let _ = std::fs::create_dir(&extract_dir);

        let krate_name = format!("{}-{}.crate.tar.gz", &self.name, &self.version);
        let krate_file_path = krate_download_dir.join(krate_name);
        let mut krate_dest = std::fs::File::create(&krate_file_path).unwrap();
        let mut krate_req = ureq::get(&url).call().unwrap().into_reader();
        std::io::copy(&mut krate_req, &mut krate_dest).unwrap();
        // unzip the tarball
        let dl = std::fs::File::open(krate_file_path).unwrap();

        let ungz_tar = flate2::read::GzDecoder::new(dl);
        // extract the tar archive
        let mut archiv = tar::Archive::new(ungz_tar);
        let extract_path = extract_dir.clone();
        archiv.unpack(&extract_path).expect("Failed to extract!");
        // extracted
        dbg!(&extract_path);
        Krate {
            version: self.version.clone(),
            name: self.name.clone(),
            path: extract_dir.join(format!("{}-{}/", self.name, self.version))
        }
    }
}

impl Krate {
    fn run_clippy_lints(&self, cargo_clippy_path:  &PathBuf) -> String {
        let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap();
        let project_root = &self.path;
        dbg!(&cargo_clippy_path);
        dbg!(&project_root);

        let output = std::process::Command::new(cargo_clippy_path).current_dir(project_root).output();
        dbg!(&output);
        let output = String::from_utf8_lossy(&output.unwrap().stderr).into_owned();
        dbg!(&output);
        output
    }
}

fn build_clippy() {
    Command::new("cargo")
        .arg("build")
        .output()
        .expect("Failed to build clippy!");
}

// the main fn
pub fn run() {
    let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy");
    let clippy_driver_path: PathBuf = PathBuf::from("target/debug/clippy-driver");

    // crates we want to check:
    let krates: Vec<KrateSource> = vec![KrateSource::new("regex", "1.4.2"), KrateSource::new("cargo", "0.49.0"), ];

    println!("Compiling clippy...");
    build_clippy();
    println!("Done compiling");

    // assert that clippy is found
    assert!(
        cargo_clippy_path.is_file(),
        "target/debug/cargo-clippy binary not found! {}",
        cargo_clippy_path.display()
    );
    assert!(
        clippy_driver_path.is_file(),
        "target/debug/clippy-driver binary not found! {}",
        clippy_driver_path.display()
     );

    // download and extract the crates, then run clippy on them and collect clippys warnings
    let _clippy_lint_results: Vec<String> = krates
        .into_iter()
        .map(|krate| krate.download_and_extract())
        .map(|krate| krate.run_clippy_lints(&cargo_clippy_path))
        .collect::<Vec<String>>();
}
<commit_msg>cargo clippy dev: collecting one-line clippy warnings works now<commit_after>use std::path::PathBuf;
use std::process::Command;

// represents an archive we download from crates.io
#[derive(Debug)]
struct KrateSource {
    version: String,
    name: String,
}

// represents the extracted sourcecode of a crate
#[derive(Debug)]
struct Krate {
    version: String,
    name: String,
    path: PathBuf,
}

impl KrateSource {
    fn new(name: &str, version: &str) -> Self {
        KrateSource {
            version: version.into(),
            name: name.into(),
        }
    }
    fn download_and_extract(&self) -> Krate {
        let extract_dir = PathBuf::from("target/crater/crates");

        // download
        let krate_download_dir = PathBuf::from("target/crater/downloads");

        let url = format!(
            "https://crates.io/api/v1/crates/{}/{}/download",
            self.name, self.version
        );
        println!("Downloading {}, {} / {}", self.name, self.version, url);
        let _ = std::fs::create_dir("target/crater/");

        let _ = std::fs::create_dir(&krate_download_dir);
        let _ = std::fs::create_dir(&extract_dir);

        let krate_name = format!("{}-{}.crate.tar.gz", &self.name, &self.version);
        let krate_file_path = krate_download_dir.join(krate_name);
        let mut krate_dest = std::fs::File::create(&krate_file_path).unwrap();
        let mut krate_req = ureq::get(&url).call().unwrap().into_reader();
        std::io::copy(&mut krate_req, &mut krate_dest).unwrap();
        // unzip the tarball
        let dl = std::fs::File::open(krate_file_path).unwrap();

        let ungz_tar = flate2::read::GzDecoder::new(dl);
        // extract the tar archive
        let mut archiv = tar::Archive::new(ungz_tar);
        let extract_path = extract_dir.clone();
        archiv.unpack(&extract_path).expect("Failed to extract!");
        // extracted
        dbg!(&extract_path);
        Krate {
            version: self.version.clone(),
            name: self.name.clone(),
            path: extract_dir.join(format!("{}-{}/", self.name, self.version)),
        }
    }
}

impl Krate {
    fn run_clippy_lints(&self, cargo_clippy_path: &PathBuf) -> String {
        let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap();
        let project_root = &self.path;
        dbg!(&cargo_clippy_path);
        dbg!(&project_root);

        let output = std::process::Command::new(cargo_clippy_path)
            .args(&["--", "--message-format=short","--", "--cap-lints=warn",])
            .current_dir(project_root)
            .output();
        let output: String = String::from_utf8_lossy(&output.unwrap().stderr).lines().filter(|line|line.contains(": warning: ")).collect();
       // output.lines().for_each(|l| println!("{}", l));
        //dbg!(&output);

        output
    }
}

fn build_clippy() {
    Command::new("cargo")
        .arg("build")
        .output()
        .expect("Failed to build clippy!");
}

// the main fn
pub fn run() {
    let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy");
    let clippy_driver_path: PathBuf = PathBuf::from("target/debug/clippy-driver");

    // crates we want to check:
    let krates: Vec<KrateSource> = vec![KrateSource::new("regex", "1.4.2"), KrateSource::new("cargo", "0.49.0")];

    println!("Compiling clippy...");
    build_clippy();
    println!("Done compiling");

    // assert that clippy is found
    assert!(
        cargo_clippy_path.is_file(),
        "target/debug/cargo-clippy binary not found! {}",
        cargo_clippy_path.display()
    );
    assert!(
        clippy_driver_path.is_file(),
        "target/debug/clippy-driver binary not found! {}",
        clippy_driver_path.display()
    );

    // download and extract the crates, then run clippy on them and collect clippys warnings
    let clippy_lint_results: Vec<String> = krates
        .into_iter()
        .map(|krate| krate.download_and_extract())
        .map(|krate| krate.run_clippy_lints(&cargo_clippy_path))
        .collect::<Vec<String>>();
        dbg!(clippy_lint_results);
}
<|endoftext|>