hcl_edit/structure/
block.rs

1use crate::structure::{Attribute, Body, Structure};
2use crate::{Decor, Decorate, Decorated, Ident};
3use std::ops::{self, Range};
4
5/// Represents an HCL block which consists of a block identifier, zero or more block labels and a
6/// block body.
7///
8/// In HCL syntax this is represented as:
9///
10/// ```hcl
11/// block_identifier "block_label1" "block_label2" {
12///   body
13/// }
14/// ```
15#[derive(Debug, Clone, Eq)]
16pub struct Block {
17    /// The block identifier.
18    pub ident: Decorated<Ident>,
19    /// Zero or more block labels.
20    pub labels: Vec<BlockLabel>,
21    /// Represents the `Block`'s body.
22    pub body: Body,
23
24    decor: Decor,
25    span: Option<Range<usize>>,
26}
27
28impl Block {
29    /// Creates a new `Block` from an identifier.
30    pub fn new(ident: impl Into<Decorated<Ident>>) -> Block {
31        Block {
32            ident: ident.into(),
33            labels: Vec::new(),
34            body: Body::new(),
35            decor: Decor::default(),
36            span: None,
37        }
38    }
39
40    /// Creates a new [`BlockBuilder`] to start building a new `Block` with the provided
41    /// identifier.
42    #[inline]
43    pub fn builder(ident: impl Into<Decorated<Ident>>) -> BlockBuilder {
44        BlockBuilder::new(ident.into())
45    }
46
47    /// Returns `true` if the block has labels.
48    ///
49    /// # Example
50    ///
51    /// ```
52    /// use hcl_edit::{structure::Block, Ident};
53    ///
54    /// let block = Block::new(Ident::new("foo"));
55    /// assert!(!block.is_labeled());
56    ///
57    /// let labeled_block = Block::builder(Ident::new("foo"))
58    ///     .label("bar")
59    ///     .build();
60    /// assert!(labeled_block.is_labeled());
61    /// ```
62    #[inline]
63    pub fn is_labeled(&self) -> bool {
64        !self.labels.is_empty()
65    }
66
67    /// Returns `true` if the block has the given identifier.
68    ///
69    /// # Example
70    ///
71    /// ```
72    /// use hcl_edit::{structure::Block, Ident};
73    ///
74    /// let block = Block::new(Ident::new("foo"));
75    /// assert!(block.has_ident("foo"));
76    /// assert!(!block.has_ident("bar"));
77    /// ```
78    #[inline]
79    pub fn has_ident(&self, ident: &str) -> bool {
80        self.ident.as_str() == ident
81    }
82
83    /// Returns `true` if the `Block`'s labels and the provided ones share a common prefix.
84    ///
85    /// For example, `&["foo"]` will match blocks that fulfil either of these criteria:
86    ///
87    /// - Single `"foo"` label.
88    /// - Multiple labels, with `"foo"` being in first position.
89    ///
90    /// For an alternative which matches labels exactly see [`Block::has_exact_labels`].
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use hcl_edit::{structure::Block, Ident};
96    ///
97    /// let block = Block::builder(Ident::new("resource"))
98    ///     .labels(["aws_s3_bucket", "mybucket"])
99    ///     .build();
100    ///
101    /// assert!(block.has_labels(&["aws_s3_bucket"]));
102    /// assert!(block.has_labels(&["aws_s3_bucket", "mybucket"]));
103    /// assert!(!block.has_labels(&["mybucket"]));
104    /// ```
105    ///
106    /// One use case for this method is to find blocks in a [`Body`] that have a common label
107    /// prefix:
108    ///
109    /// ```
110    /// use hcl_edit::structure::{Attribute, Block, Body};
111    /// use hcl_edit::Ident;
112    ///
113    /// let body = Body::builder()
114    ///     .attribute(Attribute::new(Ident::new("foo"), "bar"))
115    ///     .block(
116    ///         Block::builder(Ident::new("resource"))
117    ///             .labels(["aws_s3_bucket", "bucket1"])
118    ///     )
119    ///     .block(
120    ///         Block::builder(Ident::new("resource"))
121    ///             .labels(["aws_db_instance", "db_instance"])
122    ///     )
123    ///     .block(
124    ///         Block::builder(Ident::new("resource"))
125    ///             .labels(["aws_s3_bucket", "bucket2"])
126    ///     )
127    ///     .build();
128    ///
129    /// let buckets: Vec<&Block> = body.get_blocks("resource")
130    ///     .filter(|block| block.has_labels(&["aws_s3_bucket"]))
131    ///     .collect();
132    ///
133    /// assert_eq!(
134    ///     buckets,
135    ///     [
136    ///         &Block::builder(Ident::new("resource"))
137    ///             .labels(["aws_s3_bucket", "bucket1"])
138    ///             .build(),
139    ///         &Block::builder(Ident::new("resource"))
140    ///             .labels(["aws_s3_bucket", "bucket2"])
141    ///             .build()
142    ///     ]
143    /// );
144    /// ```
145    pub fn has_labels<T>(&self, labels: &[T]) -> bool
146    where
147        T: AsRef<str>,
148    {
149        if self.labels.len() < labels.len() {
150            false
151        } else {
152            self.labels
153                .iter()
154                .zip(labels.iter())
155                .all(|(a, b)| a.as_str() == b.as_ref())
156        }
157    }
158
159    /// Returns `true` if the `Block`'s labels match the provided ones exactly.
160    ///
161    /// For an alternative which matches a common label prefix see [`Block::has_labels`].
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use hcl_edit::{structure::Block, Ident};
167    ///
168    /// let block = Block::builder(Ident::new("resource"))
169    ///     .labels(["aws_s3_bucket", "mybucket"])
170    ///     .build();
171    ///
172    /// assert!(!block.has_exact_labels(&["aws_s3_bucket"]));
173    /// assert!(block.has_exact_labels(&["aws_s3_bucket", "mybucket"]));
174    /// ```
175    ///
176    /// One use case for this method is to find blocks in a [`Body`] that have an exact set of
177    /// labels:
178    ///
179    /// ```
180    /// use hcl_edit::structure::{Attribute, Block, Body};
181    /// use hcl_edit::Ident;
182    ///
183    /// let body = Body::builder()
184    ///     .block(
185    ///         Block::builder(Ident::new("resource"))
186    ///             .labels(["aws_s3_bucket", "bucket1"])
187    ///     )
188    ///     .block(
189    ///         Block::builder(Ident::new("resource"))
190    ///             .labels(["aws_s3_bucket", "bucket2"])
191    ///     )
192    ///     .build();
193    ///
194    /// let buckets: Vec<&Block> = body.get_blocks("resource")
195    ///     .filter(|block| block.has_exact_labels(&["aws_s3_bucket", "bucket1"]))
196    ///     .collect();
197    ///
198    /// assert_eq!(
199    ///     buckets,
200    ///     [
201    ///         &Block::builder(Ident::new("resource"))
202    ///             .labels(["aws_s3_bucket", "bucket1"])
203    ///             .build(),
204    ///     ]
205    /// );
206    /// ```
207    pub fn has_exact_labels<T>(&self, labels: &[T]) -> bool
208    where
209        T: AsRef<str>,
210    {
211        self.labels.len() == labels.len() && self.has_labels(labels)
212    }
213
214    pub(crate) fn despan(&mut self, input: &str) {
215        self.decor.despan(input);
216        self.ident.decor_mut().despan(input);
217        for label in &mut self.labels {
218            label.despan(input);
219        }
220        self.body.despan(input);
221    }
222}
223
224impl PartialEq for Block {
225    fn eq(&self, other: &Self) -> bool {
226        self.ident == other.ident && self.labels == other.labels && self.body == other.body
227    }
228}
229
230/// Represents an HCL block label.
231///
232/// In HCL syntax this can be represented either as a quoted string literal...
233///
234/// ```hcl
235/// block_identifier "block_label1" {
236///   body
237/// }
238/// ```
239///
240/// ...or as a bare identifier:
241///
242/// ```hcl
243/// block_identifier block_label1 {
244///   body
245/// }
246/// ```
247#[derive(Debug, Clone, PartialEq, Eq)]
248pub enum BlockLabel {
249    /// A bare HCL block label.
250    Ident(Decorated<Ident>),
251    /// A quoted string literal.
252    String(Decorated<String>),
253}
254
255impl BlockLabel {
256    /// Returns `true` if the block label is an identifier.
257    pub fn is_ident(&self) -> bool {
258        matches!(self, BlockLabel::Ident(_))
259    }
260
261    /// Returns `true` if the block label is a string.
262    pub fn is_string(&self) -> bool {
263        matches!(self, BlockLabel::String(_))
264    }
265
266    /// Returns a reference to the underlying string.
267    pub fn as_str(&self) -> &str {
268        match self {
269            BlockLabel::Ident(ident) => ident.as_str(),
270            BlockLabel::String(string) => string.as_str(),
271        }
272    }
273
274    pub(crate) fn despan(&mut self, input: &str) {
275        match self {
276            BlockLabel::Ident(ident) => ident.decor_mut().despan(input),
277            BlockLabel::String(string) => string.decor_mut().despan(input),
278        }
279    }
280}
281
282impl From<Ident> for BlockLabel {
283    fn from(value: Ident) -> Self {
284        BlockLabel::from(Decorated::new(value))
285    }
286}
287
288impl From<Decorated<Ident>> for BlockLabel {
289    fn from(value: Decorated<Ident>) -> Self {
290        BlockLabel::Ident(value)
291    }
292}
293
294impl From<&str> for BlockLabel {
295    fn from(value: &str) -> Self {
296        BlockLabel::from(value.to_string())
297    }
298}
299
300impl From<String> for BlockLabel {
301    fn from(value: String) -> Self {
302        BlockLabel::from(Decorated::new(value))
303    }
304}
305
306impl From<Decorated<String>> for BlockLabel {
307    fn from(value: Decorated<String>) -> Self {
308        BlockLabel::String(value)
309    }
310}
311
312impl AsRef<str> for BlockLabel {
313    fn as_ref(&self) -> &str {
314        self.as_str()
315    }
316}
317
318impl ops::Deref for BlockLabel {
319    type Target = str;
320
321    fn deref(&self) -> &Self::Target {
322        self.as_str()
323    }
324}
325
326decorate_impl!(Block);
327span_impl!(Block);
328forward_decorate_impl!(BlockLabel => { Ident, String });
329forward_span_impl!(BlockLabel => { Ident, String });
330
331/// `BlockBuilder` builds an HCL [`Block`].
332///
333/// The builder allows to build the `Block` by adding labels, attributes and other nested blocks
334/// via chained method calls. A call to [`.build()`](BlockBuilder::build) produces the final
335/// `Block`.
336///
337/// ## Example
338///
339/// ```
340/// use hcl_edit::structure::{Attribute, Block, Body};
341/// use hcl_edit::Ident;
342///
343/// let block = Block::builder(Ident::new("resource"))
344///     .labels(["aws_s3_bucket", "mybucket"])
345///     .attribute(Attribute::new(Ident::new("name"), "mybucket"))
346///     .block(
347///         Block::builder(Ident::new("logging"))
348///             .attribute(Attribute::new(Ident::new("target_bucket"), "mylogsbucket"))
349///     )
350///     .build();
351/// ```
352#[derive(Debug)]
353pub struct BlockBuilder {
354    ident: Decorated<Ident>,
355    labels: Vec<BlockLabel>,
356    body: Body,
357}
358
359impl BlockBuilder {
360    fn new(ident: Decorated<Ident>) -> BlockBuilder {
361        BlockBuilder {
362            ident,
363            labels: Vec::new(),
364            body: Body::new(),
365        }
366    }
367
368    /// Adds a `BlockLabel`.
369    ///
370    /// Consumes `self` and returns a new `BlockBuilder`.
371    #[inline]
372    pub fn label(mut self, label: impl Into<BlockLabel>) -> Self {
373        self.labels.push(label.into());
374        self
375    }
376
377    /// Adds `BlockLabel`s from an iterator.
378    ///
379    /// Consumes `self` and returns a new `BlockBuilder`.
380    #[inline]
381    pub fn labels<I>(mut self, iter: I) -> BlockBuilder
382    where
383        I: IntoIterator,
384        I::Item: Into<BlockLabel>,
385    {
386        self.labels.extend(iter.into_iter().map(Into::into));
387        self
388    }
389
390    /// Adds an `Attribute` to the block body.
391    ///
392    /// Consumes `self` and returns a new `BlockBuilder`.
393    #[inline]
394    pub fn attribute(self, attr: impl Into<Attribute>) -> BlockBuilder {
395        self.structure(attr.into())
396    }
397
398    /// Adds `Attribute`s to the block body from an iterator.
399    ///
400    /// Consumes `self` and returns a new `BlockBuilder`.
401    #[inline]
402    pub fn attributes<I>(self, iter: I) -> BlockBuilder
403    where
404        I: IntoIterator,
405        I::Item: Into<Attribute>,
406    {
407        self.structures(iter.into_iter().map(Into::into))
408    }
409
410    /// Adds another `Block` to the block body.
411    ///
412    /// Consumes `self` and returns a new `BlockBuilder`.
413    #[inline]
414    pub fn block(self, block: impl Into<Block>) -> BlockBuilder {
415        self.structure(block.into())
416    }
417
418    /// Adds `Block`s to the block body from an iterator.
419    ///
420    /// Consumes `self` and returns a new `BlockBuilder`.
421    #[inline]
422    pub fn blocks<I>(self, iter: I) -> BlockBuilder
423    where
424        I: IntoIterator,
425        I::Item: Into<Block>,
426    {
427        self.structures(iter.into_iter().map(Into::into))
428    }
429
430    /// Adds a `Structure` to the block body.
431    ///
432    /// Consumes `self` and returns a new `BlockBuilder`.
433    #[inline]
434    pub fn structure(mut self, structures: impl Into<Structure>) -> BlockBuilder {
435        self.body.push(structures.into());
436        self
437    }
438
439    /// Adds `Structure`s to the block body from an iterator.
440    ///
441    /// Consumes `self` and returns a new `BlockBuilder`.
442    #[inline]
443    pub fn structures<I>(mut self, iter: I) -> BlockBuilder
444    where
445        I: IntoIterator,
446        I::Item: Into<Structure>,
447    {
448        self.body.extend(iter);
449        self
450    }
451
452    /// Consumes `self` and builds the [`Block`] from the items added via the builder methods.
453    #[inline]
454    pub fn build(self) -> Block {
455        Block {
456            ident: self.ident,
457            labels: self.labels,
458            body: self.body,
459            decor: Decor::default(),
460            span: None,
461        }
462    }
463}
464
465impl From<BlockBuilder> for Block {
466    #[inline]
467    fn from(builder: BlockBuilder) -> Self {
468        builder.build()
469    }
470}