diff --git a/readme.md b/readme.md
new file mode 100644
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,61 @@
+# text-replace
+
+Perform simple replacements in a text file, using a list of search/replace pairs.
+
+The search for strings to replace is performed left-to-right, preferring longer matches to shorter ones.
+
+All streams are assumed to be UTF-8 encoded.
+
+## Command-line options
+
+| Option | Description |
+| --- | --- |
+| `-h,--help` | Show help text |
+| `-i,--in-file FILEPATH` | Input file to read (optional, defaults to stdin) |
+| `-o,--out-file FILEPATH` | Output file to write (optional, defaults to stdout)
+| `-m,--mapping MAPPING` | A list of search/replace pairs, separated by any of the delimiters |
+| `-f,--map-file FILEPATH` | A file containing a list of search/replace pairs, separated by any of the delimiters |
+| `-d,--delimiter DELIMITER` | Add a delimiter that separates search/replace strings in `--mapping` and in the contents of `--map-file` |
+| `-n,--newline-delimiter` | Add newline as a delimiter |
+
+## Examples
+
+`text-replace` is useful for replacing characters with escape sequences:
+
+```
+$ echo "The (<&&>) operator"        \
+    | text-replace --delimiter " "  \
+                   --mapping "& &amp; > &gt; < &lt;"
+The (&lt;&amp;&amp;&gt;) operator
+```
+
+You can use it to swap strings. In the following example we replace `*` with `**` and vice versa:
+
+```
+$ echo "What *is* going on **here**?"  \
+    | text-replace --delimiter " "     \
+                   --mapping "* ** ** *"
+What **is** going on *here*?
+```
+
+You also have the option to read the input string and replacement list from files, and to write the output to a file:
+
+```
+$ cat input
+I am extremely apt to like Haskell once I develop sufficient
+aptitude with it.
+
+$ cat replacements
+apt -> likely
+aptitude -> ability
+like -> appreciate
+
+$ text-replace --map-file replacements  \
+               --in-file input          \
+               --out-file output        \
+               --delimiter " -> "       \
+               --newline-delimiter
+
+$ cat output
+I am extremely likely to appreciate Haskell once I develop
+sufficient ability with it.
diff --git a/test/properties.hs b/test/properties.hs
--- a/test/properties.hs
+++ b/test/properties.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE QuasiQuotes       #-}
-{-# LANGUAGE TemplateHaskell   #-}
-
 import Text.Replace
 
 -- base
diff --git a/text-replace.cabal b/text-replace.cabal
--- a/text-replace.cabal
+++ b/text-replace.cabal
@@ -1,14 +1,14 @@
-cabal-version: 2.2
+cabal-version: 3.0
 
 name: text-replace
-version: 0.1
+version: 0.1.0.1
 category: Text, Application
 synopsis: Simple text replacements from a list of search/replace pairs
 
 description:
     A library and a command-line application
     for simple string replacements in text files.
-    .
+
     The search for strings to replace is performed
     left-to-right, preferring longer matches
     to shorter ones.
@@ -26,48 +26,46 @@
 
 build-type: Simple
 
-tested-with: GHC == 8.2.2, GHC == 8.4.3,
-             GHC == 8.6.1, GHC == 8.8.1,
-             GHC == 8.10.2, GHC == 9.0.1
+extra-source-files: readme.md
 
 source-repository head
-  type: git
-  location: https://github.com/chris-martin/text-replace
-
-library
-  default-language: Haskell2010
-  hs-source-dirs: src
-  ghc-options: -Wall
+    type: git
+    location: https://github.com/chris-martin/text-replace
 
-  exposed-modules: Text.Replace
+common base
+    default-language: Haskell2010
+    ghc-options: -Wall
+    build-depends:
+        base ^>= 4.10 || ^>= 4.11 || ^>= 4.12 || ^>= 4.13 || ^>= 4.14 || ^>= 4.15 || ^>= 4.16
+      , text ^>= 1.2.2.2
 
-  build-depends: base ^>= 4.10 || ^>= 4.11 || ^>= 4.12 || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
-  build-depends: containers ^>= 0.5.10.2 || ^>= 0.6
-  build-depends: text ^>= 1.2.2.2
+library
+    import: base
+    hs-source-dirs: src
+    exposed-modules: Text.Replace
+    build-depends:
+        containers ^>= 0.5.10.2 || ^>= 0.6
 
 executable text-replace
-  default-language: Haskell2010
-  hs-source-dirs: app
-  main-is: text-replace.hs
-  ghc-options: -Wall
-
-  build-depends: text-replace
-
-  build-depends: base ^>= 4.10 || ^>= 4.11 || ^>= 4.12 || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
-  build-depends: parsec ^>= 3.1.13
-  build-depends: optparse-applicative ^>= 0.14.2 || ^>= 0.15 || ^>= 0.16
-  build-depends: text ^>= 1.2.2.2
+    import: base
+    hs-source-dirs: app
+    main-is: text-replace.hs
+    build-depends:
+        optparse-applicative ^>= 0.14.2 || ^>= 0.15 || ^>= 0.16
+      , parsec ^>= 3.1.13
+      , text-replace
 
 test-suite properties
-  default-language: Haskell2010
-  type: exitcode-stdio-1.0
-  main-is: properties.hs
-  hs-source-dirs: test
-  ghc-options: -Wall -threaded
-
-  build-depends: text-replace
-
-  build-depends: base ^>= 4.10 || ^>= 4.11 || ^>= 4.12 || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
-  build-depends: hedgehog ^>= 0.5.3 || ^>= 0.6 || ^>= 1.0
-  build-depends: neat-interpolation ^>= 0.5
-  build-depends: text ^>= 1.2.2.2
+    import: base
+    type: exitcode-stdio-1.0
+    main-is: properties.hs
+    hs-source-dirs: test
+    ghc-options: -threaded
+    default-extensions:
+        OverloadedStrings
+        QuasiQuotes
+        TemplateHaskell
+    build-depends:
+        hedgehog ^>= 0.5.3 || ^>= 0.6 || ^>= 1.0
+      , neat-interpolation ^>= 0.5
+      , text-replace
