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)