Skip to main content

typed_ident/alloc/
fragment.rs

1// =============================================================================
2// USES
3// =============================================================================
4
5// -----------------------------------------------------------------------------
6use crate::alloc::IntoIntermediate;
7use crate::core::error::{Error, ErrorKind};
8use crate::syntax::{Boundary, CasedProfile, Delimiter};
9use crate::{Fragment, FragmentBuf};
10use std_alloc::boxed::Box;
11use std_alloc::format;
12use std_alloc::string::String;
13
14// =============================================================================
15// IMPLS
16// =============================================================================
17
18// -----------------------------------------------------------------------------
19impl<B: Boundary, D: Delimiter, P: CasedProfile> Fragment<B, D, P> {
20    /// Returns a heap-allocated fragment, joined with the original fragment in
21    /// a way that preserves chunk boundaries.
22    ///
23    /// This is a convenience function for cases when the delimiter value can be
24    /// deduced by the `Default` trait. For more information on this operation,
25    /// see the [`join_with`] method.
26    ///
27    /// [`join_with`]: Self::join_with
28    ///
29    /// # Examples
30    ///
31    /// Basic Usage:
32    ///
33    /// ```
34    /// # use typed_ident::presets::unicode::lower_snake::*;
35    /// let fragment = LowerSnakeFragment::new("snake")?;
36    /// let fragment = fragment.join("fragment")?;
37    /// assert_eq!(fragment, "snake_fragment");
38    /// # Ok::<(), typed_ident::Error>(())
39    /// ```
40    #[must_use = "this function returns an allocated fragment, it does not mutate the original"]
41    #[inline]
42    pub fn join<F>(&self, fragment: F) -> Result<FragmentBuf<B, D, P>, Error>
43    where
44        D: Default,
45        F: IntoIntermediate<B, D, P>,
46    {
47        self.join_with(fragment, D::default())
48    }
49
50    /// Returns a heap-allocated fragment, joined with the original fragment in
51    /// a way that preserves chunk boundaries.
52    ///
53    /// This function takes anything that can be represented as an intermediate
54    /// fragment. That means it can take a `&str`, `char`, `Fragment`, `Chunk`,
55    /// `Identifier`, or `Segment`.
56    ///
57    /// If you're working with an fragment format which has only a single valid
58    /// delimiter value, you should instead be able to use the [`join`] method,
59    /// and should prefer that.
60    ///
61    /// [`join`]: Self::join
62    ///
63    /// # Preserving Chunk Boundaries
64    ///
65    /// At the end of the operation, the total number of chunked segments
66    /// present in the fragment will be equal to the sum of each fragment,
67    /// potentially plus one additional fragment in the case where we needed to
68    /// join using a delimiter to preserve chunk boundaries.
69    ///
70    /// Whether or not a delimiter is needed is found using the [`Boundary`]
71    /// trait. The general strategy for joining looks like this:
72    ///
73    /// 1. The string is joined on the end of the current fragment.
74    /// 2. [`Boundary::has_boundary_at`] is called with the old fragment length
75    ///    to ensure there's still a boundary between the end of the original
76    ///    fragment and the joined fragment.
77    /// 3. If there's no boundary, the `delim` character is inserted at that
78    ///    location to force a boundary.
79    ///
80    /// The goal of any joining operation is *not to merge chunks*.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`Error`] if the intermediate fragment provided is invalid, or
85    /// if the combination of `self` and `fragment` cannot produce a valid
86    /// result.
87    ///
88    /// If the intermediate fragment is invalid, then the `InvalidFormat` error
89    /// kind will be returned, with the [`byte_offset`] set to the first invalid
90    /// character of the intermediate fragment.
91    ///
92    /// If the join operation itself failed, then the `FailedJoin` error kind
93    /// is returned, without setting the `byte_offset`.
94    ///
95    /// [`Error`]: crate::Error
96    /// [`byte_offset`]: crate::Error::byte_offset
97    ///
98    /// # Examples
99    ///
100    /// Basic Usage:
101    ///
102    /// ```
103    /// # use typed_ident::syntax::delimiter::LowLine;
104    /// # use typed_ident::presets::unicode::lower_snake::*;
105    /// let fragment = LowerSnakeFragment::new("snake")?;
106    /// let fragment = fragment.join_with("fragment", LowLine)?;
107    /// assert_eq!(fragment, "snake_fragment");
108    /// # Ok::<(), typed_ident::Error>(())
109    /// ```
110    #[must_use = "this function returns an allocated fragment, it does not mutate the original"]
111    #[inline]
112    pub fn join_with<F>(&self, fragment: F, delim: D) -> Result<FragmentBuf<B, D, P>, Error>
113    where
114        F: IntoIntermediate<B, D, P>,
115    {
116        let fragment = fragment.into_intermediate()?;
117        let fragment: &Fragment<B, D, P> = fragment.as_ref();
118        let mut buffer = FragmentBuf::with_overhead(self, fragment.len() + 1);
119        buffer
120            .push_bounded_with(fragment, delim)
121            .map_err(|_| Error::new(ErrorKind::FailedJoin))?;
122        Ok(buffer)
123    }
124
125    /// Converts a string into a boxed fragment if its valid.
126    ///
127    /// # Errors
128    ///
129    /// Returns [`Error`] if the fragment does not satisfy the character
130    /// requirements. If an invalid character is found then a `InvalidFormat`
131    /// error kind is returned, with [`byte_offset`] set to the byte index for
132    /// the first invalid character.
133    ///
134    /// [`Error`]: crate::Error
135    /// [`byte_offset`]: crate::Error::byte_offset
136    ///
137    /// # Examples
138    ///
139    /// Basic Usage:
140    ///
141    /// ```
142    /// # use typed_ident::*;
143    /// # use typed_ident::presets::unicode::lower_snake::*;
144    /// let fragment: Box<LowerSnakeFragment> =
145    ///     Fragment::new_boxed(String::from("snake_fragment"))?;
146    /// # Ok::<(), typed_ident::Error>(())
147    /// ```
148    #[inline]
149    pub fn new_boxed(string: String) -> Result<Box<Fragment<B, D, P>>, Error> {
150        P::is_fragment::<D>(&string)?;
151        Ok(Self::new_boxed_unchecked(string))
152    }
153
154    /// Returns a heap-allocated fragment, replacing the provided pattern with
155    /// a fragment of the user's choice.
156    ///
157    /// For `from`, the pattern can be a `&str`, [`char`], a slice of [`char`]s,
158    /// or a function or closure that determines if a character matches.
159    ///
160    /// For `to`, this function takes anything that can be represented as an
161    /// intermediate fragment. That means it can take a `&str`, `char`,
162    /// `Fragment`, `Chunk`, `Identifier`, or `Segment`.
163    ///
164    /// # Errors
165    ///
166    /// Returns [`Error`] if the intermediate fragment provided is invalid, or
167    /// if the replacement of `from` to `to` does not produce a valid fragment.
168    ///
169    /// If the intermediate fragment is invalid, then the `InvalidFormat` error
170    /// kind will be returned, with the [`byte_offset`] set to the first invalid
171    /// character of the intermediate fragment.
172    ///
173    /// If the replace operation itself failed, then the `FailedReplace` error
174    /// kind is returned, without setting the `byte_offset`.
175    ///
176    /// [`Error`]: crate::Error
177    /// [`error_kind`]: crate::Error::error_kind
178    /// [`byte_offset`]: crate::Error::byte_offset
179    ///
180    /// # Examples
181    ///
182    /// Basic Usage:
183    ///
184    /// ```
185    /// # use typed_ident::syntax::delimiter::LowLine;
186    /// # use typed_ident::presets::unicode::lower_snake::*;
187    /// let fragment = LowerSnakeFragment::new("example_snake_identifier")?;
188    /// let fragment = fragment.replace("snake", "serpent")?;
189    /// assert_eq!(fragment, "example_serpent_identifier");
190    /// # Ok::<(), typed_ident::Error>(())
191    /// ```
192    #[must_use = "this function returns an allocated fragment, it does not mutate the original"]
193    #[inline]
194    pub fn replace<M, F>(&self, from: M, to: F) -> Result<FragmentBuf<B, D, P>, Error>
195    where
196        M: crate::core::pattern::Pattern,
197        F: IntoIntermediate<B, D, P>,
198    {
199        let to = to.into_intermediate()?;
200        FragmentBuf::from_string(from.replace(self.as_str(), to.as_ref()))
201            .map_err(|_| Error::new(ErrorKind::FailedReplace))
202    }
203
204    /// Returns a heap-allocated fragment with the provided prefix and suffix
205    /// attached to the original fragment.
206    ///
207    /// The affixes provided to this function takes anything that can be
208    /// represented as an intermediate fragment. That means it can take a
209    /// `&str`, `char`, `Fragment`, `Chunk`, `Identifier`, or `Segment`.
210    ///
211    /// # Errors
212    ///
213    /// Returns [`Error`] if the intermediate fragments provided are invalid, or
214    /// if the combination of `prefix`, `self`, and `fragment` cannot produce a
215    /// valid result.
216    ///
217    /// If an intermediate fragment is invalid, then either `InvalidPrefix` or
218    /// `InvalidSuffix` error kind will be returned (depending on which had the
219    /// format error), with the [`byte_offset`] set to the first invalid
220    /// character of the intermediate fragment.
221    ///
222    /// If the affixing operation itself failed, then the `FailedCircumfixing`
223    /// error kind is returned, without setting the `byte_offset`.
224    ///
225    /// [`Error`]: crate::Error
226    /// [`error_kind`]: crate::Error::error_kind
227    /// [`byte_offset`]: crate::Error::byte_offset
228    ///
229    /// # Examples
230    ///
231    /// Basic Usage:
232    ///
233    /// ```
234    /// # use typed_ident::presets::unicode::lower_snake::*;
235    /// let fragment = LowerSnakeFragment::new("snake")?;
236    /// let fragment = fragment.with_circumfix("lower_", "_fragment")?;
237    /// assert_eq!(fragment, "lower_snake_fragment");
238    /// # Ok::<(), typed_ident::Error>(())
239    /// ```
240    #[must_use = "this function returns an allocated fragment, it does not mutate the original"]
241    #[inline]
242    pub fn with_circumfix<F1, F2>(
243        &self,
244        prefix: F1,
245        suffix: F2,
246    ) -> Result<FragmentBuf<B, D, P>, Error>
247    where
248        F1: IntoIntermediate<B, D, P>,
249        F2: IntoIntermediate<B, D, P>,
250    {
251        let prefix = prefix
252            .into_intermediate()
253            .map_err(|e| e.with_error_kind(ErrorKind::InvalidPrefix))?;
254        let suffix = suffix
255            .into_intermediate()
256            .map_err(|e| e.with_error_kind(ErrorKind::InvalidSuffix))?;
257        FragmentBuf::from_string(format!("{prefix}{self}{suffix}"))
258            .map_err(|_| Error::new(ErrorKind::FailedCircumfixing))
259    }
260
261    /// Returns a heap-allocated fragment with the provided prefix attached to
262    /// the original fragment.
263    ///
264    /// The prefix provided to this function takes anything that can be
265    /// represented as an intermediate fragment. That means it can take a
266    /// `&str`, `char`, `Fragment`, `Chunk`, `Identifier`, or `Segment`.
267    ///
268    /// # Errors
269    ///
270    /// Returns [`Error`] if the intermediate fragment provided is invalid, or
271    /// if the combination of `prefix` and `self` cannot produce a valid result.
272    ///
273    /// If the intermediate fragment is invalid, then the error kind will be
274    /// `InvalidPrefix`, with the [`byte_offset`] set to the first invalid
275    /// character of the intermediate fragment.
276    ///
277    /// If the affixing operation itself failed, then the `FailedPrefixing`
278    /// error kind is returned, without setting the `byte_offset`.
279    ///
280    /// [`Error`]: crate::Error
281    /// [`error_kind`]: crate::Error::error_kind
282    /// [`byte_offset`]: crate::Error::byte_offset
283    ///
284    /// # Examples
285    ///
286    /// Basic Usage:
287    ///
288    /// ```
289    /// # use typed_ident::presets::unicode::lower_snake::*;
290    /// let fragment = LowerSnakeFragment::new("snake")?;
291    /// let fragment = fragment.with_prefix("lower_")?;
292    /// assert_eq!(fragment, "lower_snake");
293    /// # Ok::<(), typed_ident::Error>(())
294    /// ```
295    #[must_use = "this function returns an allocated fragment, it does not mutate the original"]
296    #[inline]
297    pub fn with_prefix<F>(&self, prefix: F) -> Result<FragmentBuf<B, D, P>, Error>
298    where
299        F: IntoIntermediate<B, D, P>,
300    {
301        let prefix = prefix
302            .into_intermediate()
303            .map_err(|e| e.with_error_kind(ErrorKind::InvalidPrefix))?;
304        FragmentBuf::from_string(format!("{prefix}{self}"))
305            .map_err(|_| Error::new(ErrorKind::FailedPrefixing))
306    }
307
308    /// Returns a heap-allocated fragment with the provided suffix attached to
309    /// the original fragment.
310    ///
311    /// The suffix provided to this function takes anything that can be
312    /// represented as an intermediate fragment. That means it can take a
313    /// `&str`, `char`, `Fragment`, `Chunk`, `Identifier`, or `Segment`.
314    ///
315    /// # Errors
316    ///
317    /// Returns [`Error`] if the intermediate fragment provided is invalid, or
318    /// if the combination of `self` and `suffix` cannot produce a valid result.
319    ///
320    /// If the intermediate fragment is invalid, then the error kind will be
321    /// `InvalidSuffix`, with the [`byte_offset`] set to the first invalid
322    /// character of the intermediate fragment.
323    ///
324    /// If the affixing operation itself failed, then the `FailedSuffixing`
325    /// error kind is returned, without setting the `byte_offset`.
326    ///
327    /// [`Error`]: crate::Error
328    /// [`error_kind`]: crate::Error::error_kind
329    /// [`byte_offset`]: crate::Error::byte_offset
330    ///
331    /// # Examples
332    ///
333    /// Basic Usage:
334    ///
335    /// ```
336    /// # use typed_ident::presets::unicode::lower_snake::*;
337    /// let fragment = LowerSnakeFragment::new("snake")?;
338    /// let fragment = fragment.with_suffix("_fragment")?;
339    /// assert_eq!(fragment, "snake_fragment");
340    /// # Ok::<(), typed_ident::Error>(())
341    /// ```
342    #[must_use = "this function returns an allocated fragment, it does not mutate the original"]
343    #[inline]
344    pub fn with_suffix<F>(&self, suffix: F) -> Result<FragmentBuf<B, D, P>, Error>
345    where
346        F: IntoIntermediate<B, D, P>,
347    {
348        let suffix = suffix
349            .into_intermediate()
350            .map_err(|e| e.with_error_kind(ErrorKind::InvalidSuffix))?;
351        FragmentBuf::from_string(format!("{self}{suffix}"))
352            .map_err(|_| Error::new(ErrorKind::FailedSuffixing))
353    }
354}
355
356// -----------------------------------------------------------------------------
357impl<B, D, P> Fragment<B, D, P> {
358    /// Converts a boxed fragment into a boxed string slice.
359    ///
360    /// # Examples
361    ///
362    /// Basic Usage:
363    ///
364    /// ```
365    /// # use typed_ident::*;
366    /// # use typed_ident::presets::unicode::lower_snake::*;
367    /// let fragment: Box<LowerSnakeFragment> =
368    ///     Fragment::new_boxed(String::from("snake_fragment"))?;
369    /// let fragment: Box<str> = fragment.into_boxed_str();
370    /// # Ok::<(), typed_ident::Error>(())
371    /// ```
372    #[must_use]
373    #[inline]
374    pub fn into_boxed_str(self: Box<Fragment<B, D, P>>) -> Box<str> {
375        // SAFETY: Fragment is transparent over str, so Box<Fragment> has the
376        // same allocation layout, pointer metadata, alignment, and ownership
377        // behavior as Box<str>.
378        unsafe { Box::from_raw(Box::into_raw(self) as *mut str) }
379    }
380
381    /// Converts a boxed identifier into a fragment buffer.
382    ///
383    /// # Examples
384    ///
385    /// Basic Usage:
386    ///
387    /// ```
388    /// # use typed_ident::*;
389    /// # use typed_ident::presets::unicode::lower_snake::*;
390    /// let fragment: Box<LowerSnakeFragment> =
391    ///     Fragment::new_boxed(String::from("snake_fragment"))?;
392    /// let buffer: LowerSnakeFragmentBuf = fragment.into_fragment_buf();
393    /// # Ok::<(), typed_ident::Error>(())
394    /// ```
395    #[must_use]
396    #[inline]
397    pub fn into_fragment_buf(self: Box<Fragment<B, D, P>>) -> FragmentBuf<B, D, P> {
398        FragmentBuf::from_string_unchecked(self.into_string())
399    }
400
401    /// Converts a boxed identifier into a string.
402    ///
403    /// # Examples
404    ///
405    /// Basic Usage:
406    ///
407    /// ```
408    /// # use typed_ident::*;
409    /// # use typed_ident::presets::unicode::lower_snake::*;
410    /// let fragment: Box<LowerSnakeFragment> =
411    ///     Fragment::new_boxed(String::from("snake_fragment"))?;
412    /// let buffer: String = fragment.into_string();
413    /// # Ok::<(), typed_ident::Error>(())
414    /// ```
415    #[must_use]
416    #[inline]
417    pub fn into_string(self: Box<Fragment<B, D, P>>) -> String {
418        self.into_boxed_str().into()
419    }
420
421    /// Converts a string into a boxed identifier, bypassing checks.
422    #[must_use]
423    #[inline]
424    pub(crate) fn new_boxed_unchecked(string: String) -> Box<Fragment<B, D, P>> {
425        let boxed_str = string.into_boxed_str();
426        // SAFETY: Fragment is transparent over str, so Box<Fragment> has the
427        // same allocation layout, pointer metadata, alignment, and ownership
428        // behavior as Box<str>.
429        unsafe { Box::from_raw(Box::into_raw(boxed_str) as *mut Fragment<B, D, P>) }
430    }
431
432    /// Converts a fragment into an owned boxed fragment.
433    ///
434    /// # Examples
435    ///
436    /// Basic Usage:
437    ///
438    /// ```
439    /// # use typed_ident::*;
440    /// # use typed_ident::presets::unicode::lower_snake::*;
441    /// let ident: &LowerSnakeFragment = Fragment::new("snake_ident")?;
442    /// let ident: Box<LowerSnakeFragment> = ident.to_boxed_fragment();
443    /// # Ok::<(), typed_ident::Error>(())
444    /// ```
445    #[must_use]
446    #[inline]
447    pub fn to_boxed_fragment(&self) -> Box<Fragment<B, D, P>> {
448        Fragment::new_boxed_unchecked(String::from(self.as_str()))
449    }
450
451    /// Converts a fragment into a fragment buffer.
452    ///
453    /// # Examples
454    ///
455    /// Basic Usage:
456    ///
457    /// ```
458    /// # use typed_ident::*;
459    /// # use typed_ident::presets::unicode::lower_snake::*;
460    /// let fragment: &LowerSnakeFragment = Fragment::new("snake_fragment")?;
461    /// let buffer: LowerSnakeFragmentBuf = fragment.to_fragment_buf();
462    /// # Ok::<(), typed_ident::Error>(())
463    /// ```
464    #[must_use]
465    #[inline]
466    pub fn to_fragment_buf(&self) -> FragmentBuf<B, D, P> {
467        FragmentBuf::from_fragment(self)
468    }
469}