From 12ca7eaeae8f389fff60fcf4ab82ee7e3c448c36 Mon Sep 17 00:00:00 2001 From: Jamie Hurst Date: Sun, 11 Aug 2024 20:29:54 +0100 Subject: [PATCH 1/5] Add Nginx config for local hosting --- README.md | 8 ++++++++ docker/nginx.conf | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docker/nginx.conf diff --git a/README.md b/README.md index 7cf3933..b49c894 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,14 @@ You can then generate the site using: python3 -m blog.generate ``` +To run a local Docker instance of Nginx to serve a generated set of files, run: + +```bash +docker run --rm --name blog -v $(pwd)/docker/nginx.conf:/etc/nginx/nginx.conf:ro -v $(pwd)/dist:/usr/share/nginx/html -p 8080:80 nginx +``` + +Then head to http://localhost:8080 to view the results. + ## Testing Testing uses Pytest - run it as follows: diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..c686d95 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,49 @@ + +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + gzip on; + + server { + listen 80; + server_name localhost; + + if ($request_uri ~ \?page=1) { + return 301 $scheme://$http_host/; + } + + if ($request_uri ~ \?page=(.*)) { + return 301 $scheme://$http_host/index-$1; + } + + location ~ \.html { + root /usr/share/nginx/html; + } + + location / { + root /usr/share/nginx/html; + + try_files $uri $uri.html /index.html; + } + } +} From 6699d755ba402ee029161e51d6a162275130bca2 Mon Sep 17 00:00:00 2001 From: Jamie Hurst Date: Mon, 12 Aug 2024 16:42:40 +0100 Subject: [PATCH 2/5] Improve nginx by using rewrite instead of redirect --- blog/templates/index.html | 2 +- docker/nginx.conf | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/blog/templates/index.html b/blog/templates/index.html index 5a3996f..b4f7c1c 100644 --- a/blog/templates/index.html +++ b/blog/templates/index.html @@ -23,7 +23,7 @@

{{ article.get_title() }}

    {% for page in pages %}
  1. - {{ loop.index }} + {{ loop.index }}
  2. {% endfor %}
diff --git a/docker/nginx.conf b/docker/nginx.conf index c686d95..628b26d 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -27,23 +27,14 @@ http { server { listen 80; server_name localhost; - - if ($request_uri ~ \?page=1) { - return 301 $scheme://$http_host/; - } + root /usr/share/nginx/html; if ($request_uri ~ \?page=(.*)) { - return 301 $scheme://$http_host/index-$1; + rewrite .* /index-$arg_page.html; } - location ~ \.html { - root /usr/share/nginx/html; - } - - location / { - root /usr/share/nginx/html; - - try_files $uri $uri.html /index.html; + if ($request_uri ~ ^/([\w\-]+)$) { + rewrite .* $request_uri.html; } } } From 58ecaa5a635aa23dbad7f98b0f500fe5c72ebb65 Mon Sep 17 00:00:00 2001 From: Jamie Hurst Date: Mon, 12 Aug 2024 16:55:35 +0100 Subject: [PATCH 3/5] Move format to f-strings and tify up generate script --- blog/generate.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/blog/generate.py b/blog/generate.py index 2d16467..4bfc9fd 100644 --- a/blog/generate.py +++ b/blog/generate.py @@ -2,8 +2,8 @@ import shutil import sys from jinja2 import Environment, PackageLoader, select_autoescape -from .articles import * -from .config import * +from .articles import get_all_articles, get_paginated_articles, get_pages +from .config import ARTICLES_DIR, DIST_DIR, VERSION env = Environment( loader=PackageLoader('blog'), @@ -15,7 +15,7 @@ def render_template(file, **kwargs): return template.render(**kwargs, version=VERSION) def generate(articles_dir=ARTICLES_DIR, dist_dir=DIST_DIR): - + # Create/clear dist folder if os.path.isdir(dist_dir): print('[INFO] Clearing existing output dir...') @@ -26,31 +26,31 @@ def generate(articles_dir=ARTICLES_DIR, dist_dir=DIST_DIR): # Get all articles and generate a page for each one print('[INFO] Loading articles...') items = get_all_articles(articles_dir) - print('[INFO] Loaded {} articles...'.format(len(items))) + print(f'[INFO] Loaded {len(items)} articles...') for item in items: - print('[INFO] Rendering and writing {}...'.format(item.get_name())) + print(f'[INFO] Rendering and writing {item.get_name()}...') rendered = render_template('view.html', article=item) - with open(dist_dir + item.get_name() + '.html', 'w') as output_file: + with open(dist_dir + item.get_name() + '.html', 'w', encoding='UTF-8') as output_file: output_file.write(rendered) # Generate sitemap print('[INFO] Rendering and writing sitemap...') rendered = render_template('sitemap.xml', articles=items) - with open(dist_dir + 'sitemap.xml', 'w') as output_file: + with open(dist_dir + 'sitemap.xml', 'w', encoding='UTF-8') as output_file: output_file.write(rendered) # Generate static pages (inc error pages) for static_page in ['404', '500', 'now']: - print('[INFO] Rendering and writing {}...'.format(static_page)) + print(f'[INFO] Rendering and writing {static_page}...') rendered = render_template(static_page + '.html') - with open(dist_dir + static_page + '.html', 'w') as output_file: + with open(dist_dir + static_page + '.html', 'w', encoding='UTF-8') as output_file: output_file.write(rendered) # Generate home page and paginated elements pages = get_pages(articles_dir) - print('[INFO] Found {} pages of articles...'.format(pages)) + print(f'[INFO] Found {pages} pages of articles...') for p in range(1, pages + 1): - print('[INFO] Rendering and writing index page {}...'.format(p)) + print(f'[INFO] Rendering and writing index page {p}...') paged_items = get_paginated_articles(articles_dir, p) rendered = render_template( 'index.html', @@ -58,14 +58,15 @@ def generate(articles_dir=ARTICLES_DIR, dist_dir=DIST_DIR): current_page=p, pages=[None] * pages ) - with open(dist_dir + 'index.html' if p == 1 else dist_dir + 'index-{}.html'.format(p), 'w') as output_file: + with open(dist_dir + 'index.html' if p == 1 else dist_dir + + f'index-{p}.html', 'w', encoding='UTF-8') as output_file: output_file.write(rendered) # Copy static assets print('[INFO] Copying static assets...') shutil.copytree('blog/static', dist_dir + 'static') - - print('Blog {} generated and output to {}'.format(VERSION, dist_dir)) + + print(f'Blog {VERSION} generated and written to {dist_dir}') if __name__ == '__main__': sys.exit(generate()) From 278952bc5de8e3d2a089e3eb4f8fa212a87615bb Mon Sep 17 00:00:00 2001 From: Jamie Hurst Date: Mon, 12 Aug 2024 16:57:41 +0100 Subject: [PATCH 4/5] Tidy up articles and config files slightly from pylint --- blog/articles.py | 13 +++++++------ blog/config.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/blog/articles.py b/blog/articles.py index c25cd0c..89623b2 100644 --- a/blog/articles.py +++ b/blog/articles.py @@ -13,10 +13,10 @@ def __init__(self, directory, filename): self.filename = filename try: - with open(directory + filename, encoding='UTF8') as file: + with open(directory + filename, encoding='UTF-8') as file: self.contents = markdown(file.read(), extensions=['fenced_code']) except FileNotFoundError as fnfe: - raise ArticleNotFoundException(f"Could not find article file {filename}") from fnfe + raise ArticleNotFoundException(f'Could not find article file {filename}') from fnfe def get_contents(self): return self.contents @@ -24,7 +24,7 @@ def get_contents(self): def get_content_only(self): title = self.find_title.search(self.contents) summary = self.find_summary.search(self.contents) - contents = self.contents.replace(title.group(0), '') + contents = self.contents.replace(title.group(0), '') if summary: return contents.replace(summary.group(0), '') return contents @@ -34,7 +34,8 @@ def get_date(self): find_date = re.compile(r'^(\d{4}-\d{2}-\d{2})_') date = find_date.match(self.filename) if not date: - raise InvalidDateForArticleException(f"Could not parse date for article {self.filename}") + raise InvalidDateForArticleException( + f'Could not parse date for article {self.filename}') return datetime.datetime.strptime(date.group(1), '%Y-%m-%d') def get_image(self): @@ -55,7 +56,8 @@ def get_summary(self): def get_title(self): title = self.find_title.search(self.contents) if not title: - raise InvalidTitleForArticleException(f"Could not parse title for article {self.filename}") + raise InvalidTitleForArticleException( + f'Could not parse title for article {self.filename}') return title.group(1) @@ -70,7 +72,6 @@ def __parse_articles(directory, files): def get_article(directory, file): return Article(directory, file + '.md') - def get_paginated_articles(directory, page=1, per_page=10): files = [f for f in listdir(directory) if isfile(join(directory, f))] files.sort() diff --git a/blog/config.py b/blog/config.py index ec007b4..b8c8133 100644 --- a/blog/config.py +++ b/blog/config.py @@ -1,4 +1,4 @@ -from os import environ +from os import environ ARTICLES_DIR = environ.get('ARTICLES_DIR', default='articles/') DIST_DIR = environ.get('DIST_DIR', default='dist/') From fb6074b4fc3d3dfa76f4397074f2854002a99681 Mon Sep 17 00:00:00 2001 From: Jamie Hurst Date: Mon, 12 Aug 2024 19:17:49 +0100 Subject: [PATCH 5/5] Simpler package management with pip requirements --- .github/workflows/build.yml | 3 +-- .github/workflows/test.yml | 3 +-- MANIFEST.in | 4 ---- README.md | 16 ++++++++++++++-- requirements.txt | 23 +++++++++++++++++++++++ setup.cfg | 15 --------------- setup.py | 12 ------------ 7 files changed, 39 insertions(+), 37 deletions(-) delete mode 100644 MANIFEST.in create mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ade813..ea7ad6b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,8 +58,7 @@ jobs: python-version: '3.12' - name: Install Dependencies run: | - python -m pip install setuptools - python setup.py develop + python -m pip install -r requirements.txt - name: Generate Distribution run: | python -m blog.generate diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc2e44e..24f7ab6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,8 +32,7 @@ jobs: python-version: '3.12' - name: Install Dependencies run: | - python -m pip install setuptools pylint pytest requests-mock coverage - python setup.py develop + python -m pip install -r requirements.txt - name: Test run: | coverage run -m pytest --verbose --junit-xml tests.xml diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 013461a..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -graft app/assets -graft app/static -graft app/templates -global-exclude *.pyc \ No newline at end of file diff --git a/README.md b/README.md index b49c894..f23d72b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ static site. You will require Python 3. Run the following to install required modules: ```bash +python3 -m pip installl -r requirements.txt python3 setup.py develop ``` @@ -20,14 +21,25 @@ python3 -m blog.generate To run a local Docker instance of Nginx to serve a generated set of files, run: ```bash -docker run --rm --name blog -v $(pwd)/docker/nginx.conf:/etc/nginx/nginx.conf:ro -v $(pwd)/dist:/usr/share/nginx/html -p 8080:80 nginx +docker run --rm -d --name blog -v $(pwd)/docker/nginx.conf:/etc/nginx/nginx.conf:ro -v $(pwd)/dist:/usr/share/nginx/html -p 8080:80 nginx ``` +You can then run the when-changed package to automatically generate the blog files anytime a change is made: + +```bash +when-changed -r -1 articles blog -c python3 -m blog.generate +``` + +*(You must be using a virtual environment or your +Python bin files must be on your path for this to work.)* + Then head to http://localhost:8080 to view the results. +You can stop the docker container with `docker stop blog`. + ## Testing -Testing uses Pytest - run it as follows: +Testing uses Pytest - run it as follows (installing the required modules too): ```bash pytest --verbose diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bba486e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +astroid==3.2.4 +certifi==2024.7.4 +charset-normalizer==3.3.2 +coverage==7.6.1 +dill==0.3.8 +idna==3.7 +iniconfig==2.0.0 +isort==5.13.2 +Jinja2==3.1.4 +Markdown==3.6 +MarkupSafe==2.1.5 +mccabe==0.7.0 +packaging==24.1 +platformdirs==4.2.2 +pluggy==1.5.0 +pylint==3.2.6 +pytest==8.3.2 +requests==2.32.3 +requests-mock==1.12.1 +tomlkit==0.13.0 +urllib3==2.2.2 +watchdog==4.0.2 +when-changed==0.3.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b11f66a..0000000 --- a/setup.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[metadata] -name = blog -maintainer = Jamie Hurst -maintainer_email = jamie@jamiehurst.co.uk -description = Basic blog application using Markdown articles. -long_description = file: README.md -long_description_content_type = text/markdown - -[tool:pytest] -testpaths = tests - -[coverage:run] -branch = True -source = - blog diff --git a/setup.py b/setup.py deleted file mode 100644 index 8a3269e..0000000 --- a/setup.py +++ /dev/null @@ -1,12 +0,0 @@ -from setuptools import find_packages, setup - -setup( - name='blog', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=[ - 'jinja2', - 'markdown', - ], -)