<commit_before>// This file is part of Grust, GObject introspection bindings for Rust
//
// Copyright (C) 2013, 2014  Mikhail Zabaluev <mikhail.zabaluev@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

#![crate_name = "grust-Gio-2_0"]
#![crate_type = "lib"]

#![feature(core)]
#![feature(hash)]

#[macro_use]
extern crate grust;

#[macro_use]
extern crate bitflags;

extern crate "gio-2_0-sys" as ffi;
extern crate "glib-2_0-sys" as glib_ffi;
extern crate "gobject-2_0-sys" as gobject_ffi;
extern crate "grust-GLib-2_0" as glib;
extern crate "grust-GObject-2_0" as gobject;

use grust::enumeration;
use grust::enumeration::IntrospectedEnum as _grust_IntrospectedEnumTrait;
use grust::error;
use grust::gstr;
use grust::gtype::GType;
use grust::marker;
use grust::object;
use grust::quark;
use grust::refcount;
use grust::types::{gint, gpointer};
use grust::wrap;

use std::fmt;
use std::mem;
use std::num;
use std::ptr;
use std::result;

#[repr(C)]
pub struct AsyncResult {
    raw: ffi::GAsyncResult,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for AsyncResult {
    type Raw = ffi::GAsyncResult;
}

#[repr(C)]
pub struct File {
    raw: ffi::GFile,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for File {
    type Raw = ffi::GFile;
}

#[repr(C)]
pub struct Cancellable {
    raw: ffi::GCancellable,
    _marker: marker::ObjectMarker
}

unsafe impl Send for Cancellable { }
unsafe impl Sync for Cancellable { }
unsafe impl wrap::Wrapper for Cancellable {
    type Raw = ffi::GCancellable;
}

#[repr(C)]
pub struct InputStream {
    raw: ffi::GInputStream,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for InputStream {
    type Raw = ffi::GInputStream;
}

#[repr(C)]
pub struct FileInputStream {
    raw: ffi::GFileInputStream,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for FileInputStream {
    type Raw = ffi::GFileInputStream;
}

#[derive(Copy, PartialEq, Eq, FromPrimitive, Debug)]
#[repr(C)]
pub enum IOErrorEnum {
    Failed = 0,
    NotFound = 1,
    Exists = 2,
    // ...
}

impl enumeration::IntrospectedEnum for IOErrorEnum {

    fn from_int(v: gint) -> Result<Self, enumeration::UnknownValue> {
        num::from_i32(v as i32)
            .ok_or_else(|| enumeration::UnknownValue(v))
    }

    fn to_int(&self) -> gint {
        *self as gint
    }

    fn name(&self) -> &'static str {
        match *self {
            IOErrorEnum::Failed => "failed",
            IOErrorEnum::NotFound => "not-found",
            IOErrorEnum::Exists   => "exists",
            // ...
        }
    }
}

impl enumeration::EnumType for IOErrorEnum {
    fn get_type() -> GType {
        unsafe { GType::from_raw(ffi::g_io_error_enum_get_type()) }
    }
}

impl error::Domain for IOErrorEnum {
    fn domain() -> quark::Quark {
        g_static_quark!(b"g-io-error-quark\0")
    }
}

impl fmt::Display for IOErrorEnum {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

pub mod flags {

    pub mod file_attribute_info {
        use grust::flags::prelude::*;
        use ffi;
        use std::fmt;

        bitflags! {
            flags Flags: guint {
                const NONE            = 0,
                const COPY_WITH_FILE  = 1,
                const COPY_WHEN_MOVED = 2,
            }
        }

        impl IntrospectedFlags for Flags {

            fn from_uint(v: guint) -> Result<Flags, UnknownFlags> {
                Flags::from_bits(v)
                    .ok_or_else(|| UnknownFlags::new(v, Flags::all().bits()))
            }

            #[inline]
            fn to_uint(&self) -> guint {
                self.bits()
            }
        }

        impl FlagsType for Flags {
            fn get_type() -> GType {
                unsafe {
                    let raw = ffi::g_file_attribute_info_flags_get_type();
                    GType::from_raw(raw)
                }
            }
        }

        impl fmt::Debug for Flags {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                const FLAG_INFO: &'static [(Flags, &'static str)] = &[
                    (COPY_WITH_FILE,  "COPY_WITH_FILE"),
                    (COPY_WHEN_MOVED, "COPY_WHEN_MOVED")
                ];
                let mut contents = String::new();
                for &(flag, name) in FLAG_INFO.iter() {
                    if self.contains(flag) {
                        if contents.is_empty() {
                            contents.push_str(name);
                        } else {
                            contents.push('|');
                            contents.push_str(name);
                        }
                    }
                }
                write!(f, "FileAttributeInfoFlags({})", contents)
            }
        }
    }
}

pub use flags::file_attribute_info::Flags as FileAttributeInfoFlags;

mod async {
    use ffi;
    use gobject_ffi;
    use gobject;

    use grust::types::gpointer;
    use grust::wrap;
    use std::mem;

    pub extern "C" fn async_ready_callback<F>(source_object: *mut gobject_ffi::GObject,
                                              res: *mut ffi::GAsyncResult,
                                              user_data: gpointer)
        where F: FnOnce(&gobject::Object, &super::AsyncResult)
    {
        let cb: Box<F> = unsafe { mem::transmute(user_data) };
        let arg1 = unsafe { wrap::from_raw::<gobject::Object>(source_object) };
        let arg2 = unsafe { wrap::from_raw::<super::AsyncResult>(res) };
        cb(arg1, arg2);
    }
}

pub mod cast {
    use grust::object;

    use gobject;

    pub trait AsAsyncResult {
        fn as_async_result(&self) -> &super::AsyncResult;
    }

    impl<T> AsAsyncResult for T where T: object::Upcast<super::AsyncResult> {

        #[inline]
        fn as_async_result(&self) -> &super::AsyncResult {
            self.upcast()
        }
    }

    pub trait AsCancellable : gobject::cast::AsObject {
        fn as_cancellable(&self) -> &super::Cancellable;
    }

    impl<T> AsCancellable for T
        where T: object::Upcast<super::Cancellable>,
              T: object::Upcast<gobject::Object>
    {
        #[inline]
        fn as_cancellable(&self) -> &super::Cancellable {
            self.upcast()
        }
    }

    pub trait AsInputStream : gobject::cast::AsObject {
        fn as_input_stream(&self) -> &super::InputStream;
    }

    impl<T> AsInputStream for T
        where T: object::Upcast<super::InputStream>,
              T: object::Upcast<gobject::Object>
    {
        #[inline]
        fn as_input_stream(&self) -> &super::InputStream {
            self.upcast()
        }
    }

    pub trait AsFileInputStream : AsInputStream {
        fn as_file_input_stream(&self) -> &super::FileInputStream;
    }

    impl<T> AsFileInputStream for T
        where T: object::Upcast<super::FileInputStream>,
              T: object::Upcast<super::InputStream>,
              T: object::Upcast<gobject::Object>
    {
        #[inline]
        fn as_file_input_stream(&self) -> &super::FileInputStream {
            self.upcast()
        }
    }

    pub trait AsFile {
        fn as_file(&self) -> &super::File;
    }

    impl<T> AsFile for T where T: object::Upcast<super::File> {

        #[inline]
        fn as_file(&self) -> &super::File {
            self.upcast()
        }
    }
}

impl File {

    pub fn new_for_path(path: &gstr::Utf8) -> refcount::Ref<File> {
        unsafe {
            let ret = ffi::g_file_new_for_path(path.as_ptr());
            refcount::Ref::from_raw(ret)
        }
    }

    pub fn get_path(&self) -> gstr::OwnedGStr {
        unsafe {
            use grust::wrap::Wrapper;
            let ret = ffi::g_file_get_path(self.as_mut_ptr());
            gstr::OwnedGStr::from_ptr(ret)
        }
    }

    pub fn read_async<F>(&self,
                         io_priority: gint,
                         cancellable: Option<&Cancellable>,
                         callback: F)
        where F: FnOnce(&gobject::Object, &AsyncResult),
              F: 'static
    {
        unsafe {
            use grust::wrap::Wrapper;
            let self_raw = self.as_mut_ptr();
            let cancellable = {
                match cancellable {
                    Some(c) => c.as_mut_ptr(),
                    None    => std::ptr::null_mut()
                }
            };
            let callback: gpointer = mem::transmute(Box::new(callback));

            ffi::g_file_read_async(self_raw,
                                   io_priority,
                                   cancellable,
                                   async::async_ready_callback::<F>,
                                   callback);
        }
    }

    pub fn read_finish(&self, res: &AsyncResult)
                      -> result::Result<refcount::Ref<FileInputStream>,
                                        error::Error> {
        let mut err: *mut glib_ffi::GError = ptr::null_mut();
        let ret = unsafe {
            use grust::wrap::Wrapper;
            ffi::g_file_read_finish(self.as_mut_ptr(),
                                    res.as_mut_ptr(),
                                    &mut err)
        };
        if err.is_null() {
            Ok(unsafe { refcount::Ref::from_raw(ret) })
        } else {
            Err(unsafe { error::Error::from_raw(err) })
        }
    }
}

unsafe impl object::ObjectType for AsyncResult {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_async_result_get_type())
        }
    }
}

unsafe impl object::ObjectType for File {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_file_get_type())
        }
    }
}

unsafe impl object::ObjectType for InputStream {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_input_stream_get_type())
        }
    }
}

unsafe impl object::ObjectType for FileInputStream {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_file_input_stream_get_type())
        }
    }
}

impl object::Upcast<gobject::Object> for Cancellable {

    #[inline]
    fn upcast(&self) -> &gobject::Object {
        unsafe {
            wrap::from_raw(&self.raw.parent_instance)
        }
    }
}

impl object::Upcast<gobject::Object> for InputStream {

    #[inline]
    fn upcast(&self) -> &gobject::Object {
        unsafe {
            wrap::from_raw(&self.raw.parent_instance)
        }
    }
}

impl object::Upcast<InputStream> for FileInputStream {

    #[inline]
    fn upcast(&self) -> &InputStream {
        unsafe {
            wrap::from_raw(&self.raw.parent_instance)
        }
    }
}

impl object::Upcast<gobject::Object> for FileInputStream {

    #[inline]
    fn upcast(&self) -> &gobject::Object {
        use cast::AsInputStream;
        self.as_input_stream().upcast()
    }
}
<commit_msg>Don't link crate bitflags<commit_after>// This file is part of Grust, GObject introspection bindings for Rust
//
// Copyright (C) 2013, 2014  Mikhail Zabaluev <mikhail.zabaluev@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

#![crate_name = "grust-Gio-2_0"]
#![crate_type = "lib"]

#![feature(core)]
#![feature(hash)]

#[macro_use]
extern crate grust;

#[macro_use]
#[no_link]
extern crate bitflags;

extern crate "gio-2_0-sys" as ffi;
extern crate "glib-2_0-sys" as glib_ffi;
extern crate "gobject-2_0-sys" as gobject_ffi;
extern crate "grust-GLib-2_0" as glib;
extern crate "grust-GObject-2_0" as gobject;

use grust::enumeration;
use grust::enumeration::IntrospectedEnum as _grust_IntrospectedEnumTrait;
use grust::error;
use grust::gstr;
use grust::gtype::GType;
use grust::marker;
use grust::object;
use grust::quark;
use grust::refcount;
use grust::types::{gint, gpointer};
use grust::wrap;

use std::fmt;
use std::mem;
use std::num;
use std::ptr;
use std::result;

#[repr(C)]
pub struct AsyncResult {
    raw: ffi::GAsyncResult,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for AsyncResult {
    type Raw = ffi::GAsyncResult;
}

#[repr(C)]
pub struct File {
    raw: ffi::GFile,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for File {
    type Raw = ffi::GFile;
}

#[repr(C)]
pub struct Cancellable {
    raw: ffi::GCancellable,
    _marker: marker::ObjectMarker
}

unsafe impl Send for Cancellable { }
unsafe impl Sync for Cancellable { }
unsafe impl wrap::Wrapper for Cancellable {
    type Raw = ffi::GCancellable;
}

#[repr(C)]
pub struct InputStream {
    raw: ffi::GInputStream,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for InputStream {
    type Raw = ffi::GInputStream;
}

#[repr(C)]
pub struct FileInputStream {
    raw: ffi::GFileInputStream,
    _marker: marker::ObjectMarker
}

unsafe impl wrap::Wrapper for FileInputStream {
    type Raw = ffi::GFileInputStream;
}

#[derive(Copy, PartialEq, Eq, FromPrimitive, Debug)]
#[repr(C)]
pub enum IOErrorEnum {
    Failed = 0,
    NotFound = 1,
    Exists = 2,
    // ...
}

impl enumeration::IntrospectedEnum for IOErrorEnum {

    fn from_int(v: gint) -> Result<Self, enumeration::UnknownValue> {
        num::from_i32(v as i32)
            .ok_or_else(|| enumeration::UnknownValue(v))
    }

    fn to_int(&self) -> gint {
        *self as gint
    }

    fn name(&self) -> &'static str {
        match *self {
            IOErrorEnum::Failed => "failed",
            IOErrorEnum::NotFound => "not-found",
            IOErrorEnum::Exists   => "exists",
            // ...
        }
    }
}

impl enumeration::EnumType for IOErrorEnum {
    fn get_type() -> GType {
        unsafe { GType::from_raw(ffi::g_io_error_enum_get_type()) }
    }
}

impl error::Domain for IOErrorEnum {
    fn domain() -> quark::Quark {
        g_static_quark!(b"g-io-error-quark\0")
    }
}

impl fmt::Display for IOErrorEnum {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

pub mod flags {

    pub mod file_attribute_info {
        use grust::flags::prelude::*;
        use ffi;
        use std::fmt;

        bitflags! {
            flags Flags: guint {
                const NONE            = 0,
                const COPY_WITH_FILE  = 1,
                const COPY_WHEN_MOVED = 2,
            }
        }

        impl IntrospectedFlags for Flags {

            fn from_uint(v: guint) -> Result<Flags, UnknownFlags> {
                Flags::from_bits(v)
                    .ok_or_else(|| UnknownFlags::new(v, Flags::all().bits()))
            }

            #[inline]
            fn to_uint(&self) -> guint {
                self.bits()
            }
        }

        impl FlagsType for Flags {
            fn get_type() -> GType {
                unsafe {
                    let raw = ffi::g_file_attribute_info_flags_get_type();
                    GType::from_raw(raw)
                }
            }
        }

        impl fmt::Debug for Flags {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                const FLAG_INFO: &'static [(Flags, &'static str)] = &[
                    (COPY_WITH_FILE,  "COPY_WITH_FILE"),
                    (COPY_WHEN_MOVED, "COPY_WHEN_MOVED")
                ];
                let mut contents = String::new();
                for &(flag, name) in FLAG_INFO.iter() {
                    if self.contains(flag) {
                        if contents.is_empty() {
                            contents.push_str(name);
                        } else {
                            contents.push('|');
                            contents.push_str(name);
                        }
                    }
                }
                write!(f, "FileAttributeInfoFlags({})", contents)
            }
        }
    }
}

pub use flags::file_attribute_info::Flags as FileAttributeInfoFlags;

mod async {
    use ffi;
    use gobject_ffi;
    use gobject;

    use grust::types::gpointer;
    use grust::wrap;
    use std::mem;

    pub extern "C" fn async_ready_callback<F>(source_object: *mut gobject_ffi::GObject,
                                              res: *mut ffi::GAsyncResult,
                                              user_data: gpointer)
        where F: FnOnce(&gobject::Object, &super::AsyncResult)
    {
        let cb: Box<F> = unsafe { mem::transmute(user_data) };
        let arg1 = unsafe { wrap::from_raw::<gobject::Object>(source_object) };
        let arg2 = unsafe { wrap::from_raw::<super::AsyncResult>(res) };
        cb(arg1, arg2);
    }
}

pub mod cast {
    use grust::object;

    use gobject;

    pub trait AsAsyncResult {
        fn as_async_result(&self) -> &super::AsyncResult;
    }

    impl<T> AsAsyncResult for T where T: object::Upcast<super::AsyncResult> {

        #[inline]
        fn as_async_result(&self) -> &super::AsyncResult {
            self.upcast()
        }
    }

    pub trait AsCancellable : gobject::cast::AsObject {
        fn as_cancellable(&self) -> &super::Cancellable;
    }

    impl<T> AsCancellable for T
        where T: object::Upcast<super::Cancellable>,
              T: object::Upcast<gobject::Object>
    {
        #[inline]
        fn as_cancellable(&self) -> &super::Cancellable {
            self.upcast()
        }
    }

    pub trait AsInputStream : gobject::cast::AsObject {
        fn as_input_stream(&self) -> &super::InputStream;
    }

    impl<T> AsInputStream for T
        where T: object::Upcast<super::InputStream>,
              T: object::Upcast<gobject::Object>
    {
        #[inline]
        fn as_input_stream(&self) -> &super::InputStream {
            self.upcast()
        }
    }

    pub trait AsFileInputStream : AsInputStream {
        fn as_file_input_stream(&self) -> &super::FileInputStream;
    }

    impl<T> AsFileInputStream for T
        where T: object::Upcast<super::FileInputStream>,
              T: object::Upcast<super::InputStream>,
              T: object::Upcast<gobject::Object>
    {
        #[inline]
        fn as_file_input_stream(&self) -> &super::FileInputStream {
            self.upcast()
        }
    }

    pub trait AsFile {
        fn as_file(&self) -> &super::File;
    }

    impl<T> AsFile for T where T: object::Upcast<super::File> {

        #[inline]
        fn as_file(&self) -> &super::File {
            self.upcast()
        }
    }
}

impl File {

    pub fn new_for_path(path: &gstr::Utf8) -> refcount::Ref<File> {
        unsafe {
            let ret = ffi::g_file_new_for_path(path.as_ptr());
            refcount::Ref::from_raw(ret)
        }
    }

    pub fn get_path(&self) -> gstr::OwnedGStr {
        unsafe {
            use grust::wrap::Wrapper;
            let ret = ffi::g_file_get_path(self.as_mut_ptr());
            gstr::OwnedGStr::from_ptr(ret)
        }
    }

    pub fn read_async<F>(&self,
                         io_priority: gint,
                         cancellable: Option<&Cancellable>,
                         callback: F)
        where F: FnOnce(&gobject::Object, &AsyncResult),
              F: 'static
    {
        unsafe {
            use grust::wrap::Wrapper;
            let self_raw = self.as_mut_ptr();
            let cancellable = {
                match cancellable {
                    Some(c) => c.as_mut_ptr(),
                    None    => std::ptr::null_mut()
                }
            };
            let callback: gpointer = mem::transmute(Box::new(callback));

            ffi::g_file_read_async(self_raw,
                                   io_priority,
                                   cancellable,
                                   async::async_ready_callback::<F>,
                                   callback);
        }
    }

    pub fn read_finish(&self, res: &AsyncResult)
                      -> result::Result<refcount::Ref<FileInputStream>,
                                        error::Error> {
        let mut err: *mut glib_ffi::GError = ptr::null_mut();
        let ret = unsafe {
            use grust::wrap::Wrapper;
            ffi::g_file_read_finish(self.as_mut_ptr(),
                                    res.as_mut_ptr(),
                                    &mut err)
        };
        if err.is_null() {
            Ok(unsafe { refcount::Ref::from_raw(ret) })
        } else {
            Err(unsafe { error::Error::from_raw(err) })
        }
    }
}

unsafe impl object::ObjectType for AsyncResult {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_async_result_get_type())
        }
    }
}

unsafe impl object::ObjectType for File {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_file_get_type())
        }
    }
}

unsafe impl object::ObjectType for InputStream {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_input_stream_get_type())
        }
    }
}

unsafe impl object::ObjectType for FileInputStream {
    fn get_type() -> GType {
        unsafe {
            GType::from_raw(ffi::g_file_input_stream_get_type())
        }
    }
}

impl object::Upcast<gobject::Object> for Cancellable {

    #[inline]
    fn upcast(&self) -> &gobject::Object {
        unsafe {
            wrap::from_raw(&self.raw.parent_instance)
        }
    }
}

impl object::Upcast<gobject::Object> for InputStream {

    #[inline]
    fn upcast(&self) -> &gobject::Object {
        unsafe {
            wrap::from_raw(&self.raw.parent_instance)
        }
    }
}

impl object::Upcast<InputStream> for FileInputStream {

    #[inline]
    fn upcast(&self) -> &InputStream {
        unsafe {
            wrap::from_raw(&self.raw.parent_instance)
        }
    }
}

impl object::Upcast<gobject::Object> for FileInputStream {

    #[inline]
    fn upcast(&self) -> &gobject::Object {
        use cast::AsInputStream;
        self.as_input_stream().upcast()
    }
}
<|endoftext|>


<commit_before>use pest::Parser;
use pest::iterators::Pair;
use tera::{Map, Context, Value, to_value};

use errors::{Result, ResultExt};
use ::context::RenderContext;

// This include forces recompiling this source file if the grammar file changes.
// Uncomment it when doing changes to the .pest file
const _GRAMMAR: &str = include_str!("content.pest");

#[derive(Parser)]
#[grammar = "content.pest"]
pub struct ContentParser;

fn replace_string_markers(input: &str) -> String {
    match input.chars().next().unwrap() {
        '"' => input.replace('"', "").to_string(),
        '\'' => input.replace('\'', "").to_string(),
        '`' => input.replace('`', "").to_string(),
        _ => unreachable!("How did you even get there"),
    }
}

fn parse_literal(pair: Pair<Rule>) -> Value {
    let mut val = None;
    for p in pair.into_inner() {
        match p.as_rule() {
            Rule::boolean => match p.as_str() {
                "true" => val = Some(Value::Bool(true)),
                "false" => val = Some(Value::Bool(false)),
                _ => unreachable!(),
            },
            Rule::string => val = Some(Value::String(replace_string_markers(p.as_str()))),
            Rule::float => {
                val = Some(to_value(p.as_str().parse::<f64>().unwrap()).unwrap());
            }
            Rule::int => {
                val = Some(to_value(p.as_str().parse::<i64>().unwrap()).unwrap());
            }
            _ => unreachable!("Unknown literal: {:?}", p)
        };
    }

    val.unwrap()
}

/// Returns (shortcode_name, kwargs)
fn parse_shortcode_call(pair: Pair<Rule>) -> (String, Map<String, Value>) {
    let mut name = None;
    let mut args = Map::new();

    for p in pair.into_inner() {
        match p.as_rule() {
            Rule::ident => { name = Some(p.into_span().as_str().to_string()); }
            Rule::kwarg => {
                let mut arg_name = None;
                let mut arg_val = None;
                for p2 in p.into_inner() {
                    match p2.as_rule() {
                        Rule::ident => { arg_name = Some(p2.into_span().as_str().to_string()); }
                        Rule::literal => { arg_val = Some(parse_literal(p2)); }
                        Rule::array => {
                            let mut vals = vec![];
                            for p3 in p2.into_inner() {
                                match p3.as_rule() {
                                    Rule::literal => vals.push(parse_literal(p3)),
                                    _ => unreachable!("Got something other than literal in an array: {:?}", p3),
                                }
                            }
                            arg_val = Some(Value::Array(vals));
                        }
                        _ => unreachable!("Got something unexpected in a kwarg: {:?}", p2),
                    }
                }

                args.insert(arg_name.unwrap(), arg_val.unwrap());
            }
            _ => unreachable!("Got something unexpected in a shortcode: {:?}", p)
        }
    }
    (name.unwrap(), args)
}

fn render_shortcode(name: &str, args: &Map<String, Value>, context: &RenderContext, body: Option<&str>) -> Result<String> {
    let mut tera_context = Context::new();
    for (key, value) in args.iter() {
        tera_context.insert(key, value);
    }
    if let Some(ref b) = body {
        // Trimming right to avoid most shortcodes with bodies ending up with a HTML new line
        tera_context.insert("body", b.trim_right());
    }
    tera_context.extend(context.tera_context.clone());
    let tpl_name = format!("shortcodes/{}.html", name);

    let res = context.tera
        .render(&tpl_name, &tera_context)
        .chain_err(|| format!("Failed to render {} shortcode", name))?;

    Ok(res)
}

pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<String> {
    let mut res = String::with_capacity(content.len());

    let mut pairs = match ContentParser::parse(Rule::page, content) {
        Ok(p) => p,
        Err(e) => {
            let fancy_e = e.renamed_rules(|rule| {
                match *rule {
                    Rule::int => "an integer".to_string(),
                    Rule::float => "a float".to_string(),
                    Rule::string => "a string".to_string(),
                    Rule::literal => "a literal (int, float, string, bool)".to_string(),
                    Rule::array => "an array".to_string(),
                    Rule::kwarg => "a keyword argument".to_string(),
                    Rule::ident => "an identifier".to_string(),
                    Rule::inline_shortcode => "an inline shortcode".to_string(),
                    Rule::ignored_inline_shortcode => "an ignored inline shortcode".to_string(),
                    Rule::sc_body_start => "the start of a shortcode".to_string(),
                    Rule::ignored_sc_body_start => "the start of an ignored shortcode".to_string(),
                    Rule::text => "some text".to_string(),
                    Rule::EOI => "end of input".to_string(),
                    Rule::double_quoted_string => "double quoted string".to_string(),
                    Rule::single_quoted_string => "single quoted string".to_string(),
                    Rule::backquoted_quoted_string => "backquoted quoted string".to_string(),
                    Rule::boolean => "a boolean (true, false)".to_string(),
                    Rule::all_chars => "a alphanumerical character".to_string(),
                    Rule::kwargs => "a list of keyword arguments".to_string(),
                    Rule::sc_def => "a shortcode definition".to_string(),
                    Rule::shortcode_with_body => "a shortcode with body".to_string(),
                    Rule::ignored_shortcode_with_body => "an ignored shortcode with body".to_string(),
                    Rule::sc_body_end => "{% end %}".to_string(),
                    Rule::ignored_sc_body_end => "{%/* end */%}".to_string(),
                    Rule::text_in_body_sc => "text in a shortcode body".to_string(),
                    Rule::text_in_ignored_body_sc => "text in an ignored shortcode body".to_string(),
                    Rule::content => "some content".to_string(),
                    Rule::page => "a page".to_string(),
                    Rule::WHITESPACE => "whitespace".to_string(),
                }
            });
            bail!("{}", fancy_e);
        }
    };

    // We have at least a `page` pair
    for p in pairs.next().unwrap().into_inner() {
        match p.as_rule() {
            Rule::text | Rule::text_in_ignored_body_sc | Rule::text_in_body_sc => res.push_str(p.into_span().as_str()),
            Rule::inline_shortcode => {
                let (name, args) = parse_shortcode_call(p);
                res.push_str(&render_shortcode(&name, &args, context, None)?);
            }
            Rule::shortcode_with_body => {
                let mut inner = p.into_inner();
                // 3 items in inner: call, body, end
                // we don't care about the closing tag
                let (name, args) = parse_shortcode_call(inner.next().unwrap());
                let body = inner.next().unwrap().into_span().as_str();
                res.push_str(&render_shortcode(&name, &args, context, Some(body))?);
            }
            Rule::ignored_inline_shortcode => {
                res.push_str(
                    &p.into_span().as_str()
                        .replacen("{{/*", "{{", 1)
                        .replacen("*/}}", "}}", 1)
                );
            }
            Rule::ignored_shortcode_with_body => {
                for p2 in p.into_inner() {
                    match p2.as_rule() {
                        Rule::ignored_sc_body_start | Rule::ignored_sc_body_end => {
                            res.push_str(
                                &p2.into_span().as_str()
                                    .replacen("{%/*", "{%", 1)
                                    .replacen("*/%}", "%}", 1)
                            );
                        }
                        Rule::text_in_ignored_body_sc => res.push_str(p2.into_span().as_str()),
                        _ => unreachable!("Got something weird in an ignored shortcode: {:?}", p2),
                    }
                }
            },
            Rule::EOI => (),
            _ => unreachable!("unexpected page rule: {:?}", p.as_rule()),
        }
    }

    Ok(res)
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use tera::Tera;
    use config::Config;
    use front_matter::InsertAnchor;
    use super::*;

    macro_rules! assert_lex_rule {
        ($rule: expr, $input: expr) => {
            let res = ContentParser::parse($rule, $input);
            println!("{:?}", $input);
            println!("{:#?}", res);
            if res.is_err() {
                println!("{}", res.unwrap_err());
                panic!();
            }
            assert!(res.is_ok());
            assert_eq!(res.unwrap().last().unwrap().into_span().end(), $input.len());
        };
    }

    fn render_shortcodes(code: &str, tera: &Tera) -> String {
        let config = Config::default();
        let permalinks = HashMap::new();
        let context = RenderContext::new(&tera, &config, "", &permalinks, InsertAnchor::None);
        super::render_shortcodes(code, &context).unwrap()
    }

    #[test]
    fn lex_text() {
        let inputs = vec!["Hello world", "HEllo \n world", "Hello 1 2 true false 'hey'"];
        for i in inputs {
            assert_lex_rule!(Rule::text, i);
        }
    }

    #[test]
    fn lex_inline_shortcode() {
        let inputs = vec![
            "{{ youtube() }}",
            "{{ youtube(id=1, autoplay=true, url='hey') }}",
            "{{ youtube(id=1, \nautoplay=true, url='hey') }}",
        ];
        for i in inputs {
            assert_lex_rule!(Rule::inline_shortcode, i);
        }
    }

    #[test]
    fn lex_inline_ignored_shortcode() {
        let inputs = vec![
            "{{/* youtube() */}}",
            "{{/* youtube(id=1, autoplay=true, url='hey') */}}",
            "{{/* youtube(id=1, \nautoplay=true, \nurl='hey') */}}",
        ];
        for i in inputs {
            assert_lex_rule!(Rule::ignored_inline_shortcode, i);
        }
    }

    #[test]
    fn lex_shortcode_with_body() {
        let inputs = vec![
            r#"{% youtube() %}
            Some text
            {% end %}"#,
            r#"{% youtube(id=1,
            autoplay=true, url='hey') %}
            Some text
            {% end %}"#,
        ];
        for i in inputs {
            assert_lex_rule!(Rule::shortcode_with_body, i);
        }
    }

    #[test]
    fn lex_ignored_shortcode_with_body() {
        let inputs = vec![
            r#"{%/* youtube() */%}
            Some text
            {%/* end */%}"#,
            r#"{%/* youtube(id=1,
            autoplay=true, url='hey') */%}
            Some text
            {%/* end */%}"#,
        ];
        for i in inputs {
            assert_lex_rule!(Rule::ignored_shortcode_with_body, i);
        }
    }

    #[test]
    fn lex_page() {
        let inputs = vec![
            "Some text and a shortcode `{{/* youtube() */}}`",
            "{{ youtube(id=1, autoplay=true, url='hey') }}",
            "{{ youtube(id=1, \nautoplay=true, url='hey') }} that's it",
            r#"
            This is a test
            {% hello() %}
            Body {{ var }}
            {% end %}
            "#
        ];
        for i in inputs {
            assert_lex_rule!(Rule::page, i);
        }
    }

    #[test]
    fn does_nothing_with_no_shortcodes() {
        let res = render_shortcodes("Hello World", &Tera::default());
        assert_eq!(res, "Hello World");
    }

    #[test]
    fn can_unignore_inline_shortcode() {
        let res = render_shortcodes("Hello World {{/* youtube() */}}", &Tera::default());
        assert_eq!(res, "Hello World {{ youtube() }}");
    }

    #[test]
    fn can_unignore_shortcode_with_body() {
        let res = render_shortcodes(r#"
Hello World
{%/* youtube() */%}Some body {{ hello() }}{%/* end */%}"#, &Tera::default());
        assert_eq!(res, "\nHello World\n{% youtube() %}Some body {{ hello() }}{% end %}");
    }

    // https://github.com/Keats/gutenberg/issues/383
    #[test]
    fn unignore_shortcode_with_body_does_not_swallow_initial_whitespace() {
        let res = render_shortcodes(r#"
Hello World
{%/* youtube() */%}
Some body {{ hello() }}{%/* end */%}"#, &Tera::default());
        assert_eq!(res, "\nHello World\n{% youtube() %}\nSome body {{ hello() }}{% end %}");
    }

    #[test]
    fn can_parse_shortcode_arguments() {
        let inputs = vec![
            ("{{ youtube() }}", "youtube", Map::new()),
            (
                "{{ youtube(id=1, autoplay=true, hello='salut', float=1.2) }}",
                "youtube",
                {
                    let mut m = Map::new();
                    m.insert("id".to_string(), to_value(1).unwrap());
                    m.insert("autoplay".to_string(), to_value(true).unwrap());
                    m.insert("hello".to_string(), to_value("salut").unwrap());
                    m.insert("float".to_string(), to_value(1.2).unwrap());
                    m
                }
            ),
            (
                "{{ gallery(photos=['something', 'else'], fullscreen=true) }}",
                "gallery",
                {
                    let mut m = Map::new();
                    m.insert("photos".to_string(), to_value(["something", "else"]).unwrap());
                    m.insert("fullscreen".to_string(), to_value(true).unwrap());
                    m
                }
            ),
        ];

        for (i, n, a) in inputs {
            let mut res = ContentParser::parse(Rule::inline_shortcode, i).unwrap();
            let (name, args) = parse_shortcode_call(res.next().unwrap());
            assert_eq!(name, n);
            assert_eq!(args, a);
        }
    }

    #[test]
    fn can_render_inline_shortcodes() {
        let mut tera = Tera::default();
        tera.add_raw_template("shortcodes/youtube.html", "Hello {{id}}").unwrap();
        let res = render_shortcodes("Inline {{ youtube(id=1) }}.", &tera);
        assert_eq!(res, "Inline Hello 1.");
    }

    #[test]
    fn can_render_shortcodes_with_body() {
        let mut tera = Tera::default();
        tera.add_raw_template("shortcodes/youtube.html", "{{body}}").unwrap();
        let res = render_shortcodes("Body\n {% youtube() %}Hey!{% end %}", &tera);
        assert_eq!(res, "Body\n Hey!");
    }

    // https://github.com/Keats/gutenberg/issues/462
    #[test]
    fn shortcodes_with_body_do_not_eat_newlines() {
        let mut tera = Tera::default();
        tera.add_raw_template("shortcodes/youtube.html", "{{body | safe}}").unwrap();
        let res = render_shortcodes("Body\n {% youtube() %}\nHello \n World{% end %}", &tera);
        assert_eq!(res, "Body\n Hello \n World");
    }
}
<commit_msg>Remove unmatchable rules from render_shortcodes<commit_after>use pest::Parser;
use pest::iterators::Pair;
use tera::{Map, Context, Value, to_value};

use errors::{Result, ResultExt};
use ::context::RenderContext;

// This include forces recompiling this source file if the grammar file changes.
// Uncomment it when doing changes to the .pest file
const _GRAMMAR: &str = include_str!("content.pest");

#[derive(Parser)]
#[grammar = "content.pest"]
pub struct ContentParser;

fn replace_string_markers(input: &str) -> String {
    match input.chars().next().unwrap() {
        '"' => input.replace('"', "").to_string(),
        '\'' => input.replace('\'', "").to_string(),
        '`' => input.replace('`', "").to_string(),
        _ => unreachable!("How did you even get there"),
    }
}

fn parse_literal(pair: Pair<Rule>) -> Value {
    let mut val = None;
    for p in pair.into_inner() {
        match p.as_rule() {
            Rule::boolean => match p.as_str() {
                "true" => val = Some(Value::Bool(true)),
                "false" => val = Some(Value::Bool(false)),
                _ => unreachable!(),
            },
            Rule::string => val = Some(Value::String(replace_string_markers(p.as_str()))),
            Rule::float => {
                val = Some(to_value(p.as_str().parse::<f64>().unwrap()).unwrap());
            }
            Rule::int => {
                val = Some(to_value(p.as_str().parse::<i64>().unwrap()).unwrap());
            }
            _ => unreachable!("Unknown literal: {:?}", p)
        };
    }

    val.unwrap()
}

/// Returns (shortcode_name, kwargs)
fn parse_shortcode_call(pair: Pair<Rule>) -> (String, Map<String, Value>) {
    let mut name = None;
    let mut args = Map::new();

    for p in pair.into_inner() {
        match p.as_rule() {
            Rule::ident => { name = Some(p.into_span().as_str().to_string()); }
            Rule::kwarg => {
                let mut arg_name = None;
                let mut arg_val = None;
                for p2 in p.into_inner() {
                    match p2.as_rule() {
                        Rule::ident => { arg_name = Some(p2.into_span().as_str().to_string()); }
                        Rule::literal => { arg_val = Some(parse_literal(p2)); }
                        Rule::array => {
                            let mut vals = vec![];
                            for p3 in p2.into_inner() {
                                match p3.as_rule() {
                                    Rule::literal => vals.push(parse_literal(p3)),
                                    _ => unreachable!("Got something other than literal in an array: {:?}", p3),
                                }
                            }
                            arg_val = Some(Value::Array(vals));
                        }
                        _ => unreachable!("Got something unexpected in a kwarg: {:?}", p2),
                    }
                }

                args.insert(arg_name.unwrap(), arg_val.unwrap());
            }
            _ => unreachable!("Got something unexpected in a shortcode: {:?}", p)
        }
    }
    (name.unwrap(), args)
}

fn render_shortcode(name: &str, args: &Map<String, Value>, context: &RenderContext, body: Option<&str>) -> Result<String> {
    let mut tera_context = Context::new();
    for (key, value) in args.iter() {
        tera_context.insert(key, value);
    }
    if let Some(ref b) = body {
        // Trimming right to avoid most shortcodes with bodies ending up with a HTML new line
        tera_context.insert("body", b.trim_right());
    }
    tera_context.extend(context.tera_context.clone());
    let tpl_name = format!("shortcodes/{}.html", name);

    let res = context.tera
        .render(&tpl_name, &tera_context)
        .chain_err(|| format!("Failed to render {} shortcode", name))?;

    Ok(res)
}

pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<String> {
    let mut res = String::with_capacity(content.len());

    let mut pairs = match ContentParser::parse(Rule::page, content) {
        Ok(p) => p,
        Err(e) => {
            let fancy_e = e.renamed_rules(|rule| {
                match *rule {
                    Rule::int => "an integer".to_string(),
                    Rule::float => "a float".to_string(),
                    Rule::string => "a string".to_string(),
                    Rule::literal => "a literal (int, float, string, bool)".to_string(),
                    Rule::array => "an array".to_string(),
                    Rule::kwarg => "a keyword argument".to_string(),
                    Rule::ident => "an identifier".to_string(),
                    Rule::inline_shortcode => "an inline shortcode".to_string(),
                    Rule::ignored_inline_shortcode => "an ignored inline shortcode".to_string(),
                    Rule::sc_body_start => "the start of a shortcode".to_string(),
                    Rule::ignored_sc_body_start => "the start of an ignored shortcode".to_string(),
                    Rule::text => "some text".to_string(),
                    Rule::EOI => "end of input".to_string(),
                    Rule::double_quoted_string => "double quoted string".to_string(),
                    Rule::single_quoted_string => "single quoted string".to_string(),
                    Rule::backquoted_quoted_string => "backquoted quoted string".to_string(),
                    Rule::boolean => "a boolean (true, false)".to_string(),
                    Rule::all_chars => "a alphanumerical character".to_string(),
                    Rule::kwargs => "a list of keyword arguments".to_string(),
                    Rule::sc_def => "a shortcode definition".to_string(),
                    Rule::shortcode_with_body => "a shortcode with body".to_string(),
                    Rule::ignored_shortcode_with_body => "an ignored shortcode with body".to_string(),
                    Rule::sc_body_end => "{% end %}".to_string(),
                    Rule::ignored_sc_body_end => "{%/* end */%}".to_string(),
                    Rule::text_in_body_sc => "text in a shortcode body".to_string(),
                    Rule::text_in_ignored_body_sc => "text in an ignored shortcode body".to_string(),
                    Rule::content => "some content".to_string(),
                    Rule::page => "a page".to_string(),
                    Rule::WHITESPACE => "whitespace".to_string(),
                }
            });
            bail!("{}", fancy_e);
        }
    };

    // We have at least a `page` pair
    for p in pairs.next().unwrap().into_inner() {
        match p.as_rule() {
            Rule::text => res.push_str(p.into_span().as_str()),
            Rule::inline_shortcode => {
                let (name, args) = parse_shortcode_call(p);
                res.push_str(&render_shortcode(&name, &args, context, None)?);
            }
            Rule::shortcode_with_body => {
                let mut inner = p.into_inner();
                // 3 items in inner: call, body, end
                // we don't care about the closing tag
                let (name, args) = parse_shortcode_call(inner.next().unwrap());
                let body = inner.next().unwrap().into_span().as_str();
                res.push_str(&render_shortcode(&name, &args, context, Some(body))?);
            }
            Rule::ignored_inline_shortcode => {
                res.push_str(
                    &p.into_span().as_str()
                        .replacen("{{/*", "{{", 1)
                        .replacen("*/}}", "}}", 1)
                );
            }
            Rule::ignored_shortcode_with_body => {
                for p2 in p.into_inner() {
                    match p2.as_rule() {
                        Rule::ignored_sc_body_start | Rule::ignored_sc_body_end => {
                            res.push_str(
                                &p2.into_span().as_str()
                                    .replacen("{%/*", "{%", 1)
                                    .replacen("*/%}", "%}", 1)
                            );
                        }
                        Rule::text_in_ignored_body_sc => res.push_str(p2.into_span().as_str()),
                        _ => unreachable!("Got something weird in an ignored shortcode: {:?}", p2),
                    }
                }
            },
            Rule::EOI => (),
            _ => unreachable!("unexpected page rule: {:?}", p.as_rule()),
        }
    }

    Ok(res)
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use tera::Tera;
    use config::Config;
    use front_matter::InsertAnchor;
    use super::*;

    macro_rules! assert_lex_rule {
        ($rule: expr, $input: expr) => {
            let res = ContentParser::parse($rule, $input);
            println!("{:?}", $input);
            println!("{:#?}", res);
            if res.is_err() {
                println!("{}", res.unwrap_err());
                panic!();
            }
            assert!(res.is_ok());
            assert_eq!(res.unwrap().last().unwrap().into_span().end(), $input.len());
        };
    }

    fn render_shortcodes(code: &str, tera: &Tera) -> String {
        let config = Config::default();
        let permalinks = HashMap::new();
        let context = RenderContext::new(&tera, &config, "", &permalinks, InsertAnchor::None);
        super::render_shortcodes(code, &context).unwrap()
    }

    #[test]
    fn lex_text() {
        let inputs = vec!["Hello world", "HEllo \n world", "Hello 1 2 true false 'hey'"];
        for i in inputs {
            assert_lex_rule!(Rule::text, i);
        }
    }

    #[test]
    fn lex_inline_shortcode() {
        let inputs = vec![
            "{{ youtube() }}",
            "{{ youtube(id=1, autoplay=true, url='hey') }}",
            "{{ youtube(id=1, \nautoplay=true, url='hey') }}",
        ];
        for i in inputs {
            assert_lex_rule!(Rule::inline_shortcode, i);
        }
    }

    #[test]
    fn lex_inline_ignored_shortcode() {
        let inputs = vec![
            "{{/* youtube() */}}",
            "{{/* youtube(id=1, autoplay=true, url='hey') */}}",
            "{{/* youtube(id=1, \nautoplay=true, \nurl='hey') */}}",
        ];
        for i in inputs {
            assert_lex_rule!(Rule::ignored_inline_shortcode, i);
        }
    }

    #[test]
    fn lex_shortcode_with_body() {
        let inputs = vec![
            r#"{% youtube() %}
            Some text
            {% end %}"#,
            r#"{% youtube(id=1,
            autoplay=true, url='hey') %}
            Some text
            {% end %}"#,
        ];
        for i in inputs {
            assert_lex_rule!(Rule::shortcode_with_body, i);
        }
    }

    #[test]
    fn lex_ignored_shortcode_with_body() {
        let inputs = vec![
            r#"{%/* youtube() */%}
            Some text
            {%/* end */%}"#,
            r#"{%/* youtube(id=1,
            autoplay=true, url='hey') */%}
            Some text
            {%/* end */%}"#,
        ];
        for i in inputs {
            assert_lex_rule!(Rule::ignored_shortcode_with_body, i);
        }
    }

    #[test]
    fn lex_page() {
        let inputs = vec![
            "Some text and a shortcode `{{/* youtube() */}}`",
            "{{ youtube(id=1, autoplay=true, url='hey') }}",
            "{{ youtube(id=1, \nautoplay=true, url='hey') }} that's it",
            r#"
            This is a test
            {% hello() %}
            Body {{ var }}
            {% end %}
            "#
        ];
        for i in inputs {
            assert_lex_rule!(Rule::page, i);
        }
    }

    #[test]
    fn does_nothing_with_no_shortcodes() {
        let res = render_shortcodes("Hello World", &Tera::default());
        assert_eq!(res, "Hello World");
    }

    #[test]
    fn can_unignore_inline_shortcode() {
        let res = render_shortcodes("Hello World {{/* youtube() */}}", &Tera::default());
        assert_eq!(res, "Hello World {{ youtube() }}");
    }

    #[test]
    fn can_unignore_shortcode_with_body() {
        let res = render_shortcodes(r#"
Hello World
{%/* youtube() */%}Some body {{ hello() }}{%/* end */%}"#, &Tera::default());
        assert_eq!(res, "\nHello World\n{% youtube() %}Some body {{ hello() }}{% end %}");
    }

    // https://github.com/Keats/gutenberg/issues/383
    #[test]
    fn unignore_shortcode_with_body_does_not_swallow_initial_whitespace() {
        let res = render_shortcodes(r#"
Hello World
{%/* youtube() */%}
Some body {{ hello() }}{%/* end */%}"#, &Tera::default());
        assert_eq!(res, "\nHello World\n{% youtube() %}\nSome body {{ hello() }}{% end %}");
    }

    #[test]
    fn can_parse_shortcode_arguments() {
        let inputs = vec![
            ("{{ youtube() }}", "youtube", Map::new()),
            (
                "{{ youtube(id=1, autoplay=true, hello='salut', float=1.2) }}",
                "youtube",
                {
                    let mut m = Map::new();
                    m.insert("id".to_string(), to_value(1).unwrap());
                    m.insert("autoplay".to_string(), to_value(true).unwrap());
                    m.insert("hello".to_string(), to_value("salut").unwrap());
                    m.insert("float".to_string(), to_value(1.2).unwrap());
                    m
                }
            ),
            (
                "{{ gallery(photos=['something', 'else'], fullscreen=true) }}",
                "gallery",
                {
                    let mut m = Map::new();
                    m.insert("photos".to_string(), to_value(["something", "else"]).unwrap());
                    m.insert("fullscreen".to_string(), to_value(true).unwrap());
                    m
                }
            ),
        ];

        for (i, n, a) in inputs {
            let mut res = ContentParser::parse(Rule::inline_shortcode, i).unwrap();
            let (name, args) = parse_shortcode_call(res.next().unwrap());
            assert_eq!(name, n);
            assert_eq!(args, a);
        }
    }

    #[test]
    fn can_render_inline_shortcodes() {
        let mut tera = Tera::default();
        tera.add_raw_template("shortcodes/youtube.html", "Hello {{id}}").unwrap();
        let res = render_shortcodes("Inline {{ youtube(id=1) }}.", &tera);
        assert_eq!(res, "Inline Hello 1.");
    }

    #[test]
    fn can_render_shortcodes_with_body() {
        let mut tera = Tera::default();
        tera.add_raw_template("shortcodes/youtube.html", "{{body}}").unwrap();
        let res = render_shortcodes("Body\n {% youtube() %}Hey!{% end %}", &tera);
        assert_eq!(res, "Body\n Hey!");
    }

    // https://github.com/Keats/gutenberg/issues/462
    #[test]
    fn shortcodes_with_body_do_not_eat_newlines() {
        let mut tera = Tera::default();
        tera.add_raw_template("shortcodes/youtube.html", "{{body | safe}}").unwrap();
        let res = render_shortcodes("Body\n {% youtube() %}\nHello \n World{% end %}", &tera);
        assert_eq!(res, "Body\n Hello \n World");
    }
}
<|endoftext|>


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

mod additional_cpp_generator;
mod bridge_converter;
mod byvalue_checker;
mod known_types;
mod parse;
mod rust_pretty_printer;
mod types;
mod unqualify;

#[cfg(any(test, feature = "build"))]
mod builder;

#[cfg(test)]
mod integration_tests;

use proc_macro2::TokenStream as TokenStream2;
use std::path::PathBuf;

use quote::ToTokens;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::{parse_quote, ItemMod, Macro};

use additional_cpp_generator::AdditionalCppGenerator;
use itertools::join;
use log::{info, warn};
use types::TypeName;

#[cfg(any(test, feature = "build"))]
pub use builder::{build, BuilderError};
pub use parse::{parse_file, parse_token_stream, ParseError, ParsedFile};

pub use cxx_gen::HEADER;

/// Re-export cxx such that clients can use the same version as
/// us. This doesn't enable clients to avoid depending on the cxx
/// crate too, unfortunately, since generated cxx::bridge code
/// refers explicitly to ::cxx. See
/// https://github.com/google/autocxx/issues/36
pub use cxx;

pub struct CppFilePair {
    pub header: Vec<u8>,
    pub implementation: Vec<u8>,
    pub header_name: String,
}

pub struct GeneratedCpp(pub Vec<CppFilePair>);

#[derive(Debug)]
pub enum Error {
    Io(std::io::Error),
    Bindgen(()),
    CxxGen(cxx_gen::Error),
    Parsing(syn::Error),
    NoAutoCxxInc,
    CouldNotCanoncalizeIncludeDir(PathBuf),
    Conversion(bridge_converter::ConvertError),
    /// No 'allow' or 'allow_pod' was specified.
    /// It might be that in future we can simply let things work
    /// without any allowlist, in which case bindgen should generate
    /// bindings for everything. That just seems very unlikely to work
    /// in the common case right now.
    NoAllowlist,
}

pub type Result<T, E = Error> = std::result::Result<T, E>;

pub enum CppInclusion {
    Define(String),
    Header(String),
}

enum State {
    NotGenerated,
    ParseOnly,
    NothingGenerated,
    Generated(ItemMod, AdditionalCppGenerator),
}

/// Core of the autocxx engine. See `generate` for most details
/// on how this works.
///
/// TODO - consider whether this 'engine' crate should actually be a
/// directory of source symlinked from all the other sub-crates, so that
/// we avoid exposing an external interface from this code.
pub struct IncludeCpp {
    inclusions: Vec<CppInclusion>,
    allowlist: Vec<String>, // not TypeName as it may be functions or whatever.
    pod_types: Vec<TypeName>,
    preconfigured_inc_dirs: Option<std::ffi::OsString>,
    exclude_utilities: bool,
    state: State,
}

impl Parse for IncludeCpp {
    fn parse(input: ParseStream) -> ParseResult<Self> {
        Self::new_from_parse_stream(input)
    }
}

fn dump_generated_code(gen: cxx_gen::GeneratedCode) -> Result<cxx_gen::GeneratedCode> {
    info!(
        "CXX:\n{}",
        String::from_utf8(gen.implementation.clone()).unwrap()
    );
    info!(
        "header:\n{}",
        String::from_utf8(gen.header.clone()).unwrap()
    );
    Ok(gen)
}

impl IncludeCpp {
    fn new_from_parse_stream(input: ParseStream) -> syn::Result<Self> {
        // Takes as inputs:
        // 1. List of headers to include
        // 2. List of #defines to include
        // 3. Allowlist

        let mut inclusions = Vec::new();
        let mut allowlist = Vec::new();
        let mut pod_types = Vec::new();
        let mut parse_only = false;
        let mut exclude_utilities = false;

        while !input.is_empty() {
            if input.parse::<Option<syn::Token![#]>>()?.is_some() {
                let ident: syn::Ident = input.parse()?;
                if ident != "include" {
                    return Err(syn::Error::new(ident.span(), "expected include"));
                }
                let hdr: syn::LitStr = input.parse()?;
                inclusions.push(CppInclusion::Header(hdr.value()));
            } else {
                let ident: syn::Ident = input.parse()?;
                input.parse::<Option<syn::Token![!]>>()?;
                if ident == "allow" || ident == "allow_pod" {
                    let args;
                    syn::parenthesized!(args in input);
                    let allow: syn::LitStr = args.parse()?;
                    allowlist.push(allow.value());
                    if ident == "allow_pod" {
                        pod_types.push(TypeName::new_from_user_input(&allow.value()));
                    }
                } else if ident == "parse_only" {
                    parse_only = true;
                } else if ident == "exclude_utilities" {
                    exclude_utilities = true;
                } else {
                    return Err(syn::Error::new(
                        ident.span(),
                        "expected allow, allow_pod or exclude_utilities",
                    ));
                }
            }
            if input.is_empty() {
                break;
            }
        }

        Ok(IncludeCpp {
            inclusions,
            allowlist,
            pod_types,
            preconfigured_inc_dirs: None,
            exclude_utilities,
            state: if parse_only {
                State::ParseOnly
            } else {
                State::NotGenerated
            },
        })
    }

    pub fn new_from_syn(mac: Macro) -> Result<Self> {
        mac.parse_body::<IncludeCpp>().map_err(Error::Parsing)
    }

    pub fn set_include_dirs<P: AsRef<std::ffi::OsStr>>(&mut self, include_dirs: P) {
        self.preconfigured_inc_dirs = Some(include_dirs.as_ref().into());
    }

    fn build_header(&self) -> String {
        join(
            self.inclusions.iter().map(|incl| match incl {
                CppInclusion::Define(symbol) => format!("#define {}\n", symbol),
                CppInclusion::Header(path) => format!("#include \"{}\"\n", path),
            }),
            "",
        )
    }

    fn determine_incdirs(&self) -> Result<Vec<PathBuf>> {
        let inc_dirs = match &self.preconfigured_inc_dirs {
            Some(d) => d.clone(),
            None => std::env::var_os("AUTOCXX_INC").ok_or(Error::NoAutoCxxInc)?,
        };
        let inc_dirs = std::env::split_paths(&inc_dirs);
        // TODO consider if we can or should look up the include path automatically
        // instead of requiring callers always to set AUTOCXX_INC.

        // On Windows, the canonical path begins with a UNC prefix that cannot be passed to
        // the MSVC compiler, so dunce::canonicalize() is used instead of std::fs::canonicalize()
        // See:
        // * https://github.com/dtolnay/cxx/pull/41
        // * https://github.com/alexcrichton/cc-rs/issues/169
        inc_dirs
            .map(|p| dunce::canonicalize(&p).map_err(|_| Error::CouldNotCanoncalizeIncludeDir(p)))
            .collect()
    }

    fn make_bindgen_builder(&self) -> Result<bindgen::Builder> {
        // TODO support different C++ versions
        let mut builder = bindgen::builder()
            .clang_args(&["-x", "c++", "-std=c++14"])
            .derive_copy(false)
            .derive_debug(false)
            .default_enum_style(bindgen::EnumVariation::Rust {
                non_exhaustive: false,
            })
            .enable_cxx_namespaces()
            .generate_inline_functions(true)
            .layout_tests(false); // TODO revisit later
        for item in known_types::get_initial_blocklist() {
            builder = builder.blacklist_item(item);
        }

        for inc_dir in self.determine_incdirs()? {
            // TODO work with OsStrs here to avoid the .display()
            builder = builder.clang_arg(format!("-I{}", inc_dir.display()));
        }

        // 3. Passes allowlist and other options to the bindgen::Builder equivalent
        //    to --output-style=cxx --allowlist=<as passed in>
        for a in &self.allowlist {
            // TODO - allowlist type/functions/separately
            builder = builder
                .whitelist_type(a)
                .whitelist_function(a)
                .whitelist_var(a);
        }

        Ok(builder)
    }

    fn inject_header_into_bindgen(&self, mut builder: bindgen::Builder) -> bindgen::Builder {
        let full_header = self.build_header();
        let full_header = format!("{}\n\n{}", known_types::get_prelude(), full_header,);
        info!("Full header: {}", full_header);
        builder = builder.header_contents("example.hpp", &full_header);
        builder
    }

    /// Generate the Rust bindings. Call `generate` first.
    pub fn generate_rs(&self) -> TokenStream2 {
        match &self.state {
            State::NotGenerated => panic!("Call generate() first"),
            State::Generated(itemmod, _) => itemmod.to_token_stream(),
            State::NothingGenerated | State::ParseOnly => TokenStream2::new(),
        }
    }

    fn parse_bindings(&self, bindings: bindgen::Bindings) -> Result<ItemMod> {
        // This bindings object is actually a TokenStream internally and we're wasting
        // effort converting to and from string. We could enhance the bindgen API
        // in future.
        let bindings = bindings.to_string();
        // Manually add the mod ffi {} so that we can ask syn to parse
        // into a single construct.
        let bindings = format!("mod bindgen {{ {} }}", bindings);
        info!("Bindings: {}", bindings);
        syn::parse_str::<ItemMod>(&bindings).map_err(Error::Parsing)
    }

    fn generate_include_list(&self) -> Vec<String> {
        let mut include_list = Vec::new();
        for incl in &self.inclusions {
            match incl {
                CppInclusion::Header(ref hdr) => {
                    include_list.push(hdr.clone());
                }
                CppInclusion::Define(_) => warn!("Currently no way to define! within cxx"),
            }
        }
        include_list
    }

    /// Actually examine the headers to find out what needs generating.
    /// Most errors occur at this stage as we fail to interpret the C++
    /// headers properly.
    ///
    /// The basic idea is this. We will run `bindgen` which will spit
    /// out a ton of Rust code corresponding to all the types and functions
    /// defined in C++. We'll then post-process that bindgen output
    /// into a form suitable for ingestion by `cxx`.
    /// (It's the `bridge_converter` mod which does that.)
    /// Along the way, the `bridge_converter` might tell us of additional
    /// C++ code which we should generate, e.g. wrappers to move things
    /// into and out of `UniquePtr`s.
    pub fn generate(&mut self) -> Result<()> {
        // If we are in parse only mode, do nothing. This is used for
        // doc tests to ensure the parsing is valid, but we can't expect
        // valid C++ header files or linkers to allow a complete build.
        match self.state {
            State::ParseOnly => return Ok(()),
            State::NotGenerated => {}
            State::Generated(_, _) | State::NothingGenerated => panic!("Only call generate once"),
        }

        if self.allowlist.is_empty() {
            return Err(Error::NoAllowlist);
        }

        let builder = self.make_bindgen_builder()?;
        let bindings = self
            .inject_header_into_bindgen(builder)
            .generate()
            .map_err(Error::Bindgen)?;
        let bindings = self.parse_bindings(bindings)?;

        let mut converter = bridge_converter::BridgeConverter::new(
            self.generate_include_list(),
            self.pod_types.clone(), // TODO take self by value to avoid clone.
        );

        let conversion = converter
            .convert(bindings, self.exclude_utilities)
            .map_err(Error::Conversion)?;
        let mut additional_cpp_generator = AdditionalCppGenerator::new(self.build_header());
        additional_cpp_generator.add_needs(conversion.additional_cpp_needs);
        let mut items = conversion.items;
        //{
        //    let local_lock = self.preprocessor_definitions.try_lock().unwrap();
        //    items.extend(local_lock.to_items());
        //}
        let mut new_bindings: ItemMod = parse_quote! {
            #[allow(non_snake_case)]
            #[allow(dead_code)]
            #[allow(non_upper_case_globals)]
            #[allow(non_camel_case_types)]
            mod ffi {
            }
        };
        new_bindings.content.as_mut().unwrap().1.append(&mut items);
        info!(
            "New bindings:\n{}",
            rust_pretty_printer::pretty_print(&new_bindings.to_token_stream())
        );
        self.state = State::Generated(new_bindings, additional_cpp_generator);
        Ok(())
    }

    /// Generate C++-side bindings for these APIs. Call `generate` first.
    pub fn generate_h_and_cxx(&self) -> Result<GeneratedCpp> {
        let mut files = Vec::new();
        match &self.state {
            State::ParseOnly => panic!("Cannot generate C++ in parse-only mode"),
            State::NotGenerated => panic!("Call generate() first"),
            State::NothingGenerated => {}
            State::Generated(itemmod, additional_cpp_generator) => {
                let rs = itemmod.into_token_stream();
                let opt = cxx_gen::Opt::default();
                let cxx_generated = cxx_gen::generate_header_and_cc(rs, &opt)
                    .map_err(Error::CxxGen)
                    .and_then(dump_generated_code)?;
                files.push(CppFilePair {
                    header: cxx_generated.header,
                    header_name: "cxxgen.h".to_string(),
                    implementation: cxx_generated.implementation,
                });

                match additional_cpp_generator.generate() {
                    None => {}
                    Some(additional_cpp) => {
                        // TODO should probably replace pragma once below with traditional include guards.
                        let declarations = format!("#pragma once\n{}", additional_cpp.declarations);
                        files.push(CppFilePair {
                            header: declarations.as_bytes().to_vec(),
                            header_name: "autocxxgen.h".to_string(),
                            implementation: additional_cpp.definitions.as_bytes().to_vec(),
                        });
                    }
                }
            }
        };
        Ok(GeneratedCpp(files))
    }

    /// Get the configured include directories.
    pub fn include_dirs(&self) -> Result<Vec<PathBuf>> {
        self.determine_incdirs()
    }
}
<commit_msg>Remove lines<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod additional_cpp_generator;
mod bridge_converter;
mod byvalue_checker;
mod known_types;
mod parse;
mod rust_pretty_printer;
mod types;
mod unqualify;

#[cfg(any(test, feature = "build"))]
mod builder;

#[cfg(test)]
mod integration_tests;

use proc_macro2::TokenStream as TokenStream2;
use std::path::PathBuf;

use quote::ToTokens;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::{parse_quote, ItemMod, Macro};

use additional_cpp_generator::AdditionalCppGenerator;
use itertools::join;
use log::{info, warn};
use types::TypeName;

#[cfg(any(test, feature = "build"))]
pub use builder::{build, BuilderError};
pub use parse::{parse_file, parse_token_stream, ParseError, ParsedFile};

pub use cxx_gen::HEADER;

/// Re-export cxx such that clients can use the same version as
/// us. This doesn't enable clients to avoid depending on the cxx
/// crate too, unfortunately, since generated cxx::bridge code
/// refers explicitly to ::cxx. See
/// https://github.com/google/autocxx/issues/36
pub use cxx;

pub struct CppFilePair {
    pub header: Vec<u8>,
    pub implementation: Vec<u8>,
    pub header_name: String,
}

pub struct GeneratedCpp(pub Vec<CppFilePair>);

#[derive(Debug)]
pub enum Error {
    Io(std::io::Error),
    Bindgen(()),
    CxxGen(cxx_gen::Error),
    Parsing(syn::Error),
    NoAutoCxxInc,
    CouldNotCanoncalizeIncludeDir(PathBuf),
    Conversion(bridge_converter::ConvertError),
    /// No 'allow' or 'allow_pod' was specified.
    /// It might be that in future we can simply let things work
    /// without any allowlist, in which case bindgen should generate
    /// bindings for everything. That just seems very unlikely to work
    /// in the common case right now.
    NoAllowlist,
}

pub type Result<T, E = Error> = std::result::Result<T, E>;

pub enum CppInclusion {
    Define(String),
    Header(String),
}

enum State {
    NotGenerated,
    ParseOnly,
    NothingGenerated,
    Generated(ItemMod, AdditionalCppGenerator),
}

/// Core of the autocxx engine. See `generate` for most details
/// on how this works.
///
/// TODO - consider whether this 'engine' crate should actually be a
/// directory of source symlinked from all the other sub-crates, so that
/// we avoid exposing an external interface from this code.
pub struct IncludeCpp {
    inclusions: Vec<CppInclusion>,
    allowlist: Vec<String>, // not TypeName as it may be functions or whatever.
    pod_types: Vec<TypeName>,
    preconfigured_inc_dirs: Option<std::ffi::OsString>,
    exclude_utilities: bool,
    state: State,
}

impl Parse for IncludeCpp {
    fn parse(input: ParseStream) -> ParseResult<Self> {
        Self::new_from_parse_stream(input)
    }
}

fn dump_generated_code(gen: cxx_gen::GeneratedCode) -> Result<cxx_gen::GeneratedCode> {
    info!(
        "CXX:\n{}",
        String::from_utf8(gen.implementation.clone()).unwrap()
    );
    info!(
        "header:\n{}",
        String::from_utf8(gen.header.clone()).unwrap()
    );
    Ok(gen)
}

impl IncludeCpp {
    fn new_from_parse_stream(input: ParseStream) -> syn::Result<Self> {
        // Takes as inputs:
        // 1. List of headers to include
        // 2. List of #defines to include
        // 3. Allowlist

        let mut inclusions = Vec::new();
        let mut allowlist = Vec::new();
        let mut pod_types = Vec::new();
        let mut parse_only = false;
        let mut exclude_utilities = false;

        while !input.is_empty() {
            if input.parse::<Option<syn::Token![#]>>()?.is_some() {
                let ident: syn::Ident = input.parse()?;
                if ident != "include" {
                    return Err(syn::Error::new(ident.span(), "expected include"));
                }
                let hdr: syn::LitStr = input.parse()?;
                inclusions.push(CppInclusion::Header(hdr.value()));
            } else {
                let ident: syn::Ident = input.parse()?;
                input.parse::<Option<syn::Token![!]>>()?;
                if ident == "allow" || ident == "allow_pod" {
                    let args;
                    syn::parenthesized!(args in input);
                    let allow: syn::LitStr = args.parse()?;
                    allowlist.push(allow.value());
                    if ident == "allow_pod" {
                        pod_types.push(TypeName::new_from_user_input(&allow.value()));
                    }
                } else if ident == "parse_only" {
                    parse_only = true;
                } else if ident == "exclude_utilities" {
                    exclude_utilities = true;
                } else {
                    return Err(syn::Error::new(
                        ident.span(),
                        "expected allow, allow_pod or exclude_utilities",
                    ));
                }
            }
            if input.is_empty() {
                break;
            }
        }

        Ok(IncludeCpp {
            inclusions,
            allowlist,
            pod_types,
            preconfigured_inc_dirs: None,
            exclude_utilities,
            state: if parse_only {
                State::ParseOnly
            } else {
                State::NotGenerated
            },
        })
    }

    pub fn new_from_syn(mac: Macro) -> Result<Self> {
        mac.parse_body::<IncludeCpp>().map_err(Error::Parsing)
    }

    pub fn set_include_dirs<P: AsRef<std::ffi::OsStr>>(&mut self, include_dirs: P) {
        self.preconfigured_inc_dirs = Some(include_dirs.as_ref().into());
    }

    fn build_header(&self) -> String {
        join(
            self.inclusions.iter().map(|incl| match incl {
                CppInclusion::Define(symbol) => format!("#define {}\n", symbol),
                CppInclusion::Header(path) => format!("#include \"{}\"\n", path),
            }),
            "",
        )
    }

    fn determine_incdirs(&self) -> Result<Vec<PathBuf>> {
        let inc_dirs = match &self.preconfigured_inc_dirs {
            Some(d) => d.clone(),
            None => std::env::var_os("AUTOCXX_INC").ok_or(Error::NoAutoCxxInc)?,
        };
        let inc_dirs = std::env::split_paths(&inc_dirs);
        // TODO consider if we can or should look up the include path automatically
        // instead of requiring callers always to set AUTOCXX_INC.

        // On Windows, the canonical path begins with a UNC prefix that cannot be passed to
        // the MSVC compiler, so dunce::canonicalize() is used instead of std::fs::canonicalize()
        // See:
        // * https://github.com/dtolnay/cxx/pull/41
        // * https://github.com/alexcrichton/cc-rs/issues/169
        inc_dirs
            .map(|p| dunce::canonicalize(&p).map_err(|_| Error::CouldNotCanoncalizeIncludeDir(p)))
            .collect()
    }

    fn make_bindgen_builder(&self) -> Result<bindgen::Builder> {
        // TODO support different C++ versions
        let mut builder = bindgen::builder()
            .clang_args(&["-x", "c++", "-std=c++14"])
            .derive_copy(false)
            .derive_debug(false)
            .default_enum_style(bindgen::EnumVariation::Rust {
                non_exhaustive: false,
            })
            .enable_cxx_namespaces()
            .generate_inline_functions(true)
            .layout_tests(false); // TODO revisit later
        for item in known_types::get_initial_blocklist() {
            builder = builder.blacklist_item(item);
        }

        for inc_dir in self.determine_incdirs()? {
            // TODO work with OsStrs here to avoid the .display()
            builder = builder.clang_arg(format!("-I{}", inc_dir.display()));
        }

        // 3. Passes allowlist and other options to the bindgen::Builder equivalent
        //    to --output-style=cxx --allowlist=<as passed in>
        for a in &self.allowlist {
            // TODO - allowlist type/functions/separately
            builder = builder
                .whitelist_type(a)
                .whitelist_function(a)
                .whitelist_var(a);
        }

        Ok(builder)
    }

    fn inject_header_into_bindgen(&self, mut builder: bindgen::Builder) -> bindgen::Builder {
        let full_header = self.build_header();
        let full_header = format!("{}\n\n{}", known_types::get_prelude(), full_header,);
        info!("Full header: {}", full_header);
        builder = builder.header_contents("example.hpp", &full_header);
        builder
    }

    /// Generate the Rust bindings. Call `generate` first.
    pub fn generate_rs(&self) -> TokenStream2 {
        match &self.state {
            State::NotGenerated => panic!("Call generate() first"),
            State::Generated(itemmod, _) => itemmod.to_token_stream(),
            State::NothingGenerated | State::ParseOnly => TokenStream2::new(),
        }
    }

    fn parse_bindings(&self, bindings: bindgen::Bindings) -> Result<ItemMod> {
        // This bindings object is actually a TokenStream internally and we're wasting
        // effort converting to and from string. We could enhance the bindgen API
        // in future.
        let bindings = bindings.to_string();
        // Manually add the mod ffi {} so that we can ask syn to parse
        // into a single construct.
        let bindings = format!("mod bindgen {{ {} }}", bindings);
        info!("Bindings: {}", bindings);
        syn::parse_str::<ItemMod>(&bindings).map_err(Error::Parsing)
    }

    fn generate_include_list(&self) -> Vec<String> {
        let mut include_list = Vec::new();
        for incl in &self.inclusions {
            match incl {
                CppInclusion::Header(ref hdr) => {
                    include_list.push(hdr.clone());
                }
                CppInclusion::Define(_) => warn!("Currently no way to define! within cxx"),
            }
        }
        include_list
    }

    /// Actually examine the headers to find out what needs generating.
    /// Most errors occur at this stage as we fail to interpret the C++
    /// headers properly.
    ///
    /// The basic idea is this. We will run `bindgen` which will spit
    /// out a ton of Rust code corresponding to all the types and functions
    /// defined in C++. We'll then post-process that bindgen output
    /// into a form suitable for ingestion by `cxx`.
    /// (It's the `bridge_converter` mod which does that.)
    /// Along the way, the `bridge_converter` might tell us of additional
    /// C++ code which we should generate, e.g. wrappers to move things
    /// into and out of `UniquePtr`s.
    pub fn generate(&mut self) -> Result<()> {
        // If we are in parse only mode, do nothing. This is used for
        // doc tests to ensure the parsing is valid, but we can't expect
        // valid C++ header files or linkers to allow a complete build.
        match self.state {
            State::ParseOnly => return Ok(()),
            State::NotGenerated => {}
            State::Generated(_, _) | State::NothingGenerated => panic!("Only call generate once"),
        }

        if self.allowlist.is_empty() {
            return Err(Error::NoAllowlist);
        }

        let builder = self.make_bindgen_builder()?;
        let bindings = self
            .inject_header_into_bindgen(builder)
            .generate()
            .map_err(Error::Bindgen)?;
        let bindings = self.parse_bindings(bindings)?;

        let mut converter = bridge_converter::BridgeConverter::new(
            self.generate_include_list(),
            self.pod_types.clone(), // TODO take self by value to avoid clone.
        );

        let conversion = converter
            .convert(bindings, self.exclude_utilities)
            .map_err(Error::Conversion)?;
        let mut additional_cpp_generator = AdditionalCppGenerator::new(self.build_header());
        additional_cpp_generator.add_needs(conversion.additional_cpp_needs);
        let mut items = conversion.items;
        let mut new_bindings: ItemMod = parse_quote! {
            #[allow(non_snake_case)]
            #[allow(dead_code)]
            #[allow(non_upper_case_globals)]
            #[allow(non_camel_case_types)]
            mod ffi {
            }
        };
        new_bindings.content.as_mut().unwrap().1.append(&mut items);
        info!(
            "New bindings:\n{}",
            rust_pretty_printer::pretty_print(&new_bindings.to_token_stream())
        );
        self.state = State::Generated(new_bindings, additional_cpp_generator);
        Ok(())
    }

    /// Generate C++-side bindings for these APIs. Call `generate` first.
    pub fn generate_h_and_cxx(&self) -> Result<GeneratedCpp> {
        let mut files = Vec::new();
        match &self.state {
            State::ParseOnly => panic!("Cannot generate C++ in parse-only mode"),
            State::NotGenerated => panic!("Call generate() first"),
            State::NothingGenerated => {}
            State::Generated(itemmod, additional_cpp_generator) => {
                let rs = itemmod.into_token_stream();
                let opt = cxx_gen::Opt::default();
                let cxx_generated = cxx_gen::generate_header_and_cc(rs, &opt)
                    .map_err(Error::CxxGen)
                    .and_then(dump_generated_code)?;
                files.push(CppFilePair {
                    header: cxx_generated.header,
                    header_name: "cxxgen.h".to_string(),
                    implementation: cxx_generated.implementation,
                });

                match additional_cpp_generator.generate() {
                    None => {}
                    Some(additional_cpp) => {
                        // TODO should probably replace pragma once below with traditional include guards.
                        let declarations = format!("#pragma once\n{}", additional_cpp.declarations);
                        files.push(CppFilePair {
                            header: declarations.as_bytes().to_vec(),
                            header_name: "autocxxgen.h".to_string(),
                            implementation: additional_cpp.definitions.as_bytes().to_vec(),
                        });
                    }
                }
            }
        };
        Ok(GeneratedCpp(files))
    }

    /// Get the configured include directories.
    pub fn include_dirs(&self) -> Result<Vec<PathBuf>> {
        self.determine_incdirs()
    }
}
<|endoftext|>


<commit_before>//! Builtin macro
use crate::db::AstDatabase;
use crate::{
    ast::{self, AstNode},
    name, AstId, CrateId, HirFileId, MacroCallId, MacroDefId, MacroDefKind, MacroFileKind,
    TextUnit,
};

use crate::quote;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinExpander {
    Column,
    File,
    Line,
    Stringify,
}

impl BuiltinExpander {
    pub fn expand(
        &self,
        db: &dyn AstDatabase,
        id: MacroCallId,
        tt: &tt::Subtree,
    ) -> Result<tt::Subtree, mbe::ExpandError> {
        match self {
            BuiltinExpander::Column => column_expand(db, id, tt),
            BuiltinExpander::File => file_expand(db, id, tt),
            BuiltinExpander::Line => line_expand(db, id, tt),
            BuiltinExpander::Stringify => stringify_expand(db, id, tt),
        }
    }
}

pub fn find_builtin_macro(
    ident: &name::Name,
    krate: CrateId,
    ast_id: AstId<ast::MacroCall>,
) -> Option<MacroDefId> {
    // FIXME: Better registering method
    if ident == &name::COLUMN_MACRO {
        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Column) })
    } else if ident == &name::FILE_MACRO {
        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::File) })
    } else if ident == &name::LINE_MACRO {
        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Line) })
    } else if ident == &name::STRINGIFY_MACRO {
        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Stringify) })
    } else {
        None
    }
}

fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
    // FIXME: Use expansion info
    let file_id = file.original_file(db);
    let text = db.file_text(file_id);
    let mut line_num = 1;

    // Count line end
    for (i, c) in text.chars().enumerate() {
        if i == pos.to_usize() {
            break;
        }
        if c == '\n' {
            line_num += 1;
        }
    }

    line_num
}

fn line_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
    let arg_start = arg.syntax().text_range().start();

    let file = id.as_file(MacroFileKind::Expr);
    let line_num = to_line_number(db, file, arg_start);

    let expanded = quote! {
        #line_num
    };

    Ok(expanded)
}

fn stringify_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let macro_content = {
        let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
        let macro_args = arg.syntax().clone();
        let text = macro_args.text();
        let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')');
        text.slice(without_parens).to_string()
    };

    let expanded = quote! {
        #macro_content
    };

    Ok(expanded)
}

fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
    // FIXME: Use expansion info
    let file_id = file.original_file(db);
    let text = db.file_text(file_id);
    let mut col_num = 1;

    for c in text[..pos.to_usize()].chars().rev() {
        if c == '\n' {
            break;
        }
        col_num = col_num + 1;
    }

    col_num
}

fn column_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
    let col_start = macro_call.syntax().text_range().start();

    let file = id.as_file(MacroFileKind::Expr);
    let col_num = to_col_number(db, file, col_start);

    let expanded = quote! {
        #col_num
    };

    Ok(expanded)
}

fn file_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let _ = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;

    // FIXME: RA purposefully lacks knowledge of absolute file names
    // so just return "".
    let file_name = "";

    let expanded = quote! {
        #file_name
    };

    Ok(expanded)
}
<commit_msg>Refactor builtin macro<commit_after>//! Builtin macro
use crate::db::AstDatabase;
use crate::{
    ast::{self, AstNode},
    name, AstId, CrateId, HirFileId, MacroCallId, MacroDefId, MacroDefKind, MacroFileKind,
    TextUnit,
};

use crate::quote;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinExpander {
    Column,
    File,
    Line,
    Stringify,
}

struct BuiltInMacroInfo {
    name: name::Name,
    kind: BuiltinExpander,
    expand: fn(
        db: &dyn AstDatabase,
        id: MacroCallId,
        _tt: &tt::Subtree,
    ) -> Result<tt::Subtree, mbe::ExpandError>,
}

macro_rules! register_builtin {
    ( $(($name:ident, $kind: ident) => $expand:ident),* ) => {
        const BUILTIN_MACROS: &[BuiltInMacroInfo] = &[
            $(BuiltInMacroInfo { name: name::$name, kind: BuiltinExpander::$kind, expand: $expand }),*
        ];
    };
}

register_builtin! {
    (COLUMN_MACRO, Column) => column_expand,
    (FILE_MACRO, File) => file_expand,
    (LINE_MACRO, Line) => line_expand,
    (STRINGIFY_MACRO, Stringify) => stringify_expand
}

impl BuiltinExpander {
    pub fn expand(
        &self,
        db: &dyn AstDatabase,
        id: MacroCallId,
        tt: &tt::Subtree,
    ) -> Result<tt::Subtree, mbe::ExpandError> {
        let expander = BUILTIN_MACROS
            .iter()
            .find(|it| *self == it.kind)
            .map(|it| it.expand)
            .ok_or_else(|| mbe::ExpandError::ConversionError)?;

        expander(db, id, tt)
    }
}

pub fn find_builtin_macro(
    ident: &name::Name,
    krate: CrateId,
    ast_id: AstId<ast::MacroCall>,
) -> Option<MacroDefId> {
    let kind = BUILTIN_MACROS.iter().find(|it| *ident == it.name).map(|it| it.kind)?;

    Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(kind) })
}

fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
    // FIXME: Use expansion info
    let file_id = file.original_file(db);
    let text = db.file_text(file_id);
    let mut line_num = 1;

    // Count line end
    for (i, c) in text.chars().enumerate() {
        if i == pos.to_usize() {
            break;
        }
        if c == '\n' {
            line_num += 1;
        }
    }

    line_num
}

fn line_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
    let arg_start = arg.syntax().text_range().start();

    let file = id.as_file(MacroFileKind::Expr);
    let line_num = to_line_number(db, file, arg_start);

    let expanded = quote! {
        #line_num
    };

    Ok(expanded)
}

fn stringify_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let macro_content = {
        let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
        let macro_args = arg.syntax().clone();
        let text = macro_args.text();
        let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')');
        text.slice(without_parens).to_string()
    };

    let expanded = quote! {
        #macro_content
    };

    Ok(expanded)
}

fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
    // FIXME: Use expansion info
    let file_id = file.original_file(db);
    let text = db.file_text(file_id);
    let mut col_num = 1;

    for c in text[..pos.to_usize()].chars().rev() {
        if c == '\n' {
            break;
        }
        col_num = col_num + 1;
    }

    col_num
}

fn column_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
    let col_start = macro_call.syntax().text_range().start();

    let file = id.as_file(MacroFileKind::Expr);
    let col_num = to_col_number(db, file, col_start);

    let expanded = quote! {
        #col_num
    };

    Ok(expanded)
}

fn file_expand(
    db: &dyn AstDatabase,
    id: MacroCallId,
    _tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
    let loc = db.lookup_intern_macro(id);
    let macro_call = loc.ast_id.to_node(db);

    let _ = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;

    // FIXME: RA purposefully lacks knowledge of absolute file names
    // so just return "".
    let file_name = "";

    let expanded = quote! {
        #file_name
    };

    Ok(expanded)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{test_db::TestDB, MacroCallLoc};
    use ra_db::{fixture::WithFixture, SourceDatabase};

    fn expand_builtin_macro(s: &str, expander: BuiltinExpander) -> String {
        let (db, file_id) = TestDB::with_single_file(&s);
        let parsed = db.parse(file_id);
        let macro_calls: Vec<_> =
            parsed.syntax_node().descendants().filter_map(|it| ast::MacroCall::cast(it)).collect();

        let ast_id_map = db.ast_id_map(file_id.into());

        // the first one should be a macro_rules
        let def = MacroDefId {
            krate: CrateId(0),
            ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0])),
            kind: MacroDefKind::BuiltIn(expander),
        };

        let loc = MacroCallLoc {
            def,
            ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[1])),
        };

        let id = db.intern_macro(loc);
        let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Expr)).unwrap();

        parsed.text().to_string()
    }

    #[test]
    fn test_column_expand() {
        let expanded = expand_builtin_macro(
            r#"
        #[rustc_builtin_macro]
        macro_rules! column {() => {}}
        column!()
"#,
            BuiltinExpander::Column,
        );

        assert_eq!(expanded, "9");
    }

    #[test]
    fn test_line_expand() {
        let expanded = expand_builtin_macro(
            r#"
        #[rustc_builtin_macro]
        macro_rules! line {() => {}}
        line!()
"#,
            BuiltinExpander::Line,
        );

        assert_eq!(expanded, "4");
    }

    #[test]
    fn test_stringify_expand() {
        let expanded = expand_builtin_macro(
            r#"
        #[rustc_builtin_macro]
        macro_rules! stringify {() => {}}
        stringify!(a b c)
"#,
            BuiltinExpander::Stringify,
        );

        assert_eq!(expanded, "\"a b c\"");
    }

    #[test]
    fn test_file_expand() {
        let expanded = expand_builtin_macro(
            r#"
        #[rustc_builtin_macro]
        macro_rules! file {() => {}}
        file!()
"#,
            BuiltinExpander::File,
        );

        assert_eq!(expanded, "\"\"");
    }
}
<|endoftext|>