"""
Defines Presentation specific objects
"""
import os
import threading
import time
from typing import IO, Optional
from six.moves import StringIO # type: ignore
import lookatme.ascii_art
import lookatme.config
import lookatme.contrib
import lookatme.prompt
import lookatme.themes
import lookatme.tui
from lookatme.parser import Parser
from lookatme.tutorial import tutor
DEFAULT_HTML_OPTIONS = {
"width": 100,
"height": 30,
"title_delim": ":",
"raw_render": False,
"raw_render_keys": ["show-all"],
"render_images": True,
}
[docs]@tutor(
"general",
"intro",
r"""
`lookatme` is a terminal-based markdown presentation tool.
That means that you can:
| Write | Render | Use |
|-----------------------|-----------------|--------------------------------|
| basic markdown slides | in the terminal | anywhere with markdown support |
Press `l`, `j`, or `right arrow` to go to the next slide, or `q` to quit.
""",
order=0,
)
class Presentation(object):
"""Defines a presentation"""
def __init__(
self,
input_stream: Optional[IO] = None,
input_str: Optional[str] = None,
theme: str = "dark",
live_reload=False,
single_slide=False,
preload_extensions=None,
safe=False,
no_ext_warn=False,
ignore_ext_failure=False,
no_threads=False,
):
"""Creates a new Presentation
:param stream input_stream: An input stream from which to read the
slide data
"""
if input_stream is None and input_str is None:
raise ValueError(
"Cannot create presentation without input_stream or input_str"
)
if input_stream is None:
input_stream = StringIO(input_str)
self.preload_extensions = preload_extensions or []
self.input_filename = None
if hasattr(input_stream, "name"):
lookatme.config.SLIDE_SOURCE_DIR = os.path.dirname(input_stream.name)
self.input_filename = input_stream.name
self.live_reload = live_reload
self.tui = None
self.single_slide = single_slide
self.safe = safe
self.no_ext_warn = no_ext_warn
self.ignore_ext_failure = ignore_ext_failure
self.initial_load_complete = False
self.no_threads = no_threads
self.cli_theme = theme
if self.live_reload:
self.reload_thread = threading.Thread(target=self.reload_watcher)
self.reload_thread.daemon = True
self.reload_thread.start()
self.reload(data=input_stream.read())
self.initial_load_complete = True
[docs] def reload_watcher(self):
"""Watch for changes to the input filename, automatically reloading
when the modified time has changed.
"""
if self.input_filename is None:
return
last_mod_time = os.path.getmtime(self.input_filename)
while True:
try:
curr_mod_time = os.path.getmtime(self.input_filename)
if curr_mod_time != last_mod_time:
self.get_tui().reload()
self.get_tui().loop.draw_screen()
last_mod_time = curr_mod_time
except Exception:
pass
finally:
time.sleep(0.25)
[docs] def reload(self, data=None):
"""Reload this presentation
:param str data: The data to render for this slide deck (optional)
"""
if data is None:
with open(str(self.input_filename), "r") as f:
data = f.read()
parser = Parser(single_slide=self.single_slide)
self.meta, self.slides, self.no_meta_source = parser.parse(data)
# only load extensions once! Live editing does not support
# auto-extension reloading
if not self.initial_load_complete:
safe_exts = set(self.preload_extensions)
new_exts = set()
# only load if running with safe=False
if not self.safe:
source_exts = set(self.meta.get("extensions", []))
new_exts = source_exts - safe_exts
self.warn_exts(new_exts)
all_exts = safe_exts | new_exts
lookatme.contrib.load_contribs(
all_exts,
safe_exts,
self.ignore_ext_failure,
)
theme = self.meta.get("theme", self.cli_theme or "dark")
self.theme_mod = __import__("lookatme.themes." + theme, fromlist=[theme])
self.styles = lookatme.config.set_global_style_with_precedence(
self.theme_mod,
self.meta.get("styles", {}),
)
self.initial_load_complete = True
[docs] def warn_exts(self, exts):
"""Warn about source-provided extensions that are to-be-loaded"""
if len(exts) == 0 or self.no_ext_warn:
return
warning = lookatme.ascii_art.WARNING
print("\n".join([" " + x for x in warning.split("\n")]))
print(
"New extensions required by {!r} are about to be loaded:\n".format(
self.input_filename
)
)
for ext in exts:
print(" - {!r}".format("lookatme.contrib." + ext))
print("")
if not lookatme.prompt.yes("Are you ok with attempting to load them?"):
print("Bailing due to unacceptance of source-required extensions")
exit(1)
[docs] def tui_init_sync(self):
if self.tui is None:
self.tui = lookatme.tui.create_tui(self, start_slide=0, no_threads=True)
self.tui.update()
[docs] def run(self, start_slide=0):
"""Run the presentation!"""
self.tui = lookatme.tui.create_tui(
self, start_slide=start_slide, no_threads=self.no_threads
)
self.tui.run()
[docs] def get_tui(self) -> lookatme.tui.MarkdownTui:
if self.tui is None:
raise ValueError("Tui has not been set, has the presentation been run yet?")
return self.tui