diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 530d2af0..4b2b034a 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -152,6 +152,15 @@ jobs:
with:
package-import-name: "columbo"
+ validate-doc-examples:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Validate docs
+ run: ./docker/validate_docs.sh
+
build-docs:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 0e99cb5a..55be492f 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -23,6 +23,11 @@ services:
<<: *devbox
command: docker/run_tests.sh --format-code
+ # test the documentation examples to make sure they can be run with Python
+ validateDocExamples:
+ <<: *devbox
+ command: docker/validate_docs.sh
+
bump:
<<: *devbox
<<: *mount-app-and-user-git-config
diff --git a/docker/run_tests.sh b/docker/run_tests.sh
index 14076475..895d80ff 100755
--- a/docker/run_tests.sh
+++ b/docker/run_tests.sh
@@ -41,10 +41,10 @@ echo "Running MyPy..."
mypy columbo tests
echo "Running black..."
-black ${BLACK_ACTION} columbo tests
+black ${BLACK_ACTION} columbo tests docs/examples/
echo "Running iSort..."
-isort ${ISORT_ACTION} columbo tests
+isort ${ISORT_ACTION} columbo tests docs/examples/
echo "Running flake8..."
flake8 columbo tests
diff --git a/docker/validate_docs.sh b/docker/validate_docs.sh
new file mode 100755
index 00000000..db6e3725
--- /dev/null
+++ b/docker/validate_docs.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+# this script will exit if one of the example files does not execute properly with python
+
+# this is set so that the script fails if one example fails to be executed properly
+set -e
+
+# install columbo
+pip install . -q;
+
+for filename in docs/examples/*.py; do
+ # provide default answers if example python file asks for input
+ yes "" | python ${filename};
+done
+
+# this will only get printed if all examples finish succesfully
+printf "\n\n\nAll of the documentation examples can be run!";
diff --git a/docs/development-guide.md b/docs/development-guide.md
index 87153901..a5d5847f 100644
--- a/docs/development-guide.md
+++ b/docs/development-guide.md
@@ -62,7 +62,7 @@ coverage rather than decreasing it.
We use [pytest][pytest-docs] as our testing framework.
-#### Stages
+### Stages
To customize / override a specific testing stage, please read the documentation specific to that tool:
@@ -73,7 +73,22 @@ To customize / override a specific testing stage, please read the documentation
4. [Flake8][flake8-docs]
5. [Bandit][bandit-docs]
-### `setup.py`
+## Validate Examples Used in Documentation
+
+In the `docs/examples/` directory of this repo, there are example Python scripts which we use in our documentation.
+You can validate that the examples run properly using:
+
+```bash
+docker-compose run --rm validateDocExamples
+```
+
+If the script fails (exits with a non-zero status), it will output information about the file that we need to fix.
+
+Note that this script will output some content in the shell every time it runs.
+Just because the script outputs content to the shell does *not* mean it has failed;
+as long as the script finishes successfully (exits with a zero status), there are no problems we need to address.
+
+## `setup.py`
Setuptools is used to packaging the library.
diff --git a/docs/examples/README.md b/docs/examples/README.md
new file mode 100644
index 00000000..ea266df8
--- /dev/null
+++ b/docs/examples/README.md
@@ -0,0 +1,5 @@
+# Examples
+
+These are the examples used in the [documentation][documentation].
+
+[documentation]: https://wayfair-incubator.github.io/columbo/
diff --git a/examples/branching_story.py b/docs/examples/branching_story.py
similarity index 93%
rename from examples/branching_story.py
rename to docs/examples/branching_story.py
index 546134d7..0c351240 100644
--- a/examples/branching_story.py
+++ b/docs/examples/branching_story.py
@@ -1,11 +1,14 @@
import columbo
+
def went_left(answers: columbo.Answers) -> bool:
return answers["which_door"] == "left"
+
def went_right(answers: columbo.Answers) -> bool:
return answers["which_door"] == "right"
+
def outcome(answers: columbo.Answers) -> str:
if answers.get("has_key", False):
return "You try the the key on the lock. With a little jiggling, it finally opens. You open the gate and leave."
@@ -15,7 +18,7 @@ def outcome(answers: columbo.Answers) -> str:
"Unable to open the gate yourself, you yell for help. A farmer in the nearby field hears you. "
"He reaches into his pocket and pulls out a key to unlock the gate and open it. "
"As you walk through the archway he says, "
- "\"What I don't understand is how you got in there. This is the only key.\""
+ '"What I don\'t understand is how you got in there. This is the only key."'
)
@@ -28,7 +31,7 @@ def outcome(answers: columbo.Answers) -> str:
"which_door",
"Which door do you walk through?",
options=["left", "right"],
- default="left"
+ default="left",
),
columbo.Confirm(
"has_key",
@@ -36,7 +39,7 @@ def outcome(answers: columbo.Answers) -> str:
"As you walk down the hallway, there is a small side table with a key on it.\n"
"Do you pick up the key before going through the door at the other end?",
should_ask=went_left,
- default=True
+ default=True,
),
columbo.Confirm(
"has_hammer",
@@ -44,7 +47,7 @@ def outcome(answers: columbo.Answers) -> str:
"The room has a single door on the opposite side of the room and a work bench with a hammer on it.\n"
"Do you pick up the hammer before going through the door at the other side?",
should_ask=went_right,
- default=True
+ default=True,
),
columbo.Echo(
"You enter a small courtyard with high walls. There is an archway that would allow you to go free, "
diff --git a/docs/examples/index_command_line_answers.py b/docs/examples/index_command_line_answers.py
new file mode 100644
index 00000000..7928342f
--- /dev/null
+++ b/docs/examples/index_command_line_answers.py
@@ -0,0 +1,33 @@
+import columbo
+
+interactions = [
+ columbo.Echo("Welcome to the Columbo example"),
+ columbo.Acknowledge("Press enter to start"),
+ columbo.BasicQuestion(
+ "user",
+ "What is your name?",
+ default="Patrick",
+ ),
+ columbo.BasicQuestion(
+ "user_email",
+ lambda answers: f"""What email address should be used to contact {answers["user"]}?""",
+ default="me@example.com",
+ ),
+ columbo.Choice(
+ "mood",
+ "How are you feeling today?",
+ options=["happy", "sad", "sleepy", "confused"],
+ default="happy",
+ ),
+ columbo.Confirm("likes_dogs", "Do you like dogs?", default=True),
+]
+
+answers = columbo.parse_args(
+ interactions,
+ args=[
+ "--user-email",
+ "patrick@example.com",
+ "--likes-dogs",
+ ],
+)
+print(answers)
diff --git a/docs/examples/index_user_prompts.py b/docs/examples/index_user_prompts.py
new file mode 100644
index 00000000..ee85f6d2
--- /dev/null
+++ b/docs/examples/index_user_prompts.py
@@ -0,0 +1,26 @@
+import columbo
+
+interactions = [
+ columbo.Echo("Welcome to the Columbo example"),
+ columbo.Acknowledge("Press enter to start"),
+ columbo.BasicQuestion(
+ "user",
+ "What is your name?",
+ default="Patrick",
+ ),
+ columbo.BasicQuestion(
+ "user_email",
+ lambda answers: f"""What email address should be used to contact {answers["user"]}?""",
+ default="me@example.com",
+ ),
+ columbo.Choice(
+ "mood",
+ "How are you feeling today?",
+ options=["happy", "sad", "sleepy", "confused"],
+ default="happy",
+ ),
+ columbo.Confirm("likes_dogs", "Do you like dogs?", default=True),
+]
+
+answers = columbo.get_answers(interactions)
+print(answers)
diff --git a/examples/optional_questions.py b/docs/examples/optional_questions.py
similarity index 65%
rename from examples/optional_questions.py
rename to docs/examples/optional_questions.py
index b983cf9a..903dc8b4 100644
--- a/examples/optional_questions.py
+++ b/docs/examples/optional_questions.py
@@ -1,7 +1,7 @@
import columbo
-def does_user_have_a_dog(answers: columbo.Answers) -> bool:
+def user_has_dog(answers: columbo.Answers) -> bool:
return answers["has_dog"]
@@ -10,15 +10,15 @@ def does_user_have_a_dog(answers: columbo.Answers) -> bool:
columbo.BasicQuestion(
"dog_name",
"What is the name of the dog?",
- should_ask=does_user_have_a_dog,
- default="Kaylee"
+ should_ask=user_has_dog,
+ default="Kaylee",
),
columbo.BasicQuestion(
"dog_breed",
"What is the breed of the dog?",
- should_ask=does_user_have_a_dog,
- default="Basset Hound"
- )
+ should_ask=user_has_dog,
+ default="Basset Hound",
+ ),
]
user_answers = columbo.get_answers(interactions)
diff --git a/docs/examples/validators.py b/docs/examples/validators.py
new file mode 100644
index 00000000..83db5ffc
--- /dev/null
+++ b/docs/examples/validators.py
@@ -0,0 +1,25 @@
+import re
+from typing import List
+
+import columbo
+
+
+def is_email_address(value: str, _: columbo.Answers) -> columbo.ValidationResponse:
+ if not re.match(r"^\w+@\w+", value):
+ error_message = f"{value} is not a valid email address"
+ return columbo.ValidationFailure(error=error_message)
+
+ return columbo.ValidationSuccess()
+
+
+interactions: List[columbo.Interaction] = [
+ columbo.BasicQuestion(
+ "user_email_address",
+ "What email address should be used to contact you?",
+ default="me@example.com",
+ validator=is_email_address,
+ )
+]
+
+user_answers = columbo.get_answers(interactions)
+print(user_answers)
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 53ca1584..401df672 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -79,34 +79,7 @@ printed next.
This is the example that appears on the [main page][docs-main] of the documentation.
```python linenums="1"
-import columbo
-
-interactions = [
- columbo.Echo("Welcome to the Columbo example"),
- columbo.Acknowledge(
- "Press enter to start"
- ),
- columbo.BasicQuestion(
- "user",
- "What is your name?",
- default="Patrick",
- ),
- columbo.BasicQuestion(
- "user_email",
- lambda answers: f"""What email address should be used to contact {answers["user"]}?""",
- default="me@example.com"
- ),
- columbo.Choice(
- "mood",
- "How are you feeling today?",
- options=["happy", "sad", "sleepy", "confused"],
- default="happy",
- ),
- columbo.Confirm("likes_dogs", "Do you like dogs?", default=True),
-]
-
-answers = columbo.get_answers(interactions)
-print(answers)
+{!examples/index_user_prompts.py!}
```
* Line 1: Import the `columbo` module.
@@ -152,37 +125,7 @@ print(answers)
The full example
```python linenums="1" hl_lines="27-30"
-import columbo
-
-interactions = [
- columbo.Echo("Welcome to the Columbo example"),
- columbo.Acknowledge(
- "Press enter to start"
- ),
- columbo.BasicQuestion(
- "user",
- "What is your name?",
- default="Patrick",
- ),
- columbo.BasicQuestion(
- "user_email",
- lambda answers: f"""What email address should be used to contact {answers["user"]}?""",
- default="me@example.com"
- ),
- columbo.Choice(
- "mood",
- "How are you feeling today?",
- options=["happy", "sad", "sleepy", "confused"],
- default="happy",
- ),
- columbo.Confirm("likes_dogs", "Do you like dogs?", default=True),
-]
-
-answers = columbo.parse_args(interactions, args=[
- "--user-email", "patrick@example.com",
- "--likes-dogs",
-])
-print(answers)
+{!examples/index_command_line_answers.py!}
```
diff --git a/docs/index.md b/docs/index.md
index f3c0d651..3c515c3f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -28,34 +28,7 @@ The primary use of `columbo` is to define a sequence of interactions that are us
using a terminal. Below is a sample which shows some ways this can be used.
```python
-import columbo
-
-interactions = [
- columbo.Echo("Welcome to the Columbo example"),
- columbo.Acknowledge(
- "Press enter to start"
- ),
- columbo.BasicQuestion(
- "user",
- "What is your name?",
- default="Patrick",
- ),
- columbo.BasicQuestion(
- "user_email",
- lambda answers: f"""What email address should be used to contact {answers["user"]}?""",
- default="me@example.com"
- ),
- columbo.Choice(
- "mood",
- "How are you feeling today?",
- options=["happy", "sad", "sleepy", "confused"],
- default="happy",
- ),
- columbo.Confirm("likes_dogs", "Do you like dogs?", default=True),
-]
-
-answers = columbo.get_answers(interactions)
-print(answers)
+{!examples/index_user_prompts.py!}
```
Below shows the output when the user accepts the default values for most of the questions. The user provides a different
@@ -95,34 +68,7 @@ $ python columbo_example.py --user-email patrick@example.com --likes-dogs
The full example
```python
-import columbo
-
-interactions = [
- columbo.Echo("Welcome to the Columbo example"),
- columbo.Acknowledge(
- "Press enter to start"
- ),
- columbo.BasicQuestion(
- "user",
- "What is your name?",
- default="Patrick",
- ),
- columbo.BasicQuestion(
- "user_email",
- lambda answers: f"""What email address should be used to contact {answers["user"]}?""",
- default="me@example.com"
- ),
- columbo.Choice(
- "mood",
- "How are you feeling today?",
- options=["happy", "sad", "sleepy", "confused"],
- default="happy",
- ),
- columbo.Confirm("likes_dogs", "Do you like dogs?", default=True),
-]
-
-answers = columbo.parse_args(interactions)
-print(answers)
+{!examples/index_command_line_answers.py!}
```
diff --git a/docs/usage-guide/optional-questions-and-branching.md b/docs/usage-guide/optional-questions-and-branching.md
index 749eeedc..9808e4c2 100644
--- a/docs/usage-guide/optional-questions-and-branching.md
+++ b/docs/usage-guide/optional-questions-and-branching.md
@@ -18,29 +18,7 @@ The following is a basic example that has two optional questions that are not as
question.
```python
-import columbo
-
-def user_has_dog(answers: columbo.Answers) -> bool:
- return answers["has_dog"]
-
-interactions = [
- columbo.Confirm("has_dog", "Do you have a dog?", default=True),
- columbo.BasicQuestion(
- "dog_name",
- "What is the name of the dog?",
- should_ask=user_has_dog,
- default="Kaylee"
- ),
- columbo.BasicQuestion(
- "dog_breed",
- "What is the breed of the dog?",
- should_ask=user_has_dog,
- default="Basset Hound"
- )
-]
-
-user_answers = columbo.get_answers(interactions)
-print(user_answers)
+{!examples/optional_questions.py!}
```
If the user accepts the default answers for each of these questions, the output will be:
@@ -69,63 +47,7 @@ isn't different from the optional questions [demonstrated above](#optional-quest
branching paths by supplying different `should_ask` values that will never both evaluate to `True`.
```python
-import columbo
-
-def went_left(answers: columbo.Answers) -> bool:
- return answers["which_door"] == "left"
-
-def went_right(answers: columbo.Answers) -> bool:
- return answers["which_door"] == "right"
-
-def outcome(answers: columbo.Answers) -> str:
- if answers.get("has_key", False):
- return "You try the the key on the lock. With a little jiggling, it finally opens. You open the gate and leave."
- if answers.get("has_hammer", False):
- return "You hit the lock with the hammer and it falls to the ground. You open the gate and leave."
- return (
- "Unable to open the gate yourself, you yell for help. A farmer in the nearby field hears you. "
- "He reaches into his pocket and pulls out a key to unlock the gate and open it. "
- "As you walk through the archway he says, "
- "\"What I don't understand is how you got in there. This is the only key.\""
- )
-
-
-interactions = [
- columbo.Echo(
- "You wake up in a room that you do not recognize. "
- "In the dim light, you can see a large door to the left and a small door to the right."
- ),
- columbo.Choice(
- "which_door",
- "Which door do you walk through?",
- options=["left", "right"],
- default="left"
- ),
- columbo.Confirm(
- "has_key",
- "You step into a short hallway and the door closes behind you, refusing to open again. "
- "As you walk down the hallway, there is a small side table with a key on it.\n"
- "Do you pick up the key before going through the door at the other end?",
- should_ask=went_left,
- default=True
- ),
- columbo.Confirm(
- "has_hammer",
- "You step into smaller room and the door closes behind, refusing to open again. "
- "The room has a single door on the opposite side of the room and a work bench with a hammer on it.\n"
- "Do you pick up the hammer before going through the door at the other side?",
- should_ask=went_right,
- default=True
- ),
- columbo.Echo(
- "You enter a small courtyard with high walls. There is an archway that would allow you to go free, "
- "but the gate is locked."
- ),
- columbo.Echo(outcome),
-]
-
-user_answers = columbo.get_answers(interactions)
-print(user_answers)
+{!examples/branching_story.py!}
```
The import thing to note in the example above is that the `Answers` dictionary can have a key-value pair for
diff --git a/docs/usage-guide/validators.md b/docs/usage-guide/validators.md
index 3c3f609f..1308c33a 100644
--- a/docs/usage-guide/validators.md
+++ b/docs/usage-guide/validators.md
@@ -41,29 +41,7 @@ word character on each side then the response is invalid and the user will have
enter an email address again (hopefully a valid one this time).
```python
-from typing import List
-import re
-
-import columbo
-
-def is_email_address(value: str, _: columbo.Answers) -> columbo.ValidationResponse:
- if not re.match(r"^\w+@\w+", value):
- error_message = f"{value} is not a valid email address"
- return columbo.ValidationFailure(error=error_message)
-
- return columbo.ValidationSuccess()
-
-interactions: List[columbo.Interaction] = [
- columbo.BasicQuestion(
- "user_email_address",
- "What email address should be used to contact you?",
- default="me@example.com",
- validator=is_email_address,
- )
-]
-
-user_answers = columbo.get_answers(interactions)
-print(user_answers)
+{!examples/validators.py!}
```
[^1]:
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index 5a3d11a8..00000000
--- a/examples/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# Examples
-
-A collection of examples that can be run to see how `columbo` works. If it not clear what a specific example is doing,
-please read the [documentation][documentation]. These examples all appear there with detailed explanations.
-
-[documentation]: https://wayfair-incubator.github.io/columbo/
diff --git a/examples/basic.py b/examples/basic.py
deleted file mode 100644
index 3c69b4ec..00000000
--- a/examples/basic.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import re
-import sys
-from typing import List, Optional, cast
-
-from columbo import (
- Acknowledge,
- Answers,
- BasicQuestion,
- Choice,
- Confirm,
- Echo,
- Interaction,
- get_answers,
- parse_args,
-)
-
-
-def user_to_email(answers: Answers) -> str:
- return f"""{cast(str, answers["user"]).lower().replace(' ', '')}@example.com"""
-
-
-def is_email(value: str, _: Answers) -> Optional[str]:
- error_message: Optional[str] = None
-
- if not re.match(r"^\w+@\w+", value):
- error_message = f"{value} is not a valid email"
-
- return error_message
-
-
-interactions: List[Interaction] = [
- Echo("Welcome to the Columbo example"),
- Acknowledge(
- "Press enter to start"
- ),
- BasicQuestion(
- "user",
- "What is your name?",
- default="Patrick",
- cli_help="Name of the user providing answers",
- ),
- BasicQuestion(
- "user_email",
- lambda answers: f"""What email address should be used to contact {answers["user"]}?""",
- default=user_to_email,
- validator=is_email,
- ),
- Choice(
- "mood",
- "How are you feeling today?",
- options=["happy", "sad", "sleepy", "confused"],
- default="happy",
- cli_help="The mood of the user.",
- ),
- Confirm("likes_dogs", "Do you like dogs?", default=True),
-]
-
-if __name__ == "__main__":
- if len(sys.argv) > 1:
- print(parse_args(interactions, sys.argv[1:]))
- else:
- print(get_answers(interactions))
diff --git a/mkdocs.yml b/mkdocs.yml
index b922f0ea..c8ba41e3 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -18,6 +18,8 @@ nav:
- Changelog: changelog.md
theme: material
markdown_extensions:
+ - markdown_include.include:
+ base_path: docs
- admonition
- codehilite
- footnotes
diff --git a/requirements-test.txt b/requirements-test.txt
index 14c390ff..b657e54a 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -4,6 +4,7 @@ bump2version==1.0.1
flake8==3.8.4
isort==5.7.0
mike==0.5.5
+markdown-include==0.6.0
mkdocs-material==7.0.3
mkdocs-minify-plugin==0.4.0
mkdocstrings==0.14.0