<commit_before>//! A CLI tool for inspecting the contents of a DICOM file.
//! Despite the name, this tool may have a different interface and output
//! from other `dcmdump` tools, and does not aim to make a drop-in
//! replacement.
//!
//! Usage:
//!
//! ```none
//! dcmdump <file.dcm>
//! ```
use dicom::core::dictionary::{DataDictionary, DictionaryEntry};
use dicom::core::header::Header;
use dicom::core::value::{PrimitiveValue, Value as DicomValue};
use dicom::core::VR;
use dicom::object::mem::{InMemDicomObject, InMemElement};
use dicom::object::{open_file, DefaultDicomObject, FileMetaTable, StandardDataDictionary};

use std::borrow::Cow;
use std::io::{stdout, ErrorKind, Result as IoResult, Write};

type DynResult<T> = Result<T, Box<dyn std::error::Error>>;

fn main() -> DynResult<()> {
    let filename = ::std::env::args()
        .nth(1)
        .expect("Missing path to DICOM file");

    let obj = open_file(filename)?;

    match dump_file(obj) {
        Err(ref e) if e.kind() == ErrorKind::BrokenPipe => {
            Ok(()) // handle broken pipe separately with a no-op
        }
        Err(e) => Err(e.into()), // raise other errors
        _ => Ok(()),             // all good
    }
}

fn dump_file(obj: DefaultDicomObject) -> IoResult<()> {
    let mut to = stdout();
    write!(to, "# Dicom-File-Format\n\n")?;

    let meta = obj.meta();

    let width = 48;

    meta_dump(&mut to, &meta, width)?;

    dump(&mut to, &obj, width, 0)?;

    Ok(())
}

fn meta_dump<W>(to: &mut W, meta: &FileMetaTable, width: u32) -> IoResult<()>
where
    W: ?Sized + Write,
{
    writeln!(
        to,
        "Media Storage SOP Class UID: {}",
        meta.media_storage_sop_class_uid
    )?;
    writeln!(
        to,
        "Media Storage SOP Instance UID: {}",
        meta.media_storage_sop_instance_uid
    )?;
    writeln!(to, "Transfer Syntax: {}", meta.transfer_syntax)?;
    writeln!(to, "Implementation Class UID: {}", meta.transfer_syntax)?;

    if let Some(v) = meta.implementation_version_name.as_ref() {
        writeln!(to, "Implementation version name: {}", v)?;
    }
    if let Some(v) = meta.source_application_entity_title.as_ref() {
        writeln!(to, "Source Application Entity Title: {}", v)?;
    }

    if let Some(v) = meta.sending_application_entity_title.as_ref() {
        writeln!(to, "Sending Application Entity Title: {}", v)?;
    }

    if let Some(v) = meta.receiving_application_entity_title.as_ref() {
        writeln!(to, "Receiving Application Entity Title: {}", v)?;
    }

    if let Some(v) = meta.private_information_creator_uid.as_ref() {
        writeln!(to, "Private Information Creator UID: {}", v)?;
    }

    if let Some(v) = meta.private_information.as_ref() {
        writeln!(
            to,
            "Private Information: {}",
            format_value_list(v.iter().map(|n| format!("{:#x}", n)), width,)
        )?;
    }

    writeln!(to)?;
    Ok(())
}

fn dump<W, D>(to: &mut W, obj: &InMemDicomObject<D>, width: u32, depth: u32) -> IoResult<()>
where
    W: ?Sized + Write,
    D: DataDictionary,
{
    for elem in obj {
        dump_element(&mut *to, &elem, width, depth)?;
    }

    Ok(())
}

fn dump_element<W, D>(to: &mut W, elem: &InMemElement<D>, width: u32, depth: u32) -> IoResult<()>
where
    W: ?Sized + Write,
    D: DataDictionary,
{
    let indent = vec![b' '; (depth * 2) as usize];
    let tag_alias = StandardDataDictionary
        .by_tag(elem.tag())
        .map(DictionaryEntry::alias)
        .unwrap_or("«Unknown Attribute»");
    to.write_all(&indent)?;
    let vm = match elem.vr() {
        VR::OB | VR::OW | VR::UN => 1,
        _ => elem.value().multiplicity(),
    };

    match elem.value() {
        DicomValue::Sequence { items, .. } => {
            writeln!(
                to,
                "{} {} {:48} # {},    {}",
                elem.tag(),
                elem.vr(),
                "",
                vm,
                tag_alias
            )?;
            for item in items {
                dump_item(&mut *to, item, width, depth + 2)?;
            }
            writeln!(
                to,
                "(FFFE, E0DD) na (SequenceDelimitationItem) {:20} # 0, SequenceDelimitationItem",
                "",
            )?;
        }
        DicomValue::PixelSequence {
            fragments,
            offset_table,
        } => {
            // write pixel sequence start line
            let vr = elem.vr();
            let num_items = 1 + fragments.len();
            writeln!(
                to,
                "{} {} (PixelSequence=#{}) {:29} # u/l, 1 PixelData",
                elem.tag(),
                vr,
                num_items,
                ""
            )?;

            // write offset table
            let byte_len = offset_table.len();
            writeln!(
                to,
                "  (FFFE,E000) pi {:48} # {}, 1 Item",
                item_value_summary(&offset_table, width),
                byte_len,
            )?;

            // write compressed fragments
            for fragment in fragments {
                let byte_len = fragment.len();
                writeln!(
                    to,
                    "  (FFFE,E000) pi {:48} # {}, 1 Item",
                    item_value_summary(&fragment, width),
                    byte_len,
                )?;
            }
        }
        DicomValue::Primitive(value) => {
            let vr = elem.vr();
            let byte_len = value.calculate_byte_len();
            writeln!(
                to,
                "{} {} {:48} # {}, {} {}",
                elem.tag(),
                vr,
                value_summary(&value, vr, width),
                byte_len,
                vm,
                tag_alias
            )?;
        }
    }

    Ok(())
}

fn dump_item<W, D>(to: &mut W, item: &InMemDicomObject<D>, width: u32, depth: u32) -> IoResult<()>
where
    W: ?Sized + Write,
    D: DataDictionary,
{
    let indent: String = std::iter::repeat(' ').take((depth * 2) as usize).collect();
    let trail: String = std::iter::repeat(' ')
        .take(usize::max(21, width as usize - 4 - indent.len()))
        .collect();
    writeln!(to, "{}(FFFE,E000) na Item {} # 0, 0 Item", indent, trail)?;
    dump(to, item, width, depth + 1)?;
    writeln!(
        to,
        "{}(FFFE,E00D) na (ItemDelimitationItem) {:23} # 0, 0 ItemDelimitationItem",
        indent, ""
    )?;
    Ok(())
}

fn value_summary(value: &PrimitiveValue, vr: VR, max_characters: u32) -> Cow<str> {
    use PrimitiveValue::*;
    match (value, vr) {
        (F32(values), _) => format_value_list(values, max_characters).into(),
        (F64(values), _) => format_value_list(values, max_characters).into(),
        (I32(values), _) => format_value_list(values, max_characters).into(),
        (I64(values), _) => format_value_list(values, max_characters).into(),
        (U32(values), _) => format_value_list(values, max_characters).into(),
        (U64(values), _) => format_value_list(values, max_characters).into(),
        (I16(values), _) => format_value_list(values, max_characters).into(),
        (U16(values), VR::OW) => format_value_list(
            values.into_iter().map(|n| format!("{:02X}", n)),
            max_characters,
        )
        .into(),
        (U16(values), _) => format_value_list(values, max_characters).into(),
        (U8(values), VR::OB) | (U8(values), VR::UN) => format_value_list(
            values.into_iter().map(|n| format!("{:02X}", n)),
            max_characters,
        )
        .into(),
        (U8(values), _) => format_value_list(values, max_characters).into(),
        (Tags(values), _) => format_value_list(values, max_characters).into(),
        (Strs(values), _) => format_value_list(values, max_characters).into(),
        (Date(values), _) => format_value_list(values, max_characters).into(),
        (Time(values), _) => format_value_list(values, max_characters).into(),
        (DateTime(values), _) => format_value_list(values, max_characters).into(),
        (Str(value), _) => cut_str(&value.to_string(), max_characters)
            .into_owned()
            .into(),
        (Empty, _) => "".into(),
    }
}

fn item_value_summary(data: &[u8], max_characters: u32) -> String {
    format_value_list(
        data.into_iter().map(|n| format!("{:02X}", n)),
        max_characters,
    )
}

fn format_value_list<I>(values: I, max_characters: u32) -> String
where
    I: IntoIterator,
    I::Item: std::fmt::Display,
{
    let pieces = values.into_iter().take(64);
    let max = max_characters as usize;
    let mut o = String::with_capacity(max);
    for piece in pieces {
        o.push_str(&piece.to_string());
        o.push('\\');
        if o.len() > max {
            break;
        }
    }
    o.pop();
    cut_str(&o, max_characters).into_owned()
}

fn cut_str(s: &str, max_characters: u32) -> Cow<str> {
    let max = (max_characters - 3) as usize;
    if s.len() > max {
        format!("{}...", &s[..max]).into()
    } else {
        s.into()
    }
}
<commit_msg>Remove call to slice.into_iter<commit_after>//! A CLI tool for inspecting the contents of a DICOM file.
//! Despite the name, this tool may have a different interface and output
//! from other `dcmdump` tools, and does not aim to make a drop-in
//! replacement.
//!
//! Usage:
//!
//! ```none
//! dcmdump <file.dcm>
//! ```
use dicom::core::dictionary::{DataDictionary, DictionaryEntry};
use dicom::core::header::Header;
use dicom::core::value::{PrimitiveValue, Value as DicomValue};
use dicom::core::VR;
use dicom::object::mem::{InMemDicomObject, InMemElement};
use dicom::object::{open_file, DefaultDicomObject, FileMetaTable, StandardDataDictionary};

use std::borrow::Cow;
use std::io::{stdout, ErrorKind, Result as IoResult, Write};

type DynResult<T> = Result<T, Box<dyn std::error::Error>>;

fn main() -> DynResult<()> {
    let filename = ::std::env::args()
        .nth(1)
        .expect("Missing path to DICOM file");

    let obj = open_file(filename)?;

    match dump_file(obj) {
        Err(ref e) if e.kind() == ErrorKind::BrokenPipe => {
            Ok(()) // handle broken pipe separately with a no-op
        }
        Err(e) => Err(e.into()), // raise other errors
        _ => Ok(()),             // all good
    }
}

fn dump_file(obj: DefaultDicomObject) -> IoResult<()> {
    let mut to = stdout();
    write!(to, "# Dicom-File-Format\n\n")?;

    let meta = obj.meta();

    let width = 48;

    meta_dump(&mut to, &meta, width)?;

    dump(&mut to, &obj, width, 0)?;

    Ok(())
}

fn meta_dump<W>(to: &mut W, meta: &FileMetaTable, width: u32) -> IoResult<()>
where
    W: ?Sized + Write,
{
    writeln!(
        to,
        "Media Storage SOP Class UID: {}",
        meta.media_storage_sop_class_uid
    )?;
    writeln!(
        to,
        "Media Storage SOP Instance UID: {}",
        meta.media_storage_sop_instance_uid
    )?;
    writeln!(to, "Transfer Syntax: {}", meta.transfer_syntax)?;
    writeln!(to, "Implementation Class UID: {}", meta.transfer_syntax)?;

    if let Some(v) = meta.implementation_version_name.as_ref() {
        writeln!(to, "Implementation version name: {}", v)?;
    }
    if let Some(v) = meta.source_application_entity_title.as_ref() {
        writeln!(to, "Source Application Entity Title: {}", v)?;
    }

    if let Some(v) = meta.sending_application_entity_title.as_ref() {
        writeln!(to, "Sending Application Entity Title: {}", v)?;
    }

    if let Some(v) = meta.receiving_application_entity_title.as_ref() {
        writeln!(to, "Receiving Application Entity Title: {}", v)?;
    }

    if let Some(v) = meta.private_information_creator_uid.as_ref() {
        writeln!(to, "Private Information Creator UID: {}", v)?;
    }

    if let Some(v) = meta.private_information.as_ref() {
        writeln!(
            to,
            "Private Information: {}",
            format_value_list(v.iter().map(|n| format!("{:#x}", n)), width,)
        )?;
    }

    writeln!(to)?;
    Ok(())
}

fn dump<W, D>(to: &mut W, obj: &InMemDicomObject<D>, width: u32, depth: u32) -> IoResult<()>
where
    W: ?Sized + Write,
    D: DataDictionary,
{
    for elem in obj {
        dump_element(&mut *to, &elem, width, depth)?;
    }

    Ok(())
}

fn dump_element<W, D>(to: &mut W, elem: &InMemElement<D>, width: u32, depth: u32) -> IoResult<()>
where
    W: ?Sized + Write,
    D: DataDictionary,
{
    let indent = vec![b' '; (depth * 2) as usize];
    let tag_alias = StandardDataDictionary
        .by_tag(elem.tag())
        .map(DictionaryEntry::alias)
        .unwrap_or("«Unknown Attribute»");
    to.write_all(&indent)?;
    let vm = match elem.vr() {
        VR::OB | VR::OW | VR::UN => 1,
        _ => elem.value().multiplicity(),
    };

    match elem.value() {
        DicomValue::Sequence { items, .. } => {
            writeln!(
                to,
                "{} {} {:48} # {},    {}",
                elem.tag(),
                elem.vr(),
                "",
                vm,
                tag_alias
            )?;
            for item in items {
                dump_item(&mut *to, item, width, depth + 2)?;
            }
            writeln!(
                to,
                "(FFFE, E0DD) na (SequenceDelimitationItem) {:20} # 0, SequenceDelimitationItem",
                "",
            )?;
        }
        DicomValue::PixelSequence {
            fragments,
            offset_table,
        } => {
            // write pixel sequence start line
            let vr = elem.vr();
            let num_items = 1 + fragments.len();
            writeln!(
                to,
                "{} {} (PixelSequence=#{}) {:29} # u/l, 1 PixelData",
                elem.tag(),
                vr,
                num_items,
                ""
            )?;

            // write offset table
            let byte_len = offset_table.len();
            writeln!(
                to,
                "  (FFFE,E000) pi {:48} # {}, 1 Item",
                item_value_summary(&offset_table, width),
                byte_len,
            )?;

            // write compressed fragments
            for fragment in fragments {
                let byte_len = fragment.len();
                writeln!(
                    to,
                    "  (FFFE,E000) pi {:48} # {}, 1 Item",
                    item_value_summary(&fragment, width),
                    byte_len,
                )?;
            }
        }
        DicomValue::Primitive(value) => {
            let vr = elem.vr();
            let byte_len = value.calculate_byte_len();
            writeln!(
                to,
                "{} {} {:48} # {}, {} {}",
                elem.tag(),
                vr,
                value_summary(&value, vr, width),
                byte_len,
                vm,
                tag_alias
            )?;
        }
    }

    Ok(())
}

fn dump_item<W, D>(to: &mut W, item: &InMemDicomObject<D>, width: u32, depth: u32) -> IoResult<()>
where
    W: ?Sized + Write,
    D: DataDictionary,
{
    let indent: String = std::iter::repeat(' ').take((depth * 2) as usize).collect();
    let trail: String = std::iter::repeat(' ')
        .take(usize::max(21, width as usize - 4 - indent.len()))
        .collect();
    writeln!(to, "{}(FFFE,E000) na Item {} # 0, 0 Item", indent, trail)?;
    dump(to, item, width, depth + 1)?;
    writeln!(
        to,
        "{}(FFFE,E00D) na (ItemDelimitationItem) {:23} # 0, 0 ItemDelimitationItem",
        indent, ""
    )?;
    Ok(())
}

fn value_summary(value: &PrimitiveValue, vr: VR, max_characters: u32) -> Cow<str> {
    use PrimitiveValue::*;
    match (value, vr) {
        (F32(values), _) => format_value_list(values, max_characters).into(),
        (F64(values), _) => format_value_list(values, max_characters).into(),
        (I32(values), _) => format_value_list(values, max_characters).into(),
        (I64(values), _) => format_value_list(values, max_characters).into(),
        (U32(values), _) => format_value_list(values, max_characters).into(),
        (U64(values), _) => format_value_list(values, max_characters).into(),
        (I16(values), _) => format_value_list(values, max_characters).into(),
        (U16(values), VR::OW) => format_value_list(
            values.into_iter().map(|n| format!("{:02X}", n)),
            max_characters,
        )
        .into(),
        (U16(values), _) => format_value_list(values, max_characters).into(),
        (U8(values), VR::OB) | (U8(values), VR::UN) => format_value_list(
            values.into_iter().map(|n| format!("{:02X}", n)),
            max_characters,
        )
        .into(),
        (U8(values), _) => format_value_list(values, max_characters).into(),
        (Tags(values), _) => format_value_list(values, max_characters).into(),
        (Strs(values), _) => format_value_list(values, max_characters).into(),
        (Date(values), _) => format_value_list(values, max_characters).into(),
        (Time(values), _) => format_value_list(values, max_characters).into(),
        (DateTime(values), _) => format_value_list(values, max_characters).into(),
        (Str(value), _) => cut_str(&value.to_string(), max_characters)
            .into_owned()
            .into(),
        (Empty, _) => "".into(),
    }
}

fn item_value_summary(data: &[u8], max_characters: u32) -> String {
    format_value_list(
        data.iter().map(|n| format!("{:02X}", n)),
        max_characters,
    )
}

fn format_value_list<I>(values: I, max_characters: u32) -> String
where
    I: IntoIterator,
    I::Item: std::fmt::Display,
{
    let pieces = values.into_iter().take(64);
    let max = max_characters as usize;
    let mut o = String::with_capacity(max);
    for piece in pieces {
        o.push_str(&piece.to_string());
        o.push('\\');
        if o.len() > max {
            break;
        }
    }
    o.pop();
    cut_str(&o, max_characters).into_owned()
}

fn cut_str(s: &str, max_characters: u32) -> Cow<str> {
    let max = (max_characters - 3) as usize;
    if s.len() > max {
        format!("{}...", &s[..max]).into()
    } else {
        s.into()
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::ffi::CStr;
use std::ptr;
use std::vec::IntoIter;

//use alloc::Alloc;
use check_errors;
use OomError;
use vk;
use VK_ENTRY;
use version::Version;

/// Queries the list of layers that are available when creating an instance.
pub fn layers_list() -> Result<LayersIterator, OomError> {
    unsafe {
        let mut num = 0;
        try!(check_errors(VK_ENTRY.EnumerateInstanceLayerProperties(&mut num, ptr::null_mut())));

        let mut layers: Vec<vk::LayerProperties> = Vec::with_capacity(num as usize);
        try!(check_errors(VK_ENTRY.EnumerateInstanceLayerProperties(&mut num,
                                                                    layers.as_mut_ptr())));
        layers.set_len(num as usize);

        Ok(LayersIterator {
            iter: layers.into_iter()
        })
    }
}

/// Properties of an available layer.
pub struct LayerProperties {
    props: vk::LayerProperties,
}

impl LayerProperties {
    /// Returns the name of the layer.
    #[inline]
    pub fn name(&self) -> &str {
        unsafe { CStr::from_ptr(self.props.layerName.as_ptr()).to_str().unwrap() }
    }

    /// Returns a description of the layer.
    #[inline]
    pub fn description(&self) -> &str {
        unsafe { CStr::from_ptr(self.props.description.as_ptr()).to_str().unwrap() }
    }

    /// Returns the version of Vulkan supported by this layer.
    #[inline]
    pub fn vulkan_version(&self) -> Version {
        Version::from_vulkan_version(self.props.specVersion)
    }

    /// Returns an implementation-specific version number for this layer.
    #[inline]
    pub fn implementation_version(&self) -> u32 {
        self.props.implementationVersion
    }
}

/// Iterator that produces the list of layers that are available.
// TODO: #[derive(Debug, Clone)]
pub struct LayersIterator {
    iter: IntoIter<vk::LayerProperties>
}

impl Iterator for LayersIterator {
    type Item = LayerProperties;

    #[inline]
    fn next(&mut self) -> Option<LayerProperties> {
        self.iter.next().map(|p| LayerProperties { props: p })
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.iter.size_hint()
    }
}

impl ExactSizeIterator for LayersIterator {
}

#[cfg(test)]
mod tests {
    use instance;

    #[test]
    fn layers_list() {
        let _ = instance::layers_list().collect::<Vec<_>>();
    }
}
<commit_msg>Fix layers test<commit_after>// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::ffi::CStr;
use std::ptr;
use std::vec::IntoIter;

//use alloc::Alloc;
use check_errors;
use OomError;
use vk;
use VK_ENTRY;
use version::Version;

/// Queries the list of layers that are available when creating an instance.
pub fn layers_list() -> Result<LayersIterator, OomError> {
    unsafe {
        let mut num = 0;
        try!(check_errors(VK_ENTRY.EnumerateInstanceLayerProperties(&mut num, ptr::null_mut())));

        let mut layers: Vec<vk::LayerProperties> = Vec::with_capacity(num as usize);
        try!(check_errors(VK_ENTRY.EnumerateInstanceLayerProperties(&mut num,
                                                                    layers.as_mut_ptr())));
        layers.set_len(num as usize);

        Ok(LayersIterator {
            iter: layers.into_iter()
        })
    }
}

/// Properties of an available layer.
pub struct LayerProperties {
    props: vk::LayerProperties,
}

impl LayerProperties {
    /// Returns the name of the layer.
    #[inline]
    pub fn name(&self) -> &str {
        unsafe { CStr::from_ptr(self.props.layerName.as_ptr()).to_str().unwrap() }
    }

    /// Returns a description of the layer.
    #[inline]
    pub fn description(&self) -> &str {
        unsafe { CStr::from_ptr(self.props.description.as_ptr()).to_str().unwrap() }
    }

    /// Returns the version of Vulkan supported by this layer.
    #[inline]
    pub fn vulkan_version(&self) -> Version {
        Version::from_vulkan_version(self.props.specVersion)
    }

    /// Returns an implementation-specific version number for this layer.
    #[inline]
    pub fn implementation_version(&self) -> u32 {
        self.props.implementationVersion
    }
}

/// Iterator that produces the list of layers that are available.
// TODO: #[derive(Debug, Clone)]
pub struct LayersIterator {
    iter: IntoIter<vk::LayerProperties>
}

impl Iterator for LayersIterator {
    type Item = LayerProperties;

    #[inline]
    fn next(&mut self) -> Option<LayerProperties> {
        self.iter.next().map(|p| LayerProperties { props: p })
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.iter.size_hint()
    }
}

impl ExactSizeIterator for LayersIterator {
}

#[cfg(test)]
mod tests {
    use instance;

    #[test]
    fn layers_list() {
        let mut list = instance::layers_list().unwrap();
        while let Some(_) = list.next() {}
    }
}
<|endoftext|>


<commit_before>use std::sync::Arc;

use wayland_backend::server::{ClientId, ObjectData, ObjectId};

use crate::{Client, DisplayHandle, Resource};

/// A trait which provides an implementation for handling a client's requests from a resource with some type
/// of associated user data.
///
///  ## General usage
///
/// You need to implement this trait on your `State` for every type of Wayland object that will be processed
/// by the [`Display`](crate::Display) working with your `State`.
///
/// You can have different implementations of the trait for the same interface but different `UserData` type,
/// this way the events for a given object will be processed by the adequate implementation depending on
/// which `UserData` was assigned to it at creation.
///
/// The way this trait works is that the [`Dispatch::request()`] method will be invoked by the
/// [`Display`](crate::Display) for every request received by an object. Your implementation can then match
/// on the associated [`Resource::Request`] enum and do any processing needed with that event.
///
/// If the request being processed created a new object, you'll receive it as a [`New<I>`]. When that is the
/// case, you *must* initialize it using the [`DataInit`] argument. **Failing to do so will cause a **panic**.
///
/// ## Modularity
///
/// To provide generic handlers for downstream usage, it is possible to make an implementation of the trait
/// that is generic over the last type argument, as illustrated below. Users will then be able to
/// automatically delegate their implementation to yours using the [`delegate_dispatch!`] macro.
///
/// As a result, when your implementation is instanciated, the last type parameter `State` will be the state
/// struct of the app using your generic implementation. You can put additional trait constraints on it to
/// specify an interface between your module and downstream code, as illustrated in this example:
///
/// ```
/// # // Maintainers: If this example changes, please make sure you also carry those changes over to the
/// # // delegate_dispatch macro.
/// use wayland_server::{protocol::wl_output, Dispatch};
///
/// /// The type we want to delegate to
/// struct DelegateToMe;
///
/// /// The user data relevant for your implementation.
/// /// When providing delegate implementation, it is recommended to use your own type here, even if it is
/// /// just a unit struct: using () would cause a risk of clashing with an other such implementation.
/// struct MyUserData;
///
/// // Now a generic implementation of Dispatch, we are generic over the last type argument instead of using
/// // the default State=Self.
/// impl<State> Dispatch<wl_output::WlOutput, MyUserData, State> for DelegateToMe
/// where
///     // State is the type which has delegated to this type, so it needs to have an impl of Dispatch itself
///     State: Dispatch<wl_output::WlOutput, MyUserData>,
///     // If your delegate type has some internal state, it'll need to access it, and you can
///     // require it by adding custom trait bounds.
///     // In this example, we just require an AsMut implementation
///     State: AsMut<DelegateToMe>,
/// {
///     fn request(
///         state: &mut State,
///         _client: &wayland_server::Client,
///         _resource: &wl_output::WlOutput,
///         _request: wl_output::Request,
///         _udata: &MyUserData,
///         _dhandle: &wayland_server::DisplayHandle,
///         _data_init: &mut wayland_server::DataInit<'_, State>,
///     ) {
///         // Here the delegate may handle incoming requests as it pleases.
///
///         // For example, it retrives its state and does some processing with it
///         let me: &mut DelegateToMe = state.as_mut();
///         // do something with `me` ...
/// #       std::mem::drop(me) // use `me` to avoid a warning
///     }
/// }
/// ```
///
/// **Note:** Due to limitations in Rust's trait resolution algorithm, a type providing a generic
/// implementation of [`Dispatch`] cannot be used directly as the dispatching state, as rustc
/// currently fails to understand that it also provides `Dispatch<I, U, Self>` (assuming all other
/// trait bounds are respected as well).
pub trait Dispatch<I: Resource, UserData, State = Self>: Sized {
    /// Called when a request from a client is processed.
    ///
    /// The implementation of this function will vary depending on what protocol is being implemented. Typically
    /// the server may respond to clients by sending events to the resource, or some other resource stored in
    /// the user data.
    fn request(
        state: &mut State,
        client: &Client,
        resource: &I,
        request: I::Request,
        data: &UserData,
        dhandle: &DisplayHandle,
        data_init: &mut DataInit<'_, State>,
    );

    /// Called when the object this user data is associated with has been destroyed.
    ///
    /// Note this type only provides an immutable reference to the user data, you will need to use
    /// interior mutability to change it.
    ///
    /// Typically a [`Mutex`](std::sync::Mutex) would be used to have interior mutability.
    ///
    /// You are given the [`ObjectId`] and [`ClientId`] associated with the destroyed object for cleanup
    /// convenience.
    ///
    /// By default this method does nothing.
    fn destroyed(_state: &mut State, _client: ClientId, _resource: ObjectId, _data: &UserData) {}
}

/// The [`ObjectData`] implementation that is internally used by this crate
#[derive(Debug)]
pub struct ResourceData<I, U> {
    marker: std::marker::PhantomData<fn(I)>,
    /// The user-data associated with this object
    pub udata: U,
}

/// A newly created object that needs to be initialized. See [`DataInit`].
#[derive(Debug)]
pub struct New<I> {
    id: I,
}

impl<I> New<I> {
    #[doc(hidden)]
    // This is only to be used by code generated by wayland-scanner
    pub fn wrap(id: I) -> New<I> {
        New { id }
    }
}

/// Helper to initialize client-created objects
///
/// This helper is provided to you in your [`Dispatch`] and [`GlobalDispatch`](super::GlobalDispatch) to
/// initialize objects created by the client, by assigning them their user-data (or [`ObjectData`] if you
/// need to go this lower-level route).
///
/// This step is mandatory, and **failing to initialize a newly created object will cause a panic**.
#[derive(Debug)]
pub struct DataInit<'a, D: 'static> {
    pub(crate) store: &'a mut Option<Arc<dyn ObjectData<D>>>,
}

impl<'a, D> DataInit<'a, D> {
    /// Initialize an object by assigning it its user-data
    pub fn init<I: Resource + 'static, U: Send + Sync + 'static>(
        &mut self,
        resource: New<I>,
        data: U,
    ) -> I
    where
        D: Dispatch<I, U> + 'static,
    {
        let arc = Arc::new(ResourceData::<I, _>::new(data));
        *self.store = Some(arc.clone() as Arc<_>);
        let mut obj = resource.id;
        obj.__set_object_data(arc);
        obj
    }

    /// Set a custom [`ObjectData`] for this object
    ///
    /// This object data is not managed by `wayland-server`, as a result you will not
    /// be able to retreive it through [`Resource::data()`](Resource::data).
    /// Instead, you'll need to retrieve it using [`Resource::object_data()`](Resource::object_data) and
    /// handle the downcasting yourself.
    pub fn custom_init<I: Resource + 'static>(
        &mut self,
        resource: New<I>,
        data: Arc<dyn ObjectData<D>>,
    ) -> I {
        *self.store = Some(data.clone());
        let mut obj = resource.id;
        obj.__set_object_data(data.into_any_arc());
        obj
    }
}

/*
 * Dispatch delegation helpers.
 */

impl<I, U> ResourceData<I, U> {
    pub(crate) fn new(udata: U) -> Self {
        ResourceData { marker: std::marker::PhantomData, udata }
    }
}

impl<I: Resource + 'static, U: Send + Sync + 'static, D: Dispatch<I, U> + 'static> ObjectData<D>
    for ResourceData<I, U>
{
    fn request(
        self: Arc<Self>,
        handle: &wayland_backend::server::Handle,
        data: &mut D,
        client_id: wayland_backend::server::ClientId,
        msg: wayland_backend::protocol::Message<wayland_backend::server::ObjectId>,
    ) -> Option<Arc<dyn ObjectData<D>>> {
        let dhandle = DisplayHandle::from(handle.clone());
        let client = match Client::from_id(&dhandle, client_id) {
            Ok(v) => v,
            Err(_) => {
                crate::log_error!("Receiving a request from a dead client ?!");
                return None;
            }
        };

        let (resource, request) = match I::parse_request(&dhandle, msg) {
            Ok(v) => v,
            Err(e) => {
                crate::log_warn!("Dispatching error encountered: {:?}, killing client.", e);
                // TODO: Kill client
                return None;
            }
        };
        let udata = resource.data::<U>().expect("Wrong user_data value for object");

        let mut new_data = None;

        <D as Dispatch<I, U>>::request(
            data,
            &client,
            &resource,
            request,
            udata,
            &dhandle,
            &mut DataInit { store: &mut new_data },
        );

        new_data
    }

    fn destroyed(
        &self,
        data: &mut D,
        client_id: wayland_backend::server::ClientId,
        object_id: wayland_backend::server::ObjectId,
    ) {
        <D as Dispatch<I, U>>::destroyed(data, client_id, object_id, &self.udata)
    }
}

/// A helper macro which delegates a set of [`Dispatch`] implementations for a resource to some other type which
/// provides a generic [`Dispatch`] implementation.
///
/// This macro allows more easily delegating smaller parts of the protocol a compositor may wish to handle
/// in a modular fashion.
///
/// # Usage
///
/// For example, say you want to delegate events for [`WlOutput`](crate::protocol::wl_output::WlOutput)
/// to the `DelegateToMe` type from the [`Dispatch`] documentation.
///
/// ```
/// use wayland_server::{delegate_dispatch, protocol::wl_output};
/// #
/// # use wayland_server::Dispatch;
/// #
/// # struct DelegateToMe;
/// #
/// # impl<D> Dispatch<wl_output::WlOutput, (), D> for DelegateToMe
/// # where
/// #     D: Dispatch<wl_output::WlOutput, ()> + AsMut<DelegateToMe>,
/// # {
/// #     fn request(
/// #         _state: &mut D,
/// #         _client: &wayland_server::Client,
/// #         _resource: &wl_output::WlOutput,
/// #         _request: wl_output::Request,
/// #         _data: &(),
/// #         _dhandle: &wayland_server::DisplayHandle,
/// #         _data_init: &mut wayland_server::DataInit<'_, D>,
/// #     ) {
/// #     }
/// # }
/// #
/// # type MyUserData = ();
///
/// // ExampleApp is the type events will be dispatched to.
///
/// /// The application state
/// struct ExampleApp {
///     /// The delegate for handling wl_registry events.
///     delegate: DelegateToMe,
/// }
///
/// // Use delegate_dispatch to implement Dispatch<wl_output::WlOutput, MyUserData> for ExampleApp.
/// delegate_dispatch!(ExampleApp: [wl_output::WlOutput: MyUserData] => DelegateToMe);
///
/// // DelegateToMe requires that ExampleApp implements AsMut<DelegateToMe>, so we provide the trait implementation.
/// impl AsMut<DelegateToMe> for ExampleApp {
///     fn as_mut(&mut self) -> &mut DelegateToMe {
///         &mut self.delegate
///     }
/// }
/// ```
#[macro_export]
macro_rules! delegate_dispatch {
    ($(@< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? $dispatch_from:ty : [$interface: ty: $udata: ty] => $dispatch_to: ty) => {
        impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::Dispatch<$interface, $udata> for $dispatch_from {
            fn request(
                state: &mut Self,
                client: &$crate::Client,
                resource: &$interface,
                request: <$interface as $crate::Resource>::Request,
                data: &$udata,
                dhandle: &$crate::DisplayHandle,
                data_init: &mut $crate::DataInit<'_, Self>,
            ) {
                <$dispatch_to as $crate::Dispatch<$interface, $udata, Self>>::request(state, client, resource, request, data, dhandle, data_init)
            }

            fn destroyed(state: &mut Self, client: $crate::backend::ClientId, resource: $crate::backend::ObjectId, data: &$udata) {
                <$dispatch_to as $crate::Dispatch<$interface, $udata, Self>>::destroyed(state, client, resource, data)
            }
        }
    };
}
<commit_msg>server: kill client when receiving a malformed message<commit_after>use std::sync::Arc;

use wayland_backend::{
    protocol::ProtocolError,
    server::{ClientId, DisconnectReason, ObjectData, ObjectId},
};

use crate::{Client, DisplayHandle, Resource};

/// A trait which provides an implementation for handling a client's requests from a resource with some type
/// of associated user data.
///
///  ## General usage
///
/// You need to implement this trait on your `State` for every type of Wayland object that will be processed
/// by the [`Display`](crate::Display) working with your `State`.
///
/// You can have different implementations of the trait for the same interface but different `UserData` type,
/// this way the events for a given object will be processed by the adequate implementation depending on
/// which `UserData` was assigned to it at creation.
///
/// The way this trait works is that the [`Dispatch::request()`] method will be invoked by the
/// [`Display`](crate::Display) for every request received by an object. Your implementation can then match
/// on the associated [`Resource::Request`] enum and do any processing needed with that event.
///
/// If the request being processed created a new object, you'll receive it as a [`New<I>`]. When that is the
/// case, you *must* initialize it using the [`DataInit`] argument. **Failing to do so will cause a **panic**.
///
/// ## Modularity
///
/// To provide generic handlers for downstream usage, it is possible to make an implementation of the trait
/// that is generic over the last type argument, as illustrated below. Users will then be able to
/// automatically delegate their implementation to yours using the [`delegate_dispatch!`] macro.
///
/// As a result, when your implementation is instanciated, the last type parameter `State` will be the state
/// struct of the app using your generic implementation. You can put additional trait constraints on it to
/// specify an interface between your module and downstream code, as illustrated in this example:
///
/// ```
/// # // Maintainers: If this example changes, please make sure you also carry those changes over to the
/// # // delegate_dispatch macro.
/// use wayland_server::{protocol::wl_output, Dispatch};
///
/// /// The type we want to delegate to
/// struct DelegateToMe;
///
/// /// The user data relevant for your implementation.
/// /// When providing delegate implementation, it is recommended to use your own type here, even if it is
/// /// just a unit struct: using () would cause a risk of clashing with an other such implementation.
/// struct MyUserData;
///
/// // Now a generic implementation of Dispatch, we are generic over the last type argument instead of using
/// // the default State=Self.
/// impl<State> Dispatch<wl_output::WlOutput, MyUserData, State> for DelegateToMe
/// where
///     // State is the type which has delegated to this type, so it needs to have an impl of Dispatch itself
///     State: Dispatch<wl_output::WlOutput, MyUserData>,
///     // If your delegate type has some internal state, it'll need to access it, and you can
///     // require it by adding custom trait bounds.
///     // In this example, we just require an AsMut implementation
///     State: AsMut<DelegateToMe>,
/// {
///     fn request(
///         state: &mut State,
///         _client: &wayland_server::Client,
///         _resource: &wl_output::WlOutput,
///         _request: wl_output::Request,
///         _udata: &MyUserData,
///         _dhandle: &wayland_server::DisplayHandle,
///         _data_init: &mut wayland_server::DataInit<'_, State>,
///     ) {
///         // Here the delegate may handle incoming requests as it pleases.
///
///         // For example, it retrives its state and does some processing with it
///         let me: &mut DelegateToMe = state.as_mut();
///         // do something with `me` ...
/// #       std::mem::drop(me) // use `me` to avoid a warning
///     }
/// }
/// ```
///
/// **Note:** Due to limitations in Rust's trait resolution algorithm, a type providing a generic
/// implementation of [`Dispatch`] cannot be used directly as the dispatching state, as rustc
/// currently fails to understand that it also provides `Dispatch<I, U, Self>` (assuming all other
/// trait bounds are respected as well).
pub trait Dispatch<I: Resource, UserData, State = Self>: Sized {
    /// Called when a request from a client is processed.
    ///
    /// The implementation of this function will vary depending on what protocol is being implemented. Typically
    /// the server may respond to clients by sending events to the resource, or some other resource stored in
    /// the user data.
    fn request(
        state: &mut State,
        client: &Client,
        resource: &I,
        request: I::Request,
        data: &UserData,
        dhandle: &DisplayHandle,
        data_init: &mut DataInit<'_, State>,
    );

    /// Called when the object this user data is associated with has been destroyed.
    ///
    /// Note this type only provides an immutable reference to the user data, you will need to use
    /// interior mutability to change it.
    ///
    /// Typically a [`Mutex`](std::sync::Mutex) would be used to have interior mutability.
    ///
    /// You are given the [`ObjectId`] and [`ClientId`] associated with the destroyed object for cleanup
    /// convenience.
    ///
    /// By default this method does nothing.
    fn destroyed(_state: &mut State, _client: ClientId, _resource: ObjectId, _data: &UserData) {}
}

/// The [`ObjectData`] implementation that is internally used by this crate
#[derive(Debug)]
pub struct ResourceData<I, U> {
    marker: std::marker::PhantomData<fn(I)>,
    /// The user-data associated with this object
    pub udata: U,
}

/// A newly created object that needs to be initialized. See [`DataInit`].
#[derive(Debug)]
pub struct New<I> {
    id: I,
}

impl<I> New<I> {
    #[doc(hidden)]
    // This is only to be used by code generated by wayland-scanner
    pub fn wrap(id: I) -> New<I> {
        New { id }
    }
}

/// Helper to initialize client-created objects
///
/// This helper is provided to you in your [`Dispatch`] and [`GlobalDispatch`](super::GlobalDispatch) to
/// initialize objects created by the client, by assigning them their user-data (or [`ObjectData`] if you
/// need to go this lower-level route).
///
/// This step is mandatory, and **failing to initialize a newly created object will cause a panic**.
#[derive(Debug)]
pub struct DataInit<'a, D: 'static> {
    pub(crate) store: &'a mut Option<Arc<dyn ObjectData<D>>>,
}

impl<'a, D> DataInit<'a, D> {
    /// Initialize an object by assigning it its user-data
    pub fn init<I: Resource + 'static, U: Send + Sync + 'static>(
        &mut self,
        resource: New<I>,
        data: U,
    ) -> I
    where
        D: Dispatch<I, U> + 'static,
    {
        let arc = Arc::new(ResourceData::<I, _>::new(data));
        *self.store = Some(arc.clone() as Arc<_>);
        let mut obj = resource.id;
        obj.__set_object_data(arc);
        obj
    }

    /// Set a custom [`ObjectData`] for this object
    ///
    /// This object data is not managed by `wayland-server`, as a result you will not
    /// be able to retreive it through [`Resource::data()`](Resource::data).
    /// Instead, you'll need to retrieve it using [`Resource::object_data()`](Resource::object_data) and
    /// handle the downcasting yourself.
    pub fn custom_init<I: Resource + 'static>(
        &mut self,
        resource: New<I>,
        data: Arc<dyn ObjectData<D>>,
    ) -> I {
        *self.store = Some(data.clone());
        let mut obj = resource.id;
        obj.__set_object_data(data.into_any_arc());
        obj
    }
}

/*
 * Dispatch delegation helpers.
 */

impl<I, U> ResourceData<I, U> {
    pub(crate) fn new(udata: U) -> Self {
        ResourceData { marker: std::marker::PhantomData, udata }
    }
}

impl<I: Resource + 'static, U: Send + Sync + 'static, D: Dispatch<I, U> + 'static> ObjectData<D>
    for ResourceData<I, U>
{
    fn request(
        self: Arc<Self>,
        handle: &wayland_backend::server::Handle,
        data: &mut D,
        client_id: wayland_backend::server::ClientId,
        msg: wayland_backend::protocol::Message<wayland_backend::server::ObjectId>,
    ) -> Option<Arc<dyn ObjectData<D>>> {
        let dhandle = DisplayHandle::from(handle.clone());
        let client = match Client::from_id(&dhandle, client_id) {
            Ok(v) => v,
            Err(_) => {
                crate::log_error!("Receiving a request from a dead client ?!");
                return None;
            }
        };

        let (sender_id, opcode) = (msg.sender_id.protocol_id(), msg.opcode);

        let (resource, request) = match I::parse_request(&dhandle, msg) {
            Ok(v) => v,
            Err(e) => {
                crate::log_warn!("Dispatching error encountered: {:?}, killing client.", e);
                handle.kill_client(
                    client.id(),
                    DisconnectReason::ProtocolError(ProtocolError {
                        code: 1,
                        object_id: 0,
                        object_interface: "wl_display".into(),
                        message: format!(
                            "Malformed request received for id {} and opcode {}.",
                            sender_id, opcode
                        ),
                    }),
                );
                return None;
            }
        };
        let udata = resource.data::<U>().expect("Wrong user_data value for object");

        let mut new_data = None;

        <D as Dispatch<I, U>>::request(
            data,
            &client,
            &resource,
            request,
            udata,
            &dhandle,
            &mut DataInit { store: &mut new_data },
        );

        new_data
    }

    fn destroyed(
        &self,
        data: &mut D,
        client_id: wayland_backend::server::ClientId,
        object_id: wayland_backend::server::ObjectId,
    ) {
        <D as Dispatch<I, U>>::destroyed(data, client_id, object_id, &self.udata)
    }
}

/// A helper macro which delegates a set of [`Dispatch`] implementations for a resource to some other type which
/// provides a generic [`Dispatch`] implementation.
///
/// This macro allows more easily delegating smaller parts of the protocol a compositor may wish to handle
/// in a modular fashion.
///
/// # Usage
///
/// For example, say you want to delegate events for [`WlOutput`](crate::protocol::wl_output::WlOutput)
/// to the `DelegateToMe` type from the [`Dispatch`] documentation.
///
/// ```
/// use wayland_server::{delegate_dispatch, protocol::wl_output};
/// #
/// # use wayland_server::Dispatch;
/// #
/// # struct DelegateToMe;
/// #
/// # impl<D> Dispatch<wl_output::WlOutput, (), D> for DelegateToMe
/// # where
/// #     D: Dispatch<wl_output::WlOutput, ()> + AsMut<DelegateToMe>,
/// # {
/// #     fn request(
/// #         _state: &mut D,
/// #         _client: &wayland_server::Client,
/// #         _resource: &wl_output::WlOutput,
/// #         _request: wl_output::Request,
/// #         _data: &(),
/// #         _dhandle: &wayland_server::DisplayHandle,
/// #         _data_init: &mut wayland_server::DataInit<'_, D>,
/// #     ) {
/// #     }
/// # }
/// #
/// # type MyUserData = ();
///
/// // ExampleApp is the type events will be dispatched to.
///
/// /// The application state
/// struct ExampleApp {
///     /// The delegate for handling wl_registry events.
///     delegate: DelegateToMe,
/// }
///
/// // Use delegate_dispatch to implement Dispatch<wl_output::WlOutput, MyUserData> for ExampleApp.
/// delegate_dispatch!(ExampleApp: [wl_output::WlOutput: MyUserData] => DelegateToMe);
///
/// // DelegateToMe requires that ExampleApp implements AsMut<DelegateToMe>, so we provide the trait implementation.
/// impl AsMut<DelegateToMe> for ExampleApp {
///     fn as_mut(&mut self) -> &mut DelegateToMe {
///         &mut self.delegate
///     }
/// }
/// ```
#[macro_export]
macro_rules! delegate_dispatch {
    ($(@< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? $dispatch_from:ty : [$interface: ty: $udata: ty] => $dispatch_to: ty) => {
        impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::Dispatch<$interface, $udata> for $dispatch_from {
            fn request(
                state: &mut Self,
                client: &$crate::Client,
                resource: &$interface,
                request: <$interface as $crate::Resource>::Request,
                data: &$udata,
                dhandle: &$crate::DisplayHandle,
                data_init: &mut $crate::DataInit<'_, Self>,
            ) {
                <$dispatch_to as $crate::Dispatch<$interface, $udata, Self>>::request(state, client, resource, request, data, dhandle, data_init)
            }

            fn destroyed(state: &mut Self, client: $crate::backend::ClientId, resource: $crate::backend::ObjectId, data: &$udata) {
                <$dispatch_to as $crate::Dispatch<$interface, $udata, Self>>::destroyed(state, client, resource, data)
            }
        }
    };
}
<|endoftext|>


<commit_before>extern crate handlebars;
extern crate itertools;
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate serde_json;

use handlebars::Handlebars;
use itertools::Itertools;
use serde_json::Value;
use std::collections::BTreeMap;
use std::ascii::AsciiExt;

trait Codegen {
    fn codegen(&self, handlebars: &Handlebars) -> String;
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    #[serde(rename="major-version")]
    pub major_version: u8,
    #[serde(rename="minor-version")]
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn load() -> AMQProtocolDefinition {
        let specs = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/specs/amqp-rabbitmq-0.9.1.json"));

        serde_json::from_str::<AMQProtocolDefinition>(specs).expect("Failed to parse AMQP specs file")
    }

    pub fn codegen(&self, templates: &AMQPTemplates) -> String {
        let handlebars = register_templates(templates);
        let mut data   = BTreeMap::new();

        data.insert("name".to_string(),          self.name.clone());
        data.insert("major_version".to_string(), format!("{}", self.major_version));
        data.insert("minor_version".to_string(), format!("{}", self.minor_version));
        data.insert("revision".to_string(),      format!("{}", self.revision));
        data.insert("port".to_string(),          format!("{}", self.port));
        data.insert("copyright".to_string(),     self.copyright.iter().join(""));
        data.insert("domains".to_string(),       self.domains.iter().map(|domain| domain.codegen(&handlebars)).join("\n"));
        data.insert("constants".to_string(),     self.constants.iter().map(|constant| constant.codegen(&handlebars)).join("\n"));
        data.insert("classes".to_string(),       self.classes.iter().map(|klass| klass.codegen(&handlebars)).join("\n"));

        handlebars.render("main", &data).expect("Failed to render main template")
    }

    pub fn codegen_full(self, full_template: &str) -> String {
        let mut handlebars = Handlebars::new();
        let mut data = BTreeMap::new();

        handlebars.register_escape_fn(handlebars::no_escape);
        handlebars.register_template_string("full", full_template).expect("Failed to register full template");

        data.insert("specs".to_string(), self);

        handlebars.render("full", &data).expect("Failed to render full template")
    }
}

fn register_templates(templates: &AMQPTemplates) -> Handlebars {
    let mut handlebars = Handlebars::new();

    handlebars.register_escape_fn(handlebars::no_escape);

    handlebars.register_template_string("main",     &templates.main).expect("Failed to register main template");
    handlebars.register_template_string("domain",   &templates.domain).expect("Failed to register domain template");
    handlebars.register_template_string("constant", &templates.constant).expect("Failed to register constant template");
    handlebars.register_template_string("class",    &templates.klass).expect("Failed to register class template");
    handlebars.register_template_string("method",   &templates.method).expect("Failed to register method template");
    handlebars.register_template_string("argument", &templates.argument).expect("Failed to register argument template");
    handlebars.register_template_string("property", &templates.property).expect("Failed to register property template");

    handlebars
}

pub struct AMQPTemplates {
    pub main:     String,
    pub domain:   String,
    pub constant: String,
    pub klass:    String,
    pub method:   String,
    pub argument: String,
    pub property: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub enum AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl AMQPType {
    fn to_string(&self) -> String {
        match *self {
            AMQPType::Bit       => "bit",
            AMQPType::Octet     => "octet",
            AMQPType::Short     => "short",
            AMQPType::Long      => "long",
            AMQPType::LongLong  => "longlong",
            AMQPType::ShortStr  => "shortstr",
            AMQPType::LongStr   => "longstr",
            AMQPType::Table     => "table",
            AMQPType::Timestamp => "timestamp",
        }.to_string()
    }

    fn to_rust_type(&self) -> String {
        match *self {
            AMQPType::Bit       => "bool",
            AMQPType::Octet     => "u8",
            AMQPType::Short     => "u16",
            AMQPType::Long      => "u32",
            AMQPType::LongLong  => "u64",
            AMQPType::ShortStr  => "String",
            AMQPType::LongStr   => "String",
            AMQPType::Table     => "String", /* FIXME: add a custom type */
            AMQPType::Timestamp => "u64",
        }.to_string()
    }
}

fn camel_name(name: &str) -> String {
    let mut new_word: bool = true;
    name.chars().fold("".to_string(), |mut result, ch| {
        if ch == '-' || ch == '_' || ch == ' ' {
            new_word = true;
            result
        } else {
            result.push(if new_word { ch.to_ascii_uppercase() } else { ch.to_ascii_lowercase() });
            new_word = false;
            result
        }
    })
}

fn snake_name(name: &str) -> String {
    match name {
        "type"   => "amqp_type".to_string(),
        "return" => "amqp_return".to_string(),
        name     => name.replace("-", "_"),
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPDomain(String, AMQPType);

impl Codegen for AMQPDomain {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(), self.0.clone());
        data.insert("type".to_string(), self.1.to_string());

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

impl Codegen for AMQPConstant {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(),  self.name.clone());
        data.insert("value".to_string(), format!("{}", self.value));
        if let Some(ref klass) = self.klass {
            data.insert("class".to_string(), klass.clone());
        }

        handlebars.render("constant", &data).expect("Failed to render constant template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

impl Codegen for AMQPClass {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),         format!("{}", self.id));
        data.insert("methods".to_string(),    self.methods.iter().map(|method| method.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),       self.name.clone());
        data.insert("snake_name".to_string(), snake_name(&self.name));
        if let Some(ref properties) = self.properties {
            data.insert("properties".to_string(), properties.iter().map(|prop| prop.codegen(&handlebars)).join("\n"));
        }

        handlebars.render("class", &data).expect("Failed to render class template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

impl Codegen for AMQPMethod {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),              format!("{}", self.id));
        data.insert("arguments".to_string(),       self.arguments.iter().map(|arg| arg.codegen(&handlebars)).join("\n"));
        data.insert("argument_fields".to_string(), self.arguments.iter().map(|arg| arg.codegen_field()).join("\n"));
        data.insert("name".to_string(),            self.name.clone());
        data.insert("synchronous".to_string(),     format!("{}", self.synchronous.unwrap_or(false)));
        data.insert("camel_name".to_string(),      camel_name(&self.name));
        data.insert("snake_name".to_string(),      snake_name(&self.name));

        handlebars.render("method", &data).expect("Failed to render method template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    #[serde(rename="default-value")]
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

impl AMQPArgument {
    fn serialize_default_value(&self) -> String {
        if let Some(ref default_value) = self.default_value {
            let s = default_value.to_string();
            match default_value {
                /* TODO: simplify that, handle Table */
                &Value::String(_) => format!("Some({}.to_string())", s),
                &Value::Number(_) => format!("Some({})", s),
                &Value::Bool(_)   => format!("Some({})", s),
                _                 => "None".to_string(),
            }
        } else {
            "None".to_string()
        }
    }

    fn serialize_domain(&self) -> String {
        if let Some(ref domain) = self.domain {
            format!("Some(\"{}\".to_string())", domain)
        } else {
            "None".to_string()
        }
    }

    fn codegen_field(&self) -> String {
        format!("pub {}: {},", snake_name(&self.name), camel_name(&self.name))
    }
}

impl Codegen for AMQPArgument {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        if let Some(ref amqp_type) = self.amqp_type {
            data.insert("type".to_string(), amqp_type.to_string());
            data.insert("value_field".to_string(), format!("pub value: {},", amqp_type.to_rust_type()));
            data.insert("default_value_method".to_string(), format!("pub fn default_value() -> Option<{}> {{ {} }}", amqp_type.to_rust_type(), &self.serialize_default_value()));
        }
        data.insert("name".to_string(),          self.name.clone());
        data.insert("camel_name".to_string(),    camel_name(&self.name));
        data.insert("snake_name".to_string(),    snake_name(&self.name));
        data.insert("domain".to_string(),        self.serialize_domain());

        handlebars.render("argument", &data).expect("Failed to render argument template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}

impl Codegen for AMQPProperty {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("type".to_string(),       self.amqp_type.to_string());
        data.insert("rust_type".to_string(),  self.amqp_type.to_rust_type());
        data.insert("name".to_string(),       self.name.clone());
        data.insert("camel_name".to_string(), camel_name(&self.name));

        handlebars.render("property", &data).expect("Failed to render property template")
    }
}

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

    fn specs() -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     vec!["Copyright 1\n".to_string(), "Copyright 2".to_string()],
            domains:       vec![AMQPDomain("domain1".to_string(), AMQPType::Octet)],
            constants:     vec![
                AMQPConstant {
                    name:  "constant1".to_string(),
                    value: 128,
                    klass: Some("class1".to_string()),
                }
            ],
            classes:       vec![
                AMQPClass {
                    id:         42,
                    methods:    vec![
                        AMQPMethod {
                            id:          64,
                            arguments:   vec![
                                AMQPArgument {
                                    amqp_type:     Some(AMQPType::LongStr),
                                    name:          "argument1".to_string(),
                                    default_value: Some(Value::String("value1".to_string())),
                                    domain:        Some("domain1".to_string()),
                                }
                            ],
                            name:        "method1".to_string(),
                            synchronous: Some(true),
                        }
                    ],
                    name:       "class1".to_string(),
                    properties: Some(vec![
                        AMQPProperty {
                            amqp_type: AMQPType::LongStr,
                            name:      "property1".to_string(),
                        }
                    ]),
                }
            ],
        }
    }

    fn templates() -> AMQPTemplates {
        AMQPTemplates {
            main:     r#"
{{name}} - {{major_version}}.{{minor_version}}.{{revision}}
{{copyright}}
port {{port}}
{{domains}}
{{constants}}
{{classes}}
"#.to_string(),
            domain:   "{{name}}: {{type}}".to_string(),
            constant: "{{name}}({{class}}) = {{value}}".to_string(),
            klass:    r#"
{{id}} - {{name}}
{{properties}}
{{methods}}
"#.to_string(),
            method:   r#"
{{id}} - {{name}}
synchronous: {{synchronous}}
{{arguments}}
"#.to_string(),
            argument: "{{name}}({{domain}}): {{type}} = {{default_value_method}}".to_string(),
            property: "{{name}}: {{type}}".to_string(),
        }
    }

    #[test]
    fn main_template() {
        assert_eq!(specs().codegen(&templates()), r#"
AMQP - 0.9.1
Copyright 1
Copyright 2
port 5672
domain1: octet
constant1(class1) = 128

42 - class1
property1: longstr

64 - method1
synchronous: true
argument1(Some("domain1".to_string())): longstr = pub fn default_value() -> Option<String> { Some("value1".to_string()) }

"#);
    }
}
<commit_msg>make the members of AMQPDomain public<commit_after>extern crate handlebars;
extern crate itertools;
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate serde_json;

use handlebars::Handlebars;
use itertools::Itertools;
use serde_json::Value;
use std::collections::BTreeMap;
use std::ascii::AsciiExt;

trait Codegen {
    fn codegen(&self, handlebars: &Handlebars) -> String;
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    #[serde(rename="major-version")]
    pub major_version: u8,
    #[serde(rename="minor-version")]
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn load() -> AMQProtocolDefinition {
        let specs = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/specs/amqp-rabbitmq-0.9.1.json"));

        serde_json::from_str::<AMQProtocolDefinition>(specs).expect("Failed to parse AMQP specs file")
    }

    pub fn codegen(&self, templates: &AMQPTemplates) -> String {
        let handlebars = register_templates(templates);
        let mut data   = BTreeMap::new();

        data.insert("name".to_string(),          self.name.clone());
        data.insert("major_version".to_string(), format!("{}", self.major_version));
        data.insert("minor_version".to_string(), format!("{}", self.minor_version));
        data.insert("revision".to_string(),      format!("{}", self.revision));
        data.insert("port".to_string(),          format!("{}", self.port));
        data.insert("copyright".to_string(),     self.copyright.iter().join(""));
        data.insert("domains".to_string(),       self.domains.iter().map(|domain| domain.codegen(&handlebars)).join("\n"));
        data.insert("constants".to_string(),     self.constants.iter().map(|constant| constant.codegen(&handlebars)).join("\n"));
        data.insert("classes".to_string(),       self.classes.iter().map(|klass| klass.codegen(&handlebars)).join("\n"));

        handlebars.render("main", &data).expect("Failed to render main template")
    }

    pub fn codegen_full(self, full_template: &str) -> String {
        let mut handlebars = Handlebars::new();
        let mut data = BTreeMap::new();

        handlebars.register_escape_fn(handlebars::no_escape);
        handlebars.register_template_string("full", full_template).expect("Failed to register full template");

        data.insert("specs".to_string(), self);

        handlebars.render("full", &data).expect("Failed to render full template")
    }
}

fn register_templates(templates: &AMQPTemplates) -> Handlebars {
    let mut handlebars = Handlebars::new();

    handlebars.register_escape_fn(handlebars::no_escape);

    handlebars.register_template_string("main",     &templates.main).expect("Failed to register main template");
    handlebars.register_template_string("domain",   &templates.domain).expect("Failed to register domain template");
    handlebars.register_template_string("constant", &templates.constant).expect("Failed to register constant template");
    handlebars.register_template_string("class",    &templates.klass).expect("Failed to register class template");
    handlebars.register_template_string("method",   &templates.method).expect("Failed to register method template");
    handlebars.register_template_string("argument", &templates.argument).expect("Failed to register argument template");
    handlebars.register_template_string("property", &templates.property).expect("Failed to register property template");

    handlebars
}

pub struct AMQPTemplates {
    pub main:     String,
    pub domain:   String,
    pub constant: String,
    pub klass:    String,
    pub method:   String,
    pub argument: String,
    pub property: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub enum AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl AMQPType {
    fn to_string(&self) -> String {
        match *self {
            AMQPType::Bit       => "bit",
            AMQPType::Octet     => "octet",
            AMQPType::Short     => "short",
            AMQPType::Long      => "long",
            AMQPType::LongLong  => "longlong",
            AMQPType::ShortStr  => "shortstr",
            AMQPType::LongStr   => "longstr",
            AMQPType::Table     => "table",
            AMQPType::Timestamp => "timestamp",
        }.to_string()
    }

    fn to_rust_type(&self) -> String {
        match *self {
            AMQPType::Bit       => "bool",
            AMQPType::Octet     => "u8",
            AMQPType::Short     => "u16",
            AMQPType::Long      => "u32",
            AMQPType::LongLong  => "u64",
            AMQPType::ShortStr  => "String",
            AMQPType::LongStr   => "String",
            AMQPType::Table     => "String", /* FIXME: add a custom type */
            AMQPType::Timestamp => "u64",
        }.to_string()
    }
}

fn camel_name(name: &str) -> String {
    let mut new_word: bool = true;
    name.chars().fold("".to_string(), |mut result, ch| {
        if ch == '-' || ch == '_' || ch == ' ' {
            new_word = true;
            result
        } else {
            result.push(if new_word { ch.to_ascii_uppercase() } else { ch.to_ascii_lowercase() });
            new_word = false;
            result
        }
    })
}

fn snake_name(name: &str) -> String {
    match name {
        "type"   => "amqp_type".to_string(),
        "return" => "amqp_return".to_string(),
        name     => name.replace("-", "_"),
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPDomain(pub String, pub AMQPType);

impl Codegen for AMQPDomain {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(), self.0.clone());
        data.insert("type".to_string(), self.1.to_string());

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

impl Codegen for AMQPConstant {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(),  self.name.clone());
        data.insert("value".to_string(), format!("{}", self.value));
        if let Some(ref klass) = self.klass {
            data.insert("class".to_string(), klass.clone());
        }

        handlebars.render("constant", &data).expect("Failed to render constant template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

impl Codegen for AMQPClass {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),         format!("{}", self.id));
        data.insert("methods".to_string(),    self.methods.iter().map(|method| method.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),       self.name.clone());
        data.insert("snake_name".to_string(), snake_name(&self.name));
        if let Some(ref properties) = self.properties {
            data.insert("properties".to_string(), properties.iter().map(|prop| prop.codegen(&handlebars)).join("\n"));
        }

        handlebars.render("class", &data).expect("Failed to render class template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

impl Codegen for AMQPMethod {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),              format!("{}", self.id));
        data.insert("arguments".to_string(),       self.arguments.iter().map(|arg| arg.codegen(&handlebars)).join("\n"));
        data.insert("argument_fields".to_string(), self.arguments.iter().map(|arg| arg.codegen_field()).join("\n"));
        data.insert("name".to_string(),            self.name.clone());
        data.insert("synchronous".to_string(),     format!("{}", self.synchronous.unwrap_or(false)));
        data.insert("camel_name".to_string(),      camel_name(&self.name));
        data.insert("snake_name".to_string(),      snake_name(&self.name));

        handlebars.render("method", &data).expect("Failed to render method template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    #[serde(rename="default-value")]
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

impl AMQPArgument {
    fn serialize_default_value(&self) -> String {
        if let Some(ref default_value) = self.default_value {
            let s = default_value.to_string();
            match default_value {
                /* TODO: simplify that, handle Table */
                &Value::String(_) => format!("Some({}.to_string())", s),
                &Value::Number(_) => format!("Some({})", s),
                &Value::Bool(_)   => format!("Some({})", s),
                _                 => "None".to_string(),
            }
        } else {
            "None".to_string()
        }
    }

    fn serialize_domain(&self) -> String {
        if let Some(ref domain) = self.domain {
            format!("Some(\"{}\".to_string())", domain)
        } else {
            "None".to_string()
        }
    }

    fn codegen_field(&self) -> String {
        format!("pub {}: {},", snake_name(&self.name), camel_name(&self.name))
    }
}

impl Codegen for AMQPArgument {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        if let Some(ref amqp_type) = self.amqp_type {
            data.insert("type".to_string(), amqp_type.to_string());
            data.insert("value_field".to_string(), format!("pub value: {},", amqp_type.to_rust_type()));
            data.insert("default_value_method".to_string(), format!("pub fn default_value() -> Option<{}> {{ {} }}", amqp_type.to_rust_type(), &self.serialize_default_value()));
        }
        data.insert("name".to_string(),          self.name.clone());
        data.insert("camel_name".to_string(),    camel_name(&self.name));
        data.insert("snake_name".to_string(),    snake_name(&self.name));
        data.insert("domain".to_string(),        self.serialize_domain());

        handlebars.render("argument", &data).expect("Failed to render argument template")
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}

impl Codegen for AMQPProperty {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("type".to_string(),       self.amqp_type.to_string());
        data.insert("rust_type".to_string(),  self.amqp_type.to_rust_type());
        data.insert("name".to_string(),       self.name.clone());
        data.insert("camel_name".to_string(), camel_name(&self.name));

        handlebars.render("property", &data).expect("Failed to render property template")
    }
}

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

    fn specs() -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     vec!["Copyright 1\n".to_string(), "Copyright 2".to_string()],
            domains:       vec![AMQPDomain("domain1".to_string(), AMQPType::Octet)],
            constants:     vec![
                AMQPConstant {
                    name:  "constant1".to_string(),
                    value: 128,
                    klass: Some("class1".to_string()),
                }
            ],
            classes:       vec![
                AMQPClass {
                    id:         42,
                    methods:    vec![
                        AMQPMethod {
                            id:          64,
                            arguments:   vec![
                                AMQPArgument {
                                    amqp_type:     Some(AMQPType::LongStr),
                                    name:          "argument1".to_string(),
                                    default_value: Some(Value::String("value1".to_string())),
                                    domain:        Some("domain1".to_string()),
                                }
                            ],
                            name:        "method1".to_string(),
                            synchronous: Some(true),
                        }
                    ],
                    name:       "class1".to_string(),
                    properties: Some(vec![
                        AMQPProperty {
                            amqp_type: AMQPType::LongStr,
                            name:      "property1".to_string(),
                        }
                    ]),
                }
            ],
        }
    }

    fn templates() -> AMQPTemplates {
        AMQPTemplates {
            main:     r#"
{{name}} - {{major_version}}.{{minor_version}}.{{revision}}
{{copyright}}
port {{port}}
{{domains}}
{{constants}}
{{classes}}
"#.to_string(),
            domain:   "{{name}}: {{type}}".to_string(),
            constant: "{{name}}({{class}}) = {{value}}".to_string(),
            klass:    r#"
{{id}} - {{name}}
{{properties}}
{{methods}}
"#.to_string(),
            method:   r#"
{{id}} - {{name}}
synchronous: {{synchronous}}
{{arguments}}
"#.to_string(),
            argument: "{{name}}({{domain}}): {{type}} = {{default_value_method}}".to_string(),
            property: "{{name}}: {{type}}".to_string(),
        }
    }

    #[test]
    fn main_template() {
        assert_eq!(specs().codegen(&templates()), r#"
AMQP - 0.9.1
Copyright 1
Copyright 2
port 5672
domain1: octet
constant1(class1) = 128

42 - class1
property1: longstr

64 - method1
synchronous: true
argument1(Some("domain1".to_string())): longstr = pub fn default_value() -> Option<String> { Some("value1".to_string()) }

"#);
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::cli::Options;
use abigen::Abigen;
use anyhow::anyhow;
use boogie_backend::{
    add_prelude, boogie_wrapper::BoogieWrapper, bytecode_translator::BoogieTranslator,
};
use bytecode::{
    function_target_pipeline::{FunctionTargetPipeline, FunctionTargetsHolder},
    pipeline_factory,
    read_write_set_analysis::{self, ReadWriteSetProcessor},
};
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
use docgen::Docgen;
use errmapgen::ErrmapGen;
#[allow(unused_imports)]
use log::{debug, info, warn};
use move_model::{
    code_writer::CodeWriter, model::GlobalEnv, parse_addresses_from_options,
    run_model_builder_with_options,
};
use std::{
    fs,
    path::{Path, PathBuf},
    time::Instant,
};

pub mod cli;

// =================================================================================================
// Prover API

pub fn run_move_prover_errors_to_stderr(options: Options) -> anyhow::Result<()> {
    let mut error_writer = StandardStream::stderr(ColorChoice::Auto);
    run_move_prover(&mut error_writer, options)
}

pub fn run_move_prover<W: WriteColor>(
    error_writer: &mut W,
    options: Options,
) -> anyhow::Result<()> {
    let now = Instant::now();
    // Run the model builder.
    let env = run_model_builder_with_options(
        &options.move_sources,
        &options.move_deps,
        options.model_builder.clone(),
        parse_addresses_from_options(options.move_named_address_values.clone())?,
    )?;
    run_move_prover_with_model(&env, error_writer, options, Some(now))
}

pub fn run_move_prover_with_model<W: WriteColor>(
    env: &GlobalEnv,
    error_writer: &mut W,
    options: Options,
    timer: Option<Instant>,
) -> anyhow::Result<()> {
    let now = timer.unwrap_or_else(Instant::now);

    let build_duration = now.elapsed();
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with model building errors",
    )?;
    env.report_diag(error_writer, options.prover.report_severity);

    // Add the prover options as an extension to the environment, so they can be accessed
    // from there.
    env.set_extension(options.prover.clone());

    // Until this point, prover and docgen have same code. Here we part ways.
    if options.run_docgen {
        return run_docgen(env, &options, error_writer, now);
    }
    // Same for ABI generator.
    if options.run_abigen {
        return run_abigen(env, &options, now);
    }
    // Same for the error map generator
    if options.run_errmapgen {
        return {
            run_errmapgen(env, &options, now);
            Ok(())
        };
    }
    // Same for read/write set analysis
    if options.run_read_write_set {
        return {
            run_read_write_set(env, &options, now);
            Ok(())
        };
    }

    // Check correct backend versions.
    options.backend.check_tool_versions()?;

    // Create and process bytecode
    let now = Instant::now();
    let targets = create_and_process_bytecode(&options, env);
    let trafo_duration = now.elapsed();
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with bytecode transformation errors",
    )?;

    // Generate boogie code
    let now = Instant::now();
    let code_writer = generate_boogie(env, &options, &targets)?;
    let gen_duration = now.elapsed();
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with boogie generation errors",
    )?;

    // Verify boogie code.
    let now = Instant::now();
    verify_boogie(env, &options, &targets, code_writer)?;
    let verify_duration = now.elapsed();

    // Report durations.
    info!(
        "{:.3}s build, {:.3}s trafo, {:.3}s gen, {:.3}s verify",
        build_duration.as_secs_f64(),
        trafo_duration.as_secs_f64(),
        gen_duration.as_secs_f64(),
        verify_duration.as_secs_f64()
    );
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with boogie verification errors",
    )
}

pub fn check_errors<W: WriteColor>(
    env: &GlobalEnv,
    options: &Options,
    error_writer: &mut W,
    msg: &'static str,
) -> anyhow::Result<()> {
    env.report_diag(error_writer, options.prover.report_severity);
    if env.has_errors() {
        Err(anyhow!(msg))
    } else {
        Ok(())
    }
}

pub fn generate_boogie(
    env: &GlobalEnv,
    options: &Options,
    targets: &FunctionTargetsHolder,
) -> anyhow::Result<CodeWriter> {
    let writer = CodeWriter::new(env.internal_loc());
    add_prelude(env, &options.backend, &writer)?;
    let mut translator = BoogieTranslator::new(env, &options.backend, targets, &writer);
    translator.translate();
    Ok(writer)
}

pub fn verify_boogie(
    env: &GlobalEnv,
    options: &Options,
    targets: &FunctionTargetsHolder,
    writer: CodeWriter,
) -> anyhow::Result<()> {
    let output_existed = std::path::Path::new(&options.output_path).exists();
    debug!("writing boogie to `{}`", &options.output_path);
    writer.process_result(|result| fs::write(&options.output_path, result))?;
    let boogie = BoogieWrapper {
        env,
        targets,
        writer: &writer,
        options: &options.backend,
    };
    boogie.call_boogie_and_verify_output(&options.output_path)?;
    if !output_existed && !options.backend.keep_artifacts {
        std::fs::remove_file(&options.output_path).unwrap_or_default();
    }
    Ok(())
}

/// Create bytecode and process it.
pub fn create_and_process_bytecode(options: &Options, env: &GlobalEnv) -> FunctionTargetsHolder {
    let mut targets = FunctionTargetsHolder::default();
    let output_dir = Path::new(&options.output_path)
        .parent()
        .expect("expect the parent directory of the output path to exist");
    let output_prefix = options.move_sources.get(0).map_or("bytecode", |s| {
        Path::new(s).file_name().unwrap().to_str().unwrap()
    });

    // Add function targets for all functions in the environment.
    for module_env in env.get_modules() {
        if options.prover.dump_bytecode {
            let dump_file = output_dir.join(format!("{}.mv.disas", output_prefix));
            fs::write(&dump_file, &module_env.disassemble()).expect("dumping disassembled module");
        }
        for func_env in module_env.get_functions() {
            targets.add_target(&func_env)
        }
    }

    // Create processing pipeline and run it.
    let pipeline = if options.experimental_pipeline {
        pipeline_factory::experimental_pipeline()
    } else {
        pipeline_factory::default_pipeline_with_options(&options.prover)
    };

    if options.prover.dump_bytecode {
        let dump_file_base = output_dir
            .join(output_prefix)
            .into_os_string()
            .into_string()
            .unwrap();
        pipeline.run_with_dump(env, &mut targets, &dump_file_base, options.prover.dump_cfg)
    } else {
        pipeline.run(env, &mut targets);
    }

    targets
}

// Tools using the Move prover top-level driver
// ============================================

// TODO: make those tools independent. Need to first address the todo to
// move the model builder into the move-model crate.

fn run_docgen<W: WriteColor>(
    env: &GlobalEnv,
    options: &Options,
    error_writer: &mut W,
    now: Instant,
) -> anyhow::Result<()> {
    let generator = Docgen::new(env, &options.docgen);
    let checking_elapsed = now.elapsed();
    info!("generating documentation");
    for (file, content) in generator.gen() {
        let path = PathBuf::from(&file);
        fs::create_dir_all(path.parent().unwrap())?;
        fs::write(path.as_path(), content)?;
    }
    let generating_elapsed = now.elapsed();
    info!(
        "{:.3}s checking, {:.3}s generating",
        checking_elapsed.as_secs_f64(),
        (generating_elapsed - checking_elapsed).as_secs_f64()
    );
    if env.has_errors() {
        env.report_diag(error_writer, options.prover.report_severity);
        Err(anyhow!("exiting with documentation generation errors"))
    } else {
        Ok(())
    }
}

fn run_abigen(env: &GlobalEnv, options: &Options, now: Instant) -> anyhow::Result<()> {
    let mut generator = Abigen::new(env, &options.abigen);
    let checking_elapsed = now.elapsed();
    info!("generating ABI files");
    generator.gen();
    for (file, content) in generator.into_result() {
        let path = PathBuf::from(&file);
        fs::create_dir_all(path.parent().unwrap())?;
        fs::write(path.as_path(), content)?;
    }
    let generating_elapsed = now.elapsed();
    info!(
        "{:.3}s checking, {:.3}s generating",
        checking_elapsed.as_secs_f64(),
        (generating_elapsed - checking_elapsed).as_secs_f64()
    );
    Ok(())
}

fn run_errmapgen(env: &GlobalEnv, options: &Options, now: Instant) {
    let mut generator = ErrmapGen::new(env, &options.errmapgen);
    let checking_elapsed = now.elapsed();
    info!("generating error map");
    generator.gen();
    generator.save_result();
    let generating_elapsed = now.elapsed();
    info!(
        "{:.3}s checking, {:.3}s generating",
        checking_elapsed.as_secs_f64(),
        (generating_elapsed - checking_elapsed).as_secs_f64()
    );
}

fn run_read_write_set(env: &GlobalEnv, options: &Options, now: Instant) {
    let mut targets = FunctionTargetsHolder::default();

    for module_env in env.get_modules() {
        for func_env in module_env.get_functions() {
            targets.add_target(&func_env)
        }
    }
    let mut pipeline = FunctionTargetPipeline::default();
    pipeline.add_processor(ReadWriteSetProcessor::new());

    let start = now.elapsed();
    info!("generating read/write set");
    pipeline.run(env, &mut targets);
    read_write_set_analysis::get_read_write_set(env, &targets);
    println!("generated for {:?}", options.move_sources);

    let end = now.elapsed();
    info!("{:.3}s analyzing", (end - start).as_secs_f64());
}
<commit_msg>Fixed a problem where move prover ignores the --generate-only flag<commit_after>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::cli::Options;
use abigen::Abigen;
use anyhow::anyhow;
use boogie_backend::{
    add_prelude, boogie_wrapper::BoogieWrapper, bytecode_translator::BoogieTranslator,
};
use bytecode::{
    function_target_pipeline::{FunctionTargetPipeline, FunctionTargetsHolder},
    pipeline_factory,
    read_write_set_analysis::{self, ReadWriteSetProcessor},
};
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
use docgen::Docgen;
use errmapgen::ErrmapGen;
#[allow(unused_imports)]
use log::{debug, info, warn};
use move_model::{
    code_writer::CodeWriter, model::GlobalEnv, parse_addresses_from_options,
    run_model_builder_with_options,
};
use std::{
    fs,
    path::{Path, PathBuf},
    time::Instant,
};

pub mod cli;

// =================================================================================================
// Prover API

pub fn run_move_prover_errors_to_stderr(options: Options) -> anyhow::Result<()> {
    let mut error_writer = StandardStream::stderr(ColorChoice::Auto);
    run_move_prover(&mut error_writer, options)
}

pub fn run_move_prover<W: WriteColor>(
    error_writer: &mut W,
    options: Options,
) -> anyhow::Result<()> {
    let now = Instant::now();
    // Run the model builder.
    let env = run_model_builder_with_options(
        &options.move_sources,
        &options.move_deps,
        options.model_builder.clone(),
        parse_addresses_from_options(options.move_named_address_values.clone())?,
    )?;
    run_move_prover_with_model(&env, error_writer, options, Some(now))
}

pub fn run_move_prover_with_model<W: WriteColor>(
    env: &GlobalEnv,
    error_writer: &mut W,
    options: Options,
    timer: Option<Instant>,
) -> anyhow::Result<()> {
    let now = timer.unwrap_or_else(Instant::now);

    let build_duration = now.elapsed();
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with model building errors",
    )?;
    env.report_diag(error_writer, options.prover.report_severity);

    // Add the prover options as an extension to the environment, so they can be accessed
    // from there.
    env.set_extension(options.prover.clone());

    // Until this point, prover and docgen have same code. Here we part ways.
    if options.run_docgen {
        return run_docgen(env, &options, error_writer, now);
    }
    // Same for ABI generator.
    if options.run_abigen {
        return run_abigen(env, &options, now);
    }
    // Same for the error map generator
    if options.run_errmapgen {
        return {
            run_errmapgen(env, &options, now);
            Ok(())
        };
    }
    // Same for read/write set analysis
    if options.run_read_write_set {
        return {
            run_read_write_set(env, &options, now);
            Ok(())
        };
    }

    // Check correct backend versions.
    options.backend.check_tool_versions()?;

    // Create and process bytecode
    let now = Instant::now();
    let targets = create_and_process_bytecode(&options, env);
    let trafo_duration = now.elapsed();
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with bytecode transformation errors",
    )?;

    // Generate boogie code
    let now = Instant::now();
    let code_writer = generate_boogie(env, &options, &targets)?;
    let gen_duration = now.elapsed();
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with boogie generation errors",
    )?;

    // Verify boogie code.
    let now = Instant::now();
    verify_boogie(env, &options, &targets, code_writer)?;
    let verify_duration = now.elapsed();

    // Report durations.
    info!(
        "{:.3}s build, {:.3}s trafo, {:.3}s gen, {:.3}s verify",
        build_duration.as_secs_f64(),
        trafo_duration.as_secs_f64(),
        gen_duration.as_secs_f64(),
        verify_duration.as_secs_f64()
    );
    check_errors(
        env,
        &options,
        error_writer,
        "exiting with boogie verification errors",
    )
}

pub fn check_errors<W: WriteColor>(
    env: &GlobalEnv,
    options: &Options,
    error_writer: &mut W,
    msg: &'static str,
) -> anyhow::Result<()> {
    env.report_diag(error_writer, options.prover.report_severity);
    if env.has_errors() {
        Err(anyhow!(msg))
    } else {
        Ok(())
    }
}

pub fn generate_boogie(
    env: &GlobalEnv,
    options: &Options,
    targets: &FunctionTargetsHolder,
) -> anyhow::Result<CodeWriter> {
    let writer = CodeWriter::new(env.internal_loc());
    add_prelude(env, &options.backend, &writer)?;
    let mut translator = BoogieTranslator::new(env, &options.backend, targets, &writer);
    translator.translate();
    Ok(writer)
}

pub fn verify_boogie(
    env: &GlobalEnv,
    options: &Options,
    targets: &FunctionTargetsHolder,
    writer: CodeWriter,
) -> anyhow::Result<()> {
    let output_existed = std::path::Path::new(&options.output_path).exists();
    debug!("writing boogie to `{}`", &options.output_path);
    writer.process_result(|result| fs::write(&options.output_path, result))?;
    if !options.prover.generate_only {
        let boogie = BoogieWrapper {
            env,
            targets,
            writer: &writer,
            options: &options.backend,
        };
        boogie.call_boogie_and_verify_output(&options.output_path)?;
        if !output_existed && !options.backend.keep_artifacts {
            std::fs::remove_file(&options.output_path).unwrap_or_default();
        }
    }
    Ok(())
}

/// Create bytecode and process it.
pub fn create_and_process_bytecode(options: &Options, env: &GlobalEnv) -> FunctionTargetsHolder {
    let mut targets = FunctionTargetsHolder::default();
    let output_dir = Path::new(&options.output_path)
        .parent()
        .expect("expect the parent directory of the output path to exist");
    let output_prefix = options.move_sources.get(0).map_or("bytecode", |s| {
        Path::new(s).file_name().unwrap().to_str().unwrap()
    });

    // Add function targets for all functions in the environment.
    for module_env in env.get_modules() {
        if options.prover.dump_bytecode {
            let dump_file = output_dir.join(format!("{}.mv.disas", output_prefix));
            fs::write(&dump_file, &module_env.disassemble()).expect("dumping disassembled module");
        }
        for func_env in module_env.get_functions() {
            targets.add_target(&func_env)
        }
    }

    // Create processing pipeline and run it.
    let pipeline = if options.experimental_pipeline {
        pipeline_factory::experimental_pipeline()
    } else {
        pipeline_factory::default_pipeline_with_options(&options.prover)
    };

    if options.prover.dump_bytecode {
        let dump_file_base = output_dir
            .join(output_prefix)
            .into_os_string()
            .into_string()
            .unwrap();
        pipeline.run_with_dump(env, &mut targets, &dump_file_base, options.prover.dump_cfg)
    } else {
        pipeline.run(env, &mut targets);
    }

    targets
}

// Tools using the Move prover top-level driver
// ============================================

// TODO: make those tools independent. Need to first address the todo to
// move the model builder into the move-model crate.

fn run_docgen<W: WriteColor>(
    env: &GlobalEnv,
    options: &Options,
    error_writer: &mut W,
    now: Instant,
) -> anyhow::Result<()> {
    let generator = Docgen::new(env, &options.docgen);
    let checking_elapsed = now.elapsed();
    info!("generating documentation");
    for (file, content) in generator.gen() {
        let path = PathBuf::from(&file);
        fs::create_dir_all(path.parent().unwrap())?;
        fs::write(path.as_path(), content)?;
    }
    let generating_elapsed = now.elapsed();
    info!(
        "{:.3}s checking, {:.3}s generating",
        checking_elapsed.as_secs_f64(),
        (generating_elapsed - checking_elapsed).as_secs_f64()
    );
    if env.has_errors() {
        env.report_diag(error_writer, options.prover.report_severity);
        Err(anyhow!("exiting with documentation generation errors"))
    } else {
        Ok(())
    }
}

fn run_abigen(env: &GlobalEnv, options: &Options, now: Instant) -> anyhow::Result<()> {
    let mut generator = Abigen::new(env, &options.abigen);
    let checking_elapsed = now.elapsed();
    info!("generating ABI files");
    generator.gen();
    for (file, content) in generator.into_result() {
        let path = PathBuf::from(&file);
        fs::create_dir_all(path.parent().unwrap())?;
        fs::write(path.as_path(), content)?;
    }
    let generating_elapsed = now.elapsed();
    info!(
        "{:.3}s checking, {:.3}s generating",
        checking_elapsed.as_secs_f64(),
        (generating_elapsed - checking_elapsed).as_secs_f64()
    );
    Ok(())
}

fn run_errmapgen(env: &GlobalEnv, options: &Options, now: Instant) {
    let mut generator = ErrmapGen::new(env, &options.errmapgen);
    let checking_elapsed = now.elapsed();
    info!("generating error map");
    generator.gen();
    generator.save_result();
    let generating_elapsed = now.elapsed();
    info!(
        "{:.3}s checking, {:.3}s generating",
        checking_elapsed.as_secs_f64(),
        (generating_elapsed - checking_elapsed).as_secs_f64()
    );
}

fn run_read_write_set(env: &GlobalEnv, options: &Options, now: Instant) {
    let mut targets = FunctionTargetsHolder::default();

    for module_env in env.get_modules() {
        for func_env in module_env.get_functions() {
            targets.add_target(&func_env)
        }
    }
    let mut pipeline = FunctionTargetPipeline::default();
    pipeline.add_processor(ReadWriteSetProcessor::new());

    let start = now.elapsed();
    info!("generating read/write set");
    pipeline.run(env, &mut targets);
    read_write_set_analysis::get_read_write_set(env, &targets);
    println!("generated for {:?}", options.move_sources);

    let end = now.elapsed();
    info!("{:.3}s analyzing", (end - start).as_secs_f64());
}
<|endoftext|>