Skip to content

Commit

Permalink
Build drafts into /draft/uuid/ and not the regular post URL
Browse files Browse the repository at this point in the history
  • Loading branch information
Siecje committed Feb 4, 2024
1 parent cfe6219 commit e6b6a7e
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 52 deletions.
34 changes: 5 additions & 29 deletions htmd/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@

import click
from flask import Flask
from flask_flatpages import FlatPages, Page
from flask_flatpages import FlatPages

from .utils import (
combine_and_minify_css,
combine_and_minify_js,
copy_missing_templates,
copy_site_file,
create_directory,
set_post_metadata,
)


Expand Down Expand Up @@ -97,37 +98,12 @@ def verify() -> None:
sys.exit(1)


def set_post_time(
app: Flask,
post: Page,
field: str,
date_time: datetime.datetime,
) -> None:
file_path = (
Path(app.config['FLATPAGES_ROOT'])
/ (post.path + app.config['FLATPAGES_EXTENSION'])
)
with file_path.open('r') as file:
lines = file.readlines()

found = False
with file_path.open('w') as file:
for line in lines:
if not found and field in line:
# Update datetime value
line = f'{field}: {date_time.isoformat()}\n' # noqa: PLW2901
found = True
elif not found and '...' in line:
# Write field and value before '...'
file.write(f'{field}: {date_time.isoformat()}\n')
found = True
file.write(line)


def set_posts_datetime(app: Flask, posts: FlatPages) -> None:
# Ensure each post has a published date
# set time for correct date field
for post in posts:
if post.meta.get('draft', False):
continue
if 'updated' not in post.meta:
published = post.meta.get('published')
if isinstance(published, datetime.datetime):
Expand All @@ -147,7 +123,7 @@ def set_posts_datetime(app: Flask, posts: FlatPages) -> None:
else:
post_datetime = now
post.meta[field] = post_datetime
set_post_time(app, post, field, post_datetime)
set_post_metadata(app, post, field, post_datetime.isoformat())


@cli.command('build', short_help='Create static version of the site.')
Expand Down
4 changes: 3 additions & 1 deletion htmd/example_site/templates/post.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

{# Open Graph Tags #}
<meta property="og:type" content="article">
<meta property="og:url" content="{{ url_for('post', year=post.meta['published'].year, month=post.meta['published'].strftime('%m'), day=post.meta['published'].strftime('%d'), path=post.path, _external=True) }}">
{% if 'draft' not in post.meta %}
<meta property="og:url" content="{{ url_for('post', year=post.meta['published'].year, month=post.meta['published'].strftime('%m'), day=post.meta['published'].strftime('%d'), path=post.path, _external=True) }}">
{% endif %}
<meta property="og:title" content="{{post.title}}">
<meta property="og:description" content="{{post.description}}">
{% if post.image or SITE_LOGO %}
Expand Down
28 changes: 22 additions & 6 deletions htmd/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import tomllib
import typing
import uuid

from bs4 import BeautifulSoup
from feedwerk.atom import AtomFeed
Expand All @@ -14,6 +15,8 @@
from htmlmin import minify
from jinja2 import ChoiceLoader, FileSystemLoader

from .utils import set_post_metadata, valid_uuid


this_dir = Path(__file__).parent

Expand All @@ -34,6 +37,7 @@ def get_project_dir() -> Path:

project_dir = get_project_dir()


app = Flask(
__name__,
static_folder=project_dir / 'static',
Expand All @@ -48,6 +52,7 @@ def get_project_dir() -> Path:
msg = 'Can not find config.toml'
sys.exit(msg)


# Flask configs are flat, config.toml is not
# Define the configuration keys and their default values
# 'Flask config': [section, key, default]
Expand All @@ -71,12 +76,12 @@ def get_project_dir() -> Path:
'DEFAULT_AUTHOR_TWITTER': ('author', 'default_twitter', ''),
'DEFAULT_AUTHOR_FACEBOOK': ('author', 'default_facebook', ''),
}

# Update app.config using the configuration keys
for flask_key, (table, key, default) in config_keys.items():
app.config[flask_key] = htmd_config.get(table, {}).get(key, default)
assert app.static_folder is not None


# To avoid full paths in config.toml
app.config['FLATPAGES_ROOT'] = (
project_dir / app.config['POSTS_FOLDER']
Expand All @@ -95,6 +100,7 @@ def get_project_dir() -> Path:
published_posts = [p for p in posts if not p.meta.get('draft', False)]
freezer = Freezer(app)


# Allow config settings (even new user created ones) to be used in templates
for key in app.config:
app.jinja_env.globals[key] = app.config[key]
Expand All @@ -113,6 +119,7 @@ def truncate_post_html(post_html: str) -> str:
app.jinja_loader, # type: ignore[list-item]
])


MONTHS = {
'01': 'January',
'02': 'February',
Expand All @@ -128,6 +135,7 @@ def truncate_post_html(post_html: str) -> str:
'12': 'December',
}


pages = Blueprint(
'pages',
__name__,
Expand Down Expand Up @@ -226,6 +234,14 @@ def post(year: str, month: str, day: str, path: str) -> ResponseReturnValue:
return render_template('post.html', post=post)


@app.route('/draft/<post_uuid>/')
def draft(post_uuid: str) -> ResponseReturnValue:
for post in posts:
if str(post.meta.get('draft', '')) == post_uuid:
return render_template('post.html', post=post)
abort(404) # noqa: RET503


@app.route('/tags/')
def all_tags() -> ResponseReturnValue:
tag_counts: dict[str, int] = {}
Expand Down Expand Up @@ -365,14 +381,14 @@ def day_view() -> Iterator[dict]: # noqa: F811


@freezer.register_generator # type: ignore[no-redef]
def post() -> Iterator[dict]: # noqa: F811
def draft() -> Iterator[dict]: # noqa: F811
draft_posts = [p for p in posts if p.meta.get('draft', False)]
for post in draft_posts:
if not valid_uuid(str(post.meta['draft'])):
post.meta['draft'] = uuid.uuid4()
set_post_metadata(app, post, 'draft', post.meta['draft'])
yield {
'day': post.meta.get('published').strftime('%d'),
'month': post.meta.get('published').strftime('%m'),
'year': post.meta.get('published').year,
'path': post.path,
'post_uuid': str(post.meta['draft']),
}


Expand Down
39 changes: 39 additions & 0 deletions htmd/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from importlib.resources import as_file, files
from pathlib import Path
import shutil
import uuid

import click
from csscompressor import compress
from flask import Flask
from flask_flatpages import Page
from jsmin import jsmin


Expand Down Expand Up @@ -93,3 +96,39 @@ def copy_site_file(directory: Path, filename: str) -> None:

with as_file(source_path) as file:
copy_file(file, destination_path)


def set_post_metadata(
app: Flask,
post: Page,
field: str,
value: str,
) -> None:
file_path = (
Path(app.config['FLATPAGES_ROOT'])
/ (post.path + app.config['FLATPAGES_EXTENSION'])
)
with file_path.open('r') as file:
lines = file.readlines()

found = False
with file_path.open('w') as file:
for line in lines:
if not found and field in line:
# Update datetime value
line = f'{field}: {value}\n' # noqa: PLW2901
found = True
elif not found and '...' in line:
# Write field and value before '...'
file.write(f'{field}: {value}\n')
found = True
file.write(line)


def valid_uuid(string: str) -> bool:
try:
uuid.UUID(string, version=4)
except ValueError:
return False
else:
return True
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ order-by-type = false
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]

[tool.ruff.lint.per-file-ignores]
"htmd/utils.py" = ["I001"]
"tests/test_app.py" = ["ARG001"]
"tests/test_build.py" = ["I001"]
"tests/test_drafts.py" = ["ARG001", "I001"]
Expand Down
8 changes: 8 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@ def test_page_does_not_exist(client: FlaskClient) -> None:
# before this change pages.page was serving templates
response = client.get('/author/')
assert response.status_code == 404 # noqa: PLR2004


def test_draft_does_not_exist(client: FlaskClient) -> None:
# Ensure htmd preview matches build
# Only pages will be served
# before this change pages.page was serving templates
response = client.get('/draft/dne/')
assert response.status_code == 404 # noqa: PLR2004
51 changes: 35 additions & 16 deletions tests/test_drafts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,75 +21,94 @@ def set_example_as_draft() -> None:
post_file.write(line)


def get_example_draft_uuid() -> str:
draft_path = Path('posts') / 'example.md'
with draft_path.open('r') as draft_file: # pragma: no branch
for line in draft_file.readlines(): # pragma: no branch
if 'draft' in line:
return line.replace('draft:', '').strip()
return '' # pragma: no cover


@pytest.fixture(scope='module')
def build_draft() -> Generator[None, None, None]: # noqa: PT004
def build_draft() -> Generator[CliRunner, None, None]:
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(start)
set_example_as_draft()
result = runner.invoke(build)
assert result.exit_code == 0
# Tests code is run here
yield
yield runner


def test_draft_is_built(build_draft: None) -> None:
def test_draft_is_built(build_draft: CliRunner) -> None:
post_path = Path('build') / '2014' / '10' / '30' / 'example' / 'index.html'
with post_path.open('r') as post_page:
assert 'Example Post' in post_page.read()
assert post_path.exists() is False

draft_uuid = get_example_draft_uuid()
draft_path = Path('build') / 'draft' / draft_uuid / 'index.html'
assert draft_path.is_file() is True

# build again now that draft has uuid
result = build_draft.invoke(build)
assert result.exit_code == 0
assert re.search(SUCCESS_REGEX, result.output)


def test_no_drafts_home(build_draft: None) -> None:
def test_no_drafts_home(build_draft: CliRunner) -> None:
with (Path('build') / 'index.html').open('r') as home_page:
assert 'Example Post' not in home_page.read()


def test_no_drafts_atom_feed(build_draft: None) -> None:
def test_no_drafts_atom_feed(build_draft: CliRunner) -> None:
with (Path('build') / 'feed.atom').open('r') as feed_page:
assert 'Example Post' not in feed_page.read()


def test_no_drafts_all_posts(build_draft: None) -> None:
def test_no_drafts_all_posts(build_draft: CliRunner) -> None:
with (Path('build') / 'all' / 'index.html').open('r') as web_page:
assert 'Example Post' not in web_page.read()


def test_no_drafts_all_tags(build_draft: None) -> None:
def test_no_drafts_all_tags(build_draft: CliRunner) -> None:
with (Path('build') / 'tags' / 'index.html').open('r') as web_page:
assert 'first' not in web_page.read()


def test_no_drafts_in_tag(build_draft: None) -> None:
def test_no_drafts_in_tag(build_draft: CliRunner) -> None:
# tag page exists because the draft links to it
with (Path('build') / 'tags' / 'first' / 'index.html').open('r') as web_page:
assert 'Example Post' not in web_page.read()


def test_no_drafts_for_author(build_draft: None) -> None:
def test_no_drafts_for_author(build_draft: CliRunner) -> None:
# author page exists because the draft links to it
with (Path('build') / 'author' / 'Taylor' / 'index.html').open('r') as web_page:
assert 'Example Post' not in web_page.read()


def test_no_drafts_for_year(build_draft: None) -> None:
def test_no_drafts_for_year(build_draft: CliRunner) -> None:
# folder exists becaues of URL for post
assert (Path('build') / '2014' / 'index.html').exists() is False


def test_no_drafts_for_month(build_draft: None) -> None:
def test_no_drafts_for_month(build_draft: CliRunner) -> None:
# folder exists becaues of URL for post
assert (Path('build') / '2014' / '10' / 'index.html').exists() is False


def test_no_drafts_for_day(build_draft: None) -> None:
def test_no_drafts_for_day(build_draft: CliRunner) -> None:
# folder exists becaues of URL for post
assert (Path('build') / '2014' / '10' / '30' / 'index.html').exists() is False


def test_draft_without_published(run_start: CliRunner):
expected_output = ''
def test_draft_without_published(run_start: CliRunner) -> None:
set_example_as_draft()
remove_fields_from_example_post(('published', 'updated'))
result = run_start.invoke(build)
assert result.exit_code == 0
assert re.search(SUCCESS_REGEX, result.output)
draft_uuid = get_example_draft_uuid()
draft_path = Path('build') / 'draft' / draft_uuid / 'index.html'
assert draft_path.is_file() is True

0 comments on commit e6b6a7e

Please sign in to comment.