<commit_before>use std::{error,fmt};

use super::{EvaluationContext,Functions,Value};

pub trait Function {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>;
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum ArgumentType {
    Nodeset,
    Boolean,
    Number,
    String,
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum Error {
    TooManyArguments{ expected: usize, actual: usize },
    NotEnoughArguments{ expected: usize, actual: usize },
    WrongType{ expected: ArgumentType, actual: ArgumentType },
}

impl Error {
    fn wrong_type(actual: &Value, expected: ArgumentType) -> Error {
        let actual = match *actual {
            Value::Nodes(..)   => ArgumentType::Nodeset,
            Value::String(..)  => ArgumentType::String,
            Value::Number(..)  => ArgumentType::Number,
            Value::Boolean(..) => ArgumentType::Boolean,
        };

        Error::WrongType {
            expected: expected,
            actual: actual
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            TooManyArguments{..}   => "too many arguments",
            NotEnoughArguments{..} => "not enough arguments",
            WrongType{..}          => "argument of wrong type",
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        use self::Error::*;
        match *self {
            TooManyArguments{expected, actual} => {
                write!(fmt, "too many arguments, expected {} but had {}", expected, actual)
            },
            NotEnoughArguments{expected, actual} => {
                write!(fmt, "not enough arguments, expected {} but had {}", expected, actual)
            },
            WrongType{expected, actual} => {
                write!(fmt, "argument was the wrong type, expected {:?} but had {:?}", expected, actual)
            },
        }
    }
}

fn minimum_arg_count<T>(args: &Vec<T>, minimum: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < minimum {
        Err(Error::NotEnoughArguments{expected: minimum, actual: actual})
    } else {
        Ok(())
    }
}

fn exact_arg_count<T>(args: &Vec<T>, expected: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < expected {
        Err(Error::NotEnoughArguments{ expected: expected, actual: actual })
    } else if actual > expected {
        Err(Error::TooManyArguments{ expected: expected, actual: actual })
    } else {
        Ok(())
    }
}

fn string_args(args: Vec<Value>) -> Result<Vec<String>, Error> {
    fn string_arg(v: Value) -> Result<String, Error> {
        match v {
            Value::String(s) => Ok(s),
            _ => Err(Error::wrong_type(&v, ArgumentType::String)),
        }
    }

    args.into_iter().map(string_arg).collect()
}

struct Last;

impl Function for Last {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.size() as f64))
    }
}

struct Position;

impl Function for Position {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.position() as f64))
    }
}

struct Count;

impl Function for Count {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Nodes(ref nodeset) => Ok(Value::Number(nodeset.size() as f64)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Nodeset)),
        }
    }
}

struct Concat;

impl Function for Concat {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(minimum_arg_count(&args, 2));
        let args = try!(string_args(args));
        Ok(Value::String(args.concat()))
    }
}

struct StartsWith;

impl Function for StartsWith {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let v = args[0].starts_with(&*args[1]);
        Ok(Value::Boolean(v))
    }
}

struct Contains;

impl Function for Contains {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let v = args[0].contains(&*args[1]);
        Ok(Value::Boolean(v))
    }
}

struct SubstringBefore;

impl Function for SubstringBefore {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let haystack = &args[0];

        let s = match haystack.find_str(&*args[1]) {
            Some(pos) => &haystack[..pos],
            None => "",
        };

        Ok(Value::String(s.to_string()))
    }
}

struct SubstringAfter;

impl Function for SubstringAfter {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let haystack = &args[0];
        let needle = &*args[1];

        let s = match haystack.find_str(needle) {
            Some(pos) => &haystack[pos + needle.len()..],
            None => "",
        };

        Ok(Value::String(s.to_string()))
    }
}

struct Not;

impl Function for Not {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Boolean(v) => Ok(Value::Boolean(!v)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Boolean)),
        }
    }
}

struct True;

impl Function for True {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Boolean(true))
    }
}

struct False;

impl Function for False {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Boolean(false))
    }
}

pub fn register_core_functions(functions: &mut Functions) {
    functions.insert("last".to_string(), box Last);
    functions.insert("position".to_string(), box Position);
    functions.insert("count".to_string(), box Count);
    functions.insert("concat".to_string(), box Concat);
    functions.insert("starts-with".to_string(), box StartsWith);
    functions.insert("contains".to_string(), box Contains);
    functions.insert("substring-before".to_string(), box SubstringBefore);
    functions.insert("substring-after".to_string(), box SubstringAfter);
    functions.insert("not".to_string(), box Not);
    functions.insert("true".to_string(), box True);
    functions.insert("false".to_string(), box False);
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;
    use document::Package;
    use super::super::{EvaluationContext,Value,Functions,Variables,Namespaces};
    use super::super::nodeset::ToNode;
    use super::{
        Function,
        Error,
        Last,
        Position,
        Count,
        Concat,
        StartsWith,
        Contains,
        SubstringBefore,
        SubstringAfter,
    };

    struct Setup<'d> {
        functions: Functions,
        variables: Variables<'d>,
        namespaces: Namespaces,
    }

    impl<'d> Setup<'d> {
        fn new() -> Setup<'d> {
            Setup {
                functions: HashMap::new(),
                variables: HashMap::new(),
                namespaces: HashMap::new(),
            }
        }

        fn evaluate<N, F>(&self, node: N, f: F, args: Vec<Value<'d>>)
            -> Result<Value<'d>, Error>
            where N: ToNode<'d>,
                  F: Function
        {
            let context = EvaluationContext::new(
                node, &self.functions, &self.variables, &self.namespaces
            );
            f.evaluate(&context, args)
        }
    }

    #[test]
    fn last_returns_context_size() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let r = setup.evaluate(doc.root(), Last, vec![]);

        assert_eq!(Ok(Value::Number(1.0)), r);
    }

    #[test]
    fn position_returns_context_position() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let r = setup.evaluate(doc.root(), Position, vec![]);

        assert_eq!(Ok(Value::Number(1.0)), r);
    }

    #[test]
    fn count_counts_nodes_in_nodeset() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let nodeset = nodeset![doc.root()];
        let r = setup.evaluate(doc.root(), Count, vec![Value::Nodes(nodeset)]);

        assert_eq!(Ok(Value::Number(1.0)), r);
    }

    #[test]
    fn concat_combines_strings() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = vec![Value::String("hello".to_string()),
                        Value::String(" ".to_string()),
                        Value::String("world".to_string())];
        let r = setup.evaluate(doc.root(), Concat, args);

        assert_eq!(Ok(Value::String("hello world".to_string())), r);
    }

    #[test]
    fn starts_with_checks_prefixes() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = vec![Value::String("hello".to_string()),
                        Value::String("he".to_string())];
        let r = setup.evaluate(doc.root(), StartsWith, args);

        assert_eq!(Ok(Value::Boolean(true)), r);
    }

    #[test]
    fn contains_looks_for_a_needle() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = vec![Value::String("astronomer".to_string()),
                        Value::String("ono".to_string())];
        let r = setup.evaluate(doc.root(), Contains, args);

        assert_eq!(Ok(Value::Boolean(true)), r);
    }

    #[test]
    fn substring_before_slices_before() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = vec![Value::String("1999/04/01".to_string()),
                        Value::String("/".to_string())];
        let r = setup.evaluate(doc.root(), SubstringBefore, args);

        assert_eq!(Ok(Value::String("1999".to_string())), r);
    }

    #[test]
    fn substring_after_slices_after() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = vec![Value::String("1999/04/01".to_string()),
                        Value::String("/".to_string())];
        let r = setup.evaluate(doc.root(), SubstringAfter, args);

        assert_eq!(Ok(Value::String("04/01".to_string())), r);
    }
}
<commit_msg>Streamline function tests that only need literals<commit_after>use std::{error,fmt};

use super::{EvaluationContext,Functions,Value};

pub trait Function {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>;
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum ArgumentType {
    Nodeset,
    Boolean,
    Number,
    String,
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum Error {
    TooManyArguments{ expected: usize, actual: usize },
    NotEnoughArguments{ expected: usize, actual: usize },
    WrongType{ expected: ArgumentType, actual: ArgumentType },
}

impl Error {
    fn wrong_type(actual: &Value, expected: ArgumentType) -> Error {
        let actual = match *actual {
            Value::Nodes(..)   => ArgumentType::Nodeset,
            Value::String(..)  => ArgumentType::String,
            Value::Number(..)  => ArgumentType::Number,
            Value::Boolean(..) => ArgumentType::Boolean,
        };

        Error::WrongType {
            expected: expected,
            actual: actual
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            TooManyArguments{..}   => "too many arguments",
            NotEnoughArguments{..} => "not enough arguments",
            WrongType{..}          => "argument of wrong type",
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        use self::Error::*;
        match *self {
            TooManyArguments{expected, actual} => {
                write!(fmt, "too many arguments, expected {} but had {}", expected, actual)
            },
            NotEnoughArguments{expected, actual} => {
                write!(fmt, "not enough arguments, expected {} but had {}", expected, actual)
            },
            WrongType{expected, actual} => {
                write!(fmt, "argument was the wrong type, expected {:?} but had {:?}", expected, actual)
            },
        }
    }
}

fn minimum_arg_count<T>(args: &Vec<T>, minimum: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < minimum {
        Err(Error::NotEnoughArguments{expected: minimum, actual: actual})
    } else {
        Ok(())
    }
}

fn exact_arg_count<T>(args: &Vec<T>, expected: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < expected {
        Err(Error::NotEnoughArguments{ expected: expected, actual: actual })
    } else if actual > expected {
        Err(Error::TooManyArguments{ expected: expected, actual: actual })
    } else {
        Ok(())
    }
}

fn string_args(args: Vec<Value>) -> Result<Vec<String>, Error> {
    fn string_arg(v: Value) -> Result<String, Error> {
        match v {
            Value::String(s) => Ok(s),
            _ => Err(Error::wrong_type(&v, ArgumentType::String)),
        }
    }

    args.into_iter().map(string_arg).collect()
}

struct Last;

impl Function for Last {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.size() as f64))
    }
}

struct Position;

impl Function for Position {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.position() as f64))
    }
}

struct Count;

impl Function for Count {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Nodes(ref nodeset) => Ok(Value::Number(nodeset.size() as f64)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Nodeset)),
        }
    }
}

struct Concat;

impl Function for Concat {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(minimum_arg_count(&args, 2));
        let args = try!(string_args(args));
        Ok(Value::String(args.concat()))
    }
}

struct StartsWith;

impl Function for StartsWith {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let v = args[0].starts_with(&*args[1]);
        Ok(Value::Boolean(v))
    }
}

struct Contains;

impl Function for Contains {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let v = args[0].contains(&*args[1]);
        Ok(Value::Boolean(v))
    }
}

struct SubstringBefore;

impl Function for SubstringBefore {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let haystack = &args[0];

        let s = match haystack.find_str(&*args[1]) {
            Some(pos) => &haystack[..pos],
            None => "",
        };

        Ok(Value::String(s.to_string()))
    }
}

struct SubstringAfter;

impl Function for SubstringAfter {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let haystack = &args[0];
        let needle = &*args[1];

        let s = match haystack.find_str(needle) {
            Some(pos) => &haystack[pos + needle.len()..],
            None => "",
        };

        Ok(Value::String(s.to_string()))
    }
}

struct Not;

impl Function for Not {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Boolean(v) => Ok(Value::Boolean(!v)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Boolean)),
        }
    }
}

struct True;

impl Function for True {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Boolean(true))
    }
}

struct False;

impl Function for False {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Boolean(false))
    }
}

pub fn register_core_functions(functions: &mut Functions) {
    functions.insert("last".to_string(), box Last);
    functions.insert("position".to_string(), box Position);
    functions.insert("count".to_string(), box Count);
    functions.insert("concat".to_string(), box Concat);
    functions.insert("starts-with".to_string(), box StartsWith);
    functions.insert("contains".to_string(), box Contains);
    functions.insert("substring-before".to_string(), box SubstringBefore);
    functions.insert("substring-after".to_string(), box SubstringAfter);
    functions.insert("not".to_string(), box Not);
    functions.insert("true".to_string(), box True);
    functions.insert("false".to_string(), box False);
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;
    use document::Package;
    use super::super::{EvaluationContext,LiteralValue,Value,Functions,Variables,Namespaces};
    use super::super::nodeset::ToNode;
    use super::{
        Function,
        Error,
        Last,
        Position,
        Count,
        Concat,
        StartsWith,
        Contains,
        SubstringBefore,
        SubstringAfter,
    };

    struct Setup<'d> {
        functions: Functions,
        variables: Variables<'d>,
        namespaces: Namespaces,
    }

    impl<'d> Setup<'d> {
        fn new() -> Setup<'d> {
            Setup {
                functions: HashMap::new(),
                variables: HashMap::new(),
                namespaces: HashMap::new(),
            }
        }

        fn evaluate<N, F>(&self, node: N, f: F, args: Vec<Value<'d>>)
            -> Result<Value<'d>, Error>
            where N: ToNode<'d>,
                  F: Function
        {
            let context = EvaluationContext::new(
                node, &self.functions, &self.variables, &self.namespaces
            );
            f.evaluate(&context, args)
        }
    }

    fn evaluate_literal<F>(f: F, args: Vec<LiteralValue>) -> Result<LiteralValue, Error>
        where F: Function
    {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = args.into_iter().map(|a| a.into_value()).collect();

        let r = setup.evaluate(doc.root(), f, args);

        r.map(|r| r.into_literal_value())
    }

    #[test]
    fn last_returns_context_size() {
        let r = evaluate_literal(Last, vec![]);
        assert_eq!(Ok(LiteralValue::Number(1.0)), r);
    }

    #[test]
    fn position_returns_context_position() {
        let r = evaluate_literal(Position, vec![]);

        assert_eq!(Ok(LiteralValue::Number(1.0)), r);
    }

    #[test]
    fn count_counts_nodes_in_nodeset() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let nodeset = nodeset![doc.root()];
        let r = setup.evaluate(doc.root(), Count, vec![Value::Nodes(nodeset)]);

        assert_eq!(Ok(Value::Number(1.0)), r);
    }

    #[test]
    fn concat_combines_strings() {
        let args = vec![LiteralValue::String("hello".to_string()),
                        LiteralValue::String(" ".to_string()),
                        LiteralValue::String("world".to_string())];
        let r = evaluate_literal(Concat, args);

        assert_eq!(Ok(LiteralValue::String("hello world".to_string())), r);
    }

    #[test]
    fn starts_with_checks_prefixes() {
        let args = vec![LiteralValue::String("hello".to_string()),
                        LiteralValue::String("he".to_string())];
        let r = evaluate_literal(StartsWith, args);

        assert_eq!(Ok(LiteralValue::Boolean(true)), r);
    }

    #[test]
    fn contains_looks_for_a_needle() {
        let args = vec![LiteralValue::String("astronomer".to_string()),
                        LiteralValue::String("ono".to_string())];
        let r = evaluate_literal(Contains, args);

        assert_eq!(Ok(LiteralValue::Boolean(true)), r);
    }

    #[test]
    fn substring_before_slices_before() {
        let args = vec![LiteralValue::String("1999/04/01".to_string()),
                        LiteralValue::String("/".to_string())];
        let r = evaluate_literal(SubstringBefore, args);

        assert_eq!(Ok(LiteralValue::String("1999".to_string())), r);
    }

    #[test]
    fn substring_after_slices_after() {
        let args = vec![LiteralValue::String("1999/04/01".to_string()),
                        LiteralValue::String("/".to_string())];
        let r = evaluate_literal(SubstringAfter, args);

        assert_eq!(Ok(LiteralValue::String("04/01".to_string())), r);
    }
}
<|endoftext|>


<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::Entries;
use pgs_files::passwd::PasswdEntry;

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(|user| Entries::<PasswdEntry>::new(Path::new("/etc/passwd")).find(|entry| entry.uid == owner || entry.name == user))
                             .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>fix invalid crontab user lookup logic<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));
            }

            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::iter::IntoIterator;

use syntax::ast;
use syntax::codemap::{DUMMY_SP, Span};
use syntax::owned_slice::OwnedSlice;

use ident::ToIdent;
use invoke::{Invoke, Identity};
use lifetime::{IntoLifetime, IntoLifetimeDef, LifetimeDefBuilder};
use name::ToName;
use path::IntoPath;
use ty_param::TyParamBuilder;

//////////////////////////////////////////////////////////////////////////////

pub struct GenericsBuilder<F=Identity> {
    callback: F,
    span: Span,
    lifetimes: Vec<ast::LifetimeDef>,
    ty_params: Vec<ast::TyParam>,
    predicates: Vec<ast::WherePredicate>,
}

impl GenericsBuilder {
    pub fn new() -> Self {
        GenericsBuilder::new_with_callback(Identity)
    }

    pub fn from_generics(generics: ast::Generics) -> Self {
        GenericsBuilder::from_generics_with_callback(Identity, generics)
    }
}

impl<F> GenericsBuilder<F>
    where F: Invoke<ast::Generics>,
{
    pub fn new_with_callback(callback: F) -> Self {
        GenericsBuilder {
            callback: callback,
            span: DUMMY_SP,
            lifetimes: Vec::new(),
            ty_params: Vec::new(),
            predicates: Vec::new(),
        }
    }

    pub fn from_generics_with_callback(callback: F, generics: ast::Generics) -> Self {
        GenericsBuilder {
            callback: callback,
            span: DUMMY_SP,
            lifetimes: generics.lifetimes,
            ty_params: generics.ty_params.into_vec(),
            predicates: generics.where_clause.predicates,
        }
    }

    pub fn with(self, generics: ast::Generics) -> Self {
        self.with_lifetimes(generics.lifetimes.into_iter())
            .with_ty_params(generics.ty_params.move_iter())
            .with_predicates(generics.where_clause.predicates.into_iter())
    }

    pub fn span(mut self, span: Span) -> Self {
        self.span = span;
        self
    }

    pub fn with_lifetimes<I, L>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=L>,
              L: IntoLifetimeDef,
    {
        let iter = iter.into_iter().map(|lifetime_def| lifetime_def.into_lifetime_def());
        self.lifetimes.extend(iter);
        self
    }

    pub fn with_lifetime_names<I, N>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=N>,
              N: ToName,
    {
        for name in iter {
            self = self.lifetime_name(name);
        }
        self
    }

    pub fn with_lifetime(mut self, lifetime: ast::LifetimeDef) -> Self {
        self.lifetimes.push(lifetime);
        self
    }

    pub fn lifetime_name<N>(self, name: N) -> Self
        where N: ToName,
    {
        self.lifetime(name).build()
    }

    pub fn lifetime<N>(self, name: N) -> LifetimeDefBuilder<Self>
        where N: ToName,
    {
        LifetimeDefBuilder::new_with_callback(name, self)
    }

    pub fn with_ty_params<I>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=ast::TyParam>,
    {
        self.ty_params.extend(iter);
        self
    }

    pub fn with_ty_param_ids<I, T>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=T>,
              T: ToIdent,
    {
        for id in iter {
            self = self.ty_param_id(id);
        }
        self
    }

    pub fn with_ty_param(mut self, ty_param: ast::TyParam) -> Self {
        self.ty_params.push(ty_param);
        self
    }

    pub fn ty_param_id<I>(self, id: I) -> Self
        where I: ToIdent,
    {
        self.ty_param(id).build()
    }

    pub fn ty_param<I>(self, id: I) -> TyParamBuilder<Self>
        where I: ToIdent,
    {
        let span = self.span;
        TyParamBuilder::new_with_callback(id, self).span(span)
    }

    pub fn with_predicates<I>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=ast::WherePredicate>,
    {
        self.predicates.extend(iter);
        self
    }

    pub fn with_predicate(mut self, predicate: ast::WherePredicate) -> Self {
        self.predicates.push(predicate);
        self
    }

    pub fn add_lifetime_bound<L>(mut self, lifetime: L) -> Self
        where L: IntoLifetime,
    {
        let lifetime = lifetime.into_lifetime();

        for lifetime_def in self.lifetimes.iter_mut() {
            lifetime_def.bounds.push(lifetime.clone());
        }

        for ty_param in self.ty_params.iter_mut() {
            *ty_param = TyParamBuilder::from_ty_param(ty_param.clone())
                .lifetime_bound(lifetime.clone())
                .build();
        }

        self
    }

    pub fn add_ty_param_bound<P>(mut self, path: P) -> Self
        where P: IntoPath,
    {
        let path = path.into_path();

        for ty_param in self.ty_params.iter_mut() {
            *ty_param = TyParamBuilder::from_ty_param(ty_param.clone())
                .trait_bound(path.clone()).build()
                .build();
        }

        self
    }

    pub fn strip_bounds(mut self) -> Self {
        self.strip_lifetimes()
            .strip_ty_params()
            .strip_predicates()
    }

    pub fn strip_lifetimes(mut self) -> Self {
        for lifetime in self.lifetimes.iter_mut() {
            lifetime.bounds = vec![];
        }
        self
    }

    pub fn strip_ty_params(mut self) -> Self {
        for ty_param in self.ty_params.iter_mut() {
            ty_param.bounds = OwnedSlice::empty();
        }
        self
    }

    pub fn strip_predicates(mut self) -> Self {
        self.predicates = vec![];
        self
    }

    pub fn build(self) -> F::Result {
        self.callback.invoke(ast::Generics {
            lifetimes: self.lifetimes,
            ty_params: OwnedSlice::from_vec(self.ty_params),
            where_clause: ast::WhereClause {
                id: ast::DUMMY_NODE_ID,
                predicates: self.predicates,
            },
        })
    }
}

impl<F> Invoke<ast::LifetimeDef> for GenericsBuilder<F>
    where F: Invoke<ast::Generics>,
{
    type Result = Self;

    fn invoke(self, lifetime: ast::LifetimeDef) -> Self {
        self.with_lifetime(lifetime)
    }
}

impl<F> Invoke<ast::TyParam> for GenericsBuilder<F>
    where F: Invoke<ast::Generics>,
{
    type Result = Self;

    fn invoke(self, ty_param: ast::TyParam) -> Self {
        self.with_ty_param(ty_param)
    }
}
<commit_msg>Silence a warning<commit_after>use std::iter::IntoIterator;

use syntax::ast;
use syntax::codemap::{DUMMY_SP, Span};
use syntax::owned_slice::OwnedSlice;

use ident::ToIdent;
use invoke::{Invoke, Identity};
use lifetime::{IntoLifetime, IntoLifetimeDef, LifetimeDefBuilder};
use name::ToName;
use path::IntoPath;
use ty_param::TyParamBuilder;

//////////////////////////////////////////////////////////////////////////////

pub struct GenericsBuilder<F=Identity> {
    callback: F,
    span: Span,
    lifetimes: Vec<ast::LifetimeDef>,
    ty_params: Vec<ast::TyParam>,
    predicates: Vec<ast::WherePredicate>,
}

impl GenericsBuilder {
    pub fn new() -> Self {
        GenericsBuilder::new_with_callback(Identity)
    }

    pub fn from_generics(generics: ast::Generics) -> Self {
        GenericsBuilder::from_generics_with_callback(Identity, generics)
    }
}

impl<F> GenericsBuilder<F>
    where F: Invoke<ast::Generics>,
{
    pub fn new_with_callback(callback: F) -> Self {
        GenericsBuilder {
            callback: callback,
            span: DUMMY_SP,
            lifetimes: Vec::new(),
            ty_params: Vec::new(),
            predicates: Vec::new(),
        }
    }

    pub fn from_generics_with_callback(callback: F, generics: ast::Generics) -> Self {
        GenericsBuilder {
            callback: callback,
            span: DUMMY_SP,
            lifetimes: generics.lifetimes,
            ty_params: generics.ty_params.into_vec(),
            predicates: generics.where_clause.predicates,
        }
    }

    pub fn with(self, generics: ast::Generics) -> Self {
        self.with_lifetimes(generics.lifetimes.into_iter())
            .with_ty_params(generics.ty_params.move_iter())
            .with_predicates(generics.where_clause.predicates.into_iter())
    }

    pub fn span(mut self, span: Span) -> Self {
        self.span = span;
        self
    }

    pub fn with_lifetimes<I, L>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=L>,
              L: IntoLifetimeDef,
    {
        let iter = iter.into_iter().map(|lifetime_def| lifetime_def.into_lifetime_def());
        self.lifetimes.extend(iter);
        self
    }

    pub fn with_lifetime_names<I, N>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=N>,
              N: ToName,
    {
        for name in iter {
            self = self.lifetime_name(name);
        }
        self
    }

    pub fn with_lifetime(mut self, lifetime: ast::LifetimeDef) -> Self {
        self.lifetimes.push(lifetime);
        self
    }

    pub fn lifetime_name<N>(self, name: N) -> Self
        where N: ToName,
    {
        self.lifetime(name).build()
    }

    pub fn lifetime<N>(self, name: N) -> LifetimeDefBuilder<Self>
        where N: ToName,
    {
        LifetimeDefBuilder::new_with_callback(name, self)
    }

    pub fn with_ty_params<I>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=ast::TyParam>,
    {
        self.ty_params.extend(iter);
        self
    }

    pub fn with_ty_param_ids<I, T>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=T>,
              T: ToIdent,
    {
        for id in iter {
            self = self.ty_param_id(id);
        }
        self
    }

    pub fn with_ty_param(mut self, ty_param: ast::TyParam) -> Self {
        self.ty_params.push(ty_param);
        self
    }

    pub fn ty_param_id<I>(self, id: I) -> Self
        where I: ToIdent,
    {
        self.ty_param(id).build()
    }

    pub fn ty_param<I>(self, id: I) -> TyParamBuilder<Self>
        where I: ToIdent,
    {
        let span = self.span;
        TyParamBuilder::new_with_callback(id, self).span(span)
    }

    pub fn with_predicates<I>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=ast::WherePredicate>,
    {
        self.predicates.extend(iter);
        self
    }

    pub fn with_predicate(mut self, predicate: ast::WherePredicate) -> Self {
        self.predicates.push(predicate);
        self
    }

    pub fn add_lifetime_bound<L>(mut self, lifetime: L) -> Self
        where L: IntoLifetime,
    {
        let lifetime = lifetime.into_lifetime();

        for lifetime_def in self.lifetimes.iter_mut() {
            lifetime_def.bounds.push(lifetime.clone());
        }

        for ty_param in self.ty_params.iter_mut() {
            *ty_param = TyParamBuilder::from_ty_param(ty_param.clone())
                .lifetime_bound(lifetime.clone())
                .build();
        }

        self
    }

    pub fn add_ty_param_bound<P>(mut self, path: P) -> Self
        where P: IntoPath,
    {
        let path = path.into_path();

        for ty_param in self.ty_params.iter_mut() {
            *ty_param = TyParamBuilder::from_ty_param(ty_param.clone())
                .trait_bound(path.clone()).build()
                .build();
        }

        self
    }

    pub fn strip_bounds(self) -> Self {
        self.strip_lifetimes()
            .strip_ty_params()
            .strip_predicates()
    }

    pub fn strip_lifetimes(mut self) -> Self {
        for lifetime in self.lifetimes.iter_mut() {
            lifetime.bounds = vec![];
        }
        self
    }

    pub fn strip_ty_params(mut self) -> Self {
        for ty_param in self.ty_params.iter_mut() {
            ty_param.bounds = OwnedSlice::empty();
        }
        self
    }

    pub fn strip_predicates(mut self) -> Self {
        self.predicates = vec![];
        self
    }

    pub fn build(self) -> F::Result {
        self.callback.invoke(ast::Generics {
            lifetimes: self.lifetimes,
            ty_params: OwnedSlice::from_vec(self.ty_params),
            where_clause: ast::WhereClause {
                id: ast::DUMMY_NODE_ID,
                predicates: self.predicates,
            },
        })
    }
}

impl<F> Invoke<ast::LifetimeDef> for GenericsBuilder<F>
    where F: Invoke<ast::Generics>,
{
    type Result = Self;

    fn invoke(self, lifetime: ast::LifetimeDef) -> Self {
        self.with_lifetime(lifetime)
    }
}

impl<F> Invoke<ast::TyParam> for GenericsBuilder<F>
    where F: Invoke<ast::Generics>,
{
    type Result = Self;

    fn invoke(self, ty_param: ast::TyParam) -> Self {
        self.with_ty_param(ty_param)
    }
}
<|endoftext|>


<commit_before>use common::eof;
use nom::branch::alt;
use nom::bytes::complete::{tag, tag_no_case};
use nom::combinator::peek;
use nom::sequence::terminated;
use nom::IResult;

fn keyword_follow_char(i: &[u8]) -> IResult<&[u8], &[u8]> {
    peek(alt((
        tag(" "),
        tag("\n"),
        tag(";"),
        tag("("),
        tag(")"),
        tag("\t"),
        tag(","),
        tag("="),
        eof,
    )))(i)
}

fn keyword_a_to_c(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("ABORT"), keyword_follow_char),
        terminated(tag_no_case("ACTION"), keyword_follow_char),
        terminated(tag_no_case("ADD"), keyword_follow_char),
        terminated(tag_no_case("AFTER"), keyword_follow_char),
        terminated(tag_no_case("ALL"), keyword_follow_char),
        terminated(tag_no_case("ALTER"), keyword_follow_char),
        terminated(tag_no_case("ANALYZE"), keyword_follow_char),
        terminated(tag_no_case("AND"), keyword_follow_char),
        terminated(tag_no_case("AS"), keyword_follow_char),
        terminated(tag_no_case("ASC"), keyword_follow_char),
        terminated(tag_no_case("ATTACH"), keyword_follow_char),
        terminated(tag_no_case("AUTOINCREMENT"), keyword_follow_char),
        terminated(tag_no_case("BEFORE"), keyword_follow_char),
        terminated(tag_no_case("BEGIN"), keyword_follow_char),
        terminated(tag_no_case("BETWEEN"), keyword_follow_char),
        terminated(tag_no_case("BY"), keyword_follow_char),
        terminated(tag_no_case("CASCADE"), keyword_follow_char),
        terminated(tag_no_case("CASE"), keyword_follow_char),
        terminated(tag_no_case("CAST"), keyword_follow_char),
        terminated(tag_no_case("CHECK"), keyword_follow_char),
        terminated(tag_no_case("COLLATE"), keyword_follow_char),
    ))(i)
}

fn keyword_c_to_e(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("COLUMN"), keyword_follow_char),
        terminated(tag_no_case("COMMIT"), keyword_follow_char),
        terminated(tag_no_case("CONFLICT"), keyword_follow_char),
        terminated(tag_no_case("CONSTRAINT"), keyword_follow_char),
        terminated(tag_no_case("CREATE"), keyword_follow_char),
        terminated(tag_no_case("CROSS"), keyword_follow_char),
        terminated(tag_no_case("CURRENT_DATE"), keyword_follow_char),
        terminated(tag_no_case("CURRENT_TIME"), keyword_follow_char),
        terminated(tag_no_case("CURRENT_TIMESTAMP"), keyword_follow_char),
        terminated(tag_no_case("DATABASE"), keyword_follow_char),
        terminated(tag_no_case("DEFAULT"), keyword_follow_char),
        terminated(tag_no_case("DEFERRABLE"), keyword_follow_char),
        terminated(tag_no_case("DEFERRED"), keyword_follow_char),
        terminated(tag_no_case("DELETE"), keyword_follow_char),
        terminated(tag_no_case("DESC"), keyword_follow_char),
        terminated(tag_no_case("DETACH"), keyword_follow_char),
        terminated(tag_no_case("DISTINCT"), keyword_follow_char),
        terminated(tag_no_case("DROP"), keyword_follow_char),
        terminated(tag_no_case("EACH"), keyword_follow_char),
        terminated(tag_no_case("ELSE"), keyword_follow_char),
        terminated(tag_no_case("END"), keyword_follow_char),
    ))(i)
}

fn keyword_e_to_i(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("ESCAPE"), keyword_follow_char),
        terminated(tag_no_case("EXCEPT"), keyword_follow_char),
        terminated(tag_no_case("EXCLUSIVE"), keyword_follow_char),
        terminated(tag_no_case("EXISTS"), keyword_follow_char),
        terminated(tag_no_case("EXPLAIN"), keyword_follow_char),
        terminated(tag_no_case("FAIL"), keyword_follow_char),
        terminated(tag_no_case("FOR"), keyword_follow_char),
        terminated(tag_no_case("FOREIGN"), keyword_follow_char),
        terminated(tag_no_case("FROM"), keyword_follow_char),
        terminated(tag_no_case("FULL"), keyword_follow_char),
        terminated(tag_no_case("FULLTEXT"), keyword_follow_char),
        terminated(tag_no_case("GLOB"), keyword_follow_char),
        terminated(tag_no_case("GROUP"), keyword_follow_char),
        terminated(tag_no_case("HAVING"), keyword_follow_char),
        terminated(tag_no_case("IF"), keyword_follow_char),
        terminated(tag_no_case("IGNORE"), keyword_follow_char),
        terminated(tag_no_case("IMMEDIATE"), keyword_follow_char),
        terminated(tag_no_case("IN"), keyword_follow_char),
        terminated(tag_no_case("INDEX"), keyword_follow_char),
        terminated(tag_no_case("INDEXED"), keyword_follow_char),
        terminated(tag_no_case("INITIALLY"), keyword_follow_char),
    ))(i)
}

fn keyword_i_to_o(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("INNER"), keyword_follow_char),
        terminated(tag_no_case("INSERT"), keyword_follow_char),
        terminated(tag_no_case("INSTEAD"), keyword_follow_char),
        terminated(tag_no_case("INTERSECT"), keyword_follow_char),
        terminated(tag_no_case("INTO"), keyword_follow_char),
        terminated(tag_no_case("IS"), keyword_follow_char),
        terminated(tag_no_case("ISNULL"), keyword_follow_char),
        terminated(tag_no_case("ORDER"), keyword_follow_char),
        terminated(tag_no_case("JOIN"), keyword_follow_char),
        terminated(tag_no_case("KEY"), keyword_follow_char),
        terminated(tag_no_case("LEFT"), keyword_follow_char),
        terminated(tag_no_case("LIKE"), keyword_follow_char),
        terminated(tag_no_case("LIMIT"), keyword_follow_char),
        terminated(tag_no_case("MATCH"), keyword_follow_char),
        terminated(tag_no_case("NATURAL"), keyword_follow_char),
        terminated(tag_no_case("NO"), keyword_follow_char),
        terminated(tag_no_case("NOT"), keyword_follow_char),
        terminated(tag_no_case("NOTNULL"), keyword_follow_char),
        terminated(tag_no_case("NULL"), keyword_follow_char),
        terminated(tag_no_case("OF"), keyword_follow_char),
        terminated(tag_no_case("OFFSET"), keyword_follow_char),
    ))(i)
}

fn keyword_o_to_s(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("ON"), keyword_follow_char),
        terminated(tag_no_case("OR"), keyword_follow_char),
        terminated(tag_no_case("OUTER"), keyword_follow_char),
        terminated(tag_no_case("PLAN"), keyword_follow_char),
        terminated(tag_no_case("PRAGMA"), keyword_follow_char),
        terminated(tag_no_case("PRIMARY"), keyword_follow_char),
        terminated(tag_no_case("QUERY"), keyword_follow_char),
        terminated(tag_no_case("RAISE"), keyword_follow_char),
        terminated(tag_no_case("RECURSIVE"), keyword_follow_char),
        terminated(tag_no_case("REFERENCES"), keyword_follow_char),
        terminated(tag_no_case("REGEXP"), keyword_follow_char),
        terminated(tag_no_case("REINDEX"), keyword_follow_char),
        terminated(tag_no_case("RELEASE"), keyword_follow_char),
        terminated(tag_no_case("RENAME"), keyword_follow_char),
        terminated(tag_no_case("REPLACE"), keyword_follow_char),
        terminated(tag_no_case("RESTRICT"), keyword_follow_char),
        terminated(tag_no_case("RIGHT"), keyword_follow_char),
        terminated(tag_no_case("ROLLBACK"), keyword_follow_char),
        terminated(tag_no_case("ROW"), keyword_follow_char),
        terminated(tag_no_case("SAVEPOINT"), keyword_follow_char),
        terminated(tag_no_case("SELECT"), keyword_follow_char),
    ))(i)
}

fn keyword_s_to_z(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("SET"), keyword_follow_char),
        terminated(tag_no_case("TABLE"), keyword_follow_char),
        terminated(tag_no_case("TEMP"), keyword_follow_char),
        terminated(tag_no_case("TEMPORARY"), keyword_follow_char),
        terminated(tag_no_case("THEN"), keyword_follow_char),
        terminated(tag_no_case("TO"), keyword_follow_char),
        terminated(tag_no_case("TRANSACTION"), keyword_follow_char),
        terminated(tag_no_case("TRIGGER"), keyword_follow_char),
        terminated(tag_no_case("UNION"), keyword_follow_char),
        terminated(tag_no_case("UNIQUE"), keyword_follow_char),
        terminated(tag_no_case("UPDATE"), keyword_follow_char),
        terminated(tag_no_case("USING"), keyword_follow_char),
        terminated(tag_no_case("VACUUM"), keyword_follow_char),
        terminated(tag_no_case("VALUES"), keyword_follow_char),
        terminated(tag_no_case("VIEW"), keyword_follow_char),
        terminated(tag_no_case("VIRTUAL"), keyword_follow_char),
        terminated(tag_no_case("WHEN"), keyword_follow_char),
        terminated(tag_no_case("WHERE"), keyword_follow_char),
        terminated(tag_no_case("WITH"), keyword_follow_char),
        terminated(tag_no_case("WITHOUT"), keyword_follow_char),
    ))(i)
}

// Matches any SQL reserved keyword
pub fn sql_keyword(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        keyword_a_to_c,
        keyword_c_to_e,
        keyword_e_to_i,
        keyword_i_to_o,
        keyword_o_to_s,
        keyword_s_to_z,
    ))(i)
}

pub fn escape_if_keyword(s: &str) -> String {
    if sql_keyword(s.as_bytes()).is_ok() {
        format!("`{}`", s)
    } else {
        s.to_owned()
    }
}
<commit_msg>Add comment explaining max tuple size for alt<commit_after>use common::eof;
use nom::branch::alt;
use nom::bytes::complete::{tag, tag_no_case};
use nom::combinator::peek;
use nom::sequence::terminated;
use nom::IResult;

// NOTE: Each keyword_$start_letter_to_$end_letter function uses `alt`,
// which is implemented for tuples sizes up to 21. Because of this constraint
// on maximum tuple sizes, keywords are aggregated into groups of 20

fn keyword_follow_char(i: &[u8]) -> IResult<&[u8], &[u8]> {
    peek(alt((
        tag(" "),
        tag("\n"),
        tag(";"),
        tag("("),
        tag(")"),
        tag("\t"),
        tag(","),
        tag("="),
        eof,
    )))(i)
}

fn keyword_a_to_c(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("ABORT"), keyword_follow_char),
        terminated(tag_no_case("ACTION"), keyword_follow_char),
        terminated(tag_no_case("ADD"), keyword_follow_char),
        terminated(tag_no_case("AFTER"), keyword_follow_char),
        terminated(tag_no_case("ALL"), keyword_follow_char),
        terminated(tag_no_case("ALTER"), keyword_follow_char),
        terminated(tag_no_case("ANALYZE"), keyword_follow_char),
        terminated(tag_no_case("AND"), keyword_follow_char),
        terminated(tag_no_case("AS"), keyword_follow_char),
        terminated(tag_no_case("ASC"), keyword_follow_char),
        terminated(tag_no_case("ATTACH"), keyword_follow_char),
        terminated(tag_no_case("AUTOINCREMENT"), keyword_follow_char),
        terminated(tag_no_case("BEFORE"), keyword_follow_char),
        terminated(tag_no_case("BEGIN"), keyword_follow_char),
        terminated(tag_no_case("BETWEEN"), keyword_follow_char),
        terminated(tag_no_case("BY"), keyword_follow_char),
        terminated(tag_no_case("CASCADE"), keyword_follow_char),
        terminated(tag_no_case("CASE"), keyword_follow_char),
        terminated(tag_no_case("CAST"), keyword_follow_char),
        terminated(tag_no_case("CHECK"), keyword_follow_char),
        terminated(tag_no_case("COLLATE"), keyword_follow_char),
    ))(i)
}

fn keyword_c_to_e(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("COLUMN"), keyword_follow_char),
        terminated(tag_no_case("COMMIT"), keyword_follow_char),
        terminated(tag_no_case("CONFLICT"), keyword_follow_char),
        terminated(tag_no_case("CONSTRAINT"), keyword_follow_char),
        terminated(tag_no_case("CREATE"), keyword_follow_char),
        terminated(tag_no_case("CROSS"), keyword_follow_char),
        terminated(tag_no_case("CURRENT_DATE"), keyword_follow_char),
        terminated(tag_no_case("CURRENT_TIME"), keyword_follow_char),
        terminated(tag_no_case("CURRENT_TIMESTAMP"), keyword_follow_char),
        terminated(tag_no_case("DATABASE"), keyword_follow_char),
        terminated(tag_no_case("DEFAULT"), keyword_follow_char),
        terminated(tag_no_case("DEFERRABLE"), keyword_follow_char),
        terminated(tag_no_case("DEFERRED"), keyword_follow_char),
        terminated(tag_no_case("DELETE"), keyword_follow_char),
        terminated(tag_no_case("DESC"), keyword_follow_char),
        terminated(tag_no_case("DETACH"), keyword_follow_char),
        terminated(tag_no_case("DISTINCT"), keyword_follow_char),
        terminated(tag_no_case("DROP"), keyword_follow_char),
        terminated(tag_no_case("EACH"), keyword_follow_char),
        terminated(tag_no_case("ELSE"), keyword_follow_char),
        terminated(tag_no_case("END"), keyword_follow_char),
    ))(i)
}

fn keyword_e_to_i(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("ESCAPE"), keyword_follow_char),
        terminated(tag_no_case("EXCEPT"), keyword_follow_char),
        terminated(tag_no_case("EXCLUSIVE"), keyword_follow_char),
        terminated(tag_no_case("EXISTS"), keyword_follow_char),
        terminated(tag_no_case("EXPLAIN"), keyword_follow_char),
        terminated(tag_no_case("FAIL"), keyword_follow_char),
        terminated(tag_no_case("FOR"), keyword_follow_char),
        terminated(tag_no_case("FOREIGN"), keyword_follow_char),
        terminated(tag_no_case("FROM"), keyword_follow_char),
        terminated(tag_no_case("FULL"), keyword_follow_char),
        terminated(tag_no_case("FULLTEXT"), keyword_follow_char),
        terminated(tag_no_case("GLOB"), keyword_follow_char),
        terminated(tag_no_case("GROUP"), keyword_follow_char),
        terminated(tag_no_case("HAVING"), keyword_follow_char),
        terminated(tag_no_case("IF"), keyword_follow_char),
        terminated(tag_no_case("IGNORE"), keyword_follow_char),
        terminated(tag_no_case("IMMEDIATE"), keyword_follow_char),
        terminated(tag_no_case("IN"), keyword_follow_char),
        terminated(tag_no_case("INDEX"), keyword_follow_char),
        terminated(tag_no_case("INDEXED"), keyword_follow_char),
        terminated(tag_no_case("INITIALLY"), keyword_follow_char),
    ))(i)
}

fn keyword_i_to_o(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("INNER"), keyword_follow_char),
        terminated(tag_no_case("INSERT"), keyword_follow_char),
        terminated(tag_no_case("INSTEAD"), keyword_follow_char),
        terminated(tag_no_case("INTERSECT"), keyword_follow_char),
        terminated(tag_no_case("INTO"), keyword_follow_char),
        terminated(tag_no_case("IS"), keyword_follow_char),
        terminated(tag_no_case("ISNULL"), keyword_follow_char),
        terminated(tag_no_case("ORDER"), keyword_follow_char),
        terminated(tag_no_case("JOIN"), keyword_follow_char),
        terminated(tag_no_case("KEY"), keyword_follow_char),
        terminated(tag_no_case("LEFT"), keyword_follow_char),
        terminated(tag_no_case("LIKE"), keyword_follow_char),
        terminated(tag_no_case("LIMIT"), keyword_follow_char),
        terminated(tag_no_case("MATCH"), keyword_follow_char),
        terminated(tag_no_case("NATURAL"), keyword_follow_char),
        terminated(tag_no_case("NO"), keyword_follow_char),
        terminated(tag_no_case("NOT"), keyword_follow_char),
        terminated(tag_no_case("NOTNULL"), keyword_follow_char),
        terminated(tag_no_case("NULL"), keyword_follow_char),
        terminated(tag_no_case("OF"), keyword_follow_char),
        terminated(tag_no_case("OFFSET"), keyword_follow_char),
    ))(i)
}

fn keyword_o_to_s(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("ON"), keyword_follow_char),
        terminated(tag_no_case("OR"), keyword_follow_char),
        terminated(tag_no_case("OUTER"), keyword_follow_char),
        terminated(tag_no_case("PLAN"), keyword_follow_char),
        terminated(tag_no_case("PRAGMA"), keyword_follow_char),
        terminated(tag_no_case("PRIMARY"), keyword_follow_char),
        terminated(tag_no_case("QUERY"), keyword_follow_char),
        terminated(tag_no_case("RAISE"), keyword_follow_char),
        terminated(tag_no_case("RECURSIVE"), keyword_follow_char),
        terminated(tag_no_case("REFERENCES"), keyword_follow_char),
        terminated(tag_no_case("REGEXP"), keyword_follow_char),
        terminated(tag_no_case("REINDEX"), keyword_follow_char),
        terminated(tag_no_case("RELEASE"), keyword_follow_char),
        terminated(tag_no_case("RENAME"), keyword_follow_char),
        terminated(tag_no_case("REPLACE"), keyword_follow_char),
        terminated(tag_no_case("RESTRICT"), keyword_follow_char),
        terminated(tag_no_case("RIGHT"), keyword_follow_char),
        terminated(tag_no_case("ROLLBACK"), keyword_follow_char),
        terminated(tag_no_case("ROW"), keyword_follow_char),
        terminated(tag_no_case("SAVEPOINT"), keyword_follow_char),
        terminated(tag_no_case("SELECT"), keyword_follow_char),
    ))(i)
}

fn keyword_s_to_z(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        terminated(tag_no_case("SET"), keyword_follow_char),
        terminated(tag_no_case("TABLE"), keyword_follow_char),
        terminated(tag_no_case("TEMP"), keyword_follow_char),
        terminated(tag_no_case("TEMPORARY"), keyword_follow_char),
        terminated(tag_no_case("THEN"), keyword_follow_char),
        terminated(tag_no_case("TO"), keyword_follow_char),
        terminated(tag_no_case("TRANSACTION"), keyword_follow_char),
        terminated(tag_no_case("TRIGGER"), keyword_follow_char),
        terminated(tag_no_case("UNION"), keyword_follow_char),
        terminated(tag_no_case("UNIQUE"), keyword_follow_char),
        terminated(tag_no_case("UPDATE"), keyword_follow_char),
        terminated(tag_no_case("USING"), keyword_follow_char),
        terminated(tag_no_case("VACUUM"), keyword_follow_char),
        terminated(tag_no_case("VALUES"), keyword_follow_char),
        terminated(tag_no_case("VIEW"), keyword_follow_char),
        terminated(tag_no_case("VIRTUAL"), keyword_follow_char),
        terminated(tag_no_case("WHEN"), keyword_follow_char),
        terminated(tag_no_case("WHERE"), keyword_follow_char),
        terminated(tag_no_case("WITH"), keyword_follow_char),
        terminated(tag_no_case("WITHOUT"), keyword_follow_char),
    ))(i)
}

// Matches any SQL reserved keyword
pub fn sql_keyword(i: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((
        keyword_a_to_c,
        keyword_c_to_e,
        keyword_e_to_i,
        keyword_i_to_o,
        keyword_o_to_s,
        keyword_s_to_z,
    ))(i)
}

pub fn escape_if_keyword(s: &str) -> String {
    if sql_keyword(s.as_bytes()).is_ok() {
        format!("`{}`", s)
    } else {
        s.to_owned()
    }
}
<|endoftext|>