Source code for lookatme.pres

"""
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 get_slide_scroll_lines(self, width: int, height: int) -> int: """Return the number of lines that need to be scrolled for each slide""" if self.tui is None: raise RuntimeError("Tui was none") # make the slide_body.last_size get cached via the scrollbar # this ensures that we're using the correct width/height for the slide # body self.tui.render((width, height), True) slide_size = self.tui.slide_body.body._last_size num_lines = self.tui.get_num_slide_body_lines(slide_size) if num_lines <= height: scroll_lines = 0 else: scroll_lines = num_lines - slide_size[1] return scroll_lines
[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