Skip to content

Commit

Permalink
feat(structure): add BlockBuilder and BodyBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
martinohmann committed May 6, 2023
1 parent 2d08db1 commit 2855c5e
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 4 deletions.
151 changes: 150 additions & 1 deletion crates/hcl-edit/src/structure/block.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::repr::{Decor, Decorate, Decorated, SetSpan, Span};
use crate::structure::Body;
use crate::structure::{Attribute, Body, Structure};
use crate::Ident;
use std::ops::{self, Range};

Expand Down Expand Up @@ -38,6 +38,13 @@ impl Block {
}
}

/// Creates a new [`BlockBuilder`] to start building a new `Block` with the provided
/// identifier.
#[inline]
pub fn builder(ident: impl Into<Decorated<Ident>>) -> BlockBuilder {
BlockBuilder::new(ident.into())
}

pub(crate) fn despan(&mut self, input: &str) {
self.decor.despan(input);
self.ident.decor_mut().despan(input);
Expand Down Expand Up @@ -154,3 +161,145 @@ decorate_impl!(Block);
span_impl!(Block);
forward_decorate_impl!(BlockLabel => { Ident, String });
forward_span_impl!(BlockLabel => { Ident, String });

/// `BlockBuilder` builds an HCL [`Block`].
///
/// The builder allows to build the `Block` by adding labels, attributes and other nested blocks
/// via chained method calls. A call to [`.build()`](BlockBuilder::build) produces the final
/// `Block`.
///
/// ## Example
///
/// ```
/// use hcl_edit::structure::{Attribute, Block, Body};
/// use hcl_edit::Ident;
///
/// let block = Block::builder(Ident::new("resource"))
/// .label("aws_s3_bucket")
/// .label("mybucket")
/// .attribute(Attribute::new(Ident::new("name"), "mybucket"))
/// .block(
/// Block::builder(Ident::new("logging"))
/// .attribute(Attribute::new(Ident::new("target_bucket"), "mylogsbucket"))
/// )
/// .build();
/// ```
#[derive(Debug)]
pub struct BlockBuilder {
ident: Decorated<Ident>,
labels: Vec<BlockLabel>,
body: Body,
}

impl BlockBuilder {
fn new(ident: Decorated<Ident>) -> BlockBuilder {
BlockBuilder {
ident,
labels: Vec::new(),
body: Body::new(),
}
}

/// Adds a `BlockLabel`.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn label(mut self, label: impl Into<BlockLabel>) -> Self {
self.labels.push(label.into());
self
}

/// Adds `BlockLabel`s from an iterator.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn labels<I>(mut self, iter: I) -> BlockBuilder
where
I: IntoIterator,
I::Item: Into<BlockLabel>,
{
self.labels.extend(iter.into_iter().map(Into::into));
self
}

/// Adds an `Attribute` to the block body.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn attribute(self, attr: impl Into<Attribute>) -> BlockBuilder {
self.structure(attr.into())
}

/// Adds `Attribute`s to the block body from an iterator.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn attributes<I>(self, iter: I) -> BlockBuilder
where
I: IntoIterator,
I::Item: Into<Attribute>,
{
self.structures(iter.into_iter().map(Into::into))
}

/// Adds another `Block` to the block body.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn block(self, block: impl Into<Block>) -> BlockBuilder {
self.structure(block.into())
}

/// Adds `Block`s to the block body from an iterator.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn blocks<I>(self, iter: I) -> BlockBuilder
where
I: IntoIterator,
I::Item: Into<Block>,
{
self.structures(iter.into_iter().map(Into::into))
}

/// Adds a `Structure` to the block body.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn structure(mut self, structures: impl Into<Structure>) -> BlockBuilder {
self.body.push(structures.into());
self
}

/// Adds `Structure`s to the block body from an iterator.
///
/// Consumes `self` and returns a new `BlockBuilder`.
#[inline]
pub fn structures<I>(mut self, iter: I) -> BlockBuilder
where
I: IntoIterator,
I::Item: Into<Structure>,
{
self.body.extend(iter);
self
}

/// Consumes `self` and builds the [`Block`] from the items added via the builder methods.
#[inline]
pub fn build(self) -> Block {
Block {
ident: self.ident,
labels: self.labels,
body: self.body,
decor: Decor::default(),
span: None,
}
}
}

impl From<BlockBuilder> for Block {
#[inline]
fn from(builder: BlockBuilder) -> Self {
builder.build()
}
}
108 changes: 108 additions & 0 deletions crates/hcl-edit/src/structure/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ impl Body {
}
}

/// Creates a new [`BodyBuilder`] to start building a new `Body`.
#[inline]
pub fn builder() -> BodyBuilder {
BodyBuilder::default()
}

/// Returns `true` if the body contains no structures.
#[inline]
pub fn is_empty(&self) -> bool {
Expand Down Expand Up @@ -528,3 +534,105 @@ impl<'a> IntoIterator for &'a mut Body {

decorate_impl!(Body);
span_impl!(Body);

/// `BodyBuilder` builds a HCL [`Body`].
///
/// The builder allows to build the `Body` by adding attributes and other nested blocks via chained
/// method calls. A call to [`.build()`](BodyBuilder::build) produces the final `Body`.
///
/// ## Example
///
/// ```
/// use hcl_edit::structure::{Attribute, Block, Body};
/// use hcl_edit::Ident;
///
/// let body = Body::builder()
/// .block(
/// Block::builder(Ident::new("resource"))
/// .label("aws_s3_bucket")
/// .label("mybucket")
/// .attribute(Attribute::new(Ident::new("name"), "mybucket"))
/// )
/// .build();
/// ```
#[derive(Debug, Default)]
pub struct BodyBuilder {
body: Body,
}

impl BodyBuilder {
/// Adds an `Attribute` to the body.
///
/// Consumes `self` and returns a new `BodyBuilder`.
#[inline]
pub fn attribute(self, attr: impl Into<Attribute>) -> BodyBuilder {
self.structure(attr.into())
}

/// Adds `Attribute`s to the body from an iterator.
///
/// Consumes `self` and returns a new `BodyBuilder`.
#[inline]
pub fn attributes<I>(self, iter: I) -> BodyBuilder
where
I: IntoIterator,
I::Item: Into<Attribute>,
{
self.structures(iter.into_iter().map(Into::into))
}

/// Adds a `Block` to the body.
///
/// Consumes `self` and returns a new `BodyBuilder`.
#[inline]
pub fn block(self, block: impl Into<Block>) -> BodyBuilder {
self.structure(block.into())
}

/// Adds `Block`s to the body from an iterator.
///
/// Consumes `self` and returns a new `BodyBuilder`.
#[inline]
pub fn blocks<I>(self, iter: I) -> BodyBuilder
where
I: IntoIterator,
I::Item: Into<Block>,
{
self.structures(iter.into_iter().map(Into::into))
}

/// Adds a `Structure` to the body.
///
/// Consumes `self` and returns a new `BodyBuilder`.
#[inline]
pub fn structure(mut self, structures: impl Into<Structure>) -> BodyBuilder {
self.body.push(structures.into());
self
}

/// Adds `Structure`s to the body from an iterator.
///
/// Consumes `self` and returns a new `BodyBuilder`.
#[inline]
pub fn structures<I>(mut self, iter: I) -> BodyBuilder
where
I: IntoIterator,
I::Item: Into<Structure>,
{
self.body.extend(iter);
self
}

/// Consumes `self` and builds the [`Body`] from the structures added via the builder methods.
#[inline]
pub fn build(self) -> Body {
self.body
}
}

impl From<BodyBuilder> for Body {
#[inline]
fn from(builder: BodyBuilder) -> Self {
builder.build()
}
}
6 changes: 3 additions & 3 deletions crates/hcl-edit/src/structure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ mod block;
mod body;

pub use self::attribute::Attribute;
pub use self::block::{Block, BlockLabel};
pub use self::block::{Block, BlockBuilder, BlockLabel};
pub use self::body::{
Attributes, AttributesMut, Blocks, BlocksMut, Body, IntoAttributes, IntoBlocks, IntoIter, Iter,
IterMut,
Attributes, AttributesMut, Blocks, BlocksMut, Body, BodyBuilder, IntoAttributes, IntoBlocks,
IntoIter, Iter, IterMut,
};
use crate::repr::{Decor, Decorate, SetSpan, Span};
use std::ops::Range;
Expand Down

0 comments on commit 2855c5e

Please sign in to comment.