<commit_before>// Copyright (c) 2014 Robert Clipsham <robert@octarineparrot.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]

extern crate libc;

pub const AF_LINK: libc::c_int = 18;

const IF_NAMESIZE: uint = 16;
const IFNAMSIZ: uint = IF_NAMESIZE;
const IOC_IN: libc::c_ulong = 0x80000000;
const IOC_OUT: libc::c_ulong = 0x40000000;
const IOC_INOUT: libc::c_ulong = IOC_IN | IOC_OUT;
const IOCPARM_SHIFT: libc::c_ulong = 13;
const IOCPARM_MASK: libc::c_ulong = (1 << (IOCPARM_SHIFT as uint)) - 1;

const SIZEOF_IFREQ: libc::c_ulong = 32;
const SIZEOF_C_UINT: libc::c_ulong = 4;
#[cfg(target_os = "freebsd")]
const SIZEOF_C_LONG: libc::c_int = 8;

pub const BIOCSETIF: libc::c_ulong = IOC_IN |
                                      ((SIZEOF_IFREQ & IOCPARM_MASK) << 16u) |
                                      ('B' as libc::c_ulong << 8u) |
                                      108;
pub const BIOCIMMEDIATE: libc::c_ulong = IOC_IN |
                                          ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                          ('B' as libc::c_ulong << 8) |
                                          112;
pub const BIOCGBLEN: libc::c_ulong = IOC_OUT |
                                      ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                      ('B' as libc::c_ulong << 8) |
                                      102;
pub const BIOCGDLT: libc::c_ulong = IOC_OUT |
                                      ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                      ('B' as libc::c_ulong << 8) |
                                      106;

pub const BIOCSBLEN: libc::c_ulong = IOC_INOUT |
                                      ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                      ('B' as libc::c_ulong << 8) |
                                      102;
pub const BIOCSHDRCMPLT: libc::c_ulong = IOC_IN |
                                          ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                          ('B' as libc::c_ulong << 8) |
                                          117;

#[cfg(target_os = "freebsd")]
pub const BIOCFEEDBACK: libc::c_ulong = IOC_IN |
                                          ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                          ('B' as libc::c_ulong << 8) |
                                          124;
// NOTE Could use BIOCSSEESENT on OS X, though set to 1 by default anyway

pub const DLT_NULL: libc::c_uint = 0;

#[cfg(target_os = "freebsd")]
const BPF_ALIGNMENT: libc::c_int = SIZEOF_C_LONG;
#[cfg(target_os = "macos")]
#[cfg(windows)]
const BPF_ALIGNMENT: libc::c_int = 4;

pub fn BPF_WORDALIGN<T : Int + ToPrimitive + FromPrimitive>(x: T) -> T {
    use std::num::{from_i32};
    let bpf_alignment: T = from_i32(BPF_ALIGNMENT).unwrap();
    let one: T = from_i32(1).unwrap();

    (x + (bpf_alignment - one)) & !(bpf_alignment - one)
}

// See /usr/include/net/if.h
pub struct ifreq {
    pub ifr_name: [libc::c_char, ..IFNAMSIZ],
    pub ifru_addr: libc::sockaddr, // NOTE Should be a union
}

// See /usr/include/net/if_dl.h
// sdl_data does not match if_dl.h, since the size of 12 is a minimum. Will be unsafe when sdl_nlen > 40.
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
pub struct sockaddr_dl {
    pub sdl_len: libc::c_uchar,
    pub sdl_family: libc::c_uchar,
    pub sdl_index: libc::c_ushort,
    pub sdl_type: libc::c_uchar,
    pub sdl_nlen: libc::c_uchar,
    pub sdl_alen: libc::c_uchar,
    pub sdl_slen: libc::c_uchar,
    pub sdl_data: [libc::c_char, ..46],
}

// See man 4 bpf or /usr/include/net/bpf.h [windows: or Common/Packet32.h]
#[cfg(target_os = "freebsd")]
#[cfg(target_os = "macos", target_word_size = "32")]
#[cfg(windows)]
pub struct bpf_hdr {
    pub bh_tstamp: libc::timeval,
    pub bh_caplen: u32,
    pub bh_datalen: u32,
    pub bh_hdrlen: libc::c_ushort,
}

pub struct timeval32 {
    pub tv_sec: i32,
    pub tv_usec: i32,
}

#[cfg(target_os = "macos", target_word_size = "64")]
pub struct bpf_hdr {
    pub bh_tstamp: timeval32,
    pub bh_caplen: u32,
    pub bh_datalen: u32,
    pub bh_hdrlen: libc::c_ushort,
}

#[cfg(not(windows))]
extern {
    pub fn ioctl(d: libc::c_int, request: libc::c_ulong, ...) -> libc::c_int;
}

<commit_msg>specify that the mismatch is only on OS X<commit_after>// Copyright (c) 2014 Robert Clipsham <robert@octarineparrot.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]

extern crate libc;

pub const AF_LINK: libc::c_int = 18;

const IF_NAMESIZE: uint = 16;
const IFNAMSIZ: uint = IF_NAMESIZE;
const IOC_IN: libc::c_ulong = 0x80000000;
const IOC_OUT: libc::c_ulong = 0x40000000;
const IOC_INOUT: libc::c_ulong = IOC_IN | IOC_OUT;
const IOCPARM_SHIFT: libc::c_ulong = 13;
const IOCPARM_MASK: libc::c_ulong = (1 << (IOCPARM_SHIFT as uint)) - 1;

const SIZEOF_IFREQ: libc::c_ulong = 32;
const SIZEOF_C_UINT: libc::c_ulong = 4;
#[cfg(target_os = "freebsd")]
const SIZEOF_C_LONG: libc::c_int = 8;

pub const BIOCSETIF: libc::c_ulong = IOC_IN |
                                      ((SIZEOF_IFREQ & IOCPARM_MASK) << 16u) |
                                      ('B' as libc::c_ulong << 8u) |
                                      108;
pub const BIOCIMMEDIATE: libc::c_ulong = IOC_IN |
                                          ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                          ('B' as libc::c_ulong << 8) |
                                          112;
pub const BIOCGBLEN: libc::c_ulong = IOC_OUT |
                                      ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                      ('B' as libc::c_ulong << 8) |
                                      102;
pub const BIOCGDLT: libc::c_ulong = IOC_OUT |
                                      ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                      ('B' as libc::c_ulong << 8) |
                                      106;

pub const BIOCSBLEN: libc::c_ulong = IOC_INOUT |
                                      ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                      ('B' as libc::c_ulong << 8) |
                                      102;
pub const BIOCSHDRCMPLT: libc::c_ulong = IOC_IN |
                                          ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                          ('B' as libc::c_ulong << 8) |
                                          117;

#[cfg(target_os = "freebsd")]
pub const BIOCFEEDBACK: libc::c_ulong = IOC_IN |
                                          ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) |
                                          ('B' as libc::c_ulong << 8) |
                                          124;
// NOTE Could use BIOCSSEESENT on OS X, though set to 1 by default anyway

pub const DLT_NULL: libc::c_uint = 0;

#[cfg(target_os = "freebsd")]
const BPF_ALIGNMENT: libc::c_int = SIZEOF_C_LONG;
#[cfg(target_os = "macos")]
#[cfg(windows)]
const BPF_ALIGNMENT: libc::c_int = 4;

pub fn BPF_WORDALIGN<T : Int + ToPrimitive + FromPrimitive>(x: T) -> T {
    use std::num::{from_i32};
    let bpf_alignment: T = from_i32(BPF_ALIGNMENT).unwrap();
    let one: T = from_i32(1).unwrap();

    (x + (bpf_alignment - one)) & !(bpf_alignment - one)
}

// See /usr/include/net/if.h
pub struct ifreq {
    pub ifr_name: [libc::c_char, ..IFNAMSIZ],
    pub ifru_addr: libc::sockaddr, // NOTE Should be a union
}

// See /usr/include/net/if_dl.h
// sdl_data does not match if_dl.h on OS X, since the size of 12 is a minimum. Will be unsafe
// when sdl_nlen > 40.
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
pub struct sockaddr_dl {
    pub sdl_len: libc::c_uchar,
    pub sdl_family: libc::c_uchar,
    pub sdl_index: libc::c_ushort,
    pub sdl_type: libc::c_uchar,
    pub sdl_nlen: libc::c_uchar,
    pub sdl_alen: libc::c_uchar,
    pub sdl_slen: libc::c_uchar,
    pub sdl_data: [libc::c_char, ..46],
}

// See man 4 bpf or /usr/include/net/bpf.h [windows: or Common/Packet32.h]
#[cfg(target_os = "freebsd")]
#[cfg(target_os = "macos", target_word_size = "32")]
#[cfg(windows)]
pub struct bpf_hdr {
    pub bh_tstamp: libc::timeval,
    pub bh_caplen: u32,
    pub bh_datalen: u32,
    pub bh_hdrlen: libc::c_ushort,
}

pub struct timeval32 {
    pub tv_sec: i32,
    pub tv_usec: i32,
}

#[cfg(target_os = "macos", target_word_size = "64")]
pub struct bpf_hdr {
    pub bh_tstamp: timeval32,
    pub bh_caplen: u32,
    pub bh_datalen: u32,
    pub bh_hdrlen: libc::c_ushort,
}

#[cfg(not(windows))]
extern {
    pub fn ioctl(d: libc::c_int, request: libc::c_ulong, ...) -> libc::c_int;
}

<|endoftext|>


<commit_before>/// Lazy iterator over bits of a vector of bytes, starting with the LSB
/// (least-significat bit) of the first element of the vector.
pub struct BitIterator { object: Vec<u8>, current_byte: uint, current_bit: uint }

impl BitIterator {
    pub fn new(obj: Vec<u8>) -> BitIterator {
        BitIterator { object: obj, current_byte: 0, current_bit: 0 }
    }
}

impl Iterator<u8> for BitIterator {
    fn next(&mut self) -> Option<u8> {
        let result = self.object[self.current_byte] >> self.current_bit & 0x1;

        if self.current_bit + 1 == ::std::u8::BITS {
            self.current_byte += 1;
        }
        self.current_bit = (self.current_bit + 1) % ::std::u8::BITS;

        if self.current_byte == self.object.len() {
            return None;
        } else {
            return Some(result);
        }
    }
}
<commit_msg>Add BitIterator test.<commit_after>/// Lazy iterator over bits of a vector of bytes, starting with the LSB
/// (least-significat bit) of the first element of the vector.
pub struct BitIterator { object: Vec<u8>, current_byte: uint, current_bit: uint }

impl BitIterator {
    pub fn new(obj: Vec<u8>) -> BitIterator {
        BitIterator { object: obj, current_byte: 0, current_bit: 0 }
    }
}

impl Iterator<u8> for BitIterator {
    fn next(&mut self) -> Option<u8> {
        let result = self.object[self.current_byte] >> self.current_bit & 0x1;

        if self.current_bit + 1 == ::std::u8::BITS {
            self.current_byte += 1;
        }
        self.current_bit = (self.current_bit + 1) % ::std::u8::BITS;

        if self.current_byte == self.object.len() {
            return None;
        } else {
            return Some(result);
        }
    }
}

#[test]
fn test_iterator() {
    let bytes = vec!(0xCA, 0xFE);
    let expected_bits = vec!(0,1,0,1, 0,0,1,1, 0,1,1,1, 1,1,1,1);
    let mut i = 0;

    for bit in BitIterator::new(bytes) {
        println!("{} == {}", bit, expected_bits[i]);
        assert_eq!(bit, expected_bits[i]);
        i += 1;
    }
}
<|endoftext|>


<commit_before>use codegen::*;
use internal::*;
use templating::*;

use amq_protocol_types::*;
use serde_json::{self, Value};

#[derive(Debug, Serialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    pub major_version: u8,
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn load() -> AMQProtocolDefinition {
        let specs = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/specs/amqp-rabbitmq-0.9.1.json"));

        serde_json::from_str::<_AMQProtocolDefinition>(specs).expect("Failed to parse AMQP specs file").to_specs()
    }

    pub fn code_generator(self, templates: AMQPTemplates) -> CodeGenerator {
        CodeGenerator::new(self, templates)
    }

    pub fn simple_code_generator(self, template: String) -> CodeGenerator {
        self.code_generator(AMQPTemplates {
            main: template,
            ..Default::default()
        })
    }
}

#[derive(Debug, Serialize)]
pub struct AMQPDomain {
    pub name:      String,
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

#[derive(Debug, Serialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

#[derive(Debug, Serialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

#[derive(Debug, Serialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}
<commit_msg>codegen: impl Deserialize for specs structs<commit_after>use codegen::*;
use internal::*;
use templating::*;

use amq_protocol_types::*;
use serde_json::{self, Value};

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    pub major_version: u8,
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn load() -> AMQProtocolDefinition {
        let specs = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/specs/amqp-rabbitmq-0.9.1.json"));

        serde_json::from_str::<_AMQProtocolDefinition>(specs).expect("Failed to parse AMQP specs file").to_specs()
    }

    pub fn code_generator(self, templates: AMQPTemplates) -> CodeGenerator {
        CodeGenerator::new(self, templates)
    }

    pub fn simple_code_generator(self, template: String) -> CodeGenerator {
        self.code_generator(AMQPTemplates {
            main: template,
            ..Default::default()
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPDomain {
    pub name:      String,
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}
<|endoftext|>


<commit_before>use crate::clippy_project_root;
use std::fs::{self, OpenOptions};
use std::io::prelude::*;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};

struct LintData<'a> {
    pass: &'a str,
    name: &'a str,
    category: &'a str,
    project_root: PathBuf,
}

trait Context {
    fn context<C: AsRef<str>>(self, text: C) -> Self;
}

impl<T> Context for io::Result<T> {
    fn context<C: AsRef<str>>(self, text: C) -> Self {
        match self {
            Ok(t) => Ok(t),
            Err(e) => {
                let message = format!("{}: {}", text.as_ref(), e);
                Err(io::Error::new(ErrorKind::Other, message))
            },
        }
    }
}

/// Creates the files required to implement and test a new lint and runs `update_lints`.
///
/// # Errors
///
/// This function errors out if the files couldn't be created or written to.
pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> {
    let lint = LintData {
        pass: pass.expect("`pass` argument is validated by clap"),
        name: lint_name.expect("`name` argument is validated by clap"),
        category: category.expect("`category` argument is validated by clap"),
        project_root: clippy_project_root(),
    };

    create_lint(&lint, msrv).context("Unable to create lint implementation")?;
    create_test(&lint).context("Unable to create a test for the new lint")
}

fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
    let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
        "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
        "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
        _ => {
            unreachable!("`pass_type` should only ever be `early` or `late`!");
        },
    };

    let camel_case_name = to_camel_case(lint.name);
    let lint_contents = get_lint_file_contents(
        lint.pass,
        pass_type,
        pass_lifetimes,
        lint.name,
        &camel_case_name,
        lint.category,
        pass_import,
        context_import,
        enable_msrv,
    );

    let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
    write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
}

fn create_test(lint: &LintData<'_>) -> io::Result<()> {
    fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
        let mut path = location.into().join(case);
        fs::create_dir(&path)?;
        write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;

        path.push("src");
        fs::create_dir(&path)?;
        let header = format!("// compile-flags: --crate-name={}", lint_name);
        write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;

        Ok(())
    }

    if lint.category == "cargo" {
        let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
        let test_dir = lint.project_root.join(relative_test_dir);
        fs::create_dir(&test_dir)?;

        create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
        create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
    } else {
        let test_path = format!("tests/ui/{}.rs", lint.name);
        let test_contents = get_test_file_contents(lint.name, None);
        write_file(lint.project_root.join(test_path), test_contents)
    }
}

fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
    fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
        OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(path)?
            .write_all(contents)
    }

    inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
}

fn to_camel_case(name: &str) -> String {
    name.split('_')
        .map(|s| {
            if s.is_empty() {
                String::from("")
            } else {
                [&s[0..1].to_uppercase(), &s[1..]].concat()
            }
        })
        .collect()
}

fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
    let mut contents = format!(
        "#![warn(clippy::{})]

fn main() {{
    // test code goes here
}}
",
        lint_name
    );

    if let Some(header) = header_commands {
        contents = format!("{}\n{}", header, contents);
    }

    contents
}

fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
    format!(
        r#"
# {}

[package]
name = "{}"
version = "0.1.0"
publish = false

[workspace]
"#,
        hint, lint_name
    )
}

fn get_lint_file_contents(
    pass_name: &str,
    pass_type: &str,
    pass_lifetimes: &str,
    lint_name: &str,
    camel_case_name: &str,
    category: &str,
    pass_import: &str,
    context_import: &str,
    enable_msrv: bool,
) -> String {
    let mut result = String::new();

    let name_camel = camel_case_name;
    let name_upper = lint_name.to_uppercase();

    result.push_str(&if enable_msrv {
        format!(
            "use clippy_utils::msrvs;
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
use rustc_semver::RustcVersion;
use rustc_session::{{declare_tool_lint, impl_lint_pass}};

",
            pass_type = pass_type,
            pass_import = pass_import,
            context_import = context_import,
        )
    } else {
        format!(
            "{pass_import}
use rustc_lint::{{{context_import}, {pass_type}}};
use rustc_session::{{declare_lint_pass, declare_tool_lint}};

",
            pass_import = pass_import,
            pass_type = pass_type,
            context_import = context_import
        )
    });

    result.push_str(&format!(
        "declare_clippy_lint! {{
    /// ### What it does
    ///
    /// ### Why is this bad?
    ///
    /// ### Example
    /// ```rust
    /// // example code where clippy issues a warning
    /// ```
    /// Use instead:
    /// ```rust
    /// // example code which does not raise clippy warning
    /// ```
    pub {name_upper},
    {category},
    \"default lint description\"
}}",
        name_upper = name_upper,
        category = category,
    ));

    result.push_str(&if enable_msrv {
        format!(
            "
pub struct {name_camel} {{
    msrv: Option<RustcVersion>,
}}

impl {name_camel} {{
    #[must_use]
    pub fn new(msrv: Option<RustcVersion>) -> Self {{
        Self {{ msrv }}
    }}
}}

impl_lint_pass!({name_camel} => [{name_upper}]);

impl {pass_type}{pass_lifetimes} for {name_camel} {{
    extract_msrv_attr!({context_import});
}}

// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
//       e.g. store.register_{pass_name}_pass(move || Box::new({module_name}::{name_camel}::new(msrv)));
// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
// TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
",
            pass_type = pass_type,
            pass_lifetimes = pass_lifetimes,
            pass_name = pass_name,
            name_upper = name_upper,
            name_camel = name_camel,
            module_name = lint_name,
            context_import = context_import,
        )
    } else {
        format!(
            "
declare_lint_pass!({name_camel} => [{name_upper}]);

impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
//
// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
//       e.g. store.register_{pass_name}_pass(|| Box::new({module_name}::{name_camel}));
",
            pass_type = pass_type,
            pass_lifetimes = pass_lifetimes,
            pass_name = pass_name,
            name_upper = name_upper,
            name_camel = name_camel,
            module_name = lint_name,
        )
    });

    result
}

#[test]
fn test_camel_case() {
    let s = "a_lint";
    let s2 = to_camel_case(s);
    assert_eq!(s2, "ALint");

    let name = "a_really_long_new_lint";
    let name2 = to_camel_case(name);
    assert_eq!(name2, "AReallyLongNewLint");

    let name3 = "lint__name";
    let name4 = to_camel_case(name3);
    assert_eq!(name4, "LintName");
}
<commit_msg>Fix `clippy::too-many-arguments` violation<commit_after>use crate::clippy_project_root;
use std::fs::{self, OpenOptions};
use std::io::prelude::*;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};

struct LintData<'a> {
    pass: &'a str,
    name: &'a str,
    category: &'a str,
    project_root: PathBuf,
}

trait Context {
    fn context<C: AsRef<str>>(self, text: C) -> Self;
}

impl<T> Context for io::Result<T> {
    fn context<C: AsRef<str>>(self, text: C) -> Self {
        match self {
            Ok(t) => Ok(t),
            Err(e) => {
                let message = format!("{}: {}", text.as_ref(), e);
                Err(io::Error::new(ErrorKind::Other, message))
            },
        }
    }
}

/// Creates the files required to implement and test a new lint and runs `update_lints`.
///
/// # Errors
///
/// This function errors out if the files couldn't be created or written to.
pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> {
    let lint = LintData {
        pass: pass.expect("`pass` argument is validated by clap"),
        name: lint_name.expect("`name` argument is validated by clap"),
        category: category.expect("`category` argument is validated by clap"),
        project_root: clippy_project_root(),
    };

    create_lint(&lint, msrv).context("Unable to create lint implementation")?;
    create_test(&lint).context("Unable to create a test for the new lint")
}

fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
    let lint_contents = get_lint_file_contents(lint, enable_msrv);

    let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
    write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
}

fn create_test(lint: &LintData<'_>) -> io::Result<()> {
    fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
        let mut path = location.into().join(case);
        fs::create_dir(&path)?;
        write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;

        path.push("src");
        fs::create_dir(&path)?;
        let header = format!("// compile-flags: --crate-name={}", lint_name);
        write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;

        Ok(())
    }

    if lint.category == "cargo" {
        let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
        let test_dir = lint.project_root.join(relative_test_dir);
        fs::create_dir(&test_dir)?;

        create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
        create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
    } else {
        let test_path = format!("tests/ui/{}.rs", lint.name);
        let test_contents = get_test_file_contents(lint.name, None);
        write_file(lint.project_root.join(test_path), test_contents)
    }
}

fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
    fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
        OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(path)?
            .write_all(contents)
    }

    inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
}

fn to_camel_case(name: &str) -> String {
    name.split('_')
        .map(|s| {
            if s.is_empty() {
                String::from("")
            } else {
                [&s[0..1].to_uppercase(), &s[1..]].concat()
            }
        })
        .collect()
}

fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
    let mut contents = format!(
        "#![warn(clippy::{})]

fn main() {{
    // test code goes here
}}
",
        lint_name
    );

    if let Some(header) = header_commands {
        contents = format!("{}\n{}", header, contents);
    }

    contents
}

fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
    format!(
        r#"
# {}

[package]
name = "{}"
version = "0.1.0"
publish = false

[workspace]
"#,
        hint, lint_name
    )
}

fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
    let mut result = String::new();

    let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
        "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
        "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
        _ => {
            unreachable!("`pass_type` should only ever be `early` or `late`!");
        },
    };

    let lint_name = lint.name;
    let pass_name = lint.pass;
    let category = lint.category;
    let name_camel = to_camel_case(lint.name);
    let name_upper = lint_name.to_uppercase();

    result.push_str(&if enable_msrv {
        format!(
            "use clippy_utils::msrvs;
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
use rustc_semver::RustcVersion;
use rustc_session::{{declare_tool_lint, impl_lint_pass}};

",
            pass_type = pass_type,
            pass_import = pass_import,
            context_import = context_import,
        )
    } else {
        format!(
            "{pass_import}
use rustc_lint::{{{context_import}, {pass_type}}};
use rustc_session::{{declare_lint_pass, declare_tool_lint}};

",
            pass_import = pass_import,
            pass_type = pass_type,
            context_import = context_import
        )
    });

    result.push_str(&format!(
        "declare_clippy_lint! {{
    /// ### What it does
    ///
    /// ### Why is this bad?
    ///
    /// ### Example
    /// ```rust
    /// // example code where clippy issues a warning
    /// ```
    /// Use instead:
    /// ```rust
    /// // example code which does not raise clippy warning
    /// ```
    pub {name_upper},
    {category},
    \"default lint description\"
}}",
        name_upper = name_upper,
        category = category,
    ));

    result.push_str(&if enable_msrv {
        format!(
            "
pub struct {name_camel} {{
    msrv: Option<RustcVersion>,
}}

impl {name_camel} {{
    #[must_use]
    pub fn new(msrv: Option<RustcVersion>) -> Self {{
        Self {{ msrv }}
    }}
}}

impl_lint_pass!({name_camel} => [{name_upper}]);

impl {pass_type}{pass_lifetimes} for {name_camel} {{
    extract_msrv_attr!({context_import});
}}

// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
//       e.g. store.register_{pass_name}_pass(move || Box::new({module_name}::{name_camel}::new(msrv)));
// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
// TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
",
            pass_type = pass_type,
            pass_lifetimes = pass_lifetimes,
            pass_name = pass_name,
            name_upper = name_upper,
            name_camel = name_camel,
            module_name = lint_name,
            context_import = context_import,
        )
    } else {
        format!(
            "
declare_lint_pass!({name_camel} => [{name_upper}]);

impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
//
// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
//       e.g. store.register_{pass_name}_pass(|| Box::new({module_name}::{name_camel}));
",
            pass_type = pass_type,
            pass_lifetimes = pass_lifetimes,
            pass_name = pass_name,
            name_upper = name_upper,
            name_camel = name_camel,
            module_name = lint_name,
        )
    });

    result
}

#[test]
fn test_camel_case() {
    let s = "a_lint";
    let s2 = to_camel_case(s);
    assert_eq!(s2, "ALint");

    let name = "a_really_long_new_lint";
    let name2 = to_camel_case(name);
    assert_eq!(name2, "AReallyLongNewLint");

    let name3 = "lint__name";
    let name4 = to_camel_case(name3);
    assert_eq!(name4, "LintName");
}
<|endoftext|>


<commit_before>//  Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#![deny(
    clippy::unwrap_used,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unstable_features,
    unused_import_braces
)]

mod anti_spam;
mod warp_;

pub use duniter_conf::gva_conf::GvaConf;

use async_graphql::http::GraphQLPlaygroundConfig;
use dubp::common::prelude::*;
use dubp::documents::transaction::TransactionDocumentV10;
use dubp::{block::DubpBlockV10, crypto::hashs::Hash};
use dubp::{
    common::crypto::keys::{ed25519::PublicKey, KeyPair as _},
    crypto::keys::ed25519::Ed25519KeyPair,
};
use duniter_conf::DuniterMode;
use duniter_dbs::databases::txs_mp_v2::TxsMpV2DbReadable;
use duniter_dbs::prelude::*;
use duniter_dbs::{kv_typed::prelude::*, FileBackend};
use duniter_gva_db::*;
use duniter_gva_gql::{GvaSchema, QueryContext};
use duniter_gva_indexer::{get_gva_db_ro, get_gva_db_rw};
use duniter_mempools::Mempools;
use futures::{StreamExt, TryStreamExt};
use std::{convert::Infallible, path::Path};
use warp::{http::Response as HttpResponse, Filter as _, Rejection};

#[derive(Debug)]
pub struct GvaModule {
    conf: Option<GvaConf>,
    currency: String,
    dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
    gva_db_ro: &'static GvaV1DbRo<FileBackend>,
    mempools: Mempools,
    mode: DuniterMode,
    self_keypair: Ed25519KeyPair,
    software_version: &'static str,
}

#[async_trait::async_trait]
impl duniter_module::DuniterModule for GvaModule {
    const INDEX_BLOCKS: bool = true;

    fn apply_block(
        block: &DubpBlockV10,
        _conf: &duniter_conf::DuniterConf,
        profile_path_opt: Option<&Path>,
    ) -> KvResult<()> {
        let gva_db = get_gva_db_rw(profile_path_opt);
        duniter_gva_indexer::apply_block(&block, gva_db)
    }
    fn revert_block(
        block: &DubpBlockV10,
        _conf: &duniter_conf::DuniterConf,
        profile_path_opt: Option<&Path>,
    ) -> KvResult<()> {
        let gva_db = get_gva_db_rw(profile_path_opt);
        duniter_gva_indexer::revert_block(&block, gva_db)
    }
    fn init(
        conf: &duniter_conf::DuniterConf,
        currency: &str,
        dbs_pool: &fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
        mempools: Mempools,
        mode: duniter_conf::DuniterMode,
        profile_path_opt: Option<&Path>,
        software_version: &'static str,
    ) -> anyhow::Result<(Self, Vec<duniter_module::Endpoint>)> {
        let mut endpoints = Vec::new();
        if let Some(conf) = conf.gva.clone() {
            let remote_port = conf.get_remote_port();
            endpoints.push(format!(
                "GVA {}{} {} {}",
                if remote_port == 443 || conf.get_remote_tls() {
                    "S "
                } else {
                    ""
                },
                conf.get_remote_host(),
                remote_port,
                conf.get_remote_path(),
            ));
            endpoints.push(format!(
                "GVASUB {}{} {} {}",
                if remote_port == 443 || conf.get_remote_tls() {
                    "S "
                } else {
                    ""
                },
                conf.get_remote_host(),
                remote_port,
                conf.get_remote_subscriptions_path(),
            ));
        };
        Ok((
            GvaModule {
                conf: conf.gva.to_owned(),
                currency: currency.to_owned(),
                dbs_pool: dbs_pool.to_owned(),
                gva_db_ro: get_gva_db_ro(profile_path_opt),
                mempools,
                mode,
                self_keypair: conf.self_key_pair.clone(),
                software_version,
            },
            endpoints,
        ))
    }

    async fn start(self) -> anyhow::Result<()> {
        // Do not start GVA server on js tests
        if std::env::var_os("DUNITER_JS_TESTS") != Some("yes".into()) {
            let GvaModule {
                conf,
                currency,
                dbs_pool,
                gva_db_ro,
                mempools,
                mode,
                self_keypair,
                software_version,
            } = self;

            if let DuniterMode::Start = mode {
                if let Some(conf) = conf {
                    GvaModule::start_inner(
                        conf,
                        currency,
                        dbs_pool,
                        gva_db_ro,
                        mempools,
                        self_keypair,
                        software_version,
                    )
                    .await
                }
            }
        }
        Ok(())
    }
    // Needed for BMA only, will be removed when the migration is complete.
    fn get_transactions_history_for_bma(
        dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
        profile_path_opt: Option<&Path>,
        pubkey: PublicKey,
    ) -> KvResult<Option<duniter_module::TxsHistoryForBma>> {
        let gva_db = get_gva_db_ro(profile_path_opt);
        let duniter_gva_dbs_reader::txs_history::TxsHistory {
            sent,
            received,
            sending,
            pending,
        } = dbs_pool
            .execute(move |dbs| {
                duniter_gva_dbs_reader::txs_history::get_transactions_history_for_bma(
                    gva_db,
                    &dbs.txs_mp_db,
                    pubkey,
                )
            })
            .expect("dbs pool disconnected")?;
        Ok(Some(duniter_module::TxsHistoryForBma {
            sent: sent
                .into_iter()
                .map(
                    |GvaTxDbV1 {
                         tx,
                         written_block,
                         written_time,
                     }| (tx, written_block, written_time),
                )
                .collect(),
            received: received
                .into_iter()
                .map(
                    |GvaTxDbV1 {
                         tx,
                         written_block,
                         written_time,
                     }| (tx, written_block, written_time),
                )
                .collect(),
            sending,
            pending,
        }))
    }
    // Needed for BMA only, will be removed when the migration is complete.
    fn get_tx_by_hash(
        dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
        hash: Hash,
        profile_path_opt: Option<&Path>,
    ) -> KvResult<Option<(TransactionDocumentV10, Option<BlockNumber>)>> {
        let gva_db = get_gva_db_ro(profile_path_opt);
        dbs_pool
            .execute(move |dbs| {
                if let Some(tx) = dbs.txs_mp_db.txs().get(&duniter_dbs::HashKeyV2(hash))? {
                    Ok(Some((tx.0, None)))
                } else if let Some(tx_db) = gva_db.txs().get(&duniter_dbs::HashKeyV2(hash))? {
                    Ok(Some((tx_db.tx, Some(tx_db.written_block.number))))
                } else {
                    Ok(None)
                }
            })
            .expect("dbs pool disconnected")
    }
}

impl GvaModule {
    async fn start_inner(
        conf: GvaConf,
        currency: String,
        dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
        gva_db_ro: &'static GvaV1DbRo<FileBackend>,
        mempools: Mempools,
        self_keypair: Ed25519KeyPair,
        software_version: &'static str,
    ) {
        log::info!("GvaServer::start: conf={:?}", conf);
        let self_pubkey = self_keypair.public_key();
        duniter_bca::set_bca_executor(
            currency.clone(),
            dbs_pool.clone(),
            duniter_gva_dbs_reader::create_dbs_reader(gva_db_ro),
            self_keypair,
            software_version,
            mempools.txs,
        );
        let gva_schema = duniter_gva_gql::build_schema_with_data(
            duniter_gva_gql::GvaSchemaData {
                dbs_reader: duniter_gva_dbs_reader::create_dbs_reader(gva_db_ro),
                dbs_pool,
                server_meta_data: duniter_gva_gql::ServerMetaData {
                    currency,
                    self_pubkey,
                    software_version,
                },
                txs_mempool: mempools.txs,
            },
            true,
        );

        let graphql_post = warp_::graphql(
            &conf,
            gva_schema.clone(),
            async_graphql::http::MultipartOptions::default(),
        );

        let conf_clone = conf.clone();
        let graphql_playground =
            warp::path::path(conf.get_path())
                .and(warp::get())
                .map(move || {
                    HttpResponse::builder()
                        .header("content-type", "text/html")
                        .body(async_graphql::http::playground_source(
                            GraphQLPlaygroundConfig::new(&format!("/{}", &conf_clone.get_path()))
                                .subscription_endpoint(&format!(
                                    "/{}",
                                    &conf_clone.get_subscriptions_path(),
                                )),
                        ))
                });

        let routes = graphql_playground
            .or(graphql_post)
            .or(warp_::graphql_ws(&conf, gva_schema.clone()))
            .recover(|err: Rejection| async move {
                if let Some(warp_::BadRequest(err)) = err.find() {
                    return Ok::<_, Infallible>(warp::reply::with_status(
                        err.to_string(),
                        http::StatusCode::BAD_REQUEST,
                    ));
                }

                Ok(warp::reply::with_status(
                    "INTERNAL_SERVER_ERROR".to_string(),
                    http::StatusCode::INTERNAL_SERVER_ERROR,
                ))
            });

        log::info!(
            "GVA server listen on http://{}:{}/{}",
            conf.get_ip4(),
            conf.get_port(),
            &conf.get_path()
        );
        if let Some(ip6) = conf.get_ip6() {
            log::info!(
                "GVA server listen on http://{}:{}/{}",
                ip6,
                conf.get_port(),
                &conf.get_path()
            );
            futures::future::join(
                warp::serve(routes.clone()).run((conf.get_ip4(), conf.get_port())),
                warp::serve(routes).run((ip6, conf.get_port())),
            )
            .await;
        } else {
            warp::serve(routes)
                .run((conf.get_ip4(), conf.get_port()))
                .await;
        }
        log::warn!("GVA server stopped");
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use duniter_conf::DuniterConf;
    use duniter_mempools::Mempools;
    use duniter_module::DuniterModule;
    use fast_threadpool::{ThreadPool, ThreadPoolConfig};
    use unwrap::unwrap;

    #[tokio::test]
    #[ignore]
    async fn launch_mem_gva() -> anyhow::Result<()> {
        let dbs = unwrap!(SharedDbs::mem());
        let threadpool = ThreadPool::start(ThreadPoolConfig::default(), dbs);

        GvaModule::init(
            &DuniterConf::default(),
            "",
            &threadpool.into_async_handler(),
            Mempools::default(),
            duniter_conf::DuniterMode::Start,
            None,
            "test",
        )?
        .0
        .start()
        .await?;

        Ok(())
    }
}
<commit_msg>[doc] gva: ad some comments<commit_after>//  Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#![deny(
    clippy::unwrap_used,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unstable_features,
    unused_import_braces
)]

mod anti_spam;
mod warp_;

pub use duniter_conf::gva_conf::GvaConf;

use async_graphql::http::GraphQLPlaygroundConfig;
use dubp::common::prelude::*;
use dubp::documents::transaction::TransactionDocumentV10;
use dubp::{block::DubpBlockV10, crypto::hashs::Hash};
use dubp::{
    common::crypto::keys::{ed25519::PublicKey, KeyPair as _},
    crypto::keys::ed25519::Ed25519KeyPair,
};
use duniter_conf::DuniterMode;
use duniter_dbs::databases::txs_mp_v2::TxsMpV2DbReadable;
use duniter_dbs::prelude::*;
use duniter_dbs::{kv_typed::prelude::*, FileBackend};
use duniter_gva_db::*;
use duniter_gva_gql::{GvaSchema, QueryContext};
use duniter_gva_indexer::{get_gva_db_ro, get_gva_db_rw};
use duniter_mempools::Mempools;
use futures::{StreamExt, TryStreamExt};
use std::{convert::Infallible, path::Path};
use warp::{http::Response as HttpResponse, Filter as _, Rejection};

#[derive(Debug)]
pub struct GvaModule {
    conf: Option<GvaConf>,
    currency: String,
    dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
    gva_db_ro: &'static GvaV1DbRo<FileBackend>,
    mempools: Mempools,
    mode: DuniterMode,
    self_keypair: Ed25519KeyPair,
    software_version: &'static str,
}

#[async_trait::async_trait]
impl duniter_module::DuniterModule for GvaModule {
    const INDEX_BLOCKS: bool = true;

    fn apply_block(
        block: &DubpBlockV10,
        _conf: &duniter_conf::DuniterConf,
        profile_path_opt: Option<&Path>,
    ) -> KvResult<()> {
        let gva_db = get_gva_db_rw(profile_path_opt);
        duniter_gva_indexer::apply_block(&block, gva_db)
    }
    fn revert_block(
        block: &DubpBlockV10,
        _conf: &duniter_conf::DuniterConf,
        profile_path_opt: Option<&Path>,
    ) -> KvResult<()> {
        let gva_db = get_gva_db_rw(profile_path_opt);
        duniter_gva_indexer::revert_block(&block, gva_db)
    }
    fn init(
        conf: &duniter_conf::DuniterConf,
        currency: &str,
        dbs_pool: &fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
        mempools: Mempools,
        mode: duniter_conf::DuniterMode,
        profile_path_opt: Option<&Path>,
        software_version: &'static str,
    ) -> anyhow::Result<(Self, Vec<duniter_module::Endpoint>)> {
        let mut endpoints = Vec::new();
        if let Some(conf) = conf.gva.clone() {
            let remote_port = conf.get_remote_port();
            endpoints.push(format!(
                "GVA {}{} {} {}",
                if remote_port == 443 || conf.get_remote_tls() {
                    "S "
                } else {
                    ""
                },
                conf.get_remote_host(),
                remote_port,
                conf.get_remote_path(),
            ));
            endpoints.push(format!(
                "GVASUB {}{} {} {}",
                if remote_port == 443 || conf.get_remote_tls() {
                    "S "
                } else {
                    ""
                },
                conf.get_remote_host(),
                remote_port,
                conf.get_remote_subscriptions_path(),
            ));
        };
        Ok((
            GvaModule {
                conf: conf.gva.to_owned(),
                currency: currency.to_owned(),
                dbs_pool: dbs_pool.to_owned(),
                gva_db_ro: get_gva_db_ro(profile_path_opt),
                mempools,
                mode,
                self_keypair: conf.self_key_pair.clone(),
                software_version,
            },
            endpoints,
        ))
    }

    async fn start(self) -> anyhow::Result<()> {
        // Do not start GVA server on js tests
        if std::env::var_os("DUNITER_JS_TESTS") != Some("yes".into()) {
            let GvaModule {
                conf,
                currency,
                dbs_pool,
                gva_db_ro,
                mempools,
                mode,
                self_keypair,
                software_version,
            } = self;

            if let DuniterMode::Start = mode {
                if let Some(conf) = conf {
                    GvaModule::start_inner(
                        conf,
                        currency,
                        dbs_pool,
                        gva_db_ro,
                        mempools,
                        self_keypair,
                        software_version,
                    )
                    .await
                }
            }
        }
        Ok(())
    }
    // Needed for BMA only, will be removed when the migration is complete.
    fn get_transactions_history_for_bma(
        dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
        profile_path_opt: Option<&Path>,
        pubkey: PublicKey,
    ) -> KvResult<Option<duniter_module::TxsHistoryForBma>> {
        let gva_db = get_gva_db_ro(profile_path_opt);
        let duniter_gva_dbs_reader::txs_history::TxsHistory {
            sent,
            received,
            sending,
            pending,
        } = dbs_pool
            .execute(move |dbs| {
                duniter_gva_dbs_reader::txs_history::get_transactions_history_for_bma(
                    gva_db,
                    &dbs.txs_mp_db,
                    pubkey,
                )
            })
            .expect("dbs pool disconnected")?;
        Ok(Some(duniter_module::TxsHistoryForBma {
            sent: sent
                .into_iter()
                .map(
                    |GvaTxDbV1 {
                         tx,
                         written_block,
                         written_time,
                     }| (tx, written_block, written_time),
                )
                .collect(),
            received: received
                .into_iter()
                .map(
                    |GvaTxDbV1 {
                         tx,
                         written_block,
                         written_time,
                     }| (tx, written_block, written_time),
                )
                .collect(),
            sending,
            pending,
        }))
    }
    // Needed for BMA only, will be removed when the migration is complete.
    fn get_tx_by_hash(
        dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
        hash: Hash,
        profile_path_opt: Option<&Path>,
    ) -> KvResult<Option<(TransactionDocumentV10, Option<BlockNumber>)>> {
        let gva_db = get_gva_db_ro(profile_path_opt);
        dbs_pool
            .execute(move |dbs| {
                if let Some(tx) = dbs.txs_mp_db.txs().get(&duniter_dbs::HashKeyV2(hash))? {
                    Ok(Some((tx.0, None)))
                } else if let Some(tx_db) = gva_db.txs().get(&duniter_dbs::HashKeyV2(hash))? {
                    Ok(Some((tx_db.tx, Some(tx_db.written_block.number))))
                } else {
                    Ok(None)
                }
            })
            .expect("dbs pool disconnected")
    }
}

impl GvaModule {
    async fn start_inner(
        conf: GvaConf,
        currency: String,
        dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
        gva_db_ro: &'static GvaV1DbRo<FileBackend>,
        mempools: Mempools,
        self_keypair: Ed25519KeyPair,
        software_version: &'static str,
    ) {
        log::info!("GvaServer::start: conf={:?}", conf);

        // Create BcaExecutor and GvaSchema
        let self_pubkey = self_keypair.public_key();
        duniter_bca::set_bca_executor(
            currency.clone(),
            dbs_pool.clone(),
            duniter_gva_dbs_reader::create_dbs_reader(gva_db_ro),
            self_keypair,
            software_version,
            mempools.txs,
        );
        let gva_schema = duniter_gva_gql::build_schema_with_data(
            duniter_gva_gql::GvaSchemaData {
                dbs_reader: duniter_gva_dbs_reader::create_dbs_reader(gva_db_ro),
                dbs_pool,
                server_meta_data: duniter_gva_gql::ServerMetaData {
                    currency,
                    self_pubkey,
                    software_version,
                },
                txs_mempool: mempools.txs,
            },
            true,
        );

        // Create warp server routes
        let graphql_post = warp_::graphql(
            &conf,
            gva_schema.clone(),
            async_graphql::http::MultipartOptions::default(),
        );

        let conf_clone = conf.clone();
        let graphql_playground =
            warp::path::path(conf.get_path())
                .and(warp::get())
                .map(move || {
                    HttpResponse::builder()
                        .header("content-type", "text/html")
                        .body(async_graphql::http::playground_source(
                            GraphQLPlaygroundConfig::new(&format!("/{}", &conf_clone.get_path()))
                                .subscription_endpoint(&format!(
                                    "/{}",
                                    &conf_clone.get_subscriptions_path(),
                                )),
                        ))
                });

        let routes = graphql_playground
            .or(graphql_post)
            .or(warp_::graphql_ws(&conf, gva_schema.clone()))
            .recover(|err: Rejection| async move {
                if let Some(warp_::BadRequest(err)) = err.find() {
                    return Ok::<_, Infallible>(warp::reply::with_status(
                        err.to_string(),
                        http::StatusCode::BAD_REQUEST,
                    ));
                }

                Ok(warp::reply::with_status(
                    "INTERNAL_SERVER_ERROR".to_string(),
                    http::StatusCode::INTERNAL_SERVER_ERROR,
                ))
            });

        // Start warp server
        log::info!(
            "GVA server listen on http://{}:{}/{}",
            conf.get_ip4(),
            conf.get_port(),
            &conf.get_path()
        );
        if let Some(ip6) = conf.get_ip6() {
            log::info!(
                "GVA server listen on http://{}:{}/{}",
                ip6,
                conf.get_port(),
                &conf.get_path()
            );
            futures::future::join(
                warp::serve(routes.clone()).run((conf.get_ip4(), conf.get_port())),
                warp::serve(routes).run((ip6, conf.get_port())),
            )
            .await;
        } else {
            warp::serve(routes)
                .run((conf.get_ip4(), conf.get_port()))
                .await;
        }
        log::warn!("GVA server stopped");
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use duniter_conf::DuniterConf;
    use duniter_mempools::Mempools;
    use duniter_module::DuniterModule;
    use fast_threadpool::{ThreadPool, ThreadPoolConfig};
    use unwrap::unwrap;

    #[tokio::test]
    #[ignore]
    async fn launch_mem_gva() -> anyhow::Result<()> {
        let dbs = unwrap!(SharedDbs::mem());
        let threadpool = ThreadPool::start(ThreadPoolConfig::default(), dbs);

        GvaModule::init(
            &DuniterConf::default(),
            "",
            &threadpool.into_async_handler(),
            Mempools::default(),
            duniter_conf::DuniterMode::Start,
            None,
            "test",
        )?
        .0
        .start()
        .await?;

        Ok(())
    }
}
<|endoftext|>


<commit_before>use std::{fs, path::Path};

use crate::{files::File, sources::Elife, utils::http::download};
use eyre::{bail, Result};
use futures::future::join_all;
use minidom::Element;

/// Import an eLife article into a project
pub async fn import(
    project: &Path,
    source: &Elife,
    destination: Option<String>,
) -> Result<Vec<File>> {
    let url = format!("https://elifesciences.org/articles/{}.xml", source.article);
    let response = reqwest::get(&url).await?;
    let response = match response.error_for_status() {
        Ok(response) => response,
        Err(error) => {
            if let Some(status) = error.status() {
                if status == 404 {
                    bail!("Not found '{}', is the article number correct?", url)
                }
            }
            bail!(error)
        }
    };

    let xml = response.text().await?;
    // Parsing requires that the root element have a namespace, so insert an
    // arbitrary one.
    let xml = xml.replace("<article ", "<article xmlns=\"article\"");
    let mut root: Element = xml.parse()?;

    let destination =
        project.join(destination.unwrap_or_else(|| format!("elife-{}.xml", source.article)));
    let parent_dir = destination.parent().expect("Should always have a parent");
    let file_name = destination
        .file_name()
        .expect("Should always have a file name")
        .to_string_lossy()
        .to_string();
    let media_dir = format!("{}.media", file_name);

    // Walk the tree, collecting the URLs of images to download and rewriting
    // the `href` attributes of `graphic` elements to local JPG files (instead of TIFs).
    fn walk(article: u32, media_dir: &str, elem: &mut Element) -> Vec<(String, String)> {
        let mut images = Vec::new();
        for child in elem.children_mut() {
            if child.name() == "graphic" {
                let mut rewritten = false;
                for (name, value) in child.attrs_mut() {
                    if name == "xlink:href" && value.starts_with("elife") {
                        let href = if value.ends_with(".tif") {
                            value.clone()
                        } else {
                            [value, ".tif"].concat()
                        };
                        let url = format!(
                            "https://iiif.elifesciences.org/lax:{article}%2F{href}/full/full/0/default.jpg",
                            article = article,
                            href = href
                        );
                        let relative_path = [
                            media_dir,
                            "/",
                            &href
                                .replace(&format!("elife-{}-", article), "")
                                .replace(".tif", ".jpg"),
                        ]
                        .concat();

                        *value = relative_path.clone();
                        images.push((url, relative_path));
                        rewritten = true;
                        break;
                    }
                }
                if rewritten {
                    child.set_attr("mime-subtype", "jpeg")
                }
            }
            images.append(&mut walk(article, media_dir, child));
        }
        images
    }
    let images = walk(source.article, &media_dir, &mut root);

    // Write the re-written XML
    let mut writer = fs::File::create(parent_dir.join(file_name))?;
    root.write_to(&mut writer)?;

    // If there are any images then ensure the media directory is created (and empty)
    if !images.is_empty() {
        let dir = parent_dir.join(media_dir);
        if dir.exists() {
            fs::remove_dir_all(&dir)?
        };
        fs::create_dir_all(&dir)?;
    }

    // Download images concurrently
    let results = join_all(
        images
            .iter()
            .map(|(url, path)| download(url, parent_dir.join(path))),
    )
    .await;
    results.iter().for_each(|result| {
        if let Err(error) = result {
            tracing::warn!("While downloading image {}: {}", url, error.to_string())
        };
    });

    Ok(vec![])
}
<commit_msg>fix(ELife Source): Add a user agent header to avoid 403<commit_after>use std::{fs, path::Path};

use crate::{files::File, sources::Elife, utils::http::download};
use eyre::{bail, Result};
use futures::future::join_all;
use minidom::Element;

/// Import an eLife article into a project
pub async fn import(
    project: &Path,
    source: &Elife,
    destination: Option<String>,
) -> Result<Vec<File>> {
    let url = format!("https://elifesciences.org/articles/{}.xml", source.article);
    let client = reqwest::Client::new();
    let response = client
        .get(&url)
        .header(reqwest::header::USER_AGENT, "Stencila")
        .send()
        .await?;
    let response = match response.error_for_status() {
        Ok(response) => response,
        Err(error) => {
            if let Some(status) = error.status() {
                if status == 404 {
                    bail!("Not found '{}', is the article number correct?", url)
                }
            }
            bail!(error)
        }
    };

    let xml = response.text().await?;
    // Parsing requires that the root element have a namespace, so insert an
    // arbitrary one.
    let xml = xml.replace("<article ", "<article xmlns=\"article\"");
    let mut root: Element = xml.parse()?;

    let destination =
        project.join(destination.unwrap_or_else(|| format!("elife-{}.xml", source.article)));
    let parent_dir = destination.parent().expect("Should always have a parent");
    let file_name = destination
        .file_name()
        .expect("Should always have a file name")
        .to_string_lossy()
        .to_string();
    let media_dir = format!("{}.media", file_name);

    // Walk the tree, collecting the URLs of images to download and rewriting
    // the `href` attributes of `graphic` elements to local JPG files (instead of TIFs).
    fn walk(article: u32, media_dir: &str, elem: &mut Element) -> Vec<(String, String)> {
        let mut images = Vec::new();
        for child in elem.children_mut() {
            if child.name() == "graphic" {
                let mut rewritten = false;
                for (name, value) in child.attrs_mut() {
                    if name == "xlink:href" && value.starts_with("elife") {
                        let href = if value.ends_with(".tif") {
                            value.clone()
                        } else {
                            [value, ".tif"].concat()
                        };
                        let url = format!(
                            "https://iiif.elifesciences.org/lax:{article}%2F{href}/full/full/0/default.jpg",
                            article = article,
                            href = href
                        );
                        let relative_path = [
                            media_dir,
                            "/",
                            &href
                                .replace(&format!("elife-{}-", article), "")
                                .replace(".tif", ".jpg"),
                        ]
                        .concat();

                        *value = relative_path.clone();
                        images.push((url, relative_path));
                        rewritten = true;
                        break;
                    }
                }
                if rewritten {
                    child.set_attr("mime-subtype", "jpeg")
                }
            }
            images.append(&mut walk(article, media_dir, child));
        }
        images
    }
    let images = walk(source.article, &media_dir, &mut root);

    // Write the re-written XML
    let mut writer = fs::File::create(parent_dir.join(file_name))?;
    root.write_to(&mut writer)?;

    // If there are any images then ensure the media directory is created (and empty)
    if !images.is_empty() {
        let dir = parent_dir.join(media_dir);
        if dir.exists() {
            fs::remove_dir_all(&dir)?
        };
        fs::create_dir_all(&dir)?;
    }

    // Download images concurrently
    let results = join_all(
        images
            .iter()
            .map(|(url, path)| download(url, parent_dir.join(path))),
    )
    .await;
    results.iter().for_each(|result| {
        if let Err(error) = result {
            tracing::warn!("While downloading image {}: {}", url, error.to_string())
        };
    });

    Ok(vec![])
}
<|endoftext|>


<commit_before>use std::env;
use std::fs;
use std::io;
use std::process;
use std::process::{Command, Stdio};
use sys_info;
use kuchiki;

use clap::ArgMatches;
use tempdir::TempDir;
use hyper::Client;
use config::Config;
use extractor;
use downloader;
use profile;

pub fn init() {
    let config = Config::new();
    if config.install_dir.exists() || config.shim_dir.exists() {
        println!("Already initalized. Reinitializing....");
    }
    fs::create_dir_all(&config.install_dir).expect("Could not create installation directory.");
    fs::create_dir_all(&config.shim_dir).expect("Could not create shims directory.");
    fs::create_dir_all(&config.versions_dir).expect("Could not create versions directory.");
    if !env::home_dir()
        .unwrap()
        .join(".profile")
        .exists() {
        println!(r#"Write the following thing:

. $HOME\.groonga\shims\bin\source-groonga.sh
"#)
    }
}

pub fn install(m: &ArgMatches) {
    const BASE_URL: &'static str = "http://packages.groonga.org/source/groonga";
    let config = Config::from_matches(m);
    println!("Obtaining Groonga version: {}",
             config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}", config.version.unwrap());
    let groonga_source = format!("{}.zip", groonga_dir.clone());
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Already installed. Ready to use it.");
        return ();
    }

    let download_dir = TempDir::new("grnenv-rs")
        .expect("Could not create temp dir.")
        .into_path();

    let client = Client::new();
    let filename = downloader::file_download(&client,
                                             &*format!("{}/{}", BASE_URL, groonga_source),
                                             download_dir.clone(),
                                             "groonga.zip")
        .expect("Failed to download");
    extractor::extract_zip(&filename, &download_dir);
    assert!(env::set_current_dir(&download_dir.join(groonga_dir.clone())).is_ok());

    // TODO: Should specify on Linux?
    match inner_autoreconf() {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not execute autoreconf -v.");
            process::exit(1);
        }
    }

    match inner_configure(&config, groonga_dir.clone()) {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not configure.");
            process::exit(1);
        }
    }

    match inner_build() {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not build.");
            process::exit(1);
        }
    }

    match inner_install() {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not install.");
            process::exit(1);
        }
    }

    #[cfg(target_os = "macos")]
    fn inner_autoreconf() -> Result<process::ExitStatus, io::Error> {
        let mut cmd = Command::new("autoreconf")
            .args(&["-v"])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }

    #[cfg(not(target_os = "macos"))]
    fn inner_autoreconf() -> Result<u32, io::Error> {
        Ok(0)
    }

    // mainly used for macOS.
    fn openssl_pkg_config_path() -> String {
        let key = "OPENSSL_PKG_CONFIG_PATH";
        env::var(key).unwrap_or("/usr/local/opt/openssl/lib/pkgconfig".to_string())
    }

    fn inner_configure(config: &Config,
                       groonga_dir: String)
                       -> Result<process::ExitStatus, io::Error> {
        println!("{}",
                 config.versions_dir.join(groonga_dir.clone()).display());
        let mut cmd = Command::new("./configure")
            .args(&[&*format!("--prefix={}",
                              config.versions_dir.join(groonga_dir.clone()).display()),
                    &*format!("PKG_CONFIG_PATH={}", openssl_pkg_config_path())])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }

    fn inner_build() -> Result<process::ExitStatus, io::Error> {
        let mut cmd = Command::new("make")
            .args(&["-j", &*format!("{}", sys_info::cpu_num().unwrap_or(2))])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }

    fn inner_install() -> Result<process::ExitStatus, io::Error> {
        let mut cmd = Command::new("make")
            .args(&["install"])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }
}

pub fn switch(m: &ArgMatches) {
    let config = Config::from_matches(m);
    println!("Using Groonga version: {}", config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}", config.version.unwrap());
    match config.version {
        Some("system") => {
            profile::unix::remove_grnenv_profile(&config.shim_dir).unwrap();
            return ();
        }
        Some(_) => {
            profile::unix::create_profile_source(&config.shim_dir,
                                                 &groonga_dir,
                                                 &config.versions_dir)
                .expect("Could not create source-groonga.sh")
        }
        None => unreachable!(),
    }
}

pub fn uninstall(m: &ArgMatches) {
    let config = Config::from_matches(m);
    let groonga_dir = format!("groonga-{}", config.version.unwrap());
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Removing {}....", groonga_dir.clone());
        fs::remove_dir_all(&config.versions_dir.join(groonga_dir))
            .expect("Could not remove specified directory.");
    } else {
        println!("{} is not installed!", groonga_dir.clone());
        process::exit(1);
    }
}

pub fn list() {
    use kuchiki::traits::*;

    if let Ok(doc) = kuchiki::parse_html().from_http("http://packages.groonga.org/source/groonga") {
        let docs = doc.select("tr").unwrap_or_else(|e| panic!("failed to find tr elements: {:?}", e)).collect::<Vec<_>>();
        println!("Installable Groonga:");
        for handle in &docs {
            let texts = handle.as_node().descendants().text_nodes().collect::<Vec<_>>();
            if let Some(text) = texts.first() {
                let package = text.as_node().text_contents();
                if package.contains("groonga") && package.contains("zip") && !package.contains("asc") {
                    let display = package.split(".zip").collect::<Vec<_>>();
                    println!("\t{}", display.first().unwrap_or(&""));
                }
            }
        }
    } else {
        println!("{}", "The page couldn't be fetched");
    }
}
<commit_msg>rustfmt<commit_after>use std::env;
use std::fs;
use std::io;
use std::process;
use std::process::{Command, Stdio};
use sys_info;
use kuchiki;

use clap::ArgMatches;
use tempdir::TempDir;
use hyper::Client;
use config::Config;
use extractor;
use downloader;
use profile;

pub fn init() {
    let config = Config::new();
    if config.install_dir.exists() || config.shim_dir.exists() {
        println!("Already initalized. Reinitializing....");
    }
    fs::create_dir_all(&config.install_dir).expect("Could not create installation directory.");
    fs::create_dir_all(&config.shim_dir).expect("Could not create shims directory.");
    fs::create_dir_all(&config.versions_dir).expect("Could not create versions directory.");
    if !env::home_dir()
        .unwrap()
        .join(".profile")
        .exists() {
        println!(r#"Write the following thing:

. $HOME\.groonga\shims\bin\source-groonga.sh
"#)
    }
}

pub fn install(m: &ArgMatches) {
    const BASE_URL: &'static str = "http://packages.groonga.org/source/groonga";
    let config = Config::from_matches(m);
    println!("Obtaining Groonga version: {}",
             config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}", config.version.unwrap());
    let groonga_source = format!("{}.zip", groonga_dir.clone());
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Already installed. Ready to use it.");
        return ();
    }

    let download_dir = TempDir::new("grnenv-rs")
        .expect("Could not create temp dir.")
        .into_path();

    let client = Client::new();
    let filename = downloader::file_download(&client,
                                             &*format!("{}/{}", BASE_URL, groonga_source),
                                             download_dir.clone(),
                                             "groonga.zip")
        .expect("Failed to download");
    extractor::extract_zip(&filename, &download_dir);
    assert!(env::set_current_dir(&download_dir.join(groonga_dir.clone())).is_ok());

    // TODO: Should specify on Linux?
    match inner_autoreconf() {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not execute autoreconf -v.");
            process::exit(1);
        }
    }

    match inner_configure(&config, groonga_dir.clone()) {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not configure.");
            process::exit(1);
        }
    }

    match inner_build() {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not build.");
            process::exit(1);
        }
    }

    match inner_install() {
        Ok(o) => println!("{}", o),
        Err(_) => {
            println!("Could not install.");
            process::exit(1);
        }
    }

    #[cfg(target_os = "macos")]
    fn inner_autoreconf() -> Result<process::ExitStatus, io::Error> {
        let mut cmd = Command::new("autoreconf")
            .args(&["-v"])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }

    #[cfg(not(target_os = "macos"))]
    fn inner_autoreconf() -> Result<u32, io::Error> {
        Ok(0)
    }

    // mainly used for macOS.
    fn openssl_pkg_config_path() -> String {
        let key = "OPENSSL_PKG_CONFIG_PATH";
        env::var(key).unwrap_or("/usr/local/opt/openssl/lib/pkgconfig".to_string())
    }

    fn inner_configure(config: &Config,
                       groonga_dir: String)
                       -> Result<process::ExitStatus, io::Error> {
        println!("{}",
                 config.versions_dir.join(groonga_dir.clone()).display());
        let mut cmd = Command::new("./configure")
            .args(&[&*format!("--prefix={}",
                              config.versions_dir.join(groonga_dir.clone()).display()),
                    &*format!("PKG_CONFIG_PATH={}", openssl_pkg_config_path())])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }

    fn inner_build() -> Result<process::ExitStatus, io::Error> {
        let mut cmd = Command::new("make")
            .args(&["-j", &*format!("{}", sys_info::cpu_num().unwrap_or(2))])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }

    fn inner_install() -> Result<process::ExitStatus, io::Error> {
        let mut cmd = Command::new("make")
            .args(&["install"])
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap_or_else(|e| panic!("failed to execute child: {}", e));
        let status = cmd.wait();
        status
    }
}

pub fn switch(m: &ArgMatches) {
    let config = Config::from_matches(m);
    println!("Using Groonga version: {}", config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}", config.version.unwrap());
    match config.version {
        Some("system") => {
            profile::unix::remove_grnenv_profile(&config.shim_dir).unwrap();
            return ();
        }
        Some(_) => {
            profile::unix::create_profile_source(&config.shim_dir,
                                                 &groonga_dir,
                                                 &config.versions_dir)
                .expect("Could not create source-groonga.sh")
        }
        None => unreachable!(),
    }
}

pub fn uninstall(m: &ArgMatches) {
    let config = Config::from_matches(m);
    let groonga_dir = format!("groonga-{}", config.version.unwrap());
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Removing {}....", groonga_dir.clone());
        fs::remove_dir_all(&config.versions_dir.join(groonga_dir))
            .expect("Could not remove specified directory.");
    } else {
        println!("{} is not installed!", groonga_dir.clone());
        process::exit(1);
    }
}

pub fn list() {
    use kuchiki::traits::*;

    if let Ok(doc) = kuchiki::parse_html().from_http("http://packages.groonga.org/source/groonga") {
        let docs = doc.select("tr")
            .unwrap_or_else(|e| panic!("failed to find tr elements: {:?}", e))
            .collect::<Vec<_>>();
        println!("Installable Groonga:");
        for handle in &docs {
            let texts = handle.as_node().descendants().text_nodes().collect::<Vec<_>>();
            if let Some(text) = texts.first() {
                let package = text.as_node().text_contents();
                if package.contains("groonga") && package.contains("zip") &&
                   !package.contains("asc") {
                    let display = package.split(".zip").collect::<Vec<_>>();
                    println!("\t{}", display.first().unwrap_or(&""));
                }
            }
        }
    } else {
        println!("{}", "The page couldn't be fetched");
    }
}
<|endoftext|>


<commit_before>use crate::{model::Msg, types::get_value_from_input_event};
use web_sys::HtmlInputElement;
use yew::prelude::*;

pub struct CommandLine {
    all_entries: Vec<Entry>,
    valid_entries: Vec<Entry>,
    // Among valid (filtered) entries.
    selected_command_index: usize,
    value: String,
}

#[derive(PartialEq, Clone, Properties, Debug)]
pub struct CommandLineProperties {
    pub value: String,
    pub entries: Vec<Entry>,
    pub enabled: bool,
    pub input_node_ref: NodeRef,
    #[prop_or_default]
    pub placeholder: String,
    #[prop_or_default]
    pub oninput: Callback<String>,
    #[prop_or_default]
    pub onselect: Callback<Msg>,
    #[prop_or_default]
    pub onenter: Callback<()>,
    #[prop_or_default]
    pub ondelete: Callback<()>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Entry {
    pub label: String,
    pub description: String,
    pub action: Msg,
    pub valid_classes: Vec<String>,
}

#[derive(Debug)]
pub enum CommandLineMsg {
    Click,
    Key(KeyboardEvent),
    Input(String),
}

impl Component for CommandLine {
    type Message = CommandLineMsg;
    type Properties = CommandLineProperties;

    fn create(ctx: &Context<Self>) -> Self {
        let mut c = Self {
            all_entries: ctx.props().entries.clone(),
            valid_entries: vec![],
            selected_command_index: 0,
            value: ctx.props().value.clone(),
        };
        c.update_valid_entries();
        c
    }

    fn changed(&mut self, ctx: &Context<Self>) -> bool {
        self.all_entries = ctx.props().entries.clone();
        self.value = ctx.props().value.clone();
        self.update_valid_entries();
        true
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        let props = ctx.props();
        let value = &self.value;
        let enabled = props.enabled;
        let valid_entries = &self.valid_entries;
        let selected_entry = valid_entries.get(self.selected_command_index);
        let selected_entry_suffix = selected_entry
            .cloned()
            .map(|v| v.label)
            .unwrap_or_default()
            .strip_prefix(value)
            .map(|v| v.to_string())
            .unwrap_or_default();
        let selected = enabled;
        // TODO: Fuzzy search.
        let entries: Vec<_> = if selected {
            valid_entries
                .iter()
                .enumerate()
                .map(|(i, v)| {
                    let value_string = v.label.clone();
                    let value_suffix = value_string.strip_prefix(value).unwrap_or_default();

                    // let node = v.to_node();
                    let action = v.action.clone();
                    let onselect = props.onselect.clone();
                    let onclick = Callback::from(move |e: MouseEvent| {
                        // Avoid moving the focus away from the input.
                        e.prevent_default();
                        onselect.emit(action.clone());
                    });
                    let mut classes_item = vec!["block", "border"];
                    if i == self.selected_command_index {
                        classes_item.push("selected");
                    }
                    // Avoid re-selecting the node, we want to move to next.
                    html! {

                          { value.clone() }
                          { value_suffix }

                    }
                })
                .collect()
        } else {
            vec![]
        };
        let classes_dropdown = vec!["absolute", "z-10", "bg-white", "w-64"];
        // let id = view::command_input_id(&path);
        let style = if !value.is_empty() {
            format!("width: {}ch;", value.len())
        } else {
            "width: 0.1ch;".to_string()
        };
        let rows = value.split('\n').count();
        // XXX: Chrome inspector CSS color editor.
        let placeholder = if value.is_empty() && !selected && !props.placeholder.is_empty() {
            Some(html! {
                //
{ props.placeholder.clone() }

                { props.placeholder.clone() }
            })
        } else {
            None
        };
        let suffix = if selected {
            selected_entry_suffix
        } else {
            "".to_string()
        };
        let class = vec![
            "inline-flex",
            "w-full",
            "bg-transparent",
            "resize-none",
            "overflow-hidden",
        ];
        // let errors = vec![];
        // if !errors.is_empty() {
        //     class.push("error");
        // }

        // let editing = if model.mode == crate::types::Mode::Edit {
        let dropdown = if enabled && selected {
            html! {

                    { for entries }

            }
        } else {
            html! {}
        };
        let suffix = if enabled && selected {
            html! {
                { suffix }
            }
        } else {
            html! {}
        };
        let mut classes = if enabled && selected {
            match selected_entry {
                Some(entry) => entry.valid_classes.clone().join(" "),
                None => "".to_string(),
            }
        } else {
            "".to_string()
        };
        classes.push_str(" flex");

        let oninput = props.oninput.clone();
        let oninput = ctx.link().callback(move |e: InputEvent| {
            let v = get_value_from_input_event(e);
            oninput.emit(v.clone());
            CommandLineMsg::Input(v)
        });

        let onclick = ctx.link().callback(|_| CommandLineMsg::Click);
        let onfocus = ctx.link().callback(|_| CommandLineMsg::Click);
        let onkeydown = ctx.link().callback(CommandLineMsg::Key);

        html! {

                        <textarea
                            rows={ format!("{}", rows) }
                            ref={ ctx.props().input_node_ref.clone() }
                            //   id={ id }
                            class={ class }
                            type="text"
                            oninput={ oninput }
                            onkeydown={ onkeydown }
                            //   onfocus={ onfocus }
                            value={ value.to_string() }
                            style={ style }
                            //   disabled={ model.mode != crate::types::Mode::Edit }
                            autocomplete="off"
                        >
                        </textarea>
                        { for placeholder }
                        { suffix }

                    { dropdown }

        }
    }

    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            CommandLineMsg::Click => {
                let input_node = ctx.props().input_node_ref.clone();
                input_node
                    .cast::<HtmlInputElement>()
                    .unwrap()
                    .focus()
                    .unwrap();
                true
            }
            CommandLineMsg::Input(value) => {
                self.value = value;
                self.update_valid_entries();
                true
            }
            CommandLineMsg::Key(e) => {
                log::debug!("key: {:?}", e.key());
                let props = ctx.props();
                let entries = &self.valid_entries;
                let selected_command_index = self.selected_command_index;
                if e.shift_key() {
                    return false;
                }
                match e.key().as_ref() {
                    "Backspace" => {
                        if self.value.is_empty() {
                            props.ondelete.emit(());
                        }
                    }
                    "ArrowUp" => {
                        if !entries.is_empty() {
                            self.selected_command_index = if selected_command_index > 0 {
                                selected_command_index - 1
                            } else {
                                entries.len() - 1
                            }
                        }
                    }
                    "ArrowDown" => {
                        if !entries.is_empty() {
                            self.selected_command_index =
                                if selected_command_index < entries.len() - 1 {
                                    selected_command_index + 1
                                } else {
                                    0
                                }
                        }
                    }
                    "Enter" => {
                        e.prevent_default();
                        let selected_entry = entries.get(selected_command_index).cloned();
                        if let Some(selected_entry) = selected_entry {
                            let action = selected_entry.action;
                            props.onselect.emit(action);
                        } else {
                            props.onenter.emit(());
                        }
                    }
                    _ => {}
                };
                true
            }
        }
    }
}

impl CommandLine {
    fn update_valid_entries(&mut self) {
        self.valid_entries = self
            .all_entries
            .clone()
            .into_iter()
            .filter(|v| v.label.starts_with(&self.value))
            .collect();
    }
}
<commit_msg>disable textarea<commit_after>use crate::{model::Msg, types::get_value_from_input_event};
use web_sys::HtmlInputElement;
use yew::prelude::*;

pub struct CommandLine {
    all_entries: Vec<Entry>,
    valid_entries: Vec<Entry>,
    // Among valid (filtered) entries.
    selected_command_index: usize,
    value: String,
}

#[derive(PartialEq, Clone, Properties, Debug)]
pub struct CommandLineProperties {
    pub value: String,
    pub entries: Vec<Entry>,
    pub enabled: bool,
    pub input_node_ref: NodeRef,
    #[prop_or_default]
    pub placeholder: String,
    #[prop_or_default]
    pub oninput: Callback<String>,
    #[prop_or_default]
    pub onselect: Callback<Msg>,
    #[prop_or_default]
    pub onenter: Callback<()>,
    #[prop_or_default]
    pub ondelete: Callback<()>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Entry {
    pub label: String,
    pub description: String,
    pub action: Msg,
    pub valid_classes: Vec<String>,
}

#[derive(Debug)]
pub enum CommandLineMsg {
    Click,
    Key(KeyboardEvent),
    Input(String),
}

impl Component for CommandLine {
    type Message = CommandLineMsg;
    type Properties = CommandLineProperties;

    fn create(ctx: &Context<Self>) -> Self {
        let mut c = Self {
            all_entries: ctx.props().entries.clone(),
            valid_entries: vec![],
            selected_command_index: 0,
            value: ctx.props().value.clone(),
        };
        c.update_valid_entries();
        c
    }

    fn changed(&mut self, ctx: &Context<Self>) -> bool {
        self.all_entries = ctx.props().entries.clone();
        self.value = ctx.props().value.clone();
        self.update_valid_entries();
        true
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        let props = ctx.props();
        let value = &self.value;
        let enabled = props.enabled;
        let valid_entries = &self.valid_entries;
        let selected_entry = valid_entries.get(self.selected_command_index);
        let selected_entry_suffix = selected_entry
            .cloned()
            .map(|v| v.label)
            .unwrap_or_default()
            .strip_prefix(value)
            .map(|v| v.to_string())
            .unwrap_or_default();
        let selected = enabled;
        // TODO: Fuzzy search.
        let entries: Vec<_> = if selected {
            valid_entries
                .iter()
                .enumerate()
                .map(|(i, v)| {
                    let value_string = v.label.clone();
                    let value_suffix = value_string.strip_prefix(value).unwrap_or_default();

                    // let node = v.to_node();
                    let action = v.action.clone();
                    let onselect = props.onselect.clone();
                    let onclick = Callback::from(move |e: MouseEvent| {
                        // Avoid moving the focus away from the input.
                        e.prevent_default();
                        onselect.emit(action.clone());
                    });
                    let mut classes_item = vec!["block", "border"];
                    if i == self.selected_command_index {
                        classes_item.push("selected");
                    }
                    // Avoid re-selecting the node, we want to move to next.
                    html! {

                          { value.clone() }
                          { value_suffix }

                    }
                })
                .collect()
        } else {
            vec![]
        };
        let classes_dropdown = vec!["absolute", "z-10", "bg-white", "w-64"];
        // let id = view::command_input_id(&path);
        let style = if !value.is_empty() {
            format!("width: {}ch;", value.len())
        } else {
            "width: 0.1ch;".to_string()
        };
        let rows = value.split('\n').count();
        // XXX: Chrome inspector CSS color editor.
        let placeholder = if value.is_empty() && !selected && !props.placeholder.is_empty() {
            Some(html! {
                //
{ props.placeholder.clone() }

                { props.placeholder.clone() }
            })
        } else {
            None
        };
        let suffix = if selected {
            selected_entry_suffix
        } else {
            "".to_string()
        };
        let class = vec![
            "inline-flex",
            "w-full",
            "bg-transparent",
            "resize-none",
            "overflow-hidden",
        ];
        // let errors = vec![];
        // if !errors.is_empty() {
        //     class.push("error");
        // }

        // let editing = if model.mode == crate::types::Mode::Edit {
        let dropdown = if enabled && selected {
            html! {

                    { for entries }

            }
        } else {
            html! {}
        };
        let suffix = if enabled && selected {
            html! {
                { suffix }
            }
        } else {
            html! {}
        };
        let mut classes = if enabled && selected {
            match selected_entry {
                Some(entry) => entry.valid_classes.clone().join(" "),
                None => "".to_string(),
            }
        } else {
            "".to_string()
        };
        classes.push_str(" flex");

        let oninput = props.oninput.clone();
        let oninput = ctx.link().callback(move |e: InputEvent| {
            let v = get_value_from_input_event(e);
            oninput.emit(v.clone());
            CommandLineMsg::Input(v)
        });

        let onclick = ctx.link().callback(|_| CommandLineMsg::Click);
        let onfocus = ctx.link().callback(|_| CommandLineMsg::Click);
        let onkeydown = ctx.link().callback(CommandLineMsg::Key);

        html! {

                        <textarea
                            rows={ format!("{}", rows) }
                            ref={ ctx.props().input_node_ref.clone() }
                            //   id={ id }
                            class={ class }
                            type="text"
                            oninput={ oninput }
                            onkeydown={ onkeydown }
                            disabled={ !enabled }
                            //   onfocus={ onfocus }
                            value={ value.to_string() }
                            style={ style }
                            //   disabled={ model.mode != crate::types::Mode::Edit }
                            autocomplete="off"
                        >
                        </textarea>
                        { for placeholder }
                        { suffix }

                    { dropdown }

        }
    }

    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            CommandLineMsg::Click => {
                let input_node = ctx.props().input_node_ref.clone();
                input_node
                    .cast::<HtmlInputElement>()
                    .unwrap()
                    .focus()
                    .unwrap();
                true
            }
            CommandLineMsg::Input(value) => {
                self.value = value;
                self.update_valid_entries();
                true
            }
            CommandLineMsg::Key(e) => {
                log::debug!("key: {:?}", e.key());
                let props = ctx.props();
                let entries = &self.valid_entries;
                let selected_command_index = self.selected_command_index;
                if e.shift_key() {
                    return false;
                }
                match e.key().as_ref() {
                    "Backspace" => {
                        if self.value.is_empty() {
                            props.ondelete.emit(());
                        }
                    }
                    "ArrowUp" => {
                        if !entries.is_empty() {
                            self.selected_command_index = if selected_command_index > 0 {
                                selected_command_index - 1
                            } else {
                                entries.len() - 1
                            }
                        }
                    }
                    "ArrowDown" => {
                        if !entries.is_empty() {
                            self.selected_command_index =
                                if selected_command_index < entries.len() - 1 {
                                    selected_command_index + 1
                                } else {
                                    0
                                }
                        }
                    }
                    "Enter" => {
                        e.prevent_default();
                        let selected_entry = entries.get(selected_command_index).cloned();
                        if let Some(selected_entry) = selected_entry {
                            let action = selected_entry.action;
                            props.onselect.emit(action);
                        } else {
                            props.onenter.emit(());
                        }
                    }
                    _ => {}
                };
                true
            }
        }
    }
}

impl CommandLine {
    fn update_valid_entries(&mut self) {
        self.valid_entries = self
            .all_entries
            .clone()
            .into_iter()
            .filter(|v| v.label.starts_with(&self.value))
            .collect();
    }
}
<|endoftext|>