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}