Repository: d0c-s4vage/lookatme

Issue:
Add new cmd-line option to force single slide rendering
**Describe the Feature Request**

A single slide should be rendered if `--single` or `--one` (or something) is specified. This also means that hrules should be actually rendered as well.

See #91 and #29 for some more context.

Discussion:

Patch:
diff --git a/README.md b/README.md
index 9302700..c1a1e0b 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ Sourcing external files example
 ## CLI Options
 
 ```
-Usage: lookatme [OPTIONS] [INPUT_FILE]
+Usage: lookatme [OPTIONS] [INPUT_FILES]...
 
   lookatme - An interactive, terminal-based markdown presentation tool.
 
@@ -67,11 +67,15 @@ Options:
   --debug
   -l, --log PATH
   -t, --theme [dark|light]
-  -s, --style [default|emacs|friendly|colorful|autumn|murphy|manni|monokai|perldoc|pastie|borland|trac|native|fruity|bw|vim|vs|tango|rrt|xcode|igor|paraiso-light|paraiso-dark|lovelace|algol|algol_nu|arduino|rainbow_dash|abap|solarized-dark|solarized-light|sas|stata|stata-light|stata-dark]
+  -s, --style [default|emacs|friendly|colorful|autumn|murphy|manni|monokai|perldoc|pastie|borland|trac|native|fruity|bw|vim|vs|tango|rrt|xcode|igor|paraiso-light|paraiso-dark|lovelace|algol|algol_nu|arduino|rainbow_dash|abap|solarized-dark|solarized-light|sas|stata|stata-light|stata-dark|inkpot]
   --dump-styles                   Dump the resolved styles that will be used
                                   with the presentation to stdout
+
   --live, --live-reload           Watch the input filename for modifications
                                   and automatically reload
+
+  --single, --one                 Render the source as a single slide
+  --version                       Show the version and exit.
   --help                          Show this message and exit.
 ```
 
diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst
index 8f0f295..c63ccc6 100644
--- a/docs/source/getting_started.rst
+++ b/docs/source/getting_started.rst
@@ -20,20 +20,24 @@ The ``lookatme`` CLI has a few options to control it's behavior:
 
 .. code-block:: text
 
-    Usage: lookatme [OPTIONS] [INPUT_FILE]
+   Usage: lookatme [OPTIONS] [INPUT_FILES]...
 
-      lookatme - An interactive, terminal-based markdown presentation tool.
+     lookatme - An interactive, terminal-based markdown presentation tool.
 
-    Options:
-      --debug
-      -l, --log PATH
-      -t, --theme [dark|light]
-      -s, --style [default|emacs|friendly|colorful|autumn|murphy|manni|monokai|perldoc|pastie|borland|trac|native|fruity|bw|vim|vs|tango|rrt|xcode|igor|paraiso-light|paraiso-dark|lovelace|algol|algol_nu|arduino|rainbow_dash|abap|solarized-dark|solarized-light|sas|stata|stata-light|stata-dark]
-      --dump-styles                   Dump the resolved styles that will be used
-                                      with the presentation to stdout
-      --live, --live-reload           Watch the input filename for modifications
-                                      and automatically reload
-      --help                          Show this message and exit.
+   Options:
+     --debug
+     -l, --log PATH
+     -t, --theme [dark|light]
+     -s, --style [default|emacs|friendly|colorful|autumn|murphy|manni|monokai|perldoc|pastie|borland|trac|native|fruity|bw|vim|vs|tango|rrt|xcode|igor|paraiso-light|paraiso-dark|lovelace|algol|algol_nu|arduino|rainbow_dash|abap|solarized-dark|solarized-light|sas|stata|stata-light|stata-dark|inkpot]
+     --dump-styles                   Dump the resolved styles that will be used
+                                     with the presentation to stdout
+
+     --live, --live-reload           Watch the input filename for modifications
+                                     and automatically reload
+
+     --single, --one                 Render the source as a single slide
+     --version                       Show the version and exit.
+     --help                          Show this message and exit.
 
 ``--live`` / ``--live-reload``
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -50,6 +54,12 @@ are possible:
   :width: 800
   :alt: Live Updates
 
+``--single`` / ``--one``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Render the markdown source as a single slide, ignoring all hrules. Scroll
+overflowing slides with the up/down arrow keys and page up/page down.
+
 ``--debug`` and ``--log``
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/docs/source/style_precendence.rst b/docs/source/style_precendence.rst
index b44856e..ade6027 100644
--- a/docs/source/style_precendence.rst
+++ b/docs/source/style_precendence.rst
@@ -105,6 +105,11 @@ The default styles and formats are defined in the marshmallow schemas in
        fg: '#346,bold'
        prefix: '░░░░░ '
        suffix: ''
+   hrule:
+     char: ─
+     style:
+       bg: default
+       fg: '#777'
    link:
      bg: default
      fg: '#33c,underline'
diff --git a/lookatme/__main__.py b/lookatme/__main__.py
index fafd6d2..641138c 100644
--- a/lookatme/__main__.py
+++ b/lookatme/__main__.py
@@ -58,13 +58,21 @@ from lookatme.schemas import StyleSchema
     is_flag=True,
     default=False,
 )
+@click.option(
+    "--single",
+    "--one",
+    "single_slide",
+    help="Render the source as a single slide",
+    is_flag=True,
+    default=False
+)
 @click.version_option(lookatme.__version__)
 @click.argument(
     "input_files",
     type=click.File("r"),
     nargs=-1,
 )
-def main(debug, log_path, theme, code_style, dump_styles, input_files, live_reload):
+def main(debug, log_path, theme, code_style, dump_styles, input_files, live_reload, single_slide):
     """lookatme - An interactive, terminal-based markdown presentation tool.
     """
     if debug:
@@ -74,7 +82,13 @@ def main(debug, log_path, theme, code_style, dump_styles, input_files, live_relo
 
     if len(input_files) == 0:
         input_files = [io.StringIO("")]
-    pres = Presentation(input_files[0], theme, code_style, live_reload=live_reload)
+    pres = Presentation(
+        input_files[0],
+        theme,
+        code_style,
+        live_reload=live_reload,
+        single_slide=single_slide
+    )
 
     if dump_styles:
         print(StyleSchema().dumps(pres.styles))
diff --git a/lookatme/parser.py b/lookatme/parser.py
index 43f9a1d..c222df3 100644
--- a/lookatme/parser.py
+++ b/lookatme/parser.py
@@ -18,9 +18,10 @@ class Parser(object):
     """A parser for markdown presentation files
     """
 
-    def __init__(self):
+    def __init__(self, single_slide=False):
         """Create a new Parser instance
         """
+        self._single_slide = single_slide
 
     def parse(self, input_data):
         """Parse the provided input data into a Presentation object
@@ -47,7 +48,12 @@ class Parser(object):
 
         num_hrules, hinfo = self._scan_for_smart_split(tokens)
 
-        if num_hrules == 0:
+        if self._single_slide:
+            def slide_split_check(token):
+                False
+            def heading_mod(token):
+                pass
+        elif num_hrules == 0:
             if meta["title"] in ["", None]:
                 meta["title"] = hinfo["title"]
             def slide_split_check(token):
diff --git a/lookatme/pres.py b/lookatme/pres.py
index 5e973df..d086cbf 100644
--- a/lookatme/pres.py
+++ b/lookatme/pres.py
@@ -20,7 +20,8 @@ from lookatme.utils import dict_deep_update
 class Presentation(object):
     """Defines a presentation
     """
-    def __init__(self, input_stream, theme, style_override=None, live_reload=False):
+    def __init__(self, input_stream, theme, style_override=None, live_reload=False,
+                 single_slide=False):
         """Creates a new Presentation
 
         :param stream input_stream: An input stream from which to read the
@@ -34,6 +35,7 @@ class Presentation(object):
         self.style_override = style_override
         self.live_reload = live_reload
         self.tui = None
+        self.single_slide = single_slide
 
         self.theme_mod = __import__("lookatme.themes." + theme, fromlist=[theme])
 
@@ -73,7 +75,7 @@ class Presentation(object):
             with open(self.input_filename, "r") as f:
                 data = f.read()
 
-        parser = Parser()
+        parser = Parser(single_slide=self.single_slide)
         self.meta, self.slides = parser.parse(data)
         lookatme.contrib.load_contribs(self.meta.get("extensions", []))
 
diff --git a/lookatme/render/markdown_block.py b/lookatme/render/markdown_block.py
index 4eee44f..4187ea4 100644
--- a/lookatme/render/markdown_block.py
+++ b/lookatme/render/markdown_block.py
@@ -63,6 +63,18 @@ def render_newline(token, body, stack, loop):
     return urwid.Divider()
 
 
+@contrib_first
+def render_hrule(token, body, stack, loop):
+    """Render a newline
+
+    See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
+    value descriptions.
+    """
+    hrule_conf = config.STYLE["hrule"]
+    div = urwid.Divider(hrule_conf['char'], top=1, bottom=1)
+    return urwid.Pile([urwid.AttrMap(div, spec_from_style(hrule_conf['style']))])
+
+
 @contrib_first
 def render_heading(token, body, stack, loop):
     """Render markdown headings, using the defined styles for the styling and
diff --git a/lookatme/schemas.py b/lookatme/schemas.py
index f411d29..4b44aa3 100644
--- a/lookatme/schemas.py
+++ b/lookatme/schemas.py
@@ -71,6 +71,7 @@ class NumberingSchema(Schema):
             "10": fields.Str(validate=_NUMBERING_VALIDATION),
         }
 
+
 class StyleFieldSchema(Schema):
     fg = fields.Str(default="")
     bg = fields.Str(default="")
@@ -83,6 +84,14 @@ class HeadingStyleSchema(Schema):
     bg = fields.Str(default="")
 
 
+class HruleSchema(Schema):
+    char = fields.Str(default="─")
+    style = fields.Nested(StyleFieldSchema, default=StyleFieldSchema().dump({
+        "fg": "#777",
+        "bg": "default",
+    }))
+
+
 class BlockQuoteSchema(Schema):
     side = fields.Str(default="╎")
     top_corner = fields.Str(default="┌")
@@ -170,6 +179,7 @@ class StyleSchema(Schema):
     numbering = fields.Nested(NumberingSchema, default=NumberingSchema().dump(NumberingSchema()))
     table = fields.Nested(TableSchema, default=TableSchema().dump(TableSchema()))
     quote = fields.Nested(BlockQuoteSchema, default=BlockQuoteSchema().dump(BlockQuoteSchema()))
+    hrule = fields.Nested(HruleSchema, default=HruleSchema().dump(HruleSchema()))
     link = fields.Nested(StyleFieldSchema, default={
         "fg": "#33c,underline",
         "bg": "default",
diff --git a/lookatme/themes/light.py b/lookatme/themes/light.py
index f83a1aa..fa01296 100644
--- a/lookatme/themes/light.py
+++ b/lookatme/themes/light.py
@@ -23,6 +23,12 @@ theme = {
             "bg":"default",
         },
     },
+    "hrule": {
+        "style": {
+            "fg": "#555",
+            "bg": "default",
+        }
+    },
     "link": {
         "fg": "#22f,underline,bold",
         "bg": "default",


Test patch:
diff --git a/tests/test_markdown.py b/tests/test_markdown.py
index 8ef7e79..6cf8ebb 100644
--- a/tests/test_markdown.py
+++ b/tests/test_markdown.py
@@ -222,6 +222,30 @@ def test_numbered_lists(mocker):
     assert_render(stripped_rows, rendered)
 
 
+def test_hrule(mocker):
+    """Test that hrules render correctly
+    """
+    mocker.patch.object(lookatme.config, "LOG")
+    fake_config = mocker.patch.object(lookatme.render.markdown_block, "config")
+    fake_config.STYLE = {
+        "hrule": {
+            "style": {
+                "fg": "",
+                "bg": "",
+            },
+            "char": "=",
+        },
+    }
+
+    rendered = render_markdown("---", width=10, single_slide=True)
+    stripped_rows = [
+        b'',
+        b'==========',
+        b'',
+    ]
+    assert_render(stripped_rows, rendered)
+
+
 def test_block_quote(mocker):
     """Test block quote rendering
     """
diff --git a/tests/utils.py b/tests/utils.py
index cf791ad..c245b30 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -23,14 +23,14 @@ def assert_render(correct_render, rendered, full_strip=False):
             assert correct_render[idx] == stripped
 
 
-def render_markdown(markdown, height=50):
+def render_markdown(markdown, height=50, width=200, single_slide=False):
     """Returns the rendered canvas contents of the markdown
     """
     loop = urwid.MainLoop(urwid.ListBox([]))
     renderer = lookatme.tui.SlideRenderer(loop)
     renderer.start()
 
-    parser = Parser()
+    parser = Parser(single_slide=single_slide)
     _, slides = parser.parse_slides({"title": ""}, markdown)
 
     renderer.stop()
@@ -39,7 +39,7 @@ def render_markdown(markdown, height=50):
 
     container = urwid.ListBox([urwid.Text("testing")])
     container.body = contents
-    return list(container.render((200,height)).content())
+    return list(container.render((width,height)).content())
 
 
 def spec_and_text(item):