Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
set -e

cat <<EOF >mymath.py
def f(x,y):
return x*x + y*y
EOF
cat <<EOF >lib.py
import mymath
print("Result: {}".format(mymath.f(3,4)))
EOF
touch -t 200711121015 lib.py mymath.py

printf 'lib.py\nmymath.py\n' | python3 -u ./hotload.py lib.py > output &
pid="$!"

sleep 0.1
sed -i.bak 's/f(3,4)/f(4,4)/' lib.py
sleep 0.1
sed -i.bak 's/return /return 1 + /' mymath.py
sleep 0.1
kill "$pid"

strings output | grep -o -m1 'Successfully reloaded lib'
strings output | grep -o -m1 'Result: 25$'
strings output | grep -o -m1 'Result: 32$'
strings output | grep -o -m1 'Result: 33$'

15 changes: 15 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: test

on:
push

jobs:
shelltest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- run: sh -x .github/workflows/test.sh

36 changes: 33 additions & 3 deletions hotload.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ def _file_changed(f):
def _all_file_changes(filepaths):
return {path: _file_changed(path) for path in filepaths}

def _changed_modules(new_changed, last_changed):
list = []
if last_changed is None:
return list
modules = {}
for module in sys.modules.copy().values():
file = getattr(module, '__file__', None)
if file is not None:
modules[file] = module
for path in new_changed:
if new_changed[path] != last_changed[path]:
module = modules.get(path, None)
if module is not None:
list.append(module)
return list

def listfiles(folder, ext=""):
fs = list()
Expand Down Expand Up @@ -135,6 +150,13 @@ class ClearTerminal(Runnable):
def run(self):
os.system("cls" if os.name == "nt" else "clear")

class ReloadModules(Runnable):
def __init__(self, module):
self.main_module = module
def run(self, modules):
for module in modules:
if module != self.main_module:
_reload_module(module)

def hotload(watch, steps, waittime_ms=1.0 / 144):
"""Hotload that code!"""
Expand All @@ -148,18 +170,25 @@ def hotload(watch, steps, waittime_ms=1.0 / 144):
# Take note of when files were last changed before we start reloading
last_changed = None

is_module_reloader = lambda step: type(step).__name__ == "ReloadModules"
do_reload_modules = bool(list(filter(is_module_reloader, steps)))

# Begin the loop! Each Runner is responsible for handling its own exceptions.
while True:
new_changed = _all_file_changes(watchfiles)
if last_changed == new_changed:
time.sleep(waittime_ms)
else:
reload_begin_ms = time.time() * 1000
changed_modules = _changed_modules(new_changed, last_changed) if do_reload_modules else []
last_changed = new_changed
try:
for step in steps:
try:
step.run()
if is_module_reloader(step):
step.run(changed_modules)
else:
step.run()
except KeyboardInterrupt:
raise
except:
Expand Down Expand Up @@ -210,7 +239,7 @@ def main():
print()
print(" ls *py | hotload hello.py")

watchfiles = [f.strip() for f in sys.stdin.readlines()]
watchfiles = [os.path.normpath(os.path.join(os.getcwd(), f.strip())) for f in sys.stdin.readlines()]

if not watchfiles:
print("Error: no watch files specified.")
Expand All @@ -227,7 +256,7 @@ def main():

conf = {
"watch": [watchfiles],
"steps": [ClearTerminal(), reloaded_module],
"steps": [ClearTerminal(), ReloadModules(reloaded_module.module), reloaded_module],
}

hotload(**conf)
Expand All @@ -237,3 +266,4 @@ def main():

if __name__ == "__main__":
main()