Files
q-tools/py-packages/markslider/markslider/markslider.py
2024-10-07 15:49:49 +03:00

1022 lines
32 KiB
Python
Executable File

#!/usr/bin/env python3
# coding=utf-8
#
# Copyright 2016 Ville Rantanen
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""Markslider: a slideshow engine based on markdown."""
__author__ = "Ville Rantanen <ville.q.rantanen@gmail.com>"
__version__ = "1.3.2"
import sys, os, argparse, re, datetime
from argparse import ArgumentParser
import traceback, tty, termios, subprocess, signal
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
import ansicodes, md_color
try:
import climage
except ImportError:
pass
HL = ">"
EOS = "# End of Slides"
class getch:
def get(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class EndProgram(Exception):
"""Nice exit"""
pass
class slide_reader:
"""Class for reading files."""
def __init__(self, files, opts):
self.filename = files[0]
self.files = files
self.reader = None
self.opts = opts
self.scrsize = get_console_size()
self.pages = 0
self.page = 0
self.file_start_page = []
self.width = None
self.height = None
self.max_width = None
self.max_height = None
self.data = []
self.re_image_convert = re.compile(r"(.*)(!\[.*\])\((.*)\)>")
self.re_command = re.compile(r"(.*)`(.*)`>(.*)")
# ~ self.control_chars = ''.join(map(unichr, range(0,32) + range(127,160)))
# ~ self.control_char_re = re.compile('[%s]' % re.escape(self.control_chars))
self.background = []
self.read()
self.pygments = False
if opts.syntax_pygments:
try:
self.pygments = Pygmentizer(opts.syntax_formatter, opts.syntax_style)
except ImportError as e:
self.pygments = False
def read(self):
"""Read a file, set pages and data"""
self.pages = 0
self.background = []
self.data = []
self.file_start_page = []
first_slide_found = False
for fname in self.files:
first_slide_found = False
f = open(fname, "r")
new_page = []
in_code = False
for row in f:
if not row:
continue
row = row.rstrip("\n\r ")
# find end of show
if row == EOS:
break
# find header to start a new page
if row.startswith("#") and not row.startswith("##") and not in_code:
first_slide_found = True
if len(new_page) > 0:
self.data.append(new_page)
new_page = []
if row.startswith("```"):
in_code = not in_code
# if first slide havent been found yet:
if not first_slide_found:
self.background.append(row)
continue
new_page.extend(self.generate_content(row))
if len(new_page) > 0:
self.data.append(new_page)
f.close()
self.file_start_page.append(len(self.data))
if len(self.data) == 0:
raise ValueError("File does not have a # header")
self.rename_duplicates()
self.toc()
self.pages = len(self.data)
self.inc_page_no(0)
self.max_width = 0
self.max_height = 0
for page in self.data:
self.max_height = max(self.max_height, len(page))
for row in page:
self.max_width = max(self.max_width, len(row))
def get_data(self):
return self.data
def get_current_filename(self):
for i, offset in enumerate(self.file_start_page):
if offset > self.page:
return self.files[i]
return "NA"
def get_filename(self):
return self.filename
def get_page(self, page):
try:
return self.data[page]
except IndexError:
return None
def get_pages(self):
return self.pages
def get_current_page(self):
return self.data[self.page]
def get_page_no(self):
return self.page
def inc_page_no(self, inc=1, loop=False):
self.page += inc
if self.page < 0:
self.page = 0
if loop:
self.page = self.pages - 1
if self.page >= self.pages:
self.page = self.pages - 1
if loop:
self.page = 0
self.width = max([len(x) for x in self.data[self.page]])
self.height = len(self.data[self.page])
def last_page(self):
self.page = self.pages - 1
def first_page(self):
self.page = 0
def get_page_height(self):
return self.height
def get_page_width(self):
return self.width
def get_max_height(self):
return self.max_height
def get_max_width(self):
return self.max_width
def get_toc(self, display_position=False):
title = self.opts.toc if self.opts.toc else "Table of Contents"
TOC = ["# " + title, ""]
offset = (self.opts.toc_page - 1) if self.opts.toc else 0
for h1, page in enumerate(self.data[offset:]):
title = page[0].strip("# ")
if display_position and h1 == self.page - offset:
title = "_%s_" % (title,)
TOC.append("%d. %s" % (h1 + 1, title))
subh = [0, 0, 0]
if self.opts.toc_depth > 1:
for line in page:
title = line.strip("# ")
if re.search(r"^##[^#]", line):
subh = [subh[0] + 1, 0, 0]
TOC.append(" %d.%d. %s" % (h1 + 1, subh[0], title))
if self.opts.toc_depth == 2:
continue
if re.search(r"^###[^#]", line):
subh = [subh[0], subh[1] + 1, 0]
TOC.append(
" %d.%d.%d. %s" % (h1 + 1, subh[0], subh[1], title)
)
if self.opts.toc_depth == 3:
continue
if re.search(r"^####[^#]", line):
subh = [subh[0], subh[1], subh[2] + 1]
TOC.append(
" %d.%d.%d.%d. %s"
% (h1 + 1, subh[0], subh[1], subh[2], title)
)
return TOC
def toc(self):
if self.opts.toc:
TOC = self.get_toc()
self.data.insert(self.opts.toc_page - 1, TOC)
# adding 1 is not fullproof, if toc page is after the first document
self.file_start_page = [1 + i for i in self.file_start_page]
def generate_content(self, s):
"""Check for launchable items, or converted images"""
if self.opts.execute_read:
if s.find("`>") > -1:
command = self.re_command.match(s)
if command != None:
return self.launch(command)
image = self.re_image_convert.match(s)
try:
return self.convert_image(image)
except Exception:
pass
return [s]
def launch(self, command):
"""Launch in a string using tags `command`>
Remove empty lines from beginning and end of stdout.
"""
output = subprocess.check_output(command.group(2).strip(), shell=True)
if type(output) == bytes:
output = output.decode("utf-8")
output = output.split("\n")
while len(output[0].strip()) == 0:
if len(output) == 1:
return [""]
del output[0]
while len(output[-1].strip()) == 0:
if len(output) == 1:
return [""]
del output[-1]
return_value = [command.group(1)]
return_value.extend(output)
return_value.append(command.group(3))
return return_value
# return [s]
def convert_image(self, image):
"""convert image using tags ![]()>
Remove empty lines from beginning and end of stdout.
"""
# ~ 2=title
# ~ 3=image command
width = max(5, self.scrsize[1] - 10)
output = climage.convert(
image.group(3),
is_unicode=True,
is_truecolor=True,
is_256color=False,
is_16color=False,
is_8color=False,
width=width,
palette="default",
)
output = output.split("\n")
while len(output[0].strip()) == 0:
if len(output) == 1:
return [""]
del output[0]
while len(output[-1].strip()) == 0:
if len(output) == 1:
return [""]
del output[-1]
return_value = [image.group(1), *output]
return return_value
def rename_duplicates(self):
if not opts.rename_title:
return
titles = {}
page_nos = []
for page in self.data:
if not page[0] in titles:
titles[page[0]] = 0
titles[page[0]] += 1
page_nos.append(titles[page[0]])
for page, page_no in zip(self.data, page_nos):
if titles[page[0]] > 1:
page[0] += " [%d/%d]" % (page_no, titles[page[0]])
class Pygmentizer:
def __init__(self, formatter, style):
import pygments
import pygments.lexers
import pygments.formatters
import pygments.styles
self.pygments = pygments
self.lexers = pygments.lexers
self.formatters = pygments.formatters
self.styles = pygments.styles
self.style = self.styles.get_style_by_name(style)
self.formatter = self.formatters.get_formatter_by_name(
formatter, style=self.style
)
self.lexer = None
def format(self, code):
in_block = False
blocks = []
current_block = -1
for i, row in enumerate(code):
if row.startswith("```"):
in_block = not in_block
lexer_name = row.replace("```", "").strip()
if len(lexer_name) == 0:
in_block = False
if in_block:
blocks.append({"lexer": lexer_name, "code": [], "rows": []})
current_block += 1
else:
if in_block:
blocks[current_block]["code"].append(row)
blocks[current_block]["rows"].append(i)
preformatted_rows = []
for block in blocks:
lexer = self.lexers.get_lexer_by_name(block["lexer"])
tokens = self.pygments.lex("\n".join(block["code"]), lexer)
formatted = self.pygments.format(tokens, self.formatter)
# ~ print(block["rows"])
# ~ print(formatted.split("\n"))
for row, formatted_row in zip(block["rows"], formatted.split("\n")):
code[row] = formatted_row
preformatted_rows.append(row)
return code, preformatted_rows
def get_interactive_help_text():
return """ left/right,page up/down,home,end
change page
c list contents (toc)
M modify file with VIM
q exit browser
r reload the presentation
s toggle status bar
t toggle timer (reqs. --timer switch)
,/. scroll page
up/down move highlight
enter execute highlighted line
h help"""
def setup_options():
"""Create command line options"""
usage = (
"""
MarkSlider: a markdown based slideshow engine
Special syntaxes:
* Colors: insert string ${C}, where C is one of KRGBYMCWkrgbymcwSUZ
* Text before first "# header" is not shown
* Text after a "# End of Slides" is not shown
* Execute shell: ` command -switch `! Beware of malicious code!
* Execute and print output: ` command `> Beware of malicious code!
* Convert images to ANSI, with climage: ![](file.jpg)>
Keyboard shortcuts:
"""
+ get_interactive_help_text()
)
parser = ArgumentParser(
description=usage,
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__author__,
)
parser.add_argument("-v", "--version", action="version", version=__version__)
parser.add_argument(
"--export",
action="store",
dest="screenshots",
default=False,
type=str,
help="Take screenshots of the slideshow in the given folder.",
)
content = parser.add_argument_group("content")
execution = parser.add_argument_group("execution")
control = parser.add_argument_group("controls")
content.add_argument(
"--background",
action="store_true",
dest="background",
default=False,
help="Use the rows before the first # header as slide background",
)
content.add_argument(
"--center",
action="store_true",
dest="center",
default=False,
help="Center slides on screen.",
)
content.add_argument(
"--dc",
action="store_true",
dest="dark_colors",
default=False,
help="Use dark colorscheme, better for white background terminals.",
)
content.add_argument(
"-m",
action="store_false",
dest="autocolor",
default=True,
help="Disable color by markdown structure.",
)
content.add_argument(
"--no-color",
"-n",
action="store_false",
dest="color",
default=True,
help="Disable color.",
)
content.add_argument(
"--no-rename",
"--nr",
action="store_false",
dest="rename_title",
default=True,
help="Disable automatic renaming of duplicate titles.",
)
content.add_argument(
"--toc",
action="store",
dest="toc",
default=False,
const="Table of Contents",
type=str,
nargs="?",
help="Insert table of contents. Define the header, or use default: %(const)s",
)
content.add_argument(
"--toc-page",
action="store",
dest="toc_page",
default=2,
type=int,
help="Insert table of contents on a chosen page. default: %(const)s",
)
content.add_argument(
"--toc-depth",
action="store",
dest="toc_depth",
default=2,
type=int,
choices=list(range(1, 5)),
help="Table of contents display depth. default: %(const)s",
)
content.add_argument(
"--no-pygments",
action="store_false",
dest="syntax_pygments",
default=True,
help="Disable autocoloring syntax with Pygments. Used only for ``` code blocks, if language is mentioned, ex.: ```python . Note! Do not have empty lines after ```",
)
content.add_argument(
"--syntax-formatter",
action="store",
dest="syntax_formatter",
default="terminal256",
choices=["terminal", "terminal256", "terminal16m"],
help="Syntax highlighter formatter. default: %(default)s",
)
content.add_argument(
"--syntax-style",
action="store",
dest="syntax_style",
default="monokai",
help="Syntax highlighter style, see Pygments styles. default: %(default)s",
)
execution.add_argument(
"-e",
action="store_true",
dest="execute",
default=False,
help="Execute commands in `! or `> tags at show time with Enter key. WARNING: Potentially very dangerous to run others' slides with this switch!",
)
execution.add_argument(
"-E",
action="store_true",
dest="execute_read",
default=False,
help="Execute commands in ``> tags at file read time. WARNING: Potentially very dangerous to run others' slides with this switch!",
)
control.add_argument(
"--exit",
action="store_true",
dest="exit_last",
default=False,
help="Exit after last slide.",
)
control.add_argument(
"-s",
action="store_false",
dest="menu",
default=True,
help="Disable status bar.",
)
control.add_argument(
"--timer",
action="store",
dest="slideTimer",
default=False,
type=int,
help="Timer for slideshow. If set, starts automatic slide changing.",
)
content.add_argument(
"-w",
action="store_false",
dest="wrap",
default=True,
help="Disable line wrapping. Cuts long lines.",
)
parser.add_argument("files", type=str, nargs="+", help="File(s) to show")
opts = parser.parse_args()
opts.slideShow = not not opts.slideTimer
if opts.screenshots:
opts.slideShow = True
opts.slideTimer = 1
opts.exit_last = True
return opts
def page_print(reader, opts, offset):
"""Print a page"""
page = reader.get_current_page()
scrsize = opts.size
# clear page
bc.clear()
if opts.center: # Placeholder for 80x25 center alignment
align_width = reader.get_max_width()
align_x_offset = int(scrsize[1] / 2 - align_width / 2)
align_pad = " " * align_x_offset
align_y_offset = int(scrsize[0] / 2 - reader.get_max_height() / 2)
bc.down_line(align_y_offset)
else:
align_pad = ""
# Print header
if opts.dark_colors:
coloring = "${b}${U}"
else:
coloring = "${U}${Y}"
print(align_pad + colorify(coloring + page[0], opts) + bc.Z)
if opts.background:
bc.save()
if opts.color:
sys.stdout.write(
"\n".join([align_pad + bc.color_string(x) for x in reader.background])
)
else:
sys.stdout.write(
"\n".join([align_pad + bc.nocolor_string(x) for x in reader.background])
)
bc.restore()
if sys.version_info < (3, 0):
# python2 magic
page = [row.decode("utf-8") for row in page]
# Print page rows
if not opts.wrap:
page = [cut_line(row, scrsize[1] - 1) for row in page]
parsed = md_color.parse(page)
if opts.autocolor:
if reader.pygments:
# ~ to_pygmentize = []
# ~ pygmented_rows = []
# ~ for token_i in range(len(parsed)):
# ~ if parsed[token_i][0] == 'multiline_code':
# ~ to_pygmentize.append(parsed[token_i][1])
# ~ pygmented_rows.append(token_i)
# ~ if len(to_pygmentize) > 0:
preformatted, preformatted_rows = reader.pygments.format(
[x[1] for x in parsed]
)
for row_i in preformatted_rows:
parsed[row_i][0] = "preformatted"
parsed[row_i][1] = preformatted[row_i]
colored = md_color.colorize(parsed, not opts.color, opts.dark_colors)
else:
if opts.color:
colored = [bc.color_string(row[1]) for row in parsed]
else:
colored = [bc.nocolor_string(row[1]) for row in parsed]
r = 0
for row_i in range(len(page)):
if row_i == 0:
continue
if row_i < offset[0]:
continue
row = page[row_i]
# page[1+offset[0]:]:
if opts.wrap:
row_lines = int(float(len(row)) / scrsize[1])
else:
row_lines = 0
row_lines = 0
colored_row = colored[row_i] # =colorify(row,opts)
if offset[1] == r + 1 + offset[0]:
colored_row = add_highlight(row, opts)
sys.stdout.write(align_pad + colored_row)
if r >= scrsize[0] - 2:
break
r += row_lines + 1
sys.stdout.write("\n")
sys.stdout.flush()
return
def print_menu(reader, opts):
bc.posprint(
opts.size[0],
0,
colorify(
"${y}%d${Z}/%d %s|%s"
% (
1 + reader.get_page_no(),
reader.get_pages(),
os.path.basename(reader.get_current_filename()),
"slideshow" if opts.slideShow else "",
),
opts,
),
)
def print_time(opts):
now = datetime.datetime.now()
bc.posprint(
opts.size[0],
opts.size[1] - 5,
colorify("%02d:%02d" % (now.hour, now.minute), opts),
)
def print_help(reader, opts):
"""Create a window with help message"""
helptext = get_interactive_help_text().split("\n")
maxlen = max([len(x) for x in helptext])
bc.posprint(3, 5, " +" + "-" * maxlen + "+ ")
bc.posprint(
4,
5,
colorify(
" |${U}${Y}" + ("{:^" + str(maxlen) + "}").format("Help") + "${Z}| ", opts
),
)
for y, row in enumerate(helptext):
bc.posprint(5 + y, 5, (" |{:<" + str(maxlen) + "}| ").format(row))
bc.posprint(6 + y, 5, " +" + "-" * maxlen + "+ ")
sys.stdout.write(bc.pos(opts.size[0], opts.size[1]))
inkey = getch.get()
def print_toc(reader, opts):
"""Create a window with TOC"""
text = reader.get_toc(display_position=True)
title = opts.toc if opts.toc else "Table of Contents"
maxlen = max([len(x) for x in text])
bc.posprint(3, 2, " +" + "-" * maxlen + "+ ")
parsed = md_color.parse(text)
if opts.autocolor:
colored = md_color.colorize(parsed, not opts.color, opts.dark_colors)
else:
if opts.color:
colored = [bc.color_string(row[1]) for row in parsed]
else:
colored = [bc.nocolor_string(row[1]) for row in parsed]
for y, row in enumerate(colored):
bc.posprint(4 + y, 2, (" |{:<" + str(maxlen) + "}| ").format(" "))
bc.posprint(4 + y, 3, ("|{:<" + str(maxlen) + "}").format(row))
bc.posprint(5 + y, 2, " +" + "-" * maxlen + "+ ")
sys.stdout.write(bc.pos(opts.size[0], opts.size[1]))
inkey = getch.get()
def offset_change(opts, reader, offset, new_offset):
"""Change the display position of page"""
new_offset = (offset[0] + new_offset[0], offset[1] + new_offset[1])
offsety = min(reader.get_page_height() - 1, new_offset[0])
offseth = min(reader.get_page_height(), new_offset[1])
return [max(0, o) for o in (offsety, offseth)]
def timeouthandler(sig, frame):
# ~ print(sig,frame)
raise IOError("Input timeout")
def getkeypress():
try:
return ord(getch.get())
except:
return False
def browser(opts, files):
"""Main function for printing"""
try:
reader = slide_reader(files, opts)
except:
print("Error in reading the file:")
for o in sys.exc_info():
print(o)
sys.exit(1)
offset = (0, 0)
try:
while 1:
opts.size = get_console_size()
page_print(reader, opts, offset)
if opts.menu:
print_menu(reader, opts)
print_time(opts)
sys.stdout.write(bc.pos(opts.size[0], opts.size[1]))
sys.stdout.flush()
if opts.screenshots:
take_screenshot(reader, opts)
while True:
if opts.slideTimer and opts.slideShow:
signal.signal(signal.SIGALRM, timeouthandler)
signal.alarm(opts.slideTimer)
elif opts.menu:
signal.signal(signal.SIGALRM, timeouthandler)
signal.alarm(15)
inkey = getkeypress()
signal.alarm(0)
if not inkey and not opts.slideShow and opts.menu:
# normal operation, just update the time
print_time(opts)
continue
if not inkey and opts.slideShow:
# slideshow mode
if opts.exit_last:
if reader.page + 1 == reader.pages:
return
reader.inc_page_no(1, True)
offset = (0, 0)
break
# ~ print(inkey)
if inkey in [113, 3, 120]:
# print('Exited in: ')
return
if inkey in [67, 54, 32]: # PGDN or space
if opts.exit_last:
if reader.page + 1 == reader.pages:
return
reader.inc_page_no(1)
offset = (0, 0)
if inkey in [68, 53, 127]:
reader.inc_page_no(-1)
offset = (0, 0)
if inkey == 72 or inkey == 49: # HOME
reader.first_page()
offset = (0, 0)
if inkey == 70 or inkey == 52: # END
reader.last_page()
offset = (0, 0)
if inkey == ord("c"):
print_toc(reader, opts)
if inkey == ord("h"):
print_help(reader, opts)
if inkey == ord("s"):
opts.menu = not opts.menu
if inkey == ord("t"):
opts.slideShow = not opts.slideShow
if inkey == ord("r"):
reader.read()
offset = offset_change(opts, reader, offset, (0, 0))
if inkey == ord("M"):
modify_file(reader, offset)
reader.read()
offset = offset_change(opts, reader, offset, (0, 0))
if inkey == ord(","):
offset = offset_change(opts, reader, offset, (-1, 0))
if inkey == ord("."):
offset = offset_change(opts, reader, offset, (1, 0))
if inkey == 65: # up
offset = offset_change(opts, reader, offset, (0, -1))
if inkey == 66: # down
offset = offset_change(opts, reader, offset, (0, 1))
if inkey == 13: # enter
launch(reader, opts, offset)
break
except IOError:
pass
except KeyboardInterrupt:
sys.exit(0)
except EndProgram:
pass
except:
print("Unexpected error:")
print(traceback.format_exc())
sys.exit(1)
def get_console_size():
rows, columns = os.popen("stty size", "r").read().split()
return (int(rows), int(columns))
def colorify(s, opts):
"""Add colors to string"""
if not opts.color:
return bc.nocolor_string(s)
c = bc.color_string(s) # +bc.Z
return c
def cut_line(s, i):
"""cut a color tagged string, and remove control chars"""
s = s[:i]
s = re.sub(r"\$$", "", re.sub(r"\$\{$", "", re.sub(r"\$\{.$", "", s)))
return s
def add_highlight(s, opts):
"""Add cursor position highlight"""
if len(s.strip()) == 0:
cleaned = HL
else:
cleaned = bc.nocolor_string(s)
tagged = "${Y}" + cleaned + "${Z}"
return colorify(tagged, opts)
def launch(reader, opts, offset):
"""Launch in a string using tags $!command$! or $>command$>
Remove empty lines from beginning and end of stdout in $> commands.
Detects URLS and markdown images ![Alt text](/path/to/img.jpg)
"""
if not opts.execute:
bc.posprint(offset[1] - offset[0] + 1, 0, "Execution not enabled!")
inkey = getch.get()
return
s = reader.get_current_page()[offset[1]]
urls = re.findall(
r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+",
s,
)
images = re.findall(r"!\[[^]]+\]\([^\)]+\)", s)
# sanity check
if s.find("`") == -1 and len(urls) == 0 and len(images) == 0:
return
run_command = re.match(r"(.*)`(.*)`!(.*)", s)
show_command = re.match(r"(.*)`(.*)`>(.*)", s)
if show_command != None:
output = subprocess.check_output(show_command.group(2).strip(), shell=True)
if type(output) == bytes:
output = output.decode("utf-8")
output = output.split("\n")
while len(output[0].strip()) == 0:
if len(output) == 1:
return
del output[0]
while len(output[-1].strip()) == 0:
if len(output) == 1:
return
del output[-1]
for y, l in enumerate(output):
bc.posprint(y + offset[1] - offset[0] + 2, 0, " " * len(l))
bc.clear_to_end()
bc.posprint(y + offset[1] - offset[0] + 2, 0, l)
inkey = getch.get()
return
if run_command != None:
subprocess.call(run_command.group(2), shell=True, executable="/bin/bash")
inkey = getch.get()
return
# Open URLS last
if len(urls) > 0:
# Remove ) at the end of url: [name](link) markdown syntax
subprocess.call(
"%s '%s' &"
% (
get_open_command(),
urls[0].rstrip(")"),
),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
)
return
if len(images) > 0:
image = re.sub(r".*\(([^\)]+)\).*", "\\1", images[0])
subprocess.call(
"%s '%s' &"
% (
get_open_command(),
image,
),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
)
return
return
def modify_file(reader, offset):
row = 1
row_restarts = reader.file_start_page
for page in range(reader.page):
if opts.toc_page == page + 1 and opts.toc:
continue
row += len(reader.data[page])
if (page + 1) in row_restarts:
row = 1
subprocess.call(
"vim +%d -c 'exe \"normal! zt\"' -c %d %s"
% (row, row + offset[1], reader.get_current_filename()),
shell=True,
)
def take_screenshot(reader, opts):
out_file = os.path.join(opts.screenshots, "slide%03d.png" % (reader.page + 1,))
if not os.path.exists(opts.screenshots):
os.mkdir(opts.screenshots)
subprocess.call(
"sleep 0.5; import -window %s '%s'" % (opts.xwinid, out_file),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
)
def get_open_command():
if sys.platform.startswith("darwin"):
return "open"
else:
return "xdg-open"
def main():
global bc
global getch
global opts
bc = ansicodes.code()
getch = getch()
opts = setup_options()
if opts.screenshots:
print("Click the terminal window to read window ID")
xwininfo = subprocess.check_output("xwininfo", shell=True)
if type(xwininfo) == bytes:
xwininfo = xwininfo.decode("utf-8")
xwininfo = re.search(r"Window id: (0x[0-9]+)", xwininfo)
if xwininfo:
opts.xwinid = xwininfo.group(1)
else:
print("Cannot parse window ID")
sys.exit(1)
browser(opts, opts.files)
print("\n\n")
if opts.screenshots:
print(
"Crop the images for terminal tabs, and PDFize e.g.:\n- mogrify -chop 0x50 %s/*png\n- convert %s/*png %s.pdf"
% (
opts.screenshots,
opts.screenshots,
os.path.basename(opts.screenshots),
)
)
if __name__ == "__main__":
main()