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.
- pyvbml-0.1.0/.gitignore +13 -0
- pyvbml-0.1.0/LICENSE +21 -0
- pyvbml-0.1.0/PKG-INFO +37 -0
- pyvbml-0.1.0/README.md +26 -0
- pyvbml-0.1.0/pyproject.toml +45 -0
- pyvbml-0.1.0/pyvbml/__init__.py +9 -0
- pyvbml-0.1.0/pyvbml/calendar.py +165 -0
- pyvbml-0.1.0/pyvbml/character_codes.py +378 -0
- pyvbml-0.1.0/pyvbml/character_codes_to_ascii.py +103 -0
- pyvbml-0.1.0/pyvbml/character_codes_to_string.py +147 -0
- pyvbml-0.1.0/pyvbml/classic.py +288 -0
- pyvbml-0.1.0/pyvbml/copy_character_codes.py +11 -0
- pyvbml-0.1.0/pyvbml/create_empty_board.py +13 -0
- pyvbml-0.1.0/pyvbml/emojis_to_character_codes.py +22 -0
- pyvbml-0.1.0/pyvbml/get_lines_from_words.py +57 -0
- pyvbml-0.1.0/pyvbml/has_special_characters.py +15 -0
- pyvbml-0.1.0/pyvbml/horizontal_align.py +71 -0
- pyvbml-0.1.0/pyvbml/index.py +101 -0
- pyvbml-0.1.0/pyvbml/layout_components.py +59 -0
- pyvbml-0.1.0/pyvbml/multiple_character_mappings.py +37 -0
- pyvbml-0.1.0/pyvbml/parse_calendar_component.py +11 -0
- pyvbml-0.1.0/pyvbml/parse_component.py +70 -0
- pyvbml-0.1.0/pyvbml/parse_props.py +59 -0
- pyvbml-0.1.0/pyvbml/random_colors.py +21 -0
- pyvbml-0.1.0/pyvbml/render_component.py +22 -0
- pyvbml-0.1.0/pyvbml/sanitize_special_characters.py +13 -0
- pyvbml-0.1.0/pyvbml/split_words.py +45 -0
- pyvbml-0.1.0/pyvbml/types.py +103 -0
- pyvbml-0.1.0/pyvbml/vertical_align.py +41 -0
pyvbml-0.1.0/.gitignore
ADDED
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
|
+
[](https://pypi.org/project/pyvbml/)
|
|
15
|
+
[](https://ko-fi.com/natekspencer)
|
|
16
|
+
[](https://github.com/sponsors/natekspencer)
|
|
17
|
+
|
|
18
|
+
[](LICENSE)
|
|
19
|
+
[](https://pypi.org/project/pyvbml/)
|
|
20
|
+

|
|
21
|
+

|
|
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
|
+
[](https://www.star-history.com/#natekspencer/pyvbml)
|
pyvbml-0.1.0/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# pyvbml
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pyvbml/)
|
|
4
|
+
[](https://ko-fi.com/natekspencer)
|
|
5
|
+
[](https://github.com/sponsors/natekspencer)
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://pypi.org/project/pyvbml/)
|
|
9
|
+

|
|
10
|
+

|
|
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
|
+
[](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,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
|