From 4ca2288950aaa953d10b2d4fb89c731beb8043e7 Mon Sep 17 00:00:00 2001 From: ville rantanen Date: Mon, 22 Jun 2015 09:23:02 +0300 Subject: [PATCH] new python markslider --- bin/markslider | 2 +- reporting/markslider.py | 411 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 412 insertions(+), 1 deletion(-) create mode 100755 reporting/markslider.py diff --git a/bin/markslider b/bin/markslider index 59a8bb9..30722c1 120000 --- a/bin/markslider +++ b/bin/markslider @@ -1 +1 @@ -../reporting/markslider \ No newline at end of file +../reporting/markslider.py \ No newline at end of file diff --git a/reporting/markslider.py b/reporting/markslider.py new file mode 100755 index 0000000..a0ebd43 --- /dev/null +++ b/reporting/markslider.py @@ -0,0 +1,411 @@ +#!/usr/bin/env python +# +# Copyright 2015 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 . +# + +'''Markslider: a slideshow engine based on markdown.''' + +__author__ = "Ville Rantanen " + +__version__ = "0.1" + +import sys,os,argparse,re +from argparse import ArgumentParser +import traceback,tty,termios + +class bc: + K="\033[1;30m" + R="\033[1;31m" + G="\033[1;32m" + B="\033[1;34m" + Y="\033[1;33m" + M="\033[1;35m" + C="\033[1;36m" + W="\033[1;37m" + k="\033[30m" + r="\033[31m" + g="\033[32m" + b="\033[34m" + y="\033[33m" + m="\033[35m" + c="\033[36m" + w="\033[37m" + S = '\033[1m' + U = '\033[4m' + Z = '\033[0m' + CLR = '\033[2J' + + color_keys="KRGBYMCWkrgbymcwSUZ" + color_list=[K,R,G,B,Y,M,C,W,k,r,g,b,y,m,c,w,S,U,Z] + + def pos(self,y,x): + return "\033["+str(y)+";"+str(x)+"H" + + def posprint(self, y,x,s): + sys.stdout.write( self.pos(y,x) + str(s) ) + + def clear(self): + sys.stdout.write( self.CLR+self.pos(0,0) ) + + def color_string(self,s): + for i,c in enumerate(self.color_keys): + s=s.replace("${"+c+"}",self.color_list[i]) + return s + def nocolor_string(self,s): + for i,c in enumerate(self.color_keys): + s=s.replace("${"+c+"}","") + return s + + +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 way of exiting the program ''' + pass + +class slide_reader: + """ Class for reading files. """ + def __init__(self,filename,opts): + self.filename=filename + self.reader=None + self.opts=opts + self.pages=0 + self.page=0 + self.width=None + self.height=None + self.data=[] + #~ 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.read() + + def read(self): + ''' Read a file, set pages and data ''' + + f=open(self.filename,'r') + self.pages=0 + self.data=[] + new_page=[] + for row in f: + if not row: + continue + row=row.decode('utf-8').rstrip("\n\r") + if row.startswith("#") and not row.startswith("##"): + if len(new_page)>0: + self.data.append(new_page) + new_page=[] + new_page.append(row) + if len(new_page)>0: + self.data.append(new_page) + self.pages=len(self.data) + self.inc_page_no(0) + self.toc() + + def get_data(self): + return self.data + 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): + self.page+=inc + if self.page<0: + self.page=0 + if self.page>=self.pages: + self.page=self.pages-1 + 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 toc(self): + if self.opts.toc: + TOC=["# "+self.opts.toc,""] + for h1,page in enumerate(self.data): + title=page[0].strip("# ") + TOC.append("%d. %s"%(h1+1,title)) + h2=0 + for line in page: + if re.search("^##[^#]", line): + h2+=1 + title=line.strip("# ") + TOC.append(" %d.%d. %s"%(h1+1,h2,title)) + self.data.insert(1,TOC) + + + +def get_interactive_help_text(): + return ''' left/right,page up/down,home,end + change page + m toggle page numbers + q exit browser + r reload the file + ,/. scroll page + up/down move highlight + h help''' + +def setup_options(): + ''' Create command line options ''' + usage=''' + + Interactive mode keymap: +'''+get_interactive_help_text() + + parser=ArgumentParser(description=usage, + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__author__) + + parser.add_argument("--no-color","-n",action="store_false",dest="color",default=True, + help="Disable color.") + parser.add_argument("-v","--version",action="version",version=__version__) + parser.add_argument("-m",action="store_true",dest="autocolor",default=False, + help="Color by markdown structure.") + parser.add_argument("--dc",action="store_true",dest="dark_colors",default=False, + help="Use dark colorscheme, better for white background terminals.") + parser.add_argument("--toc",action="store_true",dest="toc",default=False, + help="Display TOC as 2nd slide.") + parser.add_argument("-s",action="store_false",dest="menu",default=True, + help="Disable status bar.") + parser.add_argument("-w",action="store_false",dest="wrap",default=True, + help="Disable line wrapping. Cuts long lines.") + parser.add_argument("-t",action="store",dest="toc",default=False, + const="Table of contents", type=str, nargs='?', + help="Insert table of contents as page 2. Define the header, or use default: %(const)s") + parser.add_argument("filename",type=str, + help="File to show") + opts=parser.parse_args() + return opts + + +def page_print(reader,opts,offset): + ''' Print a page ''' + + page=reader.get_current_page() + scrsize=opts.size + # clear page + bc.clear() + # Print header + if opts.dark_colors: + coloring="${U}${b}" + else: + coloring="${U}${Y}" + print(colorify(coloring+page[0],opts)) + + # Print page rows + r=0 + for row in page[1+offset[0]:]: + if not opts.wrap: + row=cut_line(row,scrsize[1]-1) + row_lines=0 + else: + row_lines=int(float(len(row))/scrsize[1]) + colored=colorify(row,opts) + if offset[1]==r+1: + colored=colorify("${Y}>"+bc.nocolor_string(row),opts) + sys.stdout.write(colored) + + if r>=scrsize[0]-2: + break + r+=row_lines+1 + sys.stdout.write("\n") + + return + +def menu_print(reader,opts): + + bc.posprint( opts.size[0], 0, + colorify("${y}%d${Z}/%d %s|"%( + 1+reader.get_page_no(), + reader.get_pages(), + reader.get_filename()), + 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,6,"+"+"-"*maxlen+"+") + bc.posprint(4,6,("|{:^"+str(maxlen)+"}|").format("Help")) + for y,row in enumerate(helptext): + bc.posprint(5+y,6,("|{:<"+str(maxlen)+"}|").format(row)) + bc.posprint(6+y,6,"+"+"-"*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]) + offsety=max(0,offsety) + if offsety==0 and new_offset[0]!=0: + return (offsety,offset[1]) + offseth=min(opts.size[0]-1,new_offset[1]) + offseth=min(reader.get_page_height(),offseth) + offseth=max(0,offseth) + return (offsety, offseth) + +def browser(opts,filename): + ''' Main function for printing ''' + + try: + reader=slide_reader(filename,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: + menu_print(reader,opts) + sys.stdout.write(bc.pos(opts.size[0], opts.size[1])) + while 1: + inkey=ord(getch.get()) + #~ print(inkey) + if inkey in [113,3,120]: + #print('Exited in: ') + return + if inkey in [67,54,32]: # PGDN or space + 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('h'): + print_help(reader,opts) + if inkey==ord('m'): + opts.menu=not opts.menu + if inkey==ord('r'): + reader.read() + offset=(0, 0) + if inkey==ord(','): + offset=offset_change(opts,reader,offset,(-1, 1)) + if inkey==ord('.'): + offset=offset_change(opts,reader,offset,(1, -1)) + if inkey==65: + offset=offset_change(opts,reader,offset,(0, -1)) + if inkey==66: + offset=offset_change(opts,reader,offset,(0, 1)) + break + #~ stdscr.refresh() + + + except IOError: + pass + except KeyboardInterrupt: + #~ reset_curses(stdscr) + sys.exit(0) + except EndProgram: + pass + except: + #~ reset_curses(stdscr) + 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) + if opts.autocolor and not s.startswith("${"): + rules=["(^\s*\*.*)", # * bullets + "(^\s*[0-9]+\..*)", # 1. ordered + "(^#.*)", ## Header + "(^\s\s\s\s[^\*0-9\s].*)", # code block + "(\`[^\s]*[^\`]+\`)", # code inline + "(\[[^]]+\]\([^\)]+\))", # [link](url) + "(\*{1,2}[^\s]*[^\*\s]+\*{1,2})", # *bold* + "(_[^\s]*[^_\s]+_)", # _bold_ + "(<[^>]+>)"] # + if opts.dark_colors: + colors=["${r}\\1", # * bullets + "${r}\\1", # 1. ordered + "${U}${b}\\1", ## Header + "${m}\\1", # code block + "${m}\\1${Z}", # code inline + "${B}${U}\\1${Z}", # [link](url) + "${W}\\1${Z}", # *bold* + "${W}\\1${Z}", # _bold_ + "${K}\\1${Z}"] # + else: + colors=["${y}\\1", # * bullets + "${y}\\1", # 1. ordered + "${U}${Y}\\1", ## Header + "${c}\\1", # code block + "${c}\\1${Z}", # code inline + "${B}${U}\\1${Z}", # [link](url) + "${W}\\1${Z}", # *bold* + "${W}\\1${Z}", # _bold_ + "${K}\\1${Z}"] # + for r in zip(rules,colors): + s=re.sub(r[0],r[1],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("\$$","", + re.sub("\$\{$","", + re.sub("\$\{.$","", + s))) + return s + +bc=bc() +getch=getch() +opts=setup_options() +browser(opts,opts.filename)