From f678da2330edfcd539eb7ea2b3d20b1bc9dbf681 Mon Sep 17 00:00:00 2001 From: q Date: Mon, 1 Oct 2018 20:36:29 +0300 Subject: [PATCH] markslider python3 upgrades --- files/mvregex | 5 + reporting/ansi.py | 30 ++-- reporting/markslider.packager.sh | 2 +- reporting/markslider.py | 230 ++++++++++++++++++++----------- reporting/markslider.tar.gz | Bin 11336 -> 11698 bytes reporting/md_color.py | 117 ++++++++++------ 6 files changed, 248 insertions(+), 136 deletions(-) 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 a73ef9241a51f8e6819e9baa00a63432867b6c0f..7af32afd6a3082c7f16684f895b8adb2fcf22aa3 100644 GIT binary patch literal 11698 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 KT&QgT$N&Hc6bohm 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)