+ * @version 0.0.1
+ * @license MIT
+ */
+
+'use strict';
+
+/** Generate a terminal widget. */
+class Termynal {
+ /**
+ * Construct the widget's settings.
+ * @param {(string|Node)=} container - Query selector or container element.
+ * @param {Object=} options - Custom settings.
+ * @param {string} options.prefix - Prefix to use for data attributes.
+ * @param {number} options.startDelay - Delay before animation, in ms.
+ * @param {number} options.typeDelay - Delay between each typed character, in ms.
+ * @param {number} options.lineDelay - Delay between each line, in ms.
+ * @param {number} options.progressLength - Number of characters displayed as progress bar.
+ * @param {string} options.progressChar – Character to use for progress bar, defaults to █.
+ * @param {number} options.progressPercent - Max percent of progress.
+ * @param {string} options.cursor – Character to use for cursor, defaults to ▋.
+ * @param {Object[]} lineData - Dynamically loaded line data objects.
+ * @param {boolean} options.noInit - Don't initialise the animation.
+ */
+ constructor(container = '#termynal', options = {}) {
+ this.container = (typeof container === 'string') ? document.querySelector(container) : container;
+ this.pfx = `data-${options.prefix || 'ty'}`;
+ this.originalStartDelay = this.startDelay = options.startDelay
+ || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600;
+ this.originalTypeDelay = this.typeDelay = options.typeDelay
+ || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90;
+ this.originalLineDelay = this.lineDelay = options.lineDelay
+ || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500;
+ this.progressLength = options.progressLength
+ || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40;
+ this.progressChar = options.progressChar
+ || this.container.getAttribute(`${this.pfx}-progressChar`) || '█';
+ this.progressPercent = options.progressPercent
+ || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100;
+ this.cursor = options.cursor
+ || this.container.getAttribute(`${this.pfx}-cursor`) || '▋';
+ this.lineData = this.lineDataToElements(options.lineData || []);
+ this.loadLines()
+ if (!options.noInit) this.init()
+ }
+
+ loadLines() {
+ // Load all the lines and create the container so that the size is fixed
+ // Otherwise it would be changing and the user viewport would be constantly
+ // moving as she/he scrolls
+ const finish = this.generateFinish()
+ finish.style.visibility = 'hidden'
+ this.container.appendChild(finish)
+ // Appends dynamically loaded lines to existing line elements.
+ this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData);
+ for (let line of this.lines) {
+ line.style.visibility = 'hidden'
+ this.container.appendChild(line)
+ }
+ const restart = this.generateRestart()
+ restart.style.visibility = 'hidden'
+ this.container.appendChild(restart)
+ this.container.setAttribute('data-termynal', '');
+ }
+
+ /**
+ * Initialise the widget, get lines, clear container and start animation.
+ */
+ init() {
+ /**
+ * Calculates width and height of Termynal container.
+ * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
+ */
+ const containerStyle = getComputedStyle(this.container);
+ this.container.style.width = containerStyle.width !== '0px' ?
+ containerStyle.width : undefined;
+ this.container.style.minHeight = containerStyle.height !== '0px' ?
+ containerStyle.height : undefined;
+
+ this.container.setAttribute('data-termynal', '');
+ this.container.innerHTML = '';
+ for (let line of this.lines) {
+ line.style.visibility = 'visible'
+ }
+ this.start();
+ }
+
+ /**
+ * Start the animation and rener the lines depending on their data attributes.
+ */
+ async start() {
+ this.addFinish()
+ await this._wait(this.startDelay);
+
+ for (let line of this.lines) {
+ const type = line.getAttribute(this.pfx);
+ const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay;
+
+ if (type == 'input') {
+ line.setAttribute(`${this.pfx}-cursor`, this.cursor);
+ await this.type(line);
+ await this._wait(delay);
+ }
+
+ else if (type == 'progress') {
+ await this.progress(line);
+ await this._wait(delay);
+ }
+
+ else {
+ this.container.appendChild(line);
+ await this._wait(delay);
+ }
+
+ line.removeAttribute(`${this.pfx}-cursor`);
+ }
+ this.addRestart()
+ this.finishElement.style.visibility = 'hidden'
+ this.lineDelay = this.originalLineDelay
+ this.typeDelay = this.originalTypeDelay
+ this.startDelay = this.originalStartDelay
+ }
+
+ generateRestart() {
+ const restart = document.createElement('a')
+ restart.onclick = (e) => {
+ e.preventDefault()
+ this.container.innerHTML = ''
+ this.init()
+ }
+ restart.href = '#'
+ restart.setAttribute('data-terminal-control', '')
+ restart.innerHTML = "restart ↻"
+ return restart
+ }
+
+ generateFinish() {
+ const finish = document.createElement('a')
+ finish.onclick = (e) => {
+ e.preventDefault()
+ this.lineDelay = 0
+ this.typeDelay = 0
+ this.startDelay = 0
+ }
+ finish.href = '#'
+ finish.setAttribute('data-terminal-control', '')
+ finish.innerHTML = "fast →"
+ this.finishElement = finish
+ return finish
+ }
+
+ addRestart() {
+ const restart = this.generateRestart()
+ this.container.appendChild(restart)
+ }
+
+ addFinish() {
+ const finish = this.generateFinish()
+ this.container.appendChild(finish)
+ }
+
+ /**
+ * Animate a typed line.
+ * @param {Node} line - The line element to render.
+ */
+ async type(line) {
+ const chars = [...line.textContent];
+ line.textContent = '';
+ this.container.appendChild(line);
+
+ for (let char of chars) {
+ const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay;
+ await this._wait(delay);
+ line.textContent += char;
+ }
+ }
+
+ /**
+ * Animate a progress bar.
+ * @param {Node} line - The line element to render.
+ */
+ async progress(line) {
+ const progressLength = line.getAttribute(`${this.pfx}-progressLength`)
+ || this.progressLength;
+ const progressChar = line.getAttribute(`${this.pfx}-progressChar`)
+ || this.progressChar;
+ const chars = progressChar.repeat(progressLength);
+ const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`)
+ || this.progressPercent;
+ line.textContent = '';
+ this.container.appendChild(line);
+
+ for (let i = 1; i < chars.length + 1; i++) {
+ await this._wait(this.typeDelay);
+ const percent = Math.round(i / chars.length * 100);
+ line.textContent = `${chars.slice(0, i)} ${percent}%`;
+ if (percent>progressPercent) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Helper function for animation delays, called with `await`.
+ * @param {number} time - Timeout, in ms.
+ */
+ _wait(time) {
+ return new Promise(resolve => setTimeout(resolve, time));
+ }
+
+ /**
+ * Converts line data objects into line elements.
+ *
+ * @param {Object[]} lineData - Dynamically loaded lines.
+ * @param {Object} line - Line data object.
+ * @returns {Element[]} - Array of line elements.
+ */
+ lineDataToElements(lineData) {
+ return lineData.map(line => {
+ let div = document.createElement('div');
+ div.innerHTML = `${line.value || ''}`;
+
+ return div.firstElementChild;
+ });
+ }
+
+ /**
+ * Helper function for generating attributes string.
+ *
+ * @param {Object} line - Line data object.
+ * @returns {string} - String of attributes.
+ */
+ _attributes(line) {
+ let attrs = '';
+ for (let prop in line) {
+ // Custom add class
+ if (prop === 'class') {
+ attrs += ` class=${line[prop]} `
+ continue
+ }
+ if (prop === 'type') {
+ attrs += `${this.pfx}="${line[prop]}" `
+ } else if (prop !== 'value') {
+ attrs += `${this.pfx}-${prop}="${line[prop]}" `
+ }
+ }
+
+ return attrs;
+ }
+}
+
+/**
+* HTML API: If current script has container(s) specified, initialise Termynal.
+*/
+if (document.currentScript.hasAttribute('data-termynal-container')) {
+ const containers = document.currentScript.getAttribute('data-termynal-container');
+ containers.split('|')
+ .forEach(container => new Termynal(container))
+}
diff --git a/docs/tutorial/bigger-applications.md b/docs/tutorial/bigger-applications.md
index 6ee298337..f6c623b84 100644
--- a/docs/tutorial/bigger-applications.md
+++ b/docs/tutorial/bigger-applications.md
@@ -292,10 +292,16 @@ The end result is that the item paths are now:
Now, run `uvicorn`, using the module `app.main` and the variable `app`:
-```bash
-uvicorn app.main:app --reload
+
+
+```console
+$ uvicorn app.main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+
+
And open the docs at http://127.0.0.1:8000/docs.
You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags:
diff --git a/docs/tutorial/debugging.md b/docs/tutorial/debugging.md
index 3800abd1b..e527bf971 100644
--- a/docs/tutorial/debugging.md
+++ b/docs/tutorial/debugging.md
@@ -12,10 +12,14 @@ In your FastAPI application, import and run `uvicorn` directly:
The main purpose of the `__name__ == "__main__"` is to have some code that is executed when your file is called with:
-```bash
-python myapp.py
+
+
+```console
+$ python myapp.py
```
+
+
but is not called when another file imports it, like in:
```Python
@@ -28,10 +32,14 @@ Let's say your file is named `myapp.py`.
If you run it with:
-```bash
-python myapp.py
+
+
+```console
+$ python myapp.py
```
+
+
then the internal variable `__name__` in your file, created automatically by Python, will have as value the string `"__main__"`.
So, the section:
diff --git a/docs/tutorial/first-steps.md b/docs/tutorial/first-steps.md
index dd916f330..639cfed39 100644
--- a/docs/tutorial/first-steps.md
+++ b/docs/tutorial/first-steps.md
@@ -8,10 +8,20 @@ Copy that to a file `main.py`.
Run the live server:
-```bash
-uvicorn main:app --reload
+
+
+```console
+$ uvicorn main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+INFO: Started reloader process [28720]
+INFO: Started server process [28722]
+INFO: Waiting for application startup.
+INFO: Application startup complete.
```
+
+
!!! note
The command `uvicorn main:app` refers to:
@@ -19,16 +29,13 @@ uvicorn main:app --reload
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--reload`: make the server restart after code changes. Only use for development.
-You will see an output like:
+In the output, there's a line with something like:
```hl_lines="4"
-INFO: Started reloader process [17961]
-INFO: Started server process [17962]
-INFO: Waiting for application startup.
-INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
-That last line shows the URL where your app is being served, in your local machine.
+That line shows the URL where your app is being served, in your local machine.
### Check it
@@ -144,10 +151,16 @@ This will be the main point of interaction to create all your API.
This `app` is the same one referred by `uvicorn` in the command:
-```bash
-uvicorn main:app --reload
+
+
+```console
+$ uvicorn main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+
+
If you create your app like:
```Python hl_lines="3"
@@ -156,10 +169,16 @@ If you create your app like:
And put it in a file `main.py`, then you would call `uvicorn` like:
-```bash
-uvicorn main:my_awesome_api --reload
+
+
+```console
+$ uvicorn main:my_awesome_api --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+
+
### Step 3: create a *path operation*
#### Path
diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md
index 0aa572c88..ae073518a 100644
--- a/docs/tutorial/index.md
+++ b/docs/tutorial/index.md
@@ -12,10 +12,20 @@ All the code blocks can be copied and used directly (they are actually tested Py
To run any of the examples, copy the code to a file `main.py`, and start `uvicorn` with:
-```bash
-uvicorn main:app --reload
+
+
+```console
+$ uvicorn main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+INFO: Started reloader process [28720]
+INFO: Started server process [28722]
+INFO: Waiting for application startup.
+INFO: Application startup complete.
```
+
+
It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally.
Using it in your editor is what really shows you the benefits of FastAPI, seeing how little code you have to write, all the type checks, autocompletion, etc.
@@ -28,10 +38,16 @@ The first step is to install FastAPI.
For the tutorial, you might want to install it with all the optional dependencies and features:
-```bash
-pip install fastapi[all]
+
+
+```console
+$ pip install fastapi[all]
+
+---> 100%
```
+
+
...that also includes `uvicorn`, that you can use as the server that runs your code.
!!! note
diff --git a/docs/tutorial/security/first-steps.md b/docs/tutorial/security/first-steps.md
index 5d215bdd0..6fef16235 100644
--- a/docs/tutorial/security/first-steps.md
+++ b/docs/tutorial/security/first-steps.md
@@ -33,10 +33,16 @@ Copy the example in a file `main.py`:
Run the example with:
-```bash
-uvicorn main:app --reload
+
+
+```console
+$ uvicorn main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+
+
## Check it
Go to the interactive docs at: http://127.0.0.1:8000/docs.
diff --git a/docs/tutorial/security/oauth2-jwt.md b/docs/tutorial/security/oauth2-jwt.md
index 70945f236..cd15cb9d2 100644
--- a/docs/tutorial/security/oauth2-jwt.md
+++ b/docs/tutorial/security/oauth2-jwt.md
@@ -28,10 +28,16 @@ If you want to play with JWT tokens and see how they work, check
+
+```console
+$ pip install pyjwt
+
+---> 100%
```
+
+
## Password hashing
"Hashing" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish.
@@ -56,10 +62,16 @@ The recommended algorithm is "Bcrypt".
So, install PassLib with Bcrypt:
-```bash
-pip install passlib[bcrypt]
+
+
+```console
+$ pip install passlib[bcrypt]
+
+---> 100%
```
+
+
!!! tip
With `passlib`, you could even configure it to be able to read passwords created by **Django**, a **Flask** security plug-in or many others.
@@ -101,10 +113,16 @@ Create a random secret key that will be used to sign the JWT tokens.
To generate a secure random secret key use the command:
-```bash
-openssl rand -hex 32
+
+
+```console
+$ openssl rand -hex 32
+
+09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
```
+
+
And copy the output to the variable `SECRET_KEY` (don't use the one in the example).
Create a variable `ALGORITHM` with the algorithm used to sign the JWT token and set it to `"HS256"`.
diff --git a/docs/tutorial/sql-databases.md b/docs/tutorial/sql-databases.md
index 77fd2c8f4..ba4b83648 100644
--- a/docs/tutorial/sql-databases.md
+++ b/docs/tutorial/sql-databases.md
@@ -440,8 +440,8 @@ A "migration" is the set of steps needed whenever you change the structure of yo
!!! info
For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports":
- ```bash
- pip install async-exit-stack async-generator
+ ```console
+ $ pip install async-exit-stack async-generator
```
This installs async-exit-stack and async-generator.
@@ -596,10 +596,17 @@ You can copy this code and use it as is.
Then you can run it with Uvicorn:
-```bash
-uvicorn sql_app.main:app --reload
+
+
+
+```console
+$ uvicorn sql_app.main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+
+
And then, you can open your browser at http://127.0.0.1:8000/docs.
And you will be able to interact with your **FastAPI** application, reading data from a real database:
diff --git a/docs/tutorial/static-files.md b/docs/tutorial/static-files.md
index 1b0725291..0a1d63956 100644
--- a/docs/tutorial/static-files.md
+++ b/docs/tutorial/static-files.md
@@ -4,10 +4,16 @@ You can serve static files automatically from a directory using `StaticFiles`.
First you need to install `aiofiles`:
-```bash
-pip install aiofiles
+
+
+```console
+$ pip install aiofiles
+
+---> 100%
```
+
+
## Use `StaticFiles`
* Import `StaticFiles`.
diff --git a/docs/tutorial/testing.md b/docs/tutorial/testing.md
index 1b1da2ab2..673352772 100644
--- a/docs/tutorial/testing.md
+++ b/docs/tutorial/testing.md
@@ -103,14 +103,36 @@ For more information about how to pass data to the backend (using `requests` or
After that, you just need to install `pytest`:
-```bash
-pip install pytest
+
+
+```console
+$ pip install pytest
+
+---> 100%
```
+
+
It will detect the files and tests automatically, execute them, and report the results back to you.
Run the tests with:
-```bash
-pytest
+
+
+```console
+$ pytest
+
+================ test session starts ================
+platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
+rootdir: /home/user/code/superawesome-cli/app
+plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
+collected 6 items
+
+---> 100%
+
+test_main.py ...... [100%]
+
+================= 1 passed in 0.03s =================
```
+
+
diff --git a/mkdocs.yml b/mkdocs.yml
index b1f88184f..bacae9bac 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -141,8 +141,10 @@ extra:
link: 'https://tiangolo.com'
extra_css:
+ - 'css/termynal.css'
- 'css/custom.css'
extra_javascript:
- 'https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js'
+ - 'js/termynal.js'
- 'js/custom.js'