Compare commits

..
4 Commits
Author SHA1 Message Date
david-spacil dfb3a9ef9b chore: odstranit resources.py a konfiguraci flake8 z balíčku (v2.1.4) (#71)
* chore: odstranit resources.py a konfiguraci flake8 z balíčku

Scanner na plugins.qgis.org označil plugin jako „Validated
(configured)“ kvůli amcr_viewer/.flake8. Ten potlačoval jen stylové
nálezy (E302, E305, E501) ve vygenerovaném resources.py, který se
nikde neimportuje – ikony se načítají přímo z PNG. resources.py
repozitář pluginů nevyžaduje (validator.py v QGIS-Django, PyQGIS
cookbook: „optional“).

- Smazán amcr_viewer/resources.py a amcr_viewer/.flake8.
- CI: flake8 běží s --isolated (výchozí pravidla jako scanner);
  kontrola ZIPu místo .flake8 hlídá povinný LICENSE.
- check_sources.py: žádné skryté soubory v balíčku, ani config
  soubory scanneru.
- pyproject.toml, README.md, AGENTS.md: odstraněny odkazy na oba
  soubory, popsán postup bez konfigurace.

Ověřeno: check_sources, flake8 7.3.0, ruff 0.16.5, bandit 1.9.4,
detect-secrets bez nálezů; pyqgis4-checker čistý; smoke test v QGIS
3.44.15 (Qt 5) i 4.2.3 (Qt 6); ZIP bez skrytých souborů.

Připraveno s pomocí AI (Claude).

* Verze 2.1.4

Povýšena verze v metadata.txt (+ changelog) a CITATION.cff po odstranění resources.py a konfigurace flake8.

Připraveno s pomocí AI (Claude).
2026-10-01 18:45:48 +02:00
david-spacil 48e0e2a3b5 docs: bump verze i v CITATION.cff + kontrola v CI (#69)
Verze a datum releasu v CITATION.cff se povyšují ručně spolu
s metadata.txt a snadno se zapomenou (u v2.1.3 dorovnáno dodatečně
v 5841fc1).

- AGENTS.md: pravidlo povýšit CITATION.cff (version, date-released)
  spolu s metadata.txt; doplněn popis jobu Balíček pluginu.
- Šablona PR: bod kontrolního seznamu zahrnuje CITATION.cff.
- CI (Balíček pluginu): krok ověří, že verze v CITATION.cff
  odpovídá metadata.txt.

Připraveno s pomocí AI (Claude).
2026-10-01 15:57:53 +02:00
david-spacil 5841fc15de Update CITATION.cff 2026-10-01 15:34:52 +02:00
david-spacil f8d938e353 fix: hesláře osob se po aktualizaci tiše vyprázdní (#68)
Digiarchiv v4.1.0 (Solr 10, json.nl=arrarr) vrací položky facet jako
dvojice ["hodnota", počet] místo objektů {"name": ...}. fetch_set četl
r["name"], spadl na TypeError a hesláře vedoucích a nálezců se uložily
prázdné.

- _facet_name() přijímá oba formáty facet (starý i nový).
- Selhání setu vrací prázdný seznam i při přerušeném stránkování, ať
  se neuloží jen část hesláře.
- download_heslare() ponechá u selhaného nebo prázdného setu předchozí
  hodnoty z heslar.csv a vrátí seznam selhaných setů.
- Dialog při částečném selhání zobrazí varování místo „Hotovo“.
- Verze 2.1.3 + changelog.

Ověřeno proti produkčnímu API: vedoucí 2497, nálezci 426, ostatní
hesláře beze změny; simulované selhání ponechá předchozí hodnoty.

Refs #67, #66
Připraveno s pomocí AI (Claude).
2026-10-01 15:33:41 +02:00
15 changed files with 140 additions and 420 deletions

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@ Nepotřebné body můžeš smazat. Komentáře (<!-- ... -->) se v PR nezobrazuj
## Kontrolní seznam ## Kontrolní seznam
- [ ] Změny jsou v souladu se stylem projektu (viz `AGENTS.md`) - [ ] Změny jsou v souladu se stylem projektu (viz `AGENTS.md`)
- [ ] Při změně funkcí povýšena verze v `amcr_viewer/metadata.txt` a doplněn `changelog` - [ ] Při změně funkcí povýšena verze v `amcr_viewer/metadata.txt` (+ `changelog`) a v `CITATION.cff` (`version`, `date-released`)
- [ ] Otestováno v QGIS (min. podporovaná verze 3.44) - [ ] Otestováno v QGIS (min. podporovaná verze 3.44)
- [ ] PR míří do správné cílové větve - [ ] PR míří do správné cílové větve
- [ ] Větev odpovídá konvenci (`feat/ fix/ docs/ chore/<téma>`, AI `agents/<jméno>/<téma>`) - [ ] Větev odpovídá konvenci (`feat/ fix/ docs/ chore/<téma>`, AI `agents/<jméno>/<téma>`)
+20 -7
View File
@@ -74,10 +74,10 @@ jobs:
print('detect-secrets: bez nálezů') print('detect-secrets: bez nálezů')
" "
# Na plugins.qgis.org je informativní, tady blokuje – konfigurace # Na plugins.qgis.org je informativní, tady blokuje. Bez konfigurace,
# v amcr_viewer/.flake8 je stejná pro obě místa. # tj. se stejnými výchozími pravidly jako scanner.
- name: Flake8 - name: Flake8
run: flake8 --config amcr_viewer/.flake8 amcr_viewer/ run: flake8 --isolated amcr_viewer/
# Nad rámec plugins.qgis.org; konfigurace v pyproject.toml # Nad rámec plugins.qgis.org; konfigurace v pyproject.toml
- name: Ruff - name: Ruff
@@ -147,14 +147,27 @@ jobs:
- name: Zip plugin - name: Zip plugin
run: zip -r amcr_viewer.zip amcr_viewer -x "*.git*" run: zip -r amcr_viewer.zip amcr_viewer -x "*.git*"
# Kontrola obsahu ZIPu. Config soubory pro scanner musí být uvnitř # Verze v CITATION.cff se povyšuje ručně spolu s metadata.txt a snadno
# vedle metadata.txt, jinak je plugins.qgis.org nenajde – a některé # se zapomene – tag by pak nesl v citaci jinou verzi než plugin
# nástroje skryté soubory tiše vynechávají. - name: Verify CITATION.cff version
run: |
plugin=$(sed -n 's/^version=//p' amcr_viewer/metadata.txt \
| tr -d "\r\"' ")
citace=$(sed -n 's/^version://p' CITATION.cff \
| tr -d "\r\"' ")
echo "metadata.txt: '$plugin', CITATION.cff: '$citace'"
if [ -z "$plugin" ] || [ "$plugin" != "$citace" ]; then
echo "::error file=CITATION.cff::verze '$citace' neodpovídá" \
"metadata.txt ('$plugin')"
exit 1
fi
# Kontrola obsahu ZIPu: povinné soubory jsou uvnitř, git soubory ne.
- name: Verify archive contents - name: Verify archive contents
run: | run: |
unzip -l amcr_viewer.zip unzip -l amcr_viewer.zip
for soubor in amcr_viewer/metadata.txt amcr_viewer/__init__.py \ for soubor in amcr_viewer/metadata.txt amcr_viewer/__init__.py \
amcr_viewer/.flake8; do amcr_viewer/LICENSE; do
unzip -l amcr_viewer.zip | grep -qF " $soubor" \ unzip -l amcr_viewer.zip | grep -qF " $soubor" \
|| { echo "::error::v ZIPu chybí $soubor"; exit 1; } || { echo "::error::v ZIPu chybí $soubor"; exit 1; }
done done
-41
View File
@@ -1,41 +0,0 @@
name: Release PR Checks
# Hlídá bump verze a changelog v okamžiku, kdy se release větev
# version/vX.Y.Z slévá do main – tady poprvé jde skutečně o to, co se
# vydá, a zapomenutý bump by se jinak dostal rovnou k tagu a releasu.
#
# Kontroly viz tests/check_version_bump.py: verze v metadata.txt musí jít
# nahoru, první položka changelogu musí patřit nové verzi, CITATION.cff
# musí mít stejnou verzi (a posunuté date-released) a název větve musí
# odpovídat verzi, kterou nese.
on:
pull_request:
branches:
- main
permissions:
contents: read
jobs:
version-bump:
name: Kontrola verze a changelogu
runs-on: ubuntu-latest
if: startsWith(github.head_ref, 'version/')
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Verze a changelog
run: |
python3 tests/check_version_bump.py \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.head_ref }}"
+11 -7
View File
@@ -140,6 +140,9 @@ flatpak run --command=sh org.qgis.qgis -c \
- Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`). - Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`).
- **Při každé změně chování / nové funkci** povyš verzi a doplň položku do - **Při každé změně chování / nové funkci** povyš verzi a doplň položku do
`changelog=` v `metadata.txt` (formát `vX.Y.Z (RRRR-MM-DD)` + odrážky). `changelog=` v `metadata.txt` (formát `vX.Y.Z (RRRR-MM-DD)` + odrážky).
- Současně povyš i **`CITATION.cff`** v kořeni repozitáře: `version:` na
stejnou verzi jako v `metadata.txt` a `date-released:` na datum releasu.
Oba soubory musí mít stejnou verzi, než se založí tag.
- Datum v changelogu ber z **deterministického zdroje**, ne z paměti, např. - Datum v changelogu ber z **deterministického zdroje**, ne z paměti, např.
`python -c "import datetime; print(datetime.date.today().isoformat())"`. `python -c "import datetime; print(datetime.date.today().isoformat())"`.
- Release se spouští **pushnutím tagu `vX.Y.Z`**, ne publikací releasu - Release se spouští **pushnutím tagu `vX.Y.Z`**, ne publikací releasu
@@ -189,7 +192,7 @@ tohle:
| **Lint a bezpečnost** | `check_sources.py`, bandit, detect-secrets, flake8, ruff | | **Lint a bezpečnost** | `check_sources.py`, bandit, detect-secrets, flake8, ruff |
| **Kompatibilita s Qt6** | `pyqgis4-checker` v dockeru | | **Kompatibilita s Qt6** | `pyqgis4-checker` v dockeru |
| **Smoke test** | `smoke_test.py` v `qgis/qgis:ltr` i `qgis/qgis:stable` | | **Smoke test** | `smoke_test.py` v `qgis/qgis:ltr` i `qgis/qgis:stable` |
| **Balíček pluginu** | sestaví ZIP, ověří obsah, přiloží jako artefakt | | **Balíček pluginu** | ověří shodu verze v `CITATION.cff` a `metadata.txt`, sestaví ZIP, ověří obsah, přiloží jako artefakt |
Smoke test běží v obou podporovaných řadách: `ltr` je QGIS 3.44 na Qt5, Smoke test běží v obou podporovaných řadách: `ltr` je QGIS 3.44 na Qt5,
`stable` je QGIS 4.x na Qt6. `stable` je QGIS 4.x na Qt6.
@@ -205,7 +208,7 @@ pip install bandit detect-secrets flake8 ruff
python3 tests/check_sources.py python3 tests/check_sources.py
bandit -r amcr_viewer/ bandit -r amcr_viewer/
detect-secrets scan --all-files amcr_viewer/ detect-secrets scan --all-files amcr_viewer/
flake8 --config amcr_viewer/.flake8 amcr_viewer/ flake8 --isolated amcr_viewer/
ruff check . ruff check .
# smoke test v obou verzích QGIS (docker, bez instalace čehokoli) # smoke test v obou verzích QGIS (docker, bez instalace čehokoli)
@@ -222,11 +225,12 @@ Na co si dát pozor:
v logu. Workflow proto kontroluje, že log obsahuje jen hlavičku. v logu. Workflow proto kontroluje, že log obsahuje jen hlavičku.
- **`detect-secrets` bez `--all-files` prohledá jen soubory sledované - **`detect-secrets` bez `--all-files` prohledá jen soubory sledované
gitem** a o nesledovaném souboru mlčí. Vypadá to jako čistý výsledek. gitem** a o nesledovaném souboru mlčí. Vypadá to jako čistý výsledek.
- **Konfigurace lintů je rozdělená schválně.** `amcr_viewer/.flake8` leží - **Flake8 běží bez konfigurace** (`--isolated`), tedy se stejnými
vedle `metadata.txt`, protože scanner na plugins.qgis.org hledá config výchozími pravidly jako scanner na plugins.qgis.org. Do balíčku nepatří
soubory jen v kořeni balíčku uvnitř ZIPu; díky tomu platí stejná pravidla `.flake8`, `.bandit` ani `.secrets.baseline`: scanner by plugin označil
v CI, lokálně i při uploadu. Konfigurace ruffu je naopak v kořenovém jako „Validated (configured)“ a nález je lepší opravit v kódu.
`pyproject.toml` – ruff se do balíčku pluginu nedistribuuje. Konfigurace ruffu je v kořenovém `pyproject.toml` – ruff se do balíčku
pluginu nedistribuuje.
Viz https://plugins.qgis.org/docs/security-scanning/config-files Viz https://plugins.qgis.org/docs/security-scanning/config-files
- **Verze nástrojů jsou v workflow napevno.** Výchozí sada pravidel ruffu se - **Verze nástrojů jsou v workflow napevno.** Výchozí sada pravidel ruffu se
mezi verzemi mění, takže bez pinu by CI začalo padat samo od sebe. mezi verzemi mění, takže bez pinu by CI začalo padat samo od sebe.
+2 -2
View File
@@ -25,5 +25,5 @@ abstract: >-
the Digital archive of the Archaeological Map of the the Digital archive of the Archaeological Map of the
Czech Republic (https://digiarchiv.aiscr.cz/). Czech Republic (https://digiarchiv.aiscr.cz/).
license: GPL-3.0 license: GPL-3.0
version: '2.1.2' version: '2.1.4'
date-released: '2026-09-01' date-released: '2026-10-01'
+3 -5
View File
@@ -138,11 +138,11 @@ restriction". Click *Vybrat…* to open a searchable, checkable list.
| Lokalita – typ | — | ✓ | — | `f_typ_lokality` | | Lokalita – typ | — | ✓ | — | `f_typ_lokality` |
| Lokalita – druh | — | ✓ | — | `f_druh_lokality` | | Lokalita – druh | — | ✓ | — | `f_druh_lokality` |
| Lokalita – jistota určení | — | ✓ | — | `f_jistota` | | Lokalita – jistota určení | — | ✓ | — | `f_jistota` |
| Lokalita – stav dochování | — | ✓ | — | `f_lokalita_zachovalost` | | Lokalita - stav dochování | — | ✓ | — | `f_lokalita_zachovalost` |
| Období | ✓ | ✓ | ✓ | `f_obdobi` | | Období | ✓ | ✓ | ✓ | `f_obdobi` |
| Kategorie nálezu | — | — | ✓ | `f_kategorie` | | Kategorie nálezu | — | — | ✓ | `f_kategorie` |
| Druh nálezu | — | — | ✓ | `f_druh_nalezu` | | Druh nálezu | — | — | ✓ | `f_druh_nalezu` |
| Materiál | — | — | ✓ | `f_specifikace` | | Specifikace nálezu | — | — | ✓ | `f_specifikace` |
| Okolnosti nálezu | — | — | ✓ | `f_nalezove_okolnosti` | | Okolnosti nálezu | — | — | ✓ | `f_nalezove_okolnosti` |
| Nálezce | — | — | ✓ | `f_nalezce` | | Nálezce | — | — | ✓ | `f_nalezce` |
| Datum nálezu | — | — | ✓ | `samostatny_nalez_datum_nalezu` | | Datum nálezu | — | — | ✓ | `samostatny_nalez_datum_nalezu` |
@@ -333,9 +333,7 @@ amcr_viewer/ the plugin package (this is what gets zipped)
codelists/heslar.csv cached controlled vocabularies codelists/heslar.csv cached controlled vocabularies
i18n/ Qt translation files i18n/ Qt translation files
*.png toolbar and menu icons *.png toolbar and menu icons
resources.py generated by pyrcc, currently unused
metadata.txt plugin metadata and changelog metadata.txt plugin metadata and changelog
.flake8 lint config, read by the plugins.qgis.org scanner
tests/ tests/
check_sources.py source hygiene checks (no QGIS needed) check_sources.py source hygiene checks (no QGIS needed)
smoke_test.py loads the plugin in a real, headless QGIS smoke_test.py loads the plugin in a real, headless QGIS
@@ -404,7 +402,7 @@ Reproducing them locally:
```bash ```bash
python3 tests/check_sources.py python3 tests/check_sources.py
ruff check . ruff check .
flake8 --config amcr_viewer/.flake8 amcr_viewer/ flake8 --isolated amcr_viewer/
bandit -r amcr_viewer/ bandit -r amcr_viewer/
docker run --rm -v "$PWD:/work:ro" -w /work --user "$(id -u):$(id -g)" \ docker run --rm -v "$PWD:/work:ro" -w /work --user "$(id -u):$(id -g)" \
-e HOME=/tmp qgis/qgis:stable python3 tests/smoke_test.py -e HOME=/tmp qgis/qgis:stable python3 tests/smoke_test.py
-12
View File
@@ -1,12 +0,0 @@
# Konfigurace flake8 pro plugin AMČR Viewer.
#
# Soubor leží vedle metadata.txt schválně: scanner na plugins.qgis.org
# hledá .flake8 pouze v kořeni balíčku uvnitř ZIPu, takže stejná pravidla
# platí v CI, lokálně i při uploadu.
# https://plugins.qgis.org/docs/security-scanning/config-files
[flake8]
# resources.py je vygenerovaný výstup pyrcc ("All changes made in this
# file will be lost"), není nikde importovaný a zdrojový .qrc v repu není.
# Ručně se neformátuje.
per-file-ignores =
*resources.py: E302,E305,E501
+66 -5
View File
@@ -104,6 +104,21 @@ def load_all_data():
return categorized_data return categorized_data
def _facet_name(item):
"""
Returns the value of one facet item from the Digiarchive API.
Digiarchive v4.1.0 (Solr 10, json.nl=arrarr) returns facet items as
["value", count] pairs; older versions returned {"name": "value", ...}
objects. Both shapes are accepted so the plugin works against either.
"""
if isinstance(item, dict):
return item.get("name")
if isinstance(item, (list, tuple)) and item:
return item[0]
return None
def fetch_set(base_url, internal_name, api_set, task=None): def fetch_set(base_url, internal_name, api_set, task=None):
dataset = [] dataset = []
params_amcr = { params_amcr = {
@@ -206,7 +221,9 @@ def fetch_set(base_url, internal_name, api_set, task=None):
for r in records: for r in records:
nazev = r["name"] nazev = _facet_name(r)
if not nazev:
continue
dataset.append({ dataset.append({
'Název': nazev, 'Název': nazev,
@@ -217,17 +234,48 @@ def fetch_set(base_url, internal_name, api_set, task=None):
break break
except Exception as e: except Exception as e:
# A partial set (e.g. pagination interrupted halfway) would
# silently drop codes – report the whole set as failed instead
# and let the caller keep the previous values
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
f"Chyba u setu {api_set}: {e}", f"Chyba u setu {api_set}: {e}",
"AMČR", Qgis.MessageLevel.Warning) "AMČR", Qgis.MessageLevel.Warning)
break return []
return dataset return dataset
def download_heslare(task=None): def _read_existing_rows():
"""Fetches the codelists from the AMČR API and saves it to a CSV file.""" """
Returns the rows of the current heslar.csv grouped by category, so a set
that fails to download can keep its previous values.
"""
rows = {}
if not os.path.exists(OUTPUT_FILE):
return rows
try:
with open(OUTPUT_FILE, encoding='utf-8-sig', newline='') as f:
for row in csv.DictReader(f, delimiter=';'):
cat = (row.get('Kategorie') or '').strip()
if cat:
rows.setdefault(cat, []).append(row)
except Exception as e:
QgsMessageLog.logMessage(
f"Nelze načíst stávající hesláře: {e}",
"AMČR", Qgis.MessageLevel.Warning)
return rows
def download_heslare(task=None, failed=None):
"""
Fetches the codelists from the AMČR API and saves it to a CSV file.
A set that fails or comes back empty keeps its rows from the current
heslar.csv instead of being wiped; its name is appended to ``failed``
(if given) so the caller can warn the user.
"""
ensure_codelists_dir() ensure_codelists_dir()
existing = _read_existing_rows()
all_data = [] all_data = []
total_sets = len(slovnicek) total_sets = len(slovnicek)
# index, (interni, api_nazev) # index, (interni, api_nazev)
@@ -251,6 +299,18 @@ def download_heslare(task=None):
if data is None: if data is None:
return False # Cancelled mid-download return False # Cancelled mid-download
if not data:
# Never replace a working codelist with nothing – an API change
# would otherwise silently empty the filter in the dialog
old = existing.get(interni, [])
QgsMessageLog.logMessage(
f"Heslář '{interni}' se nepodařilo stáhnout, "
f"ponechávám předchozí hodnoty ({len(old)} položek).",
"AMČR", Qgis.MessageLevel.Warning)
if failed is not None:
failed.append(interni)
data = old
all_data.extend(data) all_data.extend(data)
# Report progress (0-100) # Report progress (0-100)
@@ -261,7 +321,8 @@ def download_heslare(task=None):
# Save to CSV # Save to CSV
with open(OUTPUT_FILE, 'w', newline='', encoding='utf-8-sig') as f: with open(OUTPUT_FILE, 'w', newline='', encoding='utf-8-sig') as f:
fieldnames = ['Název', 'Kód', 'Kategorie'] fieldnames = ['Název', 'Kód', 'Kategorie']
writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';') writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';',
extrasaction='ignore')
writer.writeheader() writer.writeheader()
writer.writerows(all_data) writer.writerows(all_data)
+23 -3
View File
@@ -75,12 +75,15 @@ class UpdateCodelistsTask(QgsTask):
super().__init__(description, QgsTask.Flag.CanCancel) super().__init__(description, QgsTask.Flag.CanCancel)
self.success = False self.success = False
self.exception = None self.exception = None
# Codelists that failed to download and kept their previous values
self.failed_sets = []
def run(self): def run(self):
"""Runs in a background thread.""" """Runs in a background thread."""
try: try:
# Call the download function with the task reference # Call the download function with the task reference
self.success = download_heslare(task=self) self.success = download_heslare(
task=self, failed=self.failed_sets)
return self.success return self.success
except Exception as e: except Exception as e:
self.exception = e self.exception = e
@@ -91,6 +94,13 @@ class UpdateCodelistsTask(QgsTask):
if result: if result:
# Safely update the global variables in the main thread # Safely update the global variables in the main thread
refresh_globals() refresh_globals()
if self.failed_sets:
QgsMessageLog.logMessage(
"Hesláře AMČR aktualizovány částečně, beze změny "
f"zůstaly: {', '.join(self.failed_sets)}",
"AMČR", Qgis.MessageLevel.Warning
)
else:
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
"Hesláře AMČR byly úspěšně aktualizovány.", "Hesláře AMČR byly úspěšně aktualizovány.",
"AMČR", Qgis.MessageLevel.Info "AMČR", Qgis.MessageLevel.Info
@@ -333,7 +343,7 @@ class AmcrFilterDialog(QDialog):
layout.addWidget(self.picker_jistota) layout.addWidget(self.picker_jistota)
self.picker_lokalita_zachovalost = self.setup_picker( self.picker_lokalita_zachovalost = self.setup_picker(
"Lokalita – stav dochování", "Lokalita - stav dochování",
'lokalita_zachovalost', 'lokalita_zachovalost',
LOKALITA_ZACHOVALOST LOKALITA_ZACHOVALOST
) )
@@ -360,7 +370,7 @@ class AmcrFilterDialog(QDialog):
layout.addWidget(self.picker_druh_nalezu) layout.addWidget(self.picker_druh_nalezu)
self.picker_specifikace = self.setup_picker( self.picker_specifikace = self.setup_picker(
"Materiál", "Specifikace nálezu",
'specifikace', 'specifikace',
SPECIFIKACE SPECIFIKACE
) )
@@ -629,6 +639,16 @@ class AmcrFilterDialog(QDialog):
def on_completed(): def on_completed():
_cleanup() _cleanup()
if task.failed_sets:
QMessageBox.warning(
parent_win,
"Hesláře aktualizovány částečně",
"Některé hesláře se nepodařilo stáhnout, "
"ponechány byly jejich předchozí hodnoty:\n"
+ "\n".join(f"• {name}" for name in task.failed_sets)
+ "\n\nPodrobnosti jsou v panelu Zprávy, záložka AMČR."
)
return
QMessageBox.information( QMessageBox.information(
parent_win, parent_win,
"Hotovo", "Hotovo",
+1 -7
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import json import json
import requests import requests
@@ -680,8 +680,6 @@ def load_amcr_data(canvas, bb, filters=None,
# One feature per component – # One feature per component –
# all data on a single row, no relations needed # all data on a single row, no relations needed
if komps: if komps:
komps_count = len(komps)
for komp in komps: for komp in komps:
if not komp_projde_filtrem( if not komp_projde_filtrem(
komp, filter_areal, komp, filter_areal,
@@ -703,7 +701,6 @@ def load_amcr_data(canvas, bb, filters=None,
komp.get('komponenta_obdobi') komp.get('komponenta_obdobi')
or {} or {}
).get('value', ""), ).get('value', ""),
'vaha': 1/komps_count,
} }
pian_lookup[dj_pian_value].append( pian_lookup[dj_pian_value].append(
komp_meta) komp_meta)
@@ -1059,7 +1056,6 @@ def load_amcr_data(canvas, bb, filters=None,
"poznamka": "Poznámka/bližší popis", "poznamka": "Poznámka/bližší popis",
"pred_org": "Předáno organizaci", "pred_org": "Předáno organizaci",
"evidencni": "Evidenční číslo", "evidencni": "Evidenční číslo",
"prvek_vaha": "Váha prvku",
} }
if komponenty == "true": if komponenty == "true":
@@ -1067,7 +1063,6 @@ def load_amcr_data(canvas, bb, filters=None,
QgsField("komponenta", QMetaType.Type.QString), QgsField("komponenta", QMetaType.Type.QString),
QgsField("komponenta_areal", QMetaType.Type.QString), QgsField("komponenta_areal", QMetaType.Type.QString),
QgsField("komponenta_obdobi", QMetaType.Type.QString), QgsField("komponenta_obdobi", QMetaType.Type.QString),
QgsField("prvek_vaha", QMetaType.Type.Double),
] ]
for vl in layers: for vl in layers:
@@ -1214,7 +1209,6 @@ def load_amcr_data(canvas, bb, filters=None,
meta.get('komponenta_id', ""), meta.get('komponenta_id', ""),
meta.get('komponenta_areal', ""), meta.get('komponenta_areal', ""),
meta.get('komponenta_obdobi', ""), meta.get('komponenta_obdobi', ""),
meta.get('vaha', 1),
]) ])
feat.setAttributes(atributy) feat.setAttributes(atributy)
+7 -6
View File
@@ -8,7 +8,7 @@ name=AMČR Viewer
qgisMinimumVersion=3.44.0 qgisMinimumVersion=3.44.0
qgisMaximumVersion=4.99.0 qgisMaximumVersion=4.99.0
description=Viewing and downloading the AMČR data. description=Viewing and downloading the AMČR data.
version=2.2.0 version=2.1.4
author=David Spáčil author=David Spáčil
email=spacil@arub.cz email=spacil@arub.cz
@@ -23,11 +23,12 @@ repository=https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer
hasProcessingProvider=no hasProcessingProvider=no
# Uncomment the following line and add your changelog: # Uncomment the following line and add your changelog:
changelog= changelog=
Plný seznam změn v češtině je dostupný zde: https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases/tag/v2.2.0 Plný seznam změn v češtině je dostupný zde: https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases/tag/v2.1.1
v2.2.0 (2026-09-02) v2.1.4 (2026-10-01)
* Filter labels unified: "Specifikace nálezu" renamed to "Materiál" to match the attribute alias * Removed unused generated resources.py and the bundled flake8 config, so the plugin passes the plugins.qgis.org scan without custom configuration
* README rewritten to match the current state of the code v2.1.3 (2026-10-01)
* Tables for Akce and Lokality contain a field with feature weight, when Komponenty rendering is enabled * Fixed empty person codelists (excavation leaders, finders) after updating codelists against Digiarchive v4.1.0
* A codelist that fails to download keeps its previous values and the user is warned
v2.1.2 (2026-09-01) v2.1.2 (2026-09-01)
* Qt6 compatibility * Qt6 compatibility
* Code clean-up * Code clean-up
-128
View File
@@ -1,128 +0,0 @@
# -*- coding: utf-8 -*-
# Resource object code
#
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.13)
#
# WARNING! All changes made in this file will be lost!
from qgis.PyQt import QtCore
qt_resource_data = b"\
\x00\x00\x04\x0a\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\
\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\
\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x02\x15\
\x16\x11\x2c\x9d\x48\x83\xbb\x00\x00\x03\x8a\x49\x44\x41\x54\x48\
\xc7\xad\x95\x4b\x68\x5c\x55\x18\xc7\x7f\xe7\xdc\x7b\x67\xe6\xce\
\x4c\x66\x26\x49\xd3\x24\x26\xa6\xc6\xf8\x40\x21\xa5\x04\xb3\x28\
\xda\x98\x20\xa5\x0b\xad\x55\xa8\x2b\xc5\x50\x1f\xa0\x6e\x34\x2b\
\x45\x30\x14\x02\xba\x52\x69\x15\x17\x66\x63\x45\x97\x95\xa0\xad\
\x0b\xfb\xc0\x06\x25\xb6\x71\x61\x12\x41\x50\xdb\x2a\x21\xd1\xe2\
\x24\xf3\x9e\xc9\xcc\xbd\xe7\x1c\x17\x35\x43\x1e\x33\x21\xb6\xfd\
\x56\x87\xf3\x9d\xfb\xfb\x1e\xf7\xff\x9d\x23\x8c\x31\x43\x95\xf4\
\x85\x1e\x3f\x3b\x35\xac\xfd\xcc\x43\xdc\xa4\x49\x3b\xfe\x9d\x1d\
\xdb\x7b\x22\x90\x78\xf8\xb2\x28\xa7\xbe\x7d\xc1\x4b\x9d\x79\xdf\
\x18\x15\xe5\x16\x99\x10\x56\xde\x69\xdc\x3f\x22\xfd\xec\xd4\xf0\
\xad\x04\x03\x18\xa3\xa2\x7e\x76\x6a\x58\xde\x68\x2b\xb4\x36\xf8\
\xbe\xc6\x18\x53\xdb\xef\xe7\xfa\xec\xed\x67\x63\x10\x42\x00\xf0\
\xfb\xd5\x65\x2a\x15\x45\xc7\x6d\x0d\x00\xc4\xa2\xc1\xaa\x6f\x0d\
\x3e\x6c\xab\xc2\x1c\x56\xa4\x77\x4b\xb0\xf2\x35\x15\x5f\x21\x85\
\xe0\xc8\x6b\x5f\x92\x2d\x37\x33\x39\xf9\x03\x27\x8e\x1f\xa2\xf7\
\xbe\x9d\x04\x1c\x0b\x37\xe4\xac\xff\xa6\x30\x87\xbd\xba\x00\x6a\
\x06\x79\xe5\xf5\xaf\x89\xd9\x92\xc5\xcc\x0a\xd9\x7c\x19\xcf\xe9\
\xe2\xe4\xa9\x2f\x78\x7c\xff\x01\x72\x85\x0a\x2b\x65\x1f\xa5\x4c\
\xb5\xb2\x55\x16\x80\xbd\x31\xda\xda\x20\x1f\x7d\x3e\xcd\xc2\xfd\
\x59\xa6\x93\x39\x92\xd1\x22\xea\x9b\x16\xce\x9d\x3f\xce\xe0\x83\
\x03\x24\x82\x59\x3a\xdb\x7b\x88\xc7\x82\x68\x63\x58\xc9\xcc\x62\
\x8c\x21\x18\xb0\x6a\xc3\x37\x06\x49\x16\xff\x24\x6b\xa5\x49\xbb\
\x25\xbc\xa2\xa6\x21\xbb\x40\x7f\xdf\x00\x83\xbd\x01\x8e\x3c\xd5\
\x45\xd7\x8e\x6b\x9c\x9c\x98\x25\x1a\xb6\xe8\xbe\x3d\xc2\xdd\x77\
\x44\x48\xc4\x1c\x22\xe1\xeb\x58\x59\xaf\xcf\xd3\x33\x29\x2e\x34\
\x2d\x91\x93\x3e\xbe\x34\x78\x01\xc5\xe2\x61\xc5\xae\x72\x8e\x70\
\xc8\xc2\x0d\x5a\xbc\xf5\xee\x2f\x9c\xfa\x3e\x86\x69\x7a\x8e\xcf\
\x26\xe6\xf9\x63\xa1\x44\xa1\xa4\xd0\xda\x6c\x0d\x2f\x15\x7c\xb4\
\x67\x28\x59\x0a\xcf\xd6\x54\xe2\x06\x13\x87\x2b\x6f\x68\xa6\x27\
\xaf\x31\x32\x36\xc7\xb2\x7f\x17\xef\x7d\x7c\x8c\x33\x67\xcf\x12\
\x70\x24\x4a\x69\xd6\x6a\x46\xd6\xd3\x70\x72\xa9\x82\x67\x34\x45\
\xad\x28\xdb\x1a\x15\x34\x98\xff\x46\xed\xef\x37\x0d\x99\xbf\x4a\
\x3c\x30\x38\xc0\xc8\x4b\xaf\x92\x5a\x9c\xe2\xe0\x23\x6d\x74\xb4\
\xba\x84\x5d\x0b\x29\x45\x7d\xb8\x94\x82\x96\xb6\x10\xf3\xc5\x12\
\x2a\xef\x53\x11\x1a\x63\xad\x3f\x93\x19\x85\xf1\xb1\x77\x58\x5a\
\xf8\x99\x97\x9f\xe9\xa6\x75\x47\x90\xc6\xb8\x43\xd8\xb5\xb6\xce\
\xfc\xfa\xfd\x00\xfb\x3e\xf4\xc8\x05\x35\xba\x5e\xeb\x46\x21\xf9\
\xcf\x0a\xa9\x8c\x87\xe3\x48\xdc\x90\xb5\x6e\x98\x6a\xaa\x65\xf2\
\x52\x92\x43\x2f\x5e\xc2\x8c\x02\x1a\x10\xf5\x07\xac\xc3\x75\x70\
\x83\x92\x80\xb3\xf9\xd0\x26\xf8\x8f\xb3\x29\xc6\x3e\xb8\x8c\x19\
\x35\x75\x6b\x7b\x7e\x3c\xca\x45\x0c\x7e\x49\x31\xf4\x58\x3b\xf7\
\xf6\x34\x90\x88\x39\x04\x1c\x59\x1f\xfe\xdb\xd5\x3c\x5f\x9d\x4b\
\x32\xfd\x44\xb2\xba\xd7\xfa\xb6\x60\xcf\xde\x16\xdc\x90\x45\x4c\
\x4a\x2a\x9e\x62\xfe\x4e\xc5\xc8\xc1\x4e\xda\x76\x86\xe8\xe9\x0a\
\xe3\xd8\x92\x58\xd4\xc6\xb2\x44\x6d\x78\x2a\x53\xe1\xca\x7c\x99\
\x63\x5d\xbf\x56\x9d\xbd\x9f\x44\x18\x7a\xba\x95\x27\x0f\xb4\xd3\
\xdc\x18\xc0\xf3\x0d\x52\x40\xd8\xb5\xb0\xa4\x20\x14\xb2\x70\x6c\
\x81\x63\xcb\xaa\x42\xd6\xfd\xb7\xf4\xec\xa3\x06\xa0\x50\x52\xd8\
\x4e\x1b\x7e\x4a\xd3\x31\xf9\x29\xcf\xfe\xd4\x49\x7f\x5f\x13\xfb\
\xfa\x9b\x71\x43\x92\x58\xd4\x21\x18\x90\xac\xde\xb0\x42\x50\x13\
\x58\x33\xf3\x88\x6b\xa1\xfd\x65\x96\xf2\x79\xc6\x43\x7b\xd8\x75\
\x38\xcc\x3d\xdd\xd1\xaa\xcf\x71\xe4\xff\x7f\x91\x56\x33\xaf\xea\
\x37\xe7\xa1\x94\x21\x16\xb5\xd1\x06\x2c\x29\x36\xf5\x72\x9b\x96\
\x95\xc0\xc4\xda\x9d\x78\x83\x43\x53\x22\x80\x65\x09\x1c\xfb\x86\
\xc1\x00\xe7\x25\x70\x14\x48\x6f\x1e\x22\x51\xe3\x75\xd9\xb6\xa5\
\x81\xa3\x32\xb1\xfb\xf4\x0c\x30\xb8\xb1\x82\x9b\xb0\x09\x60\x30\
\xb1\xfb\xf4\xcc\xbf\xa0\xe9\x6e\xae\x5a\xdf\x4b\x81\x00\x00\x00\
\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
"
qt_resource_name = b"\
\x00\x07\
\x07\x3b\xe0\xb3\
\x00\x70\
\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\
\x00\x0b\
\x06\x1f\xb8\xc2\
\x00\x61\
\x00\x6d\x00\x63\x00\x72\x00\x5f\x00\x76\x00\x69\x00\x65\x00\x77\x00\x65\x00\x72\
\x00\x08\
\x0a\x61\x5a\xa7\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
"
qt_resource_struct_v1 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
"
qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9c\x23\xfd\x16\x70\
"
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
if qt_version < [5, 8, 0]:
rcc_version = 1
qt_resource_struct = qt_resource_struct_v1
else:
rcc_version = 2
qt_resource_struct = qt_resource_struct_v2
def qInitResources():
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()
+3 -4
View File
@@ -5,15 +5,14 @@
# jen k tomu, aby ruff choval stejně v CI, lokálně i za rok. Bez explicitní # jen k tomu, aby ruff choval stejně v CI, lokálně i za rok. Bez explicitní
# konfigurace se výchozí sada pravidel mezi verzemi ruffu mění. # konfigurace se výchozí sada pravidel mezi verzemi ruffu mění.
# #
# Konfigurace flake8 je záměrně jinde: v amcr_viewer/.flake8, protože ji # Flake8 záměrně žádnou konfiguraci nemá a běží s výchozími pravidly – stejně
# musí najít i scanner na plugins.qgis.org. # jako scanner na plugins.qgis.org. Config soubor v balíčku by plugin
# označil jako „Validated (configured)“.
[tool.ruff] [tool.ruff]
line-length = 79 line-length = 79
# QGIS 3.44 běží na Pythonu 3.9 a novějším # QGIS 3.44 běží na Pythonu 3.9 a novějším
target-version = "py39" target-version = "py39"
# Generovaný výstup pyrcc, "All changes made in this file will be lost"
extend-exclude = ["amcr_viewer/resources.py"]
[tool.ruff.lint] [tool.ruff.lint]
select = [ select = [
+3 -5
View File
@@ -23,10 +23,6 @@ import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BALICEK = os.path.join(ROOT, "amcr_viewer") BALICEK = os.path.join(ROOT, "amcr_viewer")
# Files that belong in the plugin package even though the upload scanner
# would otherwise call them hidden
POVOLENE_SKRYTE = {".flake8", ".bandit", ".secrets.baseline"}
# Extensions that have no business inside a plugin package # Extensions that have no business inside a plugin package
PODEZRELE = {".exe", ".dll", ".so", ".dylib", ".sh", ".bat", ".cmd", PODEZRELE = {".exe", ".dll", ".so", ".dylib", ".sh", ".bat", ".cmd",
".pyc", ".pyd", ".jar", ".bin"} ".pyc", ".pyd", ".jar", ".bin"}
@@ -72,7 +68,9 @@ for cesta in vsechny_soubory():
if rezim & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH): if rezim & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
nalezy.append(f"{zkratka(cesta)}: spustitelná práva " nalezy.append(f"{zkratka(cesta)}: spustitelná práva "
f"({stat.filemode(rezim)})") f"({stat.filemode(rezim)})")
if jmeno.startswith(".") and jmeno not in POVOLENE_SKRYTE: # No exceptions: scanner config files (.flake8, .bandit,
# .secrets.baseline) would mark the upload "Validated (configured)"
if jmeno.startswith("."):
nalezy.append(f"{zkratka(cesta)}: skrytý soubor v balíčku pluginu") nalezy.append(f"{zkratka(cesta)}: skrytý soubor v balíčku pluginu")
if os.path.splitext(jmeno)[1].lower() in PODEZRELE: if os.path.splitext(jmeno)[1].lower() in PODEZRELE:
nalezy.append(f"{zkratka(cesta)}: podezřelý typ souboru") nalezy.append(f"{zkratka(cesta)}: podezřelý typ souboru")
-187
View File
@@ -1,187 +0,0 @@
# -*- coding: utf-8 -*-
"""
Guards the release PR (version/vX.Y.Z -> main).
Checks that the version actually moved forward and that the three places
that name it agree, because plugins.qgis.org and Zenodo both read from
files a human has to remember to touch by hand:
* amcr_viewer/metadata.txt: version= must be higher than on main, and the
first changelog entry must be for that exact version
* CITATION.cff: version must match metadata.txt, and date-released must
not be left pointing at the old release
* the branch name (version/vX.Y.Z) must match metadata.txt, so a stray
push to the wrong release branch is caught before merge
Run from the repository root:
python3 tests/check_version_bump.py <base_sha> <head_ref>
"""
import datetime
import re
import subprocess
import sys
METADATA = "amcr_viewer/metadata.txt"
CITATION = "CITATION.cff"
VERZE_METADATA = re.compile(r"^version=(.+)$", re.MULTILINE)
VERZE_CITATION = re.compile(r"^version:\s*'?([^'\n]+)'?\s*$", re.MULTILINE)
DATUM_CITATION = re.compile(r"^date-released:\s*'?([^'\n]+)'?\s*$",
re.MULTILINE)
# Přeskočí úvodní řádek "Plný seznam změn ... /vX.Y.Z" a najde první
# skutečnou položku changelogu.
POLOZKA_CHANGELOGU = re.compile(r"^\s+v([0-9][^\s(]*)\s*\(", re.MULTILINE)
BRANCH_VERZE = re.compile(r"^version/v(.+)$")
nalezy = []
def nacti_na_base(base_sha, cesta):
vysledek = subprocess.run(
["git", "show", f"{base_sha}:{cesta}"],
capture_output=True, text=True,
)
if vysledek.returncode != 0:
nalezy.append(f"{cesta}: na cílové větvi nejde přečíst "
f"(git show {base_sha}:{cesta} selhalo)")
return None
return vysledek.stdout
def nacti(cesta):
with open(cesta, encoding="utf-8") as f:
return f.read()
def hledej(vzor, text, cesta, popis):
shoda = vzor.search(text)
if not shoda:
nalezy.append(f"{cesta}: {popis} nenalezeno")
return None
return shoda.group(1).strip()
def verze_tuple(verze):
jadro = verze.split("-", 1)[0]
try:
return tuple(int(c) for c in jadro.split("."))
except ValueError:
return None
def je_bump(stara, nova):
if stara == nova:
return False
stara_t, nova_t = verze_tuple(stara), verze_tuple(nova)
if stara_t is None or nova_t is None:
# Nestandardní formát verze – nejde spolehlivě porovnat čísly,
# stačí tedy, že se řetězec liší.
return True
if nova_t != stara_t:
return nova_t > stara_t
# Stejné jádro (např. "-alpha" přípona): bump platí, pokud se text
# liší a nejde o couvnutí z release na prerelease.
return "-" in stara or "-" not in nova
def datum(text):
try:
return datetime.date.fromisoformat(text)
except ValueError:
return None
def main():
if len(sys.argv) != 3:
print("použití: check_version_bump.py <base_sha> <head_ref>")
return 2
base_sha, head_ref = sys.argv[1], sys.argv[2]
base_metadata = nacti_na_base(base_sha, METADATA)
base_citation = nacti_na_base(base_sha, CITATION)
head_metadata = nacti(METADATA)
head_citation = nacti(CITATION)
if base_metadata is None or base_citation is None:
# Bez base souborů nejde nic dalšího smysluplně ověřit.
print("Nálezy:")
for nalez in nalezy:
print(f" {nalez}")
return 1
stara_verze = hledej(VERZE_METADATA, base_metadata, METADATA,
"verze na cílové větvi (version=)")
nova_verze = hledej(VERZE_METADATA, head_metadata, METADATA,
"verze (version=)")
stara_citation_verze = hledej(VERZE_CITATION, base_citation, CITATION,
"verze na cílové větvi (version:)")
nova_citation_verze = hledej(VERZE_CITATION, head_citation, CITATION,
"verze (version:)")
stare_datum = hledej(DATUM_CITATION, base_citation, CITATION,
"date-released na cílové větvi")
nove_datum = hledej(DATUM_CITATION, head_citation, CITATION,
"date-released")
if None in (stara_verze, nova_verze, stara_citation_verze,
nova_citation_verze, stare_datum, nove_datum):
print("Nálezy:")
for nalez in nalezy:
print(f" {nalez}")
return 1
# 1. metadata.txt: verze musí jít dopředu.
if not je_bump(stara_verze, nova_verze):
nalezy.append(
f"{METADATA}: verze nepovýšena ({stara_verze} -> {nova_verze})")
# 2. metadata.txt: první položka changelogu musí patřit nové verzi.
prvni_polozka = POLOZKA_CHANGELOGU.search(head_metadata)
if not prvni_polozka:
nalezy.append(f"{METADATA}: v changelog= nenalezena žádná položka "
f"'vX.Y.Z (...)'")
elif prvni_polozka.group(1) != nova_verze:
nalezy.append(
f"{METADATA}: první položka changelogu je pro "
f"v{prvni_polozka.group(1)}, ale version={nova_verze}")
# 3. CITATION.cff: verze musí souhlasit s metadata.txt.
if nova_citation_verze != nova_verze:
nalezy.append(
f"{CITATION}: version: {nova_citation_verze} neodpovídá "
f"{METADATA} version={nova_verze}")
# 4. CITATION.cff: date-released se musí posunout, a ne dozadu.
stary_datum_obj, novy_datum_obj = datum(stare_datum), datum(nove_datum)
if stary_datum_obj is None or novy_datum_obj is None:
nalezy.append(f"{CITATION}: date-released není platné datum "
f"ISO 8601 ({stare_datum!r} -> {nove_datum!r})")
elif novy_datum_obj < stary_datum_obj:
nalezy.append(
f"{CITATION}: date-released couvlo ({stare_datum} -> "
f"{nove_datum})")
elif nove_datum == stare_datum and stara_citation_verze != nova_citation_verze:
nalezy.append(
f"{CITATION}: version se změnila, ale date-released zůstalo "
f"na {stare_datum}")
# 5. Název release větve musí odpovídat verzi, kterou nese.
shoda_branch = BRANCH_VERZE.match(head_ref)
if shoda_branch and shoda_branch.group(1) != nova_verze:
nalezy.append(
f"větev {head_ref!r} neodpovídá {METADATA} "
f"version={nova_verze}")
if nalezy:
print("Nálezy:")
for nalez in nalezy:
print(f" {nalez}")
return 1
print(f"Kontrola verze a changelogu: v{stara_verze} -> v{nova_verze}, "
f"bez nálezů")
return 0
if __name__ == "__main__":
sys.exit(main())