Skip to content

Commit

Permalink
Watch also monitors loaded images
Browse files Browse the repository at this point in the history
  • Loading branch information
spirali committed May 18, 2024
1 parent dbbb9c9 commit 28098b9
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 13 deletions.
4 changes: 3 additions & 1 deletion docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@
deck.render("slides.pdf")
```
* Run `python slides.py`. It creates file `slides.pdf`.
* Run `python slides.py`. It creates file `slides.pdf`.
If you do not want to build your script manually, you can use [Automatic slide rebuilding](guide/watch.md)
13 changes: 13 additions & 0 deletions docs/guide/watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Slides rebuilding

Nelsie can automatically rebuild slides when the source Python file is changed or when images used in slides are
changed.
It is started by the following command (lets assume that our slides are defined in `slides.py`).

```commandline
$ python3 -m nelsie watch slides.py
```

Nelsie builds the slides and starts to watch `slides.py` and used images and rebuilds the slides when relevant files are
changed.
Note that if the first build fails, the watch is not started.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ nav:
- guide/box.md
- guide/colors.md
- guide/list.md
- guide/watch.md

theme:
name: material
Expand Down
12 changes: 9 additions & 3 deletions python/nelsie/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import importlib
import traceback
from . import nelsie as nelsie_rs
from . import watch


def parse_args():
Expand All @@ -13,20 +14,25 @@ def parse_args():


def load_py_file(filename):
watch._WATCH_SET = set()
importlib.machinery.SourceFileLoader("slides", filename).load_module()
s = watch._WATCH_SET
watch._WATCH_SET = None
s.add(filename)
return list(s)


def main():
args = parse_args()
source_filename = args.source_filename
print("Initial build", source_filename)
load_py_file(source_filename)
files = load_py_file(source_filename)
print("Watching", source_filename)
while True:
nelsie_rs.watch(source_filename)
nelsie_rs.watch(files)
print("Building ...", source_filename)
try:
load_py_file(source_filename)
files = load_py_file(source_filename)
print("... finished")
except Exception:
traceback.print_exc()
Expand Down
6 changes: 5 additions & 1 deletion python/nelsie/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .layoutexpr import LayoutExpr
from .shapes import Path
from .textstyle import TextStyle, _data_to_text_style
from .watch import watch_path


@dataclass
Expand Down Expand Up @@ -95,11 +96,14 @@ def image(self, path: str, enable_steps=True, shift_steps=0, **box_args):
Create a box with an image. Supported formats: SVG, PNG, JPEG, GIF, ORA
"""
assert shift_steps >= 0

slide = self.get_box().slide
if slide.image_directory is not None:
path = os.path.join(slide.image_directory, path)
path = os.path.abspath(path)
watch_path(path)
image = ImageContent(
path=os.path.abspath(path),
path=path,
enable_steps=enable_steps,
shift_steps=shift_steps,
)
Expand Down
6 changes: 6 additions & 0 deletions python/nelsie/watch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
_WATCH_SET: None | set = None


def watch_path(path):
if _WATCH_SET is not None:
_WATCH_SET.add(path)
11 changes: 6 additions & 5 deletions src/pyinterface/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ use std::thread::sleep;
use std::time::Duration;

#[pyfunction]
pub fn watch(py: Python<'_>, path: &str) -> PyResult<()> {
pub fn watch(py: Python<'_>, paths: Vec<String>) -> PyResult<()> {
let (tx, rx) = std::sync::mpsc::channel();

let mut watcher = RecommendedWatcher::new(tx, Config::default())
.map_err(|e| PyException::new_err(e.to_string()))?;

watcher
.watch(path.as_ref(), RecursiveMode::NonRecursive)
.map_err(|e| PyException::new_err(e.to_string()))?;

for path in paths {
watcher
.watch(path.as_ref(), RecursiveMode::NonRecursive)
.map_err(|e| PyException::new_err(e.to_string()))?;
}
loop {
py.check_signals()?;
if let Ok(Ok(event)) = rx.recv_timeout(Duration::from_secs(1)) {
Expand Down
24 changes: 21 additions & 3 deletions tests/test_reload.py → tests/test_watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
import sys
import time
import hashlib
import shutil
import os
from conftest import ASSETS_DIR


def test_reload(tmp_path):
def test_watch(tmp_path):
def copy_image(name):
shutil.copy(str(os.path.join(ASSETS_DIR, name)), str(tmp_path.joinpath("test.jpeg")))

def write(content):
tmp_path.joinpath("slides.py").write_text(
f"""
from nelsie import SlideDeck
deck = SlideDeck()
@deck.slide()
def test(slide):
{content}
Expand All @@ -24,17 +31,28 @@ def make_hash():
return hashlib.md5(f.read())

with change_workdir(tmp_path):
copy_image("testimg.jpeg")
write("slide.box(width=100, height=100, bg_color='green')")
p = subprocess.Popen([sys.executable, "-m", "nelsie", "watch", "slides.py"])
time.sleep(1.0)

hash1 = make_hash()
assert p.poll() is None

write("slide.box(width=100, height=100, bg_color='blue')")
copy_image("testimg.png")
time.sleep(1.0)
hash2 = make_hash()
assert hash1 != hash2
assert hash1.hexdigest() == hash2.hexdigest()

write("slide.image('test.jpeg', width=100, height=100)")
time.sleep(1.0)
hash3 = make_hash()
assert hash1.hexdigest() != hash3.hexdigest()

copy_image("testimg.jpeg")
time.sleep(1.0)
hash4 = make_hash()
assert hash3.hexdigest() != hash4.hexdigest()

assert p.poll() is None
p.kill()
Expand Down

0 comments on commit 28098b9

Please sign in to comment.