unic-char-range-0.9.0/benches/benchmarks.rs010064400007650000024000000021041343520353600167750ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #![feature(test)] extern crate test; use std::char; use unic_char_range::CharRange; #[bench] fn forward_iteration(b: &mut test::Bencher) { b.iter(|| CharRange::all().iter().count()) } #[bench] fn forward_iteration_baseline(b: &mut test::Bencher) { b.iter(|| (0..0x11_0000).filter_map(char::from_u32).count()) } #[bench] fn reverse_iteration(b: &mut test::Bencher) { b.iter(|| CharRange::all().iter().rev().count()) } #[bench] fn reverse_iteration_baseline(b: &mut test::Bencher) { b.iter(|| (0..0x11_0000).rev().filter_map(char::from_u32).count()) } #[bench] fn range_length(b: &mut test::Bencher) { b.iter(|| CharRange::all().len()) } unic-char-range-0.9.0/Cargo.toml.orig010064400007650000024000000020151343664667500156130ustar0000000000000000[package] name = "unic-char-range" version = "0.9.0" edition = "2018" authors = ["The UNIC Project Developers"] repository = "https://github.com/open-i18n/rust-unic/" license = "MIT/Apache-2.0" description = "UNIC — Unicode Character Tools — Character Range and Iteration" keywords = ["text", "unicode", "utilities", "iteration"] categories = ["internationalization", "text-processing"] # No tests/benches that depends on /data/ exclude = [] [dependencies] rayon = { version = "1.0", optional = true } [features] default = [] std = [] # Unstable features unstable = ["exact-size-is-empty", "fused", "trusted-len"] exact-size-is-empty = [] fused = [] trusted-len = [] [badges] maintenance = { status = "actively-developed" } is-it-maintained-issue-resolution = { repository = "open-i18n/rust-unic" } is-it-maintained-open-issues = { repository = "open-i18n/rust-unic" } appveyor = { repository = "open-i18n/rust-unic", branch = "master", service = "github" } travis-ci = { repository = "open-i18n/rust-unic", branch = "master" } unic-char-range-0.9.0/Cargo.toml0000644000000027270000000000000120520ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "unic-char-range" version = "0.9.0" authors = ["The UNIC Project Developers"] exclude = [] description = "UNIC — Unicode Character Tools — Character Range and Iteration" keywords = ["text", "unicode", "utilities", "iteration"] categories = ["internationalization", "text-processing"] license = "MIT/Apache-2.0" repository = "https://github.com/open-i18n/rust-unic/" [dependencies.rayon] version = "1.0" optional = true [features] default = [] exact-size-is-empty = [] fused = [] std = [] trusted-len = [] unstable = ["exact-size-is-empty", "fused", "trusted-len"] [badges.appveyor] branch = "master" repository = "open-i18n/rust-unic" service = "github" [badges.is-it-maintained-issue-resolution] repository = "open-i18n/rust-unic" [badges.is-it-maintained-open-issues] repository = "open-i18n/rust-unic" [badges.maintenance] status = "actively-developed" [badges.travis-ci] branch = "master" repository = "open-i18n/rust-unic" unic-char-range-0.9.0/examples/macro_use_std_tests.rs010064400007650000024000000011301343520353600211360ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #[macro_use] extern crate unic_char_range; fn main() { assert!(chars!('\u{0}'..='\u{2}') .iter() .eq(['\u{0}', '\u{1}', '\u{2}'].iter().cloned())); } unic-char-range-0.9.0/src/iter.rs010064400007650000024000000074121343520353600150120ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use core::{char, ops}; use crate::{step, CharRange}; const SURROGATE_RANGE: ops::Range = 0xD800..0xE000; /// An iterator over a range of unicode code points. /// /// Constructed via `CharRange::iter`. See `CharRange` for more information. #[derive(Clone, Debug)] pub struct CharIter { /// The lowest uniterated character (inclusive). /// /// Iteration is finished if this is higher than `high`. low: char, /// The highest uniterated character (inclusive). /// /// Iteration is finished if this is lower than `low`. high: char, } impl From for CharIter { fn from(range: CharRange) -> CharIter { CharIter { low: range.low, high: range.high, } } } impl From for CharRange { fn from(iter: CharIter) -> CharRange { CharRange { low: iter.low, high: iter.high, } } } impl CharIter { #[inline] #[allow(unsafe_code)] // When stepping `self.low` forward would go over `char::MAX`, // Set `self.high` to `'\0'` instead. It will have the same effect -- // consuming the last element from the iterator and ending iteration. fn step_forward(&mut self) { if self.low == char::MAX { self.high = '\0' } else { self.low = unsafe { step::forward(self.low) } } } #[inline] #[allow(unsafe_code)] // When stepping `self.high` backward would cause underflow, // set `self.low` to `char::MAX` instead. It will have the same effect -- // consuming the last element from the iterator and ending iteration. fn step_backward(&mut self) { if self.high == '\0' { self.low = char::MAX; } else { self.high = unsafe { step::backward(self.high) } } } #[inline] /// ExactSizeIterator::is_empty() for stable fn is_finished(&self) -> bool { self.low > self.high } } impl Iterator for CharIter { type Item = char; #[inline] fn next(&mut self) -> Option { if self.is_finished() { return None; } let ch = self.low; self.step_forward(); Some(ch) } fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } fn last(self) -> Option { if self.is_finished() { None } else { Some(self.high) } } fn max(self) -> Option { self.last() } fn min(mut self) -> Option { self.next() } } impl DoubleEndedIterator for CharIter { #[inline] fn next_back(&mut self) -> Option { if self.is_finished() { None } else { let ch = self.high; self.step_backward(); Some(ch) } } } impl ExactSizeIterator for CharIter { fn len(&self) -> usize { if self.is_finished() { return 0; } let naive_range = (self.low as u32)..(self.high as u32 + 1); if naive_range.start <= SURROGATE_RANGE.start && SURROGATE_RANGE.end <= naive_range.end { naive_range.len() - SURROGATE_RANGE.len() } else { naive_range.len() } } #[cfg(feature = "exact-size-is-empty")] fn is_empty(&self) -> bool { self.is_finished() } } unic-char-range-0.9.0/src/iter_fused.rs010064400007650000024000000010031343520353600161660ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use core::iter; use super::iter::CharIter; impl iter::FusedIterator for CharIter {} unic-char-range-0.9.0/src/iter_trusted_len.rs010064400007650000024000000010351343520353600174150ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use core::iter; use super::iter::CharIter; #[allow(unsafe_code)] unsafe impl iter::TrustedLen for CharIter {} unic-char-range-0.9.0/src/lib.rs010064400007650000024000000041161343520353600146130ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "exact-size-is-empty", feature(exact_size_is_empty))] #![cfg_attr(feature = "trusted-len", feature(trusted_len))] #![warn( bad_style, missing_debug_implementations, missing_docs, unconditional_recursion )] #![deny(unsafe_code)] //! # UNIC — Unicode Character Tools — Character Range //! //! A simple way to control iteration over a range of characters. //! //! # Examples //! //! ``` //! #[macro_use] extern crate unic_char_range; //! //! # fn main() { //! for character in chars!('a'..='z') { //! // character is each character in the lowercase english alphabet in order //! } //! //! for character in chars!(..) { //! // character is every valid char from lowest codepoint to highest //! } //! # } //! ``` //! //! # Features //! //! None of these features are included by default; they rely on unstable Rust feature gates. //! //! - `unstable`: enables all features //! - `exact-size-is-empty`: provide a specific impl of [`ExactSizeIterator::is_empty`][is_empty] //! - `trusted-len`: impl the [`TrustedLen`] contract //! //! [is_empty]: https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html#method.is_empty //! [`FusedIterator`]: https://doc.rust-lang.org/std/iter/trait.FusedIterator.html //! [`TrustedLen`]: https://doc.rust-lang.org/std/iter/trait.TrustedLen.html //! mod pkg_info; pub use crate::pkg_info::{PKG_DESCRIPTION, PKG_NAME, PKG_VERSION}; mod iter; pub use crate::iter::CharIter; mod range; pub use crate::range::CharRange; #[macro_use] mod macros; mod step; mod iter_fused; #[cfg(feature = "trusted-len")] mod iter_trusted_len; #[cfg(feature = "rayon")] mod par_iter; unic-char-range-0.9.0/src/macros.rs010064400007650000024000000027601343520353600153340ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #[macro_export] /// Convenience macro for the initialization of `CharRange`s. /// /// # Syntax /// /// ``` /// # #[macro_use] extern crate unic_char_range; /// # fn main() { /// chars!('a'..'z'); // The half open range including 'a' and excluding 'z' /// chars!('a'..='z'); // The closed range including 'a' and including 'z' /// chars!(..); // All characters /// # } /// ``` /// /// `chars!('a'..='z')` and `chars!(..)` are constant-time expressions, and can be used /// where such are required, such as in the initialization of constant data structures. /// /// NOTE: Because an `expr` capture cannot be followed by a `..`/`..=`, this macro captures token /// trees. This means that if you want to pass more than one token, you must parenthesize it (e.g. /// `chars!('\0' ..= (char::MAX))`). macro_rules! chars { ( $low:tt .. $high:tt ) => { $crate::CharRange::open_right($low, $high) }; ( $low:tt ..= $high:tt ) => { $crate::CharRange { low: $low, high: $high, } }; ( .. ) => { $crate::CharRange::all() }; } unic-char-range-0.9.0/src/par_iter.rs010064400007650000024000000054671343520353600156640ustar0000000000000000// Copyright 2018 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use rayon; use self::rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; use self::rayon::prelude::*; use crate::step::{AFTER_SURROGATE, BEFORE_SURROGATE}; use crate::CharRange; use core::char; use core::ops::Range; const SKIP_LENGTH: u32 = crate::step::AFTER_SURROGATE as u32 - crate::step::BEFORE_SURROGATE as u32 - 1; #[derive(Clone, Debug)] pub struct Iter(rayon::iter::Map, fn(u32) -> char>); impl ParallelIterator for Iter { type Item = char; fn drive_unindexed(self, consumer: C) -> C::Result where C: UnindexedConsumer, { self.0.drive_unindexed(consumer) } } impl IndexedParallelIterator for Iter { fn len(&self) -> usize { self.0.len() } fn drive>(self, consumer: C) -> C::Result { self.0.drive(consumer) } fn with_producer>(self, callback: CB) -> CB::Output { self.0.with_producer(callback) } } impl CharRange { fn compact_range(&self) -> Range { let low = self.low as u32; let high = self.high as u32 + 1; low..(if self.high >= AFTER_SURROGATE { high - SKIP_LENGTH } else { high }) } } impl IntoParallelIterator for CharRange { type Item = char; type Iter = Iter; fn into_par_iter(self) -> Self::Iter { Iter(self.compact_range().into_par_iter().map(|c| { let c = if c > BEFORE_SURROGATE as u32 { c + SKIP_LENGTH } else { c }; debug_assert!(c <= BEFORE_SURROGATE as u32 || c >= AFTER_SURROGATE as u32); debug_assert!(c <= char::MAX as u32); #[allow(unsafe_code)] unsafe { char::from_u32_unchecked(c) } })) } } impl<'a> IntoParallelIterator for &'a CharRange { type Item = char; type Iter = Iter; fn into_par_iter(self) -> Self::Iter { (*self).into_par_iter() } } #[cfg(test)] mod tests { use super::*; #[test] fn length_agrees() { assert_eq!(chars!(..).iter().count(), chars!(..).par_iter().count()) } #[test] #[cfg(feature = "std")] fn content_agrees() { assert_eq!( chars!(..).iter().collect::>(), chars!(..).par_iter().collect::>() ) } } unic-char-range-0.9.0/src/pkg_info.rs010064400007650000024000000013121343520353600156340ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Package information /// UNIC component version. pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); /// UNIC component name. pub const PKG_NAME: &str = env!("CARGO_PKG_NAME"); /// UNIC component description. pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); unic-char-range-0.9.0/src/range.rs010064400007650000024000000160521343520353600151430ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use core::{char, cmp}; #[cfg(feature = "std")] use std::collections::Bound; use self::cmp::Ordering; use crate::CharIter; /// A range of unicode code points. /// /// The most idiomatic way to construct this range is through the use of the `chars!` macro: /// /// ``` /// #[macro_use] extern crate unic_char_range; /// use unic_char_range::CharRange; /// /// # fn main() { /// assert_eq!(chars!('a'..='z'), CharRange::closed('a', 'z')); /// assert_eq!(chars!('a'..'z'), CharRange::open_right('a', 'z')); /// assert_eq!(chars!(..), CharRange::all()); /// # } /// ``` /// /// If constructed in reverse order, such that `self.high` is ordered before `self.low`, /// the range is empty. If you want to iterate in decreasing order, use `.iter().rev()`. /// All empty ranges are considered equal no matter the internal state. #[derive(Copy, Clone, Debug, Eq)] pub struct CharRange { /// The lowest character in this range (inclusive). pub low: char, /// The highest character in this range (inclusive). pub high: char, } /// Constructors impl CharRange { /// Construct a closed range of characters. /// /// If `stop` is ordered before `start`, the resulting range will be empty. /// /// # Example /// /// ``` /// # use unic_char_range::*; /// assert_eq!( /// CharRange::closed('a', 'd').iter().collect::>(), /// vec!['a', 'b', 'c', 'd'] /// ) /// ``` pub fn closed(start: char, stop: char) -> CharRange { CharRange { low: start, high: stop, } } /// Construct a half open (right) range of characters. /// /// # Example /// /// ``` /// # use unic_char_range::*; /// assert_eq!( /// CharRange::open_right('a', 'd').iter().collect::>(), /// vec!['a', 'b', 'c'] /// ) /// ``` pub fn open_right(start: char, stop: char) -> CharRange { let mut iter = CharRange::closed(start, stop).iter(); let _ = iter.next_back(); iter.into() } /// Construct a half open (left) range of characters. /// /// # Example /// /// ``` /// # use unic_char_range::*; /// assert_eq!( /// CharRange::open_left('a', 'd').iter().collect::>(), /// vec!['b', 'c', 'd'] /// ) /// ``` pub fn open_left(start: char, stop: char) -> CharRange { let mut iter = CharRange::closed(start, stop).iter(); let _ = iter.next(); iter.into() } /// Construct a fully open range of characters. /// /// # Example /// /// ``` /// # use unic_char_range::*; /// assert_eq!( /// CharRange::open('a', 'd').iter().collect::>(), /// vec!['b', 'c'] /// ) /// ``` pub fn open(start: char, stop: char) -> CharRange { let mut iter = CharRange::closed(start, stop).iter(); let _ = iter.next(); let _ = iter.next_back(); iter.into() } #[cfg(feature = "std")] /// Construct a range of characters from bounds. pub fn bound(start: Bound, stop: Bound) -> CharRange { let start = if start == Bound::Unbounded { Bound::Included('\u{0}') } else { start }; let stop = if stop == Bound::Unbounded { Bound::Included(char::MAX) } else { stop }; match (start, stop) { (Bound::Included(start), Bound::Included(stop)) => CharRange::closed(start, stop), (Bound::Excluded(start), Bound::Excluded(stop)) => CharRange::open(start, stop), (Bound::Included(start), Bound::Excluded(stop)) => CharRange::open_right(start, stop), (Bound::Excluded(start), Bound::Included(stop)) => CharRange::open_left(start, stop), (Bound::Unbounded, _) | (_, Bound::Unbounded) => unreachable!(), } } /// Construct a range over all Unicode characters (Unicode Scalar Values). pub fn all() -> CharRange { CharRange::closed('\u{0}', char::MAX) } /// Construct a range over all characters of *assigned* Unicode Planes. /// /// Assigned *normal* (non-special) Unicode Planes are: /// - Plane 0: *Basic Multilingual Plane* (BMP) /// - Plane 1: *Supplementary Multilingual Plane* (SMP) /// - Plane 2: *Supplementary Ideographic Plane* (SIP) /// /// Unicode Plane 14, *Supplementary Special-purpose Plane* (SSP), is not included in this /// range, mainly because of the limit of `CharRange` only supporting a continuous range. /// /// Unicode Planes 3 to 13 are *Unassigned* planes and therefore excluded. /// /// Unicode Planes 15 and 16 are *Private Use Area* planes and won't have Unicode-assigned /// characters. pub fn assigned_normal_planes() -> CharRange { CharRange::closed('\u{0}', '\u{2_FFFF}') } } /// Collection-like fns impl CharRange { /// Does this range include a character? /// /// # Examples /// /// ``` /// # use unic_char_range::CharRange; /// assert!( CharRange::closed('a', 'g').contains('d')); /// assert!( ! CharRange::closed('a', 'g').contains('z')); /// /// assert!( ! CharRange:: open ('a', 'a').contains('a')); /// assert!( ! CharRange::closed('z', 'a').contains('g')); /// ``` pub fn contains(&self, ch: char) -> bool { self.low <= ch && ch <= self.high } /// Determine the ordering of this range and a character. /// /// # Panics /// /// Panics if the range is empty. This fn may be adjusted in the future to not panic /// in optimized builds. Even if so, an empty range will never compare as `Ordering::Equal`. pub fn cmp_char(&self, ch: char) -> Ordering { // possible optimization: only assert this in debug builds assert!(!self.is_empty(), "Cannot compare empty range's ordering"); if self.high < ch { Ordering::Less } else if self.low > ch { Ordering::Greater } else { Ordering::Equal } } /// How many characters are in this range? pub fn len(&self) -> usize { self.iter().len() } /// Is this range empty? pub fn is_empty(&self) -> bool { self.low > self.high } /// Create an iterator over this range. pub fn iter(&self) -> CharIter { (*self).into() } } impl IntoIterator for CharRange { type IntoIter = CharIter; type Item = char; fn into_iter(self) -> CharIter { self.iter() } } impl PartialEq for CharRange { fn eq(&self, other: &CharRange) -> bool { (self.is_empty() && other.is_empty()) || (self.low == other.low && self.high == other.high) } } unic-char-range-0.9.0/src/step.rs010064400007650000024000000024361343520353600150230ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use core::char; pub const BEFORE_SURROGATE: char = '\u{D7FF}'; pub const AFTER_SURROGATE: char = '\u{E000}'; #[inline] #[allow(unsafe_code)] /// Step a character one step towards `char::MAX`. /// /// # Safety /// /// If the given character is `char::MAX`, the return value is not a valid character. pub unsafe fn forward(ch: char) -> char { if ch == BEFORE_SURROGATE { AFTER_SURROGATE } else { char::from_u32_unchecked(ch as u32 + 1) } } #[inline] #[allow(unsafe_code)] /// Step a character one step towards `'\0'`. /// /// # Safety /// /// If the given character is `'\0'`, this will cause an underflow. /// (Thus, it will panic in debug mode, undefined behavior in release mode.) pub unsafe fn backward(ch: char) -> char { if ch == AFTER_SURROGATE { BEFORE_SURROGATE } else { char::from_u32_unchecked(ch as u32 - 1) } } unic-char-range-0.9.0/tests/iter_tests.rs010064400007650000024000000041101343520353600165770ustar0000000000000000// Copyright 2017 The UNIC Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::{char, u32, vec}; use unic_char_range::CharRange; fn all_chars() -> vec::IntoIter { (u32::MIN..u32::MAX) .take_while(|&u| u <= char::MAX as u32) .filter_map(char::from_u32) .collect::>() .into_iter() } #[test] fn test_iter_all_chars() { assert!(CharRange::all().iter().eq(all_chars())) } #[test] fn test_iter_all_chars_rev() { assert!(CharRange::all().iter().rev().eq(all_chars().rev())) } #[test] fn test_iter_all_chars_mixed_next_back() { let mut custom = CharRange::all().iter(); let mut simple = all_chars(); while let Some(custom_char) = custom.next() { assert_eq!(Some(custom_char), simple.next()); assert_eq!(custom.next_back(), simple.next_back()); } assert_eq!(None, simple.next()); } #[test] fn test_iter_all_chars_into_iter() { for _ch in CharRange::all() { // nothing } } #[test] fn test_iter_fused() { let mut iter = CharRange::all().iter(); let mut fused = all_chars().fuse(); assert!(iter.by_ref().eq(fused.by_ref())); for _ in 0..100 { assert_eq!(iter.next(), fused.next()); } } #[test] fn test_iter_exact_trusted_len() { fn assert_presents_right_len(iter: &I, len: usize) { assert_eq!(iter.len(), len); assert_eq!(iter.size_hint(), (len, Some(len))); } let mut iter = CharRange::all().iter(); let mut predicted_length = iter.len(); assert_eq!(predicted_length, all_chars().len()); while let Some(_) = iter.next() { predicted_length -= 1; assert_presents_right_len(&iter, predicted_length); } assert_presents_right_len(&iter, 0); } unic-char-range-0.9.0/.cargo_vcs_info.json0000644000000001120000000000000140360ustar00{ "git": { "sha1": "5878605364af97a3358368a6eaef02104af2e016" } }