packages feed

ip 1.0.0 → 1.1.0

raw patch · 12 files changed

+249/−108 lines, 12 filesdep +quickcheck-classesdep ~attoparsecdep ~primitivePVP ok

version bump matches the API change (PVP)

Dependencies added: quickcheck-classes

Dependency ranges changed: attoparsec, primitive

API changes (from Hackage documentation)

- Net.IPv4: print :: IPv4 -> IO ()
- Net.Mac: [getMac] :: Mac -> Word64
- Net.Mac: decodeEither :: Text -> Either String Mac
- Net.Mac: decodeEitherWith :: MacCodec -> Text -> Either String Mac
- Net.Mac: parserLenientUtf8 :: Parser Mac
- Net.Types: [getMac] :: Mac -> Word64
+ Net.IPv4: ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4
+ Net.IPv6: ipv6 :: Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> IPv6
+ Net.Mac: decodeBytes :: ByteString -> Maybe Mac
+ Net.Mac: mac :: Word64 -> Mac
- Net.IP: ipv4 :: IP -> Maybe IPv4
+ Net.IP: ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IP
- Net.IP: ipv6 :: IP -> Maybe IPv6
+ Net.IP: ipv6 :: Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> Word16 -> IP

Files

ip.cabal view
@@ -1,5 +1,5 @@ name: ip-version: 1.0.0+version: 1.1.0 synopsis: Library for IP and MAC addresses homepage: https://github.com/andrewthad/haskell-ip#readme license: BSD3@@ -48,13 +48,13 @@     Data.ByteString.Builder.Fixed   build-depends:       base >= 4.8  && < 5-    , attoparsec+    , attoparsec >= 0.13 && < 0.14     , aeson >= 0.9  && < 1.3     , hashable >= 1.2  && < 1.3     , text >= 1.2  && < 1.3     , bytestring >= 0.10 && < 0.11     , vector >= 0.11 && < 0.13-    , primitive+    , primitive >= 0.6 && < 0.7   -- if impl(ghcjs)   --   build-depends: ghcjs-base >= 0.2 && < 0.3   ghc-options: -Wall -O2@@ -70,6 +70,7 @@     , test-framework     , test-framework-quickcheck2     , QuickCheck+    , quickcheck-classes     , text     , bytestring     , HUnit@@ -81,13 +82,13 @@     IPv4Text2     IPv4ByteString1     -- IPv4TextVariableBuilder-  ghc-options:         -Wall -O2-  default-language:    Haskell2010+  ghc-options: -Wall -O2+  default-language: Haskell2010  test-suite doctest-  type:                exitcode-stdio-1.0-  hs-source-dirs:      test-  main-is:             Doctests.hs+  type: exitcode-stdio-1.0+  hs-source-dirs: test+  main-is: Doctests.hs   build-depends:       base     , ip
src/Data/ByteString/Builder/Fixed.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE BangPatterns #-} -{-# OPTIONS_GHC -O2 -Wall -funbox-strict-fields #-}+{-# OPTIONS_GHC -Wall -funbox-strict-fields #-}  {-| For concatenating fixed-width strings that are only a few     characters each, this can be six times faster than the builder@@ -22,22 +22,17 @@   , word12HexFixedUpper   ) where -import Control.Monad.ST import Data.Monoid import Data.Word import Data.Word.Synthetic.Word12 (Word12) import Data.Bits import Data.Char (ord) import Text.Printf-import Debug.Trace import Data.ByteString.Internal (ByteString(..)) import Foreign import Data.ByteString.Short (ShortByteString)-import System.IO.Unsafe import qualified Data.ByteString.Internal as BI-import qualified Data.ByteString.Builder as BBuilder import qualified Data.ByteString as ByteString-import qualified Data.ByteString.Lazy as LByteString import qualified Data.ByteString.Char8 as BC8 import qualified Data.ByteString.Short.Internal as SBS @@ -138,10 +133,6 @@ word8 :: Builder Word8 word8 = BuilderFunction (BC8.pack "-") $ \i marr w -> pokeByteOff marr i w {-# INLINE word8 #-}--word8At :: Int -> Word64 -> Word8-word8At i w = fromIntegral (unsafeShiftR w i)-{-# INLINE word8At #-}  -- | Taken from @Data.ByteString.Internal@. The same warnings --   apply here.
src/Data/Text/Builder/Fixed.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE BangPatterns #-} -{-# OPTIONS_GHC -O2 -Wall -funbox-strict-fields #-}+{-# OPTIONS_GHC -Wall -funbox-strict-fields #-}  {-| For concatenating fixed-width strings that are only a few     characters each, this can be ten times faster than the builder@@ -25,23 +25,12 @@ import Data.Monoid import Data.Word import Data.Bits-import Text.Printf-import Debug.Trace import Data.Char (ord) import Data.Word.Synthetic.Word12 (Word12)-import Data.Vector (Vector)-import Data.Foldable (fold) import Data.Text (Text)-import qualified Data.Vector as Vector import qualified Data.Text as Text-import qualified Data.Text.Lazy as LText-import qualified Data.Text.Lazy.IO as LText-import qualified Data.Text.Lazy.Builder as TBuilder-import qualified Data.Text.Lazy.Builder.Int as TBuilder-import qualified Data.Text.IO as Text import qualified Data.Text.Array as A import qualified Data.Text.Internal as TI-import qualified Data.Text.Internal.Unsafe.Char as TC import qualified Data.Text.Builder.Common.Internal as I  data Builder a where
src/Data/Word/Synthetic/Word12.hs view
@@ -35,7 +35,6 @@ import           GHC.Base import           GHC.Enum import           GHC.Num-import           GHC.Ptr import           GHC.Read import           GHC.Real import           GHC.Show
src/Net/IP.hs view
@@ -1,9 +1,35 @@+{-# OPTIONS_GHC -Wall #-}++{-| An IP data type representing either an IPv4 address or+    an IPv6 address. The user can think of this+    as though it were a sum type. However, to minimize indirections,+    it is actually implemented as an 'IPv6' address, with 'IPv4'+    addresses being represented as an IPv4-mapped IPv6 addresses:++    > +---------+---------+--------------++    > | 80 bits | 16 bits | 32 bits      |+    > +---------+---------+--------------++    > | 00...00 | FFFF    | IPv4 address |+    > +---------+---------+--------------+++    All functions and instance methods that deal with textual conversion+    will encode an 'IP' using either dot-decimal notation (for IPv4) or+    RFC 5952 (for IPv6). They will decode an 'IP' from either format+    as well. The 'Show' instance presents an address in as valid haskell code +    that resembles the formatted address:+    +    >>> decode "192.168.3.100"+    Just (ipv4 192 168 3 100)+    >>> decode "A3F5:12:F26::1466:8B91"+    Just (ipv6 0xa3f5 0x0012 0x0f26 0x0000 0x0000 0x0000 0x1466 0x8b91)+-}+ module Net.IP   ( -- * Pattern Matching     case_+    -- * Construction   , ipv4   , ipv6-    -- * Construction   , fromIPv4   , fromIPv6     -- * Textual Conversion@@ -17,10 +43,11 @@ import Data.Bits import Net.IPv6 (IPv6(..)) import Net.IPv4 (IPv4(..))-import Text.Read (Read(..),Lexeme(Ident),lexP,parens)-import Text.ParserCombinators.ReadPrec (prec,step,(+++))+import Text.Read (Read(..))+import Text.ParserCombinators.ReadPrec ((+++)) import Data.Aeson (FromJSON(..),ToJSON(..)) import Data.Text (Text)+import Data.Word (Word8,Word16) import qualified Net.IPv4 as IPv4 import qualified Net.IPv6 as IPv6 import qualified Data.Aeson as Aeson@@ -30,14 +57,17 @@   then f (IPv4 (fromIntegral w2))   else g addr --- | If the address is an 'IPv4' address, return the address.-ipv4 :: IP -> Maybe IPv4-ipv4 = case_ Just (const Nothing)+-- | Construct an 'IP' address from the four octets of+--   an IPv4 address.+ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IP+ipv4 a b c d = fromIPv4 (IPv4.fromOctets a b c d) --- | If the address is an 'IPv6' address, and if it is not---   an IPv4-mapped IPv6 address, return the address.-ipv6 :: IP -> Maybe IPv6-ipv6 = case_ (const Nothing) Just+-- | Construct an 'IP' address from the eight 16-bit+--   chunks of an IPv6 address.+ipv6 :: Word16 -> Word16 -> Word16 -> Word16+     -> Word16 -> Word16 -> Word16 -> Word16+     -> IP+ipv6 a b c d e f g h = fromIPv6 (IPv6.fromWord16s a b c d e f g h)  fromIPv4 :: IPv4 -> IP fromIPv4 (IPv4 w) = IP (IPv6 0 (0x0000FFFF00000000 .|. fromIntegral w))@@ -64,20 +94,10 @@   deriving (Eq,Ord)  instance Show IP where-  showsPrec p = case_-    (\x -> showParen (p > 10) $ showString "fromIPv4 " . showsPrec 11 x)-    (\x -> showParen (p > 10) $ showString "fromIPv6 " . showsPrec 11 x)+  showsPrec p = case_ (showsPrec p) (showsPrec p)  instance Read IP where-  readPrec = parens $-    ( prec 10 $ do-        Ident "fromIPv4 " <- lexP-        fmap fromIPv4 (step readPrec)-    ) +++-    ( prec 10 $ do-        Ident "fromIPv6 " <- lexP-        fmap fromIPv6 (step readPrec)-    )+  readPrec = fmap fromIPv4 readPrec +++ fmap fromIPv6 readPrec  instance ToJSON IP where   toJSON = Aeson.String . encode
src/Net/IPv4.hs view
@@ -23,7 +23,8 @@  module Net.IPv4   ( -- * Conversion Functions-    fromOctets+    ipv4+  , fromOctets   , toOctets     -- * Special IP Addresses   , any@@ -40,7 +41,6 @@   , builder   , reader   , parser-  , print     -- ** UTF-8 ByteString   , encodeUtf8   , decodeUtf8@@ -54,7 +54,7 @@   , IPv4(..)   ) where -import Prelude hiding (any,print)+import Prelude hiding (any) import Data.Bits ((.&.),(.|.),shiftR,shiftL,unsafeShiftR) import Data.Word import Data.Hashable@@ -77,7 +77,6 @@ import Text.ParserCombinators.ReadPrec (prec,step) import qualified Data.Text.Read as TextRead import qualified Data.ByteString.Builder as Builder-import qualified Data.Text.IO as Text import qualified Data.ByteString.Unsafe as ByteString import qualified Data.Text.Read as Text (Reader) import qualified Data.Text.Lazy.Builder as TBuilder@@ -107,6 +106,7 @@ -- -- >>> import Test.QuickCheck (Arbitrary(..)) -- >>> import qualified Prelude as P+-- >>> import qualified Data.Text.IO as T -- >>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary } -- @@ -117,12 +117,16 @@ --   Additionally, it is used for the 'Show' and 'Read' instances --   of 'IPv4' to help keep things readable in GHCi. -----   >>> let ip = fromOctets 192 168 1 1---   >>> ip---   fromOctets 192 168 1 1---   >>> getIPv4 ip+--   >>> let addr = ipv4 192 168 1 1+--   >>> addr+--   ipv4 192 168 1 1+--   >>> getIPv4 addr --   3232235777 --+ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4+ipv4 = fromOctets++-- | An alias for the 'ipv4' smart constructor. fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4 fromOctets a b c d = fromOctets'   (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)@@ -211,12 +215,18 @@ public :: IPv4 -> Bool public = not . reserved +-- | Encode an 'IPv4' address to 'Text' using dot-decimal notation:+--+--   >>> T.putStrLn (encode (ipv4 192 168 2 47))+--   192.168.2.47 encode :: IPv4 -> Text encode = toDotDecimalText +-- | Decode an 'IPv4' address. decode :: Text -> Maybe IPv4 decode = decodeIPv4TextMaybe +-- | Encode an 'IPv4' address to a text 'TBuilder.Builder'. builder :: IPv4 -> TBuilder.Builder builder = toDotDecimalBuilder @@ -226,10 +236,7 @@ parser :: AT.Parser IPv4 parser = dotDecimalParser --- | This exists mostly for testing purposes.-print :: IPv4 -> IO ()-print = Text.putStrLn . encode-+-- | Encode an 'IPv4' address to a UTF-8 encoded 'ByteString'. encodeUtf8 :: IPv4 -> ByteString encodeUtf8 = toBSPreAllocated @@ -292,7 +299,7 @@   where   limitSize i =     if i > 255-      then fail "All octets in an ip address must be between 0 and 255"+      then fail "All octets in an ipv4 address must be between 0 and 255"       else return i  {- $string@@ -318,7 +325,7 @@  instance Show IPv4 where   showsPrec p addr = showParen (p > 10)-    $ showString "fromOctets "+    $ showString "ipv4 "     . showsPrec 11 a     . showChar ' '     . showsPrec 11 b@@ -331,7 +338,7 @@  instance Read IPv4 where   readPrec = parens $ prec 10 $ do-    Ident "fromOctets" <- lexP+    Ident "ipv4" <- lexP     a <- step readPrec     b <- step readPrec     c <- step readPrec@@ -395,7 +402,7 @@ instance ToJSONKey IPv4 where   toJSONKey = ToJSONKeyText     encode-    (\ip -> Aeson.unsafeToEncoding $ BB.char7 '"' <> builderUtf8 ip <> BB.char7 '"')+    (\addr -> Aeson.unsafeToEncoding $ BB.char7 '"' <> builderUtf8 addr <> BB.char7 '"')  #if MIN_VERSION_aeson(1,0,0)  instance FromJSONKey IPv4 where@@ -405,7 +412,7 @@ aesonParser :: Text -> Aeson.Parser IPv4 aesonParser t = case decode t of   Nothing -> fail "Could not parse IPv4 address"-  Just ip -> return ip+  Just addr -> return addr   ------------------------------------
src/Net/IPv4/Range.hs view
@@ -59,7 +59,8 @@ -- -- >>> import qualified Prelude as P -- >>> import qualified Net.IPv4 as I--- >>> import Net.IPv4 (fromOctets)+-- >>> import qualified Data.Text.IO as T+-- >>> import Net.IPv4 (fromOctets,ipv4) -- >>> import Test.QuickCheck (Arbitrary(..)) -- >>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary } -- >>> instance Arbitrary IPv4Range where { arbitrary = IPv4Range <$> arbitrary <*> arbitrary }@@ -108,7 +109,7 @@ -- | The inclusive lower bound of an 'IPv4Range'. This is conventionally --   understood to be the broadcast address of a subnet. For example: ----- >>> I.print $ lowerInclusive $ IPv4Range (fromOctets 10 10 1 160) 25+-- >>> T.putStrLn $ I.encode $ lowerInclusive $ IPv4Range (ipv4 10 10 1 160) 25 -- 10.10.1.128 -- -- Note that the lower bound of a normalized 'IPv4Range' is simply the@@ -149,7 +150,7 @@ -- | Convert an 'IPv4Range' into a list of the 'IPv4' addresses that --   are in it. -- >>> let r = IPv4Range (fromOctets 192 168 1 8) 30--- >>> mapM_ I.print (toList r)+-- >>> mapM_ (T.putStrLn . I.encode) (toList r) -- 192.168.1.8 -- 192.168.1.9 -- 192.168.1.10
src/Net/IPv6.hs view
@@ -6,6 +6,7 @@   ( -- * Types     IPv6(..)     -- * Convert+  , ipv6   , fromOctets   , fromWord16s   , toWord16s@@ -50,7 +51,7 @@  instance Show IPv6 where   showsPrec p addr = showParen (p > 10)-    $ showString "fromWord16s "+    $ showString "ipv6 "     . showHexWord16 a     . showChar ' '     . showHexWord16 b@@ -66,7 +67,6 @@     . showHexWord16 g     . showChar ' '     . showHexWord16 h-    . showChar ' '     where     (a,b,c,d,e,f,g,h) = toWord16s addr @@ -86,7 +86,7 @@  instance Read IPv6 where   readPrec = parens $ prec 10 $ do-    Ident "fromWord16s" <- lexP+    Ident "ipv6" <- lexP     a <- step readPrec     b <- step readPrec     c <- step readPrec@@ -128,11 +128,18 @@ --   feature for suppress zeroes in an 'IPv6' address, but it should be --   readable enough for hacking in GHCi. -----   >>> let ip = fromWord16s 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1---   >>> ip---   fromWord16s 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001---   >>> T.putStrLn (encode ip)+--   >>> let addr = ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1+--   >>> addr+--   ipv6 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001+--   >>> T.putStrLn (encode addr) --   3124::dead:cafe:ff:fe00:1+ipv6 :: +     Word16 -> Word16 -> Word16 -> Word16+  -> Word16 -> Word16 -> Word16 -> Word16+  -> IPv6+ipv6 = fromWord16s++-- | An alias for the 'ipv6' smart constructor. fromWord16s ::      Word16 -> Word16 -> Word16 -> Word16   -> Word16 -> Word16 -> Word16 -> Word16
src/Net/Mac.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE InstanceSigs #-}@@ -11,7 +12,8 @@ {-# OPTIONS_GHC -Wall #-} module Net.Mac   ( -- * Convert-    fromOctets+    mac+  , fromOctets   , toOctets     -- * Textual Conversion     -- ** Text@@ -19,8 +21,6 @@   , encodeWith   , decode   , decodeWith-  , decodeEither-  , decodeEitherWith   , builder   , parser   , parserWith@@ -32,7 +32,8 @@   , builderUtf8   , parserUtf8   , parserWithUtf8-  , parserLenientUtf8+    -- ** ByteString+  , decodeBytes     -- * Types   , Mac(..)   , MacCodec(..)@@ -40,7 +41,7 @@   ) where  import Data.Word-import Data.Bits ((.|.),unsafeShiftL,unsafeShiftR)+import Data.Bits ((.|.),unsafeShiftL,unsafeShiftR,(.&.)) import Data.Text (Text) import Data.Word (Word8) import Data.Word.Synthetic.Word12 (Word12)@@ -49,7 +50,11 @@ import Data.Aeson (FromJSON(..),ToJSON(..)) import Data.Hashable (Hashable) import GHC.Generics (Generic)-import Data.Char (ord)+import Data.Char (ord,chr)+import Text.Read (Read(..),Lexeme(Ident),lexP,parens)+import Text.ParserCombinators.ReadPrec (prec,step)+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as BU import qualified Data.ByteString.Builder as BB import qualified Data.Attoparsec.ByteString as ABW import qualified Data.Attoparsec.Text as AT@@ -65,12 +70,31 @@   ToJSONKeyFunction(..),FromJSONKeyFunction(..)) #endif +-- $setup+--+-- These are here to get doctest's property checking to work+--+-- >>> :set -XOverloadedStrings+-- >>> import Test.QuickCheck (Arbitrary(..))+-- >>> import qualified Data.Text.IO as T+-- >>> import qualified Data.ByteString.Char8 as BC+-- >>> instance Arbitrary Mac where { arbitrary = fmap (Mac . (0xFFFFFFFFFFFF .&.)) arbitrary }++-- | Construct a 'Mac' address from a 'Word64'. Only the lower+--   48 bits are used.+mac :: Word64 -> Mac+mac w = Mac (w .&. 0xFFFFFFFFFFFF)++-- | Create a 'Mac' address from six octets. fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Mac fromOctets a b c d e f = Mac $ unsafeWord48FromOctets   (fromIntegral a) (fromIntegral b) (fromIntegral c)   (fromIntegral d) (fromIntegral e) (fromIntegral f)-{-# INLINE fromOctets #-} +-- | Convert a 'Mac' address to the six octets that make it up.+--   This function and 'fromOctets' are inverses:+--+--   prop> m == (let (a,b,c,d,e,f) = toOctets m in fromOctets a b c d e f) toOctets :: Mac -> (Word8,Word8,Word8,Word8,Word8,Word8) toOctets (Mac w) =   ( fromIntegral $ unsafeShiftR w 40@@ -81,14 +105,41 @@   , fromIntegral w   ) +-- | Decode a 'Mac' address from a 'ByteString'. Each byte is interpreted+--   as an octet of the 'Mac' address. Consequently, 'ByteString's+--   of length 6 successfully decode, and all other 'ByteString's fail+--   to decode.+--+--   >>> decodeBytes (B.pack [0x6B,0x47,0x18,0x90,0x55,0xC3])+--   Just (mac 0x6b47189055c3)+--   >>> decodeBytes (B.replicate 6 0x3A)+--   Just (mac 0x3a3a3a3a3a3a)+--   >>> decodeBytes (B.replicate 7 0x3A)+--   Nothing+decodeBytes :: ByteString -> Maybe Mac+decodeBytes bs = if B.length bs == 6+  then Just $ fromOctets+    (BU.unsafeIndex bs 0)+    (BU.unsafeIndex bs 1)+    (BU.unsafeIndex bs 2)+    (BU.unsafeIndex bs 3)+    (BU.unsafeIndex bs 4)+    (BU.unsafeIndex bs 5)+  else Nothing+ rightToMaybe :: Either a b -> Maybe b rightToMaybe = either (const Nothing) Just  c2w :: Char -> Word8 c2w = fromIntegral . ord +-- | Encode a 'Mac' address lowercase hex, separating every two characters+--   with a colon:+--+--   >>> T.putStrLn (encode (Mac 0xA47F247AB423))+--   a4:7f:24:7a:b4:23 encode :: Mac -> Text-encode = encodeWith defCodec -- Internal.macToTextDefault w+encode = encodeWith defCodec  encodeWith :: MacCodec -> Mac -> Text encodeWith (MacCodec g u) m = case g of@@ -106,12 +157,6 @@     True -> TFB.run (fixedBuilderQuadruples TFB.word8HexFixedUpper) (Pair c m)     False -> TFB.run (fixedBuilderQuadruples TFB.word8HexFixedLower) (Pair c m) -decodeEitherWith :: MacCodec -> Text -> Either String Mac-decodeEitherWith codec t = AT.parseOnly (parserWith codec <* AT.endOfInput) t--decodeEither :: Text -> Either String Mac-decodeEither = decodeEitherWith defCodec- decode :: Text -> Maybe Mac decode = decodeWith defCodec @@ -264,11 +309,23 @@ word12At i (Mac w) = fromIntegral (unsafeShiftR w i) {-# INLINE word12At #-} +-- | Encode a 'Mac' address, as lowercase hexadecimal digits+--   separated by a colon:+--+--   >>> BC.putStrLn (encodeUtf8 (mac 0x64255A0F2C47))+--   64:25:5a:0f:2c:47 encodeUtf8 :: Mac -> ByteString encodeUtf8 = encodeWithUtf8 defCodec  -- | Lenient decoding of MAC address that accepts lowercase, uppercase, --   and any kind separator.+--+--   >>> decodeUtf8 "A2:DE:AD:BE:EF:67"+--   Just (mac 0xa2deadbeef67)+--   >>> decodeUtf8 "13-a2-fe-a4-17-96"+--   Just (mac 0x13a2fea41796)+--   >>> decodeUtf8 "0A42.47BA.67C2"+--   Just (mac 0x0a4247ba67c2) decodeUtf8 :: ByteString -> Maybe Mac decodeUtf8 = decodeLenientUtf8 @@ -283,10 +340,11 @@ builderUtf8 :: Mac -> BB.Builder builderUtf8 = BB.byteString . encodeUtf8 --- | Parser for a 'Mac' address using with a colon as the---   separator (i.e. @FA:43:B2:C0:0F:99@).+-- | Lenient parser for a 'Mac' address using any character+--   as the separator and accepting any digit grouping+--   (i.e. @FA:43:B2:C0:0F:99@ or @A065.647B.87FA@). parserUtf8 :: AB.Parser Mac-parserUtf8 = parserWithUtf8 defCodec+parserUtf8 = parserLenientUtf8  -- | Parser for a 'Mac' address using the provided settings. parserWithUtf8 :: MacCodec -> AB.Parser Mac@@ -479,9 +537,38 @@ -- | A 48-bit MAC address. Do not use the data constructor for this --   type. It is not considered part of the stable API, and it --   allows you to construct invalid MAC addresses.-newtype Mac = Mac { getMac :: Word64 }-  deriving (Eq,Ord,Show,Read,Generic)+newtype Mac = Mac Word64+  deriving (Eq,Ord,Generic) +-- What this instance does is to display the+-- inner contents in hexadecimal and pad them out to+-- 6 bytes in case it begins with several zeroes.+-- It also uses the smart constructor instead+-- of the actual constructor+instance Show Mac where+  showsPrec p (Mac addr) = showParen (p > 10)+    $ showString "mac "+    . showHexWord48 addr++instance Read Mac where+  readPrec = parens $ prec 10 $ do+    Ident "mac" <- lexP+    w <- step readPrec+    return (mac w)++showHexWord48 :: Word64 -> ShowS+showHexWord48 w = showString "0x" . go 11+  where+  go :: Int -> ShowS+  go !ix = if ix >= 0+    then showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) (ix * 4)) .&. 0xF)) . go (ix - 1)+    else id++nibbleToHex :: Word -> Char+nibbleToHex w+  | w < 10 = chr (fromIntegral (w + 48))+  | otherwise = chr (fromIntegral (w + 87))+ data MacCodec = MacCodec   { macCodecGrouping :: !MacGrouping   , macCodecUpperCase :: !Bool@@ -512,7 +599,7 @@ instance FromJSONKey Mac where   fromJSONKey = FromJSONKeyTextParser $ \t -> case decode t of     Nothing -> fail "invalid mac address"-    Just mac -> return mac+    Just addr -> return addr #endif  instance FromJSON Mac where
test/Doctests.hs view
@@ -4,5 +4,11 @@ main = doctest   [ "src/Net/IPv4.hs"   , "src/Net/IPv6.hs"+  , "src/Net/IP.hs"   , "src/Net/IPv4/Range.hs"+  , "src/Data/Word/Synthetic/Word12.hs"+  , "src/Data/Text/Builder/Common/Internal.hs"+  , "src/Data/Text/Builder/Fixed.hs"+  , "src/Data/ByteString/Builder/Fixed.hs"+  , "src/Net/Mac.hs"   ]
test/Naive.hs view
@@ -13,12 +13,9 @@ import Data.ByteString (ByteString) import Control.Monad.ST import Data.Text.Encoding (encodeUtf8)-import qualified Data.ByteString.Char8  as BC8-import qualified Data.ByteString        as ByteString-import qualified Data.ByteString.Unsafe as ByteString-import qualified Data.Text.Lazy         as LText-import qualified Data.Text.Lazy.Builder as TBuilder-import qualified Data.Text.Array        as TArray+import qualified Data.ByteString.Char8 as BC8+import qualified Data.ByteString as ByteString+import qualified Data.Text.Lazy as LText  encodeByteString :: IPv4 -> ByteString encodeByteString = encodeUtf8 . encodeText
test/Test.hs view
@@ -6,22 +6,25 @@ module Main (main) where  import Naive+import Data.Proxy (Proxy(..)) import Test.Framework (defaultMain, testGroup, Test) import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck (Arbitrary(..),oneof,Gen,elements)+import Test.QuickCheck (Arbitrary(..),Property,oneof,Gen,elements) import Test.HUnit (Assertion,(@?=)) import Numeric (showHex) import Test.QuickCheck.Property (failed,succeeded,Result(..)) import Data.Bifunctor+import Test.QuickCheck.Classes (jsonProps,showReadProps) import qualified Test.Framework.Providers.HUnit as PH -import Net.Types (IPv4(..),IPv4Range(..),Mac(..),IPv6(..),MacGrouping(..),MacCodec(..))+import Net.Types (IP,IPv4(..),IPv4Range(..),Mac(..),IPv6(..),MacGrouping(..),MacCodec(..)) import qualified Data.Text as Text import qualified Data.ByteString.Char8 as BC8 import qualified Net.IPv4 as IPv4 import qualified Net.IPv6 as IPv6 import qualified Net.IPv4.Range as IPv4Range import qualified Net.Mac as Mac+import qualified Net.IP as IP  import qualified Data.Attoparsec.Text as AT import qualified Data.Attoparsec.ByteString as AB@@ -92,8 +95,29 @@     , testProperty "Membership agrees with bounds" propMemberUpperLower     , testProperty "Range contains self" propRangeSelf     ]+  , testGroup "Instances"+    [ testGroup "IPv4"+      [ namedPropertiesToTest "JSON" (jsonProps (Proxy :: Proxy IPv4))+      , namedPropertiesToTest "Show Read" (showReadProps (Proxy :: Proxy IPv4))+      ]+    , testGroup "IPv6"+      [ namedPropertiesToTest "JSON" (jsonProps (Proxy :: Proxy IPv6))+      , namedPropertiesToTest "Show Read" (showReadProps (Proxy :: Proxy IPv6))+      ]+    , testGroup "IP"+      [ namedPropertiesToTest "JSON" (jsonProps (Proxy :: Proxy IP))+      , namedPropertiesToTest "Show Read" (showReadProps (Proxy :: Proxy IP))+      ]+    , testGroup "Mac"+      [ namedPropertiesToTest "JSON" (jsonProps (Proxy :: Proxy Mac))+      , namedPropertiesToTest "Show Read" (showReadProps (Proxy :: Proxy Mac))+      ]+    ]   ] +namedPropertiesToTest :: String -> [(String,Property)] -> Test+namedPropertiesToTest name pairs = testGroup name (map (uncurry testProperty) pairs)+ propEncodeDecodeIso :: Eq a => (a -> b) -> (b -> Maybe a) -> a -> Bool propEncodeDecodeIso f g a = g (f a) == Just a @@ -291,6 +315,18 @@   deriving instance Arbitrary IPv4++instance Arbitrary IPv6 where+  arbitrary = IPv6 <$> arbitrary <*> arbitrary++-- Half of the test cases generated are IPv6 mapped+-- IPv4 addresses.+instance Arbitrary IP where+  arbitrary = oneof+    [ IP.fromIPv4 <$> arbitrary+    , IP.fromIPv6 <$> arbitrary+    ]+ instance Arbitrary Mac where   arbitrary = Mac.fromOctets     <$> arbitrary