From 33462d09a7c632a281a3d0988fa68f246f012f94 Mon Sep 17 00:00:00 2001 From: martinohmann Date: Sun, 7 May 2023 00:04:09 +0200 Subject: [PATCH] feat(structure): add `BlockBuilder` and `BodyBuilder` (#227) --- crates/hcl-edit/src/structure/block.rs | 150 ++++++++++++++++++++++++- crates/hcl-edit/src/structure/body.rs | 107 ++++++++++++++++++ crates/hcl-edit/src/structure/mod.rs | 6 +- 3 files changed, 259 insertions(+), 4 deletions(-) diff --git a/crates/hcl-edit/src/structure/block.rs b/crates/hcl-edit/src/structure/block.rs index 7e4ba250..7f4762a2 100644 --- a/crates/hcl-edit/src/structure/block.rs +++ b/crates/hcl-edit/src/structure/block.rs @@ -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}; @@ -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>) -> BlockBuilder { + BlockBuilder::new(ident.into()) + } + pub(crate) fn despan(&mut self, input: &str) { self.decor.despan(input); self.ident.decor_mut().despan(input); @@ -154,3 +161,144 @@ 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")) +/// .labels(["aws_s3_bucket", "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, + labels: Vec, + body: Body, +} + +impl BlockBuilder { + fn new(ident: Decorated) -> 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) -> Self { + self.labels.push(label.into()); + self + } + + /// Adds `BlockLabel`s from an iterator. + /// + /// Consumes `self` and returns a new `BlockBuilder`. + #[inline] + pub fn labels(mut self, iter: I) -> BlockBuilder + where + I: IntoIterator, + I::Item: Into, + { + 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) -> 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(self, iter: I) -> BlockBuilder + where + I: IntoIterator, + I::Item: Into, + { + 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) -> 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(self, iter: I) -> BlockBuilder + where + I: IntoIterator, + I::Item: Into, + { + 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) -> 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(mut self, iter: I) -> BlockBuilder + where + I: IntoIterator, + I::Item: Into, + { + 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 for Block { + #[inline] + fn from(builder: BlockBuilder) -> Self { + builder.build() + } +} diff --git a/crates/hcl-edit/src/structure/body.rs b/crates/hcl-edit/src/structure/body.rs index c50c5f1a..7e873976 100644 --- a/crates/hcl-edit/src/structure/body.rs +++ b/crates/hcl-edit/src/structure/body.rs @@ -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 { @@ -528,3 +534,104 @@ 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")) +/// .labels(["aws_s3_bucket", "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) -> 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(self, iter: I) -> BodyBuilder + where + I: IntoIterator, + I::Item: Into, + { + 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) -> 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(self, iter: I) -> BodyBuilder + where + I: IntoIterator, + I::Item: Into, + { + 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) -> 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(mut self, iter: I) -> BodyBuilder + where + I: IntoIterator, + I::Item: Into, + { + 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 for Body { + #[inline] + fn from(builder: BodyBuilder) -> Self { + builder.build() + } +} diff --git a/crates/hcl-edit/src/structure/mod.rs b/crates/hcl-edit/src/structure/mod.rs index c12f2474..15647720 100644 --- a/crates/hcl-edit/src/structure/mod.rs +++ b/crates/hcl-edit/src/structure/mod.rs @@ -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;