diff --git a/docs/css/custom.css b/docs/css/custom.css
index d4063f2..b9d77de 100644
--- a/docs/css/custom.css
+++ b/docs/css/custom.css
@@ -4,6 +4,6 @@
display: block;
}
-.termy {
+.termy [data-termynal] {
white-space: pre-wrap;
}
diff --git a/docs/css/termynal.css b/docs/css/termynal.css
index 954d005..0484e65 100644
--- a/docs/css/termynal.css
+++ b/docs/css/termynal.css
@@ -18,7 +18,8 @@
background: var(--color-bg);
color: var(--color-text);
font-size: 18px;
- font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
+ /* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */
+ font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
border-radius: 4px;
padding: 75px 45px 35px;
position: relative;
diff --git a/docs/js/termynal.js b/docs/js/termynal.js
index 47f7f47..8b0e933 100644
--- a/docs/js/termynal.js
+++ b/docs/js/termynal.js
@@ -135,7 +135,7 @@ class Termynal {
}
restart.href = '#'
restart.setAttribute('data-terminal-control', '')
- restart.innerHTML = "restart \u27f3" // Refresh emoji
+ restart.innerHTML = "restart ↻"
return restart
}
@@ -149,7 +149,7 @@ class Termynal {
}
finish.href = '#'
finish.setAttribute('data-terminal-control', '')
- finish.innerHTML = "fast \u2b95" // Fast emoji arrow
+ finish.innerHTML = "fast →"
this.finishElement = finish
return finish
}
diff --git a/docs/src/arguments/tutorial001.py b/docs/src/arguments/tutorial001.py
new file mode 100644
index 0000000..e0412c8
--- /dev/null
+++ b/docs/src/arguments/tutorial001.py
@@ -0,0 +1,9 @@
+import typer
+
+
+def main(name: str = typer.Argument(...)):
+ typer.echo(f"Hello {name}")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs/src/arguments/tutorial002.py b/docs/src/arguments/tutorial002.py
new file mode 100644
index 0000000..c624c87
--- /dev/null
+++ b/docs/src/arguments/tutorial002.py
@@ -0,0 +1,12 @@
+import typer
+
+
+def main(name: str = typer.Argument(None)):
+ if name is None:
+ typer.echo("Hello World!")
+ else:
+ typer.echo(f"Hello {name}")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs/src/arguments/tutorial003.py b/docs/src/arguments/tutorial003.py
new file mode 100644
index 0000000..538397c
--- /dev/null
+++ b/docs/src/arguments/tutorial003.py
@@ -0,0 +1,9 @@
+import typer
+
+
+def main(name: str = typer.Argument("Wade Wilson")):
+ typer.echo(f"Hello {name}")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs/tutorial/arguments.md b/docs/tutorial/arguments.md
new file mode 100644
index 0000000..a1d7ff8
--- /dev/null
+++ b/docs/tutorial/arguments.md
@@ -0,0 +1,213 @@
+The same way that you have `typer.Option()` to help you define things for *CLI options*, there's also the equivalent `typer.Argument()` for *CLI arguments*.
+
+## Optional *CLI arguments*
+
+We said before that *by default*:
+
+* *CLI options* are **optional**
+* *CLI arguments* are **required**
+
+Again, that's how they work *by default*, and that's the convention in many CLI programs and systems.
+
+But you can change that.
+
+In fact, it's very common to have **optional** *CLI arguments*, it's way more common than having **required** *CLI options*.
+
+As an example of how it could be useful, let's see how the `ls` command works.
+
+
+
+```console
+// If you just type
+$ ls
+
+// ls will "list" the files and directories in the current directory
+typer tests README.md LICENSE
+
+// But it also receives an optional CLI argument
+$ ls ./tests/
+
+// And then ls will list the files and directories inside of that directory from the CLI argument
+__init__.py test_tutorial
+```
+
+
+
+### An alternative *CLI argument* declaration
+
+In the First Steps you saw how to add a *CLI argument*:
+
+```Python hl_lines="4"
+{!./src/first_steps/tutorial002.py!}
+```
+
+Now let's see an alternative way to create the same *CLI argument*:
+
+```Python hl_lines="4"
+{!./src/arguments/tutorial001.py!}
+```
+
+Before you had this function parameter:
+
+```Python
+name: str
+```
+
+And because `name` didn't have any default value it would be a **required parameter** for the Python function, in Python terms.
+
+**Typer** does the same and makes it a **required** *CLI argument*.
+
+And then we changed it to:
+
+```Python
+name: str = typer.Argument(...)
+```
+
+The same as with `typer.Option()`, there is a `typer.Argument()`.
+
+And now as `typer.Argument()` is the "default value" of the function's parameter, in Python terms, it would mean that "it is no longer required" (in Python terms).
+
+As we no longer have the Python function default value (or its absence) to tell it if something is required or not and what is the default value, the first parameter to `typer.Argument()` serves the same purpose of defining that default value, or making it required.
+
+To make it *required*, we pass `...` as that first parameter to the function.
+
+!!! info
+ If you hadn't seen that `...` before: it is a a special single value, it is part of Python and is called "Ellipsis".
+
+!!! tip
+ This works exactly the same way `typer.Option()` does.
+
+All we did there achieves the same thing as before, a **required** *CLI argument*:
+
+
+
+```console
+$ python main.py
+
+Usage: main.py [OPTIONS] NAME
+Try "main.py --help" for help.
+
+Error: Missing argument "NAME".
+```
+
+
+
+It's still not very useful, but it works correctly.
+
+And being able to declare a **required** *CLI argument* using `name: str = typer.Argument(...)` that works exactly the same as `name: str` will come handy later.
+
+### Make an optional *CLI argument*
+
+Now, finally what we came for, an optional *CLI argument*.
+
+To make a *CLI argument* optional, use `typer.Argument()` and pass a different "default" as the first parameter to `typer.Argument()`, for example `None`:
+
+```Python hl_lines="4"
+{!./src/arguments/tutorial002.py!}
+```
+
+Now we have:
+
+```Python
+name: str = typer.Argument(None)
+```
+
+Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*).
+
+And because the first parameter passed to `typer.Argument(None)` (the new "default" value) is `None`, **Typer** knows that this is an **optional** *CLI argument*, if no value is provided when calling it in the command line, it will have that default value of `None`.
+
+Check the help:
+
+
+
+```console
+// First check the help
+$ python main.py --help
+
+Usage: main.py [OPTIONS] [NAME]
+
+Options:
+ --install-completion Install completion for the current shell.
+ --show-completion Show completion for the current shell, to copy it or customize the installation.
+ --help Show this message and exit.
+```
+
+
+
+!!! tip
+ Notice that `NAME` is still a *CLI argument*, it's shown up there in the "`Usage: main.py` ...".
+
+ Also notice that now `[NAME]` has brackets ("`[`" and "`]`") around (before it was just `NAME`) to denote that it's **optional**, not **required**.
+
+Now run it and test it:
+
+
+
+```console
+// With no CLI argument
+$ python main.py
+
+Hello World!
+
+// With one optional CLI argument
+$ python main.py
+
+Hello Camila
+```
+
+
+
+!!! tip
+ Notice that "`Camila`" here is an optional *CLI argument*, not a *CLI option*, because we didn't use something like "`--name Camila`", we just passed "`Camila`" directly to the program/command.
+
+## An optional *CLI argument* with a default
+
+We can also make a *CLI argument* have a default value other than `None`:
+
+```Python hl_lines="4"
+{!./src/arguments/tutorial003.py!}
+```
+
+And test it:
+
+
+
+```console
+// With no optional CLI argument
+$ python main.py
+
+Hello Wade Wilson
+
+// With one CLI argument
+$ python main.py Camila
+
+Hello Camila
+```
+
+
+
+## About *CLI arguments* help
+
+*CLI arguments* are commonly used for the most necessary things in a program.
+
+They are normally required and, when present, they are normally the main subject of whatever the command is doing.
+
+For that reason, Typer (actually Click underneath) doesn't attempt to automatically document *CLI arguments*.
+
+And you should document them as part of the command documentation, normally in a docstring.
+
+Check the last example from the First Steps:
+
+```Python hl_lines="5 6 7 8 9"
+{!./src/first_steps/tutorial006.py!}
+```
+
+Here the *CLI argument* `NAME` is documented as part of the command help text.
+
+You should document your *CLI arguments* the same way.
+
+## Other uses
+
+`typer.Argument()` has several other users. For data validation, to enable other features, etc.
+
+But you will see about that later in the docs.
diff --git a/mkdocs.yml b/mkdocs.yml
index be5ed5f..adcfba9 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -24,6 +24,7 @@ nav:
- Tutorial - User Guide - Intro: 'tutorial/index.md'
- First Steps: 'tutorial/first-steps.md'
- CLI Options: 'tutorial/options.md'
+ - CLI Arguments: 'tutorial/arguments.md'
- Alternatives, Inspiration and Comparisons: 'alternatives.md'
- Help Typer - Get Help: 'help-typer.md'
- Development - Contributing: 'contributing.md'
diff --git a/tests/test_tutorial/test_arguments/__init__.py b/tests/test_tutorial/test_arguments/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_tutorial/test_arguments/test_tutorial001.py b/tests/test_tutorial/test_arguments/test_tutorial001.py
new file mode 100644
index 0000000..668e657
--- /dev/null
+++ b/tests/test_tutorial/test_arguments/test_tutorial001.py
@@ -0,0 +1,33 @@
+import subprocess
+
+import typer
+from typer.testing import CliRunner
+
+from arguments import tutorial001 as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+def test_call_no_arg():
+ result = runner.invoke(app)
+ assert result.exit_code != 0
+ assert 'Error: Missing argument "NAME".' in result.output
+
+
+def test_call_arg():
+ result = runner.invoke(app, ["Camila"])
+ assert result.exit_code == 0
+ assert "Hello Camila" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ ["coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_arguments/test_tutorial002.py b/tests/test_tutorial/test_arguments/test_tutorial002.py
new file mode 100644
index 0000000..82c6198
--- /dev/null
+++ b/tests/test_tutorial/test_arguments/test_tutorial002.py
@@ -0,0 +1,39 @@
+import subprocess
+
+import typer
+from typer.testing import CliRunner
+
+from arguments import tutorial002 as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] [NAME]" in result.output
+
+
+def test_call_no_arg():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Hello World!" in result.output
+
+
+def test_call_arg():
+ result = runner.invoke(app, ["Camila"])
+ assert result.exit_code == 0
+ assert "Hello Camila" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ ["coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_arguments/test_tutorial003.py b/tests/test_tutorial/test_arguments/test_tutorial003.py
new file mode 100644
index 0000000..255b793
--- /dev/null
+++ b/tests/test_tutorial/test_arguments/test_tutorial003.py
@@ -0,0 +1,39 @@
+import subprocess
+
+import typer
+from typer.testing import CliRunner
+
+from arguments import tutorial003 as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] [NAME]" in result.output
+
+
+def test_call_no_arg():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Hello Wade Wilson" in result.output
+
+
+def test_call_arg():
+ result = runner.invoke(app, ["Camila"])
+ assert result.exit_code == 0
+ assert "Hello Camila" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ ["coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout