Begin moving the name parsers to the Network.DNS.RBL.Domain namespace.
[dead/harbl.git] / harbl / src / Network / DNS / RBL / Domain / Letter.hs
1 -- | The... also... the simplest module you'll ever see. It contains
2 -- the 'Letter' type and a Parsec parser to parse one. We don't
3 -- export its constructor because then you could do something dumb
4 -- like stick a digit inside one.
5 --
6 -- These are defined in RFC1035, Section 2.3.1, \"Preferred name
7 -- syntax\" <https://tools.ietf.org/html/rfc1035#section-2.3.1>:
8 --
9 -- <letter> ::= any one of the 52 alphabetic characters A through
10 -- Z in upper case and a through z in lower case
11 --
12 module Network.DNS.RBL.Domain.Letter (
13 Letter,
14 letter )
15 where
16
17 import Data.Char ( toLower )
18 import qualified Text.Parsec as Parsec ( letter )
19 import Text.Parsec.String ( Parser )
20
21 import Network.DNS.RBL.Pretty ( Pretty(..) )
22
23 -- * Letters
24
25 -- | A wrapper around a letter character.
26 --
27 -- ==== _Examples_
28 --
29 -- >>> Letter 'x'
30 -- Letter 'x'
31 --
32 newtype Letter = Letter Char deriving (Show)
33
34
35 -- | Pretty-printing for letters that we've already parsed. Just
36 -- shows/prints the letter character.
37 --
38 -- ==== _Examples_
39 --
40 -- >>> let l = Letter 'x'
41 -- >>> pretty_print l
42 -- x
43 --
44 instance Pretty Letter where pretty_show (Letter l) = [l]
45
46
47 -- | Parse a single letter, but wrap it in our 'Letter' type.
48 --
49 -- ==== _Examples_
50 --
51 -- >>> import Text.Parsec ( parseTest )
52 --
53 -- Letters are parsed correctly:
54 --
55 -- >>> parseTest letter "x"
56 -- Letter 'x'
57 --
58 -- But digits are not:
59 --
60 -- >>> parseTest letter "1"
61 -- parse error at (line 1, column 1):
62 -- unexpected "1"
63 -- expecting letter
64 --
65 letter :: Parser Letter
66 letter = fmap Letter Parsec.letter
67
68
69
70 -- | The derived instance of 'Eq' for letters is incorrect. All
71 -- comparisons should be made case-insensitively. The following
72 -- is an excerpt from RFC1035:
73 --
74 -- 2.3.3. Character Case
75 --
76 -- For all parts of the DNS that are part of the official
77 -- protocol, all comparisons between character strings (e.g.,
78 -- labels, domain names, etc.) are done in a case-insensitive
79 -- manner...
80 --
81 -- Since each part of DNS name is composed of our custom types, it
82 -- suffices to munge the equality for 'Letter'. RFC4343
83 -- <https://tools.ietf.org/html/rfc4343> clarifies the
84 -- case-insensitivity rules, but the fact that we're treating DNS
85 -- names as strings makes most of those problems go away (in
86 -- exchange for new ones).
87 --
88 -- ==== _Examples_
89 --
90 -- >>> let l1 = Letter 'x'
91 -- >>> let l2 = Letter 'x'
92 -- >>> let l3 = Letter 'X'
93 -- >>> let l4 = Letter 'X'
94 -- >>> l1 == l2
95 -- True
96 -- >>> l1 == l3
97 -- True
98 -- >>> l1 == l4
99 -- True
100 -- >>> l3 == l4
101 -- True
102 --
103 instance Eq Letter where
104 (Letter l1) == (Letter l2) = (toLower l1) == (toLower l2)