--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 | search_exclude: true
4 | image: images/logo.png
5 | ---
6 |
7 | This site is built with [fastpages](https://github.com/fastai/fastpages), An easy to use blogging platform with extra features for Jupyter Notebooks.
8 |
9 | 
10 |
11 | [fastpages](https://github.com/fastai/fastpages) automates the process of creating blog posts via GitHub Actions, so you don't have to fuss with conversion scripts. A full list of features can be found on [GitHub](https://github.com/fastai/fastpages).
12 |
13 | You can edit the `index.html` file to change this content.
14 |
15 | # Posts
16 |
--------------------------------------------------------------------------------
/_includes/notebook_github_link.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/.github/workflows/gh-page.yaml:
--------------------------------------------------------------------------------
1 | name: GH-Pages Status
2 | on: page_build
3 |
4 | permissions:
5 | actions: write
6 | pull-requests: write
7 | contents: write
8 |
9 | jobs:
10 | see-page-build-payload:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: check status
14 | run: |
15 | import os
16 | status, errormsg = os.getenv('STATUS'), os.getenv('ERROR')
17 | assert status == 'built', 'There was an error building the page on GitHub pages.\n\nStatus: {}\n\nError messsage: {}'.format(status, errormsg)
18 | shell: python
19 | env:
20 | STATUS: ${{ github.event.build.status }}
21 | ERROR: ${{ github.event.build.error.message }}
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/_action_files/action.yml:
--------------------------------------------------------------------------------
1 | name: 'fastpages: An easy to use blogging platform with support for Jupyter Notebooks.'
2 | description: Converts Jupyter notebooks and Word docs into Jekyll blog posts.
3 | author: Hamel Husain
4 | inputs:
5 | BOOL_SAVE_MARKDOWN:
6 | description: Either 'true' or 'false'. Whether or not to commit converted markdown files from notebooks and word documents into the _posts directory in your repo. This is useful for debugging.
7 | required: false
8 | default: false
9 | SSH_DEPLOY_KEY:
10 | description: a ssh deploy key is required if BOOL_SAVE_MARKDOWN = 'true'
11 | required: false
12 | branding:
13 | color: 'blue'
14 | icon: 'book'
15 | runs:
16 | using: 'docker'
17 | image: 'Dockerfile'
18 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: 'fastpages: An easy to use blogging platform with support for Jupyter Notebooks.'
2 | description: Converts Jupyter notebooks and Word docs into Jekyll blog posts.
3 | author: Hamel Husain
4 | inputs:
5 | BOOL_SAVE_MARKDOWN:
6 | description: Either 'true' or 'false'. Whether or not to commit converted markdown files from notebooks and word documents into the _posts directory in your repo. This is useful for debugging.
7 | required: false
8 | default: false
9 | SSH_DEPLOY_KEY:
10 | description: a ssh deploy key is required if BOOL_SAVE_MARKDOWN = 'true'
11 | required: false
12 | branding:
13 | color: 'blue'
14 | icon: 'book'
15 | runs:
16 | using: 'docker'
17 | image: '_action_files/Dockerfile'
18 |
--------------------------------------------------------------------------------
/_pages/search.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | permalink: /search/
4 | title: Search
5 | search_exclude: true
6 | ---
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/_action_files/fastpages.tpl:
--------------------------------------------------------------------------------
1 | {%- extends 'hide.tpl' -%}
2 | {%- block body -%}
3 | {%- set internals = ["metadata", "output_extension", "inlining",
4 | "raw_mimetypes", "global_content_filter"] -%}
5 | ---
6 | {%- for k in resources |reject("in", internals) %}
7 | {% if k == "summary" and "description" not in resources %}description{% else %}{{ k }}{% endif %}: {{ resources[k] }}
8 | {%- endfor %}
9 | layout: notebook
10 | ---
11 |
12 |
18 |
19 |
20 | {{ super() }}
21 |
22 | {%- endblock body %}
--------------------------------------------------------------------------------
/_posts/README.md:
--------------------------------------------------------------------------------
1 | ⚠️ Do not delete this directory! You can delete the blog post files in this directory, but you should still keep this directory around as Jekyll expects this folder to exist.
2 |
3 | # Auto-convert markdown files To Posts
4 |
5 | [`fastpages`](https://github.com/fastai/fastpages) will automatically convert markdown files saved into this directory as blog posts!
6 |
7 | You must save your notebook with the naming convention `YYYY-MM-DD-*.md`. Examples of valid filenames are:
8 |
9 | ```shell
10 | 2020-01-28-My-First-Post.md
11 | 2012-09-12-how-to-write-a-blog.md
12 | ```
13 |
14 | # Resources
15 |
16 | - [Jekyll posts](https://jekyllrb.com/docs/posts/)
17 | - [Example markdown post](https://github.com/fastai/fastpages/blob/master/_posts/2020-01-14-test-markdown-post.md)
18 |
--------------------------------------------------------------------------------
/_notebooks/README.md:
--------------------------------------------------------------------------------
1 | # Auto-convert Jupyter Notebooks To Posts
2 |
3 | [`fastpages`](https://github.com/fastai/fastpages) will automatically convert [Jupyter](https://jupyter.org/) Notebooks saved into this directory as blog posts!
4 |
5 | You must save your notebook with the naming convention `YYYY-MM-DD-*.ipynb`. Examples of valid filenames are:
6 |
7 | ```shell
8 | 2020-01-28-My-First-Post.ipynb
9 | 2012-09-12-how-to-write-a-blog.ipynb
10 | ```
11 |
12 | If you fail to name your file correctly, `fastpages` will automatically attempt to fix the problem by prepending the last modified date of your notebook. However, it is recommended that you name your files properly yourself for more transparency.
13 |
14 | See [Writing Blog Posts With Jupyter](https://github.com/fastai/fastpages#writing-blog-posts-with-jupyter) for more details.
--------------------------------------------------------------------------------
/_includes/toc.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/_action_files/nb2post.py:
--------------------------------------------------------------------------------
1 | """Converts Jupyter Notebooks to Jekyll compliant blog posts"""
2 | from datetime import datetime
3 | import re, os, logging
4 | from nbdev import export2html
5 | from nbdev.export2html import Config, Path, _to_html, _re_block_notes
6 | from fast_template import rename_for_jekyll
7 |
8 | warnings = set()
9 |
10 | # Modify the naming process such that destination files get named properly for Jekyll _posts
11 | def _nb2htmlfname(nb_path, dest=None):
12 | fname = rename_for_jekyll(nb_path, warnings=warnings)
13 | if dest is None: dest = Config().doc_path
14 | return Path(dest)/fname
15 |
16 | # TODO: Open a GitHub Issue in addition to printing warnings
17 | for original, new in warnings:
18 | print(f'{original} has been renamed to {new} to be complaint with Jekyll naming conventions.\n')
19 |
20 | ## apply monkey patches
21 | export2html._nb2htmlfname = _nb2htmlfname
22 | export2html.notebook2html(fname='_notebooks/*.ipynb', dest='_posts/', template_file='/fastpages/fastpages.tpl', execute=False)
23 |
--------------------------------------------------------------------------------
/_action_files/pr_comment.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Make a comment on a PR.
4 | # Usage:
5 | # > pr_comment.sh <>
6 |
7 | set -e
8 |
9 | # This is populated by our secret from the Workflow file.
10 | if [[ -z "${GITHUB_TOKEN}" ]]; then
11 | echo "Set the GITHUB_TOKEN env variable."
12 | exit 1
13 | fi
14 |
15 | if [[ -z "${ISSUE_NUMBER}" ]]; then
16 | echo "Set the ISSUE_NUMBER env variable."
17 | exit 1
18 | fi
19 |
20 | if [ -z "$1" ]
21 | then
22 | echo "No MESSAGE argument supplied. Usage: issue_comment.sh "
23 | exit 1
24 | fi
25 |
26 | MESSAGE=$1
27 |
28 | ## Set Vars
29 | URI=https://api.github.com
30 | API_VERSION=v3
31 | API_HEADER="Accept: application/vnd.github.${API_VERSION}+json"
32 | AUTH_HEADER="Authorization: token ${GITHUB_TOKEN}"
33 |
34 | # Create a comment with APIv3 # POST /repos/:owner/:repo/issues/:issue_number/comments
35 | curl -XPOST -sSL \
36 | -d "{\"body\": \"$MESSAGE\"}" \
37 | -H "${AUTH_HEADER}" \
38 | -H "${API_HEADER}" \
39 | "${URI}/repos/${GITHUB_REPOSITORY}/issues/${ISSUE_NUMBER}/comments"
40 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug
3 | about: Use this template for filing bugs
4 | title: ""
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Required Prerequisites for filing a bug
11 |
12 | ### You must follow ALL the steps in the [troubleshooting guide](https://github.com/fastai/fastpages/blob/master/_fastpages_docs/TROUBLESHOOTING.md). Not doing so may result in automatic closure of the issue.
13 |
14 |
15 | ## Required information
16 |
17 | 1. Steps to reproduce the problem
18 | 2. A link to the notebook or markdown file where the error is occurring
19 | 3. If the error is happening in GitHub Actions, a link to the specific error along with how you are able to reproduce this error. You must provide this **in addition to the link to the notebook or markdown file**.
20 | 4. A screenshot / dump of relevant logs or error messages you are receiving from your local development environment. Instructions of running a local development server is provided in the [development guide](https://github.com/fastai/fastpages/blob/master/_fastpages_docs/DEVELOPMENT.md).
21 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yaml:
--------------------------------------------------------------------------------
1 | name: Build-Docker
2 | on:
3 | push:
4 | paths:
5 | - Gemfile*
6 | branches:
7 | - master
8 | pull_request:
9 | paths:
10 | - Gemfile*
11 |
12 | jobs:
13 | jekyll-fastpages:
14 | if: github.repository == 'fastai/fastpages'
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@main
18 |
19 | - name: setup directories for Jekyll build
20 | run: sudo chmod -R 777 .
21 |
22 | - name: build container
23 | run: |
24 | docker build -t fastai/fastpages-jekyll -f _action_files/fastpages-jekyll.Dockerfile .
25 |
26 | - name: push container
27 | if: github.event == 'push'
28 | run: |
29 | echo ${PASSWORD} | docker login -u $USERNAME --password-stdin
30 | docker push fastai/fastpages-jekyll
31 | env:
32 | USERNAME: ${{ secrets.DOCKER_USERNAME }}
33 | PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
34 |
--------------------------------------------------------------------------------
/.github/workflows/issue_reminder.yaml:
--------------------------------------------------------------------------------
1 | name: Issue Reminder
2 | on:
3 | issues:
4 | types: [opened]
5 |
6 | permissions:
7 | actions: write
8 | pull-requests: write
9 | contents: write
10 |
11 | jobs:
12 | issue_comment:
13 | if: |
14 | (github.repository == 'fastai/fastpages')
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Comment on issue
18 | uses: actions/github-script@0.6.0
19 | with:
20 | github-token: ${{secrets.GITHUB_TOKEN}}
21 | script: |
22 | var url = 'https://github.com/fastai/fastpages/blob/master/_fastpages_docs/TROUBLESHOOTING.md'
23 | var msg = `Thank you for opening an issue. If this issue is related to a bug, please follow the steps and provide the information outlined in the [Troubleshooting Guide](${url}). Failure to follow these instructions may result in automatic closing of this issue.`
24 | github.issues.createComment({
25 | issue_number: context.issue.number,
26 | owner: context.repo.owner,
27 | repo: context.repo.repo,
28 | body: msg
29 | })
30 |
--------------------------------------------------------------------------------
/_pages/tags.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: categories
3 | permalink: /categories/
4 | title: Tags
5 | search_exclude: true
6 | ---
7 |
8 | {% if site.categories.size > 0 %}
9 |
31 |
32 | {% endif %}
33 | {% endfor %}
34 | {% endfor %}
35 |
36 | {% endif %}
37 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | fastpages: &fastpages
4 | working_dir: /data
5 | environment:
6 | - INPUT_BOOL_SAVE_MARKDOWN=false
7 | build:
8 | context: ./_action_files
9 | dockerfile: ./Dockerfile
10 | image: fastpages-dev
11 | logging:
12 | driver: json-file
13 | options:
14 | max-size: 50m
15 | stdin_open: true
16 | tty: true
17 | volumes:
18 | - .:/data/
19 |
20 | converter:
21 | <<: *fastpages
22 | command: /fastpages/action_entrypoint.sh
23 |
24 | notebook:
25 | <<: *fastpages
26 | command: jupyter notebook --allow-root --no-browser --ip=0.0.0.0 --port=8080 --NotebookApp.token='' --NotebookApp.password=''
27 | ports:
28 | - "8080:8080"
29 |
30 | watcher:
31 | <<: *fastpages
32 | command: watchmedo shell-command --command /fastpages/action_entrypoint.sh --pattern *.ipynb --recursive --drop
33 | network_mode: host # for GitHub Codespaces https://github.com/features/codespaces/
34 |
35 | jekyll:
36 | working_dir: /data
37 | image: fastai/fastpages-jekyll
38 | restart: unless-stopped
39 | ports:
40 | - "4000:4000"
41 | volumes:
42 | - .:/data/
43 | command: >
44 | bash -c "chmod -R u+rw . && jekyll serve --host 0.0.0.0 --trace --strict_front_matter"
45 |
--------------------------------------------------------------------------------
/_fastpages_docs/_upgrade_pr.md:
--------------------------------------------------------------------------------
1 | Hello :wave: @{_username_}!
2 |
3 | This PR pulls the most recent files from [fastpages](https://github.com/fastai/fastpages), and attempts to replace relevant files in your repository, without changing the content of your blog posts. This allows you to receive bug fixes and feature updates.
4 |
5 | ## Warning
6 |
7 | If you have applied **customizations to the HTML or styling of your site, they may be lost if you merge this PR. Please review the changes this PR makes carefully before merging!.** However, for people who only write content and don't change the styling of their site, this method is recommended.
8 |
9 | If you would like more fine-grained control over what changes to accept or decline, consider [following this approach](https://stackoverflow.com/questions/56577184/github-pull-changes-from-a-template-repository/56577320) instead.
10 |
11 | ### What to Expect After Merging This PR
12 |
13 | - GitHub Actions will build your site, which will take 3-4 minutes to complete. **This will happen anytime you push changes to the master branch of your repository.** You can monitor the logs of this if you like on the [Actions tab of your repo](https://github.com/{_username_}/{_repo_name_}/actions).
14 | - You can monitor the status of your site in the GitHub Pages section of your [repository settings](https://github.com/{_username_}/{_repo_name_}/settings).
15 |
--------------------------------------------------------------------------------
/_fastpages_docs/NOTEBOOK_FOOTNOTES.md:
--------------------------------------------------------------------------------
1 | # Detailed Guide To Footnotes in Notebooks
2 |
3 | Notebook -> HTML Footnotes don't work the same as Markdown. There isn't a good solution, so made these Jekyll plugins as a workaround
4 |
5 | ```
6 | This adds a linked superscript {% fn 15 %}
7 |
8 | {{ "This is the actual footnote" | fndetail: 15 }}
9 | ```
10 |
11 | 
12 |
13 | You can have links, but then you have to use **single quotes** to escape the link.
14 | ```
15 | This adds a linked superscript {% fn 20 %}
16 |
17 | {{ 'This is the actual footnote with a [link](www.github.com) as well!' | fndetail: 20 }}
18 | ```
19 | 
20 |
21 | However, what if you want a single quote in your footnote? There is not an easy way to escape that. Fortunately, you can use the special HTML character `'` (you must keep the semicolon!). For example, you can include a single quote like this:
22 |
23 |
24 | ```
25 | This adds a linked superscript {% fn 20 %}
26 |
27 | {{ 'This is the actual footnote; with a [link](www.github.com) as well! and a single quote ' too!' | fndetail: 20 }}
28 | ```
29 |
30 | 
31 |
--------------------------------------------------------------------------------
/_action_files/hide.tpl:
--------------------------------------------------------------------------------
1 | {%- extends 'basic.tpl' -%}
2 |
3 | {% block codecell %}
4 | {{ "{% raw %}" }}
5 | {{ super() }}
6 | {{ "{% endraw %}" }}
7 | {% endblock codecell %}
8 |
9 | {% block input_group -%}
10 | {%- if cell.metadata.collapse_show -%}
11 |
12 |
13 |
41 | {%- else -%}
42 | {{ super() }}
43 | {%- endif -%}
44 | {% endblock output_area_prompt %}
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | # Hello! This is where you manage which Jekyll version is used to run.
3 | # When you want to use a different version, change it below, save the
4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
5 | #
6 | # bundle exec jekyll serve
7 | #
8 | # This will help ensure the proper Jekyll version is running.
9 | # Happy Jekylling!
10 | gem "jekyll", "~> 4.1.0"
11 | # This is the default theme for new Jekyll sites. You may change this to anything you like.
12 | gem "minima"
13 | # To upgrade, run `bundle update github-pages`.
14 | # gem "github-pages", group: :jekyll_plugins
15 | # If you have any plugins, put them here!
16 | group :jekyll_plugins do
17 | gem "jekyll-feed", "~> 0.12"
18 | gem 'jekyll-octicons'
19 | gem 'jekyll-remote-theme'
20 | gem "jekyll-twitter-plugin"
21 | gem 'jekyll-relative-links'
22 | gem 'jekyll-seo-tag'
23 | gem 'jekyll-toc'
24 | gem 'jekyll-gist'
25 | gem 'jekyll-paginate'
26 | gem 'jekyll-sitemap'
27 | end
28 |
29 | gem "kramdown-math-katex"
30 | gem "jemoji"
31 |
32 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
33 | # and associated library.
34 | install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do
35 | gem "tzinfo", "~> 1.2"
36 | gem "tzinfo-data"
37 | end
38 |
39 | # Performance-booster for watching directories on Windows
40 | gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform?
41 |
42 | gem "faraday", "< 1.0"
43 |
44 |
--------------------------------------------------------------------------------
/_action_files/check_js.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # The purpose of this script is to check parity between official hosted third party js libraries, and alternative CDNs used on this site.
3 |
4 | function compare {
5 | printf "=================\ncomparing:\n%s vs. %s\n" "$1" "$2"
6 | wget "$1" -O f1 &> /dev/null
7 | wget "$2" -O f2 &> /dev/null
8 | if ! cmp f1 f2;
9 | then
10 | printf "Files are NOT the same!\n"
11 | exit 1;
12 | else
13 | printf "Files are the same.\n"
14 | fi
15 | }
16 |
17 | compare "https://unpkg.com/@primer/css/dist/primer.css" "https://cdnjs.cloudflare.com/ajax/libs/Primer/15.2.0/primer.css"
18 | #compare "https://hypothes.is/embed.js" "https://cdn.jsdelivr.net/npm/hypothesis/build/boot.js"
19 | compare "https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js" "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.12.0/contrib/auto-render.min.js"
20 | compare "https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css" "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.12.0/katex.min.css"
21 | compare "https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js" "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.12.0/katex.min.js"
22 | compare "https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js" "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js"
23 |
24 | # Remove files created for comparison
25 | rm f1 f2
26 |
--------------------------------------------------------------------------------
/_action_files/fast_template.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | import re, os
3 | from pathlib import Path
4 | from typing import Tuple, Set
5 |
6 | # Check for YYYY-MM-DD
7 | _re_blog_date = re.compile(r'([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])-)')
8 | # Check for leading dashses or numbers
9 | _re_numdash = re.compile(r'(^[-\d]+)')
10 |
11 | def rename_for_jekyll(nb_path: Path, warnings: Set[Tuple[str, str]]=None) -> str:
12 | """
13 | Return a Path's filename string appended with its modified time in YYYY-MM-DD format.
14 | """
15 | assert nb_path.exists(), f'{nb_path} could not be found.'
16 |
17 | # Checks if filename is compliant with Jekyll blog posts
18 | if _re_blog_date.match(nb_path.name): return nb_path.with_suffix('.md').name.replace(' ', '-')
19 |
20 | else:
21 | clean_name = _re_numdash.sub('', nb_path.with_suffix('.md').name).replace(' ', '-')
22 |
23 | # Gets the file's last modified time and and append YYYY-MM-DD- to the beginning of the filename
24 | mdate = os.path.getmtime(nb_path) - 86400 # subtract one day b/c dates in the future break Jekyll
25 | dtnm = datetime.fromtimestamp(mdate).strftime("%Y-%m-%d-") + clean_name
26 | assert _re_blog_date.match(dtnm), f'{dtnm} is not a valid name, filename must be pre-pended with YYYY-MM-DD-'
27 | # push this into a set b/c _nb2htmlfname gets called multiple times per conversion
28 | if warnings: warnings.add((nb_path, dtnm))
29 | return dtnm
30 |
--------------------------------------------------------------------------------
/_action_files/settings.ini:
--------------------------------------------------------------------------------
1 | [DEFAULT]
2 | lib_name = nbdev
3 | user = fastai
4 | branch = master
5 | version = 0.2.10
6 | description = Writing a library entirely in notebooks
7 | keywords = jupyter notebook
8 | author = Sylvain Gugger and Jeremy Howard
9 | author_email = info@fast.ai
10 | baseurl =
11 | title = nbdev
12 | copyright = fast.ai
13 | license = apache2
14 | status = 2
15 | min_python = 3.6
16 | audience = Developers
17 | language = English
18 | requirements = nbformat>=4.4.0 nbconvert>=5.6.1 pyyaml fastscript packaging
19 | console_scripts = nbdev_build_lib=nbdev.cli:nbdev_build_lib
20 | nbdev_update_lib=nbdev.cli:nbdev_update_lib
21 | nbdev_diff_nbs=nbdev.cli:nbdev_diff_nbs
22 | nbdev_test_nbs=nbdev.cli:nbdev_test_nbs
23 | nbdev_build_docs=nbdev.cli:nbdev_build_docs
24 | nbdev_nb2md=nbdev.cli:nbdev_nb2md
25 | nbdev_trust_nbs=nbdev.cli:nbdev_trust_nbs
26 | nbdev_clean_nbs=nbdev.clean:nbdev_clean_nbs
27 | nbdev_read_nbs=nbdev.cli:nbdev_read_nbs
28 | nbdev_fix_merge=nbdev.cli:nbdev_fix_merge
29 | nbdev_install_git_hooks=nbdev.cli:nbdev_install_git_hooks
30 | nbdev_bump_version=nbdev.cli:nbdev_bump_version
31 | nbdev_new=nbdev.cli:nbdev_new
32 | nbdev_detach=nbdev.cli:nbdev_detach
33 | nbs_path = nbs
34 | doc_path = images/copied_from_nb
35 | doc_host = https://nbdev.fast.ai
36 | doc_baseurl = %(baseurl)s/images/copied_from_nb/
37 | git_url = https://github.com/fastai/nbdev/tree/master/
38 | lib_path = nbdev
39 | tst_flags = fastai2
40 | custom_sidebar = False
41 | cell_spacing = 1
42 | monospace_docstrings = False
43 | jekyll_styles = note,warning,tip,important,youtube,twitter
44 |
--------------------------------------------------------------------------------
/_word/README.md:
--------------------------------------------------------------------------------
1 | # Automatically Convert MS Word (*.docx) Documents To Blog Posts
2 |
3 | _Note: You can convert Google Docs to Word Docs by navigating to the File menu, and selecting Download > Microsoft Word (.docx)_
4 |
5 | [`fastpages`](https://github.com/fastai/fastpages) will automatically convert Word Documents (.docx) saved into this directory as blog posts!. Furthermore, images in your document are saved and displayed as you would expect on your blog post automatically.
6 |
7 | ## Usage
8 |
9 | 1. Create a Word Document (must be .docx) with the contents of your blog post.
10 |
11 | 2. Save your file with the naming convention `YYYY-MM-DD-*.docx` into the `/_word` folder of this repo. For example `2020-01-28-My-First-Post.docx`. This [naming convention is required by Jekyll](https://jekyllrb.com/docs/posts/) to render your blog post.
12 | - Be careful to name your file correctly! It is easy to forget the last dash in `YYYY-MM-DD-`. Furthermore, the character immediately following the dash should only be an alphabetical letter. Examples of valid filenames are:
13 |
14 | ```shell
15 | 2020-01-28-My-First-Post.docx
16 | 2012-09-12-how-to-write-a-blog.docx
17 | ```
18 |
19 | - If you fail to name your file correctly, `fastpages` will automatically attempt to fix the problem by prepending the last modified date of your notebook to your generated blog post. However, it is recommended that you name your files properly yourself for more transparency.
20 |
21 | 3. Synchronize your files with GitHub by [following the instructions in this blog post](https://www.fast.ai/2020/01/18/gitblog/).
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | help:
2 | cat Makefile
3 |
4 | # start (or restart) the services
5 | server: .FORCE
6 | docker-compose down --remove-orphans || true;
7 | docker-compose up
8 |
9 | # start (or restart) the services in detached mode
10 | server-detached: .FORCE
11 | docker-compose down || true;
12 | docker-compose up -d
13 |
14 | # build or rebuild the services WITHOUT cache
15 | build: .FORCE
16 | chmod 777 Gemfile.lock
17 | docker-compose stop || true; docker-compose rm || true;
18 | docker build --no-cache -t fastai/fastpages-jekyll -f _action_files/fastpages-jekyll.Dockerfile .
19 | docker-compose build --force-rm --no-cache
20 |
21 | # rebuild the services WITH cache
22 | quick-build: .FORCE
23 | docker-compose stop || true;
24 | docker build -t fastai/fastpages-jekyll -f _action_files/fastpages-jekyll.Dockerfile .
25 | docker-compose build
26 |
27 | # convert word & nb without Jekyll services
28 | convert: .FORCE
29 | docker-compose up converter
30 |
31 | # stop all containers
32 | stop: .FORCE
33 | docker-compose stop
34 | docker ps | grep fastpages | awk '{print $1}' | xargs docker stop
35 |
36 | # remove all containers
37 | remove: .FORCE
38 | docker-compose stop || true; docker-compose rm || true;
39 |
40 | # get shell inside the notebook converter service (Must already be running)
41 | bash-nb: .FORCE
42 | docker-compose exec watcher /bin/bash
43 |
44 | # get shell inside jekyll service (Must already be running)
45 | bash-jekyll: .FORCE
46 | docker-compose exec jekyll /bin/bash
47 |
48 | # restart just the Jekyll server
49 | restart-jekyll: .FORCE
50 | docker-compose restart jekyll
51 |
52 | .FORCE:
53 | chmod -R u+rw .
54 |
--------------------------------------------------------------------------------
/_action_files/word2post.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # This sets the environment variable when testing locally and not in a GitHub Action
4 | if [ -z "$GITHUB_ACTIONS" ]; then
5 | GITHUB_WORKSPACE='/data'
6 | echo "=== Running Locally: All assets expected to be in the directory /data ==="
7 | fi
8 |
9 | # Loops through directory of *.docx files and converts to markdown
10 | # markdown files are saved in _posts, media assets are saved in assets/img//media
11 | for FILENAME in "${GITHUB_WORKSPACE}"/_word/*.docx; do
12 | [ -e "$FILENAME" ] || continue # skip when glob doesn't match
13 | NAME=${FILENAME##*/} # Get filename without the directory
14 | NEW_NAME=$(python3 "/fastpages/word2post.py" "${FILENAME}") # clean filename to be Jekyll compliant for posts
15 | BASE_NEW_NAME=${NEW_NAME%.md} # Strip the file extension
16 |
17 | if [ -z "$NEW_NAME" ]; then
18 | echo "Unable To Rename: ${FILENAME} to a Jekyll complaint filename for blog posts"
19 | exit 1
20 | fi
21 |
22 | echo "Converting: ${NAME} ---to--- ${NEW_NAME}"
23 | cd ${GITHUB_WORKSPACE} || { echo "Failed to change to Github workspace directory"; exit 1; }
24 | pandoc --from docx --to gfm --output "${GITHUB_WORKSPACE}/_posts/${NEW_NAME}" --columns 9999 \
25 | --extract-media="assets/img/${BASE_NEW_NAME}" --standalone "${FILENAME}"
26 |
27 | # Inject correction to image links in markdown
28 | sed -i.bak 's/!\[\](assets/!\[\]({{ site.url }}{{ site.baseurl }}\/assets/g' "_posts/${NEW_NAME}"
29 | # Remove intermediate files
30 | rm _posts/*.bak 2> /dev/null || true
31 |
32 | cat "${GITHUB_WORKSPACE}/_action_files/word_front_matter.txt" "_posts/${NEW_NAME}" > temp && mv temp "_posts/${NEW_NAME}"
33 | done
34 |
--------------------------------------------------------------------------------
/_action_files/action_entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # setup ssh: allow key to be used without a prompt and start ssh agent
5 | export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
6 | eval "$(ssh-agent -s)"
7 |
8 | ######## Run notebook/word converter ########
9 | # word converter using pandoc
10 | /fastpages/word2post.sh
11 | # notebook converter using nbdev
12 | cp /fastpages/settings.ini .
13 | python /fastpages/nb2post.py
14 |
15 |
16 | ######## Optionally save files and build GitHub Pages ########
17 | if [[ "$INPUT_BOOL_SAVE_MARKDOWN" == "true" ]];then
18 |
19 | if [ -z "$INPUT_SSH_DEPLOY_KEY" ];then
20 | echo "You must set the SSH_DEPLOY_KEY input if BOOL_SAVE_MARKDOWN is set to true.";
21 | exit 1;
22 | fi
23 |
24 | # Get user's email from commit history
25 | if [[ "$GITHUB_EVENT_NAME" == "push" ]];then
26 | USER_EMAIL=$(jq '.commits | .[0] | .author.email' < "$GITHUB_EVENT_PATH")
27 | else
28 | USER_EMAIL="actions@github.com"
29 | fi
30 |
31 | # Setup Git credentials if we are planning to change the data in the repo
32 | git config --global user.name "$GITHUB_ACTOR"
33 | git config --global user.email "$USER_EMAIL"
34 | git remote add fastpages-origin "git@github.com:$GITHUB_REPOSITORY.git"
35 | echo "${INPUT_SSH_DEPLOY_KEY}" > _mykey
36 | chmod 400 _mykey
37 | ssh-add _mykey
38 |
39 | # Optionally save intermediate markdown
40 | if [[ "$INPUT_BOOL_SAVE_MARKDOWN" == "true" ]]; then
41 | git pull fastpages-origin "${GITHUB_REF}" --ff-only
42 | git add _posts
43 | git commit -m "[Bot] Update $INPUT_FORMAT blog posts" --allow-empty
44 | git push fastpages-origin HEAD:"$GITHUB_REF"
45 | fi
46 | fi
47 |
48 |
49 |
--------------------------------------------------------------------------------
/assets/badges/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/js/search-data.json:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 | {
4 | {% assign comma = false %}
5 | {%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%}
6 | {% for post in site.posts %}
7 | {% if post.search_exclude != true %}
8 | {% if comma == true%},{% endif %}"post{{ forloop.index0 }}": {
9 | "title": "{{ post.title | replace: '&', '&' }}",
10 | "content": "{{ post.content | markdownify | replace: ' 1
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Check if secret exists
24 | if: github.event_name == 'push'
25 | run: |
26 | if [ -z "$deploy_key" ]
27 | then
28 | echo "You do not have a secret named SSH_DEPLOY_KEY. This means you did not follow the setup instructions carefully. Please try setting up your repo again with the right secrets."
29 | exit 1;
30 | fi
31 | env:
32 | deploy_key: ${{ secrets.SSH_DEPLOY_KEY }}
33 |
34 | - name: Copy Repository Contents
35 | uses: actions/checkout@main
36 | with:
37 | persist-credentials: false
38 |
39 | - name: convert notebooks and word docs to posts
40 | uses: ./_action_files
41 |
42 | - name: setup directories for Jekyll build
43 | run: |
44 | rm -rf _site
45 | sudo chmod -R 777 .
46 |
47 | - name: Jekyll build
48 | uses: docker://fastai/fastpages-jekyll
49 | with:
50 | args: bash -c "jekyll build -V --strict_front_matter --trace"
51 | env:
52 | JEKYLL_ENV: "production"
53 |
54 | - name: copy CNAME file into _site if CNAME exists
55 | run: |
56 | sudo chmod -R 777 _site/
57 | cp CNAME _site/ 2>/dev/null || :
58 |
59 | - name: Deploy
60 | if: github.event_name == 'push'
61 | uses: peaceiris/actions-gh-pages@v3
62 | with:
63 | deploy_key: ${{ secrets.SSH_DEPLOY_KEY }}
64 | publish_dir: ./_site
65 |
--------------------------------------------------------------------------------
/assets/badges/colab.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/_posts/2020-01-14-test-markdown-post.md:
--------------------------------------------------------------------------------
1 | ---
2 | toc: true
3 | layout: post
4 | description: A minimal example of using markdown with fastpages.
5 | categories: [markdown]
6 | title: An Example Markdown Post
7 | ---
8 | # Example Markdown Post
9 |
10 | ## Basic setup
11 |
12 | Jekyll requires blog post files to be named according to the following format:
13 |
14 | `YEAR-MONTH-DAY-filename.md`
15 |
16 | Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `filename` is whatever file name you choose, to remind yourself what this post is about. `.md` is the file extension for markdown files.
17 |
18 | The first line of the file should start with a single hash character, then a space, then your title. This is how you create a "*level 1 heading*" in markdown. Then you can create level 2, 3, etc headings as you wish but repeating the hash character, such as you see in the line `## File names` above.
19 |
20 | ## Basic formatting
21 |
22 | You can use *italics*, **bold**, `code font text`, and create [links](https://www.markdownguide.org/cheat-sheet/). Here's a footnote [^1]. Here's a horizontal rule:
23 |
24 | ---
25 |
26 | ## Lists
27 |
28 | Here's a list:
29 |
30 | - item 1
31 | - item 2
32 |
33 | And a numbered list:
34 |
35 | 1. item 1
36 | 1. item 2
37 |
38 | ## Boxes and stuff
39 |
40 | > This is a quotation
41 |
42 | {% include alert.html text="You can include alert boxes" %}
43 |
44 | ...and...
45 |
46 | {% include info.html text="You can include info boxes" %}
47 |
48 | ## Images
49 |
50 | 
51 |
52 | ## Code
53 |
54 | You can format text and code per usual
55 |
56 | General preformatted text:
57 |
58 | # Do a thing
59 | do_thing()
60 |
61 | Python code and output:
62 |
63 | ```python
64 | # Prints '2'
65 | print(1+1)
66 | ```
67 |
68 | 2
69 |
70 | Formatting text as shell commands:
71 |
72 | ```shell
73 | echo "hello world"
74 | ./some_script.sh --option "value"
75 | wget https://example.com/cat_photo1.png
76 | ```
77 |
78 | Formatting text as YAML:
79 |
80 | ```yaml
81 | key: value
82 | - another_key: "another value"
83 | ```
84 |
85 |
86 | ## Tables
87 |
88 | | Column 1 | Column 2 |
89 | |-|-|
90 | | A thing | Another thing |
91 |
92 |
93 | ## Tweetcards
94 |
95 | {% twitter https://twitter.com/jakevdp/status/1204765621767901185?s=20 %}
96 |
97 |
98 | ## Footnotes
99 |
100 |
101 |
102 | [^1]: This is the footnote.
103 |
104 |
--------------------------------------------------------------------------------
/_fastpages_docs/_manual_setup.md:
--------------------------------------------------------------------------------
1 | # Manual Setup Instructions
2 |
3 | These are the setup steps that are automated by [setup.yaml](.github/workflows/setup.yaml)
4 |
5 | 1. Click the [](https://github.com/fastai/fastpages/generate) button to create a copy of this repo in your account.
6 |
7 | 2. [Follow these instructions to create an ssh-deploy key](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys). Make sure you **select Allow write access** when adding this key to your GitHub account.
8 |
9 | 3. [Follow these instructions to upload your deploy key](https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets) as an encrypted secret on GitHub. Make sure you name your key `SSH_DEPLOY_KEY`. Note: The deploy key secret is your **private key** (NOT the public key).
10 |
11 | 4. [Create a branch](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository#creating-a-branch) named `gh-pages`.
12 |
13 | 5. Change the badges on this README to point to **your** repository instead of `fastai/fastpages`. Badges are organized in a section at the beginning of this README. For example, you should replace `fastai` and `fastpages` in the below url:
14 |
15 | ``
16 |
17 | to
18 |
19 | ``
20 |
21 | 6. Change `baseurl:` in `_config.yaml` to the name of your repository. For example, instead of
22 |
23 | `baseurl: "/fastpages"`
24 |
25 | this should be
26 |
27 | `baseurl: "/your-repo-name"`
28 |
29 | 7. Similarly, change the `url:` parameter in `_config.yaml` to the url your blog will be served on. For example, instead of
30 |
31 | `url: "https://fastpages.fast.ai/"`
32 |
33 | this should be
34 |
35 | `url: "https://.github.io"`
36 |
37 | 8. Read through `_config.yaml` carefully as there may be other options that must be set. The comments in this file will provide instructions.
38 |
39 | 9. Delete the `CNAME` file from the root of your `master` branch (or change it if you are using a custom domain)
40 |
41 | 10. Go to your [repository settings and enable GitHub Pages](https://help.github.com/en/enterprise/2.13/user/articles/configuring-a-publishing-source-for-github-pages) with the `gh-pages` branch you created earlier.
--------------------------------------------------------------------------------
/_layouts/home.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 |
--------------------------------------------------------------------------------
/_fastpages_docs/_setup_pr_template.md:
--------------------------------------------------------------------------------
1 | Hello :wave: @{_username_}! Thank you for using fastpages!
2 |
3 | ## Before you merge this PR
4 |
5 | 1. Create an ssh key-pair. Open this utility. Select: `RSA` and `4096` and leave `Passphrase` blank. Click the blue button `Generate-SSH-Keys`.
6 |
7 | 2. Navigate to this link and click `New repository secret`. Copy and paste the **Private Key** into the `Value` field. This includes the "---BEGIN RSA PRIVATE KEY---" and "--END RSA PRIVATE KEY---" portions. **In the `Name` field, name the secret `SSH_DEPLOY_KEY`.**
8 |
9 | 3. Navigate to this link and click the `Add deploy key` button. Paste your **Public Key** from step 1 into the `Key` box. In the `Title`, name the key anything you want, for example `fastpages-key`. Finally, **make sure you click the checkbox next to `Allow write access`** (pictured below), and click `Add key` to save the key.
10 |
11 | 
12 |
13 |
14 | ### What to Expect After Merging This PR
15 |
16 | - GitHub Actions will build your site, which will take 2-3 minutes to complete. **This will happen anytime you push changes to the master branch of your repository.** You can monitor the logs of this if you like on the [Actions tab of your repo](https://github.com/{_username_}/{_repo_name_}/actions).
17 | - Your GH-Pages Status badge on your README will eventually appear and be green, indicating your first successful build.
18 | - You can monitor the status of your site in the GitHub Pages section of your [repository settings](https://github.com/{_username_}/{_repo_name_}/settings).
19 |
20 | If you are not using a custom domain, your website will appear at:
21 |
22 | #### https://{_username_}.github.io/{_repo_name_}
23 |
24 |
25 | ## Optional: Using a Custom Domain
26 |
27 | 1. After merging this PR, add a file named `CNAME` at the root of your repo. For example, the `fastpages` blog is hosted at `https://fastpages.fast.ai`, which means [our CNAME](https://github.com/fastai/fastpages/blob/master/CNAME) contains the following contents:
28 |
29 |
30 | >`fastpages.fast.ai`
31 |
32 |
33 | 2. Change the `url` and `baseurl` parameters in your `/_config.yml` file to reflect your custom domain.
34 |
35 |
36 | Wondering how to setup a custom domain? See [this article](https://dev.to/trentyang/how-to-setup-google-domain-for-github-pages-1p58). You must add a CNAME file to the root of your master branch for the intructions in the article to work correctly.
37 |
38 |
39 | ## Questions
40 |
41 | Please use the [nbdev & blogging channel](https://forums.fast.ai/c/fastai-users/nbdev/48) in the fastai forums for any questions or feature requests.
42 |
--------------------------------------------------------------------------------
/_layouts/post.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 |
6 |
7 |
{{ page.title | escape }}
8 | {%- if page.description -%}
9 | {%- if site.html_escape.description -%}
10 |
61 | {%- if page.comments -%}
62 | {%- include utterances.html -%}
63 | {%- endif -%}
64 | {%- if site.disqus.shortname -%}
65 | {%- include disqus_comments.html -%}
66 | {%- endif -%}
67 |
68 |
69 |
--------------------------------------------------------------------------------
/assets/badges/binder.svg:
--------------------------------------------------------------------------------
1 | launchlaunchbinderbinder
--------------------------------------------------------------------------------
/.github/workflows/check_config.yaml:
--------------------------------------------------------------------------------
1 | name: Check Configurations
2 | on: push
3 |
4 | permissions:
5 | actions: write
6 | pull-requests: write
7 | contents: write
8 |
9 | jobs:
10 | check-config:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@main
14 |
15 | - name: Set up Python
16 | uses: actions/setup-python@v1
17 | with:
18 | python-version: 3.7
19 |
20 | - name: install dependencies
21 | run: pip3 install pyyaml
22 |
23 | - name: check baseurl
24 | id: baseurl
25 | run: |
26 | import yaml
27 | from pathlib import Path
28 | from configparser import ConfigParser
29 | settings = ConfigParser()
30 |
31 | config_path = Path('_config.yml')
32 | settings_path = Path('_action_files/settings.ini')
33 |
34 | assert config_path.exists(), 'Did not find _config.yml in the current directory!'
35 | assert settings_path.exists(), 'Did not find _action_files/settings.ini in the current directory!'
36 |
37 | settings.read(settings_path)
38 | with open('_config.yml') as f:
39 | config = yaml.safe_load(f)
40 |
41 | errmsg = f"The value set for baseurl in _action_files/settings.ini and _config.yml are not identical. Please fix and try again."
42 | assert config['baseurl'] == settings['DEFAULT']['baseurl'], errmsg
43 | shell: python
44 |
45 | - name: Create issue if baseurl rule is violated
46 | if: steps.baseurl.outcome == 'failure'
47 | uses: actions/github-script@0.6.0
48 | with:
49 | github-token: ${{secrets.GITHUB_TOKEN}}
50 | script: |
51 | var err = process.env.ERROR_STRING;
52 | var run_id = process.env.RUN_ID;
53 | github.issues.create({
54 | owner: context.repo.owner,
55 | repo: context.repo.repo,
56 | title: "Error with repository configuration: baseurl",
57 | body: `${err}\n See run [${run_id}](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${run_id}) for more details.`
58 | })
59 | env:
60 | ERROR_STRING: "You have not configured your baseurl correctly, please read the instructions in _config.yml carefully."
61 | RUN_ID: ${{ github.run_id }}
62 |
63 | - name: check for User Pages
64 | id: userpage
65 | run: |
66 | import os
67 | nwo = os.getenv('GITHUB_REPOSITORY')
68 | errmsg = "fastpages does not support User Pages or repo names that end with github.io, please see https://forums.fast.ai/t/fastpages-replacing-main-username-github-io-page-w-fastpages/64316/3 for more details."
69 | assert ".github.io" not in nwo, errmsg
70 | shell: python
71 |
72 | - name: Create Issue if User Pages rule is violated
73 | if: steps.userpage.outcome == 'failure'
74 | uses: actions/github-script@0.6.0
75 | with:
76 | github-token: ${{secrets.GITHUB_TOKEN}}
77 | script: |
78 | github.issues.create({
79 | owner: context.repo.owner,
80 | repo: context.repo.repo,
81 | title: "Error with repository configuration: repo name",
82 | body: 'fastpages does not support User Pages or repo names that end with github.io, please see https://forums.fast.ai/t/fastpages-replacing-main-username-github-io-page-w-fastpages/64316/3 for more details.'
83 | })
84 |
--------------------------------------------------------------------------------
/_includes/custom-head.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons:
3 |
4 | 1. Head over to https://realfavicongenerator.net/ to add your own favicons.
5 | 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet.
6 | {% endcomment %}
7 |
8 |
9 | {%- include favicons.html -%}
10 |
11 |
12 |
13 | {%- if site.annotations -%}
14 |
15 | {%- endif -%}
16 |
17 | {% if site.use_math %}
18 |
19 |
20 |
21 |
22 |
33 | {% endif %}
34 |
35 |
56 |
57 |
64 |
--------------------------------------------------------------------------------
/_fastpages_docs/README_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | [//]: # (This template replaces README.md when someone creates a new repo with the fastpages template.)
2 |
3 | 
4 | 
5 | [](https://github.com/fastai/fastpages)
6 |
7 | https://{_username_}.github.io/{_repo_name_}/
8 |
9 | # My Blog
10 |
11 |
12 | _powered by [fastpages](https://github.com/fastai/fastpages)_
13 |
14 |
15 | ## What To Do Next?
16 |
17 | Great! You have setup your repo. Now its time to start writing content. Some helpful links:
18 |
19 | - [Writing Blogs With Jupyter](https://github.com/fastai/fastpages#writing-blog-posts-with-jupyter)
20 |
21 | - [Writing Blogs With Markdown](https://github.com/fastai/fastpages#writing-blog-posts-with-markdown)
22 |
23 | - [Writing Blog Posts With Word](https://github.com/fastai/fastpages#writing-blog-posts-with-microsoft-word)
24 |
25 | - [(Optional) Preview Your Blog Locally](_fastpages_docs/DEVELOPMENT.md)
26 |
27 | Note: you may want to remove example blog posts from the `_posts`, `_notebooks` or `_word` folders (but leave them empty, don't delete these folders) if you don't want these blog posts to appear on your site.
28 |
29 | Please use the [nbdev & blogging channel](https://forums.fast.ai/c/fastai-users/nbdev/48) in the fastai forums for any questions or feature requests.
30 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | # Welcome to Jekyll!
2 | #
3 | # This config file is meant for settings that affect your whole blog.
4 | #
5 | # If you need help with YAML syntax, here are some quick references for you:
6 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml
7 | # https://learnxinyminutes.com/docs/yaml/
8 |
9 | title: fastpages
10 | description: An easy to use blogging platform with support for Jupyter Notebooks.
11 | github_username: fastai
12 | # you can comment the below line out if your repo name is not different than your baseurl
13 | github_repo: "fastpages"
14 |
15 | # OPTIONAL: override baseurl and url if using a custom domain
16 | # Note: leave out the trailing / from this value.
17 | url: "https://fastpages.fast.ai" # the base hostname & protocol for your site, e.g. http://example.com
18 |
19 | ###########################################################
20 | ######### Special Instructions for baseurl ###############
21 | #
22 | #### Scenario One: If you do not have a Custom Domain #####
23 | # - if you are not using a custom domain, the baseurl *must* be set to your repo name
24 | #
25 | #### Scenario Two: If you have a Custom Domain #####
26 | # 1. If your domain does NOT have a subpath, this leave this value as ""
27 | # 2. If your domain does have a subpath, you must preceed the value with a / and NOT have a / at the end.
28 | # For example:
29 | # "" is valid
30 | # "/blog" is valid
31 | # "/blog/site/" is invalid ( / at the end)
32 | # "/blog/site" is valid
33 | # "blog/site" is invalid ( because doesn't begin with a /)
34 | #
35 | # 3. You must replace the parameter `baseurl` in _action_files/settings.ini with the same value as you set here but WITHOUT QUOTES.
36 | #
37 | baseurl: "" # the subpath of your site, e.g. "/blog".
38 |
39 | # Github and twitter are optional:
40 | minima:
41 | social_links:
42 | twitter: fastdotai
43 | github: fastai
44 |
45 | # Set this to true to get LaTeX math equation support
46 | use_math:
47 |
48 | # Set this to true to display the summary of your blog post under your title on the Home page.
49 | show_description: true
50 |
51 | # Set this to true to display image previews on home page, if they exist
52 | show_image: false
53 |
54 | # Set this to true to turn on annotations with hypothes.is (https://web.hypothes.is/)
55 | annotations: false
56 |
57 | # Set this to true to display tags on each post
58 | show_tags: true
59 |
60 | # Add your Google Analytics ID here if you have one and want to use it
61 | google_analytics: UA-57531313-5
62 |
63 | exclude:
64 | - docker-compose.yml
65 | - action.yml
66 | - Makefile
67 |
68 | # this setting allows you to keep pages organized in the _pages folder
69 | include:
70 | - _pages
71 |
72 | # This specifies what badges are turned on by default for notebook posts.
73 | default_badges:
74 | github: true
75 | binder: true
76 | colab: true
77 | deepnote: true
78 |
79 | # Escape HTML in post descriptions
80 | html_escape:
81 | description: false
82 |
83 | # Everything below here should be left alone. Modifications may break fastpages
84 | future: true
85 | theme: minima
86 | plugins:
87 | - jekyll-feed
88 | - jekyll-gist
89 | - jekyll-octicons
90 | - jekyll-toc
91 | - jekyll-twitter-plugin
92 | - jekyll-relative-links
93 | - jekyll-seo-tag
94 | - jekyll-remote-theme
95 | - jekyll-paginate
96 | - jekyll-sitemap
97 | - jemoji
98 |
99 | # See https://jekyllrb.com/docs/pagination/
100 | # For pagination to work, you cannot have index.md at the root of your repo, instead you must rename this file to index.html
101 | paginate: 15
102 | paginate_path: /page:num/
103 |
104 | remote_theme: jekyll/minima
105 |
106 | titles_from_headings:
107 | enabled: true
108 | strip_title: true
109 | collections: true
110 |
111 | highlighter: rouge
112 | markdown: kramdown
113 | kramdown:
114 | math_engine: katex
115 | input: GFM
116 | auto_ids: true
117 | hard_wrap: false
118 | syntax_highlighter: rouge
119 |
120 | # to limit size of xml as it can grow quite large.
121 | feed:
122 | posts_limit: 5 #default posts_limit: 10
123 | excerpt_only: true
124 |
125 | exclude:
126 | - settings.ini
127 |
--------------------------------------------------------------------------------
/_fastpages_docs/DEVELOPMENT.md:
--------------------------------------------------------------------------------
1 | # Development Guide
2 | - [Seeing All Options From the Terminal](#seeing-all-commands-in-the-terminal)
3 | - [Basic usage: viewing your blog](#basic-usage-viewing-your-blog)
4 | - [Converting the pages locally](#converting-the-pages-locally)
5 | - [Visual Studio Code integration](#visual-studio-code-integration)
6 | - [Advanced usage](#advanced-usage)
7 | - [Rebuild all the containers](#rebuild-all-the-containers)
8 | - [Removing all the containers](#removing-all-the-containers)
9 | - [Attaching a shell to a container](#attaching-a-shell-to-a-container)
10 |
11 | You can run your fastpages blog on your local machine, and view any changes you make to your posts, including Jupyter Notebooks and Word documents, live.
12 | The live preview requires that you have Docker installed on your machine. [Follow the instructions on this page if you need to install Docker.](https://www.docker.com/products/docker-desktop)
13 |
14 | ## Seeing All Commands In The Terminal
15 |
16 | There are many different `docker-compose` commands that are necessary to manage the lifecycle of the fastpages Docker containers. To make this easier, we aliased common commands in a [Makefile](https://www.gnu.org/software/make/manual/html_node/Introduction.html).
17 |
18 | You can quickly see all available commands by running this command in the root of your repository:
19 |
20 | `make`
21 |
22 | ## Basic usage: viewing your blog
23 |
24 | All of the commands in this block assume that you're in your blog root directory.
25 | To run the blog with live preview:
26 |
27 | ```bash
28 | make server
29 | ```
30 |
31 | When you run this command for the first time, it'll build the required Docker images, and the process might take a couple minutes.
32 |
33 | This command will build all the necessary containers and run the following services:
34 | 1. A service that monitors any changes in `./_notebooks/*.ipynb/` and `./_word/*.docx;*.doc` and rebuild the blog on change.
35 | 2. A Jekyll server on https://127.0.0.1:4000 — use this to preview your blog.
36 |
37 | The services will output to your terminal. If you close the terminal or hit `Ctrl-C`, the services will stop.
38 | If you want to run the services in the background:
39 |
40 | ```bash
41 | # run all services in the background
42 | make server-detached
43 |
44 | # stop the services
45 | make stop
46 | ```
47 |
48 | If you need to restart just the Jekyll server, and it's running in the background — you can do `make restart-jekyll`.
49 |
50 | _Note that the blog won't autoreload on change, you'll have to refresh your browser manually._
51 |
52 | **If containers won't start**: try `make build` first, this would rebuild all the containers from scratch, This might fix the majority of update problems.
53 |
54 | ## Converting the pages locally
55 |
56 | If you just want to convert your notebooks and word documents to `.md` posts in `_posts`, this command will do it for you:
57 |
58 | ```bash
59 | make convert
60 | ```
61 |
62 | You can launch just the jekyll server with `make server`.
63 |
64 | ## Visual Studio Code integration
65 |
66 | If you're using VSCode with the Docker extension, you can run these containers from the sidebar: `fastpages_watcher_1` and `fastpages_jekyll_1`.
67 | The containers will only show up in the list after you run or build them for the first time. So if they're not in the list — try `make build` in the console.
68 |
69 | ## Advanced usage
70 |
71 | ### Rebuild all the containers
72 | If you changed files in `_action_files` directory, you might need to rebuild the containers manually, without cache.
73 |
74 | ```bash
75 | make build
76 | ```
77 |
78 | ### Removing all the containers
79 | Want to start from scratch and remove all the containers?
80 |
81 | ```
82 | make remove
83 | ```
84 |
85 | ### Attaching a shell to a container
86 | You can attach a terminal to a running service:
87 |
88 | ```bash
89 |
90 | # If the container is already running:
91 |
92 | # attach to a bash shell in the jekyll service
93 | make bash-jekyll
94 |
95 | # attach to a bash shell in the watcher service.
96 | make bash-nb
97 | ```
98 |
99 | _Note: you can use `docker-compose run` instead of `make bash-nb` or `make bash-jekyll` to start a service and then attach to it.
100 | Or you can run all your services in the background, `make server-detached`, and then use `make bash-nb` or `make bash-jekyll` as in the examples above._
101 |
102 |
--------------------------------------------------------------------------------
/_fastpages_docs/UPGRADE.md:
--------------------------------------------------------------------------------
1 | # Upgrading fastpages
2 |
3 |
4 |
5 | - [Automated Upgrade](#automated-upgrade)
6 | - [Step 1: Open An Issue With The Upgrade Template.](#step-1-open-an-issue-with-the-upgrade-template)
7 | - [Step 2: Click `Submit new issue`](#step-2-click-submit-new-issue)
8 | - [Step 3: A Link to Pull Request Will Appear](#step-3-a-link-to-pull-request-will-appear)
9 | - [Step 4: Review & Merge PR](#step-4-review-merge-pr)
10 | - [Manual Upgrade](#manual-upgrade)
11 | - [Easy Way (Recommended)](#easy-way-recommended)
12 | - [Advanced](#advanced)
13 | - [Additional Resources](#additional-resources)
14 |
15 |
16 |
17 | **For fastpages repos that are older than December 1st, 2020 the only way to upgrade is to create a brand-new fastpages repo and copy your blog post files into it.** This is because of breaking changes that were made to GitHub Actions around that time.
18 |
19 | There are two ways to upgrade fastpages. One is an automated way that assumes you have made no changes to the HTML of your site. Alternatively, you may [upgrade manually](#manual-upgrade) and determine which changes to accept or reject. For most people we recommend upgrading fastpages automatically.
20 |
21 | ## Automated Upgrade
22 |
23 | - This method is appropriate for those who have not customized the HTML of their site.
24 | - **If you are unsure, try the Automated approach and review which files are changed in the automated PR** to see if this appropriate for you.
25 |
26 | ### Step 1: Open An Issue With The Upgrade Template.
27 |
28 | - Open a new issue in your repository, and push the "Get Started" button for the `[fastpages] Automated Upgrade` Issue template, which looks like this:
29 | - **IF YOU DON'T SEE THIS**: you have an older version of fastpages and you **must [manually upgrade](#manual-upgrade) once** to get this new functionality.
30 |
31 | 
32 |
33 | ### Step 2: Click `Submit new issue`
34 |
35 | - Be careful not to change anything before clicking the button.
36 |
37 | 
38 |
39 | ### Step 3: A Link to Pull Request Will Appear
40 |
41 | - This issue will trigger GitHub to open a PR making changes to your repository for the upgrade to take palce. A comment with the link to the PR will be made in the issue, and will look like this:
42 |
43 | 
44 |
45 | It is possible that you might receive an error message instead of this command. You can follow the instructions in the comment to troubleshoot the issue. Common reasons for receiving an error are:
46 |
47 | - You are up to date, therefore no upgrade is possible. You will see an error that there is "nothing to commit".
48 | - You already have a PR from a previous upgrade open that you never merged.
49 |
50 | Please [ask on the forums](https://forums.fast.ai/) if you have encounter another problem that is unclear.
51 |
52 | ### Step 4: Review & Merge PR
53 |
54 | - Ensure that you read the instructions in the PR carefully. Furthermore, carefully review which files will be changed to determine if this interferes with any customizations you have mades to your site. When ready, select `Merge pull request`.
55 | - If the PR is making undesired changes to files you can use the manual upgrade approach instead.
56 |
57 | ## Manual Upgrade
58 |
59 | ### Easy Way (Recommended)
60 |
61 | Create a new repo with the current `fastpages` template by following the [setup instructions](https://github.com/fastai/fastpages#setup-instructions) in the README, and copy all of your blog posts from `_notebooks`, `_word`, and `_posts` into the new template. This is very similar to what the automated process is doing.
62 |
63 | ### Advanced
64 |
65 | - This method is appropriate for those who made customizations to the HTML of fastpages.
66 | - You must proceed with caution, as new versions of fastpages may not be compatible with your customizations.
67 | - You can use git to perform the upgrade by [following this approach](https://stackoverflow.com/questions/56577184/github-pull-changes-from-a-template-repository/56577320) instead. A step-by-step companion to this stack overflow post with screenshots is [written up here](https://github.com/fastai/fastpages/issues/163#issuecomment-593766189).
68 | - Be careful to not duplicate files, as files in fastpages have been reorganized several times.
69 |
70 |
71 | ## Additional Resources
72 |
73 | - [This Actions workflow](/.github/workflows/upgrade.yaml) defines the automated upgrade process.
74 | - You can get more help with upgrading in the [fastai forums - nbdev & blogging category](https://forums.fast.ai/c/fastai-users/nbdev/48).
75 |
--------------------------------------------------------------------------------
/assets/badges/deepnote.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/_fastpages_docs/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
1 | # Filing Bugs & Troubleshooting
2 |
3 | These are required prerequisites before filing an issue on GitHub or on the [fastai forums](https://forums.fast.ai/)
4 |
5 | ## Step 1: Attempt To Upgrade fastpages
6 |
7 | See the [Upgrading guide](https://github.com/fastai/fastpages/blob/master/_fastpages_docs/UPGRADE.md).
8 |
9 | **In addition to upgrading**, if developing locally, refresh your Docker containers with the following commands from the root of your repo:
10 |
11 | `make remove` followed by `make build`
12 |
13 | ## Step 2: Search Relevant Places For Similar Issues
14 |
15 | - [ ] Search the [fastai forums](https://forums.fast.ai/) for similar problems.
16 | - [ ] Search issues on the [fastpages repo](https://github.com/fastai/fastpages/) for a similar problems?
17 | - [ ] Read the [README of the repo](https://github.com/fastai/fastpages/blob/master/README.md) carefully
18 |
19 |
20 | ## Step 3: Observe Build Logs When Developing Locally
21 |
22 | - [ ] Run the [fastpages blog server locally](DEVELOPMENT.md)
23 | - Pay attention to the emitted logs when you save your notebooks or files. Often, you will see errors here that will give you important clues.
24 | - [ ] When developing locally, you will notice that Jupyter notebooks are converted to corresponding markdown files in the `_posts` folder. Take a look at the problematic blog posts and see if you can spot the offending HTML or markdown in that code.
25 | - Use your browser's developer tools to see if there are any errors. Common errors are (1) not able to find images because they have not been saved into the right folder, (2) javascript or other errors.
26 | - If you receive a Jekyll build error or a Liquid error, search for this error on Stack Overflow to provide more insight on the problem.
27 |
28 | ## Step 4: See if there are errors in the build process of GitHub Actions.
29 |
30 | - [ ] In your GitHub repository, you will have a tab called **Actions**. To find build errors, click on the `Event` dropdown list and select `push`. Browse through tthe logs to see if you can find an error. If you receive an error, read the error message and try to debug.
31 |
32 | ## Step 5: Once you have performed all the above steps, post your issue in the fastai forums or a GitHub Issue.
33 |
34 | - [ ] Use the [nbdev & blogging category](https://forums.fast.ai/c/fastai-users/nbdev/48) to specify your problem if posting on the fastpages forums.
35 | - [ ] If you cannot find a similar issue create a new thread instead of commenting on an unrelated one.
36 | - When reporting a bug, provide this information:
37 |
38 | 1. Steps to reproduce the problem
39 | 2. **A link to the notebook or markdown file** where the error is occuring
40 | 3. If the error is happening in GitHub Actions, a link to the specific error along with how you are able to reproduce this error. You must provide this **in addition to the link to the notebook or markdown file**.
41 | 4. A screenshot / dump of relevant logs or error messages you are receiving from your local development environment. the local development server as indicated in the [development guide](https://github.com/fastai/fastpages/blob/master/_fastpages_docs/DEVELOPMENT.md).
42 |
43 |
44 | **You must provide ALL of the above information**.
45 |
46 | # Frequent Errors
47 |
48 | 1. Malformed front matter. Note that anything defined in front matter must be valid YAML. **Failure to provide valid YAML could result in your page not rendering** in your blog. For example, if you want a colon in your title you must escape it with double quotes like this:
49 |
50 | ` - title: "Deep learning: A tutorial"`
51 |
52 | or in a notebook
53 |
54 | `# "Deep learning: A tutorial"`
55 |
56 | See this [tutorial on YAML](https://rollout.io/blog/yaml-tutorial-everything-you-need-get-started/) for more information.
57 |
58 | **Colons, tilda, asteriks and other characters may cause your front matter to break and for your posts to not render.** If you violoate these conventions you often get an error that looks something like this:
59 |
60 | ```bash
61 | Error: YAML Exception reading ... (): mapping values are not allowed
62 | ```
63 |
64 | 2. Can you customize the styling or theme of fastpages? **A**: See [Customizing Fastpages](https://github.com/fastai/fastpages#customizing-fastpages)
65 |
66 | 3. Your initial build failed on GH-Pages Status
67 |
68 | `Error messsage: Unable to build page. Please try again later.` `Error: Process completed with exit code 1.`
69 |
70 | If your github username contains capital letters e.g. YourUserName, go to [config file](../_config.yml#L17) line 17 and rename `YourUserName.github.io` to `yourusername.github.io`. After the commit blog should build without error.
71 |
72 | See the [FAQ](https://github.com/fastai/fastpages#faq) for frequently asked questions.
73 |
--------------------------------------------------------------------------------
/_sass/minima/fastpages-dracula-highlight.scss:
--------------------------------------------------------------------------------
1 | // Override Syntax Highlighting In Minima With the Dracula Theme: https://draculatheme.com/
2 | // If you wish to override any of this CSS, do so in _sass/minima/custom-styles.css
3 |
4 | $dt-gray-dark: #282a36; // Background
5 | $dt-code-cell-background: #323443;
6 | $dt-gray: #44475a; // Current Line & Selection
7 | $dt-gray-light: #f8f8f2; // Foreground
8 | $dt-blue: #6272a4; // Comment
9 | $dt-cyan: #8be9fd;
10 | $dt-green: #50fa7b;
11 | $dt-orange: #ffb86c;
12 | $dt-pink: #ff79c6;
13 | $dt-purple: #bd93f9;
14 | $dt-red: #ff5555;
15 | $dt-yellow: #f1fa8c;
16 | $dt-green-light: rgb(172, 229, 145);
17 |
18 | .language-python + .language-plaintext {
19 | border-left: 1px solid grey;
20 | margin-left: 1rem !important;
21 | }
22 |
23 | // ensure dark background for code in markdown
24 | [class^="language-"]:not(.language-plaintext) pre,
25 | [class^="language-"]:not(.language-plaintext) code {
26 | background-color: $dt-code-cell-background !important;
27 | color: $dt-gray-light;
28 | }
29 |
30 | .language-python + .language-plaintext code { background-color: white !important; }
31 | .language-python + .language-plaintext pre { background-color: white !important; }
32 |
33 | // for Jupyter Notebook HTML Code Cells modified from https://www.fast.ai/public/css/hyde.css
34 |
35 | .input_area pre, .input_area div {
36 | margin-bottom:1.0rem !important;
37 | margin-top:1.5rem !important;
38 | padding-bottom:0 !important;
39 | padding-top:0 !important;
40 | background: #323443 !important;
41 | -webkit-font-smoothing: antialiased;
42 | text-rendering: optimizeLegibility;
43 | font-family: Menlo, Monaco, Consolas, "Lucida Console", Roboto, Ubuntu, monospace;
44 | border-radius: 5px;
45 | font-size: 105%;
46 | }
47 | .output_area pre, .output_area div {
48 | margin-bottom:1rem !important;
49 | margin-top:0rem !important;
50 | padding-bottom:0 !important;
51 | padding-top:0 !important;
52 | }
53 | .input_area pre {
54 | border-left: 1px solid lightcoral;
55 | }
56 | .output_area pre {
57 | border-left: 1px solid grey;
58 | margin-left: 1rem !important;
59 | font-size: 16px;
60 | }
61 |
62 | .code_cell table { width: auto; }
63 |
64 | /* Dracula Theme v1.2.5
65 | *
66 | * https://github.com/zenorocha/dracula-theme
67 | *
68 | * Copyright 2016, All rights reserved
69 | *
70 | * Code licensed under the MIT license
71 | *
72 | */
73 |
74 | .highlight {
75 | background: $dt-code-cell-background !important;
76 | color: $dt-gray-light !important;
77 | pre, code {
78 | background: $dt-code-cell-background;
79 | color: $dt-gray-light;
80 | font-size: 110%;
81 | }
82 |
83 | .hll,
84 | .s,
85 | .sa,
86 | .sb,
87 | .sc,
88 | .dl,
89 | .sd,
90 | .s2,
91 | .se,
92 | .sh,
93 | .si,
94 | .sx,
95 | .sr,
96 | .s1,
97 | .ss {
98 | color:rgb(231, 153, 122);
99 | }
100 |
101 | .go {
102 | color: $dt-gray;
103 | }
104 |
105 | .err,
106 | .g,
107 | .l,
108 | .n,
109 | .x,
110 | .ge,
111 | .gr,
112 | .gh,
113 | .gi,
114 | .gp,
115 | .gs,
116 | .gu,
117 | .gt,
118 | .ld,
119 | .no,
120 | .nd,
121 | .pi,
122 | .ni,
123 | .ne,
124 | .nn,
125 | .nx,
126 | .py,
127 | .w,
128 | .bp {
129 | color: $dt-gray-light;
130 | background-color: $dt-code-cell-background !important;
131 | }
132 |
133 | .p {
134 | font-weight: bold;
135 | color: rgb(102, 217, 239);
136 | }
137 |
138 | .ge {
139 | text-decoration: underline;
140 | }
141 |
142 | .bp {
143 | font-style: italic;
144 | }
145 |
146 | .c,
147 | .ch,
148 | .cm,
149 | .cpf,
150 | .cs {
151 | color: $dt-blue;
152 | }
153 |
154 | .c1 {
155 | color: gray;
156 | }
157 |
158 | .kd,
159 | .kt,
160 | .nb,
161 | .nl,
162 | .nv,
163 | .vc,
164 | .vg,
165 | .vi,
166 | .vm {
167 | color: $dt-cyan;
168 | }
169 |
170 | .kd,
171 | .nb,
172 | .nl,
173 | .nv,
174 | .vc,
175 | .vg,
176 | .vi,
177 | .vm {
178 | font-style: italic;
179 | }
180 |
181 | .fm,
182 | .na,
183 | .nc,
184 | .nf
185 | {
186 | color: $dt-green-light;
187 | }
188 |
189 | .k,
190 | .o,
191 | .cp,
192 | .kc,
193 | .kn,
194 | .kp,
195 | .kr,
196 | .nt,
197 | .ow {
198 | color: $dt-pink;
199 | }
200 |
201 | .kc {
202 | color: $dt-green-light;
203 | }
204 |
205 | .m,
206 | .mb,
207 | .mf,
208 | .mh,
209 | .mi,
210 | .mo,
211 | .il {
212 | color: $dt-purple;
213 | }
214 |
215 | .gd {
216 | color: $dt-red;
217 | }
218 | }
219 |
220 | p code{
221 | font-size: 19px;
222 | }
223 |
--------------------------------------------------------------------------------
/.github/workflows/setup.yaml:
--------------------------------------------------------------------------------
1 | name: Setup
2 | on: push
3 |
4 | permissions:
5 | actions: write
6 | pull-requests: write
7 | contents: write
8 | deployments: write
9 | pages: write
10 | statuses: write
11 |
12 | jobs:
13 | setup:
14 | if: (github.event.commits[0].message == 'Initial commit') && (github.run_number == 1)
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Set up Python
18 | uses: actions/setup-python@v1
19 | with:
20 | python-version: 3.6
21 |
22 | - name: Copy Repository Contents
23 | uses: actions/checkout@v2
24 |
25 | - name: modify files
26 | run: |
27 | import re, os
28 | from pathlib import Path
29 | from configparser import ConfigParser
30 | config = ConfigParser()
31 |
32 | nwo = os.getenv('GITHUB_REPOSITORY')
33 | username, repo_name = nwo.split('/')
34 | readme_template_path = Path('_fastpages_docs/README_TEMPLATE.md')
35 | readme_path = Path('README.md')
36 | config_path = Path('_config.yml')
37 | pr_msg_path = Path('_fastpages_docs/_setup_pr_template.md')
38 | settings = Path('_action_files/settings.ini')
39 |
40 | assert readme_template_path.exists(), 'Did not find _fastpages_docs/README_TEMPLATE.md in the current directory!'
41 | assert readme_path.exists(), 'Did not find README.md in the current directory!'
42 | assert config_path.exists(), 'Did not find _config.yml in the current directory!'
43 | assert pr_msg_path.exists(), 'Did not find _fastpages_docs/_setup_pr_template.md in the current directory!'
44 | assert settings.exists(), 'Did not find _action_files/settings.ini in the current directory!'
45 |
46 | # edit settings.ini file to inject baseurl
47 | config.read(settings)
48 | config['DEFAULT']['baseurl'] = f'/{repo_name}'
49 | with open('_action_files/settings.ini', 'w') as configfile:
50 | config.write(configfile)
51 |
52 | # replace content of README with template
53 | readme = readme_template_path.read_text().replace('{_username_}', username).replace('{_repo_name_}', repo_name)
54 | readme_path.write_text(readme)
55 |
56 | # update _config.yml
57 | cfg = config_path.read_text()
58 | cfg = re.sub(r'^(github_username: )(fastai)', fr'\g<1>{username}', cfg, flags=re.MULTILINE)
59 | cfg = re.sub(r'^(baseurl: )("")', r'\1"/{}"'.format(repo_name), cfg, flags=re.MULTILINE)
60 | cfg = re.sub(r'^(github_repo: ")(fastpages)', r'\1{}'.format(repo_name), cfg, flags=re.MULTILINE)
61 | cfg = re.sub(r'^(url: "https://)(fastpages.fast.ai)(")', fr'\g<1>{username}.github.io\3', cfg, flags=re.MULTILINE)
62 | cfg = re.sub('UA-57531313-5', '', cfg, flags=re.MULTILINE)
63 | config_path.write_text(cfg)
64 |
65 | # prepare the pr message
66 | pr = pr_msg_path.read_text().replace('{_username_}', username).replace('{_repo_name_}', repo_name)
67 | pr_msg_path.write_text(pr)
68 | shell: python
69 |
70 | - name: commit changes
71 | run: |
72 | git config --global user.email "${GH_EMAIL}"
73 | git config --global user.name "${GH_USERNAME}"
74 | git checkout -B fastpages-automated-setup
75 | git rm CNAME action.yml
76 | git rm _notebooks/2020-02-21-introducing-fastpages.ipynb
77 | git rm _notebooks/2020-09-01-fastcore.ipynb || true
78 | git rm _notebooks/2020-11-17-linkcheck.ipynb || true
79 | git rm -rf _notebooks/fastcore_imgs
80 | git rm -rf _notebooks/fastlinkcheck_images
81 | git rm _posts/2020-03-06-fastpages-actions.md
82 | git rm _posts/*codespaces.md || true
83 | git rm -rf images/fastpages_posts
84 | git rm .github/workflows/chatops.yaml
85 | git rm .github/workflows/docker.yaml
86 | git rm .github/workflows/issue_reminder.yaml
87 | git rm .github/workflows/setup.yaml
88 | git rm .github/ISSUE_TEMPLATE/bug.md
89 | git rm .github/ISSUE_TEMPLATE/feature_request.md
90 | git rm _word/*.docx
91 | git add _config.yml README.md _fastpages_docs/ _action_files/settings.ini
92 | git commit -m'setup repo'
93 | git push -f --set-upstream origin fastpages-automated-setup
94 | env:
95 | GH_EMAIL: ${{ github.event.commits[0].author.email }}
96 | GH_USERNAME: ${{ github.event.commits[0].author.username }}
97 |
98 | - name: Open a PR
99 | uses: actions/github-script@0.5.0
100 | with:
101 | github-token: ${{secrets.GITHUB_TOKEN}}
102 | script: |
103 | var fs = require('fs');
104 | var contents = fs.readFileSync('_fastpages_docs/_setup_pr_template.md', 'utf8');
105 | github.pulls.create({
106 | owner: context.repo.owner,
107 | repo: context.repo.repo,
108 | title: 'Initial Setup',
109 | head: 'fastpages-automated-setup',
110 | base: 'master',
111 | body: `${contents}`
112 | })
113 |
--------------------------------------------------------------------------------
/_sass/minima/fastpages-styles.scss:
--------------------------------------------------------------------------------
1 | //Default Overrides For Styles In Minima
2 | // If you wish to override any of this CSS, do so in _sass/minima/custom-styles.css
3 |
4 | .post img {
5 | display: block;
6 | // border:1px solid #021a40;
7 | vertical-align: top;
8 | margin-left: auto;
9 | margin-right: auto;
10 | }
11 |
12 | img.emoji{
13 | display: inline !important;
14 | vertical-align: baseline !important;
15 | }
16 |
17 | .post figcaption {
18 | text-align: center;
19 | font-size: .8rem;
20 | font-style: italic;
21 | color: light-grey;
22 | }
23 |
24 | .page-content {
25 | -webkit-font-smoothing: antialiased !important;
26 | text-rendering: optimizeLegibility !important;
27 | font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", "Helvetica", "Tahoma", "Arial", sans-serif !important;
28 | }
29 |
30 | // make non-headings slightly lighter
31 | .post-content p, .post-content li {
32 | font-size: 20px;
33 | color: #515151;
34 | }
35 |
36 | .post-link{
37 | font-weight: normal;
38 | }
39 |
40 | // change padding of headings
41 | h1 {
42 | margin-top:2.5rem !important;
43 | }
44 |
45 | h2 {
46 | margin-top:2rem !important;
47 | }
48 |
49 | h3, h4 {
50 | margin-top:1.5rem !important;
51 | }
52 |
53 | p {
54 | margin-top:1rem !important;
55 | margin-bottom:1rem !important;
56 | }
57 |
58 | h1, h2, h3, h4 {
59 | font-weight: normal !important;
60 | margin-bottom:0.5rem !important;
61 | code {
62 | font-size: 100%;
63 | }
64 | }
65 |
66 | pre {
67 | margin-bottom:1.5rem !important;
68 | }
69 |
70 | // make sure the post title doesn't have too much spacing
71 | .post-title { margin-top: .5rem !important; }
72 |
73 | li {
74 | h3, h4 {
75 | margin-top:.05rem !important;
76 | margin-bottom:.05rem !important;
77 | }
78 | .post-meta-description {
79 | color: rgb(88, 88, 88);
80 | font-size: 15px;
81 | margin-top:.05rem !important;
82 | margin-bottom:.05rem !important;
83 | }
84 | }
85 |
86 |
87 |
88 | // Code Folding
89 | details.description[open] summary::after {
90 | content: attr(data-open);
91 | }
92 |
93 | details.description:not([open]) summary::after {
94 | content: attr(data-close);
95 | }
96 |
97 | // Notebook badges
98 | .notebook-badge-image {
99 | border:0 !important;
100 | }
101 |
102 | // Adjust font size for footnotes.
103 | .footnotes {
104 | font-size: 12px !important;
105 | p, li{
106 | font-size: 12px !important;
107 | }
108 | }
109 |
110 | // Adjust with of social media icons were getting cut off
111 | .social-media-list{
112 | .svg-icon {
113 | width: 25px !important;
114 | height: 23px !important;
115 | }
116 | }
117 |
118 | // Make Anchor Links Appear Only on Hover
119 |
120 | .anchor-link {
121 | opacity: 0;
122 | padding-left: 0.375em;
123 | \-webkit-text-stroke: 1.75px white;
124 | \-webkit-transition: opacity 0.2s ease-in-out 0.1s;
125 | \-moz-transition: opacity 0.2s ease-in-out 0.1s;
126 | \-ms-transition: opacity 0.2s ease-in-out 0.1s;
127 | }
128 |
129 | h1:hover .anchor-link,
130 | h2:hover .anchor-link,
131 | h3:hover .anchor-link,
132 | h4:hover .anchor-link,
133 | h5:hover .anchor-link,
134 | h6:hover .anchor-link {
135 | opacity: 1;
136 | }
137 |
138 |
139 | // category tags
140 | .category-tags {
141 | margin-top: .25rem !important;
142 | margin-bottom: .25rem !important;
143 | font-size: 105%;
144 | }
145 |
146 | // Custom styling for homepage post previews
147 | .post-meta-title, .post-meta{
148 | margin-top: .25em !important;
149 | margin-bottom: .25em !important;
150 | font-size: 105%;
151 | }
152 |
153 | .page-description {
154 | margin-top: .5rem !important;
155 | margin-bottom: .5rem !important;
156 | color: #585858;
157 | font-size: 115%;
158 | }
159 |
160 | // Custom styling for category tags
161 | .category-tags-icon {
162 | font-size: 75% !important;
163 | padding-left: 0.375em;
164 | opacity: 35%;
165 | }
166 | .category-tags-link {
167 | color:rgb(187, 129, 129) !important;
168 | font-size: 13px !important;
169 | }
170 |
171 | // Search Page Styles
172 | .js-search-results {padding-top: 0.2rem;}
173 | .search-results-list-item {padding-bottom: 1rem;}
174 | .search-results-list-item .search-result-title {
175 | font-size: 16px;
176 | color: #d9230f;
177 | }
178 | .search-result-rel-url {color: silver;}
179 | .search-results-list-item a {display: block; color: #777;}
180 | .search-results-list-item a:hover, .search-results-list-item a:focus {text-decoration: none;}
181 | .search-results-list-item a:hover .search-result-title {text-decoration: underline;}
182 |
183 | .search-result-rel-date {
184 | color: rgb(109, 120, 138);
185 | font-size: 14px;
186 | }
187 |
188 | .search-result-preview {
189 | color: #777;
190 | font-size: 16px;
191 | margin-top:.02rem !important;
192 | margin-bottom:.02rem !important;
193 | }
194 | .search-result-highlight {
195 | color: #2e0137;
196 | font-weight:bold;
197 | }
198 |
199 | table {
200 | white-space: normal;
201 | max-width: 100%;
202 | font-size: 100%;
203 | border:none;
204 | th{
205 | text-align: center! important;
206 | }
207 | }
208 |
209 | // customize scrollbars
210 | ::-webkit-scrollbar {
211 | width: 14px;
212 | height: 18px;
213 | }
214 | ::-webkit-scrollbar-thumb {
215 | height: 6px;
216 | border: 4px solid rgba(0, 0, 0, 0);
217 | background-clip: padding-box;
218 | -webkit-border-radius: 7px;
219 | background-color: #9D9D9D;
220 | -webkit-box-shadow: inset -1px -1px 0px rgba(0, 0, 0, 0.05), inset 1px 1px 0px rgba(0, 0, 0, 0.05);
221 | }
222 | ::-webkit-scrollbar-button {
223 | width: 0;
224 | height: 0;
225 | display: none;
226 | }
227 | ::-webkit-scrollbar-corner {
228 | background-color: transparent;
229 | }
230 |
231 | // Wrap text outputs instead of horizontal scroll
232 | .output_text.output_execute_result {
233 | pre{
234 | white-space: pre-wrap;
235 | }
236 | }
237 |
238 |
239 | // Handling large charts on mobile devices
240 | // @media only screen and (max-width: 1200px) {
241 | // /* for mobile phone and tablet devices */
242 | .output_wrapper{
243 | overflow: auto;
244 | }
245 | // }
246 |
247 | .svg-icon.orange{
248 | width: 30px;
249 | height: 23px;
250 | }
251 |
252 | .code_cell {
253 | margin: 1.5rem 0px !important;
254 | }
255 |
256 | pre code {
257 | font-size: 15px !important;
258 | }
259 |
260 | // Handle youtube videos, so they dont break on mobile devices
261 | .youtube-iframe-wrapper {
262 | position: relative;
263 | padding-bottom: 56.10%;
264 | height: 0;
265 | overflow: hidden;
266 | }
267 |
268 | .youtube-iframe-wrapper iframe,
269 | .youtube-iframe-wrapper object,
270 | .youtube-iframe-wrapper embed {
271 | position: absolute;
272 | top: 0;
273 | left: 0;
274 | width: 100%;
275 | height: 100%;
276 | }
277 |
--------------------------------------------------------------------------------
/_fastpages_docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | _Adapted from [fastai/nbdev/CONTRIBUTING.md](https://github.com/fastai/nbdev/blob/master/CONTRIBUTING.md)_
2 |
3 | # How to contribute to fastpages
4 |
5 | First, thanks a lot for wanting to help! Some things to keep in mind:
6 |
7 | - The jupyter to blog post conversion functionality relies on [fastai/nbdev](https://github.com/fastai/nbdev). For idiosyncratic uses of nbdev that only apply to blogs that would require a large refactor to nbdev, it might be acceptable to apply a [monkey patch](https://stackoverflow.com/questions/5626193/what-is-monkey-patching) in `fastpages`. However, it is encouraged to contribute to `nbdev` where possible if there is a change that could unlock a new feature. If you are unsure, please open an issue in this repo to discuss.
8 |
9 |
10 | ## Note for new contributors from Jeremy
11 |
12 | It can be tempting to jump into a new project by questioning the stylistic decisions that have been made, such as naming, formatting, and so forth. This can be especially so for python programmers contributing to this project, which is unusual in following a number of conventions that are common in other programming communities, but not in Python. However, please don’t do this, for (amongst others) the following reasons:
13 |
14 | - Contributing to [Parkinson’s law of triviality](https://www.wikiwand.com/en/Law_of_triviality) has negative consequences for a project. Let’s focus on deep learning!
15 | - It’s exhausting to repeat the same discussion over and over again, especially when it’s been well documented already. When you have a question about the project, please check the pages in the docs website linked here.
16 | - You’re likely to get a warmer welcome from the community if you start out by contributing something that’s been requested on the forum, since you’ll be solving someone’s current problem.
17 | - If you start out by just telling us your point of view, rather than studying the background behind the decisions that have been made, you’re unlikely to be contributing anything new or useful.
18 | - I’ve been writing code for nearly 40 years now, across dozens of languages, and other folks involved have quite a bit of experience too - the approaches used are based on significant experience and research. Whilst there’s always room for improvement, it’s much more likely you’ll be making a positive contribution if you spend a few weeks studying and working within the current framework before suggesting wholesale changes.
19 |
20 |
21 | ## Did you find a bug?
22 |
23 | * Nobody is perfect, especially not us. But first, please double-check the bug doesn't come from something on your side. The [forum](http://forums.fast.ai/) is a tremendous source for help, and we'd advise to use it as a first step. Be sure to include as much code as you can so that other people can easily help you.
24 | * Then, ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/fastai/fastpages/issues).
25 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/fastai/fastpages/issues/new). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
26 | * Be sure to add the complete error messages.
27 |
28 | #### Did you write a patch that fixes a bug?
29 |
30 | * Open a new GitHub pull request with the patch.
31 | * Ensure that your PR includes a test that fails without your patch, and pass with it.
32 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
33 | * Before submitting, please be sure you abide by our [coding style](https://docs.fast.ai/dev/style.html) (where appropriate) and [the guide on abbreviations](https://docs.fast.ai/dev/abbr.html) and clean-up your code accordingly.
34 |
35 | ## Do you intend to add a new feature or change an existing one?
36 |
37 | * You can suggest your change on the [fastai forum](http://forums.fast.ai/) to see if others are interested or want to help.
38 | * Once your approach has been discussed and confirmed on the forum, you are welcome to push a PR, including a complete description of the new feature and an example of how it's used. Be sure to document your code in the notebook.
39 | * Ensure that your code includes tests that exercise not only your feature, but also any other code that might be impacted.
40 |
41 | ## PR submission guidelines
42 |
43 | Some general rules of thumb that will make your life easier.
44 |
45 | * Test locally before opening a pull request. See [the development guide](_fastpages_docs/DEVELOPMENT.md) for instructions on how to run fastpages on your local machine.
46 | * When you do open a pull request, please request a draft build of your PR by making a **comment with the magic command `/preview` in the pull request.** This will allow reviewers to see a live-preview of your changes without having to clone your branch.
47 | * You can do this multiple times, if necessary, to rebuild your preview due to changes. But please do not abuse this and test locally before doing this.
48 |
49 | * Keep each PR focused. While it's more convenient, do not combine several unrelated fixes together. Create as many branches as needing to keep each PR focused.
50 | * Do not mix style changes/fixes with "functional" changes. It's very difficult to review such PRs and it most likely get rejected.
51 | * Do not add/remove vertical whitespace. Preserve the original style of the file you edit as much as you can.
52 | * Do not turn an already submitted PR into your development playground. If after you submitted PR, you discovered that more work is needed - close the PR, do the required work and then submit a new PR. Otherwise each of your commits requires attention from maintainers of the project.
53 | * If, however, you submitted a PR and received a request for changes, you should proceed with commits inside that PR, so that the maintainer can see the incremental fixes and won't need to review the whole PR again. In the exception case where you realize it'll take many many commits to complete the requests, then it's probably best to close the PR, do the work and then submit it again. Use common sense where you'd choose one way over another.
54 | * When you open a pull request, you can generate a live preview build of how the blog site will look by making a comment in the PR that contains this command: `/preview`. GitHub will build your site and drop a temporary link for everyone to review. You can do this as multiple times if necessary, however, as mentioned previously do not turn an already submitted PR into a development playground.
55 |
56 | ## Do you have questions about the source code?
57 |
58 | * Please ask it on the [fastai forum](http://forums.fast.ai/) (after searching someone didn't ask the same one before with a quick search). We'd rather have the maximum of discussions there so that the largest number can benefit from it.
59 |
60 | ## Do you want to contribute to the documentation?
61 |
62 | * PRs are welcome for this. For any confusion about the documentation, please feel free to open an issue on this repo.
63 |
64 |
--------------------------------------------------------------------------------
/.github/workflows/chatops.yaml:
--------------------------------------------------------------------------------
1 | name: Chatops
2 | on: [issue_comment]
3 |
4 | permissions:
5 | actions: write
6 | pull-requests: write
7 | contents: write
8 |
9 | jobs:
10 | trigger-chatops:
11 | if: (github.event.issue.pull_request != null) && contains(github.event.comment.body, '/preview') && (github.repository == 'fastai/fastpages')
12 | env:
13 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
14 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
15 | CHECK_RUN_NAME: "Draft-Site-Build"
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: see payload
19 | run: |
20 | echo "FULL PAYLOAD:\n${PAYLOAD}\n"
21 | echo "PR_PAYLOAD PAYLOAD:\n${PR_PAYLOAD}"
22 | env:
23 | PAYLOAD: ${{ toJSON(github.event) }}
24 | PR_PAYLOAD: ${{ github.event.pull_request }}
25 |
26 | - name: verify env exists
27 | id: get_status
28 | run: |
29 | if [ -z ${NETLIFY_AUTH_TOKEN} ]; then echo "::set-output name=status::public"; else echo "::set-output name=status::private"; fi
30 |
31 | - name: make comment on PR if env does not exist
32 | if: steps.get_status.outputs.status == 'public'
33 | run: |
34 | ./_action_files/pr_comment.sh "Was not able to generate site preview due to absent credentials."
35 | env:
36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37 | ISSUE_NUMBER: ${{ github.event.issue.number }}
38 |
39 | - name: Fetch context about the PR that has been commented on
40 | id: chatops
41 | uses: machine-learning-apps/actions-chatops@master
42 | with:
43 | TRIGGER_PHRASE: "/preview"
44 | env: # you must supply GITHUB_TOKEN
45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 |
47 | - name: Set up Python
48 | uses: actions/setup-python@v1
49 | with:
50 | python-version: 3.6
51 |
52 | - name: install requests
53 | run: pip3 install requests
54 |
55 | - name: add check run
56 | id: create_check
57 | if: steps.get_status.outputs.status == 'private'
58 | shell: python
59 | run: |
60 | import os, requests
61 |
62 | sha = os.getenv('SHA')
63 | token = os.getenv('GITHUB_TOKEN')
64 | nwo = os.getenv('GITHUB_REPOSITORY')
65 | name = os.getenv('CHECK_RUN_NAME')
66 |
67 | url = f'https://api.github.com/repos/{nwo}/check-runs'
68 |
69 | headers = {'authorization': f'token {token}',
70 | 'accept': 'application/vnd.github.antiope-preview+json'}
71 |
72 | payload = {
73 | 'name': f'{name}',
74 | 'head_sha': f'{sha}',
75 | 'status': 'in_progress',
76 | 'output':{
77 | 'title': f'Building preview of site for {sha}.',
78 | 'summary': ' ',
79 | 'text': ' '
80 | },
81 | }
82 | response = requests.post(url=url, headers=headers, json=payload)
83 | print(response)
84 | id = response.json()['id']
85 | print(f"::set-output name=id::{id}")
86 | env:
87 | SHA: ${{ steps.chatops.outputs.SHA }}
88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89 |
90 | - name: add label
91 | if: steps.get_status.outputs.status == 'private'
92 | run: |
93 | import os, requests
94 | nwo = os.getenv('GITHUB_REPOSITORY')
95 | token = os.getenv('GITHUB_TOKEN')
96 | pr_num = os.getenv('PR_NUM')
97 | headers = {'Accept': 'application/vnd.github.symmetra-preview+json',
98 | 'Authorization': f'token {token}'}
99 | url = f"https://api.github.com/repos/{nwo}/issues/{pr_num}/labels"
100 | data = {"labels": ["draft build pending"]}
101 | result = requests.post(url=url, headers=headers, json=data)
102 | # assert response.status_code == 201, f"Received status code of {response.status_code}"
103 | print(result)
104 | shell: python
105 | env:
106 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
107 | PR_NUM: ${{ steps.chatops.outputs.PULL_REQUEST_NUMBER }}
108 | GITHUB_REPOSITORY: $GITHUB_REPOSITORY
109 |
110 | - name: Copy The PR's Branch Repository Contents
111 | uses: actions/checkout@main
112 | if: steps.get_status.outputs.status == 'private'
113 | with:
114 | ref: ${{ steps.chatops.outputs.SHA }}
115 |
116 | - name: convert notebooks and word docs to posts
117 | uses: ./ # use the code in this repo to instead of fastai/fastpages@master
118 |
119 | - name: setup directories for Jekyll build
120 | if: steps.get_status.outputs.status == 'private'
121 | run: |
122 | rm -rf _site
123 | sudo chmod -R 777 .
124 |
125 | - name: Jekyll build with baseurl as root for netifly
126 | if: steps.get_status.outputs.status == 'private'
127 | uses: docker://fastai/fastpages-jekyll
128 | with:
129 | args: bash -c "jekyll build"
130 |
131 | - name: deploy to netlify
132 | if: steps.get_status.outputs.status == 'private'
133 | id: py
134 | run: |
135 | sudo npm install -g --unsafe-perm=true netlify-cli
136 | netlify deploy --dir _site | tee _netlify_logs.txt
137 | cat _netlify_logs.txt | python _action_files/parse_netlify.py
138 |
139 | - name: make comment on PR
140 | if: steps.get_status.outputs.status == 'private'
141 | run: |
142 | MSG="A preview build of this branch has been generated for SHA: $SHA and can be viewed **live** at: ${URL}\n\nThe current fastpages site built from master can be viewed for comparison [here](https://fastpages.fast.ai/)"
143 | echo "$MSG"
144 | ./_action_files/pr_comment.sh "${MSG}"
145 | env:
146 | URL: ${{ steps.py.outputs.draft_url }}
147 | SHA: ${{ steps.chatops.outputs.SHA }}
148 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
149 | ISSUE_NUMBER: ${{ github.event.issue.number }}
150 |
151 | - name: remove label
152 | if: always()
153 | run: |
154 | import os, requests
155 | nwo = os.getenv('GITHUB_REPOSITORY')
156 | token = os.getenv('GITHUB_TOKEN')
157 | pr_num = os.getenv('PR_NUM')
158 | headers = {'Accept': 'application/vnd.github.symmetra-preview+json',
159 | 'Authorization': f'token {token}'}
160 | url = f"https://api.github.com/repos/{nwo}/issues/{pr_num}/labels/draft%20build%20pending"
161 | result = requests.delete(url=url, headers=headers)
162 | print(result)
163 | shell: python
164 | env:
165 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
166 | PR_NUM: ${{ steps.chatops.outputs.PULL_REQUEST_NUMBER }}
167 | GITHUB_REPOSITORY: $GITHUB_REPOSITORY
168 |
169 | # defensively clear check run each time
170 | - name: clear check run
171 | if: always()
172 | continue-on-error: true
173 | shell: python
174 | run: |
175 | import os, requests
176 |
177 | sha = os.getenv('SHA')
178 | conclusion = os.getenv('WORKFLOW_CONCLUSION').lower()
179 | token = os.getenv('GITHUB_TOKEN')
180 | nwo = os.getenv('GITHUB_REPOSITORY')
181 | check_run_id = os.getenv('CHECK_RUN_ID')
182 | if not check_run_id:
183 | quit()
184 |
185 | url = f'https://api.github.com/repos/{nwo}/check-runs/{check_run_id}'
186 | headers = {'authorization': f'token {token}',
187 | 'accept': 'application/vnd.github.antiope-preview+json'}
188 |
189 | data = {
190 | 'conclusion': f'{conclusion}',
191 | }
192 | response = requests.patch(url=url, headers=headers, json=data)
193 | print(response)
194 | env:
195 | SHA: ${{ steps.chatops.outputs.SHA }}
196 | WORKFLOW_CONCLUSION: ${{ job.status }}
197 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
198 | CHECK_RUN_ID: ${{ steps.create_check.outputs.id }}
199 |
--------------------------------------------------------------------------------
/_posts/2020-12-10-codespaces.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "nbdev + GitHub Codespaces: A New Literate Programming Environment"
3 | description: How a new GitHub feature makes literate programming easier than ever before.
4 | comments: true
5 | hide: false
6 | toc: false
7 | layout: post
8 | hide: false
9 | categories: [codespaces, nbdev]
10 | image: images/fastpages_posts/codespaces/codespaces.png
11 | author: "Hamel Husain & Jeremy Howard"
12 | permalink: /codespaces
13 | ---
14 |
15 | **Today, we are going to show you how to set up a literate programming environment, allowing you to use an IDE (VS Code) and an interactive computing environment (Jupyter), without leaving your browser, for free, in under 5 minutes. You'll even see how VSCode and Jupyter work together automatically!** But first, what is literate programming? And how did I go from skeptic to a zealot of literate programming?
16 |
17 | ## Introduction
18 |
19 | > Literate programming is a programming paradigm introduced by [Donald Knuth](https://en.wikipedia.org/wiki/Donald_Knuth) in which a computer program is given an explanation of its logic in a natural language, such as English, interspersed with snippets of macros and traditional source code, from which compilable source code can be generated. According to Knuth, literate programming provides higher-quality programs by forcing programmers to explicitly state the thoughts behind the program. This process makes poorly thought-out design decisions more obvious. Knuth also claims that literate programming provides a first-rate documentation system, which is not an add-on, but is grown naturally in the process of exposition of one's thoughts during a program's creation. [^1]
20 |
21 | When I first learned about literate programming, I was quite skeptical. For the longest time, I had wrongly equated [Jupyter notebooks](https://jupyter.org/) with literate programming. Indeed, Jupyter is a brilliant interactive computing system, which was awarded the Association of Computing Machinery (ACM) [Software System Award](https://blog.jupyter.org/jupyter-receives-the-acm-software-system-award-d433b0dfe3a2), and is loved by many developers. However, Jupyter falls short of the literate programming paradigm for the following reasons:[^2]
22 |
23 | - It can be difficult to compile source code from notebooks.
24 | - It can be difficult to diff and use version control with notebooks because they are not stored in plain text.
25 | - It is not clear how to automatically generate documentation from notebooks.
26 | - It is not clear how to properly run tests suites when writing code in notebooks.
27 |
28 | My skepticism quickly evaporated when I began using [nbdev](https://nbdev.fast.ai/), a project that extends notebooks to complete the literate programming ideal. I spent a month, full time, using nbdev while contributing to the python library [fastcore](https://github.com/fastai/fastcore), and can report that Donald Knuth was definitely onto something. The process of writing prose and tests alongside code forced me to deeply understand why the code does what it does, and to think deeply about its design. Furthermore, the reduced cognitive load and speed of iteration of having documentation, code, and tests in one location boosted my productivity to levels I have never before experienced as a software developer. Furthermore, I found that developing this way bolstered collaboration such that code reviews not only happened faster but were more meaningful. In short, nbdev may be the most profound productivity tool I have ever used.
29 |
30 | As a teaser, look how easy it is to instantiate this literate programming environment, which includes a notebook, a docs site and an IDE with all dependencies pre-installed! :point_down:
31 |
32 | {% include video.html url="https://github.com/machine-learning-apps/demo-videos/raw/master/codespaces-nbdev/e2e_small.mp4" %}
33 |
34 |
35 |
36 | ## Features of nbdev
37 |
38 | As discussed in the [docs](https://nbdev.fast.ai/), nbdev provides the following features:
39 |
40 | - Searchable, hyperlinked documentation, which can be automatically hosted on [GitHub Pages](https://docs.github.com/en/github/working-with-github-pages) for free.
41 | - Python modules, following best practices such as [automatically defining `__all__`](http://xion.io/post/code/python-all-wild-imports.html) with your exported functions, classes, and variables.
42 | - Pip and Conda installers.
43 | - Tests defined directly in notebooks which run in parallel. This testing system has been thoroughly tested with [GitHub Actions](https://github.com/features/actions).
44 | - Navigate and edit your code in a standard text editor or IDE, and export any changes automatically back into your notebooks.
45 |
46 | Since you are in a notebook, you can also add charts, text, links, images, videos, etc, that are included automatically in the documentation of your library, along with standardized documentation generated automatically from your code. [This site](https://docs.fast.ai/) is an example of docs generated automatically by nbdev.
47 |
48 | ## GitHub Codespaces
49 |
50 | Thanks to [Conda](https://docs.conda.io/en/latest/) and [nbdev_template](https://github.com/fastai/nbdev_template), setting up a development environment with nbdev is far easier than it used to be. However, we realized it could be even easier, thanks to a new GitHub product called [Codespaces](https://github.com/features/codespaces). Codespaces is a fully functional development environment in your browser, accessible directly from GitHub, that provides the following features:
51 |
52 | 1. A full VS Code IDE.
53 | 2. An environment that has files from the repository mounted into the environment, along with your GitHub credentials.
54 | 3. A development environment with dependencies pre-installed, backed by [Docker](https://www.docker.com/).
55 | 4. The ability to serve additional applications on arbitrary ports. For nbdev, we serve a Jupyter notebook server as well as a [Jekyll](https://jekyllrb.com/) based documentation site.
56 | 5. A shared file system, which facilitates editing code in one browser tab and rendering the results in another.
57 | 6. ... [and more](https://docs.github.com/en/github/developing-online-with-codespaces).
58 |
59 | Codespaces enables developers to immediately participate in a project without wasting time on DevOps or complicated setup steps. Most importantly, CodeSpaces with nbdev allows developers to quickly get started with creating their own software with literate programming.
60 |
61 | ## A demo of nbdev + Codespaces
62 |
63 | This demo uses the project [fastai/fastcore](https://github.com/fastai/fastcore), which was built with nbdev, as an example. First, we can navigate to this repo and launch a Codespace:
64 |
65 | {% include video.html url="https://github.com/machine-learning-apps/demo-videos/raw/master/codespaces-nbdev/1_open.mp4" %}
66 |
67 |
68 |
69 | If you are launching a fresh Codespace, it may take several minutes to set up. Once the environment is ready, we can verify that all dependencies we want are installed (in this case `fastcore` and `nbdev`):
70 |
71 | {% include video.html url="https://github.com/machine-learning-apps/demo-videos/raw/master/codespaces-nbdev/2_verify.mp4" %}
72 |
73 |
74 |
75 | Additionally, we can serve an arbitrary number of applications on user-specified ports, which we can open through VSCode as shown below:
76 |
77 | {% include video.html url="https://github.com/machine-learning-apps/demo-videos/raw/master/codespaces-nbdev/3_nb_small.mp4" %}
78 |
79 |
80 |
81 | In this case, these applications are a notebook and docs site. Changes to a notebook are reflected immediately in the data docs. Furthermore, we can use the cli command `nbdev_build_lib` to sync our notebooks with python modules. This functionality is shown below:
82 |
83 | {% include video.html url="https://github.com/machine-learning-apps/demo-videos/raw/master/codespaces-nbdev/4_reload_small.mp4" %}
84 |
85 |
86 |
87 | This is amazing! With a click of a button, I was able to:
88 |
89 | 1. Launch an IDE with all dependencies pre-installed.
90 | 2. Launch two additional applications: a Jupyter Notebook server on port 8080 and a docs site on port 4000.
91 | 3. Automatically update the docs and modules every time I make a change to a Jupyter notebook.
92 |
93 | This is just the tip of the iceberg. There are additional utilities for [writing and executing tests](https://nbdev.fast.ai/test.html), [diffing notebooks](https://nbdev.fast.ai/sync.html#Diff-notebook---library), [special flags](https://nbdev.fast.ai/magic_flags.html#How-do-comment-flags-correspond-to-magic-flags?) for hiding, showing, and collapsing cells in the generated docs, as well as [git hooks](https://nbdev.fast.ai/cli.html#nbdev_install_git_hooks) for automation. This and more functionality is covered in [the nbdev docs](https://nbdev.fast.ai/).
94 |
95 | ## Give It A Try For Yourself
96 |
97 | To try out nbdev yourself, [take this tutorial](https://nbdev.fast.ai/tutorial.html), which will walk you through everything you need to know. The tutorial also shows you how to use a repository template with the configuration files necessary to enable Codespaces with nbdev.
98 |
99 | ## You Can Write Blogs With Notebooks, Too!
100 |
101 | This blog post was written in [fastpages](https://github.com/fastai/fastpages) which is also built on nbdev! We recommend [fastpages](https://github.com/fastai/fastpages) if you want an easy way to blog with Jupyter notebooks.
102 |
103 | ## Additional Resources
104 |
105 | 1. The [GitHub Codepaces site](https://github.com/features/codespaces).
106 | 1. The official [docs for Codespaces](https://docs.github.com/en/github/developing-online-with-codespaces).
107 | 1. The nbdev [docs](https://nbdev.fast.ai/).
108 | 2. The nbdev [GitHub repo](https://github.com/fastai/nbdev).
109 | 3. [fastpages](https://github.com/fastai/fastpages): The project used to write this blog.
110 | 4. The GitHub repo [fastai/fastcore](https://github.com/fastai/fastcore), which is what we used in this blog post as an example.
111 |
112 | ----
113 | [^1]: Wikipedia article: [Literate Programming](https://en.wikipedia.org/wiki/Literate_programming)
114 | [^2]: This is not a criticism of Jupyter. Jupyter doesn't claim to be a full literate programming system. However, people can sometimes (unfairly) judge Jupyter according to this criteria.
115 |
--------------------------------------------------------------------------------
/assets/js/search.js:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 | // from https://github.com/pmarsceill/just-the-docs/blob/master/assets/js/just-the-docs.js#L47
4 |
5 | (function (jtd, undefined) {
6 |
7 | // Event handling
8 |
9 | jtd.addEvent = function(el, type, handler) {
10 | if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
11 | }
12 | jtd.removeEvent = function(el, type, handler) {
13 | if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler);
14 | }
15 | jtd.onReady = function(ready) {
16 | // in case the document is already rendered
17 | if (document.readyState!='loading') ready();
18 | // modern browsers
19 | else if (document.addEventListener) document.addEventListener('DOMContentLoaded', ready);
20 | // IE <= 8
21 | else document.attachEvent('onreadystatechange', function(){
22 | if (document.readyState=='complete') ready();
23 | });
24 | }
25 |
26 | // Show/hide mobile menu
27 |
28 | // function initNav() {
29 | // const mainNav = document.querySelector('.js-main-nav');
30 | // const pageHeader = document.querySelector('.js-page-header');
31 | // const navTrigger = document.querySelector('.js-main-nav-trigger');
32 |
33 | // jtd.addEvent(navTrigger, 'click', function(e){
34 | // e.preventDefault();
35 | // var text = navTrigger.innerText;
36 | // var textToggle = navTrigger.getAttribute('data-text-toggle');
37 |
38 | // mainNav.classList.toggle('nav-open');
39 | // pageHeader.classList.toggle('nav-open');
40 | // navTrigger.classList.toggle('nav-open');
41 | // navTrigger.innerText = textToggle;
42 | // navTrigger.setAttribute('data-text-toggle', text);
43 | // textToggle = text;
44 | // })
45 | // }
46 |
47 |
48 | // Site search
49 |
50 | function initSearch() {
51 | var request = new XMLHttpRequest();
52 | request.open('GET', '{{ "assets/js/search-data.json" | relative_url }}', true);
53 |
54 | request.onload = function(){
55 | if (request.status >= 200 && request.status < 400) {
56 | // Success!
57 | var data = JSON.parse(request.responseText);
58 |
59 | {% if site.search_tokenizer_separator != nil %}
60 | lunr.tokenizer.separator = {{ site.search_tokenizer_separator }}
61 | {% else %}
62 | lunr.tokenizer.separator = /[\s\-/]+/
63 | {% endif %}
64 |
65 | var index = lunr(function () {
66 | this.ref('id');
67 | this.field('title', { boost: 200 });
68 | this.field('content', { boost: 2 });
69 | this.field('url');
70 | this.metadataWhitelist = ['position']
71 |
72 | for (var i in data) {
73 | this.add({
74 | id: i,
75 | title: data[i].title,
76 | content: data[i].content,
77 | url: data[i].url
78 | });
79 | }
80 | });
81 |
82 | searchResults(index, data);
83 | } else {
84 | // We reached our target server, but it returned an error
85 | console.log('Error loading ajax request. Request status:' + request.status);
86 | }
87 | };
88 |
89 | request.onerror = function(){
90 | // There was a connection error of some sort
91 | console.log('There was a connection error');
92 | };
93 |
94 | request.send();
95 |
96 | function searchResults(index, data) {
97 | var index = index;
98 | var docs = data;
99 | var searchInput = document.querySelector('.js-search-input');
100 | var searchResults = document.querySelector('.js-search-results');
101 |
102 | function hideResults() {
103 | searchResults.innerHTML = '';
104 | searchResults.classList.remove('active');
105 | }
106 |
107 | jtd.addEvent(searchInput, 'keydown', function(e){
108 | switch (e.keyCode) {
109 | case 38: // arrow up
110 | e.preventDefault();
111 | var active = document.querySelector('.search-result.active');
112 | if (active) {
113 | active.classList.remove('active');
114 | if (active.parentElement.previousSibling) {
115 | var previous = active.parentElement.previousSibling.querySelector('.search-result');
116 | previous.classList.add('active');
117 | }
118 | }
119 | return;
120 | case 40: // arrow down
121 | e.preventDefault();
122 | var active = document.querySelector('.search-result.active');
123 | if (active) {
124 | if (active.parentElement.nextSibling) {
125 | var next = active.parentElement.nextSibling.querySelector('.search-result');
126 | active.classList.remove('active');
127 | next.classList.add('active');
128 | }
129 | } else {
130 | var next = document.querySelector('.search-result');
131 | if (next) {
132 | next.classList.add('active');
133 | }
134 | }
135 | return;
136 | case 13: // enter
137 | e.preventDefault();
138 | var active = document.querySelector('.search-result.active');
139 | if (active) {
140 | active.click();
141 | } else {
142 | var first = document.querySelector('.search-result');
143 | if (first) {
144 | first.click();
145 | }
146 | }
147 | return;
148 | }
149 | });
150 |
151 | jtd.addEvent(searchInput, 'keyup', function(e){
152 | switch (e.keyCode) {
153 | case 27: // When esc key is pressed, hide the results and clear the field
154 | hideResults();
155 | searchInput.value = '';
156 | return;
157 | case 38: // arrow up
158 | case 40: // arrow down
159 | case 13: // enter
160 | e.preventDefault();
161 | return;
162 | }
163 |
164 | hideResults();
165 |
166 | var input = this.value;
167 | if (input === '') {
168 | return;
169 | }
170 |
171 | var results = index.query(function (query) {
172 | var tokens = lunr.tokenizer(input)
173 | query.term(tokens, {
174 | boost: 10
175 | });
176 | query.term(tokens, {
177 | wildcard: lunr.Query.wildcard.TRAILING
178 | });
179 | });
180 |
181 | if (results.length > 0) {
182 | searchResults.classList.add('active');
183 | var resultsList = document.createElement('ul');
184 | resultsList.classList.add('search-results-list');
185 | searchResults.appendChild(resultsList);
186 |
187 | for (var i in results) {
188 | var result = results[i];
189 | var doc = docs[result.ref];
190 |
191 | var resultsListItem = document.createElement('li');
192 | resultsListItem.classList.add('search-results-list-item');
193 | resultsList.appendChild(resultsListItem);
194 |
195 | var resultLink = document.createElement('a');
196 | resultLink.classList.add('search-result');
197 | resultLink.setAttribute('href', doc.url);
198 | resultsListItem.appendChild(resultLink);
199 |
200 | var resultTitle = document.createElement('div');
201 | resultTitle.classList.add('search-result-title');
202 | resultTitle.innerText = doc.title;
203 | resultLink.appendChild(resultTitle);
204 |
205 | var resultRelUrl = document.createElement('span');
206 | resultRelUrl.classList.add('search-result-rel-date');
207 | resultRelUrl.innerText = doc.date;
208 | resultTitle.appendChild(resultRelUrl);
209 |
210 | var metadata = result.matchData.metadata;
211 | var contentFound = false;
212 | for (var j in metadata) {
213 | if (metadata[j].title) {
214 | var position = metadata[j].title.position[0];
215 | var start = position[0];
216 | var end = position[0] + position[1];
217 | resultTitle.innerHTML = doc.title.substring(0, start) + '' + doc.title.substring(start, end) + '' + doc.title.substring(end, doc.title.length)+''+doc.date+'';
218 |
219 | } else if (metadata[j].content && !contentFound) {
220 | contentFound = true;
221 |
222 | var position = metadata[j].content.position[0];
223 | var start = position[0];
224 | var end = position[0] + position[1];
225 | var previewStart = start;
226 | var previewEnd = end;
227 | var ellipsesBefore = true;
228 | var ellipsesAfter = true;
229 | for (var k = 0; k < 3; k++) {
230 | var nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
231 | var nextDot = doc.content.lastIndexOf('.', previewStart - 2);
232 | if ((nextDot > 0) && (nextDot > nextSpace)) {
233 | previewStart = nextDot + 1;
234 | ellipsesBefore = false;
235 | break;
236 | }
237 | if (nextSpace < 0) {
238 | previewStart = 0;
239 | ellipsesBefore = false;
240 | break;
241 | }
242 | previewStart = nextSpace + 1;
243 | }
244 | for (var k = 0; k < 10; k++) {
245 | var nextSpace = doc.content.indexOf(' ', previewEnd + 1);
246 | var nextDot = doc.content.indexOf('.', previewEnd + 1);
247 | if ((nextDot > 0) && (nextDot < nextSpace)) {
248 | previewEnd = nextDot;
249 | ellipsesAfter = false;
250 | break;
251 | }
252 | if (nextSpace < 0) {
253 | previewEnd = doc.content.length;
254 | ellipsesAfter = false;
255 | break;
256 | }
257 | previewEnd = nextSpace;
258 | }
259 | var preview = doc.content.substring(previewStart, start);
260 | if (ellipsesBefore) {
261 | preview = '... ' + preview;
262 | }
263 | preview += '' + doc.content.substring(start, end) + '';
264 | preview += doc.content.substring(end, previewEnd);
265 | if (ellipsesAfter) {
266 | preview += ' ...';
267 | }
268 |
269 | var resultPreview = document.createElement('div');
270 | resultPreview.classList.add('search-result-preview');
271 | resultPreview.innerHTML = preview;
272 | resultLink.appendChild(resultPreview);
273 | }
274 | }
275 | }
276 | }
277 | });
278 |
279 | // jtd.addEvent(searchInput, 'blur', function(){
280 | // setTimeout(function(){ hideResults() }, 300);
281 | // });
282 | }
283 | }
284 |
285 | // function pageFocus() {
286 | // var mainContent = document.querySelector('.js-main-content');
287 | // mainContent.focus();
288 | // }
289 |
290 | // Document ready
291 |
292 | jtd.onReady(function(){
293 | // initNav();
294 | // pageFocus();
295 | if (typeof lunr !== 'undefined') {
296 | initSearch();
297 | }
298 | });
299 |
300 | })(window.jtd = window.jtd || {});
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2020 onwards, fast.ai, Inc
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/.github/workflows/upgrade.yaml:
--------------------------------------------------------------------------------
1 | name: Upgrade fastpages
2 | on:
3 | issues:
4 | types: [opened]
5 |
6 | permissions:
7 | actions: write
8 | pull-requests: write
9 | contents: write
10 | deployments: write
11 | pages: write
12 | statuses: write
13 |
14 | jobs:
15 | check_credentials:
16 | if: |
17 | (github.repository != 'fastai/fastpages') &&
18 | (github.event.issue.title == '[fastpages] Automated Upgrade')
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: see payload
22 | run: |
23 | echo "FULL PAYLOAD:\n${PAYLOAD}\n"
24 | echo "PR_PAYLOAD PAYLOAD:\n${PR_PAYLOAD}"
25 | env:
26 | PAYLOAD: ${{ toJSON(github.event) }}
27 | PR_PAYLOAD: ${{ github.event.pull_request }}
28 |
29 | - name: Comment on issue if sufficient access does not exist
30 | if: |
31 | (github.event.issue.author_association != 'OWNER') &&
32 | (github.event.issue.author_association != 'COLLABORATOR') &&
33 | (github.event.issue.author_association != 'MEMBER')
34 | uses: actions/github-script@0.6.0
35 | with:
36 | github-token: ${{secrets.GITHUB_TOKEN}}
37 | script: |
38 | var permission_level = process.env.permission_level;
39 | var url = 'https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/permission-levels-for-a-user-account-repository#collaborator-access-on-a-repository-owned-by-a-user-account'
40 | var msg = `You must have the [permission level](${url}) of either an **OWNER**, **COLLABORATOR** or **MEMBER** to instantiate an upgrade request. Your permission level is ${permission_level}`
41 | github.issues.createComment({
42 | issue_number: context.issue.number,
43 | owner: context.repo.owner,
44 | repo: context.repo.repo,
45 | body: msg
46 | })
47 | github.issues.update({
48 | issue_number: context.issue.number,
49 | owner: context.repo.owner,
50 | repo: context.repo.repo,
51 | state: 'closed'
52 | })
53 | throw msg;
54 | env:
55 | permission_level: ${{ github.event.issue.author_association }}
56 |
57 | upgrade:
58 | needs: [check_credentials]
59 | if: |
60 | (github.repository != 'fastai/fastpages') &&
61 | (github.event.issue.title == '[fastpages] Automated Upgrade') &&
62 | (github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'COLLABORATOR' || github.event.issue.author_association == 'MEMBER')
63 | runs-on: ubuntu-latest
64 | steps:
65 | - name: Set up Python
66 | uses: actions/setup-python@v1
67 | with:
68 | python-version: 3.7
69 |
70 | - name: checkout latest fastpages
71 | uses: actions/checkout@v2
72 | with:
73 | repository: "fastai/fastpages"
74 | path: "new_files"
75 | persist-credentials: false
76 |
77 | - name: copy this repo's contents
78 | uses: actions/checkout@v2
79 | with:
80 | path: "current_files"
81 | persist-credentials: false
82 |
83 | - name: compare versions
84 | id: check_version
85 | run: |
86 | from pathlib import Path
87 | new_version = Path('new_files/_fastpages_docs/version.txt')
88 | old_version = Path('current_files/_fastpages_docs/version.txt')
89 |
90 | if old_version.exists():
91 | old_num = old_version.read_text().strip()
92 | new_num = new_version.read_text().strip()
93 | print(f'Old version: {old_num}')
94 | print(f'New version: {new_num}')
95 | if old_num == new_num:
96 | print('::set-output name=vbump::false')
97 | else:
98 | print('::set-output name=vbump::true')
99 | else:
100 | print('::set-output name=vbump::true')
101 | shell: python
102 |
103 | - name: copy new files
104 | if: steps.check_version.outputs.vbump == 'true'
105 | run: |
106 | # remove files you don't want to copy from current version of fastpages
107 | cd new_files
108 | rm -rf _posts _notebooks _word images
109 | rm *.md CNAME action.yml _config.yml index.html LICENSE
110 | rm _pages/*.md
111 | rm .github/workflows/chatops.yaml
112 | rm .github/workflows/docker.yaml
113 | rm .github/ISSUE_TEMPLATE/bug.md .github/ISSUE_TEMPLATE/feature_request.md
114 |
115 | # copy new files from fastpages into your repo
116 | for file in $(ls | egrep -v "(assets|_sass)"); do
117 | if [[ -f "$file" ]] || [[ -d "$file" ]]
118 | then
119 | echo "copying $file";
120 | cp -r $file ../current_files;
121 | fi
122 | done
123 |
124 | # copy custom-variables.scss if doesn't exist
125 | if [ ! -f "../current_files/_sass/minima/custom-variables.scss" ]; then
126 | cp _sass/minima/custom-variables.scss ../current_files/_sass/minima/
127 | fi
128 |
129 | # copy select files in assets and _sass
130 | cp -r assets/js ../current_files/assets
131 | cp -r assets/badges ../current_files/assets
132 | cp _sass/minima/fastpages-styles.scss ../current_files/_sass/minima/
133 | cp _sass/minima/fastpages-dracula-highlight.scss ../current_files/_sass/minima/
134 |
135 | # copy action workflows
136 | cp -r .github ../current_files
137 |
138 | # install dependencies
139 | pip3 install pyyaml==5.4.1
140 |
141 | - name: sync baseurl
142 | if: steps.check_version.outputs.vbump == 'true'
143 | run: |
144 | import re, os, yaml
145 | from pathlib import Path
146 | from configparser import ConfigParser
147 | settings = ConfigParser()
148 |
149 | # specify location of config files
150 | nwo = os.getenv('GITHUB_REPOSITORY')
151 | username, repo_name = nwo.split('/')
152 | settings_path = Path('current_files/_action_files/settings.ini')
153 | config_path = Path('current_files/_config.yml')
154 | setup_pr_path = Path('current_files/_fastpages_docs/_setup_pr_template.md')
155 | upgrade_pr_path = Path('current_files/_fastpages_docs/_upgrade_pr.md')
156 |
157 | assert settings_path.exists(), 'Did not find _action_files/settings.ini in your repository!'
158 | assert config_path.exists(), 'Did not find _config.yml in your repository!'
159 | assert setup_pr_path.exists(), 'Did not find_fastpages_docs/_setup_pr_template.md in the current directory!'
160 | assert upgrade_pr_path.exists(), 'Did not find _fastpages_docs/_upgrade_pr.md in your repository!'
161 |
162 | # read data from config files
163 | settings.read(settings_path)
164 | with open(config_path, 'r') as cfg:
165 | config = yaml.load(cfg)
166 |
167 | # sync value for baseurl b/w config.yml and settings.ini
168 | settings['DEFAULT']['baseurl'] = config['baseurl']
169 | with open(settings_path, 'w') as stg:
170 | settings.write(stg)
171 |
172 | # update PR templates
173 | setup_pr = setup_pr_path.read_text().replace('{_username_}', username).replace('{_repo_name_}', repo_name)
174 | setup_pr_path.write_text(setup_pr)
175 | upgrade_pr = upgrade_pr_path.read_text().replace('{_username_}', username).replace('{_repo_name_}', repo_name)
176 | upgrade_pr_path.write_text(upgrade_pr)
177 | shell: python
178 |
179 | - uses: webfactory/ssh-agent@v0.4.1
180 | if: steps.check_version.outputs.vbump == 'true'
181 | with:
182 | ssh-private-key: ${{ secrets.SSH_DEPLOY_KEY }}
183 |
184 | - name: push changes to branch
185 | if: steps.check_version.outputs.vbump == 'true'
186 | run: |
187 | # commit changes
188 | cd current_files
189 | git config --global user.email "${GH_USERNAME}@users.noreply.github.com"
190 | git config --global user.name "${GH_USERNAME}"
191 | git remote remove origin
192 | git remote add origin "git@github.com:${GITHUB_REPOSITORY}.git"
193 |
194 | git add _action_files/settings.ini
195 | git checkout -b fastpages-automated-upgrade
196 | git add -A
197 | git commit -m'upgrade fastpages'
198 | git push -f --set-upstream origin fastpages-automated-upgrade master
199 | env:
200 | GH_USERNAME: ${{ github.event.issue.user.login }}
201 |
202 | - name: Open a PR
203 | if: steps.check_version.outputs.vbump == 'true'
204 | id: pr
205 | uses: actions/github-script@0.6.0
206 | with:
207 | github-token: ${{secrets.GITHUB_TOKEN}}
208 | script: |
209 | var fs = require('fs');
210 | var contents = fs.readFileSync('current_files/_fastpages_docs/_upgrade_pr.md', 'utf8');
211 | github.pulls.create({
212 | owner: context.repo.owner,
213 | repo: context.repo.repo,
214 | title: '[fastpages] Update repo with changes from fastpages',
215 | head: 'fastpages-automated-upgrade',
216 | base: 'master',
217 | body: `${contents}`
218 | })
219 | .then(result => console.log(`::set-output name=pr_num::${result.data.number}`))
220 |
221 | - name: Comment on issue if failure
222 | if: failure() && (steps.check_version.outputs.vbump == 'true')
223 | uses: actions/github-script@0.6.0
224 | with:
225 | github-token: ${{secrets.GITHUB_TOKEN}}
226 | script: |
227 | var pr_num = process.env.PR_NUM;
228 | var repo = process.env.REPO
229 | github.issues.createComment({
230 | issue_number: context.issue.number,
231 | owner: context.repo.owner,
232 | repo: context.repo.repo,
233 | body: `An error occurred when attempting to open a PR to update fastpages. See the [Actions tab of your repo](https://github.com/${repo}/actions) for more details.`
234 | })
235 | env:
236 | PR_NUM: ${{ steps.pr.outputs.pr_num }}
237 | REPO: ${{ github.repository }}
238 |
239 | - name: Comment on issue
240 | if: steps.check_version.outputs.vbump == 'true'
241 | uses: actions/github-script@0.6.0
242 | with:
243 | github-token: ${{secrets.GITHUB_TOKEN}}
244 | script: |
245 | var pr_num = process.env.PR_NUM;
246 | var repo = process.env.REPO
247 | github.issues.createComment({
248 | issue_number: context.issue.number,
249 | owner: context.repo.owner,
250 | repo: context.repo.repo,
251 | body: `Opened PR https://github.com/${repo}/pull/${pr_num} to assist with updating fastpages.`
252 | })
253 | env:
254 | PR_NUM: ${{ steps.pr.outputs.pr_num }}
255 | REPO: ${{ github.repository }}
256 |
257 | - name: Comment on issue if version has not changed
258 | if: steps.check_version.outputs.vbump == 'false'
259 | uses: actions/github-script@0.6.0
260 | with:
261 | github-token: ${{secrets.GITHUB_TOKEN}}
262 | script: |
263 | github.issues.createComment({
264 | issue_number: context.issue.number,
265 | owner: context.repo.owner,
266 | repo: context.repo.repo,
267 | body: `Your version of fastpages is up to date. There is nothing to change.`
268 | })
269 |
270 | - name: Close Issue
271 | if: always()
272 | uses: actions/github-script@0.6.0
273 | with:
274 | github-token: ${{secrets.GITHUB_TOKEN}}
275 | script: |
276 | github.issues.update({
277 | issue_number: context.issue.number,
278 | owner: context.repo.owner,
279 | repo: context.repo.repo,
280 | state: 'closed'
281 | })
282 |
--------------------------------------------------------------------------------
/_notebooks/2020-11-17-linkcheck.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# \"Introducing fastlinkcheck\"\n",
8 | "\n",
9 | "> Say goodbye broken links on your static sites. Platform independent, fast, and built in python.\n",
10 | "- author: \"Jeremy Howard and Hamel Husain\"\n",
11 | "- toc: false\n",
12 | "- image: images/copied_from_nb/fastlinkcheck_images/fastlinkcheck.png\n",
13 | "- comments: true\n",
14 | "- categories: [nbdev, fastlinkcheck]\n",
15 | "- permalink: /fastlinkcheck/\n",
16 | "- badges: true"
17 | ]
18 | },
19 | {
20 | "cell_type": "markdown",
21 | "metadata": {},
22 | "source": [
23 | "\n",
24 | "\n",
25 | "# Motivation\n",
26 | "\n",
27 | "Recently, fastai has been hard at work improving and overhauling [nbdev](https://nbdev.fast.ai/), a literate programming environment for python. A key feature of nbdev is automated generation of documentation from Jupyter notebooks. This documentation system adds many niceties, such as the following types of hyperlinks automatically:\n",
28 | "\n",
29 | " - Links to source code on GitHub.\n",
30 | " - Links to both internal and external documentation by introspecting variable names in backticks.\n",
31 | "\n",
32 | "Because documentation is so easy to create and maintain in nbdev, we find ourselves and others creating much more of it! In addition to automatic hyperlinks, we often include our own links to relevant websites, blogs and videos when documenting code. For example, one of the largest nbdev generated sites, [docs.fast.ai](https://docs.fast.ai/), has more than 300 external and internal links at the time of this writing. \n",
33 | "\n",
34 | "\n",
35 | "# The Solution\n",
36 | "\n",
37 | "Due to the continued popularity of [fastai](https://github.com/fastai/fastai) and the growth of new nbdev projects, grooming these links manually became quite tedious. We investigated solutions that could verify links for us automatically, but were not satisfied with any existing solutions. These are the features we desired:\n",
38 | "\n",
39 | "- A platform independent solution that is not tied to a specific static site generator like Jekyll or Hugo.\n",
40 | "- Intelligent introspection of external links that are actually internal links. For example, if we are building the site docs.fast.ai, a link to `https://docs.fast.ai/tutorial` should not result in a web request, but rather introspection of the local file system for the presence of `tutorial.html` in the right location.\n",
41 | "- Verification of any links to assets like CSS, data, javascript or other files. \n",
42 | "- Logs that are well organized that allow us to see each broken link or reference to a non-existent path, and the pages these are found in.\n",
43 | "- Parallelism to verify links as fast as possible. \n",
44 | "- Lightweight, easy to install with minimal dependencies.\n",
45 | "\n",
46 | "We tried tools such as [linkchecker](https://github.com/wummel/linkchecker) and [pylinkvalidator](https://github.com/bartdag/pylinkvalidator), but these required your site to be first be hosted. Since we wanted to check links on a static site, hosting is overhead we wanted to avoid.\n",
47 | "\n",
48 | "This is what led us to create [fastlinkcheck](https://fastlinkcheck.fast.ai/), which we discuss below.\n",
49 | "\n",
50 | "> Note: For Ruby users, [htmlproofer](https://github.com/gjtorikian/html-proofer) apperas to provide overlapping functionality. We have not tried this library. "
51 | ]
52 | },
53 | {
54 | "cell_type": "markdown",
55 | "metadata": {},
56 | "source": [
57 | "# A tour of fastlinkcheck\n",
58 | "\n",
59 | "For this tour we will be referring to the files in the [fastlinkcheck repo](https://github.com/fastai/fastlinkcheck). You should clone this repo in the current directory in order to follow along:"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": 2,
65 | "metadata": {},
66 | "outputs": [
67 | {
68 | "name": "stdout",
69 | "output_type": "stream",
70 | "text": [
71 | "Cloning into 'fastlinkcheck'...\n",
72 | "remote: Enumerating objects: 135, done.\u001b[K\n",
73 | "remote: Counting objects: 100% (135/135), done.\u001b[K\n",
74 | "remote: Compressing objects: 100% (98/98), done.\u001b[K\n",
75 | "remote: Total 608 (delta 69), reused 76 (delta 34), pack-reused 473\u001b[K\n",
76 | "Receiving objects: 100% (608/608), 1.12 MiB | 10.47 MiB/s, done.\n",
77 | "Resolving deltas: 100% (302/302), done.\n"
78 | ]
79 | }
80 | ],
81 | "source": [
82 | "git clone https://github.com/fastai/fastlinkcheck.git\n",
83 | "cd fastlinkcheck"
84 | ]
85 | },
86 | {
87 | "cell_type": "markdown",
88 | "metadata": {},
89 | "source": [
90 | "## Installation\n",
91 | "\n",
92 | "You can install `fastlinkcheck` with pip:"
93 | ]
94 | },
95 | {
96 | "cell_type": "markdown",
97 | "metadata": {},
98 | "source": [
99 | "`pip install fastlinkcheck`"
100 | ]
101 | },
102 | {
103 | "cell_type": "markdown",
104 | "metadata": {},
105 | "source": [
106 | "## Usage"
107 | ]
108 | },
109 | {
110 | "cell_type": "markdown",
111 | "metadata": {},
112 | "source": [
113 | "After installing `fastlinkcheck`, the cli command `link_check` is available from the command line. We can see various options with the `--help` flag."
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": null,
119 | "metadata": {},
120 | "outputs": [
121 | {
122 | "name": "stdout",
123 | "output_type": "stream",
124 | "text": [
125 | "usage: link_check [-h] [--host HOST] [--config_file CONFIG_FILE] [--pdb]\n",
126 | " [--xtra XTRA]\n",
127 | " path\n",
128 | "\n",
129 | "Check for broken links recursively in `path`.\n",
130 | "\n",
131 | "positional arguments:\n",
132 | " path Root directory searched recursively for HTML files\n",
133 | "\n",
134 | "optional arguments:\n",
135 | " -h, --help show this help message and exit\n",
136 | " --host HOST Host and path (without protocol) of web server\n",
137 | " --config_file CONFIG_FILE\n",
138 | " Location of file with urls to ignore\n",
139 | " --pdb Run in pdb debugger (default: False)\n",
140 | " --xtra XTRA Parse for additional args (default: '')\n"
141 | ]
142 | }
143 | ],
144 | "source": [
145 | "link_check --help"
146 | ]
147 | },
148 | {
149 | "cell_type": "markdown",
150 | "metadata": {},
151 | "source": [
152 | "From the root of [fastlinkcheck repo](https://github.com/fastai/fastlinkcheck), We can search the directory `_example/broken_links` recursively for broken links like this:"
153 | ]
154 | },
155 | {
156 | "cell_type": "code",
157 | "execution_count": null,
158 | "metadata": {},
159 | "outputs": [
160 | {
161 | "name": "stdout",
162 | "output_type": "stream",
163 | "text": [
164 | " \n",
165 | "ERROR: The Following Broken Links or Paths were found:\n",
166 | "\n",
167 | "- 'http://fastlinkcheck.com/test.html' was found in the following pages:\n",
168 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`\n",
169 | "\n",
170 | "- 'http://somecdn.com/doesntexist.html' was found in the following pages:\n",
171 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`\n",
172 | "\n",
173 | "- Path('/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.js') was found in the following pages:\n",
174 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`"
175 | ]
176 | },
177 | {
178 | "ename": "",
179 | "evalue": "1",
180 | "output_type": "error",
181 | "traceback": []
182 | }
183 | ],
184 | "source": [
185 | "link_check _example/broken_links "
186 | ]
187 | },
188 | {
189 | "cell_type": "markdown",
190 | "metadata": {},
191 | "source": [
192 | "Specifying the `--host` parameter allows you detect links that are internal by identifying links with that host name. External links are verified by making a request to the appropriate website. On the other hand, internal links are verified by inspecting the presence and content of local files. \n",
193 | "\n",
194 | "We must be careful when using the `--host` argument to only pass the host (and path, if necessary) **without** the protocol. For example, this is how we specify the hostname if your site's url is `http://fastlinkcheck.com/test.html`:"
195 | ]
196 | },
197 | {
198 | "cell_type": "code",
199 | "execution_count": null,
200 | "metadata": {},
201 | "outputs": [
202 | {
203 | "name": "stdout",
204 | "output_type": "stream",
205 | "text": [
206 | " \n",
207 | "ERROR: The Following Broken Links or Paths were found:\n",
208 | "\n",
209 | "- 'http://somecdn.com/doesntexist.html' was found in the following pages:\n",
210 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`\n",
211 | "\n",
212 | "- Path('/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.js') was found in the following pages:\n",
213 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`"
214 | ]
215 | },
216 | {
217 | "ename": "",
218 | "evalue": "1",
219 | "output_type": "error",
220 | "traceback": []
221 | }
222 | ],
223 | "source": [
224 | "link_check _example/broken_links --host fastlinkcheck.com"
225 | ]
226 | },
227 | {
228 | "cell_type": "markdown",
229 | "metadata": {},
230 | "source": [
231 | "We now have one less broken link as there is indeed a file named `test.html` in the root of the path we are searching. However, if we add a path to the end of `--host` , such as `fastlinkcheck.com/mysite` the link would again be listed as broken because `_example/broken_links/mysite/test.html` does not exist:"
232 | ]
233 | },
234 | {
235 | "cell_type": "code",
236 | "execution_count": null,
237 | "metadata": {},
238 | "outputs": [
239 | {
240 | "name": "stdout",
241 | "output_type": "stream",
242 | "text": [
243 | " \n",
244 | "ERROR: The Following Broken Links or Paths were found:\n",
245 | "\n",
246 | "- 'http://fastlinkcheck.com/test.html' was found in the following pages:\n",
247 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`\n",
248 | "\n",
249 | "- 'http://somecdn.com/doesntexist.html' was found in the following pages:\n",
250 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`\n",
251 | "\n",
252 | "- Path('/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.js') was found in the following pages:\n",
253 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`"
254 | ]
255 | },
256 | {
257 | "ename": "",
258 | "evalue": "1",
259 | "output_type": "error",
260 | "traceback": []
261 | }
262 | ],
263 | "source": [
264 | "link_check _example/broken_links --host fastlinkcheck.com/mysite"
265 | ]
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "metadata": {},
270 | "source": [
271 | "You can ignore links by creating a text file that contains a list of urls and paths to ignore. For example, the file `_example/broken_links/linkcheck.rc` contains:"
272 | ]
273 | },
274 | {
275 | "cell_type": "code",
276 | "execution_count": null,
277 | "metadata": {},
278 | "outputs": [
279 | {
280 | "name": "stdout",
281 | "output_type": "stream",
282 | "text": [
283 | "test.js\n",
284 | "https://www.google.com\n"
285 | ]
286 | }
287 | ],
288 | "source": [
289 | "cat _example/broken_links/linkcheck.rc"
290 | ]
291 | },
292 | {
293 | "cell_type": "markdown",
294 | "metadata": {},
295 | "source": [
296 | "We can use this file to ignore urls and paths with the `--config_file` argument. This will filter out references to the broken link `/test.js` from our earlier results:"
297 | ]
298 | },
299 | {
300 | "cell_type": "code",
301 | "execution_count": null,
302 | "metadata": {},
303 | "outputs": [
304 | {
305 | "name": "stdout",
306 | "output_type": "stream",
307 | "text": [
308 | " \n",
309 | "ERROR: The Following Broken Links or Paths were found:\n",
310 | "\n",
311 | "- 'http://somecdn.com/doesntexist.html' was found in the following pages:\n",
312 | " - `/Users/hamelsmu/github/fastlinkcheck/_example/broken_links/test.html`"
313 | ]
314 | },
315 | {
316 | "ename": "",
317 | "evalue": "1",
318 | "output_type": "error",
319 | "traceback": []
320 | }
321 | ],
322 | "source": [
323 | "link_check _example/broken_links --host fastlinkcheck.com --config_file _example/broken_links/linkcheck.rc"
324 | ]
325 | },
326 | {
327 | "cell_type": "markdown",
328 | "metadata": {},
329 | "source": [
330 | "Finally, if there are no broken links, `link_check` will not return anything. The directory `_example/no_broken_links/` does not contain any HTML files with broken links:"
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "execution_count": null,
336 | "metadata": {},
337 | "outputs": [
338 | {
339 | "name": "stdout",
340 | "output_type": "stream",
341 | "text": [
342 | "█\r",
343 | "\r",
344 | " |--------------------| 0.00% [0/2 00:00<00:00]\r",
345 | "\r",
346 | " |██████████----------| 50.00% [1/2 00:00<00:00]\r",
347 | "\r",
348 | " |████████████████████| 100.00% [2/2 00:00<00:00]\r",
349 | "\r",
350 | " \r",
351 | "No broken links found!\n"
352 | ]
353 | }
354 | ],
355 | "source": [
356 | "link_check _example/no_broken_links"
357 | ]
358 | },
359 | {
360 | "cell_type": "markdown",
361 | "metadata": {},
362 | "source": [
363 | "## Python"
364 | ]
365 | },
366 | {
367 | "cell_type": "markdown",
368 | "metadata": {},
369 | "source": [
370 | "You can also use these utilities from python instead of the terminal. Please see [these docs](https://fastlinkcheck.fast.ai/linkcheck.html/) for more information."
371 | ]
372 | },
373 | {
374 | "cell_type": "markdown",
375 | "metadata": {},
376 | "source": [
377 | "## Using `link_check` in GitHub Actions\n",
378 | "\n",
379 | "\n",
380 | "The `link_check` CLI utility that is installed with `fastlinkcheck` can be very useful in continuous integration systems like [GitHub Actions](https://github.com/features/actions). Here is an example GitHub Actions workflow that uses `link_check`:\n",
381 | "\n",
382 | "\n",
383 | "```yaml\n",
384 | "name: Check Links\n",
385 | "on: [workflow_dispatch, push]\n",
386 | "\n",
387 | "jobs:\n",
388 | " check-links:\n",
389 | " runs-on: ubuntu-latest\n",
390 | " steps:\n",
391 | " - uses: actions/checkout@v2\n",
392 | " - uses: actions/setup-python@v2\n",
393 | " - name: check for broken links\n",
394 | " run: |\n",
395 | " pip install fastlinkcheck\n",
396 | " link_check _example \n",
397 | "```\n",
398 | "\n",
399 | "We can a few more lines of code to open an issue instead when a broken link is found, using the [gh cli](https://github.com/cli/cli):\n",
400 | "\n",
401 | "```yaml\n",
402 | "...\n",
403 | " - name: check for broken links\n",
404 | " run: |\n",
405 | " pip install fastlinkcheck\n",
406 | " link_check _example 2> err || true\n",
407 | " export GITHUB_TOKEN=\"YOUR_TOKEN\"\n",
408 | " [[ -s err ]] && gh issue create -t \"Broken links found\" -b \"$(< err)\" -R \"yourusername/yourrepo\"\n",
409 | "```\n",
410 | "\n",
411 | "We can extend this even further to only open an issue when another issue with a specific label isn't already open:\n",
412 | "\n",
413 | "```yaml\n",
414 | "...\n",
415 | " - name: check for broken links\n",
416 | " run: |\n",
417 | " pip install fastlinkcheck\n",
418 | " link_check \"docs/_site\" --host \"docs.fast.ai\" 2> err || true\n",
419 | " export GITHUB_TOKEN=\"YOUR_TOKEN\"\n",
420 | " if [[ -z $(gh issue list -l \"broken-link\")) && (-s err) ]]; then\n",
421 | " gh issue create -t \"Broken links found\" -b \"$(< err)\" -l \"broken-link\" -R \"yourusername/yourrepo\"\n",
422 | " fi\n",
423 | "```\n",
424 | "\n",
425 | "\n",
426 | "See the [GitHub Actions docs](https://docs.github.com/en/free-pro-team@latest/actions) for more information."
427 | ]
428 | },
429 | {
430 | "cell_type": "markdown",
431 | "metadata": {},
432 | "source": [
433 | "# Resources\n",
434 | "\n",
435 | "The following resources are relevant for those interested in learning more about `fastlinkcheck`:\n",
436 | "\n",
437 | "- The fastlinkcheck [GitHub repo](https://github.com/fastai/fastlinkcheck)\n",
438 | "- The fastlinkcheck [docs](https://fastlinkcheck.fast.ai/)"
439 | ]
440 | },
441 | {
442 | "cell_type": "code",
443 | "execution_count": null,
444 | "metadata": {},
445 | "outputs": [],
446 | "source": []
447 | }
448 | ],
449 | "metadata": {
450 | "kernelspec": {
451 | "display_name": "Python 3",
452 | "language": "python",
453 | "name": "python3"
454 | },
455 | "language_info": {
456 | "codemirror_mode": {
457 | "name": "ipython",
458 | "version": 3
459 | },
460 | "file_extension": ".py",
461 | "mimetype": "text/x-python",
462 | "name": "python",
463 | "nbconvert_exporter": "python",
464 | "pygments_lexer": "ipython3",
465 | "version": "3.8.2"
466 | }
467 | },
468 | "nbformat": 4,
469 | "nbformat_minor": 4
470 | }
471 |
--------------------------------------------------------------------------------