#![doc = include_str!("../README.md")]
mod block;
pub use block::Block;
mod concat;
pub use concat::Concat;
mod list;
pub use list::{List, Trailing};
#[derive(Debug, PartialEq)]
pub enum Code {
Line(String),
Block(Box<Block>),
Concat(Concat),
List(List),
}
impl From<String> for Code {
fn from(x: String) -> Self {
Code::Line(x)
}
}
impl From<&str> for Code {
fn from(x: &str) -> Self {
Code::Line(x.to_owned())
}
}
#[derive(derivative::Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct Format {
#[derivative(Default(value = "4"))]
pub indent: i32,
}
impl Format {
pub fn indent(indent: i32) -> Self {
Self::default().set_indent(indent)
}
#[inline]
pub fn set_indent(mut self, indent: i32) -> Self {
self.indent = indent;
self
}
pub fn indent_tab() -> Self {
Self::indent(-1)
}
#[inline]
pub fn set_indent_tab(self) -> Self {
self.set_indent(-1)
}
}
pub trait FormatCode {
fn format(&self) -> String {
self.format_with(&Format::default())
}
fn format_with(&self, format: &Format) -> String {
self.format_vec_with(format).join("\n")
}
fn format_vec_with(&self, format: &Format) -> Vec<String> {
let size_hint = self.size_hint();
let mut out = match size_hint {
0 => Vec::new(),
n => Vec::with_capacity(n),
};
self.format_into_vec_with(format, &mut out, false, "");
#[cfg(test)]
if size_hint > 0 {
assert_eq!(out.capacity(), size_hint);
}
out
}
fn format_into_vec_with(
&self,
format: &Format,
out: &mut Vec<String>,
connect: bool,
indent: &str,
);
fn size_hint(&self) -> usize;
}
impl std::fmt::Display for Code {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.format())
}
}
impl FormatCode for Code {
fn format_into_vec_with(
&self,
format: &Format,
out: &mut Vec<String>,
connect: bool,
indent: &str,
) {
match self {
Code::Line(line) => append_line(out, line, connect, indent),
Code::Block(body) => body.format_into_vec_with(format, out, connect, indent),
Code::Concat(body) => body.format_into_vec_with(format, out, connect, indent),
Code::List(body) => body.format_into_vec_with(format, out, connect, indent),
}
}
fn size_hint(&self) -> usize {
match self {
Code::Line(_) => 1,
Code::Block(body) => body.size_hint(),
Code::Concat(body) => body.size_hint(),
Code::List(body) => body.size_hint(),
}
}
}
pub(crate) fn append_line(out: &mut Vec<String>, line: &str, connect: bool, indent: &str) {
if connect {
if let Some(last) = out.last_mut() {
if !last.is_empty() {
last.push(' ');
}
last.push_str(line.as_ref());
return;
}
}
if indent.is_empty() {
out.push(line.to_owned());
} else {
out.push(format!("{indent}{line}"));
}
}
impl Code {
pub fn should_inline(&self) -> bool {
match self {
Code::Block(block) => block.should_inline(),
Code::List(list) => list.should_inline(),
_ => false,
}
}
pub fn is_empty(&self) -> bool {
match self {
Code::Concat(concat) => concat.is_empty(),
Code::List(list) => list.is_empty(),
_ => false,
}
}
}
#[cfg(test)]
mod test {
use indoc::indoc;
use super::*;
fn test_case_1() -> Code {
cblock!("{", [], "}").into()
}
fn test_case_2() -> Code {
cblock!("trait A {", ["fn a();"], "}").into()
}
fn test_case_3() -> Code {
cblock!(
"fn main() {",
[
cblock!("if (foo) {", ["println!(\"Hello, world!\");"], "}"),
cblock!("else {", [format!("bar({});", "giz")], "}").connected(),
],
"}"
)
.into()
}
fn test_case_4(f1: fn(&Block) -> bool, f2: fn(&List) -> bool) -> Code {
let body = vec![
Code::from("let x = 1;"),
cblock!(
"let b = {",
[clist!("," => ["1", "2", "3"]).inline_when(f2)],
"};"
)
.inline_when(f1)
.into(),
cblock!(
"let b = {",
[clist!("," => ["1", "2", "3", "4"]).inline_when(f2)],
"};"
)
.inline_when(f1)
.into(),
];
cblock!("while true {", body, "}").into()
}
#[test]
fn test1() {
let code = test_case_1();
assert_eq!("{\n}", code.to_string());
}
#[test]
fn test2() {
let code = test_case_2();
let expected = indoc! {"
trait A {
fn a();
}"};
assert_eq!(expected, code.format_with(&Format::indent(3)));
let expected = indoc! {"
trait A {
\tfn a();
}"};
assert_eq!(expected, code.format_with(&Format::indent_tab()));
}
#[test]
fn test3() {
let code: Code = test_case_3();
let expected = indoc! {"
fn main() {
if (foo) {
println!(\"Hello, world!\");
} else {
bar(giz);
}
}"};
assert_eq!(expected, code.to_string());
}
#[test]
fn test4() {
fn should_inline_list(list: &List) -> bool {
list.body().len() == 3
}
let code = test_case_4(Block::should_inline_intrinsic, should_inline_list);
let expected = indoc! {"
while true {
let x = 1;
let b = { 1, 2, 3 };
let b = {
1,
2,
3,
4,
};
}"};
assert_eq!(expected, code.to_string());
let code = test_case_4(Block::should_inline_intrinsic, |_| true);
let expected = indoc! {"
while true {
let x = 1;
let b = { 1, 2, 3 };
let b = { 1, 2, 3, 4 };
}"};
assert_eq!(expected, code.to_string());
let code = test_case_4(|_| false, |_| true);
let expected = indoc! {"
while true {
let x = 1;
let b = {
1, 2, 3
};
let b = {
1, 2, 3, 4
};
}"};
assert_eq!(expected, code.to_string());
}
}