use super::{AtomicFragment, CodeFragment, NestedFragment};
use crate::codegen::docstring::into_docstring;
use itertools::Itertools;
use std::cell::RefCell;
use std::rc::Rc;
pub trait ItemDeclarationAPI {
fn mark_as_public(&mut self);
fn is_public(&self) -> bool;
fn document(&mut self, documentation: String);
fn add_attribute(&mut self, attribute: String);
fn set_body(&mut self, body: Rc<RefCell<dyn CodeFragment>>);
fn mark_as_declare_only(&mut self);
fn mark_for_full_implementation(&mut self);
}
#[derive(Clone)]
pub struct ItemDeclaration {
pub doc: Option<String>,
pub public: bool,
pub attributes: Vec<String>,
pub definition: Rc<RefCell<dyn CodeFragment>>,
pub body: Option<Rc<RefCell<dyn CodeFragment>>>,
}
impl ItemDeclaration {
pub fn new(definition: Rc<RefCell<dyn CodeFragment>>) -> Self {
Self {
definition,
..Self::default()
}
}
pub fn set_definition(&mut self, definition: Rc<RefCell<dyn CodeFragment>>) {
self.definition = definition;
}
}
impl Default for ItemDeclaration {
fn default() -> Self {
Self {
doc: None,
public: false,
attributes: vec![],
definition: Rc::new(RefCell::new(AtomicFragment::default())),
body: None,
}
}
}
impl ItemDeclarationAPI for ItemDeclaration {
fn mark_as_public(&mut self) {
self.public = true;
}
fn is_public(&self) -> bool {
self.public
}
fn add_attribute(&mut self, attribute: String) {
self.attributes.push(attribute);
}
fn document(&mut self, documentation: String) {
self.doc = Some(documentation);
}
fn set_body(&mut self, body: Rc<RefCell<dyn CodeFragment>>) {
self.body = Some(body);
}
fn mark_as_declare_only(&mut self) {
self.body = None;
}
fn mark_for_full_implementation(&mut self) {
if self.body.is_none() {
self.body = Some(Rc::new(RefCell::new(AtomicFragment::default())));
}
}
}
impl CodeFragment for ItemDeclaration {
fn body(&self, line_width: usize) -> String {
let doc = match &self.doc {
Some(d) => into_docstring(&d, line_width) + "\n",
None => String::new(),
};
let public = if self.public { "pub " } else { "" };
let mut attrs = self
.attributes
.iter()
.map(|a| format!("#[{}]", a))
.format("\n")
.to_string();
if !attrs.is_empty() {
attrs.push('\n');
}
let preamble = format!(
"{doc}{attrs}{public}{definition}",
doc = doc,
attrs = attrs,
public = public,
definition = self.definition.borrow().body(line_width),
)
.trim()
.to_owned();
match &self.body {
Some(actual_implementation) => {
let mut nested =
NestedFragment::new(AtomicFragment::new(format!("{} {{", preamble)), "}");
nested.append(actual_implementation.clone());
nested.body(line_width) }
None => format!("{};", preamble),
}
}
fn imports(&self) -> Vec<String> {
self.definition.borrow().imports()
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
fn simple_declaration() -> ItemDeclaration {
ItemDeclaration::new(Rc::new(RefCell::new(AtomicFragment::new(
"fn foo() -> bool".to_owned(),
))))
}
#[test]
fn test_simple_declaration() {
let i = simple_declaration();
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(i.body(80), "fn foo() -> bool;");
}
#[test]
fn test_public_declaration() {
let mut i = simple_declaration();
i.mark_as_public();
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(i.body(80), "pub fn foo() -> bool;");
}
#[test]
fn test_documented_declaration() {
let mut i = simple_declaration();
i.document("Some bloody documentation for ya.".to_owned());
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(
i.body(80),
indoc! {"
/// Some bloody documentation for ya.
fn foo() -> bool;"}
);
}
#[test]
fn test_documentation_line_width() {
let mut i = simple_declaration();
i.document("Some bloody documentation for ya.".to_owned());
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(
i.body(30),
indoc! {"
/// Some bloody documentation
/// for ya.
fn foo() -> bool;"}
);
}
#[test]
fn test_attributed_declaration() {
let mut i = simple_declaration();
i.add_attribute("allow(deprecated)".to_owned());
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(
i.body(80),
indoc! {"
#[allow(deprecated)]
fn foo() -> bool;"}
);
}
#[test]
fn test_full_but_empty_declaration() {
let mut i = simple_declaration();
i.mark_for_full_implementation();
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(i.body(80), "fn foo() -> bool {}");
}
#[test]
fn test_nonempty_declaration() {
let mut i = simple_declaration();
i.set_body(Rc::new(RefCell::new(AtomicFragment::new(
"!bar()".to_owned(),
))));
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(
i.body(80),
indoc! {"
fn foo() -> bool {
!bar()
}"}
);
}
#[test]
fn test_combined_declaration() {
let mut i = simple_declaration();
i.mark_as_public();
i.document("Some bloody documentation for ya.".to_owned());
i.add_attribute("allow(deprecated)".to_owned());
assert_eq!(i.imports(), Vec::<String>::new());
assert_eq!(
i.body(80),
indoc! {"
/// Some bloody documentation for ya.
#[allow(deprecated)]
pub fn foo() -> bool;"}
);
}
}