magnus/encoding.rs
1//! Types and functions for working with encodings.
2//!
3//! This module defines 3 types for working with encodings, these types can
4//! be converted back and forth with [`From`]/[`Into`] like so:
5//! ``` text
6//! Encoding <-> RbEncoding <-> Index
7//! |______________________^
8//! ```
9//! Many functions that require an encoding take their arguments as
10//! `Into<RbEncoding>` or `Into<Index>` to ease working with the different
11//! types. The type specified for the `Into` conversion hints at the type the
12//! function natively works with, and thus will avoid any conversion cost.
13//!
14//! [`Encoding`] and [`RbEncoding`] both implement [`TryConvert`] and
15//! [`IntoValue`] so can be used as parameters and return values in functions
16//! bound to Ruby. Both convert from either an instance of `Encoding` or a
17//! string of an encoding name, and convert to an instance of `Encoding`.
18
19use std::{
20 ffi::{CStr, CString},
21 fmt,
22 ops::Range,
23 os::raw::{c_char, c_int},
24 ptr::{self, NonNull},
25};
26
27use rb_sys::{
28 rb_ascii8bit_encindex, rb_ascii8bit_encoding, rb_default_external_encoding,
29 rb_default_internal_encoding, rb_enc_ascget, rb_enc_associate_index, rb_enc_check,
30 rb_enc_codelen, rb_enc_codepoint_len, rb_enc_compatible, rb_enc_copy, rb_enc_default_external,
31 rb_enc_default_internal, rb_enc_fast_mbclen, rb_enc_find, rb_enc_find_index,
32 rb_enc_from_encoding, rb_enc_from_index, rb_enc_get_index, rb_enc_mbclen,
33 rb_enc_precise_mbclen, rb_enc_set_index, rb_enc_to_index, rb_enc_uint_chr, rb_encoding,
34 rb_filesystem_encindex, rb_filesystem_encoding, rb_find_encoding, rb_locale_encindex,
35 rb_locale_encoding, rb_to_encoding, rb_to_encoding_index, rb_usascii_encindex,
36 rb_usascii_encoding, rb_utf8_encindex, rb_utf8_encoding,
37};
38
39use crate::{
40 error::{protect, Error},
41 into_value::IntoValue,
42 object::Object,
43 r_string::RString,
44 try_convert::TryConvert,
45 value::{
46 private::{self, ReprValue as _},
47 NonZeroValue, ReprValue, Value,
48 },
49 Ruby,
50};
51
52/// # `Encoding`
53///
54/// Functions to access pre-defined Encodings.
55///
56/// See also the [`Encoding`] type.
57impl Ruby {
58 /// Returns the default internal encoding as a Ruby object.
59 ///
60 /// This is the encoding used for anything out-of-process, such as reading
61 /// from files or sockets.
62 pub fn enc_default_external(&self) -> Encoding {
63 Encoding::from_value(Value::new(unsafe { rb_enc_default_external() })).unwrap()
64 }
65
66 /// Returns the default external encoding as a Ruby object.
67 ///
68 /// If set, any out-of-process data is transcoded from the default external
69 /// encoding to the default internal encoding.
70 pub fn enc_default_internal(&self) -> Option<Encoding> {
71 Encoding::from_value(Value::new(unsafe { rb_enc_default_internal() }))
72 }
73}
74
75/// Wrapper type for a Value known to be an instance of Ruby's Encoding class.
76///
77/// This is the representation of an encoding exposed to Ruby code.
78///
79/// See the [`ReprValue`] and [`Object`] traits for additional methods
80/// available on this type. See [`Ruby`](Ruby#encoding) for methods to get an
81/// `Encoding`.
82#[derive(Clone, Copy)]
83#[repr(transparent)]
84pub struct Encoding(NonZeroValue);
85
86impl Encoding {
87 /// Return `Some(Encoding)` if `val` is an `Encoding`, `None` otherwise.
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// use magnus::{encoding::Encoding, eval};
93 /// # let _cleanup = unsafe { magnus::embed::init() };
94 ///
95 /// assert!(Encoding::from_value(eval("Encoding::US_ASCII").unwrap()).is_some());
96 /// assert!(Encoding::from_value(eval("nil").unwrap()).is_none());
97 /// ```
98 #[inline]
99 pub fn from_value(val: Value) -> Option<Self> {
100 unsafe {
101 val.is_kind_of(Ruby::get_with(val).class_encoding())
102 .then(|| Self(NonZeroValue::new_unchecked(val)))
103 }
104 }
105
106 /// Returns the default internal encoding as a Ruby object.
107 ///
108 /// This is the encoding used for anything out-of-process, such as reading
109 /// from files or sockets.
110 ///
111 /// # Panics
112 ///
113 /// Panics if called from a non-Ruby thread. See
114 /// [`Ruby::enc_default_external`] for the non-panicking version.
115 #[cfg_attr(
116 not(feature = "old-api"),
117 deprecated(note = "please use `Ruby::enc_default_external` instead")
118 )]
119 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
120 #[inline]
121 pub fn default_external() -> Self {
122 get_ruby!().enc_default_external()
123 }
124
125 /// Returns the default external encoding as a Ruby object.
126 ///
127 /// If set, any out-of-process data is transcoded from the default external
128 /// encoding to the default internal encoding.
129 ///
130 /// # Panics
131 ///
132 /// Panics if called from a non-Ruby thread. See
133 /// [`Ruby::enc_default_internal`] for the non-panicking version.
134 #[cfg_attr(
135 not(feature = "old-api"),
136 deprecated(note = "please use `Ruby::enc_default_internal` instead")
137 )]
138 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
139 #[inline]
140 pub fn default_internal() -> Option<Self> {
141 get_ruby!().enc_default_internal()
142 }
143}
144
145impl fmt::Display for Encoding {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}", unsafe { self.to_s_infallible() })
148 }
149}
150
151impl fmt::Debug for Encoding {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 write!(f, "{}", self.inspect())
154 }
155}
156
157impl From<Encoding> for Index {
158 fn from(val: Encoding) -> Self {
159 let i = unsafe { rb_to_encoding_index(val.as_rb_value()) };
160 if i == -1 {
161 panic!("got encoding index -1");
162 }
163 Index(i)
164 }
165}
166
167impl From<Encoding> for RbEncoding {
168 fn from(val: Encoding) -> Self {
169 let ptr = unsafe { rb_find_encoding(val.as_rb_value()) };
170 RbEncoding::new(ptr).expect("got NULL rb_encoding")
171 }
172}
173
174impl IntoValue for Encoding {
175 #[inline]
176 fn into_value_with(self, _: &Ruby) -> Value {
177 self.0.get()
178 }
179}
180
181impl Object for Encoding {}
182
183unsafe impl private::ReprValue for Encoding {}
184
185impl ReprValue for Encoding {}
186
187impl TryConvert for Encoding {
188 fn try_convert(val: Value) -> Result<Self, Error> {
189 if let Some(enc) = Self::from_value(val) {
190 return Ok(enc);
191 }
192 RbEncoding::try_convert(val).map(Into::into)
193 }
194}
195
196/// # `RbEncoding`
197///
198/// Functions to access pre-defined encodings.
199///
200/// See also the [`RbEncoding`] type.
201impl Ruby {
202 /// Returns the encoding that represents ASCII-8BIT a.k.a. binary.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// use magnus::{Error, Ruby};
208 ///
209 /// fn example(ruby: &Ruby) -> Result<(), Error> {
210 /// assert_eq!(ruby.ascii8bit_encoding().name(), "ASCII-8BIT");
211 /// Ok(())
212 /// }
213 /// # Ruby::init(example).unwrap()
214 /// ```
215 pub fn ascii8bit_encoding(&self) -> RbEncoding {
216 RbEncoding::new(unsafe { rb_ascii8bit_encoding() }).unwrap()
217 }
218
219 /// Returns the encoding that represents UTF-8.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// use magnus::{Error, Ruby};
225 ///
226 /// fn example(ruby: &Ruby) -> Result<(), Error> {
227 /// assert_eq!(ruby.utf8_encoding().name(), "UTF-8");
228 /// Ok(())
229 /// }
230 /// # Ruby::init(example).unwrap()
231 /// ```
232 pub fn utf8_encoding(&self) -> RbEncoding {
233 RbEncoding::new(unsafe { rb_utf8_encoding() }).unwrap()
234 }
235
236 /// Returns the encoding that represents US-ASCII.
237 ///
238 /// # Examples
239 ///
240 /// ```
241 /// use magnus::{Error, Ruby};
242 ///
243 /// fn example(ruby: &Ruby) -> Result<(), Error> {
244 /// assert_eq!(ruby.usascii_encoding().name(), "US-ASCII");
245 /// Ok(())
246 /// }
247 /// # Ruby::init(example).unwrap()
248 /// ```
249 pub fn usascii_encoding(&self) -> RbEncoding {
250 RbEncoding::new(unsafe { rb_usascii_encoding() }).unwrap()
251 }
252
253 /// Returns the encoding that represents the process' current locale.
254 ///
255 /// This is dynamic. If you change the process' locale that should also
256 /// change the return value of this function.
257 pub fn locale_encoding(&self) -> RbEncoding {
258 RbEncoding::new(unsafe { rb_locale_encoding() }).unwrap()
259 }
260
261 /// Returns the filesystem encoding.
262 ///
263 /// This is the encoding that Ruby expects data from the OS' file system
264 /// to be encoded as, such as directory names.
265 pub fn filesystem_encoding(&self) -> RbEncoding {
266 RbEncoding::new(unsafe { rb_filesystem_encoding() }).unwrap()
267 }
268
269 /// Returns the default external encoding.
270 ///
271 /// This is the encoding used for anything out-of-process, such as reading
272 /// from files or sockets.
273 pub fn default_external_encoding(&self) -> RbEncoding {
274 RbEncoding::new(unsafe { rb_default_external_encoding() }).unwrap()
275 }
276
277 /// Returns the default internal encoding.
278 ///
279 /// If set, any out-of-process data is transcoded from the default external
280 /// encoding to the default internal encoding.
281 pub fn default_internal_encoding(&self) -> Option<RbEncoding> {
282 RbEncoding::new(unsafe { rb_default_internal_encoding() })
283 }
284
285 /// Returns the encoding with the name or alias `name`.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// use magnus::{Error, Ruby};
291 ///
292 /// fn example(ruby: &Ruby) -> Result<(), Error> {
293 /// assert_eq!(ruby.find_encoding("BINARY").unwrap().name(), "ASCII-8BIT");
294 /// assert_eq!(ruby.find_encoding("UTF-8").unwrap().name(), "UTF-8");
295 /// Ok(())
296 /// }
297 /// # Ruby::init(example).unwrap()
298 /// ```
299 pub fn find_encoding(&self, name: &str) -> Option<RbEncoding> {
300 let name = CString::new(name).unwrap();
301 let ptr = unsafe { rb_enc_find(name.as_ptr()) };
302 RbEncoding::new(ptr)
303 }
304}
305
306/// Ruby's internal encoding type.
307///
308/// This type contains the data for an encoding, and is used with operations
309/// such as converting a string from one encoding to another, or reading a
310/// string character by character.
311///
312/// See [`Ruby`](Ruby#rbencoding) for methods to get an `RbEncoding`.
313#[repr(transparent)]
314pub struct RbEncoding(NonNull<rb_encoding>);
315
316impl RbEncoding {
317 pub(crate) fn new(inner: *mut rb_encoding) -> Option<Self> {
318 NonNull::new(inner).map(Self)
319 }
320
321 /// Returns the encoding that represents ASCII-8BIT a.k.a. binary.
322 ///
323 /// # Panics
324 ///
325 /// Panics if called from a non-Ruby thread. See
326 /// [`Ruby::ascii8bit_encoding`] for the non-panicking version.
327 #[cfg_attr(
328 not(feature = "old-api"),
329 deprecated(note = "please use `Ruby::ascii8bit_encoding` instead")
330 )]
331 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
332 #[inline]
333 pub fn ascii8bit() -> Self {
334 get_ruby!().ascii8bit_encoding()
335 }
336
337 /// Returns the encoding that represents UTF-8.
338 ///
339 /// # Panics
340 ///
341 /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encoding`]
342 /// for the non-panicking version.
343 #[cfg_attr(
344 not(feature = "old-api"),
345 deprecated(note = "please use `Ruby::utf8_encoding` instead")
346 )]
347 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
348 #[inline]
349 pub fn utf8() -> Self {
350 get_ruby!().utf8_encoding()
351 }
352
353 /// Returns the encoding that represents US-ASCII.
354 ///
355 /// # Panics
356 ///
357 /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encoding`]
358 /// for the non-panicking version.
359 #[cfg_attr(
360 not(feature = "old-api"),
361 deprecated(note = "please use `Ruby::usascii_encoding` instead")
362 )]
363 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
364 #[inline]
365 pub fn usascii() -> Self {
366 get_ruby!().usascii_encoding()
367 }
368
369 /// Returns the encoding that represents the process' current locale.
370 ///
371 /// This is dynamic. If you change the process' locale that should also
372 /// change the return value of this function.
373 ///
374 /// # Panics
375 ///
376 /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encoding`]
377 /// for the non-panicking version.
378 #[cfg_attr(
379 not(feature = "old-api"),
380 deprecated(note = "please use `Ruby::locale_encoding` instead")
381 )]
382 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
383 #[inline]
384 pub fn locale() -> Self {
385 get_ruby!().locale_encoding()
386 }
387
388 /// Returns the filesystem encoding.
389 ///
390 /// This is the encoding that Ruby expects data from the OS' file system
391 /// to be encoded as, such as directory names.
392 ///
393 /// # Panics
394 ///
395 /// Panics if called from a non-Ruby thread. See
396 /// [`Ruby::filesystem_encoding`] for the non-panicking version.
397 #[cfg_attr(
398 not(feature = "old-api"),
399 deprecated(note = "please use `Ruby::filesystem_encoding` instead")
400 )]
401 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
402 #[inline]
403 pub fn filesystem() -> Self {
404 get_ruby!().filesystem_encoding()
405 }
406
407 /// Returns the default external encoding.
408 ///
409 /// This is the encoding used for anything out-of-process, such as reading
410 /// from files or sockets.
411 ///
412 /// # Panics
413 ///
414 /// Panics if called from a non-Ruby thread. See
415 /// [`Ruby::default_external_encoding`] for the non-panicking version.
416 #[cfg_attr(
417 not(feature = "old-api"),
418 deprecated(note = "please use `Ruby::default_external_encoding` instead")
419 )]
420 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
421 #[inline]
422 pub fn default_external() -> Self {
423 get_ruby!().default_external_encoding()
424 }
425
426 /// Returns the default internal encoding.
427 ///
428 /// If set, any out-of-process data is transcoded from the default external
429 /// encoding to the default internal encoding.
430 ///
431 /// # Panics
432 ///
433 /// Panics if called from a non-Ruby thread. See
434 /// [`Ruby::default_internal_encoding`] for the non-panicking version.
435 #[cfg_attr(
436 not(feature = "old-api"),
437 deprecated(note = "please use `Ruby::default_internal_encoding` instead")
438 )]
439 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
440 #[inline]
441 pub fn default_internal() -> Option<Self> {
442 get_ruby!().default_internal_encoding()
443 }
444
445 /// Returns the encoding with the name or alias `name`.
446 ///
447 /// # Panics
448 ///
449 /// Panics if called from a non-Ruby thread. See [`Ruby::find_encoding`]
450 /// for the non-panicking version.
451 ///
452 /// # Examples
453 ///
454 /// ```
455 /// # #![allow(deprecated)]
456 /// use magnus::encoding::RbEncoding;
457 /// # let _cleanup = unsafe { magnus::embed::init() };
458 ///
459 /// assert_eq!(RbEncoding::find("UTF-8").unwrap().name(), "UTF-8");
460 /// assert_eq!(RbEncoding::find("BINARY").unwrap().name(), "ASCII-8BIT");
461 /// ```
462 #[cfg_attr(
463 not(feature = "old-api"),
464 deprecated(note = "please use `Ruby::find_encoding` instead")
465 )]
466 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
467 #[inline]
468 pub fn find(name: &str) -> Option<Self> {
469 get_ruby!().find_encoding(name)
470 }
471
472 pub(crate) fn as_ptr(&self) -> *mut rb_encoding {
473 self.0.as_ptr()
474 }
475
476 /// Returns the canonical name of the encoding.
477 ///
478 /// # Examples
479 ///
480 /// ```
481 /// use magnus::{Error, Ruby};
482 ///
483 /// fn example(ruby: &Ruby) -> Result<(), Error> {
484 /// assert_eq!(ruby.utf8_encoding().name(), "UTF-8");
485 /// assert_eq!(ruby.find_encoding("UTF-16").unwrap().name(), "UTF-16");
486 ///
487 /// Ok(())
488 /// }
489 /// # Ruby::init(example).unwrap()
490 /// ```
491 ///
492 /// # Panics
493 ///
494 /// Panics if the name is not valid UTF-8. Encoding names are expected to
495 /// be ASCII only.
496 pub fn name(&self) -> &str {
497 unsafe { CStr::from_ptr(self.0.as_ref().name).to_str().unwrap() }
498 }
499
500 /// Returns the minimum number of bytes the encoding needs to represent a
501 /// single character.
502 ///
503 /// # Examples
504 ///
505 /// ```
506 /// use magnus::{Error, Ruby};
507 ///
508 /// fn example(ruby: &Ruby) -> Result<(), Error> {
509 /// assert_eq!(ruby.usascii_encoding().mbminlen(), 1);
510 /// assert_eq!(ruby.utf8_encoding().mbminlen(), 1);
511 ///
512 /// Ok(())
513 /// }
514 /// # Ruby::init(example).unwrap()
515 /// ```
516 pub fn mbminlen(&self) -> usize {
517 unsafe { self.0.as_ref().min_enc_len as usize }
518 }
519
520 /// Returns the maximum number of bytes the encoding may need to represent
521 /// a single character.
522 ///
523 /// # Examples
524 ///
525 /// ```
526 /// use magnus::{Error, Ruby};
527 ///
528 /// fn example(ruby: &Ruby) -> Result<(), Error> {
529 /// assert_eq!(ruby.usascii_encoding().mbmaxlen(), 1);
530 /// assert_eq!(ruby.utf8_encoding().mbmaxlen(), 4);
531 ///
532 /// Ok(())
533 /// }
534 /// # Ruby::init(example).unwrap()
535 /// ```
536 pub fn mbmaxlen(&self) -> usize {
537 unsafe { self.0.as_ref().max_enc_len as usize }
538 }
539
540 /// Returns the number of bytes of the first character in `slice`.
541 ///
542 /// If the first byte of `slice` is mid way through a character this will
543 /// return the number of bytes until the next character boundry.
544 ///
545 /// If the slice ends before the last byte of the character this will
546 /// return the number of bytes until the end of the slice.
547 ///
548 /// See also [`fast_mbclen`](RbEncoding::fast_mbclen) and
549 /// [`precise_mbclen`](RbEncoding::precise_mbclen).
550 ///
551 /// # Examples
552 ///
553 /// ```
554 /// use magnus::{
555 /// encoding::{EncodingCapable, RbEncoding},
556 /// Error, Ruby,
557 /// };
558 ///
559 /// fn example(ruby: &Ruby) -> Result<(), Error> {
560 /// let s = ruby.str_new("🦀 café");
561 /// let encoding: RbEncoding = s.enc_get().into();
562 /// let mut chars = 0;
563 ///
564 /// unsafe {
565 /// let mut bytes = s.as_slice();
566 /// assert_eq!(bytes.len(), 10);
567 ///
568 /// while !bytes.is_empty() {
569 /// chars += 1;
570 /// let len = encoding.mbclen(bytes);
571 /// bytes = &bytes[len..];
572 /// }
573 /// }
574 ///
575 /// assert_eq!(chars, 6);
576 ///
577 /// Ok(())
578 /// }
579 /// # Ruby::init(example).unwrap()
580 /// ```
581 pub fn mbclen(&self, slice: &[u8]) -> usize {
582 let Range { start: p, end: e } = slice.as_ptr_range();
583 unsafe { rb_enc_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) as usize }
584 }
585
586 /// Returns the number of bytes of the first character in `slice`.
587 ///
588 /// If the first byte of `slice` is mid way through a character this will
589 /// return the number of bytes until the next character boundary.
590 ///
591 /// If the slice ends before the last byte of the character this will
592 /// return the theoretical number of bytes until the end of the character,
593 /// which will be past the end of the slice. If the string has been read
594 /// from an IO source this may indicate more data needs to be read.
595 ///
596 /// See also [`mbclen`](RbEncoding::mbclen) and
597 /// [`precise_mbclen`](RbEncoding::precise_mbclen).
598 ///
599 /// # Examples
600 ///
601 /// ```
602 /// use magnus::{
603 /// encoding::{EncodingCapable, RbEncoding},
604 /// Error, Ruby,
605 /// };
606 ///
607 /// fn example(ruby: &Ruby) -> Result<(), Error> {
608 /// let s = ruby.str_new("🦀 café");
609 /// let encoding: RbEncoding = s.enc_get().into();
610 /// let mut chars = 0;
611 ///
612 /// unsafe {
613 /// let mut bytes = s.as_slice();
614 /// assert_eq!(bytes.len(), 10);
615 ///
616 /// while !bytes.is_empty() {
617 /// chars += 1;
618 /// let len = encoding.fast_mbclen(bytes);
619 /// bytes = &bytes[len..];
620 /// }
621 /// }
622 ///
623 /// assert_eq!(chars, 6);
624 ///
625 /// Ok(())
626 /// }
627 /// # Ruby::init(example).unwrap()
628 /// ```
629 pub fn fast_mbclen(&self, slice: &[u8]) -> usize {
630 let Range { start: p, end: e } = slice.as_ptr_range();
631 unsafe {
632 rb_enc_fast_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) as usize
633 }
634 }
635
636 /// Returns the number of bytes of the first character in `slice`.
637 ///
638 /// See also [`mbclen`](RbEncoding::mbclen) and
639 /// [`fast_mbclen`](RbEncoding::fast_mbclen).
640 ///
641 /// # Examples
642 ///
643 /// ```
644 /// use magnus::{
645 /// encoding::{EncodingCapable, MbcLen, RbEncoding},
646 /// Error, Ruby,
647 /// };
648 ///
649 /// fn example(ruby: &Ruby) -> Result<(), Error> {
650 /// let s = ruby.str_new("🦀 café");
651 /// let encoding: RbEncoding = s.enc_get().into();
652 /// let mut chars = 0;
653 ///
654 /// unsafe {
655 /// let mut bytes = s.as_slice();
656 /// assert_eq!(bytes.len(), 10);
657 ///
658 /// while !bytes.is_empty() {
659 /// chars += 1;
660 /// match encoding.precise_mbclen(bytes) {
661 /// MbcLen::CharFound(len) => bytes = &bytes[len..],
662 /// MbcLen::NeedMore(len) => panic!("Met end of string expecting {} bytes", len),
663 /// MbcLen::Invalid => panic!("corrupted string"),
664 /// }
665 /// }
666 /// }
667 ///
668 /// assert_eq!(chars, 6);
669 ///
670 /// Ok(())
671 /// }
672 /// # Ruby::init(example).unwrap()
673 /// ```
674 pub fn precise_mbclen(&self, slice: &[u8]) -> MbcLen {
675 let Range { start: p, end: e } = slice.as_ptr_range();
676 let r =
677 unsafe { rb_enc_precise_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) };
678 if 0 < r {
679 MbcLen::CharFound(r as usize)
680 } else if r < -1 {
681 MbcLen::NeedMore((-1 - r) as usize)
682 } else if r == -1 {
683 MbcLen::Invalid
684 } else {
685 unreachable!()
686 }
687 }
688
689 /// If the first character in `slice` is included in ASCII return it and
690 /// its encoded length in `slice`, otherwise returns None.
691 ///
692 /// Typically the length will be 1, but some encodings such as UTF-16 will
693 /// encode ASCII characters in 2 bytes.
694 ///
695 /// # Examples
696 ///
697 /// ```
698 /// use magnus::{
699 /// encoding::{EncodingCapable, RbEncoding},
700 /// Error, Ruby,
701 /// };
702 ///
703 /// fn example(ruby: &Ruby) -> Result<(), Error> {
704 /// let s = ruby.str_new("example");
705 /// let encoding: RbEncoding = s.enc_get().into();
706 /// let mut chars = Vec::new();
707 ///
708 /// unsafe {
709 /// let mut bytes = s.as_slice();
710 ///
711 /// while !bytes.is_empty() {
712 /// match encoding.ascget(bytes) {
713 /// Some((char, len)) => {
714 /// chars.push(char);
715 /// bytes = &bytes[len..];
716 /// }
717 /// None => panic!("string not ASCII"),
718 /// }
719 /// }
720 /// }
721 ///
722 /// assert_eq!(chars, [101, 120, 97, 109, 112, 108, 101]);
723 ///
724 /// Ok(())
725 /// }
726 /// # Ruby::init(example).unwrap()
727 /// ```
728 pub fn ascget(&self, slice: &[u8]) -> Option<(u8, usize)> {
729 let Range { start: p, end: e } = slice.as_ptr_range();
730 let mut len = 0;
731 let c = unsafe {
732 rb_enc_ascget(
733 p as *const c_char,
734 e as *const c_char,
735 &mut len as *mut _,
736 self.as_ptr(),
737 )
738 };
739 if len == 0 {
740 panic!("{:?}", slice);
741 }
742 (c > -1).then_some((c as u8, len as usize))
743 }
744
745 /// Returns the codepoint and length in bytes of the first character in
746 /// `slice`.
747 ///
748 /// # Examples
749 ///
750 /// ```
751 /// use magnus::{
752 /// encoding::{EncodingCapable, RbEncoding},
753 /// Error, Ruby,
754 /// };
755 ///
756 /// fn example(ruby: &Ruby) -> Result<(), Error> {
757 /// let s = ruby.str_new("🦀 café");
758 /// let encoding: RbEncoding = s.enc_get().into();
759 /// let mut codepoints = Vec::new();
760 ///
761 /// unsafe {
762 /// let mut bytes = s.as_slice();
763 ///
764 /// while !bytes.is_empty() {
765 /// let (codepoint, len) = encoding.codepoint_len(bytes)?;
766 /// codepoints.push(codepoint);
767 /// bytes = &bytes[len..];
768 /// }
769 /// }
770 ///
771 /// assert_eq!(codepoints, [129408, 32, 99, 97, 102, 233]);
772 ///
773 /// Ok(())
774 /// }
775 /// # Ruby::init(example).unwrap()
776 /// ```
777 pub fn codepoint_len(&self, slice: &[u8]) -> Result<(u32, usize), Error> {
778 let Range { start: p, end: e } = slice.as_ptr_range();
779 let mut len = 0;
780 let mut c = 0;
781 protect(|| unsafe {
782 c = rb_enc_codepoint_len(
783 p as *const c_char,
784 e as *const c_char,
785 &mut len as *mut _,
786 self.as_ptr(),
787 );
788 Ruby::get_unchecked().qnil()
789 })?;
790 Ok((c, len as usize))
791 }
792
793 /// Returns the number of bytes required to represent the code point `code`
794 /// in the encoding of `self`.
795 ///
796 /// # Examples
797 ///
798 /// ```
799 /// use magnus::{Error, Ruby};
800 ///
801 /// fn example(ruby: &Ruby) -> Result<(), Error> {
802 /// assert_eq!(ruby.utf8_encoding().codelen(97)?, 1);
803 /// assert_eq!(ruby.utf8_encoding().codelen(129408)?, 4);
804 ///
805 /// Ok(())
806 /// }
807 /// # Ruby::init(example).unwrap()
808 /// ```
809 pub fn codelen(&self, code: u32) -> Result<usize, Error> {
810 let handle = unsafe { Ruby::get_unchecked() };
811 let code = code
812 .try_into()
813 .map_err(|e: <usize as TryInto<c_int>>::Error| {
814 Error::new(handle.exception_arg_error(), e.to_string())
815 })?;
816 let mut len = 0;
817 protect(|| {
818 unsafe { len = rb_enc_codelen(code, self.as_ptr()) as usize };
819 handle.qnil()
820 })?;
821 Ok(len)
822 }
823
824 /// Encode the codepoint `code` as a series of bytes in the encoding `self`
825 /// and return the result as a Ruby string.
826 ///
827 /// # Examples
828 ///
829 /// ```
830 /// use magnus::{eval, Error, Ruby};
831 ///
832 /// fn example(ruby: &Ruby) -> Result<(), Error> {
833 /// let c = ruby.usascii_encoding().chr(97)?;
834 /// let res: bool = eval!(ruby, r#"c == "a""#, c)?;
835 /// assert!(res);
836 ///
837 /// Ok(())
838 /// }
839 /// # Ruby::init(example).unwrap()
840 /// ```
841 ///
842 /// ```
843 /// use magnus::{eval, Error, Ruby};
844 ///
845 /// fn example(ruby: &Ruby) -> Result<(), Error> {
846 /// let c = ruby.utf8_encoding().chr(129408)?;
847 /// let res: bool = eval!(ruby, r#"c == "🦀""#, c)?;
848 /// assert!(res);
849 ///
850 /// Ok(())
851 /// }
852 /// # Ruby::init(example).unwrap()
853 /// ```
854 pub fn chr(&self, code: u32) -> Result<RString, Error> {
855 protect(|| unsafe {
856 RString::from_rb_value_unchecked(rb_enc_uint_chr(code, self.as_ptr()))
857 })
858 }
859
860 /// Returns `true` if the first character in `slice` is a newline in the
861 /// encoding `self`, `false` otherwise.
862 ///
863 /// # Examples
864 ///
865 /// ```
866 /// use magnus::{Error, Ruby};
867 ///
868 /// fn example(ruby: &Ruby) -> Result<(), Error> {
869 /// assert!(ruby.utf8_encoding().is_mbc_newline(&[10]));
870 /// assert!(!ruby.utf8_encoding().is_mbc_newline(&[32]));
871 ///
872 /// Ok(())
873 /// }
874 /// # Ruby::init(example).unwrap()
875 /// ```
876 pub fn is_mbc_newline(&self, slice: &[u8]) -> bool {
877 let Range { start: p, end: e } = slice.as_ptr_range();
878 unsafe {
879 self.0.as_ref().is_mbc_newline.unwrap()(p as *const _, e as *const _, self.as_ptr())
880 != 0
881 }
882 }
883
884 /// Returns whether the given codepoint `code` is of the character type
885 /// `ctype` in the encoding `self`.
886 ///
887 /// # Examples
888 ///
889 /// ```
890 /// use magnus::{encoding::CType, Error, Ruby};
891 ///
892 /// fn example(ruby: &Ruby) -> Result<(), Error> {
893 /// assert!(ruby.utf8_encoding().is_code_ctype(9, CType::Space)); // "\t"
894 /// assert!(ruby.utf8_encoding().is_code_ctype(32, CType::Space)); // " "
895 /// assert!(!ruby.utf8_encoding().is_code_ctype(65, CType::Space)); // "A"
896 /// assert!(ruby.utf8_encoding().is_code_ctype(65, CType::Alnum)); // "A"
897 /// assert!(ruby.utf8_encoding().is_code_ctype(65, CType::Upper)); // "A"
898 ///
899 /// Ok(())
900 /// }
901 /// # Ruby::init(example).unwrap()
902 /// ```
903 pub fn is_code_ctype(&self, code: u32, ctype: CType) -> bool {
904 unsafe { self.0.as_ref().is_code_ctype.unwrap()(code, ctype as _, self.as_ptr()) != 0 }
905 }
906}
907
908/// Return value for [`RbEncoding::precise_mbclen`].
909pub enum MbcLen {
910 /// Found a valid char, value is the char's length.
911 CharFound(usize),
912 /// The slice ended before the end of the current char. Value is the
913 /// theoretical total length of the char.
914 NeedMore(usize),
915 /// The bytes at the start of the slice are not valid for the encoding.
916 Invalid,
917}
918
919/// A character type.
920#[repr(u32)]
921#[derive(Debug, Copy, Clone)]
922pub enum CType {
923 /// Newline.
924 Newline = 0,
925 /// Alphabetical.
926 Alpha = 1,
927 /// Blank.
928 Blank = 2,
929 /// Control.
930 Cntrl = 3,
931 /// Digit.
932 Digit = 4,
933 /// Graph.
934 Graph = 5,
935 /// Lowercase.
936 Lower = 6,
937 /// Printable.
938 Print = 7,
939 /// Punctuation.
940 Punct = 8,
941 /// Whitespace.
942 Space = 9,
943 /// Uppercase.
944 Upper = 10,
945 /// Xdigit.
946 Xdigit = 11,
947 /// Word.
948 Word = 12,
949 /// Alphanumeric.
950 Alnum = 13,
951 /// ASCII.
952 Ascii = 14,
953}
954
955impl From<RbEncoding> for Encoding {
956 fn from(val: RbEncoding) -> Self {
957 Encoding::from_value(Value::new(unsafe { rb_enc_from_encoding(val.as_ptr()) })).unwrap()
958 }
959}
960
961impl From<RbEncoding> for Index {
962 fn from(val: RbEncoding) -> Self {
963 Index(unsafe { rb_enc_to_index(val.as_ptr()) })
964 }
965}
966
967impl IntoValue for RbEncoding {
968 #[inline]
969 fn into_value_with(self, handle: &Ruby) -> Value {
970 Encoding::from(self).into_value_with(handle)
971 }
972}
973
974impl TryConvert for RbEncoding {
975 fn try_convert(val: Value) -> Result<Self, Error> {
976 let mut ptr = ptr::null_mut();
977 protect(|| unsafe {
978 ptr = rb_to_encoding(val.as_rb_value());
979 Ruby::get_unchecked().qnil()
980 })?;
981 Ok(Self::new(ptr).unwrap())
982 }
983}
984
985/// # Encoding Index
986///
987/// Functions to access pre-defined encodings.
988///
989/// See also the [`encoding::Index`](Index) type.
990impl Ruby {
991 /// Returns the index for ASCII-8BIT a.k.a. binary.
992 pub fn ascii8bit_encindex(&self) -> Index {
993 Index(unsafe { rb_ascii8bit_encindex() })
994 }
995
996 /// Returns the index for UTF-8.
997 pub fn utf8_encindex(&self) -> Index {
998 Index(unsafe { rb_utf8_encindex() })
999 }
1000
1001 /// Returns the index for US-ASCII.
1002 pub fn usascii_encindex(&self) -> Index {
1003 Index(unsafe { rb_usascii_encindex() })
1004 }
1005
1006 /// Returns the index for the process' current locale encoding.
1007 ///
1008 /// This is dynamic. If you change the process' locale that should also
1009 /// change the return value of this function.
1010 pub fn locale_encindex(&self) -> Index {
1011 Index(unsafe { rb_locale_encindex() })
1012 }
1013
1014 /// Returns the index for filesystem encoding.
1015 ///
1016 /// This is the encoding that Ruby expects data from the OS' file system
1017 /// to be encoded as, such as directory names.
1018 pub fn filesystem_encindex(&self) -> Index {
1019 Index(unsafe { rb_filesystem_encindex() })
1020 }
1021
1022 /// Returns the index for the encoding with the name or alias `name`.
1023 ///
1024 /// # Examples
1025 ///
1026 /// ```
1027 /// use magnus::{Error, Ruby};
1028 ///
1029 /// fn example(ruby: &Ruby) -> Result<(), Error> {
1030 /// assert!(ruby.find_encindex("UTF-8").is_ok());
1031 /// assert!(ruby.find_encindex("BINARY").is_ok());
1032 /// assert!(ruby.find_encindex("none").is_err());
1033 /// Ok(())
1034 /// }
1035 /// # Ruby::init(example).unwrap()
1036 /// ```
1037 pub fn find_encindex(&self, name: &str) -> Result<Index, Error> {
1038 let name = CString::new(name).unwrap();
1039 let mut i = 0;
1040 protect(|| {
1041 unsafe { i = rb_enc_find_index(name.as_ptr()) };
1042 self.qnil()
1043 })?;
1044 if i == -1 {
1045 return Err(Error::new(
1046 self.exception_runtime_error(),
1047 format!("Encoding {:?} exists, but can not be loaded", name),
1048 ));
1049 }
1050 Ok(Index(i))
1051 }
1052}
1053
1054/// The index of an encoding in Ruby's internal encodings table.
1055///
1056/// This is the type Ruby uses to label encoding capable types, so is used with
1057/// operations that require reading or setting that label.
1058///
1059/// See [`Ruby`](Ruby#encoding-index) for methods to get an `encoding::Index`.
1060#[derive(Clone, Copy, Eq, PartialEq)]
1061#[repr(transparent)]
1062pub struct Index(c_int);
1063
1064impl Index {
1065 /// Returns the index for ASCII-8BIT a.k.a. binary.
1066 ///
1067 /// # Panics
1068 ///
1069 /// Panics if called from a non-Ruby thread. See
1070 /// [`Ruby::ascii8bit_encindex`] for the non-panicking version.
1071 #[cfg_attr(
1072 not(feature = "old-api"),
1073 deprecated(note = "please use `Ruby::ascii8bit_encindex` instead")
1074 )]
1075 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1076 #[inline]
1077 pub fn ascii8bit() -> Self {
1078 get_ruby!().ascii8bit_encindex()
1079 }
1080
1081 /// Returns the index for UTF-8.
1082 ///
1083 /// # Panics
1084 ///
1085 /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encindex`]
1086 /// for the non-panicking version.
1087 #[cfg_attr(
1088 not(feature = "old-api"),
1089 deprecated(note = "please use `Ruby::utf8_encindex` instead")
1090 )]
1091 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1092 #[inline]
1093 pub fn utf8() -> Self {
1094 get_ruby!().utf8_encindex()
1095 }
1096
1097 /// Returns the index for US-ASCII.
1098 ///
1099 /// # Panics
1100 ///
1101 /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encindex`]
1102 /// for the non-panicking version.
1103 #[cfg_attr(
1104 not(feature = "old-api"),
1105 deprecated(note = "please use `Ruby::usascii_encindex` instead")
1106 )]
1107 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1108 #[inline]
1109 pub fn usascii() -> Self {
1110 get_ruby!().usascii_encindex()
1111 }
1112
1113 /// Returns the index for the process' current locale encoding.
1114 ///
1115 /// This is dynamic. If you change the process' locale that should also
1116 /// change the return value of this function.
1117 ///
1118 /// # Panics
1119 ///
1120 /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encindex`]
1121 /// for the non-panicking version.
1122 #[cfg_attr(
1123 not(feature = "old-api"),
1124 deprecated(note = "please use `Ruby::locale_encindex` instead")
1125 )]
1126 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1127 #[inline]
1128 pub fn locale() -> Self {
1129 get_ruby!().locale_encindex()
1130 }
1131
1132 /// Returns the index for filesystem encoding.
1133 ///
1134 /// This is the encoding that Ruby expects data from the OS' file system
1135 /// to be encoded as, such as directory names.
1136 ///
1137 /// # Panics
1138 ///
1139 /// Panics if called from a non-Ruby thread. See
1140 /// [`Ruby::filesystem_encindex`] for the non-panicking version.
1141 #[cfg_attr(
1142 not(feature = "old-api"),
1143 deprecated(note = "please use `Ruby::filesystem_encindex` instead")
1144 )]
1145 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1146 #[inline]
1147 pub fn filesystem() -> Self {
1148 get_ruby!().filesystem_encindex()
1149 }
1150
1151 /// Returns the index for the encoding with the name or alias `name`.
1152 ///
1153 /// # Panics
1154 ///
1155 /// Panics if called from a non-Ruby thread. See [`Ruby::find_encindex`]
1156 /// for the non-panicking version.
1157 ///
1158 /// # Examples
1159 ///
1160 /// ```
1161 /// # #![allow(deprecated)]
1162 /// use magnus::encoding;
1163 /// # let _cleanup = unsafe { magnus::embed::init() };
1164 ///
1165 /// assert!(encoding::Index::find("UTF-8").is_ok());
1166 /// assert!(encoding::Index::find("BINARY").is_ok());
1167 /// assert!(encoding::Index::find("none").is_err());
1168 /// ```
1169 #[cfg_attr(
1170 not(feature = "old-api"),
1171 deprecated(note = "please use `Ruby::find_encindex` instead")
1172 )]
1173 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
1174 #[inline]
1175 pub fn find(name: &str) -> Result<Self, Error> {
1176 get_ruby!().find_encindex(name)
1177 }
1178
1179 pub(crate) fn to_int(self) -> c_int {
1180 self.0
1181 }
1182}
1183
1184impl From<Index> for RbEncoding {
1185 fn from(val: Index) -> Self {
1186 RbEncoding::new(unsafe { rb_enc_from_index(val.to_int()) }).expect("no encoding for index")
1187 }
1188}
1189
1190impl TryConvert for Index {
1191 fn try_convert(val: Value) -> Result<Self, Error> {
1192 let i = unsafe { rb_to_encoding_index(val.as_rb_value()) };
1193 if i == -1 && RString::from_value(val).is_some() {
1194 return Err(Error::new(
1195 Ruby::get_with(val).exception_runtime_error(),
1196 format!("ArgumentError: unknown encoding name - {}", val),
1197 ));
1198 } else if i == -1 {
1199 return TryConvert::try_convert(RString::try_convert(val)?.as_value());
1200 }
1201 Ok(Index(i))
1202 }
1203}
1204
1205/// Possible states of how a string matches its encoding.
1206#[repr(u32)]
1207#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1208pub enum Coderange {
1209 /// It is unknown if the string is valid for its encoding.
1210 Unknown = 0,
1211 /// The string is entirely within the 0 to 127 ASCII range.
1212 SevenBit = 1048576,
1213 /// The string is valid for its encoding.
1214 Valid = 2097152,
1215 /// The string holds values that are invalid for its encoding.
1216 Broken = 3145728,
1217}
1218
1219/// Trait that marks Ruby types cable of having an encoding.
1220pub trait EncodingCapable: ReprValue + Copy {
1221 /// Get the encoding of `self`.
1222 ///
1223 /// # Examples
1224 ///
1225 /// ```
1226 /// use magnus::{encoding::EncodingCapable, Error, Ruby};
1227 ///
1228 /// fn example(ruby: &Ruby) -> Result<(), Error> {
1229 /// assert!(ruby.str_new("example").enc_get() == ruby.utf8_encindex());
1230 ///
1231 /// Ok(())
1232 /// }
1233 /// # Ruby::init(example).unwrap()
1234 /// ```
1235 fn enc_get(self) -> Index {
1236 let i = unsafe { rb_enc_get_index(self.as_rb_value()) };
1237 if i == -1 {
1238 panic!("{} not encoding capable", self.as_value());
1239 }
1240 Index(i)
1241 }
1242
1243 /// Set `self`'s encoding.
1244 ///
1245 /// Returns `Err` if `self` is frozen or the encoding can not be loaded.
1246 ///
1247 /// See also [`EncodingCapable::enc_associate`].
1248 ///
1249 /// # Examples
1250 ///
1251 /// ```
1252 /// use magnus::{encoding::EncodingCapable, Error, Ruby};
1253 ///
1254 /// fn example(ruby: &Ruby) -> Result<(), Error> {
1255 /// let s = ruby.str_new("example");
1256 /// assert!(s.enc_get() == ruby.utf8_encindex());
1257 /// s.enc_set(ruby.usascii_encindex())?;
1258 /// assert!(s.enc_get() == ruby.usascii_encindex());
1259 ///
1260 /// Ok(())
1261 /// }
1262 /// # Ruby::init(example).unwrap()
1263 /// ```
1264 fn enc_set<T>(self, enc: T) -> Result<(), Error>
1265 where
1266 T: Into<Index>,
1267 {
1268 protect(|| unsafe {
1269 rb_enc_set_index(self.as_rb_value(), enc.into().to_int());
1270 Ruby::get_unchecked().qnil()
1271 })?;
1272 Ok(())
1273 }
1274
1275 /// Set `self`'s encoding, along with performing additional fix-up `self`'s
1276 /// contents.
1277 ///
1278 /// For example, Ruby's strings contain an additional terminating null byte
1279 /// hidden from Ruby, but allowing for easy c string interop. This method
1280 /// will adjust the length of that terminating char depending on the
1281 /// encoding.
1282 ///
1283 /// Returns `Err` if `self` is frozen or the encoding can not be loaded.
1284 ///
1285 /// # Examples
1286 ///
1287 /// ```
1288 /// use magnus::{encoding::EncodingCapable, Error, Ruby};
1289 ///
1290 /// fn example(ruby: &Ruby) -> Result<(), Error> {
1291 /// let s = ruby.str_new("example");
1292 /// assert!(s.enc_get() == ruby.utf8_encindex());
1293 /// s.enc_associate(ruby.usascii_encindex())?;
1294 /// assert!(s.enc_get() == ruby.usascii_encindex());
1295 ///
1296 /// Ok(())
1297 /// }
1298 /// # Ruby::init(example).unwrap()
1299 /// ```
1300 fn enc_associate<T>(self, enc: T) -> Result<(), Error>
1301 where
1302 T: Into<Index>,
1303 {
1304 protect(|| {
1305 Value::new(unsafe { rb_enc_associate_index(self.as_rb_value(), enc.into().to_int()) })
1306 })?;
1307 Ok(())
1308 }
1309}
1310
1311/// Returns the common encoding between `v1` and `v2`, or `None`.
1312///
1313/// Returns `None` if there is no common compatible encoding.
1314///
1315/// See also [`check`].
1316///
1317/// # Examples
1318///
1319/// ```
1320/// use magnus::{encoding, prelude::*, Error, Ruby};
1321///
1322/// fn example(ruby: &Ruby) -> Result<(), Error> {
1323/// let a = ruby.str_new("a");
1324/// let b = ruby.str_new("b");
1325///
1326/// assert!(a.enc_get() == ruby.utf8_encindex());
1327/// b.enc_set(ruby.usascii_encindex())?;
1328///
1329/// assert_eq!(encoding::compatible(a, b).unwrap().name(), "UTF-8");
1330///
1331/// Ok(())
1332/// }
1333/// # Ruby::init(example).unwrap()
1334/// ```
1335pub fn compatible<T, U>(v1: T, v2: U) -> Option<RbEncoding>
1336where
1337 T: EncodingCapable,
1338 U: EncodingCapable,
1339{
1340 RbEncoding::new(unsafe { rb_enc_compatible(v1.as_rb_value(), v2.as_rb_value()) })
1341}
1342
1343/// Returns the common encoding between `v1` and `v2`, or `Err`.
1344///
1345/// Returns `Err` if there is no common compatible encoding.
1346///
1347/// See also [`compatible`].
1348///
1349/// # Examples
1350///
1351/// ```
1352/// use magnus::{encoding, prelude::*, Error, Ruby};
1353///
1354/// fn example(ruby: &Ruby) -> Result<(), Error> {
1355/// let a = ruby.str_new("a");
1356/// let b = ruby.str_new("b");
1357///
1358/// assert!(a.enc_get() == ruby.utf8_encindex());
1359/// b.enc_set(ruby.usascii_encindex())?;
1360///
1361/// assert_eq!(encoding::check(a, b)?.name(), "UTF-8");
1362///
1363/// Ok(())
1364/// }
1365/// # Ruby::init(example).unwrap()
1366/// ```
1367pub fn check<T, U>(v1: T, v2: U) -> Result<RbEncoding, Error>
1368where
1369 T: EncodingCapable,
1370 U: EncodingCapable,
1371{
1372 let mut ptr = ptr::null_mut();
1373 protect(|| unsafe {
1374 ptr = rb_enc_check(v1.as_rb_value(), v2.as_rb_value());
1375 Ruby::get_with(v1).qnil()
1376 })?;
1377 Ok(RbEncoding::new(ptr).unwrap())
1378}
1379
1380/// Compies the encoding from `src` to `dst`.
1381///
1382/// This does not reconcode `dst.`
1383///
1384/// Similar to [`EncodingCapable::enc_associate`], except takes the encoding of
1385/// `src` rather than an encoding object or index.
1386///
1387/// # Examples
1388///
1389/// ```
1390/// use magnus::{encoding, prelude::*, Error, Ruby};
1391///
1392/// fn example(ruby: &Ruby) -> Result<(), Error> {
1393/// let a = ruby.str_new("a");
1394/// assert!(a.enc_get() == ruby.utf8_encindex());
1395/// let b = ruby.str_new("b");
1396/// assert!(b.enc_get() == ruby.utf8_encindex());
1397///
1398/// a.enc_set(ruby.usascii_encindex())?;
1399/// encoding::copy(b, a)?;
1400///
1401/// assert!(b.enc_get() == ruby.usascii_encindex());
1402///
1403/// Ok(())
1404/// }
1405/// # Ruby::init(example).unwrap()
1406/// ```
1407pub fn copy<T, U>(dst: T, src: U) -> Result<(), Error>
1408where
1409 T: EncodingCapable,
1410 U: EncodingCapable,
1411{
1412 protect(|| unsafe {
1413 rb_enc_copy(dst.as_rb_value(), src.as_rb_value());
1414 Ruby::get_with(dst).qnil()
1415 })?;
1416 Ok(())
1417}