tidypptsdm 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tidypptsdm-1.1.0/.gitignore +10 -0
- tidypptsdm-1.1.0/LICENSE +21 -0
- tidypptsdm-1.1.0/PKG-INFO +77 -0
- tidypptsdm-1.1.0/README.md +44 -0
- tidypptsdm-1.1.0/pyproject.toml +59 -0
- tidypptsdm-1.1.0/src/tidypptsdm/__init__.py +28 -0
- tidypptsdm-1.1.0/src/tidypptsdm/__main__.py +5 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_messages.py +82 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_monitor.py +765 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_plots.py +392 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_prep.py +251 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_report.py +274 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_schedule.py +156 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_station.py +493 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_tables.py +142 -0
- tidypptsdm-1.1.0/src/tidypptsdm/_version.py +1 -0
- tidypptsdm-1.1.0/src/tidypptsdm/cli.py +131 -0
- tidypptsdm-1.1.0/src/tidypptsdm/data/fire.csv.gz +0 -0
- tidypptsdm-1.1.0/src/tidypptsdm/data/nedap.csv.gz +0 -0
- tidypptsdm-1.1.0/src/tidypptsdm/datasets.py +48 -0
- tidypptsdm-1.1.0/src/tidypptsdm/gui.py +346 -0
- tidypptsdm-1.1.0/src/tidypptsdm/templates/report.html +172 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d21_alerts.csv +1 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d21_hours.csv +49 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d21_houses.csv +22 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d21_pig_summary.csv +26 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d21_pigs.csv +526 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d21_stations.csv +43 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d7_alerts.csv +8 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d7_hours.csv +49 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d7_houses.csv +8 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d7_pig_summary.csv +26 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d7_pigs.csv +176 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_d7_stations.csv +15 -0
- tidypptsdm-1.1.0/tests/fixtures/fire_visits.csv.gz +0 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d21_alerts.csv +2 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d21_hours.csv +49 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d21_houses.csv +22 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d21_pig_summary.csv +30 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d21_pigs.csv +610 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d21_stations.csv +43 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_alerts.csv +1 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_hours.csv +49 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_houses.csv +8 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_pig_summary.csv +29 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_pigs.csv +197 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_stations.csv +15 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_wide_feed.csv +3 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_d7_wide_pigfeed.csv +29 -0
- tidypptsdm-1.1.0/tests/fixtures/nedap_visits.csv.gz +0 -0
- tidypptsdm-1.1.0/tests/fixtures/spoiled_alerts.csv +3 -0
- tidypptsdm-1.1.0/tests/fixtures/spoiled_pig.txt +1 -0
- tidypptsdm-1.1.0/tests/fixtures/spoiled_stations.csv +15 -0
- tidypptsdm-1.1.0/tests/make_fixtures.R +38 -0
- tidypptsdm-1.1.0/tests/test_api.py +191 -0
- tidypptsdm-1.1.0/tests/test_parity.py +98 -0
tidypptsdm-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Meng Guo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tidypptsdm
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Daily monitoring of pig performance testing stations (NEDAP and FIRE)
|
|
5
|
+
Project-URL: Homepage, https://tony2015116.github.io/tidypptsdm-py/
|
|
6
|
+
Project-URL: Source, https://github.com/tony2015116/tidypptsdm-py
|
|
7
|
+
Project-URL: Issues, https://github.com/tony2015116/tidypptsdm-py/issues
|
|
8
|
+
Project-URL: R package, https://github.com/tony2015116/tidypptsdm
|
|
9
|
+
Author-email: Meng Guo <tony2015116@163.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: FIRE,NEDAP,feeding station,livestock,monitoring,performance test,pig
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: matplotlib>=3.7
|
|
22
|
+
Requires-Dist: numpy>=1.24
|
|
23
|
+
Requires-Dist: openpyxl>=3.1
|
|
24
|
+
Requires-Dist: pandas>=2.0
|
|
25
|
+
Provides-Extra: docs
|
|
26
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
27
|
+
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
|
|
28
|
+
Provides-Extra: exe
|
|
29
|
+
Requires-Dist: pyinstaller>=6.0; extra == 'exe'
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# tidypptsdm (Python) <img src="docs/assets/logo.svg" align="right" height="120" alt="logo" />
|
|
35
|
+
|
|
36
|
+
[](https://github.com/tony2015116/tidypptsdm-py/actions/workflows/tests.yml)
|
|
37
|
+
[](https://tony2015116.github.io/tidypptsdm-py/)
|
|
38
|
+
|
|
39
|
+
**种猪性能测定站日常监控**:读取 NEDAP 或 FIRE 测定站的导出文件,按测定站、栋、猪和小时汇总最近几天的数据,列出最后一天的警报(测定站停机、耳标读不到、采食量骤降、猪不来吃料或吃得太少),并生成可以逐站翻页浏览的 HTML 报告和带色阶的 Excel 工作簿。
|
|
40
|
+
|
|
41
|
+
Daily monitoring of automatic pig feeding stations (NEDAP and FIRE): daily
|
|
42
|
+
tables per station, house, pig and hour, alerts of the last day, an offline
|
|
43
|
+
HTML report and an Excel workbook.
|
|
44
|
+
|
|
45
|
+
这是 R 包 [tidypptsdm](https://github.com/tony2015116/tidypptsdm) 的 Python 版本,**计算结果与 R 版逐表一致**(测试中用两份示例数据对比)。
|
|
46
|
+
|
|
47
|
+
文档:<https://tony2015116.github.io/tidypptsdm-py/>
|
|
48
|
+
|
|
49
|
+
## 三种用法
|
|
50
|
+
|
|
51
|
+
### 1. 桌面程序(Windows,不需要安装 Python)
|
|
52
|
+
|
|
53
|
+
从 [Releases](https://github.com/tony2015116/tidypptsdm-py/releases) 下载 `tidypptsdm-windows.zip`,解压后双击 `tidypptsdm.exe`:选择导出文件夹和结果文件夹,点“运行监控”,报告自动在浏览器中打开;点“设为每天定时”即可每天自动生成报告。
|
|
54
|
+
|
|
55
|
+
### 2. 命令行
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install tidypptsdm
|
|
59
|
+
tidypptsdm run D:/nedap/exports --out D:/nedap/monitor --days 7
|
|
60
|
+
tidypptsdm schedule D:/nedap/exports --out D:/nedap/monitor --time 07:30
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Python
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import tidypptsdm as tp
|
|
67
|
+
|
|
68
|
+
mon = tp.run_monitor("D:/nedap/exports", days=7) # or tp.load_example("nedap")
|
|
69
|
+
mon.alerts
|
|
70
|
+
tp.wide_table(mon.stations, "feed_kg")
|
|
71
|
+
tp.write_monitor_report(mon, "monitor/2024-05-25", open=True)
|
|
72
|
+
tp.write_monitor_tables(mon, "monitor/2024-05-25")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 依赖
|
|
76
|
+
|
|
77
|
+
pandas、numpy、matplotlib、openpyxl。图形界面用 Python 自带的 tkinter。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# tidypptsdm (Python) <img src="docs/assets/logo.svg" align="right" height="120" alt="logo" />
|
|
2
|
+
|
|
3
|
+
[](https://github.com/tony2015116/tidypptsdm-py/actions/workflows/tests.yml)
|
|
4
|
+
[](https://tony2015116.github.io/tidypptsdm-py/)
|
|
5
|
+
|
|
6
|
+
**种猪性能测定站日常监控**:读取 NEDAP 或 FIRE 测定站的导出文件,按测定站、栋、猪和小时汇总最近几天的数据,列出最后一天的警报(测定站停机、耳标读不到、采食量骤降、猪不来吃料或吃得太少),并生成可以逐站翻页浏览的 HTML 报告和带色阶的 Excel 工作簿。
|
|
7
|
+
|
|
8
|
+
Daily monitoring of automatic pig feeding stations (NEDAP and FIRE): daily
|
|
9
|
+
tables per station, house, pig and hour, alerts of the last day, an offline
|
|
10
|
+
HTML report and an Excel workbook.
|
|
11
|
+
|
|
12
|
+
这是 R 包 [tidypptsdm](https://github.com/tony2015116/tidypptsdm) 的 Python 版本,**计算结果与 R 版逐表一致**(测试中用两份示例数据对比)。
|
|
13
|
+
|
|
14
|
+
文档:<https://tony2015116.github.io/tidypptsdm-py/>
|
|
15
|
+
|
|
16
|
+
## 三种用法
|
|
17
|
+
|
|
18
|
+
### 1. 桌面程序(Windows,不需要安装 Python)
|
|
19
|
+
|
|
20
|
+
从 [Releases](https://github.com/tony2015116/tidypptsdm-py/releases) 下载 `tidypptsdm-windows.zip`,解压后双击 `tidypptsdm.exe`:选择导出文件夹和结果文件夹,点“运行监控”,报告自动在浏览器中打开;点“设为每天定时”即可每天自动生成报告。
|
|
21
|
+
|
|
22
|
+
### 2. 命令行
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install tidypptsdm
|
|
26
|
+
tidypptsdm run D:/nedap/exports --out D:/nedap/monitor --days 7
|
|
27
|
+
tidypptsdm schedule D:/nedap/exports --out D:/nedap/monitor --time 07:30
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 3. Python
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import tidypptsdm as tp
|
|
34
|
+
|
|
35
|
+
mon = tp.run_monitor("D:/nedap/exports", days=7) # or tp.load_example("nedap")
|
|
36
|
+
mon.alerts
|
|
37
|
+
tp.wide_table(mon.stations, "feed_kg")
|
|
38
|
+
tp.write_monitor_report(mon, "monitor/2024-05-25", open=True)
|
|
39
|
+
tp.write_monitor_tables(mon, "monitor/2024-05-25")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 依赖
|
|
43
|
+
|
|
44
|
+
pandas、numpy、matplotlib、openpyxl。图形界面用 Python 自带的 tkinter。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tidypptsdm"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Daily monitoring of pig performance testing stations (NEDAP and FIRE)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Meng Guo", email = "tony2015116@163.com" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
keywords = ["pig", "performance test", "feeding station", "NEDAP", "FIRE", "livestock", "monitoring"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Natural Language :: Chinese (Simplified)",
|
|
19
|
+
"Natural Language :: English",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pandas>=2.0",
|
|
26
|
+
"numpy>=1.24",
|
|
27
|
+
"matplotlib>=3.7",
|
|
28
|
+
"openpyxl>=3.1",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
test = ["pytest>=7"]
|
|
33
|
+
docs = ["mkdocs-material>=9.5", "mkdocstrings[python]>=0.25"]
|
|
34
|
+
exe = ["pyinstaller>=6.0"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
tidypptsdm = "tidypptsdm.cli:main"
|
|
38
|
+
|
|
39
|
+
[project.gui-scripts]
|
|
40
|
+
tidypptsdm-gui = "tidypptsdm.gui:main"
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://tony2015116.github.io/tidypptsdm-py/"
|
|
44
|
+
Source = "https://github.com/tony2015116/tidypptsdm-py"
|
|
45
|
+
Issues = "https://github.com/tony2015116/tidypptsdm-py/issues"
|
|
46
|
+
"R package" = "https://github.com/tony2015116/tidypptsdm"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.version]
|
|
49
|
+
path = "src/tidypptsdm/_version.py"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/tidypptsdm"]
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.sdist]
|
|
55
|
+
include = ["src", "tests", "README.md", "LICENSE", "pyproject.toml"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
filterwarnings = ["error::FutureWarning"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""tidypptsdm: daily monitoring of pig performance testing stations.
|
|
2
|
+
|
|
3
|
+
The Python version of the R package tidypptsdm: read NEDAP or FIRE exports,
|
|
4
|
+
compute the daily tables per station, house, pig and hour, list the alerts
|
|
5
|
+
of the last day, and write an HTML report and an Excel workbook.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ._messages import Abort
|
|
9
|
+
from ._monitor import (Monitor, monitor_hours, monitor_houses, monitor_pigs,
|
|
10
|
+
monitor_stations, run_monitor, wide_table)
|
|
11
|
+
from ._plots import StationPlot, plot_monitor, save_monitor_plots
|
|
12
|
+
from ._prep import prep_visits
|
|
13
|
+
from ._report import write_monitor_report
|
|
14
|
+
from ._schedule import monitor_to_folder, schedule_monitor, unschedule_monitor
|
|
15
|
+
from ._station import read_station, station_format
|
|
16
|
+
from ._tables import write_monitor_tables
|
|
17
|
+
from .datasets import load_example, write_example
|
|
18
|
+
|
|
19
|
+
from ._version import __version__
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Abort", "Monitor", "StationPlot", "load_example", "monitor_hours",
|
|
23
|
+
"monitor_houses", "monitor_pigs", "monitor_stations", "monitor_to_folder",
|
|
24
|
+
"plot_monitor", "prep_visits", "read_station", "run_monitor",
|
|
25
|
+
"save_monitor_plots", "schedule_monitor", "station_format",
|
|
26
|
+
"unschedule_monitor", "wide_table", "write_example",
|
|
27
|
+
"write_monitor_report", "write_monitor_tables",
|
|
28
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Console messages with three verbosity levels, as in the R packages.
|
|
2
|
+
|
|
3
|
+
0: silent, 1: summary (default), 2: details. Messages go to stderr so that
|
|
4
|
+
the standard output stays free for data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import contextlib
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Iterator
|
|
12
|
+
|
|
13
|
+
_state = {"verbose": 1}
|
|
14
|
+
|
|
15
|
+
_SYMBOLS = {
|
|
16
|
+
"info": "i",
|
|
17
|
+
"success": "v",
|
|
18
|
+
"warning": "!",
|
|
19
|
+
"danger": "x",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Abort(ValueError):
|
|
24
|
+
"""Error raised for bad input, with a message meant for the user."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_verbose(verbose: int) -> int:
|
|
28
|
+
if verbose not in (0, 1, 2) or isinstance(verbose, bool):
|
|
29
|
+
raise Abort("`verbose` must be 0 (silent), 1 (summary) or 2 (details).")
|
|
30
|
+
return int(verbose)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@contextlib.contextmanager
|
|
34
|
+
def local_verbose(verbose: int) -> Iterator[int]:
|
|
35
|
+
"""Set the verbosity until the block exits."""
|
|
36
|
+
verbose = check_verbose(verbose)
|
|
37
|
+
old = _state["verbose"]
|
|
38
|
+
_state["verbose"] = verbose
|
|
39
|
+
try:
|
|
40
|
+
yield verbose
|
|
41
|
+
finally:
|
|
42
|
+
_state["verbose"] = old
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def current_verbose() -> int:
|
|
46
|
+
return _state["verbose"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _write(text: str) -> None:
|
|
50
|
+
stream = sys.stderr
|
|
51
|
+
if stream is None: # a windowed exe without console and without a log file
|
|
52
|
+
return
|
|
53
|
+
try:
|
|
54
|
+
stream.write(text + "\n")
|
|
55
|
+
except UnicodeEncodeError: # e.g. a Windows console in a legacy code page
|
|
56
|
+
enc = getattr(stream, "encoding", None) or "ascii"
|
|
57
|
+
stream.write(text.encode(enc, "replace").decode(enc) + "\n")
|
|
58
|
+
stream.flush()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def msg(text: str, kind: str = "info", level: int = 1) -> None:
|
|
62
|
+
"""Print a message if the verbosity is at least `level`."""
|
|
63
|
+
if _state["verbose"] < level:
|
|
64
|
+
return
|
|
65
|
+
if kind == "h1":
|
|
66
|
+
line = "-" * max(0, 72 - len(text) - 4)
|
|
67
|
+
_write(f"\n-- {text} {line}")
|
|
68
|
+
else:
|
|
69
|
+
_write(f"{_SYMBOLS.get(kind, 'i')} {text}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def bullets(lines: list[tuple[str, str]], level: int = 1) -> None:
|
|
73
|
+
"""Print indented lines, each with a kind ("danger", "warning", ...)."""
|
|
74
|
+
if _state["verbose"] < level:
|
|
75
|
+
return
|
|
76
|
+
for kind, text in lines:
|
|
77
|
+
_write(f" {_SYMBOLS.get(kind, '*')} {text}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def plural(n: int, word: str, plural_word: str | None = None) -> str:
|
|
81
|
+
"""`3 pigs`, `1 pig`."""
|
|
82
|
+
return f"{n:,} {word if n == 1 else (plural_word or word + 's')}"
|