<commit_before>use std::fs::File;
use std::io::{BufReader, BufRead};
use std::path::Path;
use std::collections::HashSet;
use std::num::ParseIntError;
use std::str::FromStr;
use ::error::{ProcError, ProcFile, ProcOper};
use ::{TaskId, MemSize};

#[derive(Debug, PartialEq)]
/// A struct containing information from the status file for a process.
///
/// This struct contains information from the /proc/[pid]/status or
/// /proc/[tgid]/task/[tid]/status file, for a specific pid or tgid/tid.
pub struct PidStatus {
    // TODO: Maybe these should all be optional, and be more annoying to call
    pub name: String,
    pub tgid: TaskId,
    pub pid: TaskId,
    pub ppid: TaskId,
    pub tracerpid: TaskId,
    // uid: Real, Effective, Saved, Filesystem
    pub uid: (u32, u32, u32, u32),
    // gid: Real, Effective, Saved, Filesystem
    pub gid: (u32, u32, u32, u32),
    pub fdsize: u32,
    pub vmpeak: Option<MemSize>,
    pub vmsize: Option<MemSize>,
    pub vmlck: Option<MemSize>,
    pub vmpin: Option<MemSize>,
    pub vmhwm: Option<MemSize>,
    pub vmrss: Option<MemSize>,
    pub vmdata: Option<MemSize>,
    pub vmstk: Option<MemSize>,
    pub vmexe: Option<MemSize>,
    pub vmlib: Option<MemSize>,
    pub vmpte: Option<MemSize>,
    pub vmpmd: Option<MemSize>,
    pub vmswap: Option<MemSize>,
    pub threads: u32
}

/// Extract a line, and error on missing value, or parsing failure.
macro_rules! extract_line {
    ($lines:expr, $key: expr, $func: expr) => {
        // Default value for missing fields.
        match extract_line_opt!($lines, $key, $func) {
            Some(value) => value,
            None => return Err(
                ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus,
                    Some(concat!("missing ", $key)))
            ),
        }
    }
}

/// Extract a line, evalute to an Option (None on missing field), error on parsing
/// failure.
macro_rules! extract_line_opt {
    ($lines:expr, $key: expr, $func: expr) => { {
        // Default value for missing fields>
        let mut value = None;
        for raw_line in $lines.by_ref() {
            // Unwrap error
            let line = try!(raw_line);
            // Find colon offset, error on no match.
            let colon_offset = match line.find(':') {
                Some(i) => i,
                None => return Err(
                    ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("Line missing colon"))
                ),
            };
            // Split into Key: Value based on colon offset.
            let (first, second) = line.split_at(colon_offset);
            let key = first.trim();
            let (_, last) = second.split_at(1);
            let line_val = last.trim();
            // If we're not looking for this key, try the next one.
            if !STATUS_COLS.contains(key) {
                continue;
            }
            // If key doesn't match, break
            if $key != key {
                break;
            }

            // Call parsing function after trimming value.
            value = Some(try!(
                $func(line_val).map_err(|e|
                    ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
                        Some(e), Some($key))
                )
            ));
            // We have finished finding this value
            break;
        }
        value
    } }
}

impl PidStatus {
    /// Generate PidStatus struct given a process directory
    pub fn new(pid_dir: &Path) -> Result<Self, ProcError> {
        // Try opening file
        let status_file = try!(
            File::open(pid_dir.join("status"))
                .map_err(|e| ProcError::new_err(ProcOper::Opening, ProcFile::PidStatus, e))
        );

        let lines =
            BufReader::with_capacity(4096, status_file)
                .lines()
                .map(|r|
                    match r {
                        Ok(o) => Ok(o),
                        Err(e) => Err(ProcError::new_err(ProcOper::Reading, ProcFile::PidStatus, e))
                    }
                );
        Self::parse_string(lines)
    }

    /// Parse an Iterator of lines as a /proc/[pid]/status file.
    fn parse_string<I: Iterator<Item=Result<String, ProcError>>>(mut lines: I) -> Result<Self, ProcError> {
        // It's quite important that these appear in the order that they
        // appear in the status file
        Ok(PidStatus{
            name: extract_line!(lines, "Name", |s| Ok((s as &str).to_owned()) as Result<String, ProcError>),
            tgid: extract_line!(lines, "Tgid", parse_any),
            pid: extract_line!(lines, "Pid", parse_any),
            ppid: extract_line!(lines, "PPid", parse_any),
            tracerpid: extract_line!(lines, "TracerPid", parse_any),
            uid : extract_line!(lines, "Uid", parse_uids),
            gid : extract_line!(lines, "Gid", parse_uids),
            fdsize : extract_line!(lines, "FDSize", parse_any),
            vmpeak: extract_line_opt!(lines, "VmPeak", parse_mem),
            vmsize: extract_line_opt!(lines, "VmSize", parse_mem),
            vmlck: extract_line_opt!(lines, "VmLck", parse_mem),
            vmpin: extract_line_opt!(lines, "VmPin", parse_mem),
            vmhwm: extract_line_opt!(lines, "VmHWM", parse_mem),
            vmrss: extract_line_opt!(lines, "VmRSS", parse_mem),
            vmdata: extract_line_opt!(lines, "VmData", parse_mem),
            vmstk: extract_line_opt!(lines, "VmStk", parse_mem),
            vmexe: extract_line_opt!(lines, "VmExe", parse_mem),
            vmlib: extract_line_opt!(lines, "VmLib", parse_mem),
            vmpte: extract_line_opt!(lines, "VmPTE", parse_mem),
            vmpmd: extract_line_opt!(lines, "VmPMD", parse_mem),
            vmswap: extract_line_opt!(lines, "VmSwap", parse_mem),
            threads: extract_line!(lines, "Threads", parse_any)
        })
    }
}

lazy_static! {
    // This vec should contain all columns that the parser is looking for,
    // at the moment this is definitely static.
    //
    // If this is not kept uptodate, the values will be ignored.
    static ref STATUS_COLS: HashSet<String> = vec!["Name", "Tgid", "Pid", "PPid",
        "TracerPid", "Uid", "Gid", "FDSize", "VmPeak", "VmSize", "VmLck",
        "VmPin", "VmHWM", "VmRSS", "VmData", "VmStk", "VmExe", "VmLib",
        "VmPMD", "VmPTE", "VmSwap", "Threads"]
            .into_iter()
            .map(|s| s.to_owned())
            .collect();
}

/// Parse anything that's parsable from a string.
fn parse_any<N: FromStr>(str: &str) -> Result<N, N::Err> {
    str.parse()
}

/// Parse a set of four numbers as uids or gids.
fn parse_uids(uid_str: &str) -> Result<(u32, u32, u32, u32), ProcError> {
    let uids = try!(
        uid_str.split("\t")
            .filter(|s| s != &"")
            .map(|s|
                s.parse()
            ).collect::<Result<Vec<_>, _>>()
            .map_err(|e|
                ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
                    Some(e), Some("parsing uid"))
            )

    );
    if uids.len() != 4 {
        return Err(ProcError::new_more(ProcOper::ParsingField,
            ProcFile::PidStatus, Some("missing uids")));
    }
    Ok((uids[0], uids[1], uids[2], uids[3]))
}

/// Parse a string as a kB memory string.
fn parse_mem(mem_str: &str) -> Result<MemSize, ParseIntError> {
    mem_str.trim_right_matches(" kB")
        .parse::<MemSize>()
        .map(|n| n * 1024)
}

#[test]
fn test_no_colon() {
    let lines = "Name".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("Line missing colon")))
    );
}

#[test]
fn test_missing_tgid() {
    let lines = "Name: a\n\
                 Pid: 4\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("missing Tgid")))
    );
}

#[test]
fn test_uid_parse() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Ngid:	0\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	a000	1000\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
            Some("a".parse::<u8>().unwrap_err()), Some("Uid")))
    );
}

#[test]
fn test_uid_count() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Ngid:	0\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	1000\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("Uid")))
    );
}

#[test]
fn test_mem_parse() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Ngid:	0\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	1000	1000\n\
                 Gid:	1000	1000	1000	1000\n\
                 FDSize:	256\n\
                 Groups:	10 18 27 35 101 103 104 105 250 1000 1001 \n\
                 NStgid:	27899\n\
                 NSpid:	27899\n\
                 NSpgid:	27899\n\
                 NSsid:	27899\n\
                 VmPeak:	   a0896 kB\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
            Some("a".parse::<u8>().unwrap_err()), Some("VmPeak")))
    );
}

#[test]
fn test_parsing() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	1000	1000\n\
                 Gid:	1000	1000	1000	1000\n\
                 FDSize:	256\n\
                 Groups:	10 18 27 35 101 103 104 105 250 1000 1001 \n\
                 NStgid:	27899\n\
                 NSpid:	27899\n\
                 NSpgid:	27899\n\
                 NSsid:	27899\n\
                 VmPeak:	   20896 kB\n\
                 VmSize:	   20868 kB\n\
                 VmLck:	       0 kB\n\
                 VmPin:	       0 kB\n\
                 VmHWM:	    4584 kB\n\
                 VmRSS:	    4584 kB\n\
                 VmData:	    1176 kB\n\
                 VmStk:	     136 kB\n\
                 VmExe:	     688 kB\n\
                 VmLib:	    2540 kB\n\
                 VmPTE:	      64 kB\n\
                 VmPMD:	      12 kB\n\
                 VmSwap:	       0 kB\n\
                 Threads:	1\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Ok(PidStatus {
            name: "bash".to_owned(),
            tgid: 27899,
            pid: 27899,
            ppid: 4351,
            tracerpid: 0,
            uid: (1000, 1000, 1000, 1000),
            gid: (1000, 1000, 1000, 1000),
            fdsize: 256,
            vmpeak: Some(21397504),
            vmsize: Some(21368832),
            vmlck: Some(0),
            vmpin: Some(0),
            vmhwm: Some(4694016),
            vmrss: Some(4694016),
            vmdata: Some(1204224),
            vmstk: Some(139264),
            vmexe: Some(704512),
            vmlib: Some(2600960),
            vmpte: Some(65536),
            vmpmd: Some(12288),
            vmswap: Some(0),
            threads: 1
        })
    );
}
<commit_msg>Add test for optional values in /proc/[pid]/status parsing<commit_after>use std::fs::File;
use std::io::{BufReader, BufRead};
use std::path::Path;
use std::collections::HashSet;
use std::num::ParseIntError;
use std::str::FromStr;
use ::error::{ProcError, ProcFile, ProcOper};
use ::{TaskId, MemSize};

#[derive(Debug, PartialEq)]
/// A struct containing information from the status file for a process.
///
/// This struct contains information from the /proc/[pid]/status or
/// /proc/[tgid]/task/[tid]/status file, for a specific pid or tgid/tid.
pub struct PidStatus {
    // TODO: Maybe these should all be optional, and be more annoying to call
    pub name: String,
    pub tgid: TaskId,
    pub pid: TaskId,
    pub ppid: TaskId,
    pub tracerpid: TaskId,
    // uid: Real, Effective, Saved, Filesystem
    pub uid: (u32, u32, u32, u32),
    // gid: Real, Effective, Saved, Filesystem
    pub gid: (u32, u32, u32, u32),
    pub fdsize: u32,
    pub vmpeak: Option<MemSize>,
    pub vmsize: Option<MemSize>,
    pub vmlck: Option<MemSize>,
    pub vmpin: Option<MemSize>,
    pub vmhwm: Option<MemSize>,
    pub vmrss: Option<MemSize>,
    pub vmdata: Option<MemSize>,
    pub vmstk: Option<MemSize>,
    pub vmexe: Option<MemSize>,
    pub vmlib: Option<MemSize>,
    pub vmpte: Option<MemSize>,
    pub vmpmd: Option<MemSize>,
    pub vmswap: Option<MemSize>,
    pub threads: u32
}

/// Extract a line, and error on missing value, or parsing failure.
macro_rules! extract_line {
    ($lines:expr, $key: expr, $func: expr) => {
        // Default value for missing fields.
        match extract_line_opt!($lines, $key, $func) {
            Some(value) => value,
            None => return Err(
                ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus,
                    Some(concat!("missing ", $key)))
            ),
        }
    }
}

/// Extract a line, evalute to an Option (None on missing field), error on parsing
/// failure.
macro_rules! extract_line_opt {
    ($lines:expr, $key: expr, $func: expr) => { {
        // Default value for missing fields>
        let mut value = None;
        for raw_line in $lines.by_ref() {
            // Unwrap error
            let line = try!(raw_line);
            // Find colon offset, error on no match.
            let colon_offset = match line.find(':') {
                Some(i) => i,
                None => return Err(
                    ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("Line missing colon"))
                ),
            };
            // Split into Key: Value based on colon offset.
            let (first, second) = line.split_at(colon_offset);
            let key = first.trim();
            let (_, last) = second.split_at(1);
            let line_val = last.trim();
            // If we're not looking for this key, try the next one.
            if !STATUS_COLS.contains(key) {
                continue;
            }
            // If key doesn't match, break
            if $key != key {
                break;
            }

            // Call parsing function after trimming value.
            value = Some(try!(
                $func(line_val).map_err(|e|
                    ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
                        Some(e), Some($key))
                )
            ));
            // We have finished finding this value
            break;
        }
        value
    } }
}

impl PidStatus {
    /// Generate PidStatus struct given a process directory
    pub fn new(pid_dir: &Path) -> Result<Self, ProcError> {
        // Try opening file
        let status_file = try!(
            File::open(pid_dir.join("status"))
                .map_err(|e| ProcError::new_err(ProcOper::Opening, ProcFile::PidStatus, e))
        );

        let lines =
            BufReader::with_capacity(4096, status_file)
                .lines()
                .map(|r|
                    match r {
                        Ok(o) => Ok(o),
                        Err(e) => Err(ProcError::new_err(ProcOper::Reading, ProcFile::PidStatus, e))
                    }
                );
        Self::parse_string(lines)
    }

    /// Parse an Iterator of lines as a /proc/[pid]/status file.
    fn parse_string<I: Iterator<Item=Result<String, ProcError>>>(mut lines: I) -> Result<Self, ProcError> {
        // It's quite important that these appear in the order that they
        // appear in the status file
        Ok(PidStatus{
            name: extract_line!(lines, "Name", |s| Ok((s as &str).to_owned()) as Result<String, ProcError>),
            tgid: extract_line!(lines, "Tgid", parse_any),
            pid: extract_line!(lines, "Pid", parse_any),
            ppid: extract_line!(lines, "PPid", parse_any),
            tracerpid: extract_line!(lines, "TracerPid", parse_any),
            uid : extract_line!(lines, "Uid", parse_uids),
            gid : extract_line!(lines, "Gid", parse_uids),
            fdsize : extract_line!(lines, "FDSize", parse_any),
            vmpeak: extract_line_opt!(lines, "VmPeak", parse_mem),
            vmsize: extract_line_opt!(lines, "VmSize", parse_mem),
            vmlck: extract_line_opt!(lines, "VmLck", parse_mem),
            vmpin: extract_line_opt!(lines, "VmPin", parse_mem),
            vmhwm: extract_line_opt!(lines, "VmHWM", parse_mem),
            vmrss: extract_line_opt!(lines, "VmRSS", parse_mem),
            vmdata: extract_line_opt!(lines, "VmData", parse_mem),
            vmstk: extract_line_opt!(lines, "VmStk", parse_mem),
            vmexe: extract_line_opt!(lines, "VmExe", parse_mem),
            vmlib: extract_line_opt!(lines, "VmLib", parse_mem),
            vmpte: extract_line_opt!(lines, "VmPTE", parse_mem),
            vmpmd: extract_line_opt!(lines, "VmPMD", parse_mem),
            vmswap: extract_line_opt!(lines, "VmSwap", parse_mem),
            threads: extract_line!(lines, "Threads", parse_any)
        })
    }
}

lazy_static! {
    // This vec should contain all columns that the parser is looking for,
    // at the moment this is definitely static.
    //
    // If this is not kept uptodate, the values will be ignored.
    static ref STATUS_COLS: HashSet<String> = vec!["Name", "Tgid", "Pid", "PPid",
        "TracerPid", "Uid", "Gid", "FDSize", "VmPeak", "VmSize", "VmLck",
        "VmPin", "VmHWM", "VmRSS", "VmData", "VmStk", "VmExe", "VmLib",
        "VmPMD", "VmPTE", "VmSwap", "Threads"]
            .into_iter()
            .map(|s| s.to_owned())
            .collect();
}

/// Parse anything that's parsable from a string.
fn parse_any<N: FromStr>(str: &str) -> Result<N, N::Err> {
    str.parse()
}

/// Parse a set of four numbers as uids or gids.
fn parse_uids(uid_str: &str) -> Result<(u32, u32, u32, u32), ProcError> {
    let uids = try!(
        uid_str.split("\t")
            .filter(|s| s != &"")
            .map(|s|
                s.parse()
            ).collect::<Result<Vec<_>, _>>()
            .map_err(|e|
                ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
                    Some(e), Some("parsing uid"))
            )

    );
    if uids.len() != 4 {
        return Err(ProcError::new_more(ProcOper::ParsingField,
            ProcFile::PidStatus, Some("missing uids")));
    }
    Ok((uids[0], uids[1], uids[2], uids[3]))
}

/// Parse a string as a kB memory string.
fn parse_mem(mem_str: &str) -> Result<MemSize, ParseIntError> {
    mem_str.trim_right_matches(" kB")
        .parse::<MemSize>()
        .map(|n| n * 1024)
}

#[test]
fn test_no_colon() {
    let lines = "Name".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("Line missing colon")))
    );
}

#[test]
fn test_missing_tgid() {
    let lines = "Name: a\n\
                 Pid: 4\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("missing Tgid")))
    );
}

#[test]
fn test_uid_parse() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Ngid:	0\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	a000	1000\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
            Some("a".parse::<u8>().unwrap_err()), Some("Uid")))
    );
}

#[test]
fn test_uid_count() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Ngid:	0\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	1000\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("Uid")))
    );
}

#[test]
fn test_mem_parse() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Ngid:	0\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	1000	1000\n\
                 Gid:	1000	1000	1000	1000\n\
                 FDSize:	256\n\
                 Groups:	10 18 27 35 101 103 104 105 250 1000 1001 \n\
                 NStgid:	27899\n\
                 NSpid:	27899\n\
                 NSpgid:	27899\n\
                 NSsid:	27899\n\
                 VmPeak:	   a0896 kB\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new(ProcOper::ParsingField, ProcFile::PidStatus,
            Some("a".parse::<u8>().unwrap_err()), Some("VmPeak")))
    );
}

#[test]
fn test_optional_parse() {
    let lines = "Name:  kthreadd\n\
                 State: S (sleeping)\n\
                 Tgid:  2\n\
                 Ngid:  0\n\
                 Pid:   2\n\
                 PPid:  0\n\
                 TracerPid: 0\n\
                 Uid:   0   0   0   0\n\
                 Gid:   0   0   0   0\n\
                 FDSize:    64\n\
                 Groups:    \n\
                 NStgid:    2\n\
                 NSpid: 2\n\
                 NSpgid:    0\n\
                 NSsid: 0\n\
                 Threads:   1\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Err(ProcError::new_more(ProcOper::ParsingField, ProcFile::PidStatus, Some("missing Threads")))
    );
}

#[test]
fn test_parsing() {
    let lines = "Name:	bash\n\
                 Tgid:	27899\n\
                 Pid:	27899\n\
                 PPid:	4351\n\
                 TracerPid:	0\n\
                 Uid:	1000	1000	1000	1000\n\
                 Gid:	1000	1000	1000	1000\n\
                 FDSize:	256\n\
                 Groups:	10 18 27 35 101 103 104 105 250 1000 1001 \n\
                 NStgid:	27899\n\
                 NSpid:	27899\n\
                 NSpgid:	27899\n\
                 NSsid:	27899\n\
                 VmPeak:	   20896 kB\n\
                 VmSize:	   20868 kB\n\
                 VmLck:	       0 kB\n\
                 VmPin:	       0 kB\n\
                 VmHWM:	    4584 kB\n\
                 VmRSS:	    4584 kB\n\
                 VmData:	    1176 kB\n\
                 VmStk:	     136 kB\n\
                 VmExe:	     688 kB\n\
                 VmLib:	    2540 kB\n\
                 VmPTE:	      64 kB\n\
                 VmPMD:	      12 kB\n\
                 VmSwap:	       0 kB\n\
                 Threads:	1\n\
                 ".lines().map(|l| Ok(l.to_owned()));
    let status = PidStatus::parse_string(lines);
    assert_eq!(status,
        Ok(PidStatus {
            name: "bash".to_owned(),
            tgid: 27899,
            pid: 27899,
            ppid: 4351,
            tracerpid: 0,
            uid: (1000, 1000, 1000, 1000),
            gid: (1000, 1000, 1000, 1000),
            fdsize: 256,
            vmpeak: Some(21397504),
            vmsize: Some(21368832),
            vmlck: Some(0),
            vmpin: Some(0),
            vmhwm: Some(4694016),
            vmrss: Some(4694016),
            vmdata: Some(1204224),
            vmstk: Some(139264),
            vmexe: Some(704512),
            vmlib: Some(2600960),
            vmpte: Some(65536),
            vmpmd: Some(12288),
            vmswap: Some(0),
            threads: 1
        })
    );
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! aws ec2 metadata fetcher
//!

use std::collections::HashMap;

#[cfg(test)]
use mockito;
use openssh_keys::PublicKey;
use serde_derive::Deserialize;
use slog_scope::warn;

use crate::errors::*;
use crate::network;
use crate::providers::MetadataProvider;
use crate::retry;

#[cfg(test)]
mod mock_tests;

#[cfg(not(feature = "cl-legacy"))]
static ENV_PREFIX: &str = "AWS";
#[cfg(feature = "cl-legacy")]
static ENV_PREFIX: &str = "EC2";

#[allow(non_snake_case)]
#[derive(Debug, Deserialize)]
struct InstanceIdDoc {
    region: String,
}

#[derive(Clone, Debug)]
pub struct AwsProvider {
    client: retry::Client,
}

impl AwsProvider {
    pub fn try_new() -> Result<AwsProvider> {
        let client = retry::Client::try_new()?.return_on_404(true);

        Ok(AwsProvider { client })
    }

    #[cfg(test)]
    fn endpoint_for(key: &str) -> String {
        let url = mockito::server_url();
        format!("{}/{}", url, key)
    }

    #[cfg(not(test))]
    fn endpoint_for(key: &str) -> String {
        const URL: &str = "http://169.254.169.254/2009-04-04";
        format!("{}/{}", URL, key)
    }

    fn fetch_ssh_keys(&self) -> Result<Vec<String>> {
        let keydata: Option<String> = self
            .client
            .get(
                retry::Raw,
                AwsProvider::endpoint_for("meta-data/public-keys"),
            )
            .send()?;

        let mut keys = Vec::new();
        if let Some(keys_list) = keydata {
            for l in keys_list.lines() {
                let tokens: Vec<&str> = l.split('=').collect();
                if tokens.len() != 2 {
                    return Err("error parsing keyID".into());
                }
                let key: String = self
                    .client
                    .get(
                        retry::Raw,
                        AwsProvider::endpoint_for(&format!(
                            "meta-data/public-keys/{}/openssh-key",
                            tokens[0]
                        )),
                    )
                    .send()?
                    .ok_or("missing ssh key")?;
                keys.push(key)
            }
        }
        Ok(keys)
    }
}

impl MetadataProvider for AwsProvider {
    fn attributes(&self) -> Result<HashMap<String, String>> {
        let mut out = HashMap::with_capacity(6);

        let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> {
            let value = self
                .client
                .get(retry::Raw, AwsProvider::endpoint_for(name))
                .send()?;

            if let Some(value) = value {
                map.insert(key.to_string(), value);
            }

            Ok(())
        };

        add_value(
            &mut out,
            &format!("{}_INSTANCE_ID", ENV_PREFIX),
            "meta-data/instance-id",
        )?;
        add_value(
            &mut out,
            &format!("{}_INSTANCE_TYPE", ENV_PREFIX),
            "meta-data/instance-type",
        )?;
        add_value(
            &mut out,
            &format!("{}_IPV4_LOCAL", ENV_PREFIX),
            "meta-data/local-ipv4",
        )?;
        add_value(
            &mut out,
            &format!("{}_IPV4_PUBLIC", ENV_PREFIX),
            "meta-data/public-ipv4",
        )?;
        add_value(
            &mut out,
            &format!("{}_AVAILABILITY_ZONE", ENV_PREFIX),
            "meta-data/placement/availability-zone",
        )?;
        add_value(
            &mut out,
            &format!("{}_HOSTNAME", ENV_PREFIX),
            "meta-data/hostname",
        )?;
        add_value(
            &mut out,
            &format!("{}_PUBLIC_HOSTNAME", ENV_PREFIX),
            "meta-data/public-hostname",
        )?;

        let region = self
            .client
            .get(
                retry::Json,
                AwsProvider::endpoint_for("dynamic/instance-identity/document"),
            )
            .send()?
            .map(|instance_id_doc: InstanceIdDoc| instance_id_doc.region);
        if let Some(region) = region {
            out.insert(format!("{}_REGION", ENV_PREFIX), region);
        }

        Ok(out)
    }

    fn hostname(&self) -> Result<Option<String>> {
        self.client
            .get(retry::Raw, AwsProvider::endpoint_for("meta-data/hostname"))
            .send()
    }

    fn ssh_keys(&self) -> Result<Vec<PublicKey>> {
        self.fetch_ssh_keys().map(|keys| {
            keys.into_iter()
                .map(|key| {
                    let key = PublicKey::parse(&key)?;
                    Ok(key)
                })
                .collect::<Result<Vec<_>>>()
        })?
    }

    fn networks(&self) -> Result<Vec<network::Interface>> {
        Ok(vec![])
    }

    fn virtual_network_devices(&self) -> Result<Vec<network::VirtualNetDev>> {
        warn!("virtual network devices metadata requested, but not supported on this platform");
        Ok(vec![])
    }

    fn boot_checkin(&self) -> Result<()> {
        warn!("boot check-in requested, but not supported on this platform");
        Ok(())
    }
}
<commit_msg>aws: change metadata version from 2009-04-04 to 2019-10-01<commit_after>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! aws ec2 metadata fetcher
//!

use std::collections::HashMap;

#[cfg(test)]
use mockito;
use openssh_keys::PublicKey;
use serde_derive::Deserialize;
use slog_scope::warn;

use crate::errors::*;
use crate::network;
use crate::providers::MetadataProvider;
use crate::retry;

#[cfg(test)]
mod mock_tests;

#[cfg(not(feature = "cl-legacy"))]
static ENV_PREFIX: &str = "AWS";
#[cfg(feature = "cl-legacy")]
static ENV_PREFIX: &str = "EC2";

#[allow(non_snake_case)]
#[derive(Debug, Deserialize)]
struct InstanceIdDoc {
    region: String,
}

#[derive(Clone, Debug)]
pub struct AwsProvider {
    client: retry::Client,
}

impl AwsProvider {
    pub fn try_new() -> Result<AwsProvider> {
        let client = retry::Client::try_new()?.return_on_404(true);

        Ok(AwsProvider { client })
    }

    #[cfg(test)]
    fn endpoint_for(key: &str) -> String {
        let url = mockito::server_url();
        format!("{}/{}", url, key)
    }

    #[cfg(not(test))]
    fn endpoint_for(key: &str) -> String {
        const URL: &str = "http://169.254.169.254/2019-10-01";
        format!("{}/{}", URL, key)
    }

    fn fetch_ssh_keys(&self) -> Result<Vec<String>> {
        let keydata: Option<String> = self
            .client
            .get(
                retry::Raw,
                AwsProvider::endpoint_for("meta-data/public-keys"),
            )
            .send()?;

        let mut keys = Vec::new();
        if let Some(keys_list) = keydata {
            for l in keys_list.lines() {
                let tokens: Vec<&str> = l.split('=').collect();
                if tokens.len() != 2 {
                    return Err("error parsing keyID".into());
                }
                let key: String = self
                    .client
                    .get(
                        retry::Raw,
                        AwsProvider::endpoint_for(&format!(
                            "meta-data/public-keys/{}/openssh-key",
                            tokens[0]
                        )),
                    )
                    .send()?
                    .ok_or("missing ssh key")?;
                keys.push(key)
            }
        }
        Ok(keys)
    }
}

impl MetadataProvider for AwsProvider {
    fn attributes(&self) -> Result<HashMap<String, String>> {
        let mut out = HashMap::with_capacity(6);

        let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> {
            let value = self
                .client
                .get(retry::Raw, AwsProvider::endpoint_for(name))
                .send()?;

            if let Some(value) = value {
                map.insert(key.to_string(), value);
            }

            Ok(())
        };

        add_value(
            &mut out,
            &format!("{}_INSTANCE_ID", ENV_PREFIX),
            "meta-data/instance-id",
        )?;
        add_value(
            &mut out,
            &format!("{}_INSTANCE_TYPE", ENV_PREFIX),
            "meta-data/instance-type",
        )?;
        add_value(
            &mut out,
            &format!("{}_IPV4_LOCAL", ENV_PREFIX),
            "meta-data/local-ipv4",
        )?;
        add_value(
            &mut out,
            &format!("{}_IPV4_PUBLIC", ENV_PREFIX),
            "meta-data/public-ipv4",
        )?;
        add_value(
            &mut out,
            &format!("{}_AVAILABILITY_ZONE", ENV_PREFIX),
            "meta-data/placement/availability-zone",
        )?;
        add_value(
            &mut out,
            &format!("{}_HOSTNAME", ENV_PREFIX),
            "meta-data/hostname",
        )?;
        add_value(
            &mut out,
            &format!("{}_PUBLIC_HOSTNAME", ENV_PREFIX),
            "meta-data/public-hostname",
        )?;

        let region = self
            .client
            .get(
                retry::Json,
                AwsProvider::endpoint_for("dynamic/instance-identity/document"),
            )
            .send()?
            .map(|instance_id_doc: InstanceIdDoc| instance_id_doc.region);
        if let Some(region) = region {
            out.insert(format!("{}_REGION", ENV_PREFIX), region);
        }

        Ok(out)
    }

    fn hostname(&self) -> Result<Option<String>> {
        self.client
            .get(retry::Raw, AwsProvider::endpoint_for("meta-data/hostname"))
            .send()
    }

    fn ssh_keys(&self) -> Result<Vec<PublicKey>> {
        self.fetch_ssh_keys().map(|keys| {
            keys.into_iter()
                .map(|key| {
                    let key = PublicKey::parse(&key)?;
                    Ok(key)
                })
                .collect::<Result<Vec<_>>>()
        })?
    }

    fn networks(&self) -> Result<Vec<network::Interface>> {
        Ok(vec![])
    }

    fn virtual_network_devices(&self) -> Result<Vec<network::VirtualNetDev>> {
        warn!("virtual network devices metadata requested, but not supported on this platform");
        Ok(vec![])
    }

    fn boot_checkin(&self) -> Result<()> {
        warn!("boot check-in requested, but not supported on this platform");
        Ok(())
    }
}
<|endoftext|>


<commit_before>//! Compiles the profiler part of the `compiler-rt` library.
//!
//! See the build.rs for libcompiler_builtins crate for details.

use std::env;
use std::path::Path;

fn main() {
    let target = env::var("TARGET").expect("TARGET was not set");
    let cfg = &mut cc::Build::new();

    let mut profile_sources = vec!["GCDAProfiling.c",
                                   "InstrProfiling.c",
                                   "InstrProfilingBuffer.c",
                                   "InstrProfilingFile.c",
                                   "InstrProfilingMerge.c",
                                   "InstrProfilingMergeFile.c",
                                   "InstrProfilingNameVar.c",
                                   "InstrProfilingPlatformDarwin.c",
                                   "InstrProfilingPlatformLinux.c",
                                   "InstrProfilingPlatformOther.c",
                                   "InstrProfilingRuntime.cc",
                                   "InstrProfilingUtil.c",
                                   "InstrProfilingValue.c",
                                   "InstrProfilingWriter.c"];

    if target.contains("msvc") {
        // Don't pull in extra libraries on MSVC
        cfg.flag("/Zl");
        profile_sources.push("WindowsMMap.c");
        cfg.define("strdup", Some("_strdup"));
        cfg.define("open", Some("_open"));
        cfg.define("fdopen", Some("_fdopen"));
        cfg.define("getpid", Some("_getpid"));
        cfg.define("fileno", Some("_fileno"));
    } else {
        // Turn off various features of gcc and such, mostly copying
        // compiler-rt's build system already
        cfg.flag("-fno-builtin");
        cfg.flag("-fvisibility=hidden");
        cfg.flag("-fomit-frame-pointer");
        cfg.flag("-ffreestanding");
        cfg.define("VISIBILITY_HIDDEN", None);
        cfg.define("COMPILER_RT_HAS_UNAME", Some("1"));
    }

    // The source for `compiler-rt` comes from the `compiler-builtins` crate, so
    // load our env var set by cargo to find the source code.
    let root = env::var_os("DEP_COMPILER_RT_COMPILER_RT").unwrap();
    let root = Path::new(&root);

    for src in profile_sources {
        cfg.file(root.join("lib").join("profile").join(src));
    }

    cfg.warnings(false);
    cfg.compile("profiler-rt");
}
<commit_msg>libprofiler_builtins: Set compilation flags more correctly for C code.<commit_after>//! Compiles the profiler part of the `compiler-rt` library.
//!
//! See the build.rs for libcompiler_builtins crate for details.

use std::env;
use std::path::Path;

fn main() {
    let target = env::var("TARGET").expect("TARGET was not set");
    let cfg = &mut cc::Build::new();

    let mut profile_sources = vec!["GCDAProfiling.c",
                                   "InstrProfiling.c",
                                   "InstrProfilingBuffer.c",
                                   "InstrProfilingFile.c",
                                   "InstrProfilingMerge.c",
                                   "InstrProfilingMergeFile.c",
                                   "InstrProfilingNameVar.c",
                                   "InstrProfilingPlatformDarwin.c",
                                   "InstrProfilingPlatformLinux.c",
                                   "InstrProfilingPlatformOther.c",
                                   "InstrProfilingRuntime.cc",
                                   "InstrProfilingUtil.c",
                                   "InstrProfilingValue.c",
                                   "InstrProfilingWriter.c"];

    if target.contains("msvc") {
        // Don't pull in extra libraries on MSVC
        cfg.flag("/Zl");
        profile_sources.push("WindowsMMap.c");
        cfg.define("strdup", Some("_strdup"));
        cfg.define("open", Some("_open"));
        cfg.define("fdopen", Some("_fdopen"));
        cfg.define("getpid", Some("_getpid"));
        cfg.define("fileno", Some("_fileno"));
    } else {
        // Turn off various features of gcc and such, mostly copying
        // compiler-rt's build system already
        cfg.flag("-fno-builtin");
        cfg.flag("-fvisibility=hidden");
        cfg.flag("-fomit-frame-pointer");
        cfg.flag("-ffreestanding");
        cfg.define("VISIBILITY_HIDDEN", None);
        cfg.define("COMPILER_RT_HAS_UNAME", Some("1"));
    }

    // Assume that the Unixes we are building this for have fnctl() available
    if env::var_os("CARGO_CFG_UNIX").is_some() {
        cfg.define("COMPILER_RT_HAS_FCNTL_LCK", Some("1"));
    }

    // This should be a pretty good heuristic for when to set
    // COMPILER_RT_HAS_ATOMICS
    if env::var_os("CARGO_CFG_TARGET_HAS_ATOMIC").map(|features| {
        features.to_string_lossy().to_lowercase().contains("cas")
    }).unwrap_or(false) {
        cfg.define("COMPILER_RT_HAS_ATOMICS", Some("1"));
    }

    // The source for `compiler-rt` comes from the `compiler-builtins` crate, so
    // load our env var set by cargo to find the source code.
    let root = env::var_os("DEP_COMPILER_RT_COMPILER_RT").unwrap();
    let root = Path::new(&root);

    for src in profile_sources {
        cfg.file(root.join("lib").join("profile").join(src));
    }

    cfg.warnings(false);
    cfg.compile("profiler-rt");
}
<|endoftext|>


<commit_before>use std;
use std::path::Path;

use sdl2::event::Event;
use sdl2::gfx::primitives::DrawRenderer;
use sdl2::keyboard::*;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::rect::Rect;
use sdl2::render::{BlendMode, Renderer, Texture, TextureQuery};
use sdl2::surface::Surface;
use sdl2::ttf::{Font, Sdl2TtfContext, STYLE_BOLD};

use position::Position;
use text::Text;

const BORDER_COLOR: Color = Color::RGBA(255, 255, 255, 64);

const PADDING: i32 = 10;

const FONT_COLOR: Color = Color::RGBA(45, 200, 45, 255);
const FONT_SIZE: u16 = 18;

pub struct Console<'a> {
    pub visible: bool,
    visible_start_time: u32, /* Used to ensure that the KeyDown event that opens the console does not trigger text input */

    font_file: &'a str,
    leader: Text,
    input_buffer: String,
    last_command: String,
    command_history: Vec<String>,
    history_position: usize,
    cursor_position: usize,
    buffer: Vec<String>,
    backbuffer_y: i32,
    texture: Texture,
    ttf_context: &'a Sdl2TtfContext,
    size: (u32, u32),
    font: Font<'a>,
    line_ending: bool, // Tracks where the next print call should append to
    ctrl: bool, // Tracks the Ctrl key being pressed
    shift: bool, // Tracks the Shift key being pressed
}

impl<'a> Console<'a> {
    /// Creates a new empty Console
    pub fn new(ttf_context: &'a Sdl2TtfContext,
               mut renderer: &mut Renderer,
               font_file: &'a str)
               -> Console<'a> {
        let (width, height) = renderer.window().unwrap().size();
        let mut texture =
            renderer.create_texture_streaming(PixelFormatEnum::RGBA8888, width / 2, height)
                .unwrap();
        texture.with_lock(None, |buffer: &mut [u8], pitch: usize| {
                for y in 0..height {
                    for x in 0..width / 2 {
                        let x = x as usize;
                        let y = y as usize;
                        let offset = y * pitch + x * 4;
                        buffer[offset] = 182;
                        buffer[offset + 1] = 0;
                        buffer[offset + 2] = 0;
                        buffer[offset + 3] = 0;
                    }
                }
            })
            .unwrap();

        let mut font = ttf_context.load_font(Path::new(font_file), FONT_SIZE).unwrap();
        font.set_style(STYLE_BOLD);

        Console {
            visible: false,
            visible_start_time: 0,

            font_file: font_file,
            leader: Text::new(ttf_context,
                              &mut renderer,
                              "hakka>",
                              Position::XY(PADDING, height as i32 - FONT_SIZE as i32 - PADDING),
                              FONT_SIZE,
                              FONT_COLOR,
                              font_file),
            input_buffer: "".into(),
            last_command: "".into(),
            command_history: Vec::new(),
            history_position: 0,
            cursor_position: 0,
            buffer: Vec::new(),
            backbuffer_y: 0,
            texture: texture,
            ttf_context: ttf_context,
            size: (width / 2, height),
            font: font,
            line_ending: true,
            ctrl: false,
            shift: false,
        }
    }

    pub fn process(&mut self, event: &Event) {
        match *event {
            Event::TextInput { ref text, timestamp, .. } => {
                if self.visible && timestamp > self.visible_start_time + 50 {
                    self.add_text(text);
                }
            }
            Event::MouseWheel { y, .. } => {
                if self.visible {
                    if self.buffer.len() * FONT_SIZE as usize >
                       (self.size.1 - (FONT_SIZE as u32 * 2)) as usize {
                        self.backbuffer_y += y * 6;
                        if self.backbuffer_y < 0 {
                            self.backbuffer_y = 0;
                        }
                    }
                }
            }
            Event::KeyDown { keycode, scancode, timestamp, keymod, .. } => {
                if self.visible {
                    if !keymod.intersects(LALTMOD | LCTRLMOD | LSHIFTMOD | RALTMOD | RCTRLMOD |
                                          RSHIFTMOD) {
                        // The 'Grave' scancode coresponds to the key in the top-left corner of the
                        // keyboard, below escape, on (hopefully) all keyboard layouts.
                        if let Some(Scancode::Grave) = scancode {
                            self.toggle(timestamp);
                            return;
                        }
                    }

                    match keycode {
                        Some(Keycode::LCtrl) |
                        Some(Keycode::RCtrl) => self.ctrl = true,
                        Some(Keycode::LShift) |
                        Some(Keycode::RShift) => self.shift = true,
                        Some(Keycode::C) => {
                            if self.ctrl {
                                self.input_buffer.push_str("^C");
                                self.commit();
                            }
                        }
                        Some(Keycode::Left) => {
                            self.cursor_left();
                        }
                        Some(Keycode::Right) => {
                            self.cursor_right();
                        }
                        Some(Keycode::Backspace) => {
                            self.backspace();
                        }
                        Some(Keycode::Delete) => {
                            if self.cursor_position < self.input_buffer.len() {
                                self.cursor_position += 1;
                                self.backspace();
                            }
                        }
                        _ => (),
                    }
                }
            }
            Event::KeyUp { keycode, .. } => {
                if self.visible {
                    match keycode {
                        Some(Keycode::LCtrl) |
                        Some(Keycode::RCtrl) => self.ctrl = false,
                        Some(Keycode::LShift) |
                        Some(Keycode::RShift) => self.shift = false,
                        Some(Keycode::Up) => {
                            self.history_navigate_back();
                        }
                        Some(Keycode::Down) => {
                            self.history_navigate_forward();
                        }
                        Some(Keycode::Return) => {
                            self.commit();
                        }
                        Some(Keycode::End) => {
                            self.cursor_position = self.input_buffer.len();
                        }
                        Some(Keycode::Home) => {
                            self.cursor_position = 0;
                        }
                        _ => (),
                    }
                }
            }
            _ => (),
        }
    }

    pub fn process_command(&mut self) {
        let command = self.input_buffer.clone();
        if !command.is_empty() {
            self.command_history.push(command.clone());
            self.last_command = command.clone();

            if command == "exit" {
                std::process::exit(0);
            }
        }
    }

    pub fn clear(&mut self) {
        self.buffer.clear();
    }

    fn history_navigate_back(&mut self) {
        if self.history_position > 0 {
            self.input_buffer = self.command_history[self.history_position - 1].clone();
            self.cursor_position = self.input_buffer.len();

            if self.history_position > 0 {
                self.history_position -= 1;
            }
        }
    }

    fn history_navigate_forward(&mut self) {
        if self.command_history.len() > 0 &&
           self.history_position < self.command_history.len() - 1 {
            self.input_buffer = self.command_history[self.history_position + 1].clone();
            self.cursor_position = self.input_buffer.len();
            if self.history_position < self.command_history.len() {
                self.history_position += 1;
            }
        }
    }

    pub fn try_process_command(&mut self) -> Option<String> {
        if !self.last_command.is_empty() {
            let cmd = self.last_command.clone();
            self.last_command.clear();
            Some(cmd)
        } else {
            None
        }
    }

    pub fn print<S>(&mut self, text: S)
        where S: Into<String>
    {
        if !self.line_ending {
            let last = self.buffer.last_mut().unwrap();
            last.push_str(&text.into());
        } else {
            self.buffer.push(text.into());
        }
        self.line_ending = false;
    }

    pub fn println<S>(&mut self, text: S)
        where S: Into<String>
    {
        self.buffer.push(text.into());
        self.line_ending = true;
    }

    pub fn print_lines<S>(&mut self, text: S)
        where S: Into<String>
    {
        for line in text.into().lines() {
            self.println(line);
        }
    }

    pub fn wrap_line(&mut self) {
        self.buffer.push("".into());
        self.line_ending = false;
    }

    /// Toggles the visibility of the Console
    pub fn toggle(&mut self, time: u32) {
        self.visible = !self.visible;
        if self.visible {
            self.visible_start_time = time;
        }
    }

    pub fn add_text(&mut self, input: &str) {
        self.input_buffer.insert(self.cursor_position, input.chars().next().unwrap());
        self.cursor_position += input.len();
    }

    pub fn commit(&mut self) {
        self.buffer.push(format!("hakka> {}", self.input_buffer.clone()));
        self.process_command();
        self.input_buffer.clear();
        self.cursor_position = 0;
        self.history_position = self.command_history.len();
    }

    pub fn cursor_left(&mut self) {
        if self.cursor_position > 0 {
            self.cursor_position -= 1;
            while !self.input_buffer.is_char_boundary(self.cursor_position) {
                self.cursor_position -= 1;
            }
        }
    }

    pub fn cursor_right(&mut self) {
        if self.cursor_position < self.input_buffer.len() {
            self.cursor_position += 1;
            while !self.input_buffer.is_char_boundary(self.cursor_position) {
                self.cursor_position += 1;
            }
        }
    }

    pub fn backspace(&mut self) {
        if self.visible && self.cursor_position > 0 {
            self.cursor_position -= 1;
            while !self.input_buffer.is_char_boundary(self.cursor_position) {
                self.cursor_position -= 1;
            }
            self.input_buffer.remove(self.cursor_position);
        }
    }

    /// Renders the Console
    pub fn render(&mut self, mut renderer: &mut Renderer) {
        if self.visible {

            renderer.set_blend_mode(BlendMode::Blend);
            self.texture.set_blend_mode(BlendMode::Blend);
            renderer.copy(&self.texture,
                      None,
                      Some(Rect::new(0, 0, self.size.0, self.size.1)))
                .unwrap();
            self.generate_backbuffer_texture(&mut renderer);
            self.render_leader(&mut renderer);

            // Insert the cursor via a dodgy vertical line
            let cursor_x =
                60 + PADDING as i16 +
                self.font.size_of(&self.input_buffer[..self.cursor_position]).unwrap().0 as i16;
            // Draw a dodgy cursor
            renderer.thick_line(cursor_x,
                            self.size.1 as i16 - FONT_SIZE as i16 - PADDING as i16,
                            cursor_x,
                            self.size.1 as i16 - PADDING as i16,
                            1,
                            FONT_COLOR)
                .unwrap();

            if !self.input_buffer.is_empty() {
                let text = Text::new(self.ttf_context,
                                     &mut renderer,
                                     &self.input_buffer[..],
                                     Position::XY(60 + PADDING,
                                                  self.size.1 as i32 - FONT_SIZE as i32 - PADDING),
                                     FONT_SIZE,
                                     FONT_COLOR,
                                     self.font_file);
                text.render(&mut renderer);
            }

            self.render_border(&mut renderer);
        }
    }

    fn render_border(&self, mut renderer: &mut Renderer) {
        // Render the border
        renderer.set_draw_color(Color::RGBA(255, 255, 255, 255));
        // North
        renderer.thick_line(0, 0, self.size.0 as i16, 0, 1, BORDER_COLOR).unwrap();

        // East
        renderer.thick_line(self.size.0 as i16,
                        0,
                        self.size.0 as i16,
                        self.size.1 as i16,
                        1,
                        BORDER_COLOR)
            .unwrap();

        // South
        renderer.thick_line(0,
                        self.size.1 as i16 - 1,
                        self.size.0 as i16,
                        self.size.1 as i16 - 1,
                        1,
                        BORDER_COLOR)
            .unwrap();
    }

    fn render_leader(&self, mut renderer: &mut Renderer) {
        // Render a black background behind it so the buffer scrolling looks
        // nicer.
        let rect_y = self.size.1 as i32 - FONT_SIZE as i32 - PADDING;
        renderer.set_draw_color(Color::RGBA(0, 0, 0, 255));
        renderer.fill_rect(Rect::new(0, rect_y, self.size.0, rect_y as u32)).unwrap();
        self.leader.render(&mut renderer);
    }

    fn generate_backbuffer_texture(&mut self, mut renderer: &mut Renderer) {
        let mut main_surface = Surface::new(self.size.0,
                                            (self.size.1 - (FONT_SIZE as u32)),
                                            PixelFormatEnum::RGBA8888)
            .unwrap();
        let mut counter = 2;
        for line in self.buffer.iter().rev().take(100) {
            let y_pos = self.size.1 as i32 - (FONT_SIZE as i32 * counter) + self.backbuffer_y;
            counter += 1;

            if line.trim().is_empty() {
                continue;
            }

            let surface = self.font
                .render(line)
                .blended(FONT_COLOR)
                .unwrap();
            surface.blit(None,
                      &mut main_surface,
                      Some(Rect::new(PADDING, y_pos - PADDING, self.size.1, FONT_SIZE as u32)))
                .unwrap();
        }
        let texture = renderer.create_texture_from_surface(&main_surface)
            .unwrap();

        let TextureQuery { width, height, .. } = texture.query();

        renderer.copy(&texture, None, Some(Rect::new(0, 0, width, height))).unwrap();
    }
}
<commit_msg>Stop automatic console toggle causing history navigation<commit_after>use std;
use std::path::Path;

use sdl2::event::Event;
use sdl2::gfx::primitives::DrawRenderer;
use sdl2::keyboard::*;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::rect::Rect;
use sdl2::render::{BlendMode, Renderer, Texture, TextureQuery};
use sdl2::surface::Surface;
use sdl2::ttf::{Font, Sdl2TtfContext, STYLE_BOLD};

use position::Position;
use text::Text;

const BORDER_COLOR: Color = Color::RGBA(255, 255, 255, 64);

const PADDING: i32 = 10;

const FONT_COLOR: Color = Color::RGBA(45, 200, 45, 255);
const FONT_SIZE: u16 = 18;

pub struct Console<'a> {
    pub visible: bool,
    visible_start_time: u32, /* Used to ensure that the KeyDown event that opens the console does not trigger text input */

    font_file: &'a str,
    leader: Text,
    input_buffer: String,
    last_command: String,
    command_history: Vec<String>,
    history_position: usize,
    cursor_position: usize,
    buffer: Vec<String>,
    backbuffer_y: i32,
    texture: Texture,
    ttf_context: &'a Sdl2TtfContext,
    size: (u32, u32),
    font: Font<'a>,
    line_ending: bool, // Tracks where the next print call should append to
    ctrl: bool, // Tracks the Ctrl key being pressed
    shift: bool, // Tracks the Shift key being pressed
}

impl<'a> Console<'a> {
    /// Creates a new empty Console
    pub fn new(ttf_context: &'a Sdl2TtfContext,
               mut renderer: &mut Renderer,
               font_file: &'a str)
               -> Console<'a> {
        let (width, height) = renderer.window().unwrap().size();
        let mut texture =
            renderer.create_texture_streaming(PixelFormatEnum::RGBA8888, width / 2, height)
                .unwrap();
        texture.with_lock(None, |buffer: &mut [u8], pitch: usize| {
                for y in 0..height {
                    for x in 0..width / 2 {
                        let x = x as usize;
                        let y = y as usize;
                        let offset = y * pitch + x * 4;
                        buffer[offset] = 182;
                        buffer[offset + 1] = 0;
                        buffer[offset + 2] = 0;
                        buffer[offset + 3] = 0;
                    }
                }
            })
            .unwrap();

        let mut font = ttf_context.load_font(Path::new(font_file), FONT_SIZE).unwrap();
        font.set_style(STYLE_BOLD);

        Console {
            visible: false,
            visible_start_time: 0,

            font_file: font_file,
            leader: Text::new(ttf_context,
                              &mut renderer,
                              "hakka>",
                              Position::XY(PADDING, height as i32 - FONT_SIZE as i32 - PADDING),
                              FONT_SIZE,
                              FONT_COLOR,
                              font_file),
            input_buffer: "".into(),
            last_command: "".into(),
            command_history: Vec::new(),
            history_position: 0,
            cursor_position: 0,
            buffer: Vec::new(),
            backbuffer_y: 0,
            texture: texture,
            ttf_context: ttf_context,
            size: (width / 2, height),
            font: font,
            line_ending: true,
            ctrl: false,
            shift: false,
        }
    }

    pub fn process(&mut self, event: &Event) {
        match *event {
            Event::TextInput { ref text, timestamp, .. } => {
                if self.visible && timestamp > self.visible_start_time + 50 {
                    self.add_text(text);
                }
            }
            Event::MouseWheel { y, .. } => {
                if self.visible {
                    if self.buffer.len() * FONT_SIZE as usize >
                       (self.size.1 - (FONT_SIZE as u32 * 2)) as usize {
                        self.backbuffer_y += y * 6;
                        if self.backbuffer_y < 0 {
                            self.backbuffer_y = 0;
                        }
                    }
                }
            }
            Event::KeyDown { keycode, scancode, timestamp, keymod, .. } => {
                if self.visible {
                    if !keymod.intersects(LALTMOD | LCTRLMOD | LSHIFTMOD | RALTMOD | RCTRLMOD |
                                          RSHIFTMOD) {
                        // The 'Grave' scancode coresponds to the key in the top-left corner of the
                        // keyboard, below escape, on (hopefully) all keyboard layouts.
                        if let Some(Scancode::Grave) = scancode {
                            self.toggle(timestamp);
                            return;
                        }
                    }

                    match keycode {
                        Some(Keycode::LCtrl) |
                        Some(Keycode::RCtrl) => self.ctrl = true,
                        Some(Keycode::LShift) |
                        Some(Keycode::RShift) => self.shift = true,
                        Some(Keycode::C) => {
                            if self.ctrl {
                                self.input_buffer.push_str("^C");
                                self.commit();
                            }
                        }
                        Some(Keycode::Left) => {
                            self.cursor_left();
                        }
                        Some(Keycode::Right) => {
                            self.cursor_right();
                        }
                        Some(Keycode::Backspace) => {
                            self.backspace();
                        }
                        Some(Keycode::Delete) => {
                            if self.cursor_position < self.input_buffer.len() {
                                self.cursor_position += 1;
                                self.backspace();
                            }
                        }
                        _ => (),
                    }
                }
            }
            Event::KeyUp { keycode, timestamp, .. } => {
                if self.visible {
                    match keycode {
                        Some(Keycode::LCtrl) |
                        Some(Keycode::RCtrl) => self.ctrl = false,
                        Some(Keycode::LShift) |
                        Some(Keycode::RShift) => self.shift = false,
                        Some(Keycode::Up) => {
                            // Special check that an automatic console toggle
                            // does not cause history navigation when holding the
                            // up arrow.
                            if self.visible_start_time > 0 {
                                self.history_navigate_back();
                            } else {
                                self.visible_start_time = timestamp;
                            }
                        }
                        Some(Keycode::Down) => {
                            // Special check that an automatic console toggle
                            // does not cause history navigation when holding the
                            // down arrow.
                            if self.visible_start_time > 0 {
                                self.history_navigate_forward();
                            } else {
                                self.visible_start_time = timestamp;
                            }
                        }
                        Some(Keycode::Return) => {
                            self.commit();
                        }
                        Some(Keycode::End) => {
                            self.cursor_position = self.input_buffer.len();
                        }
                        Some(Keycode::Home) => {
                            self.cursor_position = 0;
                        }
                        _ => (),
                    }
                }
            }
            _ => (),
        }
    }

    pub fn process_command(&mut self) {
        let command = self.input_buffer.clone();
        if !command.is_empty() {
            self.command_history.push(command.clone());
            self.last_command = command.clone();

            if command == "exit" {
                std::process::exit(0);
            }
        }
    }

    pub fn clear(&mut self) {
        self.buffer.clear();
    }

    fn history_navigate_back(&mut self) {
        if self.history_position > 0 {
            self.input_buffer = self.command_history[self.history_position - 1].clone();
            self.cursor_position = self.input_buffer.len();

            if self.history_position > 0 {
                self.history_position -= 1;
            }
        }
    }

    fn history_navigate_forward(&mut self) {
        if self.command_history.len() > 0 &&
           self.history_position < self.command_history.len() - 1 {
            self.input_buffer = self.command_history[self.history_position + 1].clone();
            self.cursor_position = self.input_buffer.len();
            if self.history_position < self.command_history.len() {
                self.history_position += 1;
            }
        }
    }

    pub fn try_process_command(&mut self) -> Option<String> {
        if !self.last_command.is_empty() {
            let cmd = self.last_command.clone();
            self.last_command.clear();
            Some(cmd)
        } else {
            None
        }
    }

    pub fn print<S>(&mut self, text: S)
        where S: Into<String>
    {
        if !self.line_ending {
            let last = self.buffer.last_mut().unwrap();
            last.push_str(&text.into());
        } else {
            self.buffer.push(text.into());
        }
        self.line_ending = false;
    }

    pub fn println<S>(&mut self, text: S)
        where S: Into<String>
    {
        self.buffer.push(text.into());
        self.line_ending = true;
    }

    pub fn print_lines<S>(&mut self, text: S)
        where S: Into<String>
    {
        for line in text.into().lines() {
            self.println(line);
        }
    }

    pub fn wrap_line(&mut self) {
        self.buffer.push("".into());
        self.line_ending = false;
    }

    /// Toggles the visibility of the Console
    pub fn toggle(&mut self, time: u32) {
        self.visible = !self.visible;
        if self.visible {
            self.visible_start_time = time;
        }
    }

    pub fn add_text(&mut self, input: &str) {
        self.input_buffer.insert(self.cursor_position, input.chars().next().unwrap());
        self.cursor_position += input.len();
    }

    pub fn commit(&mut self) {
        self.buffer.push(format!("hakka> {}", self.input_buffer.clone()));
        self.process_command();
        self.input_buffer.clear();
        self.cursor_position = 0;
        self.history_position = self.command_history.len();
    }

    pub fn cursor_left(&mut self) {
        if self.cursor_position > 0 {
            self.cursor_position -= 1;
            while !self.input_buffer.is_char_boundary(self.cursor_position) {
                self.cursor_position -= 1;
            }
        }
    }

    pub fn cursor_right(&mut self) {
        if self.cursor_position < self.input_buffer.len() {
            self.cursor_position += 1;
            while !self.input_buffer.is_char_boundary(self.cursor_position) {
                self.cursor_position += 1;
            }
        }
    }

    pub fn backspace(&mut self) {
        if self.visible && self.cursor_position > 0 {
            self.cursor_position -= 1;
            while !self.input_buffer.is_char_boundary(self.cursor_position) {
                self.cursor_position -= 1;
            }
            self.input_buffer.remove(self.cursor_position);
        }
    }

    /// Renders the Console
    pub fn render(&mut self, mut renderer: &mut Renderer) {
        if self.visible {

            renderer.set_blend_mode(BlendMode::Blend);
            self.texture.set_blend_mode(BlendMode::Blend);
            renderer.copy(&self.texture,
                      None,
                      Some(Rect::new(0, 0, self.size.0, self.size.1)))
                .unwrap();
            self.generate_backbuffer_texture(&mut renderer);
            self.render_leader(&mut renderer);

            // Insert the cursor via a dodgy vertical line
            let cursor_x =
                60 + PADDING as i16 +
                self.font.size_of(&self.input_buffer[..self.cursor_position]).unwrap().0 as i16;
            // Draw a dodgy cursor
            renderer.thick_line(cursor_x,
                            self.size.1 as i16 - FONT_SIZE as i16 - PADDING as i16,
                            cursor_x,
                            self.size.1 as i16 - PADDING as i16,
                            1,
                            FONT_COLOR)
                .unwrap();

            if !self.input_buffer.is_empty() {
                let text = Text::new(self.ttf_context,
                                     &mut renderer,
                                     &self.input_buffer[..],
                                     Position::XY(60 + PADDING,
                                                  self.size.1 as i32 - FONT_SIZE as i32 - PADDING),
                                     FONT_SIZE,
                                     FONT_COLOR,
                                     self.font_file);
                text.render(&mut renderer);
            }

            self.render_border(&mut renderer);
        }
    }

    fn render_border(&self, mut renderer: &mut Renderer) {
        // Render the border
        renderer.set_draw_color(Color::RGBA(255, 255, 255, 255));
        // North
        renderer.thick_line(0, 0, self.size.0 as i16, 0, 1, BORDER_COLOR).unwrap();

        // East
        renderer.thick_line(self.size.0 as i16,
                        0,
                        self.size.0 as i16,
                        self.size.1 as i16,
                        1,
                        BORDER_COLOR)
            .unwrap();

        // South
        renderer.thick_line(0,
                        self.size.1 as i16 - 1,
                        self.size.0 as i16,
                        self.size.1 as i16 - 1,
                        1,
                        BORDER_COLOR)
            .unwrap();
    }

    fn render_leader(&self, mut renderer: &mut Renderer) {
        // Render a black background behind it so the buffer scrolling looks
        // nicer.
        let rect_y = self.size.1 as i32 - FONT_SIZE as i32 - PADDING;
        renderer.set_draw_color(Color::RGBA(0, 0, 0, 255));
        renderer.fill_rect(Rect::new(0, rect_y, self.size.0, rect_y as u32)).unwrap();
        self.leader.render(&mut renderer);
    }

    fn generate_backbuffer_texture(&mut self, mut renderer: &mut Renderer) {
        let mut main_surface = Surface::new(self.size.0,
                                            (self.size.1 - (FONT_SIZE as u32)),
                                            PixelFormatEnum::RGBA8888)
            .unwrap();
        let mut counter = 2;
        for line in self.buffer.iter().rev().take(100) {
            let y_pos = self.size.1 as i32 - (FONT_SIZE as i32 * counter) + self.backbuffer_y;
            counter += 1;

            if line.trim().is_empty() {
                continue;
            }

            let surface = self.font
                .render(line)
                .blended(FONT_COLOR)
                .unwrap();
            surface.blit(None,
                      &mut main_surface,
                      Some(Rect::new(PADDING, y_pos - PADDING, self.size.1, FONT_SIZE as u32)))
                .unwrap();
        }
        let texture = renderer.create_texture_from_surface(&main_surface)
            .unwrap();

        let TextureQuery { width, height, .. } = texture.query();

        renderer.copy(&texture, None, Some(Rect::new(0, 0, width, height))).unwrap();
    }
}
<|endoftext|>


<commit_before>#[doc = "Generic pass for performing an operation on all descriptions"];

export mk_pass;

fn mk_pass(name: str, op: fn~(str) -> str) -> pass {
    {
        name: name,
        f: fn~(srv: astsrv::srv, doc: doc::cratedoc) -> doc::cratedoc {
            run(srv, doc, op)
        }
    }
}

type op = fn~(str) -> str;

fn run(
    _srv: astsrv::srv,
    doc: doc::cratedoc,
    op: op
) -> doc::cratedoc {
    let fold = fold::fold({
        fold_item: fold_item,
        fold_fn: fold_fn,
        fold_enum: fold_enum,
        fold_res: fold_res,
        fold_iface: fold_iface,
        fold_impl: fold_impl
        with *fold::default_any_fold(op)
    });
    fold.fold_crate(fold, doc)
}

fn maybe_apply_op(op: op, s: option<str>) -> option<str> {
    option::map(s) {|s| op(s) }
}

fn fold_item(fold: fold::fold<op>, doc: doc::itemdoc) -> doc::itemdoc {
    let doc = fold::default_seq_fold_item(fold, doc);

    {
        brief: maybe_apply_op(fold.ctxt, doc.brief),
        desc: maybe_apply_op(fold.ctxt, doc.desc)
        with doc
    }
}

fn fold_fn(fold: fold::fold<op>, doc: doc::fndoc) -> doc::fndoc {
    let fold_ctxt = fold.ctxt;
    let doc = fold::default_seq_fold_fn(fold, doc);

    {
        args: par::anymap(doc.args) {|doc|
            {
                desc: maybe_apply_op(fold_ctxt, doc.desc)
                with doc
            }
        },
        return: {
            desc: maybe_apply_op(fold.ctxt, doc.return.desc)
            with doc.return
        },
        failure: maybe_apply_op(fold.ctxt, doc.failure)
        with doc
    }
}

fn fold_enum(fold: fold::fold<op>, doc: doc::enumdoc) -> doc::enumdoc {
    let fold_ctxt = fold.ctxt;
    let doc = fold::default_seq_fold_enum(fold, doc);

    {
        variants: par::anymap(doc.variants) {|variant|
            {
                desc: maybe_apply_op(fold_ctxt, variant.desc)
                with variant
            }
        }
        with doc
    }
}

fn fold_res(fold: fold::fold<op>, doc: doc::resdoc) -> doc::resdoc {
    let fold_ctxt = fold.ctxt;
    let doc = fold::default_seq_fold_res(fold, doc);

    {
        args: par::anymap(doc.args) {|arg|
            {
                desc: maybe_apply_op(fold_ctxt, arg.desc)
                with arg
            }
        }
        with doc
    }
}

fn fold_iface(fold: fold::fold<op>, doc: doc::ifacedoc) -> doc::ifacedoc {
    let doc = fold::default_seq_fold_iface(fold, doc);

    {
        methods: apply_to_methods(fold.ctxt, doc.methods)
        with doc
    }
}

fn apply_to_methods(op: op, docs: [doc::methoddoc]) -> [doc::methoddoc] {
    par::anymap(docs) {|doc|
        {
            brief: maybe_apply_op(op, doc.brief),
            desc: maybe_apply_op(op, doc.desc),
            args: par::anymap(doc.args) {|doc|
                {
                    desc: maybe_apply_op(op, doc.desc)
                    with doc
                }
            },
            return: {
                desc: maybe_apply_op(op, doc.return.desc)
                with doc.return
            },
            failure: maybe_apply_op(op, doc.failure)
            with doc
        }
    }
}

fn fold_impl(fold: fold::fold<op>, doc: doc::impldoc) -> doc::impldoc {
    let doc = fold::default_seq_fold_impl(fold, doc);

    {
        methods: apply_to_methods(fold.ctxt, doc.methods)
        with doc
    }
}

#[test]
fn should_execute_op_on_enum_brief() {
    let doc = test::mk_doc("#[doc(brief = \" a \")] enum a { b }");
    assert doc.topmod.enums()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_enum_desc() {
    let doc = test::mk_doc("#[doc(desc = \" a \")] enum a { b }");
    assert doc.topmod.enums()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_variant_desc() {
    let doc = test::mk_doc("enum a { #[doc = \" a \"] b }");
    assert doc.topmod.enums()[0].variants[0].desc == some("a");
}

#[test]
fn should_execute_op_on_resource_brief() {
    let doc = test::mk_doc("#[doc(brief = \" a \")] resource r(a: bool) { }");
    assert doc.topmod.resources()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_resource_desc() {
    let doc = test::mk_doc("#[doc(desc = \" a \")] resource r(a: bool) { }");
    assert doc.topmod.resources()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_resource_args() {
    let doc = test::mk_doc(
        "#[doc(args(a = \" a \"))] resource r(a: bool) { }");
    assert doc.topmod.resources()[0].args[0].desc == some("a");
}

#[test]
fn should_execute_op_on_iface_brief() {
    let doc = test::mk_doc(
        "#[doc(brief = \" a \")] iface i { fn a(); }");
    assert doc.topmod.ifaces()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_iface_desc() {
    let doc = test::mk_doc(
        "#[doc(desc = \" a \")] iface i { fn a(); }");
    assert doc.topmod.ifaces()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_iface_method_brief() {
    let doc = test::mk_doc(
        "iface i { #[doc(brief = \" a \")] fn a(); }");
    assert doc.topmod.ifaces()[0].methods[0].brief == some("a");
}

#[test]
fn should_execute_op_on_iface_method_desc() {
    let doc = test::mk_doc(
        "iface i { #[doc(desc = \" a \")] fn a(); }");
    assert doc.topmod.ifaces()[0].methods[0].desc == some("a");
}

#[test]
fn should_execute_op_on_iface_method_args() {
    let doc = test::mk_doc(
        "iface i { #[doc(args(a = \" a \"))] fn a(a: bool); }");
    assert doc.topmod.ifaces()[0].methods[0].args[0].desc == some("a");
}

#[test]
fn should_execute_op_on_iface_method_return() {
    let doc = test::mk_doc(
        "iface i { #[doc(return = \" a \")] fn a() -> int; }");
    assert doc.topmod.ifaces()[0].methods[0].return.desc == some("a");
}

#[test]
fn should_execute_op_on_iface_method_failure_condition() {
    let doc = test::mk_doc("iface i { #[doc(failure = \" a \")] fn a(); }");
    assert doc.topmod.ifaces()[0].methods[0].failure == some("a");
}

#[test]
fn should_execute_op_on_impl_brief() {
    let doc = test::mk_doc(
        "#[doc(brief = \" a \")] impl i for int { fn a() { } }");
    assert doc.topmod.impls()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_impl_desc() {
    let doc = test::mk_doc(
        "#[doc(desc = \" a \")] impl i for int { fn a() { } }");
    assert doc.topmod.impls()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_impl_method_brief() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(brief = \" a \")] fn a() { } }");
    assert doc.topmod.impls()[0].methods[0].brief == some("a");
}

#[test]
fn should_execute_op_on_impl_method_desc() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(desc = \" a \")] fn a() { } }");
    assert doc.topmod.impls()[0].methods[0].desc == some("a");
}

#[test]
fn should_execute_op_on_impl_method_args() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(args(a = \" a \"))] fn a(a: bool) { } }");
    assert doc.topmod.impls()[0].methods[0].args[0].desc == some("a");
}

#[test]
fn should_execute_op_on_impl_method_return() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(return = \" a \")] fn a() -> int { fail } }");
    assert doc.topmod.impls()[0].methods[0].return.desc == some("a");
}

#[test]
fn should_execute_op_on_impl_method_failure_condition() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(failure = \" a \")] fn a() { } }");
    assert doc.topmod.impls()[0].methods[0].failure == some("a");
}

#[test]
fn should_execute_op_on_type_brief() {
    let doc = test::mk_doc(
        "#[doc(brief = \" a \")] type t = int;");
    assert doc.topmod.types()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_type_desc() {
    let doc = test::mk_doc(
        "#[doc(desc = \" a \")] type t = int;");
    assert doc.topmod.types()[0].desc() == some("a");
}

#[cfg(test)]
mod test {
    fn mk_doc(source: str) -> doc::cratedoc {
        astsrv::from_str(source) {|srv|
            let doc = extract::from_srv(srv, "");
            let doc = attr_pass::mk_pass().f(srv, doc);
            mk_pass({|s| str::trim(s)}).f(srv, doc)
        }
    }
}<commit_msg>rustdoc: Fix bustage<commit_after>#[doc = "Generic pass for performing an operation on all descriptions"];

export mk_pass;

fn mk_pass(name: str, op: fn~(str) -> str) -> pass {
    {
        name: name,
        f: fn~(srv: astsrv::srv, doc: doc::cratedoc) -> doc::cratedoc {
            run(srv, doc, op)
        }
    }
}

type op = fn~(str) -> str;

fn run(
    _srv: astsrv::srv,
    doc: doc::cratedoc,
    op: op
) -> doc::cratedoc {
    let fold = fold::fold({
        fold_item: fold_item,
        fold_fn: fold_fn,
        fold_enum: fold_enum,
        fold_res: fold_res,
        fold_iface: fold_iface,
        fold_impl: fold_impl
        with *fold::default_any_fold(op)
    });
    fold.fold_crate(fold, doc)
}

fn maybe_apply_op(op: op, s: option<str>) -> option<str> {
    option::map(s) {|s| op(s) }
}

fn fold_item(fold: fold::fold<op>, doc: doc::itemdoc) -> doc::itemdoc {
    let doc = fold::default_seq_fold_item(fold, doc);

    {
        brief: maybe_apply_op(fold.ctxt, doc.brief),
        desc: maybe_apply_op(fold.ctxt, doc.desc)
        with doc
    }
}

fn fold_fn(fold: fold::fold<op>, doc: doc::fndoc) -> doc::fndoc {
    let fold_ctxt = fold.ctxt;
    let doc = fold::default_seq_fold_fn(fold, doc);

    {
        args: par::anymap(doc.args) {|doc|
            {
                desc: maybe_apply_op(fold_ctxt, doc.desc)
                with doc
            }
        },
        return: {
            desc: maybe_apply_op(fold.ctxt, doc.return.desc)
            with doc.return
        },
        failure: maybe_apply_op(fold.ctxt, doc.failure)
        with doc
    }
}

fn fold_enum(fold: fold::fold<op>, doc: doc::enumdoc) -> doc::enumdoc {
    let fold_ctxt = fold.ctxt;
    let doc = fold::default_seq_fold_enum(fold, doc);

    {
        variants: par::anymap(doc.variants) {|variant|
            {
                desc: maybe_apply_op(fold_ctxt, variant.desc)
                with variant
            }
        }
        with doc
    }
}

fn fold_res(fold: fold::fold<op>, doc: doc::resdoc) -> doc::resdoc {
    let fold_ctxt = fold.ctxt;
    let doc = fold::default_seq_fold_res(fold, doc);

    {
        args: par::anymap(doc.args) {|arg|
            {
                desc: maybe_apply_op(fold_ctxt, arg.desc)
                with arg
            }
        }
        with doc
    }
}

fn fold_iface(fold: fold::fold<op>, doc: doc::ifacedoc) -> doc::ifacedoc {
    let doc = fold::default_seq_fold_iface(fold, doc);

    {
        methods: apply_to_methods(fold.ctxt, doc.methods)
        with doc
    }
}

fn apply_to_methods(op: op, docs: [doc::methoddoc]) -> [doc::methoddoc] {
    par::anymap(docs) {|doc|
        {
            brief: maybe_apply_op(op, doc.brief),
            desc: maybe_apply_op(op, doc.desc),
            args: par::anymap(doc.args) {|doc|
                {
                    desc: maybe_apply_op(op, doc.desc)
                    with doc
                }
            },
            return: {
                desc: maybe_apply_op(op, doc.return.desc)
                with doc.return
            },
            failure: maybe_apply_op(op, doc.failure)
            with doc
        }
    }
}

fn fold_impl(fold: fold::fold<op>, doc: doc::impldoc) -> doc::impldoc {
    let doc = fold::default_seq_fold_impl(fold, doc);

    {
        methods: apply_to_methods(fold.ctxt, doc.methods)
        with doc
    }
}

#[test]
fn should_execute_op_on_enum_brief() {
    let doc = test::mk_doc("#[doc(brief = \" a \")] enum a { b }");
    assert doc.topmod.enums()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_enum_desc() {
    let doc = test::mk_doc("#[doc(desc = \" a \")] enum a { b }");
    assert doc.topmod.enums()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_variant_desc() {
    let doc = test::mk_doc("enum a { #[doc = \" a \"] b }");
    assert doc.topmod.enums()[0].variants[0].desc == some("a");
}

#[test]
fn should_execute_op_on_resource_brief() {
    let doc = test::mk_doc("#[doc(brief = \" a \")] resource r(a: bool) { }");
    assert doc.topmod.resources()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_resource_desc() {
    let doc = test::mk_doc("#[doc(desc = \" a \")] resource r(a: bool) { }");
    assert doc.topmod.resources()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_resource_args() {
    let doc = test::mk_doc(
        "#[doc(args(a = \" a \"))] resource r(a: bool) { }");
    assert doc.topmod.resources()[0].args[0].desc == some("a");
}

#[test]
fn should_execute_op_on_iface_brief() {
    let doc = test::mk_doc(
        "#[doc(brief = \" a \")] iface i { fn a(); }");
    assert doc.topmod.ifaces()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_iface_desc() {
    let doc = test::mk_doc(
        "#[doc(desc = \" a \")] iface i { fn a(); }");
    assert doc.topmod.ifaces()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_iface_method_brief() {
    let doc = test::mk_doc(
        "iface i { #[doc(brief = \" a \")] fn a(); }");
    assert doc.topmod.ifaces()[0].methods[0].brief == some("a");
}

#[test]
fn should_execute_op_on_iface_method_desc() {
    let doc = test::mk_doc(
        "iface i { #[doc(desc = \" a \")] fn a(); }");
    assert doc.topmod.ifaces()[0].methods[0].desc == some("a");
}

#[test]
fn should_execute_op_on_iface_method_args() {
    let doc = test::mk_doc(
        "iface i { #[doc(args(a = \" a \"))] fn a(a: bool); }");
    assert doc.topmod.ifaces()[0].methods[0].args[0].desc == some("a");
}

#[test]
fn should_execute_op_on_iface_method_return() {
    let doc = test::mk_doc(
        "iface i { #[doc(return = \" a \")] fn a() -> int; }");
    assert doc.topmod.ifaces()[0].methods[0].return.desc == some("a");
}

#[test]
fn should_execute_op_on_iface_method_failure_condition() {
    let doc = test::mk_doc("iface i { #[doc(failure = \" a \")] fn a(); }");
    assert doc.topmod.ifaces()[0].methods[0].failure == some("a");
}

#[test]
fn should_execute_op_on_impl_brief() {
    let doc = test::mk_doc(
        "#[doc(brief = \" a \")] impl i for int { fn a() { } }");
    assert doc.topmod.impls()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_impl_desc() {
    let doc = test::mk_doc(
        "#[doc(desc = \" a \")] impl i for int { fn a() { } }");
    assert doc.topmod.impls()[0].desc() == some("a");
}

#[test]
fn should_execute_op_on_impl_method_brief() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(brief = \" a \")] fn a() { } }");
    assert doc.topmod.impls()[0].methods[0].brief == some("a");
}

#[test]
fn should_execute_op_on_impl_method_desc() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(desc = \" a \")] fn a() { } }");
    assert doc.topmod.impls()[0].methods[0].desc == some("a");
}

#[test]
fn should_execute_op_on_impl_method_args() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(args(a = \" a \"))] fn a(a: bool) { } }");
    assert doc.topmod.impls()[0].methods[0].args[0].desc == some("a");
}

#[test]
fn should_execute_op_on_impl_method_return() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(return = \" a \")] fn a() -> int { fail } }");
    assert doc.topmod.impls()[0].methods[0].return.desc == some("a");
}

#[test]
fn should_execute_op_on_impl_method_failure_condition() {
    let doc = test::mk_doc(
        "impl i for int { #[doc(failure = \" a \")] fn a() { } }");
    assert doc.topmod.impls()[0].methods[0].failure == some("a");
}

#[test]
fn should_execute_op_on_type_brief() {
    let doc = test::mk_doc(
        "#[doc(brief = \" a \")] type t = int;");
    assert doc.topmod.types()[0].brief() == some("a");
}

#[test]
fn should_execute_op_on_type_desc() {
    let doc = test::mk_doc(
        "#[doc(desc = \" a \")] type t = int;");
    assert doc.topmod.types()[0].desc() == some("a");
}

#[cfg(test)]
mod test {
    fn mk_doc(source: str) -> doc::cratedoc {
        astsrv::from_str(source) {|srv|
            let doc = extract::from_srv(srv, "");
            let doc = attr_pass::mk_pass().f(srv, doc);
            mk_pass("", {|s| str::trim(s)}).f(srv, doc)
        }
    }
}<|endoftext|>