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}