diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01266cc..d94ebeb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,18 +1,3 @@
-# 0.8.3
-
- * Update urllib3 dependency to resolve CVE-2026-21441
- * Fix carrying over data to next day from over-capturing data belonging to next header section
- * Other dependency updates
-
-# 0.8.2
-
- * Add ability to delete an invoice via 'Manage Invoices' dialog
-
-# 0.8.1
-
- * Fix bold/italic/strikethrough styling in certain conditions when toolbar action is used.
- * Move a code block or collapsed section (or generally, anything after a checkbox line until the next checkbox line) when moving an unchecked checkbox line to another day.
-
# 0.8.0
* Add .desktop file for Debian
diff --git a/Dockerfile.rpmbuild b/Dockerfile.rpmbuild
deleted file mode 100644
index 9013b6e..0000000
--- a/Dockerfile.rpmbuild
+++ /dev/null
@@ -1,110 +0,0 @@
-# syntax=docker/dockerfile:1
-FROM fedora:42
-
-# rpmbuild in a container does not auto-install BuildRequires. Since we're
-# building directly in Docker (not mock), we pre-install the common deps that
-# Fedora's pyproject macros will require for Bouquin.
-#
-# NOTE: bouquin also needs python3dist(sqlcipher4) at build time (because
-# %pyproject_buildrequires includes runtime deps). That one is NOT in Fedora;
-# we install it from /deps.
-RUN set -eux; \
- dnf -y update; \
- dnf -y install \
- rpm-build rpmdevtools \
- redhat-rpm-config \
- gcc \
- make \
- findutils \
- tar \
- gzip \
- rsync \
- python3 \
- python3-devel \
- python3-pip \
- python3-setuptools \
- python3-wheel \
- pyproject-rpm-macros \
- python3-rpm-macros \
- python3-poetry-core \
- desktop-file-utils \
- python3-requests \
- python3-markdown \
- python3-pyside6 \
- xcb-util-cursor ; \
- dnf -y clean all
-
-RUN set -eux; cat > /usr/local/bin/build-rpm <<'EOF'
-#!/usr/bin/env bash
-set -euo pipefail
-
-SRC="${SRC:-/src}"
-WORKROOT="${WORKROOT:-/work}"
-OUT="${OUT:-/out}"
-DEPS_DIR="${DEPS_DIR:-/deps}"
-VERSION_ID="$(grep VERSION_ID /etc/os-release | cut -d= -f2)"
-echo "Version ID is ${VERSION_ID}"
-
-# Install bouquin-sqlcipher4 from local rpm
-# Filter out .src.rpm and debug* subpackages if present.
-if [ -d "${DEPS_DIR}" ] && compgen -G "${DEPS_DIR}/*.rpm" > /dev/null; then
- mapfile -t rpms < <(ls -1 "${DEPS_DIR}"/*.rpm | grep -vE '(\.src\.rpm$|-(debuginfo|debugsource)-)' | grep "${VERSION_ID}")
- if [ "${#rpms[@]}" -gt 0 ]; then
- echo "Installing dependency RPMs from ${DEPS_DIR}:"
- printf ' - %s\n' "${rpms[@]}"
- dnf -y install "${rpms[@]}"
- dnf -y clean all
- else
- echo "NOTE: Only src/debug RPMs found in ${DEPS_DIR}; nothing installed." >&2
- fi
-else
- echo "NOTE: No RPMs found in ${DEPS_DIR}. If the build fails with missing python3dist(sqlcipher4)," >&2
- echo " mount your bouquin-sqlcipher4 RPM directory as -v
:/deps" >&2
-fi
-
-mkdir -p "${WORKROOT}" "${OUT}"
-WORK="${WORKROOT}/src"
-rm -rf "${WORK}"
-mkdir -p "${WORK}"
-
-rsync -a --delete \
- --exclude '.git' \
- --exclude '.venv' \
- --exclude 'dist' \
- --exclude 'build' \
- --exclude '__pycache__' \
- --exclude '.pytest_cache' \
- --exclude '.mypy_cache' \
- "${SRC}/" "${WORK}/"
-
-cd "${WORK}"
-
-# Determine version from pyproject.toml unless provided
-if [ -n "${VERSION:-}" ]; then
- ver="${VERSION}"
-else
- ver="$(grep -m1 '^version = ' pyproject.toml | sed -E 's/version = "([^"]+)".*/\1/')"
-fi
-
-TOPDIR="${WORKROOT}/rpmbuild"
-mkdir -p "${TOPDIR}"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
-
-tarball="${TOPDIR}/SOURCES/bouquin-${ver}.tar.gz"
-tar -czf "${tarball}" --transform "s#^#bouquin/#" .
-
-cp -v "rpm/bouquin.spec" "${TOPDIR}/SPECS/bouquin.spec"
-
-rpmbuild -ba "${TOPDIR}/SPECS/bouquin.spec" \
- --define "_topdir ${TOPDIR}" \
- --define "upstream_version ${ver}"
-
-shopt -s nullglob
-cp -v "${TOPDIR}"/RPMS/*/*.rpm "${OUT}/" || true
-cp -v "${TOPDIR}"/SRPMS/*.src.rpm "${OUT}/" || true
-echo "Artifacts copied to ${OUT}"
-EOF
-
-RUN chmod +x /usr/local/bin/build-rpm
-
-WORKDIR /work
-ENTRYPOINT ["/usr/local/bin/build-rpm"]
diff --git a/README.md b/README.md
index 30f7ce1..4a52ef4 100644
--- a/README.md
+++ b/README.md
@@ -101,25 +101,6 @@ sudo apt update
sudo apt install bouquin
```
-### Fedora 42
-
-```bash
-sudo rpm --import https://mig5.net/static/mig5.asc
-
-sudo tee /etc/yum.repos.d/mig5.repo > /dev/null << 'EOF'
-[mig5]
-name=mig5 Repository
-baseurl=https://rpm.mig5.net/$releasever/rpm/$basearch
-enabled=1
-gpgcheck=1
-repo_gpgcheck=1
-gpgkey=https://mig5.net/static/mig5.asc
-EOF
-
-sudo dnf upgrade --refresh
-sudo dnf install bouquin
-```
-
### From PyPi/pip
* `pip install bouquin`
diff --git a/bouquin/db.py b/bouquin/db.py
index 157aae8..d1c6a69 100644
--- a/bouquin/db.py
+++ b/bouquin/db.py
@@ -2392,18 +2392,6 @@ class DBManager:
(document_id, invoice_id),
)
- def delete_invoice(self, invoice_id: int) -> None:
- """Delete an invoice.
-
- Related invoice line items and invoice ↔ time log links are removed via
- ON DELETE CASCADE.
- """
- with self.conn:
- self.conn.execute(
- "DELETE FROM invoices WHERE id = ?",
- (invoice_id,),
- )
-
def time_logs_for_range(
self,
project_id: int,
diff --git a/bouquin/invoices.py b/bouquin/invoices.py
index fde6a92..a0b50cb 100644
--- a/bouquin/invoices.py
+++ b/bouquin/invoices.py
@@ -1065,10 +1065,6 @@ class InvoicesDialog(QDialog):
btn_row = QHBoxLayout()
btn_row.addStretch(1)
- delete_btn = QPushButton(strings._("delete"))
- delete_btn.clicked.connect(self._on_delete_clicked)
- btn_row.addWidget(delete_btn)
-
close_btn = QPushButton(strings._("close"))
close_btn.clicked.connect(self.accept)
btn_row.addWidget(close_btn)
@@ -1077,68 +1073,6 @@ class InvoicesDialog(QDialog):
self._reload_invoices()
- # ----------------------------------------------------------------- deletion
-
- def _on_delete_clicked(self) -> None:
- """Delete the currently selected invoice."""
- row = self.table.currentRow()
- if row < 0:
- sel = self.table.selectionModel().selectedRows()
- if sel:
- row = sel[0].row()
- if row < 0:
- QMessageBox.information(
- self,
- strings._("delete"),
- strings._("invoice_required"),
- )
- return
-
- base_item = self.table.item(row, self.COL_NUMBER)
- if base_item is None:
- return
-
- inv_id = base_item.data(Qt.ItemDataRole.UserRole)
- if not inv_id:
- return
-
- invoice_number = (base_item.text() or "").strip() or "?"
- proj_item = self.table.item(row, self.COL_PROJECT)
- project_name = (proj_item.text() if proj_item is not None else "").strip()
-
- label = strings._("delete")
- prompt = (
- f"{label} '{invoice_number}'"
- + (f" ({project_name})" if project_name else "")
- + "?"
- )
-
- resp = QMessageBox.question(
- self,
- label,
- prompt,
- QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
- QMessageBox.StandardButton.No,
- )
- if resp != QMessageBox.StandardButton.Yes:
- return
-
- # Remove any automatically created due-date reminder.
- if self.cfg.reminders:
- self._remove_invoice_due_reminder(row, int(inv_id))
-
- try:
- self._db.delete_invoice(int(inv_id))
- except Exception as e:
- QMessageBox.warning(
- self,
- strings._("error"),
- f"Failed to delete invoice: {e}",
- )
- return
-
- self._reload_invoices()
-
# ------------------------------------------------------------------ helpers
def _reload_projects(self) -> None:
diff --git a/bouquin/locales/en.json b/bouquin/locales/en.json
index f1f86dd..26a4d5c 100644
--- a/bouquin/locales/en.json
+++ b/bouquin/locales/en.json
@@ -436,6 +436,5 @@
"invoice_invalid_date_format": "Invalid date format",
"invoice_invalid_tax_rate": "The tax rate is invalid",
"invoice_no_items": "There are no items in the invoice",
- "invoice_number_required": "An invoice number is required",
- "invoice_required": "Please select a specific invoice before trying to delete an invoice."
+ "invoice_number_required": "An invoice number is required"
}
diff --git a/bouquin/locales/fr.json b/bouquin/locales/fr.json
index 87a6a16..d82890d 100644
--- a/bouquin/locales/fr.json
+++ b/bouquin/locales/fr.json
@@ -432,6 +432,5 @@
"invoice_invalid_date_format": "Format de date invalide",
"invoice_invalid_tax_rate": "Le taux de TVA est invalide",
"invoice_no_items": "La facture ne contient aucun article",
- "invoice_number_required": "Un numéro de facture est requis",
- "invoice_required": "Veuillez sélectionner une facture spécifique avant d'essayer de supprimer la facture."
+ "invoice_number_required": "Un numéro de facture est requis"
}
diff --git a/bouquin/main_window.py b/bouquin/main_window.py
index 2759272..9b812b4 100644
--- a/bouquin/main_window.py
+++ b/bouquin/main_window.py
@@ -28,6 +28,7 @@ from PySide6.QtGui import (
QGuiApplication,
QKeySequence,
QTextCursor,
+ QTextListFormat,
)
from PySide6.QtWidgets import (
QApplication,
@@ -870,13 +871,6 @@ class MainWindow(QMainWindow):
into the rollover target date (today, or next Monday if today
is a weekend).
- In addition to moving the unchecked checkbox *line* itself, this also
- moves any subsequent lines that belong to that unchecked item, stopping
- at the next *checked* checkbox line **or** the next markdown heading.
-
- This allows code fences, collapsed blocks, and notes under a todo to
- travel with it without accidentally pulling in the next section.
-
Returns True if any items were moved, False otherwise.
"""
if not getattr(self.cfg, "move_todos", False):
@@ -891,9 +885,7 @@ class MainWindow(QMainWindow):
# Regexes for markdown headings and checkboxes
heading_re = re.compile(r"^\s{0,3}(#+)\s+(.*)$")
- unchecked_re = re.compile(r"^(\s*)-\s*\[[\s☐]\]\s+(.*)$")
- checked_re = re.compile(r"^(\s*)-\s*\[[xX☑]\]\s+(.*)$")
- fence_re = re.compile(r"^\s*(`{3,}|~{3,})")
+ unchecked_re = re.compile(r"^\s*-\s*\[[\s☐]\]\s+")
def _normalize_heading(text: str) -> str:
"""
@@ -904,47 +896,13 @@ class MainWindow(QMainWindow):
text = re.sub(r"\s+#+\s*$", "", text)
return text.strip()
- def _update_fence_state(
- line: str, in_fence: bool, fence_marker: str | None
- ) -> tuple[bool, str | None]:
- """
- Track fenced code blocks (``` / ~~~). We ignore checkbox markers inside
- fences so we don't accidentally split/move based on "- [x]" that appears
- in code.
- """
- m = fence_re.match(line)
- if not m:
- return in_fence, fence_marker
-
- marker = m.group(1)
- if not in_fence:
- return True, marker
-
- # Close only when we see a fence of the same char and >= length
- if (
- fence_marker
- and marker[0] == fence_marker[0]
- and len(marker) >= len(fence_marker)
- ):
- return False, None
-
- return in_fence, fence_marker
-
- def _is_list_item(line: str) -> bool:
- s = line.lstrip()
- return bool(
- re.match(r"^([-*+]\s+|\d+\.\s+)", s)
- or unchecked_re.match(line)
- or checked_re.match(line)
- )
-
- def _insert_blocks_under_heading(
+ def _insert_todos_under_heading(
target_lines: list[str],
heading_level: int,
heading_text: str,
- blocks: list[list[str]],
+ todos: list[str],
) -> list[str]:
- """Ensure a heading exists and append blocks to the end of its section."""
+ """Ensure a heading exists and append todos to the end of its section."""
normalized = _normalize_heading(heading_text)
# 1) Find existing heading with same text (any level)
@@ -984,137 +942,15 @@ class MainWindow(QMainWindow):
):
insert_at -= 1
- # Insert blocks (preserve internal blank lines)
- for block in blocks:
- if not block:
- continue
-
- # Avoid gluing a paragraph to the new block unless both look like list items
- if (
- insert_at > start_idx + 1
- and target_lines[insert_at - 1].strip() != ""
- and block[0].strip() != ""
- and not (
- _is_list_item(target_lines[insert_at - 1])
- and _is_list_item(block[0])
- )
- ):
- target_lines.insert(insert_at, "")
- insert_at += 1
-
- for line in block:
- target_lines.insert(insert_at, line)
- insert_at += 1
+ for todo in todos:
+ target_lines.insert(insert_at, todo)
+ insert_at += 1
return target_lines
- def _prune_empty_headings(src_lines: list[str]) -> list[str]:
- """Remove markdown headings whose section became empty.
-
- The rollover logic removes unchecked todo *blocks* but intentionally keeps
- headings on the source day so we can re-create the same section on the
- target day. If a heading ends up with no remaining content (including
- empty subheadings), we should remove it from the source day too.
-
- Headings inside fenced code blocks are ignored.
- """
-
- # Identify headings (outside fences) and their levels
- heading_levels: dict[int, int] = {}
- heading_indices: list[int] = []
-
- in_f = False
- f_mark: str | None = None
- for idx, ln in enumerate(src_lines):
- if not in_f:
- m = heading_re.match(ln)
- if m:
- heading_indices.append(idx)
- heading_levels[idx] = len(m.group(1))
- in_f, f_mark = _update_fence_state(ln, in_f, f_mark)
-
- if not heading_indices:
- return src_lines
-
- # Compute each heading's section boundary: next heading with level <= current
- boundary: dict[int, int] = {}
- stack: list[int] = []
- for idx in heading_indices:
- lvl = heading_levels[idx]
- while stack and lvl <= heading_levels[stack[-1]]:
- boundary[stack.pop()] = idx
- stack.append(idx)
- for idx in stack:
- boundary[idx] = len(src_lines)
-
- # Build parent/children relationships based on heading levels
- children: dict[int, list[int]] = {}
- parent_stack: list[int] = []
- for idx in heading_indices:
- lvl = heading_levels[idx]
- while parent_stack and lvl <= heading_levels[parent_stack[-1]]:
- parent_stack.pop()
- if parent_stack:
- children.setdefault(parent_stack[-1], []).append(idx)
- parent_stack.append(idx)
-
- # Determine whether each heading has any non-heading, non-blank content in its span
- has_body: dict[int, bool] = {}
- for h_idx in heading_indices:
- end = boundary[h_idx]
- body = False
- in_f = False
- f_mark = None
- for j in range(h_idx + 1, end):
- ln = src_lines[j]
- if not in_f:
- if ln.strip() and not heading_re.match(ln):
- body = True
- break
- in_f, f_mark = _update_fence_state(ln, in_f, f_mark)
- has_body[h_idx] = body
-
- # Bottom-up: keep headings that have body content or any kept child headings
- keep: dict[int, bool] = {}
- for h_idx in reversed(heading_indices):
- keep_child = any(keep.get(ch, False) for ch in children.get(h_idx, []))
- keep[h_idx] = has_body[h_idx] or keep_child
-
- remove_set = {idx for idx, k in keep.items() if not k}
- if not remove_set:
- return src_lines
-
- # Remove empty headings and any immediate blank lines following them
- out: list[str] = []
- i = 0
- while i < len(src_lines):
- if i in remove_set:
- i += 1
- while i < len(src_lines) and src_lines[i].strip() == "":
- i += 1
- continue
- out.append(src_lines[i])
- i += 1
-
- # Normalize excessive blank lines created by removals
- cleaned: list[str] = []
- prev_blank = False
- for ln in out:
- blank = ln.strip() == ""
- if blank and prev_blank:
- continue
- cleaned.append(ln)
- prev_blank = blank
-
- while cleaned and cleaned[0].strip() == "":
- cleaned.pop(0)
- while cleaned and cleaned[-1].strip() == "":
- cleaned.pop()
- return cleaned
-
- # Collect moved blocks as (heading_info, block_lines)
+ # Collect moved todos as (heading_info, item_text)
# heading_info is either None or (level, heading_text)
- moved_blocks: list[tuple[tuple[int, str] | None, list[str]]] = []
+ moved_items: list[tuple[tuple[int, str] | None, str]] = []
any_moved = False
# Look back N days (yesterday = 1, up to `days_back`)
@@ -1130,87 +966,28 @@ class MainWindow(QMainWindow):
moved_from_this_day = False
current_heading: tuple[int, str] | None = None
- in_fence = False
- fence_marker: str | None = None
+ for line in lines:
+ # Track the last seen heading (# / ## / ###)
+ m_head = heading_re.match(line)
+ if m_head:
+ level = len(m_head.group(1))
+ heading_text = _normalize_heading(m_head.group(2))
+ if level <= 3:
+ current_heading = (level, heading_text)
+ # Keep headings in the original day
+ remaining_lines.append(line)
+ continue
- i = 0
- while i < len(lines):
- line = lines[i]
-
- # If we're not in a fenced code block, we can interpret headings/checkboxes
- if not in_fence:
- # Track the last seen heading (# / ## / ###)
- m_head = heading_re.match(line)
- if m_head:
- level = len(m_head.group(1))
- heading_text = _normalize_heading(m_head.group(2))
- if level <= 3:
- current_heading = (level, heading_text)
- # Keep headings in the original day (only headings ABOVE a moved block are "carried")
- remaining_lines.append(line)
- in_fence, fence_marker = _update_fence_state(
- line, in_fence, fence_marker
- )
- i += 1
- continue
-
- # Start of an unchecked checkbox block
- m_unchecked = unchecked_re.match(line)
- if m_unchecked:
- indent = m_unchecked.group(1) or ""
- item_text = m_unchecked.group(2)
- block: list[str] = [f"{indent}- [ ] {item_text}"]
-
- i += 1
- # Consume subsequent lines until the next *checked* checkbox
- # (ignoring any "- [x]" that appear inside fenced code blocks)
- block_in_fence = in_fence
- block_fence_marker = fence_marker
-
- while i < len(lines):
- nxt = lines[i]
-
- # If we're not inside a fence, a checked checkbox ends the block,
- # otherwise a new heading does as well.
- if not block_in_fence and (
- checked_re.match(nxt) or heading_re.match(nxt)
- ):
- break
-
- # Normalize any unchecked checkbox lines inside the block
- m_inner_unchecked = (
- unchecked_re.match(nxt) if not block_in_fence else None
- )
- if m_inner_unchecked:
- inner_indent = m_inner_unchecked.group(1) or ""
- inner_text = m_inner_unchecked.group(2)
- block.append(f"{inner_indent}- [ ] {inner_text}")
- else:
- block.append(nxt)
-
- # Update fence state after consuming the line
- block_in_fence, block_fence_marker = _update_fence_state(
- nxt, block_in_fence, block_fence_marker
- )
- i += 1
-
- # Carry the last heading *above* the unchecked checkbox
- moved_blocks.append((current_heading, block))
- moved_from_this_day = True
- any_moved = True
-
- # We consumed the block; keep scanning from the checked checkbox (or EOF)
- continue
-
- # Default: keep the line on the original day
- remaining_lines.append(line)
- in_fence, fence_marker = _update_fence_state(
- line, in_fence, fence_marker
- )
- i += 1
+ # Unchecked markdown checkboxes: "- [ ] " or "- [☐] "
+ if unchecked_re.match(line):
+ item_text = unchecked_re.sub("", line)
+ moved_items.append((current_heading, item_text))
+ moved_from_this_day = True
+ any_moved = True
+ else:
+ remaining_lines.append(line)
if moved_from_this_day:
- remaining_lines = _prune_empty_headings(remaining_lines)
modified_text = "\n".join(remaining_lines)
# Save the cleaned-up source day
self.db.save_new_version(
@@ -1222,52 +999,33 @@ class MainWindow(QMainWindow):
if not any_moved:
return False
- # --- Merge all moved blocks into the *target* date ---
+ # --- Merge all moved items into the *target* date ---
target_text = self.db.get_entry(target_iso) or ""
- # Treat a whitespace-only target note as truly empty; otherwise we can
- # end up appending the new heading *after* leading blank lines (e.g. if
- # a newly-created empty day was previously saved as just "\n").
- if not target_text.strip():
- target_lines = []
- else:
- target_lines = target_text.split("\n")
+ target_lines = target_text.split("\n") if target_text else []
- by_heading: dict[tuple[int, str], list[list[str]]] = {}
- plain_blocks: list[list[str]] = []
+ by_heading: dict[tuple[int, str], list[str]] = {}
+ plain_items: list[str] = []
- for heading_info, block in moved_blocks:
+ for heading_info, item_text in moved_items:
+ todo_line = f"- [ ] {item_text}"
if heading_info is None:
- plain_blocks.append(block)
+ # No heading above this checkbox in the source: behave as before
+ plain_items.append(todo_line)
else:
- by_heading.setdefault(heading_info, []).append(block)
+ by_heading.setdefault(heading_info, []).append(todo_line)
- # First insert all blocks that have headings
- for (level, heading_text), blocks in by_heading.items():
- target_lines = _insert_blocks_under_heading(
- target_lines, level, heading_text, blocks
+ # First insert all items that have headings
+ for (level, heading_text), todos in by_heading.items():
+ target_lines = _insert_todos_under_heading(
+ target_lines, level, heading_text, todos
)
- # Then append all blocks without headings at the end, like before
- if plain_blocks:
+ # Then append all items without headings at the end, like before
+ if plain_items:
if target_lines and target_lines[-1].strip():
target_lines.append("") # one blank line before the "unsectioned" todos
- first = True
- for block in plain_blocks:
- if not block:
- continue
- if (
- not first
- and target_lines
- and target_lines[-1].strip() != ""
- and block[0].strip() != ""
- and not (
- _is_list_item(target_lines[-1]) and _is_list_item(block[0])
- )
- ):
- target_lines.append("")
- target_lines.extend(block)
- first = False
+ target_lines.extend(plain_items)
new_target_text = "\n".join(target_lines)
if not new_target_text.endswith("\n"):
@@ -1483,58 +1241,46 @@ class MainWindow(QMainWindow):
self._toolbar_bound = True
def _sync_toolbar(self):
- """
- Keep the toolbar "sticky" by reflecting the markdown state at the current caret/selection.
- """
+ fmt = self.editor.currentCharFormat()
c = self.editor.textCursor()
- line = c.block().text()
-
- # Inline styles (markdown-aware)
- bold_on = bool(getattr(self.editor, "is_markdown_bold_active", lambda: False)())
- italic_on = bool(
- getattr(self.editor, "is_markdown_italic_active", lambda: False)()
- )
- strike_on = bool(
- getattr(self.editor, "is_markdown_strike_active", lambda: False)()
- )
# Block signals so setChecked() doesn't re-trigger actions
QSignalBlocker(self.toolBar.actBold)
QSignalBlocker(self.toolBar.actItalic)
QSignalBlocker(self.toolBar.actStrike)
- self.toolBar.actBold.setChecked(bold_on)
- self.toolBar.actItalic.setChecked(italic_on)
- self.toolBar.actStrike.setChecked(strike_on)
+ self.toolBar.actBold.setChecked(fmt.fontWeight() == QFont.Weight.Bold)
+ self.toolBar.actItalic.setChecked(fmt.fontItalic())
+ self.toolBar.actStrike.setChecked(fmt.fontStrikeOut())
- # Headings: infer from leading markdown markers
- heading_level = 0
- m = re.match(r"^\s*(#{1,3})\s+", line)
- if m:
- heading_level = len(m.group(1))
+ # Headings: decide which to check by current point size
+ def _approx(a, b, eps=0.5): # small float tolerance
+ return abs(float(a) - float(b)) <= eps
+
+ cur_size = fmt.fontPointSize() or self.editor.font().pointSizeF()
+
+ bH1 = _approx(cur_size, 24)
+ bH2 = _approx(cur_size, 18)
+ bH3 = _approx(cur_size, 14)
QSignalBlocker(self.toolBar.actH1)
QSignalBlocker(self.toolBar.actH2)
QSignalBlocker(self.toolBar.actH3)
QSignalBlocker(self.toolBar.actNormal)
- self.toolBar.actH1.setChecked(heading_level == 1)
- self.toolBar.actH2.setChecked(heading_level == 2)
- self.toolBar.actH3.setChecked(heading_level == 3)
- self.toolBar.actNormal.setChecked(heading_level == 0)
-
- # Lists: infer from leading markers on the current line
- bullets_on = bool(re.match(r"^\s*(?:•|-|\*)\s+", line))
- numbers_on = bool(re.match(r"^\s*\d+\.\s+", line))
- checkboxes_on = bool(re.match(r"^\s*[☐☑]\s+", line))
+ self.toolBar.actH1.setChecked(bH1)
+ self.toolBar.actH2.setChecked(bH2)
+ self.toolBar.actH3.setChecked(bH3)
+ self.toolBar.actNormal.setChecked(not (bH1 or bH2 or bH3))
+ # Lists
+ lst = c.currentList()
+ bullets_on = lst and lst.format().style() == QTextListFormat.Style.ListDisc
+ numbers_on = lst and lst.format().style() == QTextListFormat.Style.ListDecimal
QSignalBlocker(self.toolBar.actBullets)
QSignalBlocker(self.toolBar.actNumbers)
- QSignalBlocker(self.toolBar.actCheckboxes)
-
- self.toolBar.actBullets.setChecked(bullets_on)
- self.toolBar.actNumbers.setChecked(numbers_on)
- self.toolBar.actCheckboxes.setChecked(checkboxes_on)
+ self.toolBar.actBullets.setChecked(bool(bullets_on))
+ self.toolBar.actNumbers.setChecked(bool(numbers_on))
def _change_font_size(self, delta: int) -> None:
"""Change font size for all editor tabs and save the setting."""
diff --git a/bouquin/markdown_editor.py b/bouquin/markdown_editor.py
index b1a6d66..849f515 100644
--- a/bouquin/markdown_editor.py
+++ b/bouquin/markdown_editor.py
@@ -46,7 +46,7 @@ class MarkdownEditor(QTextEdit):
_COLLAPSE_LABEL_COLLAPSE = "collapse"
_COLLAPSE_LABEL_EXPAND = "expand"
_COLLAPSE_END_MARKER = ""
- # Accept either "collapse" or "expand" in the header text
+ # Accept either "collapse" or "expand" in the header text (older files used only "collapse")
_COLLAPSE_HEADER_RE = re.compile(r"^([ \t]*)([▸▾])\s+(?:collapse|expand)\s*$")
_COLLAPSE_END_RE = re.compile(r"^([ \t]*)\s*$")
@@ -114,9 +114,6 @@ class MarkdownEditor(QTextEdit):
# Track if we're currently updating text programmatically
self._updating = False
- # Track pending inline marker insertion (e.g. Italic with no selection)
- self._pending_inline_marker: str | None = None
-
# Help avoid double-click selecting of checkbox
self._suppress_next_checkbox_double_click = False
@@ -931,69 +928,6 @@ class MarkdownEditor(QTextEdit):
return None
- def _maybe_skip_over_marker_run(self, key: Qt.Key) -> bool:
- """Skip over common markdown marker runs when navigating with Left/Right.
-
- This prevents the caret from landing *inside* runs like '**', '***', '__', '___' or '~~',
- which can cause temporary toolbar-state flicker and makes navigation feel like it takes
- "two presses" to get past closing markers.
-
- Hold any modifier key (Shift/Ctrl/Alt/Meta) to disable this behavior.
- """
- c = self.textCursor()
- if c.hasSelection():
- return False
-
- p = c.position()
- doc_max = self._doc_max_pos()
-
- # Right: run starts at the caret
- if key == Qt.Key.Key_Right:
- if p >= doc_max:
- return False
- ch = self._text_range(p, p + 1)
- if ch not in ("*", "_", "~"):
- return False
-
- run = 0
- while p + run < doc_max and self._text_range(p + run, p + run + 1) == ch:
- run += 1
-
- # Only skip multi-char runs (bold/strong/emphasis runs or strike)
- if ch in ("*", "_") and run >= 2:
- c.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, run)
- self.setTextCursor(c)
- return True
- if ch == "~" and run == 2:
- c.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, 2)
- self.setTextCursor(c)
- return True
-
- return False
-
- # Left: run ends at the caret
- if key == Qt.Key.Key_Left:
- if p <= 0:
- return False
- ch = self._text_range(p - 1, p)
- if ch not in ("*", "_", "~"):
- return False
-
- run = 0
- while p - 1 - run >= 0 and self._text_range(p - 1 - run, p - run) == ch:
- run += 1
-
- if ch in ("*", "_") and run >= 2:
- c.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor, run)
- self.setTextCursor(c)
- return True
- if ch == "~" and run == 2:
- c.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor, 2)
- self.setTextCursor(c)
- return True
-
- return False
-
def keyPressEvent(self, event):
"""Handle special key events for markdown editing."""
c = self.textCursor()
@@ -1002,6 +936,7 @@ class MarkdownEditor(QTextEdit):
in_code = self._is_inside_code_block(block)
is_fence_line = block.text().strip().startswith("```")
+ # --- NEW: 3rd backtick shortcut → open code block dialog ---
# Only when we're *not* already in a code block or on a fence line.
if event.text() == "`" and not (in_code or is_fence_line):
line = block.text()
@@ -1067,14 +1002,6 @@ class MarkdownEditor(QTextEdit):
super().keyPressEvent(event)
return
- if (
- event.key() in (Qt.Key.Key_Left, Qt.Key.Key_Right)
- and event.modifiers() == Qt.KeyboardModifier.NoModifier
- and not self.textCursor().hasSelection()
- ):
- if self._maybe_skip_over_marker_run(event.key()):
- return
-
# --- Step out of a code block with Down at EOF ---
if event.key() == Qt.Key.Key_Down:
c = self.textCursor()
@@ -1582,389 +1509,19 @@ class MarkdownEditor(QTextEdit):
# ------------------------ Toolbar action handlers ------------------------
- # ------------------------ Inline markdown helpers ------------------------
-
- def _doc_max_pos(self) -> int:
- # QTextDocument includes a trailing null character; cursor positions stop before it.
- doc = self.document()
- return max(0, doc.characterCount() - 1)
-
- def _text_range(self, start: int, end: int) -> str:
- """Return document text between [start, end) using QTextCursor indexing."""
- doc_max = self._doc_max_pos()
- start = max(0, min(start, doc_max))
- end = max(0, min(end, doc_max))
- if end < start:
- start, end = end, start
- tc = QTextCursor(self.document())
- tc.setPosition(start)
- tc.setPosition(end, QTextCursor.KeepAnchor)
- return tc.selectedText()
-
- def _selection_wrapped_by(
- self,
- markers: tuple[str, ...],
- *,
- require_singletons: bool = False,
- ) -> str | None:
- """
- If the current selection is wrapped by any marker in `markers`, return the marker.
-
- Supports both cases:
- 1) the selection itself includes the markers, e.g. "**bold**"
- 2) the selection is the inner text, with markers immediately adjacent in the doc.
- """
- c = self.textCursor()
- if not c.hasSelection():
- return None
-
- sel = c.selectedText()
- start = c.selectionStart()
- end = c.selectionEnd()
- doc_max = self._doc_max_pos()
-
- # Case 1: selection includes markers
- for m in markers:
- lm = len(m)
- if len(sel) >= 2 * lm and sel.startswith(m) and sel.endswith(m):
- return m
-
- # Case 2: markers adjacent to selection
- for m in markers:
- lm = len(m)
- if start < lm or end + lm > doc_max:
- continue
- before = self._text_range(start - lm, start)
- after = self._text_range(end, end + lm)
- if before != m or after != m:
- continue
-
- if require_singletons and lm == 1:
- # Ensure the single marker isn't part of a double/triple (e.g. "**" or "__")
- ch = m
- left_marker_pos = start - 1
- right_marker_pos = end
-
- if (
- left_marker_pos - 1 >= 0
- and self._text_range(left_marker_pos - 1, left_marker_pos) == ch
- ):
- continue
- if (
- right_marker_pos + 1 <= doc_max
- and self._text_range(right_marker_pos + 1, right_marker_pos + 2)
- == ch
- ):
- continue
-
- return m
-
- return None
-
- def _caret_between_markers(
- self, marker: str, *, require_singletons: bool = False
- ) -> bool:
- """True if the caret is exactly between an opening and closing marker (e.g. **|**)."""
- c = self.textCursor()
- if c.hasSelection():
- return False
-
- p = c.position()
- lm = len(marker)
- doc_max = self._doc_max_pos()
- if p < lm or p + lm > doc_max:
- return False
-
- before = self._text_range(p - lm, p)
- after = self._text_range(p, p + lm)
- if before != marker or after != marker:
- return False
-
- if require_singletons and lm == 1:
- # Disallow if either side is adjacent to the same char (part of "**", "__", "***", etc.)
- ch = marker
- if p - 2 >= 0 and self._text_range(p - 2, p - 1) == ch:
- return False
- if p + 1 <= doc_max and self._text_range(p + 1, p + 2) == ch:
- return False
-
- return True
-
- def _caret_before_marker(
- self, marker: str, *, require_singletons: bool = False
- ) -> bool:
- """True if the caret is immediately before `marker` (e.g. |**)."""
- c = self.textCursor()
- if c.hasSelection():
- return False
-
- p = c.position()
- lm = len(marker)
- doc_max = self._doc_max_pos()
- if p + lm > doc_max:
- return False
-
- after = self._text_range(p, p + lm)
- if after != marker:
- return False
-
- if require_singletons and lm == 1:
- # Disallow if it's part of a run like "**" or "___".
- ch = marker
- if p - 1 >= 0 and self._text_range(p - 1, p) == ch:
- return False
- if p + 1 <= doc_max and self._text_range(p + 1, p + 2) == ch:
- return False
-
- return True
-
- def _unwrap_selection(
- self, marker: str, *, replacement_marker: str | None = None
- ) -> bool:
- """
- Remove `marker` wrapping from the selection.
- If replacement_marker is provided, replace marker with that (e.g. ***text*** -> *text*).
- """
- c = self.textCursor()
- if not c.hasSelection():
- return False
-
- sel = c.selectedText()
- start = c.selectionStart()
- end = c.selectionEnd()
- lm = len(marker)
- doc_max = self._doc_max_pos()
-
- def _select_inner(
- edit_cursor: QTextCursor, inner_start: int, inner_len: int
- ) -> None:
- edit_cursor.setPosition(inner_start)
- edit_cursor.setPosition(inner_start + inner_len, QTextCursor.KeepAnchor)
- self.setTextCursor(edit_cursor)
-
- # Case 1: selection includes markers
- if len(sel) >= 2 * lm and sel.startswith(marker) and sel.endswith(marker):
- inner = sel[lm:-lm]
- new_text = (
- f"{replacement_marker}{inner}{replacement_marker}"
- if replacement_marker is not None
- else inner
- )
- c.beginEditBlock()
- c.insertText(new_text)
- c.endEditBlock()
-
- # Re-select the inner content (not the markers)
- inner_start = c.position() - len(new_text)
- if replacement_marker is not None:
- inner_start += len(replacement_marker)
- _select_inner(c, inner_start, len(inner))
- return True
-
- # Case 2: marker is adjacent to selection
- if start >= lm and end + lm <= doc_max:
- before = self._text_range(start - lm, start)
- after = self._text_range(end, end + lm)
- if before == marker and after == marker:
- new_text = (
- f"{replacement_marker}{sel}{replacement_marker}"
- if replacement_marker is not None
- else sel
- )
- edit = QTextCursor(self.document())
- edit.beginEditBlock()
- edit.setPosition(start - lm)
- edit.setPosition(end + lm, QTextCursor.KeepAnchor)
- edit.insertText(new_text)
- edit.endEditBlock()
-
- inner_start = (start - lm) + (
- len(replacement_marker) if replacement_marker else 0
- )
- _select_inner(edit, inner_start, len(sel))
- return True
-
- return False
-
- def _wrap_selection(self, marker: str) -> None:
- """Wrap the current selection with `marker` and keep the content selected."""
- c = self.textCursor()
- if not c.hasSelection():
- return
- sel = c.selectedText()
- start = c.selectionStart()
- lm = len(marker)
-
- c.beginEditBlock()
- c.insertText(f"{marker}{sel}{marker}")
- c.endEditBlock()
-
- # Re-select the original content
- edit = QTextCursor(self.document())
- edit.setPosition(start + lm)
- edit.setPosition(start + lm + len(sel), QTextCursor.KeepAnchor)
- self.setTextCursor(edit)
-
- def _pos_inside_inline_span(
- self,
- patterns: list[tuple[re.Pattern, int]],
- start_in_block: int,
- end_in_block: int,
- ) -> bool:
- """True if [start_in_block, end_in_block] lies within the content region of any pattern match."""
- block_text = self.textCursor().block().text()
- for pat, mlen in patterns:
- for m in pat.finditer(block_text):
- s, e = m.span()
- cs, ce = s + mlen, e - mlen
- if cs <= start_in_block and end_in_block <= ce:
- return True
- return False
-
- def is_markdown_bold_active(self) -> bool:
- c = self.textCursor()
- bold_markers = ("***", "___", "**", "__")
-
- if c.hasSelection():
- if self._selection_wrapped_by(bold_markers) is not None:
- return True
- block = c.block()
- start_in_block = c.selectionStart() - block.position()
- end_in_block = c.selectionEnd() - block.position()
- patterns = [
- (re.compile(r"(? bool:
- c = self.textCursor()
- italic_markers = ("*", "_", "***", "___")
-
- if c.hasSelection():
- if (
- self._selection_wrapped_by(italic_markers, require_singletons=True)
- is not None
- ):
- return True
- block = c.block()
- start_in_block = c.selectionStart() - block.position()
- end_in_block = c.selectionEnd() - block.position()
- patterns = [
- (re.compile(r"(? bool:
- c = self.textCursor()
- if c.hasSelection():
- if self._selection_wrapped_by(("~~",)) is not None:
- return True
- block = c.block()
- start_in_block = c.selectionStart() - block.position()
- end_in_block = c.selectionEnd() - block.position()
- patterns = [(re.compile(r"~~(.+?)~~"), 2)]
- return self._pos_inside_inline_span(patterns, start_in_block, end_in_block)
-
- if self._caret_between_markers("~~"):
- return True
- block = c.block()
- pos_in_block = c.position() - block.position()
- patterns = [(re.compile(r"~~(.+?)~~"), 2)]
- return self._pos_inside_inline_span(patterns, pos_in_block, pos_in_block)
-
- # ------------------------ Toolbar action handlers ------------------------
-
def apply_weight(self):
- """Toggle bold formatting (markdown ** / __, and *** / ___)."""
+ """Toggle bold formatting."""
cursor = self.textCursor()
-
if cursor.hasSelection():
- # If bold+italic, toggling bold should leave italic: ***text*** -> *text*
- m = self._selection_wrapped_by(("***", "___"))
- if m is not None:
- repl = "*" if m == "***" else "_"
- if self._unwrap_selection(m, replacement_marker=repl):
- self.setFocus()
- return
-
- # Normal bold: **text** / __text__
- m = self._selection_wrapped_by(("**", "__"))
- if m is not None:
- if self._unwrap_selection(m):
- self.setFocus()
- return
-
- # Not bold: wrap selection with **
- self._wrap_selection("**")
- self.setFocus()
- return
-
- # No selection:
- # - If we're between an empty pair (**|**), remove them.
- # - If we're inside bold and sitting right before the closing marker (**text|**),
- # jump the caret *past* the marker (end-bold) instead of inserting more.
- # - Otherwise, insert a new empty pair and place the caret between.
- if self._caret_between_markers("**") or self._caret_between_markers("__"):
- marker = "**" if self._caret_between_markers("**") else "__"
- p = cursor.position()
- lm = len(marker)
- edit = QTextCursor(self.document())
- edit.beginEditBlock()
- edit.setPosition(p - lm)
- edit.setPosition(p + lm, QTextCursor.KeepAnchor)
- edit.insertText("")
- edit.endEditBlock()
- edit.setPosition(p - lm)
- self.setTextCursor(edit)
- elif self.is_markdown_bold_active() and (
- self._caret_before_marker("**") or self._caret_before_marker("__")
- ):
- marker = "**" if self._caret_before_marker("**") else "__"
- cursor.movePosition(
- QTextCursor.MoveOperation.Right,
- QTextCursor.MoveMode.MoveAnchor,
- len(marker),
- )
- self.setTextCursor(cursor)
- self._pending_inline_marker = None
+ selected = cursor.selectedText()
+ # Check if already bold
+ if selected.startswith("**") and selected.endswith("**"):
+ # Remove bold
+ new_text = selected[2:-2]
+ else:
+ # Add bold
+ new_text = f"**{selected}**"
+ cursor.insertText(new_text)
else:
# No selection - just insert markers
cursor.insertText("****")
@@ -1972,120 +1529,44 @@ class MarkdownEditor(QTextEdit):
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 2
)
self.setTextCursor(cursor)
- self._pending_inline_marker = "*"
# Return focus to editor
self.setFocus()
def apply_italic(self):
- """Toggle italic formatting (markdown * / _, and *** / ___)."""
+ """Toggle italic formatting."""
cursor = self.textCursor()
-
if cursor.hasSelection():
- # If bold+italic, toggling italic should leave bold: ***text*** -> **text**
- m = self._selection_wrapped_by(("***", "___"))
- if m is not None:
- repl = "**" if m == "***" else "__"
- if self._unwrap_selection(m, replacement_marker=repl):
- self.setFocus()
- return
-
- m = self._selection_wrapped_by(("*", "_"), require_singletons=True)
- if m is not None:
- if self._unwrap_selection(m):
- self.setFocus()
- return
-
- self._wrap_selection("*")
- self.setFocus()
- return
-
- # No selection:
- # - If we're between an empty pair (*|*), remove them.
- # - If we're inside italic and sitting right before the closing marker (*text|*),
- # jump the caret past the marker (end-italic) instead of inserting more.
- # - Otherwise, insert a new empty pair and place the caret between.
- if self._caret_between_markers(
- "*", require_singletons=True
- ) or self._caret_between_markers("_", require_singletons=True):
- marker = (
- "*"
- if self._caret_between_markers("*", require_singletons=True)
- else "_"
- )
- p = cursor.position()
- lm = len(marker)
- edit = QTextCursor(self.document())
- edit.beginEditBlock()
- edit.setPosition(p - lm)
- edit.setPosition(p + lm, QTextCursor.KeepAnchor)
- edit.insertText("")
- edit.endEditBlock()
- edit.setPosition(p - lm)
- self.setTextCursor(edit)
- self._pending_inline_marker = None
- elif self.is_markdown_italic_active() and (
- self._caret_before_marker("*", require_singletons=True)
- or self._caret_before_marker("_", require_singletons=True)
- ):
- marker = (
- "*" if self._caret_before_marker("*", require_singletons=True) else "_"
- )
- cursor.movePosition(
- QTextCursor.MoveOperation.Right,
- QTextCursor.MoveMode.MoveAnchor,
- len(marker),
- )
- self.setTextCursor(cursor)
- self._pending_inline_marker = None
+ selected = cursor.selectedText()
+ if (
+ selected.startswith("*")
+ and selected.endswith("*")
+ and not selected.startswith("**")
+ ):
+ new_text = selected[1:-1]
+ else:
+ new_text = f"*{selected}*"
+ cursor.insertText(new_text)
else:
cursor.insertText("**")
cursor.movePosition(
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 1
)
self.setTextCursor(cursor)
- self._pending_inline_marker = "*"
# Return focus to editor
self.setFocus()
def apply_strikethrough(self):
- """Toggle strikethrough formatting (markdown ~~)."""
+ """Toggle strikethrough formatting."""
cursor = self.textCursor()
-
if cursor.hasSelection():
- m = self._selection_wrapped_by(("~~",))
- if m is not None:
- if self._unwrap_selection(m):
- self.setFocus()
- return
- self._wrap_selection("~~")
- self.setFocus()
- return
-
- # No selection:
- # - If we're between an empty pair (~~|~~), remove them.
- # - If we're inside strike and sitting right before the closing marker (~~text|~~),
- # jump the caret past the marker (end-strike) instead of inserting more.
- # - Otherwise, insert a new empty pair and place the caret between.
- if self._caret_between_markers("~~"):
- p = cursor.position()
- edit = QTextCursor(self.document())
- edit.beginEditBlock()
- edit.setPosition(p - 2)
- edit.setPosition(p + 2, QTextCursor.KeepAnchor)
- edit.insertText("")
- edit.endEditBlock()
- edit.setPosition(p - 2)
- self.setTextCursor(edit)
- elif self.is_markdown_strike_active() and self._caret_before_marker("~~"):
- cursor.movePosition(
- QTextCursor.MoveOperation.Right,
- QTextCursor.MoveMode.MoveAnchor,
- 2,
- )
- self.setTextCursor(cursor)
- self._pending_inline_marker = None
+ selected = cursor.selectedText()
+ if selected.startswith("~~") and selected.endswith("~~"):
+ new_text = selected[2:-2]
+ else:
+ new_text = f"~~{selected}~~"
+ cursor.insertText(new_text)
else:
cursor.insertText("~~~~")
cursor.movePosition(
diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py
index 8e8c4bf..92383e6 100644
--- a/bouquin/toolbar.py
+++ b/bouquin/toolbar.py
@@ -98,7 +98,6 @@ class ToolBar(QToolBar):
self.actNumbers.triggered.connect(self.numbersRequested)
self.actCheckboxes = QAction("☑", self)
self.actCheckboxes.setToolTip(strings._("toolbar_toggle_checkboxes"))
- self.actCheckboxes.setCheckable(True)
self.actCheckboxes.triggered.connect(self.checkboxesRequested)
# Images
@@ -127,14 +126,22 @@ class ToolBar(QToolBar):
self.actDocuments = QAction("📁", self)
self.actDocuments.setToolTip(strings._("toolbar_documents"))
self.actDocuments.triggered.connect(self.documentsRequested)
- # Headings are mutually exclusive (like radio buttons)
+
+ # Set exclusive buttons in QActionGroups
self.grpHeadings = QActionGroup(self)
self.grpHeadings.setExclusive(True)
- for a in (self.actH1, self.actH2, self.actH3, self.actNormal):
+ for a in (
+ self.actBold,
+ self.actItalic,
+ self.actStrike,
+ self.actH1,
+ self.actH2,
+ self.actH3,
+ self.actNormal,
+ ):
a.setCheckable(True)
a.setActionGroup(self.grpHeadings)
- # List types are mutually exclusive
self.grpLists = QActionGroup(self)
self.grpLists.setExclusive(True)
for a in (self.actBullets, self.actNumbers, self.actCheckboxes):
diff --git a/debian/changelog b/debian/changelog
index c49574f..0216393 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,24 +1,3 @@
-bouquin (0.8.3) unstable; urgency=medium
-
- * Update urllib3 dependency to resolve CVE-2026-21441
- * Fix carrying over data to next day from over-capturing data belonging to next header section
- * Other dependency updates
-
- -- Miguel Jacq Fri, 30 Jan 2026 16:48:00 +1100
-
-bouquin (0.8.2) unstable; urgency=medium
-
- * Add ability to delete an invoice via 'Manage Invoices' dialog
-
- -- Miguel Jacq Wed, 31 Dec 2025 16:00:00 +1100
-
-bouquin (0.8.1) unstable; urgency=medium
-
- * Fix bold/italic/strikethrough styling in certain conditions when toolbar action is used.
- * Move a code block or collapsed section (or generally, anything after a checkbox line until the next checkbox line) when moving an unchecked checkbox line to another day.
-
- -- Miguel Jacq Tue, 26 Dec 2025 18:00:00 +1100
-
bouquin (0.8.0) unstable; urgency=medium
* Add .desktop file for Debian
diff --git a/poetry.lock b/poetry.lock
index 0cf8448..d2932af 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -14,13 +14,13 @@ files = [
[[package]]
name = "certifi"
-version = "2026.1.4"
+version = "2025.11.12"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
files = [
- {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"},
- {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"},
+ {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"},
+ {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"},
]
[[package]]
@@ -158,103 +158,103 @@ files = [
[[package]]
name = "coverage"
-version = "7.13.2"
+version = "7.13.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.10"
files = [
- {file = "coverage-7.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4af3b01763909f477ea17c962e2cca8f39b350a4e46e3a30838b2c12e31b81b"},
- {file = "coverage-7.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36393bd2841fa0b59498f75466ee9bdec4f770d3254f031f23e8fd8e140ffdd2"},
- {file = "coverage-7.13.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cc7573518b7e2186bd229b1a0fe24a807273798832c27032c4510f47ffdb896"},
- {file = "coverage-7.13.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca9566769b69a5e216a4e176d54b9df88f29d750c5b78dbb899e379b4e14b30c"},
- {file = "coverage-7.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c9bdea644e94fd66d75a6f7e9a97bb822371e1fe7eadae2cacd50fcbc28e4dc"},
- {file = "coverage-7.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5bd447332ec4f45838c1ad42268ce21ca87c40deb86eabd59888859b66be22a5"},
- {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7c79ad5c28a16a1277e1187cf83ea8dafdcc689a784228a7d390f19776db7c31"},
- {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:76e06ccacd1fb6ada5d076ed98a8c6f66e2e6acd3df02819e2ee29fd637b76ad"},
- {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:49d49e9a5e9f4dc3d3dac95278a020afa6d6bdd41f63608a76fa05a719d5b66f"},
- {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed2bce0e7bfa53f7b0b01c722da289ef6ad4c18ebd52b1f93704c21f116360c8"},
- {file = "coverage-7.13.2-cp310-cp310-win32.whl", hash = "sha256:1574983178b35b9af4db4a9f7328a18a14a0a0ce76ffaa1c1bacb4cc82089a7c"},
- {file = "coverage-7.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:a360a8baeb038928ceb996f5623a4cd508728f8f13e08d4e96ce161702f3dd99"},
- {file = "coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e"},
- {file = "coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e"},
- {file = "coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508"},
- {file = "coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b"},
- {file = "coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b"},
- {file = "coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f"},
- {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3"},
- {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b"},
- {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1"},
- {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059"},
- {file = "coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031"},
- {file = "coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e"},
- {file = "coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28"},
- {file = "coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d"},
- {file = "coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3"},
- {file = "coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99"},
- {file = "coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f"},
- {file = "coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f"},
- {file = "coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa"},
- {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce"},
- {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94"},
- {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5"},
- {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b"},
- {file = "coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41"},
- {file = "coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e"},
- {file = "coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894"},
- {file = "coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6"},
- {file = "coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc"},
- {file = "coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f"},
- {file = "coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1"},
- {file = "coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9"},
- {file = "coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c"},
- {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5"},
- {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4"},
- {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c"},
- {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31"},
- {file = "coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8"},
- {file = "coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb"},
- {file = "coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557"},
- {file = "coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e"},
- {file = "coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7"},
- {file = "coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3"},
- {file = "coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3"},
- {file = "coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421"},
- {file = "coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5"},
- {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23"},
- {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c"},
- {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f"},
- {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573"},
- {file = "coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343"},
- {file = "coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47"},
- {file = "coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7"},
- {file = "coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef"},
- {file = "coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f"},
- {file = "coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5"},
- {file = "coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4"},
- {file = "coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27"},
- {file = "coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548"},
- {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660"},
- {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92"},
- {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82"},
- {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892"},
- {file = "coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe"},
- {file = "coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859"},
- {file = "coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6"},
- {file = "coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b"},
- {file = "coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417"},
- {file = "coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee"},
- {file = "coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1"},
- {file = "coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d"},
- {file = "coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6"},
- {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a"},
- {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04"},
- {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f"},
- {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f"},
- {file = "coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3"},
- {file = "coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba"},
- {file = "coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c"},
- {file = "coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5"},
- {file = "coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3"},
+ {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"},
+ {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"},
+ {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"},
+ {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"},
+ {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"},
+ {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"},
+ {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"},
+ {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"},
+ {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"},
+ {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"},
+ {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"},
+ {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"},
+ {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"},
+ {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"},
+ {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"},
+ {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"},
+ {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"},
+ {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"},
+ {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"},
+ {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"},
+ {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"},
+ {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"},
+ {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"},
+ {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"},
+ {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"},
+ {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"},
+ {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"},
+ {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"},
+ {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"},
+ {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"},
+ {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"},
+ {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"},
+ {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"},
+ {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"},
+ {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"},
+ {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"},
]
[package.dependencies]
@@ -321,28 +321,28 @@ files = [
[[package]]
name = "markdown"
-version = "3.10.1"
+version = "3.10"
description = "Python implementation of John Gruber's Markdown."
optional = false
python-versions = ">=3.10"
files = [
- {file = "markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3"},
- {file = "markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a"},
+ {file = "markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c"},
+ {file = "markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e"},
]
[package.extras]
-docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"]
+docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
testing = ["coverage", "pyyaml"]
[[package]]
name = "packaging"
-version = "26.0"
+version = "25.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
- {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
+ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
]
[[package]]
@@ -560,58 +560,53 @@ files = [
[[package]]
name = "tomli"
-version = "2.4.0"
+version = "2.3.0"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
files = [
- {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"},
- {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"},
- {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"},
- {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"},
- {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"},
- {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"},
- {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"},
- {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"},
- {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"},
- {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"},
- {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"},
- {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"},
- {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"},
- {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"},
- {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"},
- {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"},
- {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"},
- {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"},
- {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"},
- {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"},
- {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"},
- {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"},
- {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"},
- {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"},
- {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"},
- {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"},
- {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"},
- {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"},
- {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"},
- {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"},
- {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"},
- {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"},
- {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"},
- {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"},
- {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"},
- {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"},
- {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"},
- {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"},
- {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"},
- {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"},
- {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"},
- {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"},
- {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"},
- {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"},
- {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"},
- {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"},
- {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"},
+ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"},
+ {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"},
+ {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"},
+ {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"},
+ {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"},
+ {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"},
+ {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"},
+ {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"},
+ {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"},
+ {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"},
+ {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"},
+ {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"},
+ {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"},
+ {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"},
+ {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"},
+ {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"},
+ {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"},
+ {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"},
+ {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"},
+ {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"},
+ {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"},
+ {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"},
+ {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"},
+ {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"},
+ {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"},
+ {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"},
+ {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"},
+ {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"},
+ {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"},
+ {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"},
+ {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"},
+ {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"},
+ {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"},
+ {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"},
+ {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"},
+ {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"},
+ {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"},
+ {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"},
+ {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"},
+ {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"},
+ {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"},
+ {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"},
]
[[package]]
@@ -627,13 +622,13 @@ files = [
[[package]]
name = "urllib3"
-version = "2.6.3"
+version = "2.6.2"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
files = [
- {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
- {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
+ {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"},
+ {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"},
]
[package.extras]
@@ -645,4 +640,4 @@ zstd = ["backports-zstd (>=1.0.0)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.14"
-content-hash = "0ce61476b8c35f864d395ab3a4ee4645a1ad2e16aa800cba5e77e5bfee23d6a6"
+content-hash = "0241cd7378c45e79da728a23b89defa18f776ada9af1e60f2a19b0d90f3a2c19"
diff --git a/pyproject.toml b/pyproject.toml
index 0eef382..f4592bc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "bouquin"
-version = "0.8.3"
+version = "0.8.0"
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
authors = ["Miguel Jacq "]
readme = "README.md"
@@ -13,8 +13,8 @@ include = ["bouquin/locales/*.json", "bouquin/keys/mig5.asc", "bouquin/fonts/Not
python = ">=3.10,<3.14"
pyside6 = ">=6.8.1,<7.0.0"
bouquin-sqlcipher4 = "^4.12.0"
-requests = "^2.32.3"
-markdown = "^3.7"
+requests = "^2.32.5"
+markdown = "^3.10"
[tool.poetry.scripts]
bouquin = "bouquin.__main__:main"
diff --git a/release.sh b/release.sh
index 0d3945e..2fab9ac 100755
--- a/release.sh
+++ b/release.sh
@@ -69,50 +69,4 @@ for dist in ${DISTS[@]}; do
reprepro -b /home/user/git/repo includedeb "${release}" "${debfile}"
done
-
-# RPM
-sudo apt-get -y install createrepo-c rpm
-BUILD_OUTPUT="${HOME}/git/bouquin/dist"
-KEYID="00AE817C24A10C2540461A9C1D7CDE0234DB458D"
-REPO_ROOT="${HOME}/git/repo_rpm"
-REMOTE="letessier.mig5.net:/opt/repo_rpm"
-
-DISTS=(
- fedora:42
-)
-
-for dist in ${DISTS[@]}; do
- release=$(echo ${dist} | cut -d: -f2)
- REPO_RELEASE_ROOT="${REPO_ROOT}/${release}"
- RPM_REPO="${REPO_RELEASE_ROOT}/rpm/x86_64"
- mkdir -p "$RPM_REPO"
-
- docker build \
- --no-cache \
- -f Dockerfile.rpmbuild \
- -t bouquin-rpm:${release} \
- --progress=plain \
- --build-arg BASE_IMAGE=${dist} \
- .
-
- docker run --rm -v "$PWD":/src -v "$PWD/dist/rpm":/out -v "$HOME/git/bouquin-sqlcipher4/dist/rpm":/deps:ro bouquin-rpm:${release}
- sudo chown -R "${USER}" "$PWD/dist"
-
- for file in `ls -1 "${BUILD_OUTPUT}/rpm"`; do
- rpmsign --addsign "${BUILD_OUTPUT}/rpm/$file"
- done
-
- cp "${BUILD_OUTPUT}/rpm/"*.rpm "$RPM_REPO/"
-
- createrepo_c "$RPM_REPO"
-
- echo "==> Signing repomd.xml..."
- qubes-gpg-client --local-user "$KEYID" --detach-sign --armor "$RPM_REPO/repodata/repomd.xml" > "$RPM_REPO/repodata/repomd.xml.asc"
-done
-
-echo "==> Syncing repo to server..."
-rsync -aHPvz --exclude=.git --delete "$REPO_ROOT/" "$REMOTE/"
-
-echo "Done!"
-
-ssh lupin.mig5.net "echo ${VERSION} | tee /var/www/bouquin/version.txt"
+ssh wolverine.mig5.net "echo ${VERSION} | tee /opt/www/mig5.net/bouquin/version.txt"
diff --git a/rpm/bouquin.spec b/rpm/bouquin.spec
deleted file mode 100644
index d50d461..0000000
--- a/rpm/bouquin.spec
+++ /dev/null
@@ -1,95 +0,0 @@
-# bouquin Fedora 42 RPM spec using Fedora's pyproject RPM macros (Poetry backend).
-#
-# NOTE: Bouquin depends on "bouquin-sqlcipher4" project, but the RPM actually
-# provides the Python distribution/module as "sqlcipher4". To keep Fedora's
-# auto-generated python3dist() Requires correct, we rewrite the dependency key in
-# pyproject.toml at build time.
-%global upstream_version 0.8.3
-
-Name: bouquin
-Version: %{upstream_version}
-Release: 1%{?dist}.bouquin1
-Summary: A simple, opinionated notebook application (Python/Qt/SQLCipher)
-
-License: GPL-3.0-or-later
-URL: https://git.mig5.net/mig5/bouquin
-Source0: %{name}-%{version}.tar.gz
-
-BuildArch: noarch
-
-BuildRequires: pyproject-rpm-macros
-BuildRequires: python3-devel
-BuildRequires: python3-poetry-core
-BuildRequires: desktop-file-utils
-
-# Non-Python runtime dep (Fedora equivalent of Debian's libxcb-cursor0)
-Requires: xcb-util-cursor
-
-# Make sure private repo dependency is pulled in by package name as well.
-Requires: python3-sqlcipher4 >= 4.12.0
-
-%description
-Bouquin is a simple, opinionated notebook application written in Python and Qt,
-storing data using SQLCipher.
-
-%prep
-%autosetup -n bouquin
-
-# Patch dependency name so Fedora's python dependency generator targets the
-# provider from bouquin-sqlcipher4 RPM (python3dist(sqlcipher4)).
-%{python3} - <<'PY'
-from pathlib import Path
-import re
-
-p = Path("pyproject.toml")
-txt = p.read_text(encoding="utf-8")
-
-pattern = re.compile(r'(?ms)(^\[tool\.poetry\.dependencies\]\n.*?)(^\[|\Z)')
-m = pattern.search(txt)
-if not m:
- raise SystemExit("Could not locate [tool.poetry.dependencies] in pyproject.toml")
-
-deps_block = m.group(1)
-deps_block2 = re.sub(r'(?m)^bouquin-sqlcipher4\s*=\s*(".*?")\s*$', r'sqlcipher4 = \1', deps_block)
-if deps_block == deps_block2:
- raise SystemExit("Did not find bouquin-sqlcipher4 dependency to rewrite")
-
-p.write_text(txt[:m.start(1)] + deps_block2 + txt[m.end(1):], encoding="utf-8")
-PY
-
-desktop-file-validate debian/bouquin.desktop
-
-%generate_buildrequires
-%pyproject_buildrequires
-
-%build
-%pyproject_wheel
-
-%install
-%pyproject_install
-%pyproject_save_files bouquin
-
-# Desktop integration (mirrors debian/bouquin.install)
-install -Dpm 0644 debian/bouquin.desktop %{buildroot}%{_datadir}/applications/bouquin.desktop
-install -Dpm 0644 bouquin/icons/bouquin.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/bouquin.svg
-
-%files -f %{pyproject_files}
-%license LICENSE
-%doc README.md CHANGELOG.md
-%{_bindir}/bouquin
-
-%{_datadir}/applications/bouquin.desktop
-%{_datadir}/icons/hicolor/scalable/apps/bouquin.svg
-
-%changelog
-* Fri Jan 30 2026 Miguel Jacq - %{version}-%{release}
-- Update urllib3 dependency to resolve CVE-2026-21441
-- Fix carrying over data to next day from over-capturing data belonging to next header section
-- Other dependency updates
-* Wed Dec 31 2025 Miguel Jacq - %{version}-%{release}
-- Add ability to delete an invoice via 'Manage Invoices' dialog
-* Fri Dec 26 2025 Miguel Jacq - %{version}-%{release}
-- Fix bold/italic/strikethrough styling in certain conditions when toolbar action is used.
-- Move a code block or collapsed section (or generally, anything after a checkbox line until the next checkbox line) when moving an unchecked checkbox line to another day.
-* Wed Dec 24 2025 Miguel Jacq - %{version}-%{release}
-- Initial RPM packaging for Fedora 42
diff --git a/tests/test_markdown_editor.py b/tests/test_markdown_editor.py
index 9dac5d6..a36a09e 100644
--- a/tests/test_markdown_editor.py
+++ b/tests/test_markdown_editor.py
@@ -1,7 +1,6 @@
import base64
import pytest
-import re
from bouquin.markdown_editor import MarkdownEditor
from bouquin.markdown_highlighter import MarkdownHighlighter
from bouquin.theme import Theme, ThemeConfig, ThemeManager
@@ -73,13 +72,8 @@ def test_apply_styles_and_headings(editor, qtbot):
editor.apply_italic()
editor.apply_strikethrough()
editor.apply_heading(24)
- md = editor.to_markdown().strip()
-
- assert md.startswith("# ")
- assert "~~hello world~~" in md
- assert re.search(
- r"\*{2,3}~~hello world~~\*{2,3}", md
- ) # bold or bold+italic wrapping strike
+ md = editor.to_markdown()
+ assert "**" in md and "*~~~~*" in md
def test_toggle_lists_and_checkboxes(editor):