tinyprint 0.2.2__tar.gz → 0.2.4__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.
- {tinyprint-0.2.2 → tinyprint-0.2.4}/PKG-INFO +29 -1
- tinyprint-0.2.4/README.md +31 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/pyproject.toml +1 -1
- tinyprint-0.2.4/tests/test_gui.py +312 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tests/test_integration.py +2 -2
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/gui.py +4 -1
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/preprocessor.py +27 -11
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint.egg-info/PKG-INFO +29 -1
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint.egg-info/SOURCES.txt +1 -0
- tinyprint-0.2.2/README.md +0 -3
- {tinyprint-0.2.2 → tinyprint-0.2.4}/LICENSE +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/setup.cfg +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tests/test_job.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tests/test_jobconfig.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/__init__.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/__main__.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/config.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/gui_misc.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/job.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/jobconfig.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/patches/__init__.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/patches/escpos.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/patches/mako.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/printer/__init__.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/printer/manager.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint/printer/profile.py +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint.egg-info/dependency_links.txt +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint.egg-info/entry_points.txt +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint.egg-info/requires.txt +0 -0
- {tinyprint-0.2.2 → tinyprint-0.2.4}/tinyprint.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinyprint
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: ui & cli to print zines with esc/pos printer
|
|
5
5
|
Author-email: Levin Eric Zimmermann <levin.eric.zimmermann@posteo.com>
|
|
6
6
|
License: GPL
|
|
@@ -23,3 +23,31 @@ Dynamic: license-file
|
|
|
23
23
|
# tinyprint
|
|
24
24
|
|
|
25
25
|
Software for printing tiny zines with ESC/POS printer.
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
### MacOS
|
|
32
|
+
|
|
33
|
+
Open a console and type:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)
|
|
37
|
+
brew install python
|
|
38
|
+
brew install python-tk
|
|
39
|
+
brew install pipx
|
|
40
|
+
pipx install tinyprint
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Linux
|
|
44
|
+
|
|
45
|
+
Install python, and then just use pip:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip3 install tinyprint
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Windows
|
|
52
|
+
|
|
53
|
+
I don't know, but if someone manages, please let us know.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# tinyprint
|
|
2
|
+
|
|
3
|
+
Software for printing tiny zines with ESC/POS printer.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### MacOS
|
|
10
|
+
|
|
11
|
+
Open a console and type:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)
|
|
15
|
+
brew install python
|
|
16
|
+
brew install python-tk
|
|
17
|
+
brew install pipx
|
|
18
|
+
pipx install tinyprint
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Linux
|
|
22
|
+
|
|
23
|
+
Install python, and then just use pip:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip3 install tinyprint
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Windows
|
|
30
|
+
|
|
31
|
+
I don't know, but if someone manages, please let us know.
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""Tests for the TinyPrint GUI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
import tempfile
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
# Import the GUI components
|
|
11
|
+
from tinyprint.gui import GUI, JobConfigManager
|
|
12
|
+
from tinyprint.jobconfig import JobConfig, Resource
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def temp_dir():
|
|
17
|
+
"""Create and cleanup a temporary directory for test files."""
|
|
18
|
+
temp_dir = tempfile.mkdtemp(prefix="tinyprint-test-")
|
|
19
|
+
yield temp_dir
|
|
20
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def gui_app(temp_dir):
|
|
25
|
+
"""Create a GUI instance for testing with a temporary directory."""
|
|
26
|
+
with GUI() as gui:
|
|
27
|
+
yield gui
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def job_controller(gui_app):
|
|
32
|
+
"""Return the JobController instance from the GUI."""
|
|
33
|
+
return gui_app.blk.job_controller
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_print_button_starts_job(job_controller):
|
|
37
|
+
"""Test that clicking the print button starts a print job."""
|
|
38
|
+
# Setup - mock the Job class
|
|
39
|
+
mock_job = MagicMock()
|
|
40
|
+
mock_job.is_running = False
|
|
41
|
+
|
|
42
|
+
with patch("tinyprint.gui.Job", return_value=mock_job) as mock_job_class:
|
|
43
|
+
# Simulate clicking the print button
|
|
44
|
+
job_controller.btn.print.invoke()
|
|
45
|
+
|
|
46
|
+
# Verify a job was created with the correct config
|
|
47
|
+
mock_job_class.assert_called_once()
|
|
48
|
+
assert isinstance(mock_job_class.call_args[0][0], JobConfig)
|
|
49
|
+
|
|
50
|
+
# Verify the job was started
|
|
51
|
+
mock_job.start.assert_called_once()
|
|
52
|
+
assert job_controller.job == mock_job
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_stop_button_stops_job(job_controller):
|
|
56
|
+
"""Test that clicking the stop button stops a running print job."""
|
|
57
|
+
# Setup - create a mock job
|
|
58
|
+
mock_job = MagicMock()
|
|
59
|
+
mock_job.is_running = True
|
|
60
|
+
job_controller.job = mock_job
|
|
61
|
+
|
|
62
|
+
# Simulate clicking the stop button
|
|
63
|
+
job_controller.btn.stop.invoke()
|
|
64
|
+
|
|
65
|
+
# Verify the job was stopped and cleaned up
|
|
66
|
+
mock_job.stop.assert_called_once()
|
|
67
|
+
mock_job.close.assert_called_once()
|
|
68
|
+
assert job_controller.job is None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.fixture
|
|
72
|
+
def job_config_manager(gui_app):
|
|
73
|
+
"""Create a JobConfigManager instance for testing."""
|
|
74
|
+
gui_app.blk = MagicMock()
|
|
75
|
+
gui_app.blk.job_config_editor = MagicMock()
|
|
76
|
+
gui_app.blk.job_config_editor.sync_from_model = MagicMock()
|
|
77
|
+
gui_app.tempdir = tempfile.mkdtemp()
|
|
78
|
+
return JobConfigManager(gui_app)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_job_config_manager_initialization(job_config_manager):
|
|
82
|
+
"""Test that JobConfigManager initializes correctly."""
|
|
83
|
+
assert job_config_manager.job_config is not None
|
|
84
|
+
assert isinstance(job_config_manager.job_config, JobConfig)
|
|
85
|
+
assert len(job_config_manager.job_config.resource_list) == 1
|
|
86
|
+
assert "tmp-resource.md" in job_config_manager.job_config.resource_list[0].path
|
|
87
|
+
assert job_config_manager.filename is not None
|
|
88
|
+
assert job_config_manager.filename.endswith("tmp-print-job.json")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_job_config_manager_new_config(job_config_manager, tmp_path):
|
|
92
|
+
"""Test creating a new configuration."""
|
|
93
|
+
test_file = tmp_path / "test_config.json"
|
|
94
|
+
|
|
95
|
+
# Set up the save_as dialog to return our test file
|
|
96
|
+
with patch(
|
|
97
|
+
"tkinter.filedialog.asksaveasfilename", return_value=str(test_file)
|
|
98
|
+
) as mock_save_as:
|
|
99
|
+
# Simulate clicking the new button
|
|
100
|
+
job_config_manager.btn.new.invoke()
|
|
101
|
+
|
|
102
|
+
assert job_config_manager.filename == str(test_file)
|
|
103
|
+
assert test_file.exists()
|
|
104
|
+
|
|
105
|
+
# Verify the file contains valid JSON with expected structure
|
|
106
|
+
with open(test_file, "r") as f:
|
|
107
|
+
config_data = json.load(f)
|
|
108
|
+
assert "resource_list" in config_data
|
|
109
|
+
assert isinstance(config_data["resource_list"], list)
|
|
110
|
+
assert len(config_data["resource_list"]) == 1
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@patch("tkinter.filedialog.asksaveasfilename")
|
|
114
|
+
def test_job_config_manager_save_as(mock_save_as, job_config_manager, tmp_path):
|
|
115
|
+
"""Test saving configuration to a new file."""
|
|
116
|
+
test_file = tmp_path / "test_save_as.json"
|
|
117
|
+
mock_save_as.return_value = str(test_file)
|
|
118
|
+
|
|
119
|
+
# Simulate clicking the save_as button
|
|
120
|
+
job_config_manager.btn.save_as.invoke()
|
|
121
|
+
|
|
122
|
+
mock_save_as.assert_called_once()
|
|
123
|
+
assert job_config_manager.filename == str(test_file)
|
|
124
|
+
assert test_file.exists()
|
|
125
|
+
# Verify the filename label is updated
|
|
126
|
+
assert (
|
|
127
|
+
job_config_manager.lbl.filename["textvariable"]
|
|
128
|
+
== job_config_manager.var.filename_indicator._name
|
|
129
|
+
)
|
|
130
|
+
assert job_config_manager.var.filename_indicator.get() == "test_save_as.json"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@patch("tkinter.filedialog.askopenfilename")
|
|
134
|
+
def test_job_config_manager_open_config(mock_open_file, job_config_manager, tmp_path):
|
|
135
|
+
"""Test opening an existing configuration file."""
|
|
136
|
+
# Create a test config file
|
|
137
|
+
test_file = tmp_path / "test_open.json"
|
|
138
|
+
test_config = {
|
|
139
|
+
"resource_list": [{"path": "test.txt"}],
|
|
140
|
+
"printer_config": None,
|
|
141
|
+
"copy_count": 1,
|
|
142
|
+
"cut": True,
|
|
143
|
+
"orientation": 0, # 0 = Orientation.HORIZONTAL
|
|
144
|
+
"page_size": None,
|
|
145
|
+
"sleep_time": 0.5,
|
|
146
|
+
}
|
|
147
|
+
with open(test_file, "w") as f:
|
|
148
|
+
json.dump(test_config, f)
|
|
149
|
+
|
|
150
|
+
mock_open_file.return_value = str(test_file)
|
|
151
|
+
|
|
152
|
+
# Simulate clicking the open button
|
|
153
|
+
job_config_manager.btn.open.invoke()
|
|
154
|
+
|
|
155
|
+
mock_open_file.assert_called_once()
|
|
156
|
+
assert job_config_manager.filename == str(test_file)
|
|
157
|
+
assert job_config_manager.job_config is not None
|
|
158
|
+
assert len(job_config_manager.job_config.resource_list) == 1
|
|
159
|
+
assert job_config_manager.job_config.resource_list[0].path == "test.txt"
|
|
160
|
+
assert job_config_manager.job_config.sleep_time == 0.5
|
|
161
|
+
# Verify the filename label is updated
|
|
162
|
+
assert (
|
|
163
|
+
job_config_manager.lbl.filename["textvariable"]
|
|
164
|
+
== job_config_manager.var.filename_indicator._name
|
|
165
|
+
)
|
|
166
|
+
assert job_config_manager.var.filename_indicator.get() == "test_open.json"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_job_config_manager_reload_config(job_config_manager, tmp_path):
|
|
170
|
+
"""Test reloading the current configuration from disk."""
|
|
171
|
+
# Create a test config file
|
|
172
|
+
test_file = tmp_path / "test_reload.json"
|
|
173
|
+
test_config = {
|
|
174
|
+
"resource_list": [{"path": "reload.txt"}],
|
|
175
|
+
"orientation": 0, # 0 = Orientation.HORIZONTAL
|
|
176
|
+
"sleep_time": 0.3,
|
|
177
|
+
}
|
|
178
|
+
with open(test_file, "w") as f:
|
|
179
|
+
json.dump(test_config, f)
|
|
180
|
+
|
|
181
|
+
# Set the filename and reload
|
|
182
|
+
job_config_manager.set_filename(str(test_file))
|
|
183
|
+
# Simulate clicking the reload button
|
|
184
|
+
job_config_manager.btn.reload.invoke()
|
|
185
|
+
|
|
186
|
+
# Verify the config was loaded correctly
|
|
187
|
+
assert job_config_manager.job_config is not None
|
|
188
|
+
assert len(job_config_manager.job_config.resource_list) == 1
|
|
189
|
+
assert job_config_manager.job_config.resource_list[0].path == "reload.txt"
|
|
190
|
+
assert job_config_manager.job_config.sleep_time == 0.3
|
|
191
|
+
|
|
192
|
+
# Verify the editor was synced
|
|
193
|
+
job_config_manager.gui.blk.job_config_editor.sync_from_model.assert_called_once()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_job_config_manager_set_filename(job_config_manager):
|
|
197
|
+
"""Test updating the filename and UI indicator."""
|
|
198
|
+
test_path = "/path/to/test_config.json"
|
|
199
|
+
job_config_manager.set_filename(test_path)
|
|
200
|
+
|
|
201
|
+
assert job_config_manager.filename == test_path
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_job_config_editor_initialization(gui_app):
|
|
205
|
+
"""Test that JobConfigEditor initializes correctly."""
|
|
206
|
+
editor = gui_app.blk.job_config_editor
|
|
207
|
+
assert editor is not None
|
|
208
|
+
# Verify all expected variables are created
|
|
209
|
+
assert hasattr(editor.var, "resource_list")
|
|
210
|
+
assert hasattr(editor.var, "printer_config")
|
|
211
|
+
assert hasattr(editor.var, "sleep_time")
|
|
212
|
+
# Verify widgets are created
|
|
213
|
+
assert hasattr(editor.lbl, "resource_list")
|
|
214
|
+
assert hasattr(editor.lbl, "printer_value")
|
|
215
|
+
assert hasattr(editor.lbl, "sleep_value")
|
|
216
|
+
assert hasattr(editor.btn, "select_resource")
|
|
217
|
+
assert hasattr(editor.btn, "select_printer")
|
|
218
|
+
assert hasattr(editor.cbx, "cut")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@patch("tkinter.filedialog.askopenfilenames")
|
|
222
|
+
def test_select_resource(mock_file_dialog, gui_app):
|
|
223
|
+
"""Test selecting resources updates the job config and UI."""
|
|
224
|
+
editor = gui_app.blk.job_config_editor
|
|
225
|
+
test_files = ["/path/to/file1.pdf", "/path/to/file2.jpg"]
|
|
226
|
+
mock_file_dialog.return_value = test_files
|
|
227
|
+
editor.btn.select_resource.invoke()
|
|
228
|
+
mock_file_dialog.assert_called_once()
|
|
229
|
+
assert len(editor.job_config.resource_list) == 2
|
|
230
|
+
assert editor.job_config.resource_list[0].path == test_files[0]
|
|
231
|
+
assert editor.job_config.resource_list[1].path == test_files[1]
|
|
232
|
+
assert test_files[0].split("/")[-1] in editor.var.resource_list.get()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@patch("tkinter.filedialog.askopenfilename")
|
|
236
|
+
def test_select_printer_config(mock_file_dialog, gui_app):
|
|
237
|
+
"""Test selecting a printer config updates the job config and UI."""
|
|
238
|
+
editor = gui_app.blk.job_config_editor
|
|
239
|
+
test_file = "/path/to/printer.yml"
|
|
240
|
+
mock_file_dialog.return_value = test_file
|
|
241
|
+
|
|
242
|
+
# Simulate clicking the select printer button
|
|
243
|
+
editor.btn.select_printer.invoke()
|
|
244
|
+
|
|
245
|
+
# Get the actual call arguments
|
|
246
|
+
args, kwargs = mock_file_dialog.call_args
|
|
247
|
+
assert kwargs.get("title") == "Open a file"
|
|
248
|
+
assert kwargs.get("filetypes") == (
|
|
249
|
+
("printer config", "*.yml"),
|
|
250
|
+
("all files", "*.*"),
|
|
251
|
+
)
|
|
252
|
+
assert str(kwargs.get("initialdir")) == str(Path.home())
|
|
253
|
+
|
|
254
|
+
# Verify the printer config was updated
|
|
255
|
+
assert editor.job_config.printer_config == test_file
|
|
256
|
+
assert editor.var.printer_config.get() == test_file
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_toggle_cut(gui_app):
|
|
260
|
+
"""Test toggling the cut option updates the job config."""
|
|
261
|
+
editor = gui_app.blk.job_config_editor
|
|
262
|
+
initial_cut = editor.job_config.cut
|
|
263
|
+
|
|
264
|
+
# Set the combobox to the opposite of current cut state
|
|
265
|
+
new_value = "no" if initial_cut else "yes"
|
|
266
|
+
editor.cbx.cut.set(new_value)
|
|
267
|
+
|
|
268
|
+
# Trigger the combobox selection event
|
|
269
|
+
editor.cbx.cut.event_generate("<<ComboboxSelected>>")
|
|
270
|
+
|
|
271
|
+
# Verify the cut value has been toggled
|
|
272
|
+
assert editor.job_config.cut != initial_cut
|
|
273
|
+
assert editor.job_config.cut == (new_value == "yes")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_change_sleep_time(gui_app):
|
|
277
|
+
"""Test changing the sleep time updates the job config and UI."""
|
|
278
|
+
editor = gui_app.blk.job_config_editor
|
|
279
|
+
new_sleep_time = 1.5
|
|
280
|
+
editor.scl.sleep_time.set(new_sleep_time)
|
|
281
|
+
assert editor.job_config.sleep_time == new_sleep_time
|
|
282
|
+
assert editor.var.sleep_time.get() == new_sleep_time
|
|
283
|
+
assert float(editor.lbl.sleep_value["text"]) == new_sleep_time
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def test_sync_from_model(gui_app):
|
|
287
|
+
"""Test that sync_from_model updates the UI correctly."""
|
|
288
|
+
editor = gui_app.blk.job_config_editor
|
|
289
|
+
job_config_manager = editor.gui.blk.job_config_manager
|
|
290
|
+
|
|
291
|
+
# Create a test config
|
|
292
|
+
test_config = JobConfig(
|
|
293
|
+
resource_list=[Resource("test.txt")],
|
|
294
|
+
printer_config="test_printer.yml",
|
|
295
|
+
cut=True,
|
|
296
|
+
sleep_time=1.0,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Set the job config and sync
|
|
300
|
+
job_config_manager.job_config = test_config
|
|
301
|
+
editor.sync_from_model()
|
|
302
|
+
|
|
303
|
+
# Verify UI is in sync with the model
|
|
304
|
+
assert "test.txt" in editor.var.resource_list.get()
|
|
305
|
+
assert editor.var.printer_config.get() == "test_printer.yml"
|
|
306
|
+
assert editor.cbx.cut.get() == "yes"
|
|
307
|
+
assert editor.var.sleep_time.get() == 1.0
|
|
308
|
+
|
|
309
|
+
# Verify filename indicator if it exists
|
|
310
|
+
if hasattr(job_config_manager.var, "filename_indicator"):
|
|
311
|
+
# The filename should be the default one set in JobConfigManager.__init__
|
|
312
|
+
assert job_config_manager.var.filename_indicator.get() == "tmp-print-job.json"
|
|
@@ -13,7 +13,7 @@ from tinyprint.job import Job
|
|
|
13
13
|
from tinyprint.jobconfig import JobConfig, Resource, Orientation
|
|
14
14
|
|
|
15
15
|
# Test data directory
|
|
16
|
-
TEST_DATA = Path(__file__).parent / "data"
|
|
16
|
+
TEST_DATA = Path(__file__).absolute().parent / "data"
|
|
17
17
|
|
|
18
18
|
# Test configurations
|
|
19
19
|
RESOURCES = {
|
|
@@ -69,4 +69,4 @@ def test_print(snapshot, resource_path_list, config_kwargs):
|
|
|
69
69
|
job.start()
|
|
70
70
|
job.wait()
|
|
71
71
|
|
|
72
|
-
assert snapshot == job.manager.printer.output
|
|
72
|
+
assert snapshot == job.manager.printer.output, resource_path_list
|
|
@@ -224,7 +224,10 @@ class JobConfigEditor(Block):
|
|
|
224
224
|
)
|
|
225
225
|
|
|
226
226
|
def select_file(self, attr, filetypes, multiple=False):
|
|
227
|
-
|
|
227
|
+
file_list = super().select_file(filetypes, multiple)
|
|
228
|
+
if multiple is False:
|
|
229
|
+
file_list = [file_list] if file_list else []
|
|
230
|
+
if file_list:
|
|
228
231
|
setattr(
|
|
229
232
|
self.job_config,
|
|
230
233
|
attr,
|
|
@@ -4,6 +4,7 @@ import abc
|
|
|
4
4
|
from functools import cached_property
|
|
5
5
|
import mimetypes
|
|
6
6
|
import os
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
from tempfile import TemporaryDirectory
|
|
8
9
|
from typing import Callable, TypeAlias, Optional
|
|
9
10
|
|
|
@@ -31,26 +32,26 @@ def create_pages_from_resources(config, printer_profile) -> tuple[Page, ...]:
|
|
|
31
32
|
def get_preprocessor_for_resource(resource: Resource) -> type[Preprocessor]:
|
|
32
33
|
"""Return the appropriate preprocessor class for the given resource."""
|
|
33
34
|
path = resource.path
|
|
34
|
-
if
|
|
35
|
+
if _is_text_file(path):
|
|
36
|
+
return MarkdownPreprocessor
|
|
37
|
+
else:
|
|
35
38
|
m = mimetypes.guess_type(path)
|
|
36
39
|
if m[0]:
|
|
37
40
|
if m[0].startswith("image"):
|
|
38
41
|
return ImagePreprocessor
|
|
39
42
|
elif m[0] == "application/pdf":
|
|
40
43
|
return PdfPreprocessor
|
|
41
|
-
else:
|
|
42
|
-
return MarkdownPreprocessor
|
|
43
44
|
raise NotImplementedError(f"can't guess type of {path}")
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
# helper
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
def _is_text_file(path, blocksize=4096):
|
|
49
|
+
try:
|
|
50
|
+
with open(path, "rb") as f:
|
|
51
|
+
f.read(blocksize).decode("utf-8")
|
|
52
|
+
return True
|
|
53
|
+
except UnicodeDecodeError:
|
|
54
|
+
return False
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
class Preprocessor(abc.ABC):
|
|
@@ -144,6 +145,7 @@ class MarkdownPreprocessor(Preprocessor):
|
|
|
144
145
|
md = f.read()
|
|
145
146
|
|
|
146
147
|
def _(printer):
|
|
148
|
+
self.markdown.renderer.set_markdown_path(resource.path)
|
|
147
149
|
self.markdown.renderer.set_printer(printer)
|
|
148
150
|
self.markdown.renderer.set_config(self.config)
|
|
149
151
|
self.markdown.renderer.set_printer_profile(self.printer_profile)
|
|
@@ -192,7 +194,8 @@ class _EscposRenderer(marko.renderer.Renderer):
|
|
|
192
194
|
return ""
|
|
193
195
|
|
|
194
196
|
def render_image(self, element) -> str:
|
|
195
|
-
|
|
197
|
+
path = _resolve_path(element.dest, self._markdown_path)
|
|
198
|
+
resource = Resource(path=path)
|
|
196
199
|
get_image_page(resource, self._config, self._printer_profile)(self._printer)
|
|
197
200
|
return ""
|
|
198
201
|
|
|
@@ -241,6 +244,9 @@ class _EscposRenderer(marko.renderer.Renderer):
|
|
|
241
244
|
def set_printer_profile(self, printer_profile):
|
|
242
245
|
self._printer_profile = printer_profile
|
|
243
246
|
|
|
247
|
+
def set_markdown_path(self, markdown_path):
|
|
248
|
+
self._markdown_path = markdown_path
|
|
249
|
+
|
|
244
250
|
def _set(self, *args, **kwargs):
|
|
245
251
|
if self._printer:
|
|
246
252
|
self._printer.set(*args, **kwargs)
|
|
@@ -273,3 +279,13 @@ class _EscposRenderer(marko.renderer.Renderer):
|
|
|
273
279
|
@max_char_count.setter
|
|
274
280
|
def max_char_count(self, max_char_count: int):
|
|
275
281
|
self._max_char_count = max_char_count
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _resolve_path(path: str, markdown_path: str) -> str:
|
|
285
|
+
p = Path(path)
|
|
286
|
+
|
|
287
|
+
if p.is_absolute():
|
|
288
|
+
return path
|
|
289
|
+
|
|
290
|
+
markdown_dir = Path(markdown_path).parent
|
|
291
|
+
return str((markdown_dir / p).resolve())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinyprint
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: ui & cli to print zines with esc/pos printer
|
|
5
5
|
Author-email: Levin Eric Zimmermann <levin.eric.zimmermann@posteo.com>
|
|
6
6
|
License: GPL
|
|
@@ -23,3 +23,31 @@ Dynamic: license-file
|
|
|
23
23
|
# tinyprint
|
|
24
24
|
|
|
25
25
|
Software for printing tiny zines with ESC/POS printer.
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
### MacOS
|
|
32
|
+
|
|
33
|
+
Open a console and type:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)
|
|
37
|
+
brew install python
|
|
38
|
+
brew install python-tk
|
|
39
|
+
brew install pipx
|
|
40
|
+
pipx install tinyprint
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Linux
|
|
44
|
+
|
|
45
|
+
Install python, and then just use pip:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip3 install tinyprint
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Windows
|
|
52
|
+
|
|
53
|
+
I don't know, but if someone manages, please let us know.
|
tinyprint-0.2.2/README.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|