diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml index fde3206..53dd92f 100644 --- a/.forgejo/workflows/publish.yml +++ b/.forgejo/workflows/publish.yml @@ -7,9 +7,6 @@ jobs: runs-on: based-alpine steps: - uses: actions/checkout@v4 - - name: setup cache - id: uv-cache - uses: https://git.ficd.sh/ficd/uv-cache@v1 - name: build run: | uv sync diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml deleted file mode 100644 index 2a05ee6..0000000 --- a/.forgejo/workflows/test.yml +++ /dev/null @@ -1,18 +0,0 @@ -# this workflow checks if the project can be built successfully. -# it also serves to test whether based-alpine and uv-cache are working properly. -# Unit tests will be added here eventually -on: [push] -jobs: - test-build: - runs-on: based-alpine - steps: - - name: checkout source - uses: actions/checkout@v4 - - name: setup cache - id: uv-cache - uses: https://git.ficd.sh/ficd/uv-cache@v1 - - name: sync and build - run: | - uv sync - uv build - uv run zona --version diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f6eed9..3790a07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,3 @@ -# 1.3.0 - -- Added RSS feed generation. -- Added default post description to configuration. -- Added time-of-day support to post `date` frontmatter parsing. -- `zona init` now only writes `footer.md` to the templates directory. - -# 1.2.1 - -- Added `--version` flag to CLI. - # 1.2.0 - Improved the appearance and semantics of post navigation buttons. diff --git a/README.md b/README.md index 6fe124a..c3facfb 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ For an example of a website built with zona, please see - [Site Layout](#site-layout) - [Templates](#templates) - [Markdown Footer](#markdown-footer) - - [RSS Feed Generation](#rss-feed-generation) - [Internal Link Resolution](#internal-link-resolution) - [Syntax Highlighting](#syntax-highlighting) - [Markdown Extensions](#markdown-extensions) @@ -50,7 +49,6 @@ For an example of a website built with zona, please see - Live refresh in browser preview. - `jinja2` template support with sensible defaults included. - Basic page, blog post, post list. -- RSS feed generation. - Glob ignore. - YAML frontmatter. - Easily configurable sitemap header. @@ -214,17 +212,6 @@ it's parsed and rendered into HTML, then made available to other templates as the `footer` variable. If `footer.md` is missing but `footer.html` exists, then it's used instead. **Note: links are _not_ resolved in the footer.** -### RSS Feed Generation - -Zona can also generates an RSS feed containing your blog posts. This feature is -disabled by default, and you can enable it in the -[configuration](#configuration). - -The default location is a file called `rss.xml` in the content root. All RSS -related configuration is specified in `config.yml`. If you plan to use the feed, -make sure to replace the placeholder `link`, `title`, `description`, and -`author` configuration values before you set `enabled: true`. - ### Internal Link Resolution When zona encounters links in Markdown documents, it attempts to resolve them as @@ -244,11 +231,11 @@ modified if they point to a real file that's not included in the ignore list. Zona uses [Pygments] to provide syntax highlighting for fenced code blocks. The following Pygments plugins are included: -- [pygments-kakoune](https://codeberg.org/ficd/pygments-kakoune) +- [pygments-kakoune](https://codeberg.com/ficd/pygments-kakoune) - A lexer providing for highlighting Kakoune code. Available under the `kak` and `kakrc` aliases. -- [pygments-ashen](https://codeberg.org/ficd/ashen/tree/main/item/pygments/README.md) - - An implementation of the [Ashen](https://codeberg.org/ficd/ashen) theme for +- [pygments-ashen](https://codeberg.com/ficd/ashen/tree/main/item/pygments/README.md) + - An implementation of the [Ashen](https://codeberg.com/ficd/ashen) theme for Pygments. If you want to use any external Pygments styles or lexers, they must be @@ -322,19 +309,18 @@ YAML frontmatter can be used to configure the metadata of documents. All of them are optional. `none` is used when the option is unset. The following options are available: -| Key | Type & Default | Description | -| ------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------ | -| `title` | `str` = title-cased filename. | Title of the page. | -| `description` | `str \| none` = `none` | Description. If omitted, default from [config](#configuration) will be used. | -| `date` | Date string = file modified time. | Displayed on blog posts and used for post_list sorting. | -| `show_title` | `bool` = `true` | Whether `metadata.title` should be included in the template. | -| `header` | `bool` = `true` | Whether the header sitemap should be rendered. | -| `footer` | `bool` = `true` | Whether the footer should be rendered. | -| `template` | `str \| none` = `none` | Template to use for this page. Relative to `templates/`, `.html` extension optional. | -| `post` | `bool \| none` = `none` | Whether this page is a **post**. `true`/`false` is _absolute_. Leave it unset for automatic detection. | -| `draft` | `bool` = `false` | Whether this page is a draft. See [drafts](#drafts) for more. | -| `ignore` | `bool` = `false` | Whether this page should be ignored in _both_ `final` and `draft` contexts. | -| `math` | `bool` = `true` | Whether the LaTeX extension should be enabled for this page. | +| Key | Type & Default | Description | +| ------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------ | +| `title` | `str` = title-cased filename. | Title of the page. | +| `date` | Date string = file modified time. | Displayed on blog posts and used for post_list sorting. | +| `show_title` | `bool` = `true` | Whether `metadata.title` should be included in the template. | +| `header` | `bool` = `true` | Whether the header sitemap should be rendered. | +| `footer` | `bool` = `true` | Whether the footer should be rendered. | +| `template` | `str \| none` = `none` | Template to use for this page. Relative to `templates/`, `.html` extension optional. | +| `post` | `bool \| none` = `none` | Whether this page is a **post**. `true`/`false` is _absolute_. Leave it unset for automatic detection. | +| `draft` | `bool` = `false` | Whether this page is a draft. See [drafts](#drafts) for more. | +| `ignore` | `bool` = `false` | Whether this page should be ignored in _both_ `final` and `draft` contexts. | +| `math` | `bool` = `true` | Whether the LaTeX extension should be enabled for this page. | **Note**: you can specify the date in any format that can be parsed by [`python-dateutil`](https://pypi.org/project/python-dateutil/). @@ -388,23 +374,12 @@ useful settings are listed here. Please see the default configuration: ```yaml -feed: - enabled: true - timezone: UTC - path: rss.xml - link: https://example.com - title: Zona Website - description: My zona website. - language: en - author: - name: John Doe - email: john@doe.net +base_url: / sitemap: Home: / ignore: - .marksman.toml markdown: - image_labels: true tab_length: 2 syntax_highlighting: enabled: true @@ -417,8 +392,6 @@ build: include_drafts: false blog: dir: blog - defaults: - description: A blog post server: reload: enabled: true @@ -427,15 +400,6 @@ server: | Name | Description | | -------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `feed.enabled` | Whether RSS feed should be generated. **Off by default**. | -| `feed.timezime` | Timezone to use for post `pubDate` values. Must be an IANA compliant string. | -| `feed.path` | Location of the feed, relative to content root. | -| `feed.link` | The base URL of the website. | -| `feed.title` | Website title. | -| `feed.description` | Website description. | -| `feed.language` | String specifying website's language code. | -| `author.name` | Your full name. | -| `author.email` | Your email address. | | `sitemap` | Sitemap dictionary. See [Sitemap](#sitemap). | | `ignore` | List of paths to ignore. See [Ignore List](#ignore-list). | | `markdown.tab_length` | How many spaces should be considered an indentation level. | @@ -446,7 +410,6 @@ server: | `build.clean_output_dir` | Whether previous build artifacts should be cleared when building. Recommended to leave this on. | | `build.include_drafts` | Whether drafts should be included by default. | | `blog.dir` | Name of a directory relative to `content/` whose children are automatically considered posts. | -| `blog.defaults.description` | Default description for blog posts with no `description` in their frontmatter. | | `server.reload.enabled` | Whether the preview server should use [live reload](#live-preview). | | `server.reload.scroll_tolerance` | The distance, in pixels, from the bottom to still count as "scrolled to bottom". | @@ -479,7 +442,7 @@ output. If you set `draft: true` in a page's frontmatter, it will be marked as a draft. Drafts are completely excluded from `zona build` and `zona serve` unless the `--draft` flag is specified. -[Ashen]: https://codeberg.org/ficd/ashen +[Ashen]: https://codeberg.com/ficd/ashen [Pygments]: https://pygments.org/ ## Known Problems diff --git a/pyproject.toml b/pyproject.toml index 185c12d..7653d22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "zona" -version = "1.2.2" +version = "1.2.0" description = "Opinionated static site generator." license = "BSD-3-Clause " license-files = ["LICENSE"] @@ -11,7 +11,6 @@ authors = [ requires-python = ">=3.12" dependencies = [ "dacite>=1.9.2", - "feedgen>=1.0.0", "jinja2>=3.1.6", "l2m4m>=1.0.4", "markdown>=3.8.2", @@ -58,7 +57,7 @@ reportUnusedCallResult = false reportCallInDefaultInitializer = false enableTypeIgnoreComments = true reportIgnoreCommentWithoutRule = false -allowedUntypedLibraries = ["frontmatter", "pygments", "pymdownx", "l2m4m", "feedgen", "feedgen.feed"] +allowedUntypedLibraries = ["frontmatter", "pygments", "pymdownx", "l2m4m"] [tool.ruff] line-length = 70 diff --git a/src/zona/builder.py b/src/zona/builder.py index dd9509f..416edc8 100644 --- a/src/zona/builder.py +++ b/src/zona/builder.py @@ -2,8 +2,6 @@ import shutil from datetime import date from pathlib import Path -from feedgen.feed import FeedGenerator - from zona import markdown as zmd from zona import util from zona.config import ZonaConfig @@ -57,9 +55,7 @@ class ZonaBuilder: layout.root / "content" / "static" ): logger.debug(f"Parsing {path.name}.") - item.metadata, item.content = parse_metadata( - path, config=self.config - ) + item.metadata, item.content = parse_metadata(path) if item.metadata.ignore or ( item.metadata.draft and not self.config.build.include_drafts @@ -101,45 +97,7 @@ class ZonaBuilder: items.append(item) self.items = items - def generate_feed(self) -> bytes: - post_list = self._get_post_list() - config = self.config.feed - if config.link.endswith("/"): - config.link = config.link[:-2] - fg = FeedGenerator() - fg.id(config.link) - fg.title(config.title) - author = { - "name": config.author.name, - "email": config.author.email, - } - fg.author(author) - fg.link( - href=f"{config.link}/{config.path}", - rel="self", - type="application/rss+xml", - ) - fg.language(config.language) - fg.description(config.description) - - for post in post_list: - assert post.metadata - fe = fg.add_entry() # pyright: ignore[reportUnknownVariableType] - fe.id(f"{config.link}{util.normalize_url(post.url)}") # pyright: ignore[reportUnknownMemberType] - fe.link( # pyright: ignore[reportUnknownMemberType] - href=f"{config.link}{util.normalize_url(post.url)}" - ) - fe.title(post.metadata.title) # pyright: ignore[reportUnknownMemberType] - fe.author(author) # pyright: ignore[reportUnknownMemberType] - desc = post.metadata.description - fe.description(desc) # pyright: ignore[reportUnknownMemberType] - date = post.metadata.date - fe.pubDate(date) # pyright: ignore[reportUnknownMemberType] - out: bytes = fg.rss_str(pretty=True) # pyright: ignore[reportUnknownVariableType] - assert isinstance(out, bytes) - return out - - def _get_post_list(self) -> list[Item]: + def _build(self): assert self.items # sort according to date # descending order @@ -150,12 +108,7 @@ class ZonaBuilder: else date.min, reverse=True, ) - return post_list - - def _build(self): - post_list = self._get_post_list() # number of posts - # generate RSS here posts = len(post_list) # link post chronology for i, item in enumerate(post_list): @@ -227,9 +180,4 @@ class ZonaBuilder: self._discover() logger.debug("Building...") self._build() - if self.config.feed.enabled: - rss = self.generate_feed() - path = self.layout.output / self.config.feed.path - util.ensure_parents(path) - path.write_bytes(rss) self.fresh = False diff --git a/src/zona/cli.py b/src/zona/cli.py index da15a22..4b48d3a 100644 --- a/src/zona/cli.py +++ b/src/zona/cli.py @@ -1,4 +1,3 @@ -from importlib.metadata import version as __version__ from pathlib import Path from typing import Annotated @@ -135,23 +134,8 @@ def serve( ) -def version_callback(value: bool): - if value: - print(f"Zona version: {__version__('zona')}") - raise typer.Exit() - - @app.callback() def main_entry( - version: Annotated[ # pyright: ignore[reportUnusedParameter] - bool | None, - typer.Option( - "--version", - callback=version_callback, - is_eager=True, - help="Print version info and exit.", - ), - ] = None, verbosity: Annotated[ str, typer.Option( diff --git a/src/zona/config.py b/src/zona/config.py index 84844c7..2a50fd4 100644 --- a/src/zona/config.py +++ b/src/zona/config.py @@ -1,13 +1,7 @@ -from __future__ import annotations - from dataclasses import dataclass, field -from datetime import datetime, tzinfo from pathlib import Path -from typing import Any -from zoneinfo import ZoneInfo import yaml -from dacite import Config as DaciteConfig from dacite import from_dict from zona.log import get_logger @@ -31,17 +25,9 @@ def find_config(start: Path | None = None) -> Path | None: SitemapConfig = dict[str, str] -@dataclass -class PostDefaultsConfig: - description: str = "A blog post" - - @dataclass class BlogConfig: dir: str = "blog" - defaults: PostDefaultsConfig = field( - default_factory=PostDefaultsConfig - ) @dataclass @@ -83,40 +69,12 @@ class ServerConfig: reload: ReloadConfig = field(default_factory=ReloadConfig) -@dataclass -class AuthorConfig: - name: str = "John Doe" - email: str = "john@doe.net" - - -@dataclass -class FeedConfig: - enabled: bool = True - timezone: tzinfo = field(default_factory=lambda: ZoneInfo("UTC")) - path: str = "rss.xml" - link: str = "https://example.com" - title: str = "Zona Website" - description: str = "My zona website." - language: str = "en" - author: AuthorConfig = field(default_factory=AuthorConfig) - - IGNORELIST = [".marksman.toml"] -def parse_timezone(s: Any) -> tzinfo: - if isinstance(s, str): - return ZoneInfo(s) - else: - raise TypeError( - f"Expected {str}, got {type(s)} for config key timezone" - ) - - @dataclass class ZonaConfig: base_url: str = "/" - feed: FeedConfig = field(default_factory=FeedConfig) # dictionary where key is name, value is url sitemap: SitemapConfig = field( default_factory=lambda: {"Home": "/"} @@ -129,12 +87,7 @@ class ZonaConfig: server: ServerConfig = field(default_factory=ServerConfig) @classmethod - def from_file(cls, path: Path) -> ZonaConfig: + def from_file(cls, path: Path) -> "ZonaConfig": with open(path, "r") as f: raw = yaml.safe_load(f) - config: ZonaConfig = from_dict( - data_class=cls, - data=raw, - config=DaciteConfig(type_hooks={tzinfo: parse_timezone}), - ) - return config + return from_dict(data_class=cls, data=raw) diff --git a/src/zona/data/content/static/style.css b/src/zona/data/content/static/style.css index caa0eef..56644c3 100644 --- a/src/zona/data/content/static/style.css +++ b/src/zona/data/content/static/style.css @@ -61,11 +61,13 @@ header { } .site-logo.hover-symbol::before { - content: "~/"; + content: "@"; + /* color: var(--main-bullet-color);*/ } .title.hover-symbol::before { - content: "$"; + content: ">"; + /* color: var(--main-bullet-color);*/ } .hover-symbol { @@ -91,28 +93,11 @@ header { .hover-symbol:hover::before { opacity: 1; - color: var(--main-placeholder-color); + color: var(--main-placeholder-color); /* only the symbol changes color */ } .hover-symbol:hover { background-color: transparent; -} - -.toc ul { - font-family: monospace; - text-transform: lowercase; - margin: auto; - width: 50%; -} - -.toc ul ul { - padding-left: 1em; - margin-left: 1em; - /* list-style-type: "–– ";*/ -} -.toc ul ul ul { - padding-left: 1em; - margin-left: 1em; - /* list-style-type: "-- ";*/ + /* color: var(--main-placeholder-color);*/ } .toclink { @@ -120,15 +105,6 @@ header { text-decoration: none; color: inherit; transition: color 0.15s ease; - text-transform: lowercase; - font-family: monospace; -} -.post-list a { - position: relative; - text-decoration: none; - transition: color 0.15s ease; - text-transform: lowercase; - font-family: monospace; } .toclink::before { @@ -236,35 +212,8 @@ h6 { font-weight: bold; } -/*ul {*/ -/* list-style-type: disc;*/ -/*}*/ - ul { - list-style-type: "– "; -} -ul ul { - padding-left: 1em; - margin-left: 1em; - list-style-type: "+ "; -} -ul ul ul { - list-style-type: "~ "; -} -ul ul ul ul { - list-style-type: "• "; -} -ul ul ul ul ul { - list-style-type: "– "; -} -ul ul ul ul ul ul { - list-style-type: "+ "; -} -ul ul ul ul ul ul ul { - list-style-type: "~ "; -} -ul ul ul ul ul ul ul ul { - list-style-type: "• "; + list-style-type: disc; } li::marker { diff --git a/src/zona/data/templates/page.html b/src/zona/data/templates/page.html index 0ff499b..0c22f8b 100644 --- a/src/zona/data/templates/page.html +++ b/src/zona/data/templates/page.html @@ -1,8 +1,8 @@ {% extends "base.html" %} {% block content %} {% if metadata.show_title %} {% -include "title.html" %} {% if metadata.date.date() %} +include "title.html" %} {% if metadata.date %}