<commit_before>use std::io::{self, BufRead, Write};
use std::fs::{File, create_dir_all, metadata, set_permissions};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{MetadataExt, PermissionsExt, symlink};
use std::path::Path;
use std::collections::{BTreeMap, BTreeSet};

use cronparse::Limited;
use cronparse::crontab::{CrontabEntry, SystemCrontabEntry, UserCrontabEntry};
use cronparse::schedule::{Calendar, Period, Schedule};
use cronparse::interval::Interval;

use pgs_files::passwd::{get_entry_by_name, get_entry_by_uid};

use super::{LIB_DIR, PACKAGE, REBOOT_FILE};

pub fn generate_systemd_units(entry: CrontabEntry, env: &BTreeMap<String, String>, path: &Path, dstdir: &Path) -> io::Result<()> {
    use cronparse::crontab::CrontabEntry::*;

    info!("generating units for {}: \"{}\", {:?}", path.display(), entry, env);

    let owner = try!(metadata(path)).uid();

    let mut persistent = env.get("PERSISTENT")
                            .and_then(|v| {
                                match &**v {
                                    "yes" | "true" | "1" => Some(true),
                                    "auto" | "" => None,
                                    _ => Some(false),
                                }
                            })
                            .unwrap_or_else(|| {
                                match entry {
                                    Anacron(_) |
                                    User(UserCrontabEntry { sched: Schedule::Period(_), .. }) |
                                    System(SystemCrontabEntry { sched: Schedule::Period(_), .. }) => true,
                                    _ => false,
                                }
                            });

    let batch = env.get("BATCH")
                   .map(|v| {
                       match &**v {
                           "yes" | "true" | "1" => true,
                           _ => false,
                       }
                   })
                   .unwrap_or(false);

    let random_delay = env.get("RANDOM_DELAY").and_then(|v| v.parse::<u64>().ok()).unwrap_or(1);
    let mut delay = env.get("DELAY").and_then(|v| v.parse::<u64>().ok()).unwrap_or(0);
    let hour = env.get("START_HOURS_RANGE")
                  .and_then(|v| v.splitn(1, '-').next().and_then(|v| v.parse::<u64>().ok()))
                  .unwrap_or(0);
    let shell = env.get("SHELL").map(|v| &**v).unwrap_or("/bin/sh");
    let daemon_reload = metadata(REBOOT_FILE).map(|m| m.is_file()).unwrap_or(false);

    let schedule = entry.period()
                        .and_then(|period| {
                            match *period {
                                Period::Reboot => {
                                    persistent = false;
                                    if delay == 0 {
                                        delay = 1;
                                    }
                                    None
                                }
                                Period::Minutely => {
                                    persistent = false;
                                    Some("minutely".to_owned())
                                }
                                Period::Hourly => if delay == 0 { Some("hourly".to_owned()) } else { Some(format!("*-*-* *:{}:0", delay)) },
                                Period::Midnight => {
                                    if delay == 0 { Some("daily".to_owned()) } else { Some(format!("*-*-* 0:{}:0", delay)) }
                                }
                                Period::Daily => {
                                    if delay == 0 && hour == 0 {
                                        Some("daily".to_owned())
                                    } else {
                                        Some(format!("*-*-* {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Weekly => {
                                    if delay == 0 && hour == 0 {
                                        Some("weekly".to_owned())
                                    } else {
                                        Some(format!("Mon *-*-* {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Monthly => {
                                    if delay == 0 && hour == 0 {
                                        Some("monthly".to_owned())
                                    } else {
                                        Some(format!("*-*-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Quaterly => {
                                    if delay == 0 && hour == 0 {
                                        Some("quaterly".to_owned())
                                    } else {
                                        Some(format!("*-1,4,7,10-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Biannually => {
                                    if delay == 0 && hour == 0 {
                                        Some("semiannually".to_owned())
                                    } else {
                                        Some(format!("*-1,7-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Yearly => {
                                    if delay == 0 && hour == 0 {
                                        Some("yearly".to_owned())
                                    } else {
                                        Some(format!("*-1-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Days(days) => {
                                    // workaround for anacrontab
                                    if days > 31 {
                                        Some(format!("*-1/{}-1 {}:{}:0", days / 30, hour, delay))
                                    } else {
                                        Some(format!("*-*-1/{} {}:{}:0", days, hour, delay))
                                    }
                                }
                            }
                        })
                        .or_else(|| {
                            entry.calendar().and_then(|cal| {
                                let Calendar { ref dows, ref days, ref mons, ref hrs, ref mins } = *cal;

                                Some(format!("{} *-{}-{} {}:{}:00",
                                             linearize(&**dows, "", ToString::to_string),
                                             linearize(&**mons, "*", |&mon| (mon as u8).to_string()),
                                             linearize(&**days, "*", ToString::to_string),
                                             linearize(&**hrs, "*", ToString::to_string),
                                             linearize(&**mins, "*", ToString::to_string)))
                            })
                        });

    if daemon_reload && schedule.is_none() {
        warn!("skipping job from {}: \"{}\"", path.display(), entry);
        return Ok(());
    }

    if let Some(cmd) = entry.command() {

        // make sure we know the user
        let user = try!(entry.user()
                             .and_then(get_entry_by_name)
                             .or_else(|| get_entry_by_uid(owner))
                             .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "unknown user")));

        // generate unique cron job id
        let mut md5ctx = ::md5::Context::new();
        md5ctx.consume(path.as_os_str().as_bytes());
        if let Some(ref schedule) = schedule {
            md5ctx.consume(schedule.as_bytes());
        }
        md5ctx.consume(cmd.as_bytes());
        let md5hex = tohex(&md5ctx.compute());

        // create service and timer unit names
        let service_unit_name = format!("cron-{}.service", md5hex);
        let timer_unit_name = format!("cron-{}.timer", md5hex);

        // unit paths
        let service_unit_path = dstdir.join(&service_unit_name);
        let timer_unit_path = dstdir.join(&timer_unit_name);

        // make sure cron.target.wants dir exists
        let cron_target_wants_path = dstdir.join("cron.target.wants");
        try!(create_dir_all(&cron_target_wants_path));

        // process command in case it should be put into script
        let command = if metadata(cmd).map(|m| m.is_file()).unwrap_or(false) {
            cmd.to_owned()
        } else {
            let script_command_path = dstdir.join(format!("cron-{}.sh", md5hex));

            debug!("generating script {:?} from {:?}", script_command_path, path);
            {
                let mut script_command_file = try!(File::create(&script_command_path));
                try!(writeln!(script_command_file, "#!{}", shell));
                try!(writeln!(script_command_file, "{}", cmd));
            }

            let mut perms = try!(metadata(&script_command_path)).permissions();
            perms.set_mode(0o755);
            try!(set_permissions(&script_command_path, perms));
            script_command_path.to_str().unwrap().to_owned()
        };

        debug!("generating service {:?} from {:?}", service_unit_path, path);
        {
            let mut service_unit_file = try!(File::create(service_unit_path));

            try!(writeln!(service_unit_file, r###"[Unit]
Description=[Cron] "{entry}"
Documentation=man:systemd-crontab-generator(8)
RefuseManualStart=true
RefuseManualStop=true
SourcePath={source_crontab_path}"###,
                entry = entry,
                source_crontab_path = path.display(),
                ));

            if env.contains_key("MAILTO") {
                try!(writeln!(service_unit_file, "OnFailure=cron-failure@%i.service"));
            }

            if user.uid != 0 {
                try!(writeln!(service_unit_file, "Requires=systemd-user-sessions.service"));
                if !user.dir.is_empty() {
                    try!(writeln!(service_unit_file, "RequiresMountsFor={}", user.dir));
                }
            }

            try!(writeln!(service_unit_file, r###"
[Service]
Type=oneshot
IgnoreSIGPIPE=false
ExecStart={command}"###,
                command = command,
                ));

            if schedule.is_some() && delay > 0 {
                try!(writeln!(service_unit_file, "ExecStartPre=-{}/{}/boot-delay {}", LIB_DIR, PACKAGE, delay));
            }

            if user.uid != 0 {
                try!(writeln!(service_unit_file, "User={}", user.name));
            }

            if let Some(group) = entry.group() {
                try!(writeln!(service_unit_file, "Group={}", group));
            }
            if batch {
                try!(writeln!(service_unit_file, "CPUSchedulingPolicy=idle"));
                try!(writeln!(service_unit_file, "IOSchedulingClass=idle"));
            }

            if !env.is_empty() {
                for (name, value) in env.iter() {
                    try!(writeln!(service_unit_file, r#"Environment="{}={}""#, name, value));
                }
            }
        }

        debug!("generating timer {:?} from {:?}", timer_unit_path, path);
        {
            let mut timer_unit_file = try!(File::create(&timer_unit_path));

            try!(writeln!(timer_unit_file, r###"[Unit]
Description=[Timer] "{entry}"
Documentation=man:systemd-crontab-generator(8)
PartOf=cron.target
RefuseManualStart=true
RefuseManualStop=true
SourcePath={source_crontab_path}

[Timer]
Unit={service_unit_name}"###,
                entry = entry,
                source_crontab_path = path.display(),
                service_unit_name = service_unit_name,
                ));

            if cfg![feature = "persistent"] {
                try!(writeln!(timer_unit_file, "Persistent={}", persistent));
            }

            if let Some(schedule) = schedule {
                try!(writeln!(timer_unit_file, "OnCalendar={}", schedule));
            } else {
                try!(writeln!(timer_unit_file, "OnBootSec={}m", delay));
            }

            if random_delay != 1 {
                if cfg!(feature="randomized-delay") {
                    try!(writeln!(timer_unit_file, "RandomizedDelaySec={}m", random_delay));
                } else {
                    try!(writeln!(timer_unit_file, "AccuracySec={}m", random_delay));
                }
            }
        }
        try!(symlink(timer_unit_path, cron_target_wants_path.join(timer_unit_name)));
    }

    Ok(())
}

fn linearize<T, C>(input: &[Interval<T>], star: &str, conv: C) -> String
    where T: Limited,
          C: Fn(&T) -> String
{
    if input.len() == 1 && input[0] == Interval::Full(1) {
        star.to_owned()
    } else {
        let mut output = String::new();
        for part in input.iter().flat_map(|v| v.iter()).collect::<BTreeSet<_>>().iter() {
            output.push_str(&*conv(&part));
            output.push(',');
        }
        output.pop();
        output
    }
}

fn tohex(input: &[u8]) -> String {
    #[inline]
    fn hex(d: u8) -> char {
        match d {
            0...9 => (d + 0x30) as char,
            10...15 => (d + 0x57) as char,
            _ => unreachable!("unexpected value: {}", d),
        }
    }

    let mut buf = String::with_capacity(32);
    for b in input.into_iter() {
        buf.push(hex(b >> 4));
        buf.push(hex(b & 0xf));
    }
    buf
}
<commit_msg>run command in user home directory<commit_after>use std::io::{self, BufRead, Write};
use std::fs::{File, create_dir_all, metadata, set_permissions};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{MetadataExt, PermissionsExt, symlink};
use std::path::Path;
use std::collections::{BTreeMap, BTreeSet};

use cronparse::Limited;
use cronparse::crontab::{CrontabEntry, SystemCrontabEntry, UserCrontabEntry};
use cronparse::schedule::{Calendar, Period, Schedule};
use cronparse::interval::Interval;

use pgs_files::passwd::{get_entry_by_name, get_entry_by_uid};

use super::{LIB_DIR, PACKAGE, REBOOT_FILE};

pub fn generate_systemd_units(entry: CrontabEntry, env: &BTreeMap<String, String>, path: &Path, dstdir: &Path) -> io::Result<()> {
    use cronparse::crontab::CrontabEntry::*;

    info!("generating units for {}: \"{}\", {:?}", path.display(), entry, env);

    let owner = try!(metadata(path)).uid();

    let mut persistent = env.get("PERSISTENT")
                            .and_then(|v| {
                                match &**v {
                                    "yes" | "true" | "1" => Some(true),
                                    "auto" | "" => None,
                                    _ => Some(false),
                                }
                            })
                            .unwrap_or_else(|| {
                                match entry {
                                    Anacron(_) |
                                    User(UserCrontabEntry { sched: Schedule::Period(_), .. }) |
                                    System(SystemCrontabEntry { sched: Schedule::Period(_), .. }) => true,
                                    _ => false,
                                }
                            });

    let batch = env.get("BATCH")
                   .map(|v| {
                       match &**v {
                           "yes" | "true" | "1" => true,
                           _ => false,
                       }
                   })
                   .unwrap_or(false);

    let random_delay = env.get("RANDOM_DELAY").and_then(|v| v.parse::<u64>().ok()).unwrap_or(1);
    let mut delay = env.get("DELAY").and_then(|v| v.parse::<u64>().ok()).unwrap_or(0);
    let hour = env.get("START_HOURS_RANGE")
                  .and_then(|v| v.splitn(1, '-').next().and_then(|v| v.parse::<u64>().ok()))
                  .unwrap_or(0);
    let shell = env.get("SHELL").map(|v| &**v).unwrap_or("/bin/sh");
    let daemon_reload = metadata(REBOOT_FILE).map(|m| m.is_file()).unwrap_or(false);

    let schedule = entry.period()
                        .and_then(|period| {
                            match *period {
                                Period::Reboot => {
                                    persistent = false;
                                    if delay == 0 {
                                        delay = 1;
                                    }
                                    None
                                }
                                Period::Minutely => {
                                    persistent = false;
                                    Some("minutely".to_owned())
                                }
                                Period::Hourly => if delay == 0 { Some("hourly".to_owned()) } else { Some(format!("*-*-* *:{}:0", delay)) },
                                Period::Midnight => {
                                    if delay == 0 { Some("daily".to_owned()) } else { Some(format!("*-*-* 0:{}:0", delay)) }
                                }
                                Period::Daily => {
                                    if delay == 0 && hour == 0 {
                                        Some("daily".to_owned())
                                    } else {
                                        Some(format!("*-*-* {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Weekly => {
                                    if delay == 0 && hour == 0 {
                                        Some("weekly".to_owned())
                                    } else {
                                        Some(format!("Mon *-*-* {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Monthly => {
                                    if delay == 0 && hour == 0 {
                                        Some("monthly".to_owned())
                                    } else {
                                        Some(format!("*-*-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Quaterly => {
                                    if delay == 0 && hour == 0 {
                                        Some("quaterly".to_owned())
                                    } else {
                                        Some(format!("*-1,4,7,10-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Biannually => {
                                    if delay == 0 && hour == 0 {
                                        Some("semiannually".to_owned())
                                    } else {
                                        Some(format!("*-1,7-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Yearly => {
                                    if delay == 0 && hour == 0 {
                                        Some("yearly".to_owned())
                                    } else {
                                        Some(format!("*-1-1 {}:{}:0", hour, delay))
                                    }
                                }
                                Period::Days(days) => {
                                    // workaround for anacrontab
                                    if days > 31 {
                                        Some(format!("*-1/{}-1 {}:{}:0", days / 30, hour, delay))
                                    } else {
                                        Some(format!("*-*-1/{} {}:{}:0", days, hour, delay))
                                    }
                                }
                            }
                        })
                        .or_else(|| {
                            entry.calendar().and_then(|cal| {
                                let Calendar { ref dows, ref days, ref mons, ref hrs, ref mins } = *cal;

                                Some(format!("{} *-{}-{} {}:{}:00",
                                             linearize(&**dows, "", ToString::to_string),
                                             linearize(&**mons, "*", |&mon| (mon as u8).to_string()),
                                             linearize(&**days, "*", ToString::to_string),
                                             linearize(&**hrs, "*", ToString::to_string),
                                             linearize(&**mins, "*", ToString::to_string)))
                            })
                        });

    if daemon_reload && schedule.is_none() {
        warn!("skipping job from {}: \"{}\"", path.display(), entry);
        return Ok(());
    }

    if let Some(cmd) = entry.command() {

        // make sure we know the user
        let user = try!(entry.user()
                             .and_then(get_entry_by_name)
                             .or_else(|| get_entry_by_uid(owner))
                             .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "unknown user")));

        // generate unique cron job id
        let mut md5ctx = ::md5::Context::new();
        md5ctx.consume(path.as_os_str().as_bytes());
        if let Some(ref schedule) = schedule {
            md5ctx.consume(schedule.as_bytes());
        }
        md5ctx.consume(cmd.as_bytes());
        let md5hex = tohex(&md5ctx.compute());

        // create service and timer unit names
        let service_unit_name = format!("cron-{}.service", md5hex);
        let timer_unit_name = format!("cron-{}.timer", md5hex);

        // unit paths
        let service_unit_path = dstdir.join(&service_unit_name);
        let timer_unit_path = dstdir.join(&timer_unit_name);

        // make sure cron.target.wants dir exists
        let cron_target_wants_path = dstdir.join("cron.target.wants");
        try!(create_dir_all(&cron_target_wants_path));

        // process command in case it should be put into script
        let command = if metadata(cmd).map(|m| m.is_file()).unwrap_or(false) {
            cmd.to_owned()
        } else {
            let script_command_path = dstdir.join(format!("cron-{}.sh", md5hex));

            debug!("generating script {:?} from {:?}", script_command_path, path);
            {
                let mut script_command_file = try!(File::create(&script_command_path));
                try!(writeln!(script_command_file, "#!{}", shell));
                try!(writeln!(script_command_file, "{}", cmd));
            }

            let mut perms = try!(metadata(&script_command_path)).permissions();
            perms.set_mode(0o755);
            try!(set_permissions(&script_command_path, perms));
            script_command_path.to_str().unwrap().to_owned()
        };

        debug!("generating service {:?} from {:?}", service_unit_path, path);
        {
            let mut service_unit_file = try!(File::create(service_unit_path));

            try!(writeln!(service_unit_file, r###"[Unit]
Description=[Cron] "{entry}"
Documentation=man:systemd-crontab-generator(8)
RefuseManualStart=true
RefuseManualStop=true
SourcePath={source_crontab_path}"###,
                entry = entry,
                source_crontab_path = path.display(),
                ));

            if env.contains_key("MAILTO") {
                try!(writeln!(service_unit_file, "OnFailure=cron-failure@%i.service"));
            }

            if user.uid != 0 {
                try!(writeln!(service_unit_file, "Requires=systemd-user-sessions.service"));
                if !user.dir.is_empty() {
                    try!(writeln!(service_unit_file, "RequiresMountsFor={}", user.dir));
                }
            }

            try!(writeln!(service_unit_file, r###"
[Service]
Type=oneshot
IgnoreSIGPIPE=false
ExecStart={command}"###,
                command = command,
                ));

            if schedule.is_some() && delay > 0 {
                try!(writeln!(service_unit_file, "ExecStartPre=-{}/{}/boot-delay {}", LIB_DIR, PACKAGE, delay));
            }

            if user.uid != 0 {
                try!(writeln!(service_unit_file, "User={}", user.name));
                try!(writeln!(service_unit_file, "WorkingDirectory=~"));
            }

            if let Some(group) = entry.group() {
                try!(writeln!(service_unit_file, "Group={}", group));
            }
            if batch {
                try!(writeln!(service_unit_file, "CPUSchedulingPolicy=idle"));
                try!(writeln!(service_unit_file, "IOSchedulingClass=idle"));
            }

            if !env.is_empty() {
                for (name, value) in env.iter() {
                    try!(writeln!(service_unit_file, r#"Environment="{}={}""#, name, value));
                }
            }
        }

        debug!("generating timer {:?} from {:?}", timer_unit_path, path);
        {
            let mut timer_unit_file = try!(File::create(&timer_unit_path));

            try!(writeln!(timer_unit_file, r###"[Unit]
Description=[Timer] "{entry}"
Documentation=man:systemd-crontab-generator(8)
PartOf=cron.target
RefuseManualStart=true
RefuseManualStop=true
SourcePath={source_crontab_path}

[Timer]
Unit={service_unit_name}"###,
                entry = entry,
                source_crontab_path = path.display(),
                service_unit_name = service_unit_name,
                ));

            if cfg![feature = "persistent"] {
                try!(writeln!(timer_unit_file, "Persistent={}", persistent));
            }

            if let Some(schedule) = schedule {
                try!(writeln!(timer_unit_file, "OnCalendar={}", schedule));
            } else {
                try!(writeln!(timer_unit_file, "OnBootSec={}m", delay));
            }

            if random_delay != 1 {
                if cfg!(feature="randomized-delay") {
                    try!(writeln!(timer_unit_file, "RandomizedDelaySec={}m", random_delay));
                } else {
                    try!(writeln!(timer_unit_file, "AccuracySec={}m", random_delay));
                }
            }
        }
        try!(symlink(timer_unit_path, cron_target_wants_path.join(timer_unit_name)));
    }

    Ok(())
}

fn linearize<T, C>(input: &[Interval<T>], star: &str, conv: C) -> String
    where T: Limited,
          C: Fn(&T) -> String
{
    if input.len() == 1 && input[0] == Interval::Full(1) {
        star.to_owned()
    } else {
        let mut output = String::new();
        for part in input.iter().flat_map(|v| v.iter()).collect::<BTreeSet<_>>().iter() {
            output.push_str(&*conv(&part));
            output.push(',');
        }
        output.pop();
        output
    }
}

fn tohex(input: &[u8]) -> String {
    #[inline]
    fn hex(d: u8) -> char {
        match d {
            0...9 => (d + 0x30) as char,
            10...15 => (d + 0x57) as char,
            _ => unreachable!("unexpected value: {}", d),
        }
    }

    let mut buf = String::with_capacity(32);
    for b in input.into_iter() {
        buf.push(hex(b >> 4));
        buf.push(hex(b & 0xf));
    }
    buf
}
<|endoftext|>


<commit_before>use std::collections::HashSet;

/// To prevent modification time changing
use std::{
    fs::File,
    io,
    io::{Read, Write},
    path::PathBuf,
};

/// Implement write cache in memory, and update file only if necessary
pub struct FileWriteCache {
    cnt: Vec<u8>,
    path: PathBuf,
    /// To prevent redifining some items,
    /// also it is possible to scan `cnt`, but it is possible to add
    /// user defined comments into generated file, so this is more robust
    #[allow(dead_code)]
    already_defined_items: HashSet<String>,
}

impl FileWriteCache {
    pub fn new<P: Into<PathBuf>>(p: P) -> FileWriteCache {
        FileWriteCache {
            cnt: vec![],
            path: p.into(),
            already_defined_items: HashSet::default(),
        }
    }

    pub fn update_file_if_necessary(self) -> Result<(), io::Error> {
        if let Ok(mut f) = File::open(&self.path) {
            let mut cur_cnt = vec![];
            f.read_to_end(&mut cur_cnt)?;
            if cur_cnt == self.cnt {
                return Ok(());
            }
        }
        let mut f = File::create(&self.path)?;
        f.write_all(&self.cnt)?;
        Ok(())
    }

    #[allow(dead_code)]
    pub fn define_item<S: Into<String>>(&mut self, item: S) {
        self.already_defined_items.insert(item.into());
    }

    #[allow(dead_code)]
    pub fn is_item_defined(&self, item: &str) -> bool {
        self.already_defined_items.contains(item)
    }
}

impl io::Write for FileWriteCache {
    fn write(&mut self, data: &[u8]) -> Result<usize, io::Error> {
        self.cnt.extend_from_slice(data);
        Ok(data.len())
    }
    fn flush(&mut self) -> Result<(), io::Error> {
        Ok(())
    }
}
<commit_msg>refactoring: cleanups<commit_after>/// To prevent modification time changing
use std::{
    collections::HashSet,
    fs::File,
    io,
    io::{Read, Write},
    path::PathBuf,
};

/// Implement write cache in memory, and update file only if necessary
pub struct FileWriteCache {
    cnt: Vec<u8>,
    path: PathBuf,
    /// To prevent redifining some items,
    /// also it is possible to scan `cnt`, but it is possible to add
    /// user defined comments into generated file, so this is more robust
    #[allow(dead_code)]
    already_defined_items: HashSet<String>,
}

impl FileWriteCache {
    pub fn new<P: Into<PathBuf>>(p: P) -> FileWriteCache {
        FileWriteCache {
            cnt: vec![],
            path: p.into(),
            already_defined_items: HashSet::default(),
        }
    }

    pub fn update_file_if_necessary(self) -> Result<(), io::Error> {
        if let Ok(mut f) = File::open(&self.path) {
            let mut cur_cnt = vec![];
            f.read_to_end(&mut cur_cnt)?;
            if cur_cnt == self.cnt {
                return Ok(());
            }
        }
        let mut f = File::create(&self.path)?;
        f.write_all(&self.cnt)?;
        Ok(())
    }

    #[allow(dead_code)]
    pub fn define_item<S: Into<String>>(&mut self, item: S) {
        self.already_defined_items.insert(item.into());
    }

    #[allow(dead_code)]
    pub fn is_item_defined(&self, item: &str) -> bool {
        self.already_defined_items.contains(item)
    }
}

impl io::Write for FileWriteCache {
    fn write(&mut self, data: &[u8]) -> Result<usize, io::Error> {
        self.cnt.extend_from_slice(data);
        Ok(data.len())
    }
    fn flush(&mut self) -> Result<(), io::Error> {
        Ok(())
    }
}
<|endoftext|>


<commit_before>use callback::Callable;
use frame::Frameable;
use label::{ FontSize, Labelable };
use color::{ Color, Colorable };
use dimensions::Dimensions;
use mouse::Mouse;
use point::Point;
use rectangle;
use ui_context::{ UIID, UiContext };
use vecmath::vec2_add;
use graphics::Graphics;
use graphics::character::CharacterCache;
use widget::Widget;

/// Tuple / Callback params.
pub type Idx = usize;
pub type Len = usize;

/// Represents the state of the menu.
#[derive(PartialEq, Clone, Copy)]
pub enum State {
    Closed(DrawState),
    Open(DrawState),
}

/// Represents the state of the DropDownList widget.
#[derive(PartialEq, Clone, Copy)]
pub enum DrawState {
    Normal,
    Highlighted(Idx, Len),
    Clicked(Idx, Len),
}

impl DrawState {
    /// Translate the DropDownList's DrawState to the equivalent rectangle::State.
    fn as_rect_state(&self) -> rectangle::State {
        match self {
            &DrawState::Normal => rectangle::State::Normal,
            &DrawState::Highlighted(_, _) => rectangle::State::Highlighted,
            &DrawState::Clicked(_, _) => rectangle::State::Clicked,
        }
    }
}

impl State {
    /// Translate the DropDownList's State to the equivalent rectangle::State.
    fn as_rect_state(&self) -> rectangle::State {
        match self {
            &State::Open(draw_state) | &State::Closed(draw_state) => draw_state.as_rect_state(),
        }
    }
}

widget_fns!(DropDownList, State, Widget::DropDownList(State::Closed(DrawState::Normal)));

/// Is the cursor currently over the widget? If so which item?
fn is_over(pos: Point,
           mouse_pos: Point,
           dim: Dimensions,
           state: State,
           len: Len) -> Option<Idx> {
    match state {
        State::Closed(_) => {
            match rectangle::is_over(pos, mouse_pos, dim) {
                false => None,
                true => Some(0),
            }
        },
        State::Open(_) => {
            let total_h = dim[1] * len as f64;
            match rectangle::is_over(pos, mouse_pos, [dim[0], total_h]) {
                false => None,
                true => Some((((mouse_pos[1] - pos[1]) / total_h) * len as f64) as usize),
            }
        },
    }
}

/// Determine and return the new State by comparing the mouse state
/// and position to the previous State.
fn get_new_state(is_over_idx: Option<Idx>,
                 len: Len,
                 state: State,
                 mouse: Mouse) -> State {
    use self::DrawState::{Normal, Clicked, Highlighted};
    use mouse::ButtonState::{Down, Up};
    match state {
        State::Closed(draw_state) => {
            match is_over_idx {
                Some(_) => {
                    match (draw_state, mouse.left) {
                        (Normal,            Down) => State::Closed(Normal),
                        (Normal,            Up)   |
                        (Highlighted(_, _), Up)   => State::Closed(Highlighted(0, len)),
                        (Highlighted(_, _), Down) => State::Closed(Clicked(0, len)),
                        (Clicked(_, _),     Down) => State::Closed(Clicked(0, len)),
                        (Clicked(_, _),     Up)   => State::Open(Normal),
                    }
                },
                None => State::Closed(Normal),
            }
        },
        State::Open(draw_state) => {
            match is_over_idx {
                Some(idx) => {
                    match (draw_state, mouse.left) {
                        (Normal,            Down) => State::Open(Normal),
                        (Normal,            Up)   |
                        (Highlighted(_, _), Up)   => State::Open(Highlighted(idx, len)),
                        (Highlighted(_, _), Down) => State::Open(Clicked(idx, len)),
                        (Clicked(p_idx, _), Down) => State::Open(Clicked(p_idx, len)),
                        (Clicked(_, _),     Up)   => State::Closed(Normal),
                    }
                },
                None => {
                    match (draw_state, mouse.left) {
                        (Highlighted(p_idx, _), Up) => State::Open(Highlighted(p_idx, len)),
                        _ => State::Closed(Normal),
                    }
                },
            }
        }
    }
}

/// A context on which the builder pattern can be implemented.
pub struct DropDownList<'a, F> {
    ui_id: UIID,
    strings: &'a mut Vec<String>,
    selected: &'a mut Option<Idx>,
    pos: Point,
    dim: Dimensions,
    maybe_callback: Option<F>,
    maybe_color: Option<Color>,
    maybe_frame: Option<f64>,
    maybe_frame_color: Option<Color>,
    maybe_label: Option<&'a str>,
    maybe_label_color: Option<Color>,
    maybe_label_font_size: Option<u32>,
}

impl<'a, F> DropDownList<'a, F> {
    pub fn new(ui_id: UIID,
               strings: &'a mut Vec<String>,
               selected: &'a mut Option<Idx>) -> DropDownList<'a, F> {
        DropDownList {
            ui_id: ui_id,
            strings: strings,
            selected: selected,
            pos: [0.0, 0.0],
            dim: [128.0, 32.0],
            maybe_callback: None,
            maybe_color: None,
            maybe_frame: None,
            maybe_frame_color: None,
            maybe_label: None,
            maybe_label_color: None,
            maybe_label_font_size: None,
        }
    }
}

impl<'a, F> Colorable for DropDownList<'a, F> {
    fn color(mut self, color: Color) -> Self {
        self.maybe_color = Some(color);
        self
    }
}

impl<'a, F> Frameable for DropDownList<'a, F> {
    fn frame(mut self, width: f64) -> Self {
        self.maybe_frame = Some(width);
        self
    }
    fn frame_color(mut self, color: Color) -> Self {
        self.maybe_frame_color = Some(color);
        self
    }
}

impl<'a, F> Callable<F> for DropDownList<'a, F> {
    fn callback(mut self, cb: F) -> Self {
        self.maybe_callback = Some(cb);
        self
    }
}

impl<'a, F> Labelable<'a> for DropDownList<'a, F>
{
    fn label(mut self, text: &'a str) -> Self {
        self.maybe_label = Some(text);
        self
    }

    fn label_color(mut self, color: Color) -> Self {
        self.maybe_label_color = Some(color);
        self
    }

    fn label_font_size(mut self, size: FontSize) -> Self {
        self.maybe_label_font_size = Some(size);
        self
    }
}

/*
quack! {
    list: DropDownList['a, F]
    get:
        fn () -> Size [] { Size(list.dim) }
        fn () -> DefaultWidgetState [] {
            DefaultWidgetState(
                Widget::DropDownList(State::Closed(DrawState::Normal))
            )
        }
        fn () -> Id [] { Id(list.ui_id) }
    set:
        fn (val: Position) [] { list.pos = val.0 }
        fn (val: Size) [] { list.dim = val.0 }
    action:
}
*/

impl<'a, F> ::draw::Drawable for DropDownList<'a, F>
    where
        F: FnMut(&mut Option<Idx>, Idx, String) + 'a
{

    fn draw<B, C>(&mut self, uic: &mut UiContext<C>, graphics: &mut B)
        where
            B: Graphics<Texture = <C as CharacterCache>::Texture>,
            C: CharacterCache
    {

        let state = *get_state(uic, self.ui_id);
        let mouse = uic.get_mouse_state();
        let is_over_idx = is_over(self.pos, mouse.pos, self.dim, state, self.strings.len());
        let new_state = get_new_state(is_over_idx, self.strings.len(), state, mouse);

        let sel = match *self.selected {
            Some(idx) if idx < self.strings.len() => { Some(idx) },
            _ => None,
        };
        let color = self.maybe_color.unwrap_or(uic.theme.shape_color);
        let t_size = self.maybe_label_font_size.unwrap_or(uic.theme.font_size_medium);
        let t_color = self.maybe_label_color.unwrap_or(uic.theme.label_color);

        // Call the `callback` closure if mouse was released
        // on one of the DropDownMenu items.
        match (state, new_state) {
            (State::Open(o_d_state), State::Closed(c_d_state)) => {
                match (o_d_state, c_d_state) {
                    (DrawState::Clicked(idx, _), DrawState::Normal) => {
                        match self.maybe_callback {
                            Some(ref mut callback) => (*callback)(self.selected, idx, (*self.strings)[idx].clone()),
                            None => (),
                        }
                    }, _ => (),
                }
            }, _ => (),
        }

        let frame_w = self.maybe_frame.unwrap_or(uic.theme.frame_width);
        let maybe_frame = match frame_w > 0.0 {
            true => Some((frame_w, self.maybe_frame_color.unwrap_or(uic.theme.frame_color))),
            false => None,
        };

        match new_state {

            State::Closed(_) => {
                let rect_state = new_state.as_rect_state();
                let text = match sel {
                    Some(idx) => &(*self.strings)[idx][..],
                    None => match self.maybe_label {
                        Some(text) => text,
                        None => &(*self.strings)[0][..],
                    },
                };
                rectangle::draw_with_centered_label(
                    uic.win_w, uic.win_h, graphics, uic, rect_state,
                    self.pos, self.dim, maybe_frame, color,
                    text, t_size, t_color
                )
            },

            State::Open(draw_state) => {
                for (i, string) in self.strings.iter().enumerate() {
                    let rect_state = match sel {
                        None => {
                            match draw_state {
                                DrawState::Normal => rectangle::State::Normal,
                                DrawState::Highlighted(idx, _) => {
                                    if i == idx { rectangle::State::Highlighted }
                                    else { rectangle::State::Normal }
                                },
                                DrawState::Clicked(idx, _) => {
                                    if i == idx { rectangle::State::Clicked }
                                    else { rectangle::State::Normal }
                                },
                            }
                        },
                        Some(sel_idx) => {
                            if sel_idx == i { rectangle::State::Clicked }
                            else {
                                match draw_state {
                                    DrawState::Normal => rectangle::State::Normal,
                                    DrawState::Highlighted(idx, _) => {
                                        if i == idx { rectangle::State::Highlighted }
                                        else { rectangle::State::Normal }
                                    },
                                    DrawState::Clicked(idx, _) => {
                                        if i == idx { rectangle::State::Clicked }
                                        else { rectangle::State::Normal }
                                    },
                                }
                            }
                        },
                    };
                    let idx_y = self.dim[1] * i as f64 - i as f64 * frame_w;
                    let idx_pos = vec2_add(self.pos, [0.0, idx_y]);
                    rectangle::draw_with_centered_label(
                        uic.win_w, uic.win_h, graphics, uic, rect_state, idx_pos,
                        self.dim, maybe_frame, color, &string,
                        t_size, t_color
                    )
                }
            },

        }

        set_state(uic, self.ui_id, Widget::DropDownList(new_state), self.pos, self.dim);

    }
}
<commit_msg>Implemented `Positionable` for `DropDownList`<commit_after>use callback::Callable;
use frame::Frameable;
use label::{ FontSize, Labelable };
use color::{ Color, Colorable };
use dimensions::Dimensions;
use mouse::Mouse;
use point::Point;
use position::Positionable;
use rectangle;
use ui_context::{ UIID, UiContext };
use vecmath::vec2_add;
use graphics::Graphics;
use graphics::character::CharacterCache;
use widget::Widget;

/// Tuple / Callback params.
pub type Idx = usize;
pub type Len = usize;

/// Represents the state of the menu.
#[derive(PartialEq, Clone, Copy)]
pub enum State {
    Closed(DrawState),
    Open(DrawState),
}

/// Represents the state of the DropDownList widget.
#[derive(PartialEq, Clone, Copy)]
pub enum DrawState {
    Normal,
    Highlighted(Idx, Len),
    Clicked(Idx, Len),
}

impl DrawState {
    /// Translate the DropDownList's DrawState to the equivalent rectangle::State.
    fn as_rect_state(&self) -> rectangle::State {
        match self {
            &DrawState::Normal => rectangle::State::Normal,
            &DrawState::Highlighted(_, _) => rectangle::State::Highlighted,
            &DrawState::Clicked(_, _) => rectangle::State::Clicked,
        }
    }
}

impl State {
    /// Translate the DropDownList's State to the equivalent rectangle::State.
    fn as_rect_state(&self) -> rectangle::State {
        match self {
            &State::Open(draw_state) | &State::Closed(draw_state) => draw_state.as_rect_state(),
        }
    }
}

widget_fns!(DropDownList, State, Widget::DropDownList(State::Closed(DrawState::Normal)));

/// Is the cursor currently over the widget? If so which item?
fn is_over(pos: Point,
           mouse_pos: Point,
           dim: Dimensions,
           state: State,
           len: Len) -> Option<Idx> {
    match state {
        State::Closed(_) => {
            match rectangle::is_over(pos, mouse_pos, dim) {
                false => None,
                true => Some(0),
            }
        },
        State::Open(_) => {
            let total_h = dim[1] * len as f64;
            match rectangle::is_over(pos, mouse_pos, [dim[0], total_h]) {
                false => None,
                true => Some((((mouse_pos[1] - pos[1]) / total_h) * len as f64) as usize),
            }
        },
    }
}

/// Determine and return the new State by comparing the mouse state
/// and position to the previous State.
fn get_new_state(is_over_idx: Option<Idx>,
                 len: Len,
                 state: State,
                 mouse: Mouse) -> State {
    use self::DrawState::{Normal, Clicked, Highlighted};
    use mouse::ButtonState::{Down, Up};
    match state {
        State::Closed(draw_state) => {
            match is_over_idx {
                Some(_) => {
                    match (draw_state, mouse.left) {
                        (Normal,            Down) => State::Closed(Normal),
                        (Normal,            Up)   |
                        (Highlighted(_, _), Up)   => State::Closed(Highlighted(0, len)),
                        (Highlighted(_, _), Down) => State::Closed(Clicked(0, len)),
                        (Clicked(_, _),     Down) => State::Closed(Clicked(0, len)),
                        (Clicked(_, _),     Up)   => State::Open(Normal),
                    }
                },
                None => State::Closed(Normal),
            }
        },
        State::Open(draw_state) => {
            match is_over_idx {
                Some(idx) => {
                    match (draw_state, mouse.left) {
                        (Normal,            Down) => State::Open(Normal),
                        (Normal,            Up)   |
                        (Highlighted(_, _), Up)   => State::Open(Highlighted(idx, len)),
                        (Highlighted(_, _), Down) => State::Open(Clicked(idx, len)),
                        (Clicked(p_idx, _), Down) => State::Open(Clicked(p_idx, len)),
                        (Clicked(_, _),     Up)   => State::Closed(Normal),
                    }
                },
                None => {
                    match (draw_state, mouse.left) {
                        (Highlighted(p_idx, _), Up) => State::Open(Highlighted(p_idx, len)),
                        _ => State::Closed(Normal),
                    }
                },
            }
        }
    }
}

/// A context on which the builder pattern can be implemented.
pub struct DropDownList<'a, F> {
    ui_id: UIID,
    strings: &'a mut Vec<String>,
    selected: &'a mut Option<Idx>,
    pos: Point,
    dim: Dimensions,
    maybe_callback: Option<F>,
    maybe_color: Option<Color>,
    maybe_frame: Option<f64>,
    maybe_frame_color: Option<Color>,
    maybe_label: Option<&'a str>,
    maybe_label_color: Option<Color>,
    maybe_label_font_size: Option<u32>,
}

impl<'a, F> DropDownList<'a, F> {
    pub fn new(ui_id: UIID,
               strings: &'a mut Vec<String>,
               selected: &'a mut Option<Idx>) -> DropDownList<'a, F> {
        DropDownList {
            ui_id: ui_id,
            strings: strings,
            selected: selected,
            pos: [0.0, 0.0],
            dim: [128.0, 32.0],
            maybe_callback: None,
            maybe_color: None,
            maybe_frame: None,
            maybe_frame_color: None,
            maybe_label: None,
            maybe_label_color: None,
            maybe_label_font_size: None,
        }
    }
}

impl<'a, F> Colorable for DropDownList<'a, F> {
    fn color(mut self, color: Color) -> Self {
        self.maybe_color = Some(color);
        self
    }
}

impl<'a, F> Frameable for DropDownList<'a, F> {
    fn frame(mut self, width: f64) -> Self {
        self.maybe_frame = Some(width);
        self
    }
    fn frame_color(mut self, color: Color) -> Self {
        self.maybe_frame_color = Some(color);
        self
    }
}

impl<'a, F> Callable<F> for DropDownList<'a, F> {
    fn callback(mut self, cb: F) -> Self {
        self.maybe_callback = Some(cb);
        self
    }
}

impl<'a, F> Labelable<'a> for DropDownList<'a, F>
{
    fn label(mut self, text: &'a str) -> Self {
        self.maybe_label = Some(text);
        self
    }

    fn label_color(mut self, color: Color) -> Self {
        self.maybe_label_color = Some(color);
        self
    }

    fn label_font_size(mut self, size: FontSize) -> Self {
        self.maybe_label_font_size = Some(size);
        self
    }
}

impl<'a, F> Positionable for DropDownList<'a, F> {
    fn point(mut self, pos: Point) -> Self {
        self.pos = pos;
        self
    }
}

/*
quack! {
    list: DropDownList['a, F]
    get:
        fn () -> Size [] { Size(list.dim) }
        fn () -> DefaultWidgetState [] {
            DefaultWidgetState(
                Widget::DropDownList(State::Closed(DrawState::Normal))
            )
        }
        fn () -> Id [] { Id(list.ui_id) }
    set:
        fn (val: Position) [] { list.pos = val.0 }
        fn (val: Size) [] { list.dim = val.0 }
    action:
}
*/

impl<'a, F> ::draw::Drawable for DropDownList<'a, F>
    where
        F: FnMut(&mut Option<Idx>, Idx, String) + 'a
{

    fn draw<B, C>(&mut self, uic: &mut UiContext<C>, graphics: &mut B)
        where
            B: Graphics<Texture = <C as CharacterCache>::Texture>,
            C: CharacterCache
    {

        let state = *get_state(uic, self.ui_id);
        let mouse = uic.get_mouse_state();
        let is_over_idx = is_over(self.pos, mouse.pos, self.dim, state, self.strings.len());
        let new_state = get_new_state(is_over_idx, self.strings.len(), state, mouse);

        let sel = match *self.selected {
            Some(idx) if idx < self.strings.len() => { Some(idx) },
            _ => None,
        };
        let color = self.maybe_color.unwrap_or(uic.theme.shape_color);
        let t_size = self.maybe_label_font_size.unwrap_or(uic.theme.font_size_medium);
        let t_color = self.maybe_label_color.unwrap_or(uic.theme.label_color);

        // Call the `callback` closure if mouse was released
        // on one of the DropDownMenu items.
        match (state, new_state) {
            (State::Open(o_d_state), State::Closed(c_d_state)) => {
                match (o_d_state, c_d_state) {
                    (DrawState::Clicked(idx, _), DrawState::Normal) => {
                        match self.maybe_callback {
                            Some(ref mut callback) => (*callback)(self.selected, idx, (*self.strings)[idx].clone()),
                            None => (),
                        }
                    }, _ => (),
                }
            }, _ => (),
        }

        let frame_w = self.maybe_frame.unwrap_or(uic.theme.frame_width);
        let maybe_frame = match frame_w > 0.0 {
            true => Some((frame_w, self.maybe_frame_color.unwrap_or(uic.theme.frame_color))),
            false => None,
        };

        match new_state {

            State::Closed(_) => {
                let rect_state = new_state.as_rect_state();
                let text = match sel {
                    Some(idx) => &(*self.strings)[idx][..],
                    None => match self.maybe_label {
                        Some(text) => text,
                        None => &(*self.strings)[0][..],
                    },
                };
                rectangle::draw_with_centered_label(
                    uic.win_w, uic.win_h, graphics, uic, rect_state,
                    self.pos, self.dim, maybe_frame, color,
                    text, t_size, t_color
                )
            },

            State::Open(draw_state) => {
                for (i, string) in self.strings.iter().enumerate() {
                    let rect_state = match sel {
                        None => {
                            match draw_state {
                                DrawState::Normal => rectangle::State::Normal,
                                DrawState::Highlighted(idx, _) => {
                                    if i == idx { rectangle::State::Highlighted }
                                    else { rectangle::State::Normal }
                                },
                                DrawState::Clicked(idx, _) => {
                                    if i == idx { rectangle::State::Clicked }
                                    else { rectangle::State::Normal }
                                },
                            }
                        },
                        Some(sel_idx) => {
                            if sel_idx == i { rectangle::State::Clicked }
                            else {
                                match draw_state {
                                    DrawState::Normal => rectangle::State::Normal,
                                    DrawState::Highlighted(idx, _) => {
                                        if i == idx { rectangle::State::Highlighted }
                                        else { rectangle::State::Normal }
                                    },
                                    DrawState::Clicked(idx, _) => {
                                        if i == idx { rectangle::State::Clicked }
                                        else { rectangle::State::Normal }
                                    },
                                }
                            }
                        },
                    };
                    let idx_y = self.dim[1] * i as f64 - i as f64 * frame_w;
                    let idx_pos = vec2_add(self.pos, [0.0, idx_y]);
                    rectangle::draw_with_centered_label(
                        uic.win_w, uic.win_h, graphics, uic, rect_state, idx_pos,
                        self.dim, maybe_frame, color, &string,
                        t_size, t_color
                    )
                }
            },

        }

        set_state(uic, self.ui_id, Widget::DropDownList(new_state), self.pos, self.dim);

    }
}
<|endoftext|>


<commit_before>// vim: tw=80
//#![deny(missing_docs)]

//! Examples of mock objects and their generated methods.
//!
//! This crate only exists to document the autogenerated methods of the
//! [`Mockall`](https://docs.rs/mockall/latest/mockall)
//! crate.  You should never depend on this crate.
//

#[cfg(doc)]
use mockall::*;

/// A basic trait with several kinds of method.
///
/// It is mocked by the [`MockFoo`](struct.MockFoo.html) struct.
#[cfg(doc)]
#[automock]
pub trait Foo {
    /// A method with a `'static` return type
    fn foo(&self, x: i32, y: i16) -> i32;

    /// A method returning a reference
    fn bar(&self, x: i32) -> &i32;

    /// A method returning a mutable reference
    fn baz(&mut self, x: i32) -> &mut i32;

    /// A method returning a `'static` reference
    fn bean(&self) -> &'static i32;

    /// A static method
    fn bang(x: i32) -> i32;
}

#[cfg(doc)]
#[automock(mod mock_ffi;)]
extern "C" {
    /// A foreign "C" function
    pub fn ffi_func();
}
<commit_msg>Add a use of mock! in mockall_examples<commit_after>// vim: tw=80
#![deny(missing_docs)]

//! Examples of mock objects and their generated methods.
//!
//! This crate only exists to document the autogenerated methods of the
//! [`Mockall`](https://docs.rs/mockall/latest/mockall)
//! crate.  You should never depend on this crate.
//

#[cfg(doc)]
use mockall::*;

/// A basic trait with several kinds of method.
///
/// It is mocked by the [`MockFoo`](struct.MockFoo.html) struct.
#[cfg(doc)]
#[automock]
pub trait Foo {
    /// A method with a `'static` return type
    fn foo(&self, x: i32, y: i16) -> i32;

    /// A method returning a reference
    fn bar(&self, x: i32) -> &i32;

    /// A method returning a mutable reference
    fn baz(&mut self, x: i32) -> &mut i32;

    /// A method returning a `'static` reference
    fn bean(&self) -> &'static i32;

    /// A static method
    fn bang(x: i32) -> i32;
}

/// A trait implemented by a Struct we want to mock
pub trait Bah {
    /// Some trait method
    fn bah(&self);
}

#[cfg(doc)]
mock! {
    /// structs can be mocked with `mock!`
    ///
    /// Their mock methods have an identical API to the methods generated by
    /// `#[automock]`
    pub Boo {
        /// A method on a struct
        fn boo(&self);
    }
    /// An implementation of a trait on a mocked struct
    trait Bah {
        fn bah(&self);
    }
}

#[cfg(doc)]
#[automock(mod mock_ffi;)]
extern "C" {
    /// A foreign "C" function
    pub fn ffi_func();
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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
//
//    https://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.

use super::bridge_converter::{Api, Use};
use crate::{
    additional_cpp_generator::AdditionalNeed,
    types::{make_ident, Namespace},
};
use std::collections::HashSet;
use syn::{parse_quote, ForeignItem};

/// Adds items which we always add, cos they're useful.
/// Any APIs or techniques which do not involve actual C++ interop
/// shouldn't go here, but instead should go into the main autocxx
/// src/lib.rs.
pub(crate) fn generate_utilities(apis: &mut Vec<Api>) {
    // Unless we've been specifically asked not to do so, we always
    // generate a 'make_string' function. That pretty much *always* means
    // we run two passes through bindgen. i.e. the next 'if' is always true,
    // and we always generate an additional C++ file for our bindings additions,
    // unless the include_cpp macro has specified ExcludeUtilities.
    apis.push(Api {
        ns: Namespace::new(),
        id: make_ident("make_string"),
        use_stmt: Use::Used,
        extern_c_mod_item: Some(ForeignItem::Fn(parse_quote!(
            fn make_string(str_: &str) -> UniquePtr<CxxString>;
        ))),
        additional_cpp: Some(AdditionalNeed::MakeStringConstructor),
        deps: HashSet::new(),
        bridge_item: None,
        global_items: Vec::new(),
        id_for_allowlist: None,
        bindgen_mod_item: None,
    });
}
<commit_msg>Add .to_cpp() method for Rust strings.<commit_after>// Copyright 2020 Google LLC
//
// 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
//
//    https://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.

use super::bridge_converter::{Api, Use};
use crate::{
    additional_cpp_generator::AdditionalNeed,
    types::{make_ident, Namespace},
};
use std::collections::HashSet;
use syn::{ForeignItem, Item, parse_quote};

/// Adds items which we always add, cos they're useful.
/// Any APIs or techniques which do not involve actual C++ interop
/// shouldn't go here, but instead should go into the main autocxx
/// src/lib.rs.
pub(crate) fn generate_utilities(apis: &mut Vec<Api>) {
    // Unless we've been specifically asked not to do so, we always
    // generate a 'make_string' function. That pretty much *always* means
    // we run two passes through bindgen. i.e. the next 'if' is always true,
    // and we always generate an additional C++ file for our bindings additions,
    // unless the include_cpp macro has specified ExcludeUtilities.
    apis.push(Api {
        ns: Namespace::new(),
        id: make_ident("make_string"),
        use_stmt: Use::Unused,
        extern_c_mod_item: Some(ForeignItem::Fn(parse_quote!(
            fn make_string(str_: &str) -> UniquePtr<CxxString>;
        ))),
        additional_cpp: Some(AdditionalNeed::MakeStringConstructor),
        deps: HashSet::new(),
        bridge_item: None,
        global_items: vec![
            Item::Trait(parse_quote! {
                pub trait ToCppString {
                    fn to_cpp(&self) -> cxx::UniquePtr<cxx::CxxString>;
                }
            }),
            Item::Impl(parse_quote! {
                impl ToCppString for str {
                    fn to_cpp(&self) -> cxx::UniquePtr<cxx::CxxString> {
                        cxxbridge::make_string(self)
                    }
                }
            })
        ],
        id_for_allowlist: None,
        bindgen_mod_item: None,
    });
}
<|endoftext|>


<commit_before>// Copyright 2014 The Gfx-rs Developers.
//
// 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.

use std::collections::HashSet;
use std::fmt;
use std::str;
use super::gl;

use Capabilities;
use shade;

/// A version number for a specific component of an OpenGL implementation
#[deriving(Eq, PartialEq)]
pub struct Version {
    pub major: u32,
    pub minor: u32,
    pub revision: Option<u32>,
    pub vendor_info: &'static str,
}

// FIXME https://github.com/rust-lang/rust/issues/18738
// derive PartialOrd, Ord

#[automatically_derived]
impl ::std::cmp::Ord for Version {
    #[inline]
    fn cmp(&self, __arg_0: &Version) -> ::std::cmp::Ordering {
        match *__arg_0 {
            Version {
                major: ref __self_1_0,
                minor: ref __self_1_1,
                revision: ref __self_1_2,
                vendor_info: ref __self_1_3 } =>
                    match *self {
                        Version {
                            major: ref __self_0_0,
                            minor: ref __self_0_1,
                            revision: ref __self_0_2,
                            vendor_info: ref __self_0_3 } => {
                                let __test = (*__self_0_0).cmp(&(*__self_1_0));
                                if __test == ::std::cmp::Equal {
                                    {
                                        let __test =
                                            (*__self_0_1).cmp(&(*__self_1_1));
                                        if __test == ::std::cmp::Equal {
                                            {
                                                let __test =
                                                    (*__self_0_2).cmp(&(*__self_1_2));
                                                if __test == ::std::cmp::Equal {
                                                    {
                                                        let __test =
                                                            (*__self_0_3).cmp(*__self_1_3);
                                                        if __test ==
                                                            ::std::cmp::Equal {
                                                                ::std::cmp::Equal
                                                            } else { __test }
                                                    }
                                                } else { __test }
                                            }
                                        } else { __test }
                                    }
                                } else { __test }
                            }
                    },
        }
    }
}
#[automatically_derived]
impl ::std::cmp::PartialOrd for Version {
    #[inline]
    fn partial_cmp(&self, __arg_0: &Version) ->
        ::std::option::Option<::std::cmp::Ordering> {
            match *__arg_0 {
                Version {
                    major: ref __self_1_0,
                    minor: ref __self_1_1,
                    revision: ref __self_1_2,
                    vendor_info: ref __self_1_3 } =>
                        match *self {
                            Version {
                                major: ref __self_0_0,
                                minor: ref __self_0_1,
                                revision: ref __self_0_2,
                                vendor_info: ref __self_0_3 } => {
                                    let __test =
                                        (*__self_0_0).partial_cmp(&(*__self_1_0));
                                    if __test ==
                                        ::std::option::Some(::std::cmp::Equal) {
                                            {
                                                let __test =
                                                    (*__self_0_1).partial_cmp(&(*__self_1_1));
                                                if __test ==
                                                    ::std::option::Some(::std::cmp::Equal)
                                                    {
                                                        {
                                                            let __test =
                                                                (*__self_0_2).partial_cmp(&(*__self_1_2));
                                                            if __test ==
                                                                ::std::option::Some(::std::cmp::Equal)
                                                                {
                                                                    {
                                                                        let __test =
                                                                            (*__self_0_3).partial_cmp(*__self_1_3);
                                                                        if __test ==
                                                                            ::std::option::Some(::std::cmp::Equal)
                                                                            {
                                                                                ::std::option::Some(::std::cmp::Equal)
                                                                            } else { __test }
                                                                    }
                                                                } else { __test }
                                                        }
                                                    } else { __test }
                                            }
                                        } else { __test }
                                }
                        },
            }
        }
}

impl Version {
    /// Create a new OpenGL version number
    pub fn new(major: u32, minor: u32, revision: Option<u32>,
               vendor_info: &'static str) -> Version {
        Version {
            major: major,
            minor: minor,
            revision: revision,
            vendor_info: vendor_info,
        }
    }

    /// According to the OpenGL specification, the version information is
    /// expected to follow the following syntax:
    ///
    /// ~~~bnf
    /// <major>       ::= <number>
    /// <minor>       ::= <number>
    /// <revision>    ::= <number>
    /// <vendor-info> ::= <string>
    /// <release>     ::= <major> "." <minor> ["." <release>]
    /// <version>     ::= <release> [" " <vendor-info>]
    /// ~~~
    ///
    /// Note that this function is intentionally lenient in regards to parsing,
    /// and will try to recover at least the first two version numbers without
    /// resulting in an `Err`.
    pub fn parse(src: &'static str) -> Result<Version, &'static str> {
        let (version, vendor_info) = match src.find(' ') {
            Some(i) => (src.slice_to(i), src.slice_from(i + 1)),
            None => (src, ""),
        };

        // TODO: make this even more lenient so that we can also accept
        // `<major> "." <minor> [<???>]`
        let mut it = version.split('.');
        let major = it.next().and_then(from_str);
        let minor = it.next().and_then(from_str);
        let revision = it.next().and_then(from_str);

        match (major, minor, revision) {
            (Some(major), Some(minor), revision) => Ok(Version {
                major: major,
                minor: minor,
                revision: revision,
                vendor_info: vendor_info,
            }),
            (_, _, _) => Err(src),
        }
    }
}

impl fmt::Show for Version {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match (self.major, self.minor, self.revision, self.vendor_info) {
            (major, minor, Some(revision), "") =>
                write!(f, "{}.{}.{}", major, minor, revision),
            (major, minor, None, "") =>
                write!(f, "{}.{}", major, minor),
            (major, minor, Some(revision), vendor_info) =>
                write!(f, "{}.{}.{}, {}", major, minor, revision, vendor_info),
            (major, minor, None, vendor_info) =>
                write!(f, "{}.{}, {}", major, minor, vendor_info),
        }
    }
}

/// Get a statically allocated string from the implementation using
/// `glGetString`. Fails if it `GLenum` cannot be handled by the
/// implementation's `gl.GetString` function.
fn get_string(gl: &gl::Gl, name: gl::types::GLenum) -> &'static str {
    let ptr = unsafe { gl.GetString(name) } as *const i8;
    if !ptr.is_null() {
        // This should be safe to mark as statically allocated because
        // GlGetString only returns static strings.
        unsafe { str::raw::c_str_to_static_slice(ptr) }
    } else {
        panic!("Invalid GLenum passed to `get_string`: {:x}", name)
    }
}

fn get_uint(gl: &gl::Gl, name: gl::types::GLenum) -> uint {
    let mut value = 0 as gl::types::GLint;
    unsafe { gl.GetIntegerv(name, &mut value) };
    value as uint
}

/// A unique platform identifier that does not change between releases
#[deriving(Eq, PartialEq, Show)]
pub struct PlatformName {
    /// The company responsible for the OpenGL implementation
    pub vendor: &'static str,
    /// The name of the renderer
    pub renderer: &'static str,
}

impl PlatformName {
    fn get(gl: &gl::Gl) -> PlatformName {
        PlatformName {
            vendor: get_string(gl, gl::VENDOR),
            renderer: get_string(gl, gl::RENDERER),
        }
    }
}

/// OpenGL implementation information
#[deriving(Show)]
pub struct Info {
    /// The platform identifier
    pub platform_name: PlatformName,
    /// The OpenGL API vesion number
    pub version: Version,
    /// The GLSL vesion number
    pub shading_language: Version,
    /// The extensions supported by the implementation
    pub extensions: HashSet<&'static str>,
}

impl Info {
    fn get(gl: &gl::Gl) -> Info {
        let platform_name = PlatformName::get(gl);
        let version = Version::parse(get_string(gl, gl::VERSION)).unwrap();
        let shading_language = Version::parse(get_string(gl, gl::SHADING_LANGUAGE_VERSION)).unwrap();
        let extensions = if version >= Version::new(3, 2, None, "") {
            let num_exts = get_uint(gl, gl::NUM_EXTENSIONS) as gl::types::GLuint;
            range(0, num_exts).map(|i| {
                unsafe {
                    str::raw::c_str_to_static_slice(
                        gl.GetStringi(gl::EXTENSIONS, i) as *const i8,
                    )
                }
            }).collect()
        } else {
            // Fallback
            get_string(gl, gl::EXTENSIONS).split(' ').collect()
        };
        Info {
            platform_name: platform_name,
            version: version,
            shading_language: shading_language,
            extensions: extensions,
        }
    }

    /// Returns `true` if the implementation supports the extension
    pub fn is_extension_supported(&self, s: &str) -> bool {
        self.extensions.contains_equiv(s)
    }

    pub fn is_version_or_extension_supported(&self, major: u32, minor: u32, ext: &str) -> bool {
        self.version >= Version::new(major, minor, None, "") || self.is_extension_supported(ext)
    }
}

fn to_shader_model(v: &Version) -> shade::ShaderModel {
    use shade::ShaderModel;
    match *v {
        v if v < Version::new(1, 20, None, "") => ShaderModel::Unsupported,
        v if v < Version::new(1, 50, None, "") => ShaderModel::Model30,
        v if v < Version::new(3,  0, None, "") => ShaderModel::Model40,
        v if v < Version::new(4, 30, None, "") => ShaderModel::Model41,
        _                                      => ShaderModel::Model50,
    }
}

/// Load the information pertaining to the driver and the corresponding device
/// capabilities.
pub fn get(gl: &gl::Gl) -> (Info, Capabilities) {
    let info = Info::get(gl);
    let caps = Capabilities {
        shader_model: to_shader_model(&info.shading_language),
        max_draw_buffers: get_uint(gl, gl::MAX_DRAW_BUFFERS),
        max_texture_size: get_uint(gl, gl::MAX_TEXTURE_SIZE),
        max_vertex_attributes: get_uint(gl, gl::MAX_VERTEX_ATTRIBS),
        uniform_block_supported:
            info.is_version_or_extension_supported(3, 0, "GL_ARB_uniform_buffer_object"),
        array_buffer_supported:
            info.is_version_or_extension_supported(3, 0, "GL_ARB_vertex_array_object"),
        immutable_storage_supported:
            info.is_version_or_extension_supported(4, 2, "GL_ARB_texture_storage"),
        sampler_objects_supported:
            info.is_version_or_extension_supported(3, 3, "GL_ARB_sampler_objects"),
        instance_call_supported:
            info.is_version_or_extension_supported(3, 1, "GL_ARB_draw_instanced"),
        instance_rate_supported:
            info.is_version_or_extension_supported(3, 3, "GL_ARB_instanced_arrays"),
        render_targets_supported:
            info.is_version_or_extension_supported(3, 0, "GL_ARB_framebuffer_object"),
        vertex_base_supported:
            info.is_version_or_extension_supported(3, 2, "GL_ARB_draw_elements_base_vertex"),
        instance_base_supported:
            info.is_version_or_extension_supported(4, 2, "GL_ARB_base_instance"),
    };
    (info, caps)
}

#[cfg(test)]
mod tests {
    use super::Version;
    use super::to_shader_model;
    use shade;

    #[test]
    fn test_version_parse() {
        assert_eq!(Version::parse("1"), Err("1"));
        assert_eq!(Version::parse("1."), Err("1."));
        assert_eq!(Version::parse("1 h3l1o. W0rld"), Err("1 h3l1o. W0rld"));
        assert_eq!(Version::parse("1. h3l1o. W0rld"), Err("1. h3l1o. W0rld"));
        assert_eq!(Version::parse("1.2.3"), Ok(Version::new(1, 2, Some(3), "")));
        assert_eq!(Version::parse("1.2"), Ok(Version::new(1, 2, None, "")));
        assert_eq!(Version::parse("1.2 h3l1o. W0rld"), Ok(Version::new(1, 2, None, "h3l1o. W0rld")));
        assert_eq!(Version::parse("1.2.h3l1o. W0rld"), Ok(Version::new(1, 2, None, "W0rld")));
        assert_eq!(Version::parse("1.2. h3l1o. W0rld"), Ok(Version::new(1, 2, None, "h3l1o. W0rld")));
        assert_eq!(Version::parse("1.2.3.h3l1o. W0rld"), Ok(Version::new(1, 2, Some(3), "W0rld")));
        assert_eq!(Version::parse("1.2.3 h3l1o. W0rld"), Ok(Version::new(1, 2, Some(3), "h3l1o. W0rld")));
    }

    #[test]
    fn test_shader_model() {
        assert_eq!(to_shader_model(&Version::parse("1.10").unwrap()), shade::ModelUnsupported);
        assert_eq!(to_shader_model(&Version::parse("1.20").unwrap()), shade::Model30);
        assert_eq!(to_shader_model(&Version::parse("1.50").unwrap()), shade::Model40);
        assert_eq!(to_shader_model(&Version::parse("3.00").unwrap()), shade::Model41);
        assert_eq!(to_shader_model(&Version::parse("4.30").unwrap()), shade::Model50);
    }
}
<commit_msg>Fixes deprecated warning from latest nightly<commit_after>// Copyright 2014 The Gfx-rs Developers.
//
// 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.

use std::collections::HashSet;
use std::fmt;
use std::str;
use super::gl;

use Capabilities;
use shade;

/// A version number for a specific component of an OpenGL implementation
#[deriving(Eq, PartialEq)]
pub struct Version {
    pub major: u32,
    pub minor: u32,
    pub revision: Option<u32>,
    pub vendor_info: &'static str,
}

// FIXME https://github.com/rust-lang/rust/issues/18738
// derive PartialOrd, Ord

#[automatically_derived]
impl ::std::cmp::Ord for Version {
    #[inline]
    fn cmp(&self, __arg_0: &Version) -> ::std::cmp::Ordering {
        match *__arg_0 {
            Version {
                major: ref __self_1_0,
                minor: ref __self_1_1,
                revision: ref __self_1_2,
                vendor_info: ref __self_1_3 } =>
                    match *self {
                        Version {
                            major: ref __self_0_0,
                            minor: ref __self_0_1,
                            revision: ref __self_0_2,
                            vendor_info: ref __self_0_3 } => {
                                let __test = (*__self_0_0).cmp(&(*__self_1_0));
                                if __test == ::std::cmp::Equal {
                                    {
                                        let __test =
                                            (*__self_0_1).cmp(&(*__self_1_1));
                                        if __test == ::std::cmp::Equal {
                                            {
                                                let __test =
                                                    (*__self_0_2).cmp(&(*__self_1_2));
                                                if __test == ::std::cmp::Equal {
                                                    {
                                                        let __test =
                                                            (*__self_0_3).cmp(*__self_1_3);
                                                        if __test ==
                                                            ::std::cmp::Equal {
                                                                ::std::cmp::Equal
                                                            } else { __test }
                                                    }
                                                } else { __test }
                                            }
                                        } else { __test }
                                    }
                                } else { __test }
                            }
                    },
        }
    }
}
#[automatically_derived]
impl ::std::cmp::PartialOrd for Version {
    #[inline]
    fn partial_cmp(&self, __arg_0: &Version) ->
        ::std::option::Option<::std::cmp::Ordering> {
            match *__arg_0 {
                Version {
                    major: ref __self_1_0,
                    minor: ref __self_1_1,
                    revision: ref __self_1_2,
                    vendor_info: ref __self_1_3 } =>
                        match *self {
                            Version {
                                major: ref __self_0_0,
                                minor: ref __self_0_1,
                                revision: ref __self_0_2,
                                vendor_info: ref __self_0_3 } => {
                                    let __test =
                                        (*__self_0_0).partial_cmp(&(*__self_1_0));
                                    if __test ==
                                        ::std::option::Some(::std::cmp::Equal) {
                                            {
                                                let __test =
                                                    (*__self_0_1).partial_cmp(&(*__self_1_1));
                                                if __test ==
                                                    ::std::option::Some(::std::cmp::Equal)
                                                    {
                                                        {
                                                            let __test =
                                                                (*__self_0_2).partial_cmp(&(*__self_1_2));
                                                            if __test ==
                                                                ::std::option::Some(::std::cmp::Equal)
                                                                {
                                                                    {
                                                                        let __test =
                                                                            (*__self_0_3).partial_cmp(*__self_1_3);
                                                                        if __test ==
                                                                            ::std::option::Some(::std::cmp::Equal)
                                                                            {
                                                                                ::std::option::Some(::std::cmp::Equal)
                                                                            } else { __test }
                                                                    }
                                                                } else { __test }
                                                        }
                                                    } else { __test }
                                            }
                                        } else { __test }
                                }
                        },
            }
        }
}

impl Version {
    /// Create a new OpenGL version number
    pub fn new(major: u32, minor: u32, revision: Option<u32>,
               vendor_info: &'static str) -> Version {
        Version {
            major: major,
            minor: minor,
            revision: revision,
            vendor_info: vendor_info,
        }
    }

    /// According to the OpenGL specification, the version information is
    /// expected to follow the following syntax:
    ///
    /// ~~~bnf
    /// <major>       ::= <number>
    /// <minor>       ::= <number>
    /// <revision>    ::= <number>
    /// <vendor-info> ::= <string>
    /// <release>     ::= <major> "." <minor> ["." <release>]
    /// <version>     ::= <release> [" " <vendor-info>]
    /// ~~~
    ///
    /// Note that this function is intentionally lenient in regards to parsing,
    /// and will try to recover at least the first two version numbers without
    /// resulting in an `Err`.
    pub fn parse(src: &'static str) -> Result<Version, &'static str> {
        let (version, vendor_info) = match src.find(' ') {
            Some(i) => (src.slice_to(i), src.slice_from(i + 1)),
            None => (src, ""),
        };

        // TODO: make this even more lenient so that we can also accept
        // `<major> "." <minor> [<???>]`
        let mut it = version.split('.');
        let major = it.next().and_then(from_str);
        let minor = it.next().and_then(from_str);
        let revision = it.next().and_then(from_str);

        match (major, minor, revision) {
            (Some(major), Some(minor), revision) => Ok(Version {
                major: major,
                minor: minor,
                revision: revision,
                vendor_info: vendor_info,
            }),
            (_, _, _) => Err(src),
        }
    }
}

impl fmt::Show for Version {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match (self.major, self.minor, self.revision, self.vendor_info) {
            (major, minor, Some(revision), "") =>
                write!(f, "{}.{}.{}", major, minor, revision),
            (major, minor, None, "") =>
                write!(f, "{}.{}", major, minor),
            (major, minor, Some(revision), vendor_info) =>
                write!(f, "{}.{}.{}, {}", major, minor, revision, vendor_info),
            (major, minor, None, vendor_info) =>
                write!(f, "{}.{}, {}", major, minor, vendor_info),
        }
    }
}

/// Get a statically allocated string from the implementation using
/// `glGetString`. Fails if it `GLenum` cannot be handled by the
/// implementation's `gl.GetString` function.
fn get_string(gl: &gl::Gl, name: gl::types::GLenum) -> &'static str {
    let ptr = unsafe { gl.GetString(name) } as *const i8;
    if !ptr.is_null() {
        // This should be safe to mark as statically allocated because
        // GlGetString only returns static strings.
        unsafe { str::raw::c_str_to_static_slice(ptr) }
    } else {
        panic!("Invalid GLenum passed to `get_string`: {:x}", name)
    }
}

fn get_uint(gl: &gl::Gl, name: gl::types::GLenum) -> uint {
    let mut value = 0 as gl::types::GLint;
    unsafe { gl.GetIntegerv(name, &mut value) };
    value as uint
}

/// A unique platform identifier that does not change between releases
#[deriving(Eq, PartialEq, Show)]
pub struct PlatformName {
    /// The company responsible for the OpenGL implementation
    pub vendor: &'static str,
    /// The name of the renderer
    pub renderer: &'static str,
}

impl PlatformName {
    fn get(gl: &gl::Gl) -> PlatformName {
        PlatformName {
            vendor: get_string(gl, gl::VENDOR),
            renderer: get_string(gl, gl::RENDERER),
        }
    }
}

/// OpenGL implementation information
#[deriving(Show)]
pub struct Info {
    /// The platform identifier
    pub platform_name: PlatformName,
    /// The OpenGL API vesion number
    pub version: Version,
    /// The GLSL vesion number
    pub shading_language: Version,
    /// The extensions supported by the implementation
    pub extensions: HashSet<&'static str>,
}

impl Info {
    fn get(gl: &gl::Gl) -> Info {
        let platform_name = PlatformName::get(gl);
        let version = Version::parse(get_string(gl, gl::VERSION)).unwrap();
        let shading_language = Version::parse(get_string(gl, gl::SHADING_LANGUAGE_VERSION)).unwrap();
        let extensions = if version >= Version::new(3, 2, None, "") {
            let num_exts = get_uint(gl, gl::NUM_EXTENSIONS) as gl::types::GLuint;
            range(0, num_exts).map(|i| {
                unsafe {
                    str::raw::c_str_to_static_slice(
                        gl.GetStringi(gl::EXTENSIONS, i) as *const i8,
                    )
                }
            }).collect()
        } else {
            // Fallback
            get_string(gl, gl::EXTENSIONS).split(' ').collect()
        };
        Info {
            platform_name: platform_name,
            version: version,
            shading_language: shading_language,
            extensions: extensions,
        }
    }

    /// Returns `true` if the implementation supports the extension
    pub fn is_extension_supported(&self, s: &str) -> bool {
        self.extensions.contains(s)
    }

    pub fn is_version_or_extension_supported(&self, major: u32, minor: u32, ext: &str) -> bool {
        self.version >= Version::new(major, minor, None, "") || self.is_extension_supported(ext)
    }
}

fn to_shader_model(v: &Version) -> shade::ShaderModel {
    use shade::ShaderModel;
    match *v {
        v if v < Version::new(1, 20, None, "") => ShaderModel::Unsupported,
        v if v < Version::new(1, 50, None, "") => ShaderModel::Model30,
        v if v < Version::new(3,  0, None, "") => ShaderModel::Model40,
        v if v < Version::new(4, 30, None, "") => ShaderModel::Model41,
        _                                      => ShaderModel::Model50,
    }
}

/// Load the information pertaining to the driver and the corresponding device
/// capabilities.
pub fn get(gl: &gl::Gl) -> (Info, Capabilities) {
    let info = Info::get(gl);
    let caps = Capabilities {
        shader_model: to_shader_model(&info.shading_language),
        max_draw_buffers: get_uint(gl, gl::MAX_DRAW_BUFFERS),
        max_texture_size: get_uint(gl, gl::MAX_TEXTURE_SIZE),
        max_vertex_attributes: get_uint(gl, gl::MAX_VERTEX_ATTRIBS),
        uniform_block_supported:
            info.is_version_or_extension_supported(3, 0, "GL_ARB_uniform_buffer_object"),
        array_buffer_supported:
            info.is_version_or_extension_supported(3, 0, "GL_ARB_vertex_array_object"),
        immutable_storage_supported:
            info.is_version_or_extension_supported(4, 2, "GL_ARB_texture_storage"),
        sampler_objects_supported:
            info.is_version_or_extension_supported(3, 3, "GL_ARB_sampler_objects"),
        instance_call_supported:
            info.is_version_or_extension_supported(3, 1, "GL_ARB_draw_instanced"),
        instance_rate_supported:
            info.is_version_or_extension_supported(3, 3, "GL_ARB_instanced_arrays"),
        render_targets_supported:
            info.is_version_or_extension_supported(3, 0, "GL_ARB_framebuffer_object"),
        vertex_base_supported:
            info.is_version_or_extension_supported(3, 2, "GL_ARB_draw_elements_base_vertex"),
        instance_base_supported:
            info.is_version_or_extension_supported(4, 2, "GL_ARB_base_instance"),
    };
    (info, caps)
}

#[cfg(test)]
mod tests {
    use super::Version;
    use super::to_shader_model;
    use shade;

    #[test]
    fn test_version_parse() {
        assert_eq!(Version::parse("1"), Err("1"));
        assert_eq!(Version::parse("1."), Err("1."));
        assert_eq!(Version::parse("1 h3l1o. W0rld"), Err("1 h3l1o. W0rld"));
        assert_eq!(Version::parse("1. h3l1o. W0rld"), Err("1. h3l1o. W0rld"));
        assert_eq!(Version::parse("1.2.3"), Ok(Version::new(1, 2, Some(3), "")));
        assert_eq!(Version::parse("1.2"), Ok(Version::new(1, 2, None, "")));
        assert_eq!(Version::parse("1.2 h3l1o. W0rld"), Ok(Version::new(1, 2, None, "h3l1o. W0rld")));
        assert_eq!(Version::parse("1.2.h3l1o. W0rld"), Ok(Version::new(1, 2, None, "W0rld")));
        assert_eq!(Version::parse("1.2. h3l1o. W0rld"), Ok(Version::new(1, 2, None, "h3l1o. W0rld")));
        assert_eq!(Version::parse("1.2.3.h3l1o. W0rld"), Ok(Version::new(1, 2, Some(3), "W0rld")));
        assert_eq!(Version::parse("1.2.3 h3l1o. W0rld"), Ok(Version::new(1, 2, Some(3), "h3l1o. W0rld")));
    }

    #[test]
    fn test_shader_model() {
        assert_eq!(to_shader_model(&Version::parse("1.10").unwrap()), shade::ModelUnsupported);
        assert_eq!(to_shader_model(&Version::parse("1.20").unwrap()), shade::Model30);
        assert_eq!(to_shader_model(&Version::parse("1.50").unwrap()), shade::Model40);
        assert_eq!(to_shader_model(&Version::parse("3.00").unwrap()), shade::Model41);
        assert_eq!(to_shader_model(&Version::parse("4.30").unwrap()), shade::Model50);
    }
}
<|endoftext|>


<commit_before>//! Memory buffers

use std::error::Error;
use std::fmt;

use memory;
use {IndexType, Backend};

/// Error creating a buffer.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum CreationError {
    /// Required `Usage` is not supported.
    Usage(Usage),
    /// Some other problem.
    Other,
}

/// Error creating a `BufferView`.
#[derive(Clone, Debug, PartialEq)]
pub enum ViewError {
    /// The required usage flag is not present in the image.
    Usage(Usage),
    /// The backend refused for some reason.
    Unsupported,
}

impl fmt::Display for ViewError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = self.description();
        match *self {
            ViewError::Usage(usage) => write!(f, "{}: {:?}", description, usage),
            _ => write!(f, "{}", description)
        }
    }
}

impl Error for ViewError {
    fn description(&self) -> &str {
        match *self {
            ViewError::Usage(_) =>
                "The required usage flag is not present in the image",
            ViewError::Unsupported =>
                "The backend refused for some reason",
        }
    }
}

bitflags!(
    /// Buffer usage flags.
    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
    pub struct Usage: u16 {
        ///
        const TRANSFER_SRC  = 0x1;
        ///
        const TRANSFER_DST = 0x2;
        ///
        const UNIFORM = 0x4;
        ///
        const STORAGE = 0x8;
        ///
        const UNIFORM_TEXEL = 0x10;
        ///
        const STORAGE_TEXEL = 0x20;
        ///
        const INDEX = 0x40;
        ///
        const INDIRECT = 0x80;
        ///
        const VERTEX = 0x100;
    }
);

impl Usage {
    /// Can this buffer be used in transfer operations ?
    pub fn can_transfer(&self) -> bool {
        self.intersects(Usage::TRANSFER_SRC | Usage::TRANSFER_DST)
    }
}

bitflags!(
    /// Buffer state flags.
    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
    pub struct Access: u16 {
        ///
        const TRANSFER_READ          = 0x01;
        ///
        const TRANSFER_WRITE         = 0x02;
        ///
        const INDEX_BUFFER_READ      = 0x10;
        ///
        const VERTEX_BUFFER_READ     = 0x20;
        ///
        const CONSTANT_BUFFER_READ   = 0x40;
        ///
        const INDIRECT_COMMAND_READ  = 0x80;
        ///
        const SHADER_READ = 0x100;
        ///
        const SHADER_WRITE = 0x200;
        ///
        const HOST_READ = 0x400;
        ///
        const HOST_WRITE = 0x800;
        ///
        const MEMORY_READ = 0x1000;
        ///
        const MEMORY_WRITE = 0x2000;
    }
);

/// Buffer state
pub type State = Access;

/// Index buffer view for `bind_index_buffer`.
pub struct IndexBufferView<'a, B: Backend> {
    ///
    pub buffer: &'a B::Buffer,
    ///
    pub offset: u64,
    ///
    pub index_type: IndexType,
}

/// Retrieve the complete memory requirements for this buffer,
/// taking usage and device limits into account
pub fn complete_requirements<B: Backend>(
    device: &B::Device,
    buffer: &B::UnboundBuffer,
    usage: Usage,
) -> memory::Requirements {
    use std::cmp::max;
    use device::Device;

    let mut requirements = device.get_buffer_requirements(buffer);
    if usage.can_transfer() {
        let limits = device.get_limits();
        requirements.alignment = max(
            limits.min_buffer_copy_offset_alignment as u64,
            requirements.alignment);
    }
    requirements
}
<commit_msg>Implement Error and Display for buffer::CreationError<commit_after>//! Memory buffers

use std::error::Error;
use std::fmt;

use memory;
use {IndexType, Backend};

/// Error creating a buffer.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum CreationError {
    /// Required `Usage` is not supported.
    Usage(Usage),
    /// Some other problem.
    Other,
}

impl fmt::Display for CreationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = self.description();
        match *self {
            CreationError::Usage(usage) => write!(f, "{}: {:?}", description, usage),
            _ => write!(f, "{}", description)
        }
    }
}

impl Error for CreationError {
    fn description(&self) -> &str {
        match *self {
            CreationError::Usage(_) =>
                "Required `Usage` is not supported",
            CreationError::Other =>
                "Some other problem",
        }
    }
}

/// Error creating a `BufferView`.
#[derive(Clone, Debug, PartialEq)]
pub enum ViewError {
    /// The required usage flag is not present in the image.
    Usage(Usage),
    /// The backend refused for some reason.
    Unsupported,
}

impl fmt::Display for ViewError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = self.description();
        match *self {
            ViewError::Usage(usage) => write!(f, "{}: {:?}", description, usage),
            _ => write!(f, "{}", description)
        }
    }
}

impl Error for ViewError {
    fn description(&self) -> &str {
        match *self {
            ViewError::Usage(_) =>
                "The required usage flag is not present in the image",
            ViewError::Unsupported =>
                "The backend refused for some reason",
        }
    }
}

bitflags!(
    /// Buffer usage flags.
    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
    pub struct Usage: u16 {
        ///
        const TRANSFER_SRC  = 0x1;
        ///
        const TRANSFER_DST = 0x2;
        ///
        const UNIFORM = 0x4;
        ///
        const STORAGE = 0x8;
        ///
        const UNIFORM_TEXEL = 0x10;
        ///
        const STORAGE_TEXEL = 0x20;
        ///
        const INDEX = 0x40;
        ///
        const INDIRECT = 0x80;
        ///
        const VERTEX = 0x100;
    }
);

impl Usage {
    /// Can this buffer be used in transfer operations ?
    pub fn can_transfer(&self) -> bool {
        self.intersects(Usage::TRANSFER_SRC | Usage::TRANSFER_DST)
    }
}

bitflags!(
    /// Buffer state flags.
    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
    pub struct Access: u16 {
        ///
        const TRANSFER_READ          = 0x01;
        ///
        const TRANSFER_WRITE         = 0x02;
        ///
        const INDEX_BUFFER_READ      = 0x10;
        ///
        const VERTEX_BUFFER_READ     = 0x20;
        ///
        const CONSTANT_BUFFER_READ   = 0x40;
        ///
        const INDIRECT_COMMAND_READ  = 0x80;
        ///
        const SHADER_READ = 0x100;
        ///
        const SHADER_WRITE = 0x200;
        ///
        const HOST_READ = 0x400;
        ///
        const HOST_WRITE = 0x800;
        ///
        const MEMORY_READ = 0x1000;
        ///
        const MEMORY_WRITE = 0x2000;
    }
);

/// Buffer state
pub type State = Access;

/// Index buffer view for `bind_index_buffer`.
pub struct IndexBufferView<'a, B: Backend> {
    ///
    pub buffer: &'a B::Buffer,
    ///
    pub offset: u64,
    ///
    pub index_type: IndexType,
}

/// Retrieve the complete memory requirements for this buffer,
/// taking usage and device limits into account
pub fn complete_requirements<B: Backend>(
    device: &B::Device,
    buffer: &B::UnboundBuffer,
    usage: Usage,
) -> memory::Requirements {
    use std::cmp::max;
    use device::Device;

    let mut requirements = device.get_buffer_requirements(buffer);
    if usage.can_transfer() {
        let limits = device.get_limits();
        requirements.alignment = max(
            limits.min_buffer_copy_offset_alignment as u64,
            requirements.alignment);
    }
    requirements
}
<|endoftext|>