From 0f2e22bbae24d0d7dce350c985720e108acac884 Mon Sep 17 00:00:00 2001
From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com>
Date: Sat, 30 Sep 2023 18:54:46 +0200
Subject: [PATCH] Set up Literate with Documenter

---
 .github/workflows/DocPreviewCleanup.yml |  28 +++++
 .gitignore                              |   2 +
 Project.toml                            |   4 +-
 README.md                               |  94 +--------------
 docs/Manifest.toml                      | 147 +++++++++++++++++++++---
 docs/Project.toml                       |   1 +
 docs/make.jl                            |  23 +++-
 docs/src/animals.md                     | 105 +++++++++++++++++
 docs/src/api.md                         |  12 ++
 docs/src/index.md                       |  14 ---
 src/Interfaces.jl                       |   2 +
 test/animals.jl                         | 101 ++++++++++++++++
 test/runtests.jl                        |  62 ++--------
 13 files changed, 420 insertions(+), 175 deletions(-)
 create mode 100644 .github/workflows/DocPreviewCleanup.yml
 create mode 100644 docs/src/animals.md
 create mode 100644 docs/src/api.md
 delete mode 100644 docs/src/index.md
 create mode 100644 test/animals.jl

diff --git a/.github/workflows/DocPreviewCleanup.yml b/.github/workflows/DocPreviewCleanup.yml
new file mode 100644
index 0000000..365d56f
--- /dev/null
+++ b/.github/workflows/DocPreviewCleanup.yml
@@ -0,0 +1,28 @@
+name: Doc Preview Cleanup
+
+on:
+  pull_request:
+    types: [closed]
+
+jobs:
+  doc-preview-cleanup:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      - name: Checkout gh-pages branch
+        uses: actions/checkout@v3
+        with:
+          ref: gh-pages
+      - name: Delete preview and history + push changes
+        run: |
+            if [ -d "previews/PR$PRNUM" ]; then
+              git config user.name "Documenter.jl"
+              git config user.email "documenter@juliadocs.github.io"
+              git rm -rf "previews/PR$PRNUM"
+              git commit -m "delete preview"
+              git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree})
+              git push --force origin gh-pages-new:gh-pages
+            fi
+        env:
+            PRNUM: ${{ github.event.number }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 20fe29d..77d3e64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
+.vscode
 *.jl.*.cov
 *.jl.cov
 *.jl.mem
 /Manifest.toml
 /docs/build/
+/docs/src/index.md
\ No newline at end of file
diff --git a/Project.toml b/Project.toml
index 53e524a..b257b01 100644
--- a/Project.toml
+++ b/Project.toml
@@ -7,7 +7,9 @@ version = "0.1.0"
 julia = "1.6"
 
 [extras]
+Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
+Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
 Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 
 [targets]
-test = ["Test"]
+test = ["Aqua", "Documenter", "Test"]
diff --git a/README.md b/README.md
index 82841ef..8ecd352 100644
--- a/README.md
+++ b/README.md
@@ -17,100 +17,10 @@ specifically:
 - Tests: `@implements` declarations should be tested in package tests.
 - Docs: interface documentation can be inserted into trait documentation.
 
-__Note: the syntax here is likely to change over 2022 as we work out the best ways to define interfaces__
-
-## Example
-
-See the `IterationInterface` in BaseInterfaces.jl (a subpackage of this package)
+See the `IterationInterface` in BaseInterfaces.jl (a subpackage of this package) or the documentation
 for examples of `@interface` and `@implements`.
 
-But heres an examples using Animals, and the implementation of a Duck.
-
-First we define the interface methods, and a list of mandatory and
-optional properties of the interface, with conditions, using the `@interface`
-macro.
-
-The `@interface` macro takes two argumens
-1. The name of the interface, which should usully end with "Interface"
-2. The `mandatory` and `optional` components of the interface written as a `NamedTuple`,
-  with functions or tuple of functions that test them.
-
-```julia
-module Animals
-
-using Interfaces
-
-# Define the methods the interface uses
-function age end
-function walk end
-function talk end
-function dig end
-
-# Define the interface conditions
-@interface AnimalInterface (
-    mandatory = (;
-        age = (
-            x -> age(x) isa Real,
-            x -> age(x) >= 0,
-        )
-    ),
-    optional = (;
-        walk = x -> walk(x) isa String,
-        talk = x -> talk(x) isa Symbol,
-        dig = x -> dig(x) isa String,
-    ),
-)
-
-end
-```
-
-Now we can implement the AnimalInterface, for a Duck.
-
-The `@implements` macro takes two arguments.
-1. The interface type, with a tuple of optional components in
-  its first type parameter. 
-2. The type for which the interface is implemented.
-
-```julia
-using Interfaces
-
-# Define our Duck object
-struct Duck
-    age::Int
-end
-
-# And extend Animals methods for it
-Animals.age(duck::Duck) = duck.age
-Animals.walk(::Duck) = "waddle"
-Animals.talk(::Duck) = :quack
-
-# And define the interface
-@implements Animals.AnimalInterface{(:walk, :talk)} Duck
-```
-
-Now we have some methods we can use as traits, and test the interface with:
-
-```julia
-julia> Interfaces.implements(Animals.AnimalInterface{:walk}, Duck)
-true
-
-julia> Interfaces.implements(Animals.AnimalInterface{:dig}, Duck)
-false
-
-# We can test the interface
-julia> Interfaces.test(Animals.AnimalInterface, Duck, [Duck(1), Duck(2)])
-true
-
-# Or components of it:
-julia> Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Duck, [Duck(1), Duck(2)])
-true
-
-# Test another type
-struct Chicken end
-
-julia> Interfaces.implements(Animals.AnimalInterface, Chicken) 
-false
-```
+__Note: the syntax here is likely to change as we work out the best ways to define interfaces__
 
 If you think it should behave differently or there is better syntax,
 please make an issue.
diff --git a/docs/Manifest.toml b/docs/Manifest.toml
index b041896..cb4307f 100644
--- a/docs/Manifest.toml
+++ b/docs/Manifest.toml
@@ -1,13 +1,26 @@
 # This file is machine-generated - editing it directly is not advised
 
-julia_version = "1.7.0"
+julia_version = "1.10.0-beta2"
 manifest_format = "2.0"
+project_hash = "f02347b00ee559bf1c602a5e40edf44579c536cb"
 
 [[deps.ANSIColoredPrinters]]
 git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c"
 uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9"
 version = "0.0.1"
 
+[[deps.AbstractTrees]]
+git-tree-sha1 = "faa260e4cb5aba097a73fab382dd4b5819d8ec8c"
+uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
+version = "0.4.4"
+
+[[deps.ArgTools]]
+uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+version = "1.1.1"
+
+[[deps.Artifacts]]
+uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
+
 [[deps.Base64]]
 uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
 
@@ -17,21 +30,29 @@ uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
 
 [[deps.DocStringExtensions]]
 deps = ["LibGit2"]
-git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
+git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d"
 uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
-version = "0.8.6"
+version = "0.9.3"
 
 [[deps.Documenter]]
-deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"]
-git-tree-sha1 = "3eb46f2549b52a79206469cdc03ae2518ded8d31"
+deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "Dates", "DocStringExtensions", "Downloads", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "Test", "Unicode"]
+git-tree-sha1 = "f667b805e90d643aeb1ca70189827f991a7cc115"
 uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
-version = "0.27.18"
+version = "1.1.0"
+
+[[deps.Downloads]]
+deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
+uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+version = "1.6.0"
+
+[[deps.FileWatching]]
+uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
 
 [[deps.IOCapture]]
 deps = ["Logging", "Random"]
-git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
+git-tree-sha1 = "d75853a0bdbfb1ac815478bacd89cd27b550ace6"
 uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
-version = "0.2.2"
+version = "0.2.3"
 
 [[deps.InteractiveUtils]]
 deps = ["Markdown"]
@@ -44,14 +65,43 @@ version = "0.1.0"
 
 [[deps.JSON]]
 deps = ["Dates", "Mmap", "Parsers", "Unicode"]
-git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e"
+git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
 uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
-version = "0.21.3"
+version = "0.21.4"
+
+[[deps.LazilyInitializedFields]]
+git-tree-sha1 = "410fe4739a4b092f2ffe36fcb0dcc3ab12648ce1"
+uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf"
+version = "1.2.1"
+
+[[deps.LibCURL]]
+deps = ["LibCURL_jll", "MozillaCACerts_jll"]
+uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
+version = "0.6.4"
+
+[[deps.LibCURL_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
+uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
+version = "8.0.1+1"
 
 [[deps.LibGit2]]
 deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
 uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
 
+[[deps.LibSSH2_jll]]
+deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
+uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
+version = "1.11.0+1"
+
+[[deps.Libdl]]
+uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
+
+[[deps.Literate]]
+deps = ["Base64", "IOCapture", "JSON", "REPL"]
+git-tree-sha1 = "ae5703dde29228490f03cbd64c47be8131819485"
+uuid = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
+version = "2.15.0"
+
 [[deps.Logging]]
 uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
 
@@ -59,17 +109,50 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
 deps = ["Base64"]
 uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
 
+[[deps.MarkdownAST]]
+deps = ["AbstractTrees", "Markdown"]
+git-tree-sha1 = "e8513266815200c0c8f522d6d44ffb5e9b366ae4"
+uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391"
+version = "0.1.1"
+
+[[deps.MbedTLS_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
+version = "2.28.2+1"
+
 [[deps.Mmap]]
 uuid = "a63ad114-7e13-5084-954f-fe012c677804"
 
+[[deps.MozillaCACerts_jll]]
+uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
+version = "2023.1.10"
+
 [[deps.NetworkOptions]]
 uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
+version = "1.2.0"
 
 [[deps.Parsers]]
-deps = ["Dates"]
-git-tree-sha1 = "1285416549ccfcdf0c50d4997a94331e88d68413"
+deps = ["Dates", "PrecompileTools", "UUIDs"]
+git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851"
 uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
-version = "2.3.1"
+version = "2.7.2"
+
+[[deps.Pkg]]
+deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
+uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
+version = "1.10.0"
+
+[[deps.PrecompileTools]]
+deps = ["Preferences"]
+git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f"
+uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
+version = "1.2.0"
+
+[[deps.Preferences]]
+deps = ["TOML"]
+git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e"
+uuid = "21216c6a-2e73-6563-6e65-726566657250"
+version = "1.4.1"
 
 [[deps.Printf]]
 deps = ["Unicode"]
@@ -80,11 +163,18 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
 uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
 
 [[deps.Random]]
-deps = ["SHA", "Serialization"]
+deps = ["SHA"]
 uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
 
+[[deps.RegistryInstances]]
+deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"]
+git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51"
+uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3"
+version = "0.1.0"
+
 [[deps.SHA]]
 uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+version = "0.7.0"
 
 [[deps.Serialization]]
 uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
@@ -92,9 +182,38 @@ uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
 [[deps.Sockets]]
 uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
 
+[[deps.TOML]]
+deps = ["Dates"]
+uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+version = "1.0.3"
+
+[[deps.Tar]]
+deps = ["ArgTools", "SHA"]
+uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
+version = "1.10.0"
+
 [[deps.Test]]
 deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
 uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 
+[[deps.UUIDs]]
+deps = ["Random", "SHA"]
+uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
+
 [[deps.Unicode]]
 uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
+
+[[deps.Zlib_jll]]
+deps = ["Libdl"]
+uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
+version = "1.2.13+1"
+
+[[deps.nghttp2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
+version = "1.52.0+1"
+
+[[deps.p7zip_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
+version = "17.4.0+2"
diff --git a/docs/Project.toml b/docs/Project.toml
index ba162d1..b95a17c 100644
--- a/docs/Project.toml
+++ b/docs/Project.toml
@@ -1,3 +1,4 @@
 [deps]
 Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
 Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87"
+Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
diff --git a/docs/make.jl b/docs/make.jl
index 3a86881..ff699df 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -1,14 +1,28 @@
-using Interfaces
 using Documenter
+using Interfaces
+using Literate
 
 DocMeta.setdocmeta!(Interfaces, :DocTestSetup, :(using Interfaces); recursive=true)
 
+# Copy README
+cp(
+    joinpath(dirname(@__DIR__), "README.md"),
+    joinpath(@__DIR__, "src", "index.md"),
+    force=true,
+)
+
+# Copy test files
+Literate.markdown(
+    joinpath(dirname(@__DIR__), "test", "animals.jl"),
+    joinpath(@__DIR__, "src")
+)
+
 makedocs(;
     modules=[Interfaces],
     authors="Rafael Schouten <rafaelschouten@gmail.com>",
-    repo="https://github.com/rafaqz/Interfaces.jl/blob/{commit}{path}#{line}",
     sitename="Interfaces.jl",
     format=Documenter.HTML(;
+        repolink="https://github.com/rafaqz/Interfaces.jl",
         prettyurls=get(ENV, "CI", "false") == "true",
         canonical="https://rafaqz.github.io/Interfaces.jl",
         edit_link="main",
@@ -16,10 +30,15 @@ makedocs(;
     ),
     pages=[
         "Home" => "index.md",
+        "API reference" => "api.md",
+        "Examples" => [
+            "Single argument" => "animals.md",
+        ]
     ],
 )
 
 deploydocs(;
     repo="github.com/rafaqz/Interfaces.jl",
     devbranch="main",
+    push_preview=true,
 )
diff --git a/docs/src/animals.md b/docs/src/animals.md
new file mode 100644
index 0000000..9f2d9ac
--- /dev/null
+++ b/docs/src/animals.md
@@ -0,0 +1,105 @@
+```@meta
+EditURL = "../../test/animals.jl"
+```
+
+# Single-argument interface
+
+Here's an examples using animals, and the implementation of a duck.
+
+## Definition
+
+First we define the interface methods, and a list of mandatory and
+optional properties of the interface, with conditions, using the `@interface`
+macro.
+
+The `@interface` macro takes three arguments:
+1. The name of the interface, which should usully end with "Interface"
+2. The `mandatory` and `optional` components of the interface written as a `NamedTuple`, with functions or tuple of functions that test them.
+3. The interface docstring (the interface is represented as a type)
+
+````@example animals
+module Animals
+
+using Interfaces
+
+function age end
+function walk end
+function talk end
+function dig end
+
+@interface AnimalInterface (
+    mandatory = (
+        age = (
+             "all animals have a `Real` age" => x -> age(x) isa Real,
+             "all animals have an age larger than zero" => x -> age(x) >= 0,
+        ),
+    ),
+    optional = (
+        walk = "this animal can walk" => x -> walk(x) isa String,
+        talk = "this animal can talk" => x -> talk(x) isa Symbol,
+        dig = "this animal can dig" => x -> dig(x) isa String,
+    )
+) """
+Defines a generic interface for animals to do the things they do best.
+"""
+
+end;
+nothing #hide
+````
+
+## Implementation
+
+````@example animals
+using Interfaces
+````
+
+Now we implement the `AnimalInterface`, for a `Duck`.
+
+````@example animals
+struct Duck
+    age::Int
+end
+
+Animals.age(duck::Duck) = duck.age
+Animals.walk(::Duck) = "waddle"
+Animals.talk(::Duck) = :quack
+````
+
+We then test that the interface is correctly implemented
+
+````@example animals
+ducks = [Duck(1), Duck(2)]
+Interfaces.test(Animals.AnimalInterface, Duck, ducks)
+````
+
+Finally we declare it, so that the information can be used in static dispatch.
+
+The `@implements` macro takes two arguments.
+1. The interface type, with a tuple of optional components in its first type parameter.
+2. The type for which the interface is implemented.
+
+````@example animals
+@implements Animals.AnimalInterface{(:walk,:talk)} Duck
+````
+
+Now let's see what happens when the interface is not correctly implemented.
+
+````@example animals
+struct Chicken end
+````
+
+As expected, the tests fail
+
+````@example animals
+chickens = [Chicken()]
+try
+    Interfaces.test(Animals.AnimalInterface, Chicken, chickens)
+catch e
+    print(e)
+end
+````
+
+---
+
+*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*
+
diff --git a/docs/src/api.md b/docs/src/api.md
new file mode 100644
index 0000000..b617e75
--- /dev/null
+++ b/docs/src/api.md
@@ -0,0 +1,12 @@
+# API reference
+
+## Docstrings
+
+```@autodocs
+Modules = [Interfaces]
+```
+
+## Index
+
+```@index
+```
diff --git a/docs/src/index.md b/docs/src/index.md
deleted file mode 100644
index f85e27f..0000000
--- a/docs/src/index.md
+++ /dev/null
@@ -1,14 +0,0 @@
-```@meta
-CurrentModule = Interfaces
-```
-
-# Interfaces
-
-Documentation for [Interfaces](https://github.com/rafaqz/Interfaces.jl).
-
-```@index
-```
-
-```@autodocs
-Modules = [Interfaces]
-```
diff --git a/src/Interfaces.jl b/src/Interfaces.jl
index e1ef015..00fedd8 100644
--- a/src/Interfaces.jl
+++ b/src/Interfaces.jl
@@ -1,5 +1,7 @@
 module Interfaces
 
+@doc read(joinpath(dirname(@__DIR__), "README.md"), String) Interfaces
+
 export @implements, @interface
 
 include("interface.jl")
diff --git a/test/animals.jl b/test/animals.jl
new file mode 100644
index 0000000..868f8c3
--- /dev/null
+++ b/test/animals.jl
@@ -0,0 +1,101 @@
+# # Single-argument interface
+
+# Here's an examples using animals, and the implementation of a duck.
+
+# ## Definition
+
+#=
+First we define the interface methods, and a list of mandatory and
+optional properties of the interface, with conditions, using the `@interface`
+macro.
+
+The `@interface` macro takes three arguments:
+1. The name of the interface, which should usully end with "Interface"
+2. The `mandatory` and `optional` components of the interface written as a `NamedTuple`, with functions or tuple of functions that test them.
+3. The interface docstring (the interface is represented as a type)
+=#
+
+module Animals
+
+using Interfaces
+
+function age end
+function walk end
+function talk end
+function dig end
+
+@interface AnimalInterface (
+    mandatory = (
+        age = (
+             "all animals have a `Real` age" => x -> age(x) isa Real,
+             "all animals have an age larger than zero" => x -> age(x) >= 0,
+        ),
+    ),
+    optional = (
+        walk = "this animal can walk" => x -> walk(x) isa String,
+        talk = "this animal can talk" => x -> talk(x) isa Symbol,
+        dig = "this animal can dig" => x -> dig(x) isa String,
+    )
+) """
+Defines a generic interface for animals to do the things they do best.
+"""
+
+end;
+
+# ## Implementation
+
+using Interfaces
+
+# Now we implement the `AnimalInterface`, for a `Duck`.
+
+struct Duck
+    age::Int
+end
+
+Animals.age(duck::Duck) = duck.age
+Animals.walk(::Duck) = "waddle"
+Animals.talk(::Duck) = :quack
+
+# We then test that the interface is correctly implemented
+
+ducks = [Duck(1), Duck(2)]
+Interfaces.test(Animals.AnimalInterface, Duck, ducks)
+
+#=
+Finally we declare it, so that the information can be used in static dispatch.
+
+The `@implements` macro takes two arguments.
+1. The interface type, with a tuple of optional components in its first type parameter. 
+2. The type for which the interface is implemented.
+=#
+
+@implements Animals.AnimalInterface{(:walk,:talk)} Duck
+
+# Now let's see what happens when the interface is not correctly implemented.
+struct Chicken end
+
+# As expected, the tests fail
+chickens = [Chicken()]
+try
+    Interfaces.test(Animals.AnimalInterface, Chicken, chickens)
+catch e
+    print(e)
+end
+
+# The following tests are not included in the docs  #src
+
+using Test  #src
+
+@testset "Duck" begin  #src
+    @test Interfaces.implements(Animals.AnimalInterface, Duck) == true  #src
+    @test Interfaces.implements(Animals.AnimalInterface{:dig}, Duck) == false  #src
+    @test Interfaces.test(Animals.AnimalInterface, Duck, ducks) == true  #src
+    @test Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Duck, ducks) == true  #src
+    # TODO wrap errors somehow, or just let Invariants.jl handle that.  #src
+    @test_throws MethodError Interfaces.test(Animals.AnimalInterface{:dig}, Duck, ducks)  #src
+end  #src
+
+@testset "Chicken" begin  #src
+    @test Interfaces.implements(Animals.AnimalInterface{(:walk,:talk)}, Chicken()) == false  #src
+    @test_throws MethodError Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Chicken())  #src
+end  #src
diff --git a/test/runtests.jl b/test/runtests.jl
index 8c3c9da..943b02d 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,56 +1,14 @@
+using Aqua
+using Documenter
 using Interfaces
 using Test
 
-module Animals
-
-using Interfaces
-
-function age end
-function walk end
-function talk end
-function dig end
-
-@interface AnimalInterface (
-    mandatory = (
-        age = (
-             "all animals have a `Real` age" => x -> age(x) isa Real,
-             "all animals have an age larger than zero" => x -> age(x) >= 0,
-        ),
-    ),
-    optional = (
-        walk = "this animal can walk" => x -> walk(x) isa String,
-        talk = "this animal can talk" => x -> talk(x) isa Symbol,
-        dig = "this animal can dig" => x -> dig(x) isa String,
-    )
-) """
-Defines a generic interface for animals to do the things they do best.
-"""
-
-end
-
-struct Duck
-    age::Int
-end
-
-Animals.age(duck::Duck) = duck.age
-Animals.walk(::Duck) = "waddle"
-Animals.talk(::Duck) = :quack
-
-@implements dev Animals.AnimalInterface{(:walk,:talk)} Duck
-
-@testset "duck" begin
-    ducks = [Duck(1), Duck(2)]
-    @test Interfaces.implements(Animals.AnimalInterface, Duck) == true
-    @test Interfaces.implements(Animals.AnimalInterface{:dig}, Duck) == false
-    @test Interfaces.test(Animals.AnimalInterface, Duck, ducks) == true
-    @test Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Duck, ducks) == true
-    # TODO wrap errors somehow, or just let Invariants.jl handle that.
-    @test_throws MethodError Interfaces.test(Animals.AnimalInterface{:dig}, Duck, ducks)
-end
-
-struct Chicken end
-
-@testset "chicken" begin
-    @test Interfaces.implements(Animals.AnimalInterface{(:walk,:talk)}, Chicken()) == false
-    @test_throws MethodError Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Chicken())
+@testset verbose = true "Interfaces.jl" begin
+    doctest(Interfaces)
+    @testset "Aqua" begin
+        Aqua.test_all(Interfaces)
+    end
+    @testset "Animals" begin
+        include("animals.jl")
+    end
 end