Files
2026-10-06 21:08:12 +03:00

440 lines
7.4 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Общие вспомогательные функции HTML Report.
В этом модуле не должно быть SQL-запросов.
Только:
• форматирование
• HTML escaping
• работа с файлами
• чтение CSV
• чтение gps_track.nmea
• вычисления
"""
from __future__ import annotations
import csv
import html
import os
from datetime import datetime, timezone
from pathlib import Path
from zoneinfo import ZoneInfo
from .config import TIMEZONE
# ============================================================
# HTML
# ============================================================
def esc(value) -> str:
"""
Безопасный вывод значения в HTML.
"""
if value is None:
return ""
return html.escape(str(value))
# ============================================================
# Значение по умолчанию
# ============================================================
def safe(value, default="-"):
if value is None:
return default
if value == "":
return default
return value
# ============================================================
# Размер файла
# ============================================================
def format_size(size):
if size is None:
return "-"
size = float(size)
units = [
"B",
"KB",
"MB",
"GB",
"TB"
]
for unit in units:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} PB"
def file_size(path):
path = Path(path)
if not path.exists():
return "-"
return format_size(
path.stat().st_size
)
# ============================================================
# Дата / время
# ============================================================
def parse_datetime(text):
if not text:
return None
try:
dt = datetime.fromisoformat(text)
#
# Старые записи проекта хранят UTC
# как naive datetime.
#
if dt.tzinfo is None:
dt = dt.replace(
tzinfo=timezone.utc
)
return dt
except Exception:
return None
def format_datetime(
text,
output_format="%d.%m.%Y %H:%M:%S"
):
dt = parse_datetime(text)
if dt is None:
return safe(text)
display_timezone = ZoneInfo(TIMEZONE)
return dt.astimezone(
display_timezone
).strftime(
output_format
)
def format_duration(start, end):
start = parse_datetime(start)
end = parse_datetime(end)
if start is None:
return "-"
if end is None:
return "-"
delta = end - start
seconds = int(
delta.total_seconds()
)
hours = seconds // 3600
minutes = (
seconds % 3600
) // 60
seconds = seconds % 60
return (
f"{hours:02}:{minutes:02}:{seconds:02}"
)
# Совместимость со старым кодом
duration = format_duration
# ============================================================
# CSV
# ============================================================
def read_csv(filename):
filename = Path(filename)
if not filename.exists():
return []
with open(
filename,
newline="",
encoding="utf-8",
errors="ignore"
) as f:
reader = csv.DictReader(f)
return list(reader)
# ============================================================
# Текстовый файл
# ============================================================
def read_lines(filename):
filename = Path(filename)
if not filename.exists():
return []
with open(
filename,
encoding="utf-8",
errors="ignore"
) as f:
return [
line.rstrip()
for line in f
]
# ============================================================
# gps_track.nmea
# ============================================================
def read_nmea(filename):
"""
Читает gps_track.nmea.
Формат строки:
2026-07-16T12:47:51.938726;$GPRMC,...
Возвращает:
[
(
datetime,
"$GPRMC,..."
),
...
]
"""
result = []
filename = Path(filename)
if not filename.exists():
return result
with open(
filename,
encoding="utf-8",
errors="ignore"
) as f:
for line in f:
line = line.strip()
if not line:
continue
if ";" not in line:
continue
timestamp, sentence = line.split(
";",
1
)
dt = parse_datetime(
timestamp
)
if dt is None:
continue
result.append(
(
dt,
sentence
)
)
return result
# ============================================================
# Каталог
# ============================================================
def list_directories(path):
path = Path(path)
if not path.exists():
return []
return sorted(
[
item
for item in path.iterdir()
if item.is_dir()
]
)
# ============================================================
# Список файлов
# ============================================================
def list_files(path):
path = Path(path)
if not path.exists():
return []
return sorted(
[
item
for item in path.iterdir()
if item.is_file()
]
)
# ============================================================
# Проверка существования
# ============================================================
def exists(path):
return Path(path).exists()
# ============================================================
# Имя файла
# ============================================================
def filename(path):
return Path(path).name
# ============================================================
# Имя каталога
# ============================================================
def dirname(path):
return Path(path).parent.name
# ============================================================
# Последнее изменение
# ============================================================
def modified(path):
path = Path(path)
if not path.exists():
return "-"
display_timezone = ZoneInfo(TIMEZONE)
return datetime.fromtimestamp(
path.stat().st_mtime,
timezone.utc,
).astimezone(
display_timezone
).strftime(
"%d.%m.%Y %H:%M:%S"
)
# ============================================================
# Количество строк файла
# ============================================================
def line_count(filename):
filename = Path(filename)
if not filename.exists():
return 0
with open(
filename,
encoding="utf-8",
errors="ignore"
) as f:
return sum(
1
for _ in f
)