diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for bisect-binary
+
+## 0.1
+
+* First version.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2017 Joachim Breitner
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,111 @@
+bisect-binary
+=============
+
+This little program helps with the following task:
+
+You need to analyse some binary file (e.g., the firmware for some
+microcontroller). You want to understand how it does certain things, but the
+file is big. One approach to help you in the process is to erase parts of the file
+(by overwriting it with binary zeros) and see if the file still does what it
+should be doing. If it does, then you know that the interesting parts are in
+another part of the file.
+
+`binary-binary` assists in this process by doing the book-keeping and zeroing
+out parts of the file.
+
+
+Screenshot
+----------
+
+The following screenshot shows `bisect-binary` in action, as it determins which
+part of a “Hello World” program written in Haskell are actually needed:
+It is taken from a [Asciinema demo], and edited to fit your screen.
+
+```
+$ bisect-binary -i hello -o hello-test -c 'chmod +x hello-test; timeout -k2s 2s ./hello-test
+…
+12.7% zeroes   131072B new   897000B left  [⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠛⠛⠛⠛⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⣤⣤⣤⡄] [YNRUQ?] n
+Segmentation fault
+12.7% zeroes   131072B new   897000B left  [⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠛⠛⠛⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⣤⣤⣤⡄] [YNRUQ?] n
+hello-test: internal error: stg_ap_p_ret
+(GHC version 8.0.2 for x86_64_unknown_linux)
+Please report this as a GHC bug:  http://www.haskell.org/ghc/reportabug
+Aborted
+12.7% zeroes   131072B new   897000B left  [⠀⠀⠀⠀⠀⠈⠛⠛⠛⠛⠛⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⣤⣤⣤⡄] [YNRUQ?] n
+Hello World!
+12.7% zeroes    65536B new   897000B left  [⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⣻⣿⣥⣤⣤⣤⣤⡄] [YNRUQ?] y
+Hello World!
+15.9% zeroes    65536B new   864232B left  [⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠛⢋⣤⣤⣤⣤⣤⣤⣤⣤⡄] [YNRUQ?] y
+```
+
+[Asciinema demo]: https://asciinema.org/a/7FcXeDU1BTa0SvynqSLezuaQ0?speed=2
+
+Installation
+------------
+
+Install Haskell, and run `cabal install bisect-binary`. You will find the
+binary in `~/.cabal/bin/bisect-binary`, and may want to move that to your
+`$PATH`.
+
+Usage
+-----
+
+Run `bisect-binary` with the input file, the output file, and optionally a
+command to run on every try (which could, for example, flash the
+microcontroller with the binary):
+
+```
+$ bisect-binary --help
+Binary file bisector
+
+Usage: bisect-binary (-i|--input FILE) (-o|--output FILE) [-c|--command COMMAND]
+  Fills a file with as much zeroes as possible
+
+Available options:
+  -h,--help                Show this help text
+  -i,--input FILE          input file
+  -o,--output FILE         output file
+  -c,--command COMMAND     command to run
+```
+
+Now `bisect-binary` will zero out parts of the file and ask you if the result is
+now good or bad, as seen in the screenshot above. Your options are:
+
+ * `Y`: Mark this example as good. It will remember which portions it zeroed out and from now
+        on only add to it
+ * `N`: Mark this example as bad. It will revert to the previous good version and try out
+        other parts of the file.
+ * `R`: Re-run the command.
+ * `U`: Undo the last marking.
+ * `Q`: Quit the program.
+
+Upon quitting, `bisect-binary` will leave `output` in the last known state.
+
+A persisent file name `output-file.bisect.log` is kept, so you can quit and
+restart at any time without losing your work.
+
+The status line tells you:
+ * how much of the file could be zeroed out sucessfully.
+ * how many bytes it is now adding to that.
+ * how many bytes of the file may be relevant for your task
+ * a status bar describing the progress:
+
+   * The top row visualizes the part of the file that we know we can zero out for sure.
+   * The bottom row visualizes the part that we are zeroing out next.
+
+   Two dots indicate _all_ bytes in this part, one dot indicates _some_ bytes.
+
+Contact
+-------
+
+Please reports bugs and missing features at the [GitHub bugtracker]. This is
+also where you can find the [source code].
+
+`bisect-binary` was written by [Joachim Breitner] and is licensed under a permissive MIT
+[license].
+
+[GitHub bugtracker]: https://github.com/nomeata/bisect-binary/issues
+[source code]: https://github.com/nomeata/bisect-binary
+[Joachim Breitner]: http://www.joachim-breitner.de/
+[license]: https://github.com/nomeata/gipeda/blob/LICENSE
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/bisect-binary.cabal b/bisect-binary.cabal
new file mode 100644
--- /dev/null
+++ b/bisect-binary.cabal
@@ -0,0 +1,51 @@
+name:                bisect-binary
+version:             0.1
+synopsis:            Determine relevant parts of binary data
+description:
+     This little program helps with the following task:
+     .
+     You need to analyse some binary file (e.g., the firmware for some
+     microcontroller). You want to understand how it does certain things, but the
+     file is big. One approach to help you in the process is to erase parts of the file
+     (by overwriting it with binary zeros) and see if the file still does what it
+     should be doing. If it does, then you know that the interesting parts are in
+     another part of the file.
+     .
+     `binary-binary` assists in this process by doing the book-keeping and zeroing
+     out parts of the file.
+homepage:            http://github.com/nomeata/bisect-binary
+license:             MIT
+license-file:        LICENSE
+author:              Joachim Breitner
+maintainer:          mail@joachim-breitner.de
+copyright:           2017 Joachim Breitner
+category:            Development
+build-type:          Simple
+extra-source-files:  ChangeLog.md, README.md
+cabal-version:       >=1.10
+tested-with:         GHC ==8.0.2, GHC ==8.2.2
+
+executable bisect-binary
+  main-is:             Main.hs
+  other-modules:       Intervals, Braille
+  other-extensions:    LambdaCase, DeriveGeneric
+  build-depends:       base >=4.9 && <4.12
+  build-depends:       bytestring
+  build-depends:       directory
+  build-depends:       filepath
+  build-depends:       optparse-applicative >=0.13 && <0.14
+  build-depends:       yaml >=0.8 && <0.9
+  build-depends:       haskeline >=0.7 && <0.8
+  build-depends:       time
+  build-depends:       hashable
+  build-depends:       integer-logarithms
+  build-depends:       terminal-size
+  build-depends:       process
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -Wall -Wno-name-shadowing
+
+source-repository head
+  type:     git
+  location: https://github.com/nomeata/bisect-binary
+
diff --git a/src/Braille.hs b/src/Braille.hs
new file mode 100644
--- /dev/null
+++ b/src/Braille.hs
@@ -0,0 +1,22 @@
+module Braille where
+
+import Data.Char
+import Data.Bits
+
+dotsToBrailleBar :: [Int] -> String
+dotsToBrailleBar [] = ""
+dotsToBrailleBar [x] = [dotsToBrailleChar x 0]
+dotsToBrailleBar (x:y:xs) = dotsToBrailleChar x y : dotsToBrailleBar xs
+
+dotsToBrailleChar :: Int -> Int -> Char
+dotsToBrailleChar n m =
+    chr $ 0x2800 + sum
+        [ bit 0 * fromEnum (testBit n 3)
+        , bit 1 * fromEnum (testBit n 2)
+        , bit 2 * fromEnum (testBit n 1)
+        , bit 3 * fromEnum (testBit m 3)
+        , bit 4 * fromEnum (testBit m 2)
+        , bit 5 * fromEnum (testBit m 1)
+        , bit 6 * fromEnum (testBit n 0)
+        , bit 7 * fromEnum (testBit m 0)
+        ]
diff --git a/src/Intervals.hs b/src/Intervals.hs
new file mode 100644
--- /dev/null
+++ b/src/Intervals.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE LambdaCase, DeriveGeneric #-}
+
+module Intervals where
+
+import qualified Data.ByteString.Lazy as BS
+import Text.Printf
+import Data.List hiding (union, intersect)
+import Data.Monoid ((<>))
+import Data.Yaml (ToJSON, FromJSON)
+import GHC.Generics (Generic)
+import Data.Function
+import Data.Int
+import Prelude hiding (subtract)
+
+type Offset = Int64
+
+data Interval = I { from :: Offset, to :: Offset }
+    deriving (Show,Generic)
+
+newtype Intervals = Intervals [Interval]
+    deriving (Show,Generic)
+
+mkInterval :: Offset -> Offset -> Intervals
+mkInterval f t | f < t     = Intervals [I f t]
+               | otherwise = Intervals []
+
+fullIntervals :: Offset -> Intervals
+fullIntervals len = mkInterval 0 len
+
+nullInterval :: Intervals
+nullInterval = Intervals []
+
+size :: Intervals -> Offset
+size (Intervals is) = sum [ t - f | I f t <- is ]
+
+isEmpty :: Intervals -> Bool
+isEmpty (Intervals is) = null is
+
+subSetOf :: Intervals -> Intervals -> Bool
+subSetOf a b = isEmpty (a `subtract` b)
+
+intersects :: Intervals -> Intervals -> Bool
+intersects a b = not $ isEmpty (a `intersect` b)
+
+intersect :: Intervals -> Intervals -> Intervals
+intersect (Intervals is1) (Intervals is2) = Intervals $ go is1 is2
+  where
+    go _ [] = []
+    go [] _ = []
+    go (i1:is1) (i2:is2)
+        -- reorder for symmetry
+        | to i1 < to i2 = go (i2:is2) (i1:is1)
+        -- disjoint
+        | from i1 >= to i2 = go (i1:is1) is2
+        -- overlapping
+        | otherwise = I f' (to i2) : go (i1 { from = to i2} : is1) is2
+            where f' = max (from i1) (from i2)
+
+
+union :: Intervals -> Intervals -> Intervals
+union (Intervals is1) (Intervals is2) = Intervals $ go is1 is2
+  where
+    go is [] = is
+    go [] is = is
+    go (i1:is1) (i2:is2)
+        -- reorder for symmetry
+        | to i1 < to i2 = go (i2:is2) (i1:is1)
+        -- disjoint
+        | from i1 > to i2 = i2 : go (i1:is1) is2
+        -- overlapping
+        | otherwise  = go (i1 { from = f'} : is1) is2
+            where f' = min (from i1) (from i2)
+
+subtract :: Intervals -> Intervals -> Intervals
+subtract (Intervals is1) (Intervals is2) = Intervals $ go is1 is2
+  where
+    go is [] = is
+    go [] _  = []
+    go (i1:is1) (i2:is2)
+        -- i2 past i1
+        | to i1 <= from i2 = i1 : go is1 (i2:is2)
+        -- i1 past i2
+        | to i2 <= from i1 = go (i1:is1) is2
+        -- i1 contained in i2
+        | from i2 <= from i1 , to i1 <= to i2 = go is1 (i2:is2)
+        -- i2 covers beginning of i1
+        | from i1 >= from i2 = i1 { from = to i2} : go is1 is2
+        -- i2 covers end of i1
+        | to i1 <= to i2     = i1 { to = from i2} : go is1 (i2:is2)
+        -- i2 in the middle of i1
+        | otherwise = I (from i1) (from i2) :
+                      I (to i2)   (to i1) : go is1 is2
+
+
+setZeros :: BS.ByteString -> Intervals -> BS.ByteString
+setZeros s (Intervals is) = foldl' go s is
+  where
+    go s (I f t) = prefix <> zeroes <> postfix
+      where
+        (tmp, postfix)     = BS.splitAt t s
+        (prefix, _discard) = BS.splitAt f tmp
+        zeroes = BS.replicate (t-f) 0
+
+ppInterval :: Interval -> String
+ppInterval (I f t) = printf "0x%04X-0x%04X" f t
+
+ppIntervals :: Intervals -> String
+ppIntervals (Intervals xs) = intercalate " " (map ppInterval xs)
+
+instance FromJSON Interval
+instance ToJSON Interval
+instance FromJSON Intervals
+instance ToJSON Intervals
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,291 @@
+{-# LANGUAGE LambdaCase, DeriveGeneric #-}
+
+import qualified Data.ByteString.Lazy as BS
+import Text.Printf
+import System.Directory
+import System.Exit
+import System.FilePath
+import Data.Foldable
+import Data.List hiding (union, intersect)
+import Options.Applicative
+import Control.Monad
+import Data.Monoid ((<>))
+import Data.Yaml (decodeFileEither, encodeFile, prettyPrintParseException, ToJSON, FromJSON)
+import Data.Bifunctor
+import GHC.Generics (Generic)
+import System.Console.Haskeline
+import Data.Function
+import Control.Monad.IO.Class
+import Data.Int
+import Data.Time
+import Data.Hashable
+import Math.NumberTheory.Logarithms
+import Data.Char
+import System.Process
+import Prelude hiding (subtract)
+import qualified System.Console.Terminal.Size
+
+import Intervals
+import Braille
+
+-- Core idea: The intervals to try to zero out
+
+intervalsToTry :: Offset -> [Intervals]
+intervalsToTry len =
+    [ mkInterval b (b+size)
+    | level <- toZero big
+    , let size = 2^level
+    , let shift = if odd (level - big) then size`div`2 else 0
+    , let upper = len - size - shift
+    , b <- [upper, upper - size .. 0]
+    ]
+  where big = integerLog2 (fromIntegral len)
+        toZero n = [n,(n-1)..0]
+
+-- Data storage
+
+data Log = Log
+    { lgFileSize :: Offset
+    , lgFileHash :: Int
+    , lgLogs :: [LogEntry]
+    }
+    deriving (Show,Generic)
+
+data LogEntry = LogEntry
+    { leDate :: ZonedTime
+    , leOk :: Bool
+    , leZeroed :: Intervals
+    }
+    deriving (Show,Generic)
+
+instance FromJSON Log
+instance ToJSON Log
+instance FromJSON LogEntry
+instance ToJSON LogEntry
+
+initLog :: BS.ByteString -> Log
+initLog input = Log
+    { lgFileSize = BS.length input
+    , lgFileHash = hash input
+    , lgLogs = []
+    }
+
+checkLog :: Log -> BS.ByteString -> InputT IO Log
+checkLog log input
+    | lgFileSize log /= BS.length input = do
+        outputStrLn $ printf "ERROR: Log input file size was %i, current input file is %i. Aborting" (lgFileSize log) (BS.length input)
+        liftIO $ exitFailure
+    | lgFileHash log /= hash input = do
+        outputStrLn "WARN: Log input file hash differns from actual input file hash."
+        outputStrLn "Do you want to continue?"
+        getInputChar "Y/N?" >>= \case
+            Just 'Y' -> return log
+            _ -> do
+                outputStrLn "Goodbye!"
+                liftIO $ exitFailure
+    | otherwise = do
+        outputStrLn $ printf "Loaded log file with %d previous attempts." (length (lgLogs log))
+        return log
+
+-- | Aggregate, minmal knowledge of which parts of the file are needed
+--
+data Digest = Digest
+    { conservative :: Intervals
+        -- ^ These bits can safely be zeroes
+    , needed :: [Intervals]
+        -- ^ At least one byte in each of these is needed.
+        --   (Invariant: These are disjoint from the conservative ones)
+    }
+    deriving (Show,Generic)
+
+digestLog :: Log -> Digest
+digestLog log = Digest conservative needed
+  where
+    (okEntries, badEntries) =
+        bimap (map leZeroed) (map leZeroed) $
+        partition leOk (lgLogs log)
+
+    conservative = foldl' union nullInterval okEntries
+    needed = prune (map (`subtract` conservative) badEntries)
+
+    -- could remove subsumed entries here
+    prune = id
+
+-- | It is pointless trying to zero an interval if it is a subset of what we
+-- already know can be zeroes, or if any failed case in the past shows that
+-- is a subset of this.
+pointless :: Intervals -> Digest -> Bool
+pointless try digest =
+    try `subSetOf` conservative digest ||
+    any (`subSetOf` try) (needed digest)
+
+-- The main code
+
+work :: FilePath -> FilePath -> Maybe String -> IO ()
+work inputFP outputFP commandM = runInputT defaultSettings $ do
+    input <- liftIO $ BS.readFile inputFP
+    let len = BS.length input
+
+    when (len == 0) $ do
+        outputStrLn $ printf "%s is empty." inputFP
+        liftIO $ exitSuccess
+
+    let logFile = outputFP <.> "bisect.log"
+
+    let revert digest = do
+            statusText (conservative digest) nullInterval True >>= outputStrLn
+            liftIO $ BS.writeFile outputFP $ setZeros input (conservative digest)
+            outputStrLn $ printf "Reverted %s to last known good output." outputFP
+
+        run_cmd = liftIO $ for_ commandM $ \cmd -> do
+            ph <- spawnCommand cmd
+            waitForProcess ph
+
+        -- A single run
+        test zeros = do
+            liftIO $ BS.writeFile outputFP $ setZeros input zeros
+            run_cmd
+
+        ask log msg = fix $ \loop -> do
+            getInputChar msg >>= pure . fmap toUpper >>= \case
+                Just 'Y' -> do
+                    return True
+                Just 'N' -> do
+                    return False
+                Just 'Q' -> do
+                    revert (digestLog log)
+                    liftIO $ exitSuccess
+                Just 'R' -> do
+                    run_cmd
+                    loop
+                Just 'U' -> do
+                    let log' = log { lgLogs = init (lgLogs log) }
+                    steps log'
+                    -- code smell
+                    liftIO $ exitSuccess
+                Just '?' -> do
+                    outputStrLn "Keys: Y: good. N: bad. R: rerun command. U: Undo. Q: Quit"
+                    loop
+                _ -> loop
+
+        statusText :: MonadIO m => Intervals -> Intervals -> Bool -> m String
+        statusText conservative toTry done = liftIO $ do
+            w <- getWidth
+            let barw = w - 57
+            let zeroPerc = 100 * fromIntegral (size conservative) / fromIntegral len
+            let nonZeroBytes = len - size conservative
+            return $ printf "%4.1f%% zeroes  %12s  %7dB left  %s%s"
+                (zeroPerc :: Double)
+                (if done then "" else printf "%7dB new" (size toTry))
+                nonZeroBytes
+                (if barw > 5 then braille barw len conservative toTry else "")
+                (if done then "" else " [YNRUQ?] ")
+
+
+        -- Single step of the main loop
+        step log toTry
+            | pointless toTry digest = return log
+            | otherwise = do
+                let zeros = conservative digest `union` toTry
+                test zeros
+                result <- statusText (conservative digest) toTry False >>= ask log
+                stamp <- liftIO $ getZonedTime
+                let entry = LogEntry { leDate = stamp, leOk = result, leZeroed = zeros }
+                let log' = log { lgLogs = lgLogs log ++ [entry] }
+                liftIO $ encodeFile logFile log'
+                return $ log'
+          where
+            digest = digestLog log
+
+        -- Main loop
+        steps log = do
+            foldM_ step log (intervalsToTry len)
+            outputStrLn $ printf "Done!"
+            -- TODO: What now?
+
+
+    -- Initialization
+    initialLog <- liftIO (doesFileExist logFile) >>= \case
+        False -> do
+            outputStrLn $ printf "Cannot find %s, starting from scratch." logFile
+            return (initLog input)
+        True -> do
+           liftIO (decodeFileEither logFile) >>= \case
+                Left error -> do
+                    outputStrLn $ printf "ERROR: Cannot parse %s:"
+                    outputStrLn $ prettyPrintParseException error
+                    liftIO $ exitFailure
+                Right log -> do
+                    outputStrLn $ printf "Loading log file %s." logFile
+                    checkLog log input
+
+    steps initialLog
+
+-- Argument handling
+
+main :: IO ()
+main = join . customExecParser (prefs showHelpOnError) $
+  info (helper <*> parser)
+  (  fullDesc
+  <> header "Binary file bisector"
+  <> progDesc "Fills a file with as much zeroes as possible"
+  )
+  where
+    parser :: Parser (IO ())
+    parser =
+      work
+        <$> strOption
+            (  long "input"
+            <> short 'i'
+            <> metavar "FILE"
+            <> help "input file"
+            )
+        <*> strOption
+            (  long "output"
+            <> short 'o'
+            <> metavar "FILE"
+            <> help "output file"
+            )
+        <*> optional (strOption
+            (  long "command"
+            <> short 'c'
+            <> metavar "COMMAND"
+            <> help "command to run"
+            ))
+
+-- Pretty progress bar using braille symbols:
+
+data Cover = NoCover | SomeCover | FullCover
+    deriving (Eq, Ord)
+
+braille :: Int -> Offset -> Intervals -> Intervals -> String
+braille width len lower upper = "[" ++ bar ++ "]"
+  where
+    bar = dotsToBrailleBar (map (toBits . go) parts)
+
+    parts :: [Intervals] -- (width-2)*2 intervals
+    parts = [mkInterval n (min (n + step) len) |  n <- [0,step..len-1] ]
+        where step = len `div` fromIntegral (2*(width - 2))
+
+    descOverlap :: Intervals -> Intervals -> Cover
+    descOverlap big small
+        | small `subSetOf`   big = FullCover
+        | small `intersects` big = SomeCover
+        | otherwise              = NoCover
+
+    go :: Intervals -> (Cover, Cover)
+    go i = (lower `descOverlap` i, upper `descOverlap` i)
+
+    toBits :: (Cover, Cover) -> Int
+    toBits (c1, c2) = sum
+        [ 1 * fromEnum (c1 >= SomeCover)
+        , 2 * fromEnum (c1 >= FullCover)
+        , 4 * fromEnum (c2 >= FullCover)
+        , 8 * fromEnum (c2 >= SomeCover)
+        ]
+
+
+getWidth :: IO Int
+getWidth = maybe 80 System.Console.Terminal.Size.width <$>
+    System.Console.Terminal.Size.size
+
