diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed2e129 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/cabal.config \ No newline at end of file diff --git a/README.md b/README.md index 474076c..04ae9d8 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -# hex-text \ No newline at end of file +# hex-text + +`hex-text` is a small library for converting between `ByteString`s and their representations as hexidecimal numbers encoded as `Text`. + +A `ByteString` is a list of bytes. A byte is a number between 0 and 255, represented by the `Word8` type. In a fixed-width hexidecimal representation, the lowest byte 0 is represented by the hex string `00`, and the greatest byte 255 is represented by the hex string `ff`. So, for example, the `ByteString` consisting of bytes \[ 1, 2, 3, 253, 254, 255 \] is represented as `010203fdfeff`. + +```haskell +λ> import Text.Hex (encodeHex) +λ> import Data.ByteString (pack) + +λ> (encodeHex . pack) [1, 2, 3, 253, 254, 255] +"010203fdfeff" +``` diff --git a/hex-text/.gitignore b/hex-text/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/hex-text/README.md b/hex-text/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/hex-text/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/hex-text/Setup.hs b/hex-text/Setup.hs new file mode 100644 index 0000000..e8ef27d --- /dev/null +++ b/hex-text/Setup.hs @@ -0,0 +1,3 @@ +import Distribution.Simple + +main = defaultMain diff --git a/hex-text/hex-text.cabal b/hex-text/hex-text.cabal new file mode 100644 index 0000000..445cadf --- /dev/null +++ b/hex-text/hex-text.cabal @@ -0,0 +1,49 @@ +name: hex-text +version: 0.1.0.0 +category: Text + +synopsis: ByteString-Text hexidecimal conversions + +description: Encode a ByteString as a hexidecimal Text value, + or decode hexidecimal Text as a ByteString. + +homepage: https://github.com/typeclasses/hex-text +bug-reports: https://github.com/typeclasses/hex-text/issues + +author: Chris Martin +maintainer: Chris Martin, Julie Moronuki + +copyright: 2018 Typeclass Consulting, LLC +license: Apache-2.0 +license-file: license.txt + +build-type: Simple +cabal-version: >= 1.10 + +extra-source-files: + README.md + +source-repository head + type: git + location: https://github.com/typeclasses/hex-text + +library + default-language: Haskell2010 + hs-source-dirs: src + build-depends: + base >=4.7 && <5 + , base16-bytestring + , bytestring + , text + exposed-modules: + Text.Hex + +test-suite doctest + default-language: Haskell2010 + hs-source-dirs: test + type: exitcode-stdio-1.0 + main-is: doctest.hs + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + base >=4.7 && <5 + , doctest diff --git a/hex-text/license.txt b/hex-text/license.txt new file mode 120000 index 0000000..0194195 --- /dev/null +++ b/hex-text/license.txt @@ -0,0 +1 @@ +../license.txt \ No newline at end of file diff --git a/hex-text/src/Text/Hex.hs b/hex-text/src/Text/Hex.hs new file mode 100644 index 0000000..4e59aa5 --- /dev/null +++ b/hex-text/src/Text/Hex.hs @@ -0,0 +1,134 @@ +module Text.Hex + ( + -- * Encoding and decoding + encodeHex + , decodeHex + , lazilyEncodeHex + + -- * Types + , Text + , LazyText + , ByteString + , LazyByteString + + -- * Type conversions + , lazyText + , strictText + , lazyByteString + , strictByteString + + ) where + +-- base16-bytestring +import qualified Data.ByteString.Base16 as Base16 +import qualified Data.ByteString.Base16.Lazy as LazyBase16 + +-- bytestring +import qualified Data.ByteString as ByteString +import qualified Data.ByteString.Lazy as LazyByteString + +-- text +import qualified Data.Text as Text +import qualified Data.Text.Encoding as Text +import qualified Data.Text.Lazy as LazyText +import qualified Data.Text.Lazy.Encoding as LazyText + +-- | Strict byte string + +type ByteString = + ByteString.ByteString + +-- | Lazy byte string + +type LazyByteString = + LazyByteString.ByteString + +-- | Strict text + +type Text = + Text.Text + +-- | Lazy text + +type LazyText = + LazyText.Text + +-- | +-- Encodes a byte string as hexidecimal number represented in text. +-- Each byte of the input is converted into two characters in the +-- resulting text. +-- +-- >>> (encodeHex . ByteString.singleton) 192 +-- "c0" +-- +-- >>> (encodeHex . ByteString.singleton) 168 +-- "a8" +-- +-- >>> (encodeHex . ByteString.pack) [192, 168, 1, 2] +-- "c0a80102" +-- +-- 'Text' produced by @encodeHex@ can be converted back to a +-- 'ByteString' using 'decodeHex'. +-- +-- The lazy variant of @encodeHex@ is 'lazilyEncodeHex'. + +encodeHex :: ByteString -> Text +encodeHex bs = + Text.decodeUtf8 (Base16.encode bs) + +-- | +-- Decodes hexidecimal text as a byte string. If the text contains +-- an even number of characters and consists only of the digits @0@ +-- through @9@ and letters @a@ through @f@, then the result is a +-- 'Just' value. +-- +-- >>> (fmap ByteString.unpack . decodeHex . Text.pack) "c0a80102" +-- Just [192,168,1,2] +-- +-- If the text contains an odd number of characters, decoding fails +-- and produces 'Nothing'. +-- +-- >>> (fmap ByteString.unpack . decodeHex . Text.pack) "c0a8010" +-- Nothing +-- +-- If the text contains non-hexidecimal characters, decoding fails +-- and produces 'Nothing'. +-- +-- >>> (fmap ByteString.unpack . decodeHex . Text.pack) "x0a80102" +-- Nothing +-- +-- The letters may be in either upper or lower case. This next +-- example therefore gives the same result as the first one above: +-- +-- >>> (fmap ByteString.unpack . decodeHex . Text.pack) "C0A80102" +-- Just [192,168,1,2] + +decodeHex :: Text -> Maybe ByteString +decodeHex txt = + let (x, remainder) = Base16.decode (Text.encodeUtf8 txt) + in if ByteString.null remainder then Just x else Nothing + +-- | +-- @lazilyEncodeHex@ is the lazy variant of 'encodeHex'. +-- +-- With laziness, it is possible to encode byte strings of +-- infinite length: +-- +-- >>> (LazyText.take 8 . lazilyEncodeHex . LazyByteString.pack . cycle) [1, 2, 3] +-- "01020301" + +lazilyEncodeHex :: LazyByteString -> LazyText +lazilyEncodeHex bs = + LazyText.decodeUtf8 (LazyBase16.encode bs) + +lazyText :: Text -> LazyText +lazyText = LazyText.fromStrict + +strictText :: LazyText -> Text +strictText = LazyText.toStrict + +lazyByteString :: ByteString -> LazyByteString +lazyByteString = LazyByteString.fromStrict + +strictByteString :: LazyByteString -> ByteString +strictByteString = LazyByteString.toStrict diff --git a/hex-text/test/doctest.hs b/hex-text/test/doctest.hs new file mode 100644 index 0000000..95b334d --- /dev/null +++ b/hex-text/test/doctest.hs @@ -0,0 +1,5 @@ +import Test.DocTest + +main :: IO () +main = + doctest [ "-isrc", "src/Text/Hex.hs" ] diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..bf863aa --- /dev/null +++ b/license.txt @@ -0,0 +1,13 @@ +Copyright 2018 Typeclass Consulting, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..7ffb561 --- /dev/null +++ b/stack.yaml @@ -0,0 +1,4 @@ +resolver: lts-10.6 + +packages: + - hex-text