<commit_before>//! Initialization logic for a generic instance of the `Allocate` channel allocation trait.

use std::thread;
#[cfg(feature = "getopts")]
use std::io::BufRead;
#[cfg(feature = "getopts")]
use getopts;
use std::sync::Arc;

use std::any::Any;

use crate::allocator::thread::ThreadBuilder;
use crate::allocator::{AllocateBuilder, Process, Generic, GenericBuilder};
use crate::allocator::zero_copy::initialize::initialize_networking;

use crate::logging::{CommunicationSetup, CommunicationEvent};
use logging_core::Logger;

/// Possible configurations for the communication infrastructure.
pub enum Configuration {
    /// Use one thread.
    Thread,
    /// Use one process with an indicated number of threads.
    Process(usize),
    /// Expect multiple processes.
    Cluster {
        /// Number of per-process worker threads
        threads: usize,
        /// Identity of this process
        process: usize,
        /// Addresses of all processes
        addresses: Vec<String>,
        /// Verbosely report connection process
        report: bool,
        /// Closure to create a new logger for a communication thread
        log_fn: Box<Fn(CommunicationSetup) -> Option<Logger<CommunicationEvent, CommunicationSetup>> + Send + Sync>,
    }
}

#[cfg(feature = "getopts")]
impl Configuration {

    /// Constructs a new configuration by parsing supplied text arguments.
    ///
    /// Most commonly, this uses `std::env::Args()` as the supplied iterator.
    pub fn from_args<I: Iterator<Item=String>>(args: I) -> Result<Configuration,String> {

        let mut opts = getopts::Options::new();
        opts.optopt("w", "threads", "number of per-process worker threads", "NUM");
        opts.optopt("p", "process", "identity of this process", "IDX");
        opts.optopt("n", "processes", "number of processes", "NUM");
        opts.optopt("h", "hostfile", "text file whose lines are process addresses", "FILE");
        opts.optflag("r", "report", "reports connection progress");

        opts.parse(args)
            .map_err(|e| format!("{:?}", e))
            .map(|matches| {

            // let mut config = Configuration::new(1, 0, Vec::new());
            let threads = matches.opt_str("w").map(|x| x.parse().unwrap_or(1)).unwrap_or(1);
            let process = matches.opt_str("p").map(|x| x.parse().unwrap_or(0)).unwrap_or(0);
            let processes = matches.opt_str("n").map(|x| x.parse().unwrap_or(1)).unwrap_or(1);
            let report = matches.opt_present("report");

            assert!(process < processes);

            if processes > 1 {
                let mut addresses = Vec::new();
                if let Some(hosts) = matches.opt_str("h") {
                    let reader = ::std::io::BufReader::new(::std::fs::File::open(hosts.clone()).unwrap());
                    for x in reader.lines().take(processes) {
                        addresses.push(x.unwrap());
                    }
                    if addresses.len() < processes {
                        panic!("could only read {} addresses from {}, but -n: {}", addresses.len(), hosts, processes);
                    }
                }
                else {
                    for index in 0..processes {
                        addresses.push(format!("localhost:{}", 2101 + index));
                    }
                }

                assert!(processes == addresses.len());
                Configuration::Cluster {
                    threads,
                    process,
                    addresses,
                    report,
                    log_fn: Box::new( | _ | None),
                }
            }
            else if threads > 1 { Configuration::Process(threads) }
            else { Configuration::Thread }
        })
    }

    /// Attempts to assemble the described communication infrastructure.
    pub fn try_build(self) -> Result<(Vec<GenericBuilder>, Box<Any>), String> {
        match self {
            Configuration::Thread => {
                Ok((vec![GenericBuilder::Thread(ThreadBuilder)], Box::new(())))
            },
            Configuration::Process(threads) => {
                Ok((Process::new_vector(threads).into_iter().map(|x| GenericBuilder::Process(x)).collect(), Box::new(())))
            },
            Configuration::Cluster { threads, process, addresses, report, log_fn } => {
                match initialize_networking(addresses, process, threads, report, log_fn) {
                    Ok((stuff, guard)) => {
                        Ok((stuff.into_iter().map(|x| GenericBuilder::ZeroCopy(x)).collect(), Box::new(guard)))
                    },
                    Err(err) => Err(format!("failed to initialize networking: {}", err))
                }
            },
        }
    }
}

/// Initializes communication and executes a distributed computation.
///
/// This method allocates an `allocator::Generic` for each thread, spawns local worker threads,
/// and invokes the supplied function with the allocator.
/// The method returns a `WorkerGuards<T>` which can be `join`ed to retrieve the return values
/// (or errors) of the workers.
///
///
/// # Examples
/// ```
/// use timely_communication::Allocate;
///
/// // configure for two threads, just one process.
/// let config = timely_communication::Configuration::Process(2);
///
/// // initializes communication, spawns workers
/// let guards = timely_communication::initialize(config, |mut allocator| {
///     println!("worker {} started", allocator.index());
///
///     // allocates pair of senders list and one receiver.
///     let (mut senders, mut receiver) = allocator.allocate(0);
///
///     // send typed data along each channel
///     use timely_communication::Message;
///     senders[0].send(Message::from_typed(format!("hello, {}", 0)));
///     senders[1].send(Message::from_typed(format!("hello, {}", 1)));
///
///     // no support for termination notification,
///     // we have to count down ourselves.
///     let mut expecting = 2;
///     while expecting > 0 {
///         allocator.receive();
///         if let Some(message) = receiver.recv() {
///             use std::ops::Deref;
///             println!("worker {}: received: <{}>", allocator.index(), message.deref());
///             expecting -= 1;
///         }
///         allocator.release();
///     }
///
///     // optionally, return something
///     allocator.index()
/// });
///
/// // computation runs until guards are joined or dropped.
/// if let Ok(guards) = guards {
///     for guard in guards.join() {
///         println!("result: {:?}", guard);
///     }
/// }
/// else { println!("error in computation"); }
/// ```
///
/// The should produce output like:
///
/// ```ignore
/// worker 0 started
/// worker 1 started
/// worker 0: received: <hello, 0>
/// worker 1: received: <hello, 1>
/// worker 0: received: <hello, 0>
/// worker 1: received: <hello, 1>
/// result: Ok(0)
/// result: Ok(1)
/// ```
pub fn initialize<T:Send+'static, F: Fn(Generic)->T+Send+Sync+'static>(
    config: Configuration,
    func: F,
) -> Result<WorkerGuards<T>,String> {
    let (allocators, others) = config.try_build()?;
    initialize_from(allocators, others, func)
}

/// Initializes computation and runs a distributed computation.
///
/// This version of `initialize` allows you to explicitly specify the allocators that
/// you want to use, by providing an explicit list of allocator builders. Additionally,
/// you provide `others`, a `Box<Any>` which will be held by the resulting worker guard
/// and dropped when it is dropped, which allows you to join communication threads.
///
/// # Examples
/// ```
/// use timely_communication::Allocate;
///
/// // configure for two threads, just one process.
/// let builders = timely_communication::allocator::process::Process::new_vector(2);
///
/// // initializes communication, spawns workers
/// let guards = timely_communication::initialize_from(builders, Box::new(()), |mut allocator| {
///     println!("worker {} started", allocator.index());
///
///     // allocates pair of senders list and one receiver.
///     let (mut senders, mut receiver) = allocator.allocate(0);
///
///     // send typed data along each channel
///     use timely_communication::Message;
///     senders[0].send(Message::from_typed(format!("hello, {}", 0)));
///     senders[1].send(Message::from_typed(format!("hello, {}", 1)));
///
///     // no support for termination notification,
///     // we have to count down ourselves.
///     let mut expecting = 2;
///     while expecting > 0 {
///         allocator.receive();
///         if let Some(message) = receiver.recv() {
///             use std::ops::Deref;
///             println!("worker {}: received: <{}>", allocator.index(), message.deref());
///             expecting -= 1;
///         }
///         allocator.release();
///     }
///
///     // optionally, return something
///     allocator.index()
/// });
///
/// // computation runs until guards are joined or dropped.
/// if let Ok(guards) = guards {
///     for guard in guards.join() {
///         println!("result: {:?}", guard);
///     }
/// }
/// else { println!("error in computation"); }
/// ```
pub fn initialize_from<A, T, F>(
    builders: Vec<A>,
    _others: Box<Any>,
    func: F,
) -> Result<WorkerGuards<T>,String>
where
    A: AllocateBuilder+'static,
    T: Send+'static,
    F: Fn(<A as AllocateBuilder>::Allocator)->T+Send+Sync+'static
{
    let logic = Arc::new(func);
    let mut guards = Vec::new();
    for (index, builder) in builders.into_iter().enumerate() {
        let clone = logic.clone();
        guards.push(thread::Builder::new()
                            .name(format!("worker thread {}", index))
                            .spawn(move || {
                                let communicator = builder.build();
                                (*clone)(communicator)
                            })
                            .map_err(|e| format!("{:?}", e))?);
    }

    Ok(WorkerGuards { guards, _others })
}

/// Maintains `JoinHandle`s for worker threads.
pub struct WorkerGuards<T:Send+'static> {
    guards: Vec<::std::thread::JoinHandle<T>>,
    _others: Box<Any>,
}

impl<T:Send+'static> WorkerGuards<T> {
    /// Waits on the worker threads and returns the results they produce.
    pub fn join(mut self) -> Vec<Result<T, String>> {
        self.guards.drain(..)
                   .map(|guard| guard.join().map_err(|e| format!("{:?}", e)))
                   .collect()
    }
}

impl<T:Send+'static> Drop for WorkerGuards<T> {
    fn drop(&mut self) {
        for guard in self.guards.drain(..) {
            guard.join().expect("Worker panic");
        }
        // println!("WORKER THREADS JOINED");
    }
}
<commit_msg>Add Configuration::options<commit_after>//! Initialization logic for a generic instance of the `Allocate` channel allocation trait.

use std::thread;
#[cfg(feature = "getopts")]
use std::io::BufRead;
#[cfg(feature = "getopts")]
use getopts;
use std::sync::Arc;

use std::any::Any;

use crate::allocator::thread::ThreadBuilder;
use crate::allocator::{AllocateBuilder, Process, Generic, GenericBuilder};
use crate::allocator::zero_copy::initialize::initialize_networking;

use crate::logging::{CommunicationSetup, CommunicationEvent};
use logging_core::Logger;

/// Possible configurations for the communication infrastructure.
pub enum Configuration {
    /// Use one thread.
    Thread,
    /// Use one process with an indicated number of threads.
    Process(usize),
    /// Expect multiple processes.
    Cluster {
        /// Number of per-process worker threads
        threads: usize,
        /// Identity of this process
        process: usize,
        /// Addresses of all processes
        addresses: Vec<String>,
        /// Verbosely report connection process
        report: bool,
        /// Closure to create a new logger for a communication thread
        log_fn: Box<Fn(CommunicationSetup) -> Option<Logger<CommunicationEvent, CommunicationSetup>> + Send + Sync>,
    }
}

#[cfg(feature = "getopts")]
impl Configuration {

    /// Returns a `getopts::Options` struct that can be used to print
    /// usage information in higher-level systems.
    pub fn options() -> getopts::Options {
        let mut opts = getopts::Options::new();
        opts.optopt("w", "threads", "number of per-process worker threads", "NUM");
        opts.optopt("p", "process", "identity of this process", "IDX");
        opts.optopt("n", "processes", "number of processes", "NUM");
        opts.optopt("h", "hostfile", "text file whose lines are process addresses", "FILE");
        opts.optflag("r", "report", "reports connection progress");

        opts
    }

    /// Constructs a new configuration by parsing supplied text arguments.
    ///
    /// Most commonly, this uses `std::env::Args()` as the supplied iterator.
    pub fn from_args<I: Iterator<Item=String>>(args: I) -> Result<Configuration,String> {
        let opts = Configuration::options();

        opts.parse(args)
            .map_err(|e| format!("{:?}", e))
            .map(|matches| {

            // let mut config = Configuration::new(1, 0, Vec::new());
            let threads = matches.opt_str("w").map(|x| x.parse().unwrap_or(1)).unwrap_or(1);
            let process = matches.opt_str("p").map(|x| x.parse().unwrap_or(0)).unwrap_or(0);
            let processes = matches.opt_str("n").map(|x| x.parse().unwrap_or(1)).unwrap_or(1);
            let report = matches.opt_present("report");

            assert!(process < processes);

            if processes > 1 {
                let mut addresses = Vec::new();
                if let Some(hosts) = matches.opt_str("h") {
                    let reader = ::std::io::BufReader::new(::std::fs::File::open(hosts.clone()).unwrap());
                    for x in reader.lines().take(processes) {
                        addresses.push(x.unwrap());
                    }
                    if addresses.len() < processes {
                        panic!("could only read {} addresses from {}, but -n: {}", addresses.len(), hosts, processes);
                    }
                }
                else {
                    for index in 0..processes {
                        addresses.push(format!("localhost:{}", 2101 + index));
                    }
                }

                assert!(processes == addresses.len());
                Configuration::Cluster {
                    threads,
                    process,
                    addresses,
                    report,
                    log_fn: Box::new( | _ | None),
                }
            }
            else if threads > 1 { Configuration::Process(threads) }
            else { Configuration::Thread }
        })
    }

    /// Attempts to assemble the described communication infrastructure.
    pub fn try_build(self) -> Result<(Vec<GenericBuilder>, Box<Any>), String> {
        match self {
            Configuration::Thread => {
                Ok((vec![GenericBuilder::Thread(ThreadBuilder)], Box::new(())))
            },
            Configuration::Process(threads) => {
                Ok((Process::new_vector(threads).into_iter().map(|x| GenericBuilder::Process(x)).collect(), Box::new(())))
            },
            Configuration::Cluster { threads, process, addresses, report, log_fn } => {
                match initialize_networking(addresses, process, threads, report, log_fn) {
                    Ok((stuff, guard)) => {
                        Ok((stuff.into_iter().map(|x| GenericBuilder::ZeroCopy(x)).collect(), Box::new(guard)))
                    },
                    Err(err) => Err(format!("failed to initialize networking: {}", err))
                }
            },
        }
    }
}

/// Initializes communication and executes a distributed computation.
///
/// This method allocates an `allocator::Generic` for each thread, spawns local worker threads,
/// and invokes the supplied function with the allocator.
/// The method returns a `WorkerGuards<T>` which can be `join`ed to retrieve the return values
/// (or errors) of the workers.
///
///
/// # Examples
/// ```
/// use timely_communication::Allocate;
///
/// // configure for two threads, just one process.
/// let config = timely_communication::Configuration::Process(2);
///
/// // initializes communication, spawns workers
/// let guards = timely_communication::initialize(config, |mut allocator| {
///     println!("worker {} started", allocator.index());
///
///     // allocates pair of senders list and one receiver.
///     let (mut senders, mut receiver) = allocator.allocate(0);
///
///     // send typed data along each channel
///     use timely_communication::Message;
///     senders[0].send(Message::from_typed(format!("hello, {}", 0)));
///     senders[1].send(Message::from_typed(format!("hello, {}", 1)));
///
///     // no support for termination notification,
///     // we have to count down ourselves.
///     let mut expecting = 2;
///     while expecting > 0 {
///         allocator.receive();
///         if let Some(message) = receiver.recv() {
///             use std::ops::Deref;
///             println!("worker {}: received: <{}>", allocator.index(), message.deref());
///             expecting -= 1;
///         }
///         allocator.release();
///     }
///
///     // optionally, return something
///     allocator.index()
/// });
///
/// // computation runs until guards are joined or dropped.
/// if let Ok(guards) = guards {
///     for guard in guards.join() {
///         println!("result: {:?}", guard);
///     }
/// }
/// else { println!("error in computation"); }
/// ```
///
/// The should produce output like:
///
/// ```ignore
/// worker 0 started
/// worker 1 started
/// worker 0: received: <hello, 0>
/// worker 1: received: <hello, 1>
/// worker 0: received: <hello, 0>
/// worker 1: received: <hello, 1>
/// result: Ok(0)
/// result: Ok(1)
/// ```
pub fn initialize<T:Send+'static, F: Fn(Generic)->T+Send+Sync+'static>(
    config: Configuration,
    func: F,
) -> Result<WorkerGuards<T>,String> {
    let (allocators, others) = config.try_build()?;
    initialize_from(allocators, others, func)
}

/// Initializes computation and runs a distributed computation.
///
/// This version of `initialize` allows you to explicitly specify the allocators that
/// you want to use, by providing an explicit list of allocator builders. Additionally,
/// you provide `others`, a `Box<Any>` which will be held by the resulting worker guard
/// and dropped when it is dropped, which allows you to join communication threads.
///
/// # Examples
/// ```
/// use timely_communication::Allocate;
///
/// // configure for two threads, just one process.
/// let builders = timely_communication::allocator::process::Process::new_vector(2);
///
/// // initializes communication, spawns workers
/// let guards = timely_communication::initialize_from(builders, Box::new(()), |mut allocator| {
///     println!("worker {} started", allocator.index());
///
///     // allocates pair of senders list and one receiver.
///     let (mut senders, mut receiver) = allocator.allocate(0);
///
///     // send typed data along each channel
///     use timely_communication::Message;
///     senders[0].send(Message::from_typed(format!("hello, {}", 0)));
///     senders[1].send(Message::from_typed(format!("hello, {}", 1)));
///
///     // no support for termination notification,
///     // we have to count down ourselves.
///     let mut expecting = 2;
///     while expecting > 0 {
///         allocator.receive();
///         if let Some(message) = receiver.recv() {
///             use std::ops::Deref;
///             println!("worker {}: received: <{}>", allocator.index(), message.deref());
///             expecting -= 1;
///         }
///         allocator.release();
///     }
///
///     // optionally, return something
///     allocator.index()
/// });
///
/// // computation runs until guards are joined or dropped.
/// if let Ok(guards) = guards {
///     for guard in guards.join() {
///         println!("result: {:?}", guard);
///     }
/// }
/// else { println!("error in computation"); }
/// ```
pub fn initialize_from<A, T, F>(
    builders: Vec<A>,
    _others: Box<Any>,
    func: F,
) -> Result<WorkerGuards<T>,String>
where
    A: AllocateBuilder+'static,
    T: Send+'static,
    F: Fn(<A as AllocateBuilder>::Allocator)->T+Send+Sync+'static
{
    let logic = Arc::new(func);
    let mut guards = Vec::new();
    for (index, builder) in builders.into_iter().enumerate() {
        let clone = logic.clone();
        guards.push(thread::Builder::new()
                            .name(format!("worker thread {}", index))
                            .spawn(move || {
                                let communicator = builder.build();
                                (*clone)(communicator)
                            })
                            .map_err(|e| format!("{:?}", e))?);
    }

    Ok(WorkerGuards { guards, _others })
}

/// Maintains `JoinHandle`s for worker threads.
pub struct WorkerGuards<T:Send+'static> {
    guards: Vec<::std::thread::JoinHandle<T>>,
    _others: Box<Any>,
}

impl<T:Send+'static> WorkerGuards<T> {
    /// Waits on the worker threads and returns the results they produce.
    pub fn join(mut self) -> Vec<Result<T, String>> {
        self.guards.drain(..)
                   .map(|guard| guard.join().map_err(|e| format!("{:?}", e)))
                   .collect()
    }
}

impl<T:Send+'static> Drop for WorkerGuards<T> {
    fn drop(&mut self) {
        for guard in self.guards.drain(..) {
            guard.join().expect("Worker panic");
        }
        // println!("WORKER THREADS JOINED");
    }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate glob;
#[macro_use] extern crate log;
extern crate serde_json;
extern crate semver;
extern crate toml;
#[macro_use] extern crate version;

extern crate task_hookrs;

extern crate libimagrt;
extern crate libimagstore;
extern crate libimagerror;
extern crate libimagtodo;

use std::process::exit;
use std::process::{Command, Stdio};
use std::io::stdin;

use toml::Value;

use task_hookrs::import::import_tasks;
use task_hookrs::status::TaskStatus;

use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagtodo::task::Task;
use libimagerror::trace::trace_error;

mod ui;

use ui::build_ui;
fn main() {
    let rt = generate_runtime_setup("imag-todo",
                                    &version!()[..],
                                    "Interface with taskwarrior",
                                    build_ui);

    match rt.cli().subcommand_name() {
        Some("tw-hook") => tw_hook(&rt),
        Some("list") => list(&rt),
        _ => unreachable!(),
    } // end match scmd
} // end main

fn tw_hook(rt: &Runtime) {
    let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
    if subcmd.is_present("add") {
        let stdin = stdin();
        let stdin = stdin.lock(); // implements BufRead which is required for `Task::import()`

        match Task::import(rt.store(), stdin) {
            Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
            Err(e) => {
                trace_error(&e);
                exit(1);
            }
        }
    } else if subcmd.is_present("delete") {
        // The used hook is "on-modify". This hook gives two json-objects
        // per usage und wants one (the second one) back.
        let stdin         = stdin();
        let stdin         = stdin.lock();

        match import_tasks(stdin) {
            Ok(ttasks) => for (counter, ttask) in ttasks.iter().enumerate() {
                if counter % 2 == 1 {
                    // Only every second task is needed, the first one is the
                    // task before the change, and the second one after
                    // the change. The (maybe modified) second one is
                    // expected by taskwarrior.
                    match serde_json::ser::to_string(&ttask) {
                        Ok(val) => println!("{}", val),
                        Err(e) => {
                            trace_error(&e);
                            exit(1);
                        }
                    }

                    // Taskwarrior does not have the concept of deleted tasks, but only modified
                    // ones.
                    //
                    // Here we check if the status of a task is deleted and if yes, we delete it
                    // from the store.
                    if *ttask.status() == TaskStatus::Deleted {
                        match Task::delete_by_uuid(rt.store(), *ttask.uuid()) {
                            Ok(_) => println!("Deleted task {}", *ttask.uuid()),
                            Err(e) => {
                                trace_error(&e);
                                exit(1);
                            }
                        }
                    }
                } // end if c % 2
            },
            Err(e) => {
                trace_error(&e);
                exit(1);
            },
        }
    } else {
        // Should not be possible, as one argument is required via
        // ArgGroup
        unreachable!();
    }
}

fn list(rt: &Runtime) {
    let subcmd  = rt.cli().subcommand_matches("list").unwrap();
    let verbose = subcmd.is_present("verbose");

    let res = Task::all(rt.store()) // get all tasks
        .map(|iter| { // and if this succeeded
            // filter out the ones were we can read the uuid
            let uuids : Vec<_> = iter.filter_map(|t| match t {
                Ok(v) => match v.get_header().read("todo.uuid") {
                    Ok(Some(Value::String(ref u))) => Some(u.clone()),
                    Ok(Some(_)) => {
                        warn!("Header type error");
                        None
                    },
                    Ok(None) => None,
                    Err(e) => {
                        trace_error(&e);
                        None
                    }
                },
                Err(e) => {
                    trace_error(&e);
                    None
                }
            })
            .collect();

            // compose a `task` call with them, ...
            let outstring = if verbose { // ... if verbose
                let output = Command::new("task")
                    .stdin(Stdio::null())
                    .args(&uuids)
                    .spawn()
                    .unwrap_or_else(|e| {
                        trace_error(&e);
                        panic!("Failed to execute `task` on the commandline. I'm dying now.");
                    })
                    .wait_with_output()
                    .unwrap_or_else(|e| panic!("failed to unwrap output: {}", e));

                String::from_utf8(output.stdout)
                    .unwrap_or_else(|e| panic!("failed to execute: {}", e))
            } else { // ... else just join them
                uuids.join("\n")
            };

            // and then print that
            println!("{}", outstring);
        });

    if let Err(e) = res {
        trace_error(&e);
    }
}

<commit_msg>Do not panic if there was no command passed<commit_after>extern crate clap;
extern crate glob;
#[macro_use] extern crate log;
extern crate serde_json;
extern crate semver;
extern crate toml;
#[macro_use] extern crate version;

extern crate task_hookrs;

extern crate libimagrt;
extern crate libimagstore;
extern crate libimagerror;
extern crate libimagtodo;

use std::process::exit;
use std::process::{Command, Stdio};
use std::io::stdin;

use toml::Value;

use task_hookrs::import::import_tasks;
use task_hookrs::status::TaskStatus;

use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagtodo::task::Task;
use libimagerror::trace::trace_error;

mod ui;

use ui::build_ui;
fn main() {
    let rt = generate_runtime_setup("imag-todo",
                                    &version!()[..],
                                    "Interface with taskwarrior",
                                    build_ui);

    match rt.cli().subcommand_name() {
        Some("tw-hook") => tw_hook(&rt),
        Some("list") => list(&rt),
        None => {
            warn!("No command");
        },
        _ => unreachable!(),
    } // end match scmd
} // end main

fn tw_hook(rt: &Runtime) {
    let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
    if subcmd.is_present("add") {
        let stdin = stdin();
        let stdin = stdin.lock(); // implements BufRead which is required for `Task::import()`

        match Task::import(rt.store(), stdin) {
            Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
            Err(e) => {
                trace_error(&e);
                exit(1);
            }
        }
    } else if subcmd.is_present("delete") {
        // The used hook is "on-modify". This hook gives two json-objects
        // per usage und wants one (the second one) back.
        let stdin         = stdin();
        let stdin         = stdin.lock();

        match import_tasks(stdin) {
            Ok(ttasks) => for (counter, ttask) in ttasks.iter().enumerate() {
                if counter % 2 == 1 {
                    // Only every second task is needed, the first one is the
                    // task before the change, and the second one after
                    // the change. The (maybe modified) second one is
                    // expected by taskwarrior.
                    match serde_json::ser::to_string(&ttask) {
                        Ok(val) => println!("{}", val),
                        Err(e) => {
                            trace_error(&e);
                            exit(1);
                        }
                    }

                    // Taskwarrior does not have the concept of deleted tasks, but only modified
                    // ones.
                    //
                    // Here we check if the status of a task is deleted and if yes, we delete it
                    // from the store.
                    if *ttask.status() == TaskStatus::Deleted {
                        match Task::delete_by_uuid(rt.store(), *ttask.uuid()) {
                            Ok(_) => println!("Deleted task {}", *ttask.uuid()),
                            Err(e) => {
                                trace_error(&e);
                                exit(1);
                            }
                        }
                    }
                } // end if c % 2
            },
            Err(e) => {
                trace_error(&e);
                exit(1);
            },
        }
    } else {
        // Should not be possible, as one argument is required via
        // ArgGroup
        unreachable!();
    }
}

fn list(rt: &Runtime) {
    let subcmd  = rt.cli().subcommand_matches("list").unwrap();
    let verbose = subcmd.is_present("verbose");

    let res = Task::all(rt.store()) // get all tasks
        .map(|iter| { // and if this succeeded
            // filter out the ones were we can read the uuid
            let uuids : Vec<_> = iter.filter_map(|t| match t {
                Ok(v) => match v.get_header().read("todo.uuid") {
                    Ok(Some(Value::String(ref u))) => Some(u.clone()),
                    Ok(Some(_)) => {
                        warn!("Header type error");
                        None
                    },
                    Ok(None) => None,
                    Err(e) => {
                        trace_error(&e);
                        None
                    }
                },
                Err(e) => {
                    trace_error(&e);
                    None
                }
            })
            .collect();

            // compose a `task` call with them, ...
            let outstring = if verbose { // ... if verbose
                let output = Command::new("task")
                    .stdin(Stdio::null())
                    .args(&uuids)
                    .spawn()
                    .unwrap_or_else(|e| {
                        trace_error(&e);
                        panic!("Failed to execute `task` on the commandline. I'm dying now.");
                    })
                    .wait_with_output()
                    .unwrap_or_else(|e| panic!("failed to unwrap output: {}", e));

                String::from_utf8(output.stdout)
                    .unwrap_or_else(|e| panic!("failed to execute: {}", e))
            } else { // ... else just join them
                uuids.join("\n")
            };

            // and then print that
            println!("{}", outstring);
        });

    if let Err(e) = res {
        trace_error(&e);
    }
}

<|endoftext|>


<commit_before>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::str::from_utf8;

use common::ascii::{CR, LF, SP};
pub use common::RequestError;
use common::{Body, Method, Version};
use headers::Headers;

// Helper function used for parsing the HTTP Request.
// Splits the bytes in a pair containing the bytes before the separator and after the separator.
// The separator is not included in the return values.
fn split(bytes: &[u8], separator: u8) -> (&[u8], &[u8]) {
    match bytes.iter().position(|byte| *byte == separator) {
        Some(index) => (&bytes[..index], &bytes[index + 1..]),
        None => (&[], bytes),
    }
}

/// Wrapper over HTTP URIs.
///
/// The `Uri` can not be used directly and it is only accessible from an HTTP Request.
#[derive(Clone, Debug, PartialEq)]
pub struct Uri<'a> {
    slice: &'a str,
}

impl<'a> Uri<'a> {
    fn new(slice: &'a str) -> Self {
        Uri { slice }
    }

    fn try_from(bytes: &'a [u8]) -> Result<Self, RequestError> {
        if bytes.len() == 0 {
            return Err(RequestError::InvalidUri("Empty URI not allowed."));
        }
        let utf8_slice =
            from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?;
        Ok(Uri::new(utf8_slice))
    }

    /// Returns the absolute path of the `Uri`.
    ///
    /// URIs can be represented in absolute form or relative form. The absolute form includes
    /// the HTTP scheme, followed by the absolute path as follows:
    /// "http:" "//" host [ ":" port ] [ abs_path ]
    /// The relative URIs can be one of net_path | abs_path | rel_path.
    /// This method only handles absolute URIs and relative URIs specified by abs_path.
    /// The abs_path is expected to start with '/'.
    ///
    /// # Errors
    /// Returns an empty byte array when the host or the path are empty/invalid.
    ///
    pub fn get_abs_path(&self) -> &'a str {
        const HTTP_SCHEME_PREFIX: &str = "http://";

        if self.slice.starts_with(HTTP_SCHEME_PREFIX) {
            let without_scheme = &self.slice[HTTP_SCHEME_PREFIX.len()..];
            if without_scheme.len() == 0 {
                return "";
            }
            // The host in this case includes the port and contains the bytes after http:// up to
            // the next '/'.
            match without_scheme.bytes().position(|byte| byte == b'/') {
                Some(len) => &without_scheme[len..],
                None => "",
            }
        } else {
            if self.slice.starts_with("/") {
                return &self.slice;
            }

            ""
        }
    }
}

#[derive(Debug, PartialEq)]
struct RequestLine<'a> {
    method: Method,
    uri: Uri<'a>,
    http_version: Version,
}

impl<'a> RequestLine<'a> {
    fn remove_trailing_cr(version: &[u8]) -> &[u8] {
        match version.last() {
            Some(&CR) => &version[..version.len() - 1],
            _ => version,
        }
    }

    fn parse_request_line(request_line: &[u8]) -> (&[u8], &[u8], &[u8]) {
        let (method, remaining_bytes) = split(request_line, SP);
        let (uri, remaining_bytes) = split(remaining_bytes, SP);
        let (mut version, _) = split(remaining_bytes, LF);
        version = RequestLine::remove_trailing_cr(version);

        (method, uri, version)
    }

    fn try_from(request_line: &'a [u8]) -> Result<Self, RequestError> {
        let (method, uri, version) = RequestLine::parse_request_line(request_line);

        Ok(RequestLine {
            method: Method::try_from(method)?,
            uri: Uri::try_from(uri)?,
            http_version: Version::try_from(version)?,
        })
    }

    // Returns the minimum length of a valid request. The request must contain
    // the method (GET), the URI (minmum 1 character), the HTTP method(HTTP/DIGIT.DIGIT) and
    // 3 separators (SP/LF).
    fn min_len() -> usize {
        Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 3
    }
}

/// Wrapper over an HTTP Request.
#[allow(unused)]
#[derive(Debug)]
pub struct Request<'a> {
    request_line: RequestLine<'a>,
    headers: Headers,
    body: Option<Body>,
}

impl<'a> Request<'a> {
    /// Parses a byte slice into a HTTP Request.
    ///
    /// The byte slice is expected to have the following format: </br>
    ///     * Request Line: "GET SP Request-uri SP HTTP/1.0 CRLF" - Mandatory </br>
    ///     * Request Headers "<headers> CRLF"- Optional </br>
    ///     * Entity Body - Optional </br>
    /// The request headers and the entity body is not parsed and None is returned because
    /// these are not used by the MMDS server.
    /// The only supported method is GET and the HTTP protocol is expected to be HTTP/1.0
    /// or HTTP/1.1.
    ///
    /// # Errors
    /// The function returns InvalidRequest when parsing the byte stream fails.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::Request;
    ///
    /// let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n");
    /// ```
    pub fn try_from(byte_stream: &'a [u8]) -> Result<Self, RequestError> {
        // The first line of the request is the Request Line. The line ending is LF.
        let (request_line, _) = split(byte_stream, LF);
        if request_line.len() < RequestLine::min_len() {
            return Err(RequestError::InvalidRequest);
        }

        // The Request Line should include the trailing LF.
        let request_line = RequestLine::try_from(&byte_stream[..=request_line.len()])?;
        // We ignore the Headers and Entity body because we don't need them for MMDS requests.
        Ok(Request {
            request_line,
            headers: Headers::default(),
            body: None,
        })
    }

    /// Returns the `Uri` from the parsed `Request`.
    ///
    /// The return value can be used to get the absolute path of the URI.
    pub fn uri(&self) -> &Uri {
        &self.request_line.uri
    }

    /// Returns the HTTP `Version` of the `Request`.
    pub fn http_version(&self) -> Version {
        self.request_line.http_version
    }
}

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

    impl<'a> PartialEq for Request<'a> {
        fn eq(&self, other: &Request) -> bool {
            // Ignore the other fields of Request for now because they are not used.
            return self.request_line == other.request_line;
        }
    }

    #[test]
    fn test_uri() {
        let uri = Uri::new("http://localhost/home");
        assert_eq!(uri.get_abs_path(), "/home");

        let uri = Uri::new("/home");
        assert_eq!(uri.get_abs_path(), "/home");

        let uri = Uri::new("home");
        assert_eq!(uri.get_abs_path(), "");

        let uri = Uri::new("http://");
        assert_eq!(uri.get_abs_path(), "");

        let uri = Uri::new("http://192.168.0.0");
        assert_eq!(uri.get_abs_path(), "");
    }

    #[test]
    fn test_into_request_line() {
        let expected_request_line = RequestLine {
            http_version: Version::Http10,
            method: Method::Get,
            uri: Uri::new("http://localhost/home"),
        };

        let request_line = b"GET http://localhost/home HTTP/1.0\r\n";
        match RequestLine::try_from(request_line) {
            Ok(request) => assert_eq!(request, expected_request_line),
            Err(_) => assert!(false),
        };

        let expected_request_line = RequestLine {
            http_version: Version::Http11,
            method: Method::Get,
            uri: Uri::new("http://localhost/home"),
        };

        // Happy case with request line ending in CRLF.
        let request_line = b"GET http://localhost/home HTTP/1.1\r\n";
        match RequestLine::try_from(request_line) {
            Ok(request) => assert_eq!(request, expected_request_line),
            Err(_) => assert!(false),
        };

        // Happy case with request line ending in LF instead of CRLF.
        let request_line = b"GET http://localhost/home HTTP/1.1\n";
        match RequestLine::try_from(request_line) {
            Ok(request) => assert_eq!(request, expected_request_line),
            Err(_) => assert!(false),
        };

        // Test for invalid method.
        let request_line = b"PUT http://localhost/home HTTP/1.0\r\n";
        assert_eq!(
            RequestLine::try_from(request_line).unwrap_err(),
            RequestError::InvalidHttpMethod("Unsupported HTTP method.")
        );

        // Test for invalid uri.
        let request_line = b"GET  HTTP/1.0\r\n";
        assert_eq!(
            RequestLine::try_from(request_line).unwrap_err(),
            RequestError::InvalidUri("Empty URI not allowed.")
        );

        // Test for invalid HTTP version.
        let request_line = b"GET http://localhost/home HTTP/2.0\r\n";
        assert_eq!(
            RequestLine::try_from(request_line).unwrap_err(),
            RequestError::InvalidHttpVersion("Unsupported HTTP version.")
        );
    }

    #[test]
    fn test_into_request() {
        let expected_request = Request {
            request_line: RequestLine {
                http_version: Version::Http10,
                method: Method::Get,
                uri: Uri::new("http://localhost/home"),
            },
            body: None,
            headers: Headers::default(),
        };
        let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n \
                                     Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT";
        let request = Request::try_from(request_bytes).unwrap();
        assert!(request == expected_request);
        assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
        assert_eq!(request.http_version(), Version::Http10);

        // Test for invalid Request (length is less than minimum).
        let request_bytes = b"GET";
        assert_eq!(
            Request::try_from(request_bytes).unwrap_err(),
            RequestError::InvalidRequest
        );
    }
}
<commit_msg>correct the comment of min_len() function<commit_after>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::str::from_utf8;

use common::ascii::{CR, LF, SP};
pub use common::RequestError;
use common::{Body, Method, Version};
use headers::Headers;

// Helper function used for parsing the HTTP Request.
// Splits the bytes in a pair containing the bytes before the separator and after the separator.
// The separator is not included in the return values.
fn split(bytes: &[u8], separator: u8) -> (&[u8], &[u8]) {
    match bytes.iter().position(|byte| *byte == separator) {
        Some(index) => (&bytes[..index], &bytes[index + 1..]),
        None => (&[], bytes),
    }
}

/// Wrapper over HTTP URIs.
///
/// The `Uri` can not be used directly and it is only accessible from an HTTP Request.
#[derive(Clone, Debug, PartialEq)]
pub struct Uri<'a> {
    slice: &'a str,
}

impl<'a> Uri<'a> {
    fn new(slice: &'a str) -> Self {
        Uri { slice }
    }

    fn try_from(bytes: &'a [u8]) -> Result<Self, RequestError> {
        if bytes.len() == 0 {
            return Err(RequestError::InvalidUri("Empty URI not allowed."));
        }
        let utf8_slice =
            from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?;
        Ok(Uri::new(utf8_slice))
    }

    /// Returns the absolute path of the `Uri`.
    ///
    /// URIs can be represented in absolute form or relative form. The absolute form includes
    /// the HTTP scheme, followed by the absolute path as follows:
    /// "http:" "//" host [ ":" port ] [ abs_path ]
    /// The relative URIs can be one of net_path | abs_path | rel_path.
    /// This method only handles absolute URIs and relative URIs specified by abs_path.
    /// The abs_path is expected to start with '/'.
    ///
    /// # Errors
    /// Returns an empty byte array when the host or the path are empty/invalid.
    ///
    pub fn get_abs_path(&self) -> &'a str {
        const HTTP_SCHEME_PREFIX: &str = "http://";

        if self.slice.starts_with(HTTP_SCHEME_PREFIX) {
            let without_scheme = &self.slice[HTTP_SCHEME_PREFIX.len()..];
            if without_scheme.len() == 0 {
                return "";
            }
            // The host in this case includes the port and contains the bytes after http:// up to
            // the next '/'.
            match without_scheme.bytes().position(|byte| byte == b'/') {
                Some(len) => &without_scheme[len..],
                None => "",
            }
        } else {
            if self.slice.starts_with("/") {
                return &self.slice;
            }

            ""
        }
    }
}

#[derive(Debug, PartialEq)]
struct RequestLine<'a> {
    method: Method,
    uri: Uri<'a>,
    http_version: Version,
}

impl<'a> RequestLine<'a> {
    fn remove_trailing_cr(version: &[u8]) -> &[u8] {
        match version.last() {
            Some(&CR) => &version[..version.len() - 1],
            _ => version,
        }
    }

    fn parse_request_line(request_line: &[u8]) -> (&[u8], &[u8], &[u8]) {
        let (method, remaining_bytes) = split(request_line, SP);
        let (uri, remaining_bytes) = split(remaining_bytes, SP);
        let (mut version, _) = split(remaining_bytes, LF);
        version = RequestLine::remove_trailing_cr(version);

        (method, uri, version)
    }

    fn try_from(request_line: &'a [u8]) -> Result<Self, RequestError> {
        let (method, uri, version) = RequestLine::parse_request_line(request_line);

        Ok(RequestLine {
            method: Method::try_from(method)?,
            uri: Uri::try_from(uri)?,
            http_version: Version::try_from(version)?,
        })
    }

    // Returns the minimum length of a valid request. The request must contain
    // the method (GET), the URI (minmum 1 character), the HTTP version(HTTP/DIGIT.DIGIT) and
    // 3 separators (2 SP + 1 LF).
    fn min_len() -> usize {
        Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 3
    }
}

/// Wrapper over an HTTP Request.
#[allow(unused)]
#[derive(Debug)]
pub struct Request<'a> {
    request_line: RequestLine<'a>,
    headers: Headers,
    body: Option<Body>,
}

impl<'a> Request<'a> {
    /// Parses a byte slice into a HTTP Request.
    ///
    /// The byte slice is expected to have the following format: </br>
    ///     * Request Line: "GET SP Request-uri SP HTTP/1.0 CRLF" - Mandatory </br>
    ///     * Request Headers "<headers> CRLF"- Optional </br>
    ///     * Entity Body - Optional </br>
    /// The request headers and the entity body is not parsed and None is returned because
    /// these are not used by the MMDS server.
    /// The only supported method is GET and the HTTP protocol is expected to be HTTP/1.0
    /// or HTTP/1.1.
    ///
    /// # Errors
    /// The function returns InvalidRequest when parsing the byte stream fails.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::Request;
    ///
    /// let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n");
    /// ```
    pub fn try_from(byte_stream: &'a [u8]) -> Result<Self, RequestError> {
        // The first line of the request is the Request Line. The line ending is LF.
        let (request_line, _) = split(byte_stream, LF);
        if request_line.len() < RequestLine::min_len() {
            return Err(RequestError::InvalidRequest);
        }

        // The Request Line should include the trailing LF.
        let request_line = RequestLine::try_from(&byte_stream[..=request_line.len()])?;
        // We ignore the Headers and Entity body because we don't need them for MMDS requests.
        Ok(Request {
            request_line,
            headers: Headers::default(),
            body: None,
        })
    }

    /// Returns the `Uri` from the parsed `Request`.
    ///
    /// The return value can be used to get the absolute path of the URI.
    pub fn uri(&self) -> &Uri {
        &self.request_line.uri
    }

    /// Returns the HTTP `Version` of the `Request`.
    pub fn http_version(&self) -> Version {
        self.request_line.http_version
    }
}

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

    impl<'a> PartialEq for Request<'a> {
        fn eq(&self, other: &Request) -> bool {
            // Ignore the other fields of Request for now because they are not used.
            return self.request_line == other.request_line;
        }
    }

    #[test]
    fn test_uri() {
        let uri = Uri::new("http://localhost/home");
        assert_eq!(uri.get_abs_path(), "/home");

        let uri = Uri::new("/home");
        assert_eq!(uri.get_abs_path(), "/home");

        let uri = Uri::new("home");
        assert_eq!(uri.get_abs_path(), "");

        let uri = Uri::new("http://");
        assert_eq!(uri.get_abs_path(), "");

        let uri = Uri::new("http://192.168.0.0");
        assert_eq!(uri.get_abs_path(), "");
    }

    #[test]
    fn test_into_request_line() {
        let expected_request_line = RequestLine {
            http_version: Version::Http10,
            method: Method::Get,
            uri: Uri::new("http://localhost/home"),
        };

        let request_line = b"GET http://localhost/home HTTP/1.0\r\n";
        match RequestLine::try_from(request_line) {
            Ok(request) => assert_eq!(request, expected_request_line),
            Err(_) => assert!(false),
        };

        let expected_request_line = RequestLine {
            http_version: Version::Http11,
            method: Method::Get,
            uri: Uri::new("http://localhost/home"),
        };

        // Happy case with request line ending in CRLF.
        let request_line = b"GET http://localhost/home HTTP/1.1\r\n";
        match RequestLine::try_from(request_line) {
            Ok(request) => assert_eq!(request, expected_request_line),
            Err(_) => assert!(false),
        };

        // Happy case with request line ending in LF instead of CRLF.
        let request_line = b"GET http://localhost/home HTTP/1.1\n";
        match RequestLine::try_from(request_line) {
            Ok(request) => assert_eq!(request, expected_request_line),
            Err(_) => assert!(false),
        };

        // Test for invalid method.
        let request_line = b"PUT http://localhost/home HTTP/1.0\r\n";
        assert_eq!(
            RequestLine::try_from(request_line).unwrap_err(),
            RequestError::InvalidHttpMethod("Unsupported HTTP method.")
        );

        // Test for invalid uri.
        let request_line = b"GET  HTTP/1.0\r\n";
        assert_eq!(
            RequestLine::try_from(request_line).unwrap_err(),
            RequestError::InvalidUri("Empty URI not allowed.")
        );

        // Test for invalid HTTP version.
        let request_line = b"GET http://localhost/home HTTP/2.0\r\n";
        assert_eq!(
            RequestLine::try_from(request_line).unwrap_err(),
            RequestError::InvalidHttpVersion("Unsupported HTTP version.")
        );
    }

    #[test]
    fn test_into_request() {
        let expected_request = Request {
            request_line: RequestLine {
                http_version: Version::Http10,
                method: Method::Get,
                uri: Uri::new("http://localhost/home"),
            },
            body: None,
            headers: Headers::default(),
        };
        let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n \
                                     Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT";
        let request = Request::try_from(request_bytes).unwrap();
        assert!(request == expected_request);
        assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
        assert_eq!(request.http_version(), Version::Http10);

        // Test for invalid Request (length is less than minimum).
        let request_bytes = b"GET";
        assert_eq!(
            Request::try_from(request_bytes).unwrap_err(),
            RequestError::InvalidRequest
        );
    }
}
<|endoftext|>


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

use super::PYTHON;
use bindgen::{Builder, CodegenConfig};
use regex::Regex;
use std::cmp;
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::{exit, Command};
use std::slice;
use std::sync::Mutex;
use std::time::SystemTime;
use toml;
use toml::value::Table;

lazy_static! {
    static ref OUTDIR_PATH: PathBuf = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("gecko");
}

const STRUCTS_FILE: &'static str = "structs.rs";

fn read_config(path: &PathBuf) -> Table {
    println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
    update_last_modified(&path);

    let mut contents = String::new();
    File::open(path)
        .expect("Failed to open config file")
        .read_to_string(&mut contents)
        .expect("Failed to read config file");
    match toml::from_str::<Table>(&contents) {
        Ok(result) => result,
        Err(e) => panic!("Failed to parse config file: {}", e),
    }
}

lazy_static! {
    static ref CONFIG: Table = {
        // Load Gecko's binding generator config from the source tree.
        let path = PathBuf::from(env::var_os("MOZ_SRC").unwrap())
            .join("layout/style/ServoBindings.toml");
        read_config(&path)
    };
    static ref BUILD_CONFIG: Table = {
        // Load build-specific config overrides.
        let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap())
            .join("layout/style/bindgen.toml");
        read_config(&path)
    };
    static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap();
    static ref DISTDIR_PATH: PathBuf = {
        let path = PathBuf::from(env::var_os("MOZ_DIST").unwrap());
        if !path.is_absolute() || !path.is_dir() {
            panic!("MOZ_DIST must be an absolute directory, was: {}", path.display());
        }
        path
    };
    static ref SEARCH_PATHS: Vec<PathBuf> = vec![
        DISTDIR_PATH.join("include"),
        DISTDIR_PATH.join("include/nspr"),
    ];
    static ref ADDED_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
    static ref LAST_MODIFIED: Mutex<SystemTime> =
        Mutex::new(get_modified_time(&env::current_exe().unwrap())
                   .expect("Failed to get modified time of executable"));
}

fn get_modified_time(file: &Path) -> Option<SystemTime> {
    file.metadata().and_then(|m| m.modified()).ok()
}

fn update_last_modified(file: &Path) {
    let modified = get_modified_time(file).expect("Couldn't get file modification time");
    let mut last_modified = LAST_MODIFIED.lock().unwrap();
    *last_modified = cmp::max(modified, *last_modified);
}

fn search_include(name: &str) -> Option<PathBuf> {
    for path in SEARCH_PATHS.iter() {
        let file = path.join(name);
        if file.is_file() {
            update_last_modified(&file);
            return Some(file);
        }
    }
    None
}

fn add_headers_recursively(path: PathBuf, added_paths: &mut HashSet<PathBuf>) {
    if added_paths.contains(&path) {
        return;
    }
    let mut file = File::open(&path).unwrap();
    let mut content = String::new();
    file.read_to_string(&mut content).unwrap();
    added_paths.insert(path);
    // Find all includes and add them recursively
    for cap in INCLUDE_RE.captures_iter(&content) {
        if let Some(path) = search_include(cap.get(1).unwrap().as_str()) {
            add_headers_recursively(path, added_paths);
        }
    }
}

fn add_include(name: &str) -> String {
    let mut added_paths = ADDED_PATHS.lock().unwrap();
    let file = match search_include(name) {
        Some(file) => file,
        None => panic!("Include not found: {}", name),
    };
    let result = String::from(file.to_str().unwrap());
    add_headers_recursively(file, &mut *added_paths);
    result
}

trait BuilderExt {
    fn get_initial_builder() -> Builder;
    fn include<T: Into<String>>(self, file: T) -> Builder;
}

impl BuilderExt for Builder {
    fn get_initial_builder() -> Builder {
        use bindgen::RustTarget;

        // Disable rust unions, because we replace some types inside of
        // them.
        let mut builder = Builder::default()
            .rust_target(RustTarget::Stable_1_25)
            .size_t_is_usize(true)
            .disable_untagged_union();

        let rustfmt_path = env::var_os("RUSTFMT")
            // This can be replaced with
            // > .filter(|p| !p.is_empty()).map(PathBuf::from)
            // once we can use 1.27+.
            .and_then(|p| {
                if p.is_empty() {
                    None
                } else {
                    Some(PathBuf::from(p))
                }
            });
        if let Some(path) = rustfmt_path {
            builder = builder.with_rustfmt(path);
        }

        for dir in SEARCH_PATHS.iter() {
            builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap());
        }

        builder = builder.include(add_include("mozilla-config.h"));

        if env::var("CARGO_FEATURE_GECKO_DEBUG").is_ok() {
            builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1");
        }

        let build_config = BUILD_CONFIG["build"]
            .as_table()
            .expect("Malformed config file");
        let extra_bindgen_flags = build_config["args"].as_array().unwrap().as_slice();
        for item in extra_bindgen_flags.iter() {
            builder = builder.clang_arg(item.as_str().expect("Expect string in list"));
        }

        builder
    }
    fn include<T: Into<String>>(self, file: T) -> Builder {
        self.clang_arg("-include").clang_arg(file)
    }
}

struct Fixup {
    pat: String,
    rep: String,
}

fn write_binding_file(builder: Builder, file: &str, fixups: &[Fixup]) {
    let out_file = OUTDIR_PATH.join(file);
    if let Some(modified) = get_modified_time(&out_file) {
        // Don't generate the file if nothing it depends on was modified.
        let last_modified = LAST_MODIFIED.lock().unwrap();
        if *last_modified <= modified {
            return;
        }
    }
    let command_line_opts = builder.command_line_flags();
    let result = builder.generate();
    let mut result = match result {
        Ok(bindings) => bindings.to_string(),
        Err(_) => {
            panic!(
                "Failed to generate bindings, flags: {:?}",
                command_line_opts
            );
        },
    };

    for fixup in fixups.iter() {
        result = Regex::new(&fixup.pat)
            .unwrap()
            .replace_all(&result, &*fixup.rep)
            .into_owned()
            .into();
    }
    let bytes = result.into_bytes();
    File::create(&out_file)
        .unwrap()
        .write_all(&bytes)
        .expect("Unable to write output");
}

struct BuilderWithConfig<'a> {
    builder: Builder,
    config: &'a Table,
    used_keys: HashSet<&'static str>,
}
impl<'a> BuilderWithConfig<'a> {
    fn new(builder: Builder, config: &'a Table) -> Self {
        BuilderWithConfig {
            builder,
            config,
            used_keys: HashSet::new(),
        }
    }

    fn handle_list<F>(self, key: &'static str, func: F) -> BuilderWithConfig<'a>
    where
        F: FnOnce(Builder, slice::Iter<'a, toml::Value>) -> Builder,
    {
        let mut builder = self.builder;
        let config = self.config;
        let mut used_keys = self.used_keys;
        if let Some(list) = config.get(key) {
            used_keys.insert(key);
            builder = func(builder, list.as_array().unwrap().as_slice().iter());
        }
        BuilderWithConfig {
            builder,
            config,
            used_keys,
        }
    }
    fn handle_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    where
        F: FnMut(Builder, &'a toml::Value) -> Builder,
    {
        self.handle_list(key, |b, iter| iter.fold(b, |b, item| func(b, item)))
    }
    fn handle_str_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    where
        F: FnMut(Builder, &'a str) -> Builder,
    {
        self.handle_items(key, |b, item| func(b, item.as_str().unwrap()))
    }
    fn handle_table_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    where
        F: FnMut(Builder, &'a Table) -> Builder,
    {
        self.handle_items(key, |b, item| func(b, item.as_table().unwrap()))
    }
    fn handle_common(self, fixups: &mut Vec<Fixup>) -> BuilderWithConfig<'a> {
        self.handle_str_items("headers", |b, item| b.header(add_include(item)))
            .handle_str_items("raw-lines", |b, item| b.raw_line(item))
            .handle_str_items("hide-types", |b, item| b.blacklist_type(item))
            .handle_table_items("fixups", |builder, item| {
                fixups.push(Fixup {
                    pat: item["pat"].as_str().unwrap().into(),
                    rep: item["rep"].as_str().unwrap().into(),
                });
                builder
            })
    }

    fn get_builder(self) -> Builder {
        for key in self.config.keys() {
            if !self.used_keys.contains(key.as_str()) {
                panic!(format!("Unknown key: {}", key));
            }
        }
        self.builder
    }
}

fn generate_structs() {
    let builder = Builder::get_initial_builder()
        .enable_cxx_namespaces()
        .with_codegen_config(CodegenConfig::TYPES | CodegenConfig::VARS | CodegenConfig::FUNCTIONS);
    let mut fixups = vec![];
    let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
        .handle_common(&mut fixups)
        .handle_str_items("whitelist-functions", |b, item| b.whitelist_function(item))
        .handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item))
        .handle_str_items("rusty-enums", |b, item| b.rustified_enum(item))
        .handle_str_items("whitelist-vars", |b, item| b.whitelist_var(item))
        .handle_str_items("whitelist-types", |b, item| b.whitelist_type(item))
        .handle_str_items("opaque-types", |b, item| b.opaque_type(item))
        .handle_table_items("cbindgen-types", |b, item| {
            let gecko = item["gecko"].as_str().unwrap();
            let servo = item["servo"].as_str().unwrap();
            b.blacklist_type(format!("mozilla::{}", gecko))
                .module_raw_line("root::mozilla", format!("pub use {} as {};", servo, gecko))
        })
        .handle_table_items("mapped-generic-types", |builder, item| {
            let generic = item["generic"].as_bool().unwrap();
            let gecko = item["gecko"].as_str().unwrap();
            let servo = item["servo"].as_str().unwrap();
            let gecko_name = gecko.rsplit("::").next().unwrap();
            let gecko = gecko
                .split("::")
                .map(|s| format!("\\s*{}\\s*", s))
                .collect::<Vec<_>>()
                .join("::");

            fixups.push(Fixup {
                pat: format!("\\broot\\s*::\\s*{}\\b", gecko),
                rep: format!("crate::gecko_bindings::structs::{}", gecko_name),
            });
            builder.blacklist_type(gecko).raw_line(format!(
                "pub type {0}{2} = {1}{2};",
                gecko_name,
                servo,
                if generic { "<T>" } else { "" }
            ))
        })
        .get_builder();
    write_binding_file(builder, STRUCTS_FILE, &fixups);
}

fn setup_logging() -> bool {
    struct BuildLogger {
        file: Option<Mutex<fs::File>>,
        filter: String,
    }

    impl log::Log for BuildLogger {
        fn enabled(&self, meta: &log::Metadata) -> bool {
            self.file.is_some() && meta.target().contains(&self.filter)
        }

        fn log(&self, record: &log::Record) {
            if !self.enabled(record.metadata()) {
                return;
            }

            let mut file = self.file.as_ref().unwrap().lock().unwrap();
            let _ = writeln!(
                file,
                "{} - {} - {} @ {}:{}",
                record.level(),
                record.target(),
                record.args(),
                record.file().unwrap_or("<unknown>"),
                record.line().unwrap_or(0)
            );
        }

        fn flush(&self) {
            if let Some(ref file) = self.file {
                file.lock().unwrap().flush().unwrap();
            }
        }
    }

    if let Some(path) = env::var_os("STYLO_BUILD_LOG") {
        log::set_max_level(log::LevelFilter::Debug);
        log::set_boxed_logger(Box::new(BuildLogger {
            file: fs::File::create(path).ok().map(Mutex::new),
            filter: env::var("STYLO_BUILD_FILTER")
                .ok()
                .unwrap_or_else(|| "bindgen".to_owned()),
        }))
        .expect("Failed to set logger.");

        true
    } else {
        false
    }
}

fn generate_atoms() {
    let script = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
        .join("gecko")
        .join("regen_atoms.py");
    println!("cargo:rerun-if-changed={}", script.display());
    let status = Command::new(&*PYTHON)
        .arg(&script)
        .arg(DISTDIR_PATH.as_os_str())
        .arg(OUTDIR_PATH.as_os_str())
        .status()
        .unwrap();
    if !status.success() {
        exit(1);
    }
}

pub fn generate() {
    println!("cargo:rerun-if-changed=build_gecko.rs");
    fs::create_dir_all(&*OUTDIR_PATH).unwrap();
    setup_logging();
    generate_structs();
    generate_atoms();

    for path in ADDED_PATHS.lock().unwrap().iter() {
        println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
    }
}
<commit_msg>style: Fix Gecko nightly warning.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use super::PYTHON;
use bindgen::{Builder, CodegenConfig};
use regex::Regex;
use std::cmp;
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::{exit, Command};
use std::slice;
use std::sync::Mutex;
use std::time::SystemTime;
use toml;
use toml::value::Table;

lazy_static! {
    static ref OUTDIR_PATH: PathBuf = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("gecko");
}

const STRUCTS_FILE: &'static str = "structs.rs";

fn read_config(path: &PathBuf) -> Table {
    println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
    update_last_modified(&path);

    let mut contents = String::new();
    File::open(path)
        .expect("Failed to open config file")
        .read_to_string(&mut contents)
        .expect("Failed to read config file");
    match toml::from_str::<Table>(&contents) {
        Ok(result) => result,
        Err(e) => panic!("Failed to parse config file: {}", e),
    }
}

lazy_static! {
    static ref CONFIG: Table = {
        // Load Gecko's binding generator config from the source tree.
        let path = PathBuf::from(env::var_os("MOZ_SRC").unwrap())
            .join("layout/style/ServoBindings.toml");
        read_config(&path)
    };
    static ref BUILD_CONFIG: Table = {
        // Load build-specific config overrides.
        let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap())
            .join("layout/style/bindgen.toml");
        read_config(&path)
    };
    static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap();
    static ref DISTDIR_PATH: PathBuf = {
        let path = PathBuf::from(env::var_os("MOZ_DIST").unwrap());
        if !path.is_absolute() || !path.is_dir() {
            panic!("MOZ_DIST must be an absolute directory, was: {}", path.display());
        }
        path
    };
    static ref SEARCH_PATHS: Vec<PathBuf> = vec![
        DISTDIR_PATH.join("include"),
        DISTDIR_PATH.join("include/nspr"),
    ];
    static ref ADDED_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
    static ref LAST_MODIFIED: Mutex<SystemTime> =
        Mutex::new(get_modified_time(&env::current_exe().unwrap())
                   .expect("Failed to get modified time of executable"));
}

fn get_modified_time(file: &Path) -> Option<SystemTime> {
    file.metadata().and_then(|m| m.modified()).ok()
}

fn update_last_modified(file: &Path) {
    let modified = get_modified_time(file).expect("Couldn't get file modification time");
    let mut last_modified = LAST_MODIFIED.lock().unwrap();
    *last_modified = cmp::max(modified, *last_modified);
}

fn search_include(name: &str) -> Option<PathBuf> {
    for path in SEARCH_PATHS.iter() {
        let file = path.join(name);
        if file.is_file() {
            update_last_modified(&file);
            return Some(file);
        }
    }
    None
}

fn add_headers_recursively(path: PathBuf, added_paths: &mut HashSet<PathBuf>) {
    if added_paths.contains(&path) {
        return;
    }
    let mut file = File::open(&path).unwrap();
    let mut content = String::new();
    file.read_to_string(&mut content).unwrap();
    added_paths.insert(path);
    // Find all includes and add them recursively
    for cap in INCLUDE_RE.captures_iter(&content) {
        if let Some(path) = search_include(cap.get(1).unwrap().as_str()) {
            add_headers_recursively(path, added_paths);
        }
    }
}

fn add_include(name: &str) -> String {
    let mut added_paths = ADDED_PATHS.lock().unwrap();
    let file = match search_include(name) {
        Some(file) => file,
        None => panic!("Include not found: {}", name),
    };
    let result = String::from(file.to_str().unwrap());
    add_headers_recursively(file, &mut *added_paths);
    result
}

trait BuilderExt {
    fn get_initial_builder() -> Builder;
    fn include<T: Into<String>>(self, file: T) -> Builder;
}

impl BuilderExt for Builder {
    fn get_initial_builder() -> Builder {
        use bindgen::RustTarget;

        // Disable rust unions, because we replace some types inside of
        // them.
        let mut builder = Builder::default()
            .rust_target(RustTarget::Stable_1_25)
            .size_t_is_usize(true)
            .disable_untagged_union();

        let rustfmt_path = env::var_os("RUSTFMT")
            // This can be replaced with
            // > .filter(|p| !p.is_empty()).map(PathBuf::from)
            // once we can use 1.27+.
            .and_then(|p| {
                if p.is_empty() {
                    None
                } else {
                    Some(PathBuf::from(p))
                }
            });
        if let Some(path) = rustfmt_path {
            builder = builder.with_rustfmt(path);
        }

        for dir in SEARCH_PATHS.iter() {
            builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap());
        }

        builder = builder.include(add_include("mozilla-config.h"));

        if env::var("CARGO_FEATURE_GECKO_DEBUG").is_ok() {
            builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1");
        }

        let build_config = BUILD_CONFIG["build"]
            .as_table()
            .expect("Malformed config file");
        let extra_bindgen_flags = build_config["args"].as_array().unwrap().as_slice();
        for item in extra_bindgen_flags.iter() {
            builder = builder.clang_arg(item.as_str().expect("Expect string in list"));
        }

        builder
    }
    fn include<T: Into<String>>(self, file: T) -> Builder {
        self.clang_arg("-include").clang_arg(file)
    }
}

struct Fixup {
    pat: String,
    rep: String,
}

fn write_binding_file(builder: Builder, file: &str, fixups: &[Fixup]) {
    let out_file = OUTDIR_PATH.join(file);
    if let Some(modified) = get_modified_time(&out_file) {
        // Don't generate the file if nothing it depends on was modified.
        let last_modified = LAST_MODIFIED.lock().unwrap();
        if *last_modified <= modified {
            return;
        }
    }
    let command_line_opts = builder.command_line_flags();
    let result = builder.generate();
    let mut result = match result {
        Ok(bindings) => bindings.to_string(),
        Err(_) => {
            panic!(
                "Failed to generate bindings, flags: {:?}",
                command_line_opts
            );
        },
    };

    for fixup in fixups.iter() {
        result = Regex::new(&fixup.pat)
            .unwrap()
            .replace_all(&result, &*fixup.rep)
            .into_owned()
            .into();
    }
    let bytes = result.into_bytes();
    File::create(&out_file)
        .unwrap()
        .write_all(&bytes)
        .expect("Unable to write output");
}

struct BuilderWithConfig<'a> {
    builder: Builder,
    config: &'a Table,
    used_keys: HashSet<&'static str>,
}
impl<'a> BuilderWithConfig<'a> {
    fn new(builder: Builder, config: &'a Table) -> Self {
        BuilderWithConfig {
            builder,
            config,
            used_keys: HashSet::new(),
        }
    }

    fn handle_list<F>(self, key: &'static str, func: F) -> BuilderWithConfig<'a>
    where
        F: FnOnce(Builder, slice::Iter<'a, toml::Value>) -> Builder,
    {
        let mut builder = self.builder;
        let config = self.config;
        let mut used_keys = self.used_keys;
        if let Some(list) = config.get(key) {
            used_keys.insert(key);
            builder = func(builder, list.as_array().unwrap().as_slice().iter());
        }
        BuilderWithConfig {
            builder,
            config,
            used_keys,
        }
    }
    fn handle_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    where
        F: FnMut(Builder, &'a toml::Value) -> Builder,
    {
        self.handle_list(key, |b, iter| iter.fold(b, |b, item| func(b, item)))
    }
    fn handle_str_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    where
        F: FnMut(Builder, &'a str) -> Builder,
    {
        self.handle_items(key, |b, item| func(b, item.as_str().unwrap()))
    }
    fn handle_table_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    where
        F: FnMut(Builder, &'a Table) -> Builder,
    {
        self.handle_items(key, |b, item| func(b, item.as_table().unwrap()))
    }
    fn handle_common(self, fixups: &mut Vec<Fixup>) -> BuilderWithConfig<'a> {
        self.handle_str_items("headers", |b, item| b.header(add_include(item)))
            .handle_str_items("raw-lines", |b, item| b.raw_line(item))
            .handle_str_items("hide-types", |b, item| b.blacklist_type(item))
            .handle_table_items("fixups", |builder, item| {
                fixups.push(Fixup {
                    pat: item["pat"].as_str().unwrap().into(),
                    rep: item["rep"].as_str().unwrap().into(),
                });
                builder
            })
    }

    fn get_builder(self) -> Builder {
        for key in self.config.keys() {
            if !self.used_keys.contains(key.as_str()) {
                panic!("Unknown key: {}", key);
            }
        }
        self.builder
    }
}

fn generate_structs() {
    let builder = Builder::get_initial_builder()
        .enable_cxx_namespaces()
        .with_codegen_config(CodegenConfig::TYPES | CodegenConfig::VARS | CodegenConfig::FUNCTIONS);
    let mut fixups = vec![];
    let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
        .handle_common(&mut fixups)
        .handle_str_items("whitelist-functions", |b, item| b.whitelist_function(item))
        .handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item))
        .handle_str_items("rusty-enums", |b, item| b.rustified_enum(item))
        .handle_str_items("whitelist-vars", |b, item| b.whitelist_var(item))
        .handle_str_items("whitelist-types", |b, item| b.whitelist_type(item))
        .handle_str_items("opaque-types", |b, item| b.opaque_type(item))
        .handle_table_items("cbindgen-types", |b, item| {
            let gecko = item["gecko"].as_str().unwrap();
            let servo = item["servo"].as_str().unwrap();
            b.blacklist_type(format!("mozilla::{}", gecko))
                .module_raw_line("root::mozilla", format!("pub use {} as {};", servo, gecko))
        })
        .handle_table_items("mapped-generic-types", |builder, item| {
            let generic = item["generic"].as_bool().unwrap();
            let gecko = item["gecko"].as_str().unwrap();
            let servo = item["servo"].as_str().unwrap();
            let gecko_name = gecko.rsplit("::").next().unwrap();
            let gecko = gecko
                .split("::")
                .map(|s| format!("\\s*{}\\s*", s))
                .collect::<Vec<_>>()
                .join("::");

            fixups.push(Fixup {
                pat: format!("\\broot\\s*::\\s*{}\\b", gecko),
                rep: format!("crate::gecko_bindings::structs::{}", gecko_name),
            });
            builder.blacklist_type(gecko).raw_line(format!(
                "pub type {0}{2} = {1}{2};",
                gecko_name,
                servo,
                if generic { "<T>" } else { "" }
            ))
        })
        .get_builder();
    write_binding_file(builder, STRUCTS_FILE, &fixups);
}

fn setup_logging() -> bool {
    struct BuildLogger {
        file: Option<Mutex<fs::File>>,
        filter: String,
    }

    impl log::Log for BuildLogger {
        fn enabled(&self, meta: &log::Metadata) -> bool {
            self.file.is_some() && meta.target().contains(&self.filter)
        }

        fn log(&self, record: &log::Record) {
            if !self.enabled(record.metadata()) {
                return;
            }

            let mut file = self.file.as_ref().unwrap().lock().unwrap();
            let _ = writeln!(
                file,
                "{} - {} - {} @ {}:{}",
                record.level(),
                record.target(),
                record.args(),
                record.file().unwrap_or("<unknown>"),
                record.line().unwrap_or(0)
            );
        }

        fn flush(&self) {
            if let Some(ref file) = self.file {
                file.lock().unwrap().flush().unwrap();
            }
        }
    }

    if let Some(path) = env::var_os("STYLO_BUILD_LOG") {
        log::set_max_level(log::LevelFilter::Debug);
        log::set_boxed_logger(Box::new(BuildLogger {
            file: fs::File::create(path).ok().map(Mutex::new),
            filter: env::var("STYLO_BUILD_FILTER")
                .ok()
                .unwrap_or_else(|| "bindgen".to_owned()),
        }))
        .expect("Failed to set logger.");

        true
    } else {
        false
    }
}

fn generate_atoms() {
    let script = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
        .join("gecko")
        .join("regen_atoms.py");
    println!("cargo:rerun-if-changed={}", script.display());
    let status = Command::new(&*PYTHON)
        .arg(&script)
        .arg(DISTDIR_PATH.as_os_str())
        .arg(OUTDIR_PATH.as_os_str())
        .status()
        .unwrap();
    if !status.success() {
        exit(1);
    }
}

pub fn generate() {
    println!("cargo:rerun-if-changed=build_gecko.rs");
    fs::create_dir_all(&*OUTDIR_PATH).unwrap();
    setup_logging();
    generate_structs();
    generate_atoms();

    for path in ADDED_PATHS.lock().unwrap().iter() {
        println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
    }
}
<|endoftext|>