diff --git a/files/mvregex b/files/mvregex index 5b1a9c3..cecf941 100755 --- a/files/mvregex +++ b/files/mvregex @@ -10,6 +10,7 @@ function helpexit() { echo ' -n to match non-ascii and non-printable characters, and replace to [arg1]' echo ' -f to replace match [arg1] with format [arg2], ex: -f "[0-9]\+" "%04d"' echo ' -p to replace problematic characters [^\w()[]-.] with [arg1]' + echo ' -a to replace non-alphanumeric(+dot) characters [^\w.] with [arg1]' exit } @@ -44,6 +45,10 @@ do [[ "${!i}" = "-h" ]] && helpexit SRC='[^]\[0-9a-zA-Z_.()-]' continue } + [[ "${!i}" = "-a" ]] && { + SRC='[^0-9a-zA-Z.]' + continue + } [[ -z "$SRC" ]] && { SRC="${!i}" continue diff --git a/reporting/ansi.py b/reporting/ansi.py index 7f2cf9c..58369e1 100644 --- a/reporting/ansi.py +++ b/reporting/ansi.py @@ -10,7 +10,7 @@ class code: M="\033[1;35m" C="\033[1;36m" W="\033[1;37m" - + k="\033[0;30m" r="\033[0;31m" g="\033[0;32m" @@ -19,7 +19,7 @@ class code: m="\033[0;35m" c="\033[0;36m" w="\033[0;37m" - + bk="\033[40m" br="\033[41m" bg="\033[42m" @@ -42,11 +42,11 @@ class code: CLREND = '\033[K' CLRBEG = '\033[1K' CLRSCR = CLR+"\033[0;0H" - + color_keys="K,R,G,B,Y,M,C,W,k,r,g,b,y,m,c,w,S,s,U,u,Z,ic,io,st,so,bk,br,bg,by,bb,bm,bc,bw,CLR,CLREND,CLRBEG,CLRSCR".split(",") color_list=[K,R,G,B,Y,M,C,W,k,r,g,b,y,m,c,w,S,s,U,u,Z,ic,io,st,so,bk,br,bg,by,bb,bm,bc,bw,CLR,CLREND,CLRBEG,CLRSCR] custom_match=re.compile(r'(\${)([0-9;]*[ABCDEFGHJKSTfminsu]+)(})') - + def pos(self,y,x): """ Go to absolute position """ return "\033["+str(y)+";"+str(x)+"H" @@ -57,6 +57,7 @@ class code: def posprint(self, y,x,s): """ Print string at a location """ sys.stdout.write( self.pos(y,x) + str(s) ) + self.flush() def clear(self): sys.stdout.write( self.CLRSCR+self.pos(0,0) ) @@ -77,7 +78,7 @@ class code: sys.stdout.write( "\033["+str(n)+"F" ) def down_line(self,n=1): sys.stdout.write( "\033["+str(n)+"E" ) - + def save(self): """ Save cursor position """ sys.stdout.write( "\033[s" ) @@ -93,16 +94,17 @@ class code: for i,c in enumerate(self.color_keys): s=s.replace("${"+c+"}","") return self.custom_nocolor(s) - + def custom_color(self,s): return self.custom_match.sub('\033[\\2',s) def custom_nocolor(self,s): return self.custom_match.sub('',s) - + def get_keys(self): return self.color_keys - + def flush(self): + sys.stdout.flush() def demo(): @@ -113,7 +115,7 @@ def demo(): ${S}Fo${U}rm${st}at${u}ti${ic}ng${Z} ${S}==========${Z} 0 Z Clear format - 1 S ${S}Strong ${Z} 2 s ${s}Off${Z} + 1 S ${S}Strong ${Z} 2 s ${s}Off${Z} 4 U ${U}Underline${Z} 24 u Off 7 ic ${ic}Inverse${Z} 27 io Off 9 st ${st}Strike${Z} 29 so Off @@ -129,15 +131,15 @@ ${S}======${Z} 37 w ${w}White ${Z}1 W ${W}Strong ${Z}47 bw ${bw}Background${Z} ${S}Clearing and movement ${S}=====================${Z} - 2J CLR Clear screen + 2J CLR Clear screen 2J ;H CLRSCR .clear() Clear screen and cursor to upper left K CLREND Clear to end of line 1K CLRBEG Clear to beginning of line - + s .save() Save location u .restore() Restore location - A .up() Up E .up_line() Up line - B .down() Down F .down_line() Down line - C .left() Left y;xH .pos() Absolute Position + A .up() Up E .up_line() Up line + B .down() Down F .down_line() Down line + C .left() Left y;xH .pos() Absolute Position D .right() Right """ print(c.color_string(unformatted)) diff --git a/reporting/markslider.packager.sh b/reporting/markslider.packager.sh index b3fcaea..400c5c8 100644 --- a/reporting/markslider.packager.sh +++ b/reporting/markslider.packager.sh @@ -7,7 +7,7 @@ rm -rf markslider mkdir -p markslider/scripts markslider/markslider cp -v ansi.py md_color.py markslider.py markslider/markslider/ -echo '#!/usr/bin/env python2 +echo '#!/usr/bin/env python # -*- coding: utf-8 -*- import re import sys diff --git a/reporting/markslider.py b/reporting/markslider.py index 949a608..df3c002 100755 --- a/reporting/markslider.py +++ b/reporting/markslider.py @@ -20,7 +20,7 @@ '''Markslider: a slideshow engine based on markdown.''' __author__ = "Ville Rantanen " -__version__ = "0.9" +__version__ = "1.0" import sys,os,argparse,re,datetime from argparse import ArgumentParser @@ -45,25 +45,26 @@ class getch: class EndProgram( Exception ): ''' Nice exit ''' + print('') 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.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("(.*)(!\[.*\])\((.*)\)>") - self.re_command=re.compile("(.*)`(.*)`>(.*)") + self.filename = files[0] + self.files = files + self.reader = None + self.opts = opts + 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("(.*)(!\[.*\])\((.*)\)>") + self.re_command = re.compile("(.*)`(.*)`>(.*)") #~ 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() @@ -71,10 +72,10 @@ class slide_reader: def read(self): ''' Read a file, set pages and data ''' - self.pages=0 - self.data=[] - self.file_start_page=[] - first_slide_found=False + self.pages = 0 + self.data = [] + self.file_start_page = [] + first_slide_found = False for fname in self.files: first_slide_found=False f=open(fname,'r') @@ -82,9 +83,9 @@ class slide_reader: for row in f: if not row: continue - row=row.decode('utf-8').rstrip("\n\r ") + row=row.rstrip("\n\r ") # find end of show - if row==EOS: + if row == EOS: break # find header to start a new page if row.startswith("#") and not row.startswith("##"): @@ -201,7 +202,7 @@ class slide_reader: command=self.re_command.match(s) if command !=None: return self.launch(command) - image=self.re_image_convert.match(s) + image = self.re_image_convert.match(s) if image != None: return self.convert_image(image) return [s] @@ -210,13 +211,13 @@ class slide_reader: """ Launch in a string using tags `command`> Remove empty lines from beginning and end of stdout. """ - #~ if not self.opts.execute_read: - #~ return [s] - #~ if s.find("`>")==-1: - #~ return [s] - #~ command=self.re_command.match(s) - #~ if command != None: - output = subprocess.check_output(command.group(2).strip(),shell=True).split("\n") + 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] @@ -235,7 +236,13 @@ class slide_reader: """ #~ 2=title #~ 3=image command - output = subprocess.check_output("convert %s JPEG:- | jp2a --colors --width=70 -"%image.group(3),shell=True).split("\n") + output = subprocess.check_output( + "convert %s JPEG:- | jp2a --colors --width=70 -"%( image.group(3),), + 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] @@ -273,7 +280,7 @@ Special syntaxes: * 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 ASCII, with jp2a: ![](file.jpg) + * Convert images to ASCII, with jp2a: ![](file.jpg)> Keyboard shortcuts: '''+get_interactive_help_text() @@ -333,55 +340,67 @@ Keyboard shortcuts: def page_print(reader,opts,offset): ''' Print a page ''' - page=reader.get_current_page() - scrsize=opts.size + 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=scrsize[1]/2-align_width/2 - align_pad=" "*align_x_offset - align_y_offset=scrsize[0]/2-reader.get_max_height()/2 + align_width = reader.get_max_width() + align_x_offset = scrsize[1]/2-align_width/2 + align_pad = " "*align_x_offset + align_y_offset = scrsize[0]/2 - reader.get_max_height()/2 bc.down_line(align_y_offset) else: - align_pad="" + align_pad = "" # Print header if opts.dark_colors: - coloring="${b}${U}" + coloring = "${b}${U}" else: - coloring="${U}${Y}" - print(align_pad+colorify(coloring+page[0],opts)+bc.Z) - + coloring = "${U}${Y}" + print( + align_pad + + colorify( + coloring + page[0], + opts) + + bc.Z + ) + 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) + page = [cut_line(row,scrsize[1]-1) for row in page] + parsed = md_color.parse(page) if opts.autocolor: - colored=md_color.colorize(parsed,not opts.color,opts.dark_colors) + 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 + r = 0 for row_i in range(len(page)): - if row_i==0: continue - if row_i=scrsize[0]-2: + if r >= scrsize[0] - 2: break - r+=row_lines+1 + r += row_lines + 1 sys.stdout.write("\n") sys.stdout.flush() return @@ -398,8 +417,17 @@ def print_menu(reader,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)) + 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 ''' @@ -588,24 +616,35 @@ def launch(reader,opts,offset): 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() + bc.posprint( + offset[1]-offset[0]+1, + 0, + "Execution not enabled!" + ) + inkey = getch.get() return - s=reader.get_current_page()[offset[1]] + s = reader.get_current_page()[offset[1]] urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', s) images = re.findall('!\[[^]]+\]\([^\)]+\)', s) - if s.find("`")==-1 and len(urls)==0 and len(images)==0: + # sanity check + if s.find("`") == -1 and len(urls) == 0 and len(images) == 0: return run_command=re.match("(.*)`(.*)`!(.*)",s) show_command=re.match("(.*)`(.*)`>(.*)",s) if show_command != None: - output = subprocess.check_output(show_command.group(2).strip(),shell=True).split("\n") + 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 [""] + if len(output)==1: return del output[0] while len(output[-1].strip())==0: - if len(output)==1: return [""] + 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)) @@ -614,22 +653,37 @@ def launch(reader,opts,offset): inkey=getch.get() return if run_command != None: - subprocess.call(run_command.group(2), - shell=True,executable="/bin/bash") + 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("xdg-open '%s' &"%(urls[0].rstrip(")"),), - stdout=subprocess.PIPE,stderr=subprocess.PIPE, - shell=True) + 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('.*\(([^\)]+)\).*', "\\1",images[0]) - subprocess.call("xdg-open '%s' &"%(image,), - stdout=subprocess.PIPE,stderr=subprocess.PIPE, - shell=True) + subprocess.call( + "%s '%s' &"%( + get_open_command(), + image, + ), + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + shell = True + ) return return @@ -642,16 +696,32 @@ def modify_file(reader,offset): 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) + 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 $WINDOWID '%s'"%(out_file,), - stdout=subprocess.PIPE,stderr=subprocess.PIPE, - shell=True) + subprocess.call( + "sleep 0.5; import -window $WINDOWID '%s'"%(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(): diff --git a/reporting/markslider.tar.gz b/reporting/markslider.tar.gz index a73ef92..7af32af 100644 Binary files a/reporting/markslider.tar.gz and b/reporting/markslider.tar.gz differ diff --git a/reporting/md_color.py b/reporting/md_color.py index 84a261e..53293b0 100755 --- a/reporting/md_color.py +++ b/reporting/md_color.py @@ -8,21 +8,23 @@ import ansi __author__ = "Ville Rantanen " __version__ = "0.3" -''' Rules modified from mistune project ''' +''' Rules modified from mistune project ''' def setup_options(): - bc=ansi.code() + bc = ansi.code() ''' Create command line options ''' - usage=''' + usage = ''' Markdown syntax color in ansi codes. Special syntaxes: Colors: insert string ${C}, where C is one of %s. Any ANSI control code: ${3A}, ${1;34;42m}, etc.. '''%(" ".join(bc.get_keys())) - - parser=ArgumentParser(description=usage, - epilog=__author__) + + parser = ArgumentParser( + description = usage, + epilog = __author__ + ) parser.add_argument("-v","--version",action="version",version=__version__) parser.add_argument("-D",action="store_true",dest="debug",default=False, @@ -64,19 +66,19 @@ def parse(data): else: multiline_block=match break - if multiline_block: + if multiline_block: new_block=multiline_block # Lists must end with empty line if new_block not in ('empty','list_bullet') and block.startswith('list_'): new_block='list_loose' - + if 'mod' in block_match[match]: # Style sets block in previous or next lines data[i+block_match[match]['mod']['pos']][0]=block_match[match]['mod']['name'] data[i][0]=new_block if new_block!=block: - block=new_block - return data + block=new_block + return data def colorize(data,remove_colors=False,dark_colors=False,debug=False): # Start inserting colors, and printing @@ -86,7 +88,7 @@ def colorize(data,remove_colors=False,dark_colors=False,debug=False): csc=cs+'c' for i,line in enumerate(data): row=line[1] - block=line[0] + block=line[0] multiline_block=block.startswith('multiline') if multiline_block: row=block_match[block][csc]+row @@ -122,14 +124,53 @@ def md_re_compile(d): sys.exit(1) return n +def read_data2(fp): + data = [] + # Read data + for row in f: + if not row: + continue + row = row.decode('utf-8').rstrip("\n\r ") + data.append(row) + return data + +def read_data3(fp): + data = [] + # Read data + for row in f: + if not row: + continue + row = row.rstrip("\n\r ") + data.append(row) + return data + +def write_colored2(colored, opts): + for c in colored: + sys.stdout.write(c.encode('utf-8')) + if opts.zero: + sys.stdout.write(bc.Z) + sys.stdout.write("\n") + if opts.zero_final: + sys.stdout.write(bc.Z.encode('utf-8')) + +def write_colored3(colored, opts): + for c in colored: + sys.stdout.write(c) + if opts.zero: + sys.stdout.write(bc.Z) + sys.stdout.write("\n") + if opts.zero_final: + sys.stdout.write(bc.Z) + + # re: regular expression, bc: bright colors, bcc: continue with this color after inline # dc: dark colors, dcc: continued color after inline block_match_str={ - 'block_code': {'re':'^( {4}[^*])(.*)$', - 'bc' :'${Z}${c}\\1\\2', 'bcc':'${Z}${c}', + 'block_code': {'re':'^( {4}[^*])(.*)$', + 'bc' :'${Z}${c}\\1\\2', 'bcc':'${Z}${c}', 'dc' :'${Z}${m}\\1\\2', 'dcc':'${Z}${m}'}, # code - 'multiline_code' : {'re':'^ *(`{3,}|~{3,}) *(\S*)', + 'multiline_code' : {'re':'^ *(`{3,}|~{3,}) *(\S*)', 'bc' :'${Z}${c}\\1\\2', 'bcc':'${Z}${c}', 'dc' :'${Z}${m}\\1\\2', 'dcc':'${Z}${m}'}, # ```lang 'block_quote': {'re':'^(>[ >]* )', @@ -165,9 +206,9 @@ block_match=md_re_compile(block_match_str) blocks=['block_quote', 'multiline_code','hrule', 'heading','lheading','list_bullet', 'block_code', 'text', 'empty'] inline_match_str={ - 'bold1': {'re':r'(^| |})(_[^_]+_)', + 'bold1': {'re':r'(^| |})(_[^_]+_)', 'bc':'\\1${W}\\2','dc':'\\1${W}\\2'}, # _bold_ - 'bold2': {'re':r'(^| |})(\*{1,2}[^\*]+\*{1,2})', + 'bold2': {'re':r'(^| |})(\*{1,2}[^\*]+\*{1,2})', 'bc':'\\1${W}\\2','dc':'\\1${W}\\2'}, # **bold** 'code': {'re':r'([`]+[^`]+[`]+)', 'bc':'${c}\\1','dc':'${m}\\1'}, # `code` @@ -178,15 +219,15 @@ inline_match_str={ 'dc':'${b}\\1${Z}\\2${b}\\3(${U}\\4${u})'}, # [text](link) 'image': {'re':r'(!\[[^\]]+\]\([^\)]+\))', 'bc':'${r}\\1','dc':'${g}\\1'}, # ![text](image) - 'underline': {'re':r'(^|\W)(__)([^_]+)(__)', + 'underline': {'re':r'(^|\W)(__)([^_]+)(__)', 'bc':'\\1\\2${U}\\3${Z}\\4','dc':'\\1\\2${U}\\3${Z}\\4'}, # __underline__ 'strikethrough': {'re':r'(~~)([^~]+)(~~)', 'bc':'\\1${st}\\2${so}\\3','dc':'\\1${st}\\2${so}\\3'}, # ~~strike~~ 'checkbox': {'re':r'(\[[x ]\])', 'bc':'${y}\\1','dc':'${r}\\1'}, # [x] [ ] } -inline_match=md_re_compile(inline_match_str) -inlines=['bold1','bold2','code_special','code','image','link','underline','strikethrough', 'checkbox'] +inline_match = md_re_compile(inline_match_str) +inlines = ['bold1','bold2','code_special','code','image','link','underline','strikethrough', 'checkbox'] if __name__ == "__main__": opts=setup_options() @@ -204,33 +245,27 @@ if __name__ == "__main__": template=json.load(open(opts.template,'r')) if 'inlines' in template: inlines=template['inlines'] if 'blocks' in template: blocks=template['blocks'] - if 'block_match' in template: + if 'block_match' in template: block_match_str=template['block_match'] block_match=md_re_compile(block_match_str) - if 'inline_match' in template: + if 'inline_match' in template: inline_match_str=template['inline_match'] inline_match=md_re_compile(inline_match_str) - bc=ansi.code() - if opts.filename=="-" or opts.filename==None: - f=sys.stdin + bc = ansi.code() + if opts.filename == "-" or opts.filename == None: + f = sys.stdin else: - f=open(opts.filename,'r') - data=[] - # Read data - for row in f: - if not row: - continue - row=row.decode('utf-8').rstrip("\n\r ") - data.append(row) + f = open(opts.filename, 'r') + if (sys.version_info > (3, 0)): + data = read_data3(f) + else: + data = read_data2(f) - data=parse(data) - colored=colorize(data,not opts.color,opts.dark_colors,opts.debug) - for c in colored: - sys.stdout.write(c.encode('utf-8')) - if opts.zero: - sys.stdout.write(bc.Z) - sys.stdout.write("\n") - if opts.zero_final: - sys.stdout.write(bc.Z.encode('utf-8')) + data = parse(data) + colored = colorize(data, not opts.color, opts.dark_colors, opts.debug) + if (sys.version_info > (3, 0)): + write_colored3(colored, opts) + else: + write_colored2(colored, opts)