pyvbml 0.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.
@@ -0,0 +1,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Unit test / coverage reports
13
+ .coverage
pyvbml-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nathan Spencer
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.
pyvbml-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyvbml
3
+ Version: 0.1.0
4
+ Summary: A python package to parse Vestaboard markup language (VBML) to Vestaboard character arrays
5
+ Author-email: Nathan Spencer <natekspencer@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: backports-strenum>=1.3.1; python_version < '3.11'
10
+ Description-Content-Type: text/markdown
11
+
12
+ # pyvbml
13
+
14
+ [![PyPI - Version](https://img.shields.io/pypi/v/pyvbml?style=for-the-badge)](https://pypi.org/project/pyvbml/)
15
+ [![Buy Me A Coffee/Beer](https://img.shields.io/badge/Buy_Me_A_☕/🍺-F16061?style=for-the-badge&logo=ko-fi&logoColor=white&labelColor=grey)](https://ko-fi.com/natekspencer)
16
+ [![Sponsor on GitHub](https://img.shields.io/badge/Sponsor_💜-6f42c1?style=for-the-badge&logo=github&logoColor=white&labelColor=grey)](https://github.com/sponsors/natekspencer)
17
+
18
+ [![GitHub License](https://img.shields.io/github/license/natekspencer/pyvbml?style=flat-square)](LICENSE)
19
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyvbml?style=flat-square)](https://pypi.org/project/pyvbml/)
20
+ ![Pepy Total Downloads](https://img.shields.io/pepy/dt/pyvbml?style=flat-square)
21
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/pyvbml?style=flat-square)
22
+
23
+ A Python package for parsing [Vestaboard markup language (VBML)](https://docs.vestaboard.com/docs/vbml/) to Vestaboard character arrays locally. Based on the [Vestaboard/vbml](https://github.com/Vestaboard/vbml) codebase. Used in the [Vestaboard for Home Assistant integration](https://github.com/natekspencer/ha-vestaboard).
24
+
25
+ ## ❤️ Support Me
26
+
27
+ I maintain this python project in my spare time. If you find it useful, consider supporting development:
28
+
29
+ - 💜 [Sponsor me on GitHub](https://github.com/sponsors/natekspencer)
30
+ - ☕ [Buy me a coffee / beer](https://ko-fi.com/natekspencer)
31
+ - 💸 [PayPal (direct support)](https://www.paypal.com/paypalme/natekspencer)
32
+ - ⭐ [Star this project](https://github.com/natekspencer/pyvbml)
33
+ - 📦 If you’d like to support in other ways, such as donating hardware for testing, feel free to [reach out to me](https://github.com/natekspencer)
34
+
35
+ ## 📈 Star History
36
+
37
+ [![Star History Chart](https://api.star-history.com/svg?repos=natekspencer/pyvbml)](https://www.star-history.com/#natekspencer/pyvbml)
pyvbml-0.1.0/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # pyvbml
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/pyvbml?style=for-the-badge)](https://pypi.org/project/pyvbml/)
4
+ [![Buy Me A Coffee/Beer](https://img.shields.io/badge/Buy_Me_A_☕/🍺-F16061?style=for-the-badge&logo=ko-fi&logoColor=white&labelColor=grey)](https://ko-fi.com/natekspencer)
5
+ [![Sponsor on GitHub](https://img.shields.io/badge/Sponsor_💜-6f42c1?style=for-the-badge&logo=github&logoColor=white&labelColor=grey)](https://github.com/sponsors/natekspencer)
6
+
7
+ [![GitHub License](https://img.shields.io/github/license/natekspencer/pyvbml?style=flat-square)](LICENSE)
8
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyvbml?style=flat-square)](https://pypi.org/project/pyvbml/)
9
+ ![Pepy Total Downloads](https://img.shields.io/pepy/dt/pyvbml?style=flat-square)
10
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/pyvbml?style=flat-square)
11
+
12
+ A Python package for parsing [Vestaboard markup language (VBML)](https://docs.vestaboard.com/docs/vbml/) to Vestaboard character arrays locally. Based on the [Vestaboard/vbml](https://github.com/Vestaboard/vbml) codebase. Used in the [Vestaboard for Home Assistant integration](https://github.com/natekspencer/ha-vestaboard).
13
+
14
+ ## ❤️ Support Me
15
+
16
+ I maintain this python project in my spare time. If you find it useful, consider supporting development:
17
+
18
+ - 💜 [Sponsor me on GitHub](https://github.com/sponsors/natekspencer)
19
+ - ☕ [Buy me a coffee / beer](https://ko-fi.com/natekspencer)
20
+ - 💸 [PayPal (direct support)](https://www.paypal.com/paypalme/natekspencer)
21
+ - ⭐ [Star this project](https://github.com/natekspencer/pyvbml)
22
+ - 📦 If you’d like to support in other ways, such as donating hardware for testing, feel free to [reach out to me](https://github.com/natekspencer)
23
+
24
+ ## 📈 Star History
25
+
26
+ [![Star History Chart](https://api.star-history.com/svg?repos=natekspencer/pyvbml)](https://www.star-history.com/#natekspencer/pyvbml)
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "pyvbml"
3
+ description = "A python package to parse Vestaboard markup language (VBML) to Vestaboard character arrays"
4
+ authors = [{ name = "Nathan Spencer", email = "natekspencer@gmail.com" }]
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ dependencies = [
9
+ "backports-strenum>=1.3.1; python_version < '3.11'",
10
+ ]
11
+ dynamic = ["version"]
12
+
13
+ [dependency-groups]
14
+ dev = [
15
+ "mypy>=1.19.1",
16
+ "pytest>=9.0.2",
17
+ "pytest-cov>=7.0.0",
18
+ "ruff>=0.15.4",
19
+ ]
20
+
21
+ [build-system]
22
+ requires = ["hatchling", "uv-dynamic-versioning"]
23
+ build-backend = "hatchling.build"
24
+
25
+ [tool.hatch.build.hooks.version]
26
+ path = "pyvbml/__init__.py"
27
+ pattern = true
28
+
29
+ [tool.hatch.build.targets.sdist]
30
+ include = ["pyvbml"]
31
+
32
+ [tool.hatch.version]
33
+ source = "uv-dynamic-versioning"
34
+
35
+ [tool.ruff.lint.isort]
36
+ force-sort-within-sections = true
37
+ forced-separate = ["tests"]
38
+ combine-as-imports = true
39
+ split-on-trailing-comma = false
40
+
41
+ [tool.uv]
42
+ cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true }}]
43
+
44
+ [tool.uv-dynamic-versioning]
45
+ pattern = "default-unprefixed"
@@ -0,0 +1,9 @@
1
+ """Python VBML module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .index import VBML
6
+ from .types import Align, Justify
7
+
8
+ __all__ = ["VBML", "Align", "Justify"]
9
+ __version__ = "0.1.0"
@@ -0,0 +1,165 @@
1
+ """Calendar.
2
+
3
+ Port of Vestaboard/vbml/src/calendar.ts
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import math
9
+ from calendar import monthrange
10
+ from datetime import date
11
+
12
+ from .character_codes import CharacterCode
13
+
14
+
15
+ def _char_code_for_digit(digit: str) -> int:
16
+ """Char code for digit."""
17
+ return 36 if digit == "0" else int(digit) + 26
18
+
19
+
20
+ def _char_code_for_day(day: str) -> int:
21
+ """Char code for day."""
22
+ return {
23
+ "Sun": 19,
24
+ "Mon": 13,
25
+ "Tue": 20,
26
+ "Wed": 23,
27
+ "Thu": 20,
28
+ "Fri": 6,
29
+ "Sat": 19,
30
+ }.get(day, 0)
31
+
32
+
33
+ def make_calendar(
34
+ month: int,
35
+ year: int,
36
+ *,
37
+ default_day_color: CharacterCode | int | None = None,
38
+ highlighted_days: dict[int | str, CharacterCode | int] | None = None,
39
+ hide_day_of_week: bool = False,
40
+ hide_dates: bool = False,
41
+ hide_month_year: bool = False,
42
+ ) -> list[list[int]]:
43
+ """Make calendar."""
44
+ num_days = monthrange(year, month)[1]
45
+
46
+ first_day_of_month = date(year, month, 1).strftime("%a") # e.g. "Mon"
47
+ days_of_week = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
48
+ offset = days_of_week.index(first_day_of_month)
49
+
50
+ cal_color = CharacterCode.YELLOW if default_day_color is None else default_day_color
51
+
52
+ # Row day ranges (as strings, matching the TS template literals)
53
+ first_row_days = [str(1), str(7 - offset)]
54
+ second_row_days = [str(7 - offset + 1), str(7 - offset + 7)]
55
+ third_row_days = [str(7 - offset + 8), str(7 - offset + 14)]
56
+ fourth_row_days = [str(7 - offset + 15), str(7 - offset + 21)]
57
+ fifth_start = 7 - offset + 22
58
+ fifth_end = min(7 - offset + num_days, num_days)
59
+ fifth_row_days = (
60
+ [str(fifth_start), str(fifth_end)] if fifth_start <= num_days else None
61
+ )
62
+ num_days_last_row = fifth_end - (7 - offset + 22) + 1
63
+
64
+ def dc(digit: str) -> int:
65
+ """Digit → character code, or 0 when dates are hidden."""
66
+ return 0 if hide_dates else _char_code_for_digit(digit)
67
+
68
+ if first_row_days[0] == first_row_days[1]:
69
+ first_row = (
70
+ [0, 0, 0, dc(first_row_days[0]), 0]
71
+ + [0] * offset
72
+ + [cal_color] * (7 - offset)
73
+ + [0] * (22 - 12)
74
+ )
75
+ else:
76
+ first_row = (
77
+ [
78
+ 0,
79
+ dc(first_row_days[0]),
80
+ 0 if hide_dates else 44,
81
+ dc(first_row_days[1]),
82
+ 0,
83
+ ]
84
+ + [0] * offset
85
+ + [cal_color] * (7 - offset)
86
+ + [0] * (22 - 12)
87
+ )
88
+
89
+ def _two_digit_row(row_days: list[str]) -> list[int]:
90
+ start, end = list(row_days[0]), list(row_days[1])
91
+ row: list[int] = []
92
+ row += [dc(start[0]), dc(start[1])] if len(start) > 1 else [0, dc(start[0])]
93
+ row += [0 if hide_dates else 44]
94
+ row += [dc(end[0]), dc(end[1])] if len(end) > 1 else [dc(end[0]), 0]
95
+ row += [cal_color] * 7
96
+ row += [0] * (22 - 12)
97
+ return row
98
+
99
+ second_row = _two_digit_row(second_row_days)
100
+ third_row = _two_digit_row(third_row_days)
101
+
102
+ def _exact_two_digit_row(row_days: list[str]) -> list[int]:
103
+ start, end = list(row_days[0]), list(row_days[1])
104
+ row = [
105
+ dc(start[0]),
106
+ dc(start[1]),
107
+ 0 if hide_dates else 44,
108
+ dc(end[0]),
109
+ dc(end[1]),
110
+ ]
111
+ row += [cal_color] * 7
112
+ row += [0] * (22 - 12)
113
+ return row
114
+
115
+ fourth_row = _exact_two_digit_row(fourth_row_days)
116
+
117
+ if not fifth_row_days:
118
+ fifth_row = [0] * 22
119
+ else:
120
+ start, end = list(fifth_row_days[0]), list(fifth_row_days[1])
121
+ fifth_row = [
122
+ dc(start[0]),
123
+ dc(start[1]),
124
+ 0 if hide_dates or start == end else 44,
125
+ 0 if start == end else dc(end[0]),
126
+ 0 if start == end else dc(end[1]),
127
+ ]
128
+ fifth_row += [cal_color] * num_days_last_row
129
+ fifth_row += [0] * (22 - (5 + num_days_last_row))
130
+
131
+ # Header: month/year + day-of-week labels
132
+ if hide_month_year:
133
+ month_year = [0, 0, 0, 0, 0]
134
+ else:
135
+ month_year = (
136
+ [_char_code_for_digit(c) for c in str(month)]
137
+ + [59] # slash
138
+ + [_char_code_for_digit(c) for c in str(year)[2:4]]
139
+ )
140
+
141
+ header_space = 5 - len(month_year)
142
+ header_row = (
143
+ month_year
144
+ + [0] * header_space
145
+ + (
146
+ [0] * 7
147
+ if hide_day_of_week
148
+ else [_char_code_for_day(d) for d in days_of_week]
149
+ )
150
+ + [0] * (22 - (7 + 5))
151
+ )
152
+
153
+ calendar = [header_row, first_row, second_row, third_row, fourth_row, fifth_row]
154
+
155
+ # Overlay individual day colors
156
+ for day_key, color in (highlighted_days or {}).items():
157
+ if (day := int(day_key)) > num_days:
158
+ continue # ignore days that don't exist in month
159
+ todays_row = math.floor((day + offset - 1) / 7) + 1
160
+ modulus = (day + offset - 1) % 7
161
+ # Account for spillover off the board
162
+ todays_col = (12 if modulus == 0 else 13) if todays_row > 5 else modulus + 5
163
+ calendar[5 if todays_row > 5 else todays_row][todays_col] = color
164
+
165
+ return calendar