zalgo 0.2.0

A crate for generating Zalgo text with defined parameters.
Documentation
// ISC License (ISC)
//
// Copyright (c) 2016, Austin Hellyer <hello@austinhellyer.me>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
// RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
// CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// What is Zalgo?
//
//
// | Zalgo is an Internet legend about an ominous entity believed to cause
// | insanity, death and destruction of the world, similar to the creature Cthulhu
// | created by H.P. Lovecraft in the 1920s. Zalgo is often associated with
// | scrambled text on webpages and photos of people whose eyes and mouth have been
// | covered in black.
// |
// | - [icannwiki](http://icannwiki.com/GTLD)
//! A library for easily creating modifications of text with Zalgo characters.
//!
//! What is Zalgo?
//!
//!
//! > Zalgo is an Internet legend about an ominous entity believed to cause
//! > insanity, death and destruction of the world, similar to the creature Cthulhu
//! > created by H.P. Lovecraft in the 1920s. Zalgo is often associated with
//! > scrambled text on webpages and photos of people whose eyes and mouth have been
//! > covered in black.
//! >
//! > -- [knowyourmeme](http://knowyourmeme.com/memes/zalgo)
//!
//! An example to create a modified string with Zalgo text only above the string
//! with a high amount of Zalgo text is:
//!
//! ```rust
//! use zalgo::{Generator, GeneratorArgs, ZalgoSize};
//!
//! let mut generator = Generator::new();
//! let mut out = String::new();
//! let args = GeneratorArgs::new(true, false, false, ZalgoSize::Maxi);
//! let result = generator.gen("my string", &mut out, &args);
//! ```
#![deny(missing_docs)]

extern crate rand;

/// Denotes characters to be used in the "upper" part of text.
pub static ZALGO_UP: [char; 50] = [
    '̍', '̎', '̄', '̅', '̿', '̑', '̆', '̐', '͒', '͗', '͑', '̇', '̈', '̊',
    '͂', '̓', '̈́', '͊', '͋', '͌', '̃', '̂', '̌', '͐', '̀', '́', '̋', '̏',
    '̒', '̓', '̔', '̽', '̉', 'ͣ', 'ͤ', 'ͥ', 'ͦ', 'ͧ', 'ͨ', 'ͩ', 'ͪ', 'ͫ',
    'ͬ', 'ͭ', 'ͮ', 'ͯ', '̾', '͛', '͆', '̚',
];
/// Denotes characters to be used in the "middle" part of text.
pub static ZALGO_MIDDLE: [char; 23] = [
    '̕', '̛', '̀', '́', '͘', '̡', '̢', '̧', '̨', '̴', '̵', '̶', '͏', '͜',
    '͝', '͞', '͟', '͠', '͢', '̸', '̷', '͡', '҉',
];
/// Denotes characters to be used in the "bottom" part of text.
pub static ZALGO_DOWN: [char; 40] = [
    '̖', '̗', '̘', '̙', '̜', '̝', '̞', '̟', '̠', '̤', '̥', '̦', '̩', '̪',
    '̫', '̬', '̭', '̮', '̯', '̰', '̱', '̲', '̳', '̹', '̺', '̻', '̼', 'ͅ',
    '͇', '͈', '͉', '͍', '͎', '͓', '͔', '͕', '͖', '͙', '͚', '̣',
];

const ZALGO_UP_LEN: u8 = 50;
const ZALGO_MIDDLE_LEN: u8 = 23;
const ZALGO_DOWN_LEN: u8 = 40;

use rand::{Rng, ThreadRng};

/// A definition of the character type to be used for retrieval.
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum ZalgoKind {
    /// Denotes characters to be used to appear in the top of the resulting
    /// string.
    Up,
    /// Denotes characters to be used to appear in the middle of the resulting
    /// string (i.e. similar to strikethrough text).
    Middle,
    /// Denotes characters to be used to appear in the bottom of the resulting
    /// string (i.e. similar to underlined text).
    Down,
}

/// The size of the Zalgo text within the string to produce.
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum ZalgoSize {
    /// Produce a larger amount of Zalgo text.
    Maxi,
    /// Produce a smaller amount of Zalgo text.
    Mini,
    /// Produce a randomized amount of Zalgo text.
    None,
}

/// Produces a `Vec` of the combined kinds of Zalgo `char`s. This is all of the
/// `char`s used to create a generated Zalgo `String`.
///
/// # Examples
///
/// A basic usage:
///
/// ```rust
/// let _ = zalgo::all();
///
/// // You can then manually use this `Vec` for your own uses.
/// ```
pub fn all() -> Vec<char> {
    let mut v = vec![];

    v.extend(ZALGO_UP.iter());
    v.extend(ZALGO_MIDDLE.iter());
    v.extend(ZALGO_DOWN.iter());

    v
}

/// Arguments to provide to [`Generator::gen`] and [`Generator::simple_gen`].
/// 
/// # Examples
/// 
/// Instantiating args directly:
/// 
/// ```rust
/// use zalgo::{GeneratorArgs, ZalgoSize};
/// 
/// GeneratorArgs {
///     down: Some(true),
///     size: Some(ZalgoSize::Mini),
///     ..Default::default()
/// };
/// ```
/// 
/// Creating args via [`GeneratorArgs::new`]:
/// 
/// ```rust
/// use zalgo::{GeneratorArgs, ZalgoSize};
/// 
/// GeneratorArgs::new(false, false, true, ZalgoSize::Maxi);
/// ```
/// 
/// [`Generator::gen`]: struct.Generator.html#method.gen
/// [`Generator::simple_gen`]: struct.Generator.html#method.simple_gen
/// [`GeneratorArgs::new`]: struct.GeneratorArgs.html#method.new
#[derive(Clone, Debug, Default)]
pub struct GeneratorArgs {
    /// Whether to produce zalgo below the text.
    pub down: Option<bool>,
    /// Whether to produce zalgo in the middle of the text.
    pub middle: Option<bool>,
    /// The amount of zalgo to produce.
    pub size: Option<ZalgoSize>,
    /// Whether to produce zalgo above the text.
    pub up: Option<bool>,
}

impl GeneratorArgs {
    /// Creates new generator arguments.
    pub fn new(up: bool, middle: bool, down: bool, size: ZalgoSize) -> Self {
        Self {
            down: Some(down),
            middle: Some(middle),
            size: Some(size),
            up: Some(up),
        }
    }
}

/// A generator to perform zalgo operations.
/// 
/// This is a structure that contains an internal `rand::ThreadRng` instance.
pub struct Generator {
    rng: ThreadRng,
}

impl Generator {
    /// Creates a new generator with a new ThreadRng instance.
    pub fn new() -> Self {
        Self {
            rng: rand::thread_rng(),
        }
    }

    /// Generates a String containing Zalgo text. This is customizable via
    /// defining whether to include Zalgo text above the given string, in the
    /// middle of it, and below it.
    ///
    /// The amount of Zalgo text can be (more or less) defined by the value of
    /// the `size` given. Read on the `ZalgoSize` for more information.
    ///
    /// # Examples
    ///
    /// Create Zalgo text with Zalgo `char`s in all positions, with a maximum
    /// amount of Zalgo:
    ///
    /// ```rust
    /// use zalgo::{Generator, GeneratorArgs, ZalgoSize};
    /// 
    /// let mut generator = Generator::new();
    /// let args = GeneratorArgs::new(true, true, true, ZalgoSize::Maxi);
    /// let mut out = String::new();
    /// generator.gen("test", &mut out, &args);
    /// ```
    ///
    /// Create Zalgo text with Zalgo `char`s in only the middle and lower
    /// positions, with a minimum amount of Zalgo:
    ///
    /// ```rust
    /// use zalgo::{Generator, GeneratorArgs, ZalgoSize};
    /// 
    /// let mut generator = Generator::new();
    /// let args = GeneratorArgs::new(false, true, true, ZalgoSize::Mini);
    /// let mut out = String::new();
    /// generator.gen("test", &mut out, &args);
    /// ```
    ///
    /// Create Zalgo text with Zalgo `char`s in only the lower position, with a
    /// random amount of Zalgo (can be a low amount or high amount):
    ///
    /// ```rust
    /// use zalgo::{Generator, GeneratorArgs, ZalgoSize};
    ///
    /// let mut generator = Generator::new();
    /// let args = GeneratorArgs::new(false, false, true, ZalgoSize::None);
    /// let mut out = String::new();
    /// generator.gen("test", &mut out, &args);
    /// ```
    ///
    /// Consequentially, you can also not modify your given text with any Zalgo:
    ///
    /// ```rust
    /// use zalgo::{Generator, GeneratorArgs, ZalgoSize};
    ///
    /// let mut generator = Generator::new();
    /// let args = GeneratorArgs::new(false, false, false, ZalgoSize::None);
    /// let mut out = String::new();
    /// generator.gen("test", &mut out, &args);
    /// // Technically the `ZalgoSize` value given does not matter here.
    /// ```
    pub fn gen<T: AsRef<str>>(
        &mut self,
        text: T,
        buf: &mut String,
        args: &GeneratorArgs,
    ) {
        self._gen(text.as_ref(), buf, args)
    }

    fn _gen(
        &mut self,
        text: &str,
        buf: &mut String,
        args: &GeneratorArgs,
    ) {
        let GeneratorArgs { up, middle, down, size } = args;

        for ch in text.chars() {
            // Skip the text if it's already a Zalgo char
            if is_zalgo(ch) {
                continue;
            }

            // Push the given character to the resultant string no matter what
            buf.push(ch);

            if up.unwrap_or(false) {
                let count = self.rng.gen_range(0, ZALGO_UP_LEN) as usize;

                self.gen_iter(count, &ZALGO_UP, buf);
            }

            if middle.unwrap_or(false) {
                let count = match size {
                    Some(ZalgoSize::None) => self.rng.gen_range(0, ZALGO_MIDDLE_LEN) / 2,
                    _ => self.rng.gen_range(0, ZALGO_MIDDLE_LEN),
                } as usize;

                self.gen_iter(count, &ZALGO_MIDDLE, buf);
            }

            if down.unwrap_or(false) {
                let count = self.rng.gen_range(0, ZALGO_DOWN_LEN) as usize;
                self.gen_iter(count, &ZALGO_DOWN, buf);
            }
        }
    }

    /// A simple function to generate a String containing Zalgo text.
    ///
    /// Unlike [`gen`], this is not customizable. This produces zalgo above, in
    /// the middle, and below the string.
    ///
    /// # Examples
    ///
    /// Create Zalgo text:
    ///
    /// ```rust
    /// use zalgo::Generator;
    /// 
    /// let mut out = String::new();
    /// 
    /// let mut generator = Generator::new();
    /// generator.gen_simple("test", &mut out);
    /// ```
    ///
    /// [`gen`]: #method.gen
    pub fn gen_simple<T: AsRef<str>>(&mut self, text: T, buf: &mut String) {
        self.gen(text.as_ref(), buf, &GeneratorArgs {
            down: Some(true),
            middle: Some(true),
            size: Some(ZalgoSize::Mini),
            up: Some(true),
        })
    }

    fn gen_iter(&mut self, count: usize, zalgo: &[char], buf: &mut String) {
        (0..count).for_each(|_| {
            let get = self.rng.gen_range(0, count);
            buf.push(zalgo[get]);
        });
    }
}

/// Determines whether a given `char` is a `Zalgo` `char`. This is checked by
/// checking if a combination of the defined Zalgo `char`s contains the given
/// `char`.
///
/// # Examples
///
/// A basic check:
///
/// ```rust
/// assert!(zalgo::is_zalgo('҉'));
///
/// // The following is simply a latin letter, and is not zalgo:
/// assert!(!zalgo::is_zalgo('a'));
/// ```
pub fn is_zalgo(ch: char) -> bool {
    all().contains(&ch)
}