readthememo 2025.8.1
A minimalistic, self-hosted, statically rendered, micro-blogging engine.
Requires Python: >=3.11

# `readthememo`

A minimalistic, self-hosted, statically rendered, micro-blogging engine.

---

[![PyPI - Version](https://img.shields.io/pypi/v/readthememo?logo=pypi&logoColor=%23FFD43B)](https://pypi.org/project/readthememo/)
[![builds.sr.ht status](https://builds.sr.ht/~docbibi/memos/commits/main/test-matrix.yaml.svg)](https://builds.sr.ht/~docbibi/memos/commits/main/test-matrix.yaml?)

---

## Overview

`readthememo` (command: `memos`) is a simple tool that transforms memos into static HTML pages.
It's designed for quick, low-friction publishing of short-form content without the complexity of traditional blogging platforms.

## Features

- **Minimal dependencies**: Built with Python 3.11+ using Jinja2, marko, and tyro
- **Static output**: Generates standalone HTML files
- **CommonMark (markdown) input**: Write your content in a simple, readable format
- **LogSeq-ready**: Directory format works seamlessly with LogSeq
- **Responsive design**: Uses Pico CSS for clean, semantic styling
- **CLI interface**: Simple command-line tool for building your site
- **Self-contained**: No database or server requirements

## Installation

```shell
pipx install readthememo
```

## Usage

### Basic Usage

Build HTML from a single CommonMark file:

```shell
memos path/to/your/memos.md output.html
```

Build HTML from a directory with CommonMark files (e.g. LogSeq journals):

```shell
memos path/to/your/memos/ output.html
```

### Standard Input/Output

Read from stdin and write to stdout:

```shell
cat memos.md | memos - > output.html
```

### CommonMark Format

**site.yaml:**

```markdown
---
title: "My Blog Title"
meta:
  author: "Your Name"
header:
  headline: "My Blog"
  motto:
  - "A description of your blog"
  - "by Your Name"
---
```

**2025-01-01.md:**

```markdown
## My First Memo #InterstingTopic

This is the content of my first memo.

It can contain <strong>HTML tags</strong> and multiple paragraphs.
```

**2025-01-02.md:**

```markdown
- ## Hello from LogSeq
    - This memo is bullet point heavy, as can be seen when reading raw content from a LogSeq journal.
    - The idea is to strip the first two levels of bullet points to "flatten" the structure.
```

**Key features:**

- **Natural prose**: Write directly in markdown (CommonMark)
- **LogSeq compatible**: Use `YYYY-MM-DD.md` or `YYYY_MM_DD.md` naming; strip the first two levels of bullet-points
- **Tag extraction**: Add tags to titles using `#tag` format

### Alternative: Single File with YAML Frontmatter

You can also use a single markdown file with YAML frontmatter:

```markdown
---
title: "My Blog Title"
meta:
  author: "Your Name"
header:
  headline: "My Blog"
  motto:
  - "A description of your blog"
  - "by Your Name"
---

# 2025-01-01

## My First Memo

This is the content of my first memo.

It can contain <strong>HTML tags</strong> and multiple paragraphs.

# 2025-01-02

## Another Memo #tech #thoughts

This memo has tags listed after the title.
```

This format is useful for smaller blogs or when you prefer to keep everything in one file.

### LogSeq Integration

The directory structure is designed to work seamlessly with LogSeq:

**Setup:**

- No special care needed for LogSeq
- Create a `site.yaml` file at the base of your LogSeq graph

```yaml
title: "My Blog Title"
meta:
  author: "Your Name"
header:
  headline: "My Blog"
  motto:
  - "A description of your blog"
  - "by Your Name"
```

**LogSeq workflow:**

- Open your memos directory as a LogSeq graph
- Create daily notes using LogSeq's journal feature
- Use `## Title #tag` format for your memo titles
- Write content underneath (nested)
- Repeat for other articles on the same day

**Publishing workflow:**

```shell
memos path/to/logseq-graph > output.html
```

**File naming compatibility:**

- LogSeq uses `YYYY_MM_DD.md` format by default (underscores), but will happily use files named using hyphens (
  `YYYY-MM-DD.md`)
- `readthememo` supports both `YYYY-MM-DD.md` and `YYYY_MM_DD.md`

**LogSeq features that work:**

- **Daily notes**: Perfect for memo-style content
- **Tags**: Use `#tag` in titles for automatic tag extraction
- **Block references**: Can be used within memo content
- **Linked references**: Work normally within LogSeq

**What gets published:**

- Only files matching `YYYY-MM-DD.md` or `YYYY_MM_DD.md` patterns
- Other LogSeq files (config, assets, etc.) are ignored
- Content is processed as standard markdown with HTML passthrough

## Project Structure

```
├── src/readthememo/        # Main application code
│   ├── templates/          # Jinja2 HTML templates
│   ├── cli.py              # Command-line interface
│   ├── core.py             # Core parsing and rendering logic
│   └── templates.py        # Template environment setup
├── static/                 # CSS and static assets
├── tests/                  # Test suite
├── docs/decisions/         # Architecture Decision Records
└── memos/                  # Example memos documenting this project itself
```

## Development

You will need [`uv`](https://docs.astral.sh/uv/), install it first through your method of choice.
For example with [pipx](https://pipx.pypa.io):

```shell
pipx install uv
```

### Running Tests

```shell
uv run pytest
```

### Code Quality

The project uses ruff for linting and formatting:

```shell
uv run ruff check
uv run ruff format
```

### Pre-commit Hooks

Set up pre-commit hooks:

```shell
uv run pre-commit install
```

## Architecture

This project follows a functional core, imperative shell architecture:

- **Core**: Pure functions for parsing TOML and rendering HTML
- **Shell**: CLI interface and I/O operations
- **Templates**: Jinja2 templates for HTML generation

See `docs/decisions/` for detailed architecture decisions.

## Versioning and Publishing

### Version Management

This project follows [Calendar Versioning](https://calver.org/) using the `YYYY.PATCH` format:

- **Year**: Current calendar year (e.g., `2025`)
- **Patch**: Incremental release number within the year (e.g., `8`)
- **Example**: `2025.8`

To create a new release:

1. Update the version:

   ```shell
   uv version --frozen --bump patch
   uv lock
   ```

1. Update the changelog in `CHANGELOG.md`:

   - The information about the should be there already
   - What should be left is to add the release information:

   ```shell
   sed -i --posix -e "/## \[Unreleased]/ a \\\n## [$(uv version | awk '{print $2}')] - $(date --iso=date)" CHANGELOG.md
   ```

1. Commit the changes:

   ```shell
   git commit -m "build: Release version $(uv version | awk '{print $2}')" pyproject.toml uv.lock CHANGELOG.md
   ```

### Publishing to PyPI

1. Build the package using `uv`:

   ```shell
   # Clean-up previous builds
   git clean -xdf dist/
   # Build now
   uv build
   ```

1. Ensure the `keyring` tool is available for the tokens, and define the tokens (only once per machine or after the tokens are refreshed):

   ```shell
   uv tool install keyring
   keyring set https://test.pypi.org/legacy/ __token__
   keyring set https://upload.pypi.org/legacy/ __token__
   ```

1. Publish to PyPI **(for test releases)**:

   ```shell
   uv publish --index testpypi --username __token__
   ```

1. Publish to PyPI:

   ```shell
   uv publish --username __token__
   ```

### Automated CI/CD

The project uses [SourceHut Builds](https://builds.sr.ht) for continuous integration:

- **Test Matrix**: Runs tests across Python versions (3.11, 3.12, 3.13, and 3.14)
- **Package Testing**: Validates both source distributions and wheels
- **Site Publishing**: Automatically publishes to [readthememo.app](https://readthememo.app) when changes are pushed to `main`

All CI configuration is in the `.builds/` directory.

## License

[GPL-3.0-or-later](COPYING.txt)

## Contributing

1. Check existing Architecture Decision Records in `docs/decisions/`
1. Follow the functional core, imperative shell pattern
1. Write tests for new functionality
1. Ensure code passes ruff linting
1. Update documentation as needed

## Examples

See the `memos/` directory for this very repository memos; with `site.toml` configuration and date-based `.md` files.

Also take a look at the test files (`.md`) in `tests/cases/` for single-file examples.