1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! # Smart Quotes
//!
//! This is a tiny helper crate providing a simple heursitic for
//! implementing smart quotes.
//!
//!
//! While this crate does not convert any glyphs, it gives a heuristic
//! based on the previous character, wheter the next character would be
//! an opening or closing quotation mark.
//!
//! Example usage:
//! ```
//! use smart_quotes::{decide_quote_after, Decision};
//!
//! assert_eq!(decide_quote_after(None), Decision::Open);
//!
//! assert_eq!(decide_quote_after(Some(' ')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\t')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\n')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\x0A')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\u{1680}')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\u{2005}')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\u{202F}')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\u{2029}')), Decision::Open);
//!
//! assert_eq!(decide_quote_after(Some('(')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('[')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('{')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('⟨')), Decision::Open);
//!
//! assert_eq!(decide_quote_after(Some('\u{2012}')), Decision::Open);
//! assert_eq!(decide_quote_after(Some('\u{2015}')), Decision::Open);
//!
//! assert_eq!(decide_quote_after(Some('x')), Decision::Close);
//! assert_eq!(decide_quote_after(Some('“')), Decision::Close);
//! assert_eq!(decide_quote_after(Some('‘')), Decision::Close);
//! assert_eq!(decide_quote_after(Some('.')), Decision::Close);
//! assert_eq!(decide_quote_after(Some(':')), Decision::Close);
//! ```
//!

/// The decision, whether an open or closed quotation mark should be used.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum Decision {
    Open,
    Close,
}

/// Applies the heurisic based on the previous_character.
///
/// If the quote would be the first character, you can pass `None`.
pub fn decide_quote_after(previous_character: Option<char>) -> Decision {
    match previous_character {
        None => Decision::Open,
        Some(x) if is_space(x) || is_dash(x) || is_opening_parenthesis(x) => Decision::Open,
        _ => Decision::Close,
    }
}

fn is_space(x: char) -> bool {
    match x {
        // whitespace (space and linebreaks)
        // https://en.wikipedia.org/w/index.php?title=Whitespace_character&oldid=924029247
        '\x09'..='\x0d'
        | '\x20'
        | '\u{85}'
        | '\u{a0}'
        | '\u{1680}'
        | '\u{2000}'..='\u{200A}'
        | '\u{2028}'
        | '\u{2029}'
        | '\u{202F}'
        | '\u{205F}'
        | '\u{3000}' => true,
        '\u{200B}' => true, // zero width space added here, because it indicates the separation between words
        _ => false,
    }
}

fn is_dash(x: char) -> bool {
    match x {
        '\u{2012}'..='\u{2015}' => true,
        _ => false,
    }
}

fn is_opening_parenthesis(x: char) -> bool {
    match x {
        '(' | '[' | '{' | '⟨' => true,
        _ => false,
    }
}