#!/usr/bin/env python
from Tkinter import *
from tkSimpleDialog import askstring
from tkFileDialog import asksaveasfilename
from tkFileDialog import askopenfilename
from tkMessageBox import askokcancel
import Tkinter as tk
import ttk
import threading
from ScrolledText import ScrolledText
Window = Tk( )
Window.title ( "TekstEDIT" )
index = [ "True" ]
class fontGUI:
def __init__ ( self , parent_win, text) :
self .parent_win = parent_win
self .text = text
self .font_win = Toplevel( self .parent_win )
self .font_win .title ( "Font options" )
self .frame = ttk.Frame ( self .font_win , padding= ( 12 , 12 , 12 , 12 ) )
ttk.Style ( ) .configure ( "Tframe" , background= "beige" )
self .fontsize_options = [ ]
for i in range ( 4 , 74 , 2 ) :
self .fontsize_options .append ( str ( i) )
self .fontstyle_options = [ "Arial" , "Comic Sans" , "Courier New" , "Helvetica" , "Times New Roman" ]
self .fontsize_var = StringVar( )
self .fontstyle_var = StringVar( )
self .fontsize_optbox = apply( OptionMenu, ( self .frame , self .fontsize_var ) + tuple ( self .fontsize_options ) )
self .fontstyle_optbox = apply( OptionMenu, ( self .frame , self .fontstyle_var ) + tuple ( self .fontstyle_options ) )
self .fontsize_var .set ( "12" )
self .fontstyle_var .set ( "Arial" )
self .fontsize_label = ttk.Label ( self .frame , text= "Font Size" )
self .fontstyle_label = ttk.Label ( self .frame , text= "Font Style" )
self .accept_butt = ttk.Button ( self .frame , text= "Accept" , command= self .accept )
self .cancel_butt = ttk.Button ( self .frame , text= "Cancel" , command= lambda : self .font_win .destroy ( ) )
self .frame .grid ( column= 0 , row= 0 , columnspan= 3 , rowspan= 5 )
self .fontsize_label .grid ( column= 0 , row= 0 , sticky= ( N, W, E, S) )
self .fontsize_optbox .grid ( column= 0 , row= 1 , sticky= ( N, W, E, S) )
self .fontstyle_label .grid ( column= 0 , row= 2 , columnspan= 2 , sticky= ( N, W, E, S) )
self .fontstyle_optbox .grid ( column= 0 , row= 3 , columnspan= 2 , sticky= ( N, W, E, S) )
self .accept_butt .grid ( column= 1 , row= 4 , padx= 4 , pady= 6 , sticky= ( N, W, E, S) )
self .cancel_butt .grid ( column= 2 , row= 4 , pady= 6 , sticky= ( N, W, E, S) )
self .font_win .columnconfigure ( "all" , weight= 1 )
self .font_win .rowconfigure ( "all" , weight= 1 )
self .frame .columnconfigure ( "all" , weight= 1 )
self .frame .rowconfigure ( "all" , weight= 1 )
def accept( self ) :
fontsize = self .fontsize_var .get ( )
fontstyle = self .fontstyle_var .get ( )
self .text .config ( font= ( fontstyle, int ( fontsize) ) )
self .font_win .destroy ( )
#Sets up window for find. findNext does all the actual work
class findGUI:
def __init__ ( self , parent_win, text) :
self .parent_win = parent_win
self .text = text
self .find_win = Toplevel( self .parent_win )
self .index = "1.0"
self .find_win .title ( "Find" )
self .frame = ttk.Frame ( self .find_win , padding= ( 12 , 12 , 12 , 12 ) )
ttk.Style ( ) .configure ( "Tframe" , background= "beige" )
self .search_label = ttk.Label ( self .frame , text= "Find:" )
self .search_entry = ttk.Entry ( self .frame )
self .next_but = ttk.Button ( self .frame , text= "Next" , command= lambda : self .findNext ( self .search_entry .get ( ) ) )
self .cancel_but = ttk.Button ( self .frame , text= "Cancel" , command= lambda : self .find_win .destroy ( ) )
self .frame .grid ( column= 0 , row= 0 , columnspan= 3 , rowspan= 2 )
self .search_label .grid ( column= 1 , row= 0 , sticky= ( N, W, E, S) )
self .search_entry .grid ( column= 0 , row= 1 , columnspan= 3 , pady= 4 , sticky= ( N, W, E, S) )
self .next_but .grid ( column= 2 , row= 2 , sticky= ( N, W, E, S) )
self .cancel_but .grid ( column= 3 , row= 2 , sticky= ( N, W, E, S) )
self .find_win .columnconfigure ( "all" , weight= 1 )
self .find_win .rowconfigure ( "all" , weight= 1 )
self .frame .columnconfigure ( "all" , weight= 1 )
self .frame .rowconfigure ( "all" , weight= 1 )
#Each click of next should call findNext which will highlight
#and cycle through found instances of text
def findNext( self , text) :
txt_index = self .text .search ( text, self .index , "end" )
if txt_index:
txt_end = txt_index + "+%dc" %len ( text)
self .text .tag_remove ( "sel" , "1.0" , "end" )
self .text .tag_add ( "sel" , txt_index, txt_end)
self .text .mark_set ( "insert" , txt_end)
self .text .see ( "insert" )
self .index = txt_end
if self .text .compare ( self .index , ">=" , "end-1c" ) :
self .index = "1.0"
class newWindowThread( threading .Thread ) :
def __init__ ( self , choosen= "" ) :
threading .Thread .__init__ ( self )
self .choosen = choosen
def run( self ) :
if self .choosen == "" :
root = Tk( )
newEditor = SimpleEditor( root)
root.mainloop ( )
else :
root = Tk( )
newEditor = SimpleEditor( root, self .choosen )
root.mainloop ( )
class TextLineNumbers( tk.Canvas ) :
def __init__ ( self , *args, **kwargs) :
tk.Canvas .__init__ ( self , *args, **kwargs)
self .textwidget = None
def attach( self , text_widget) :
self .textwidget = text_widget
def redraw( self , *args) :
'''redraw line numbers'''
self .delete ( "all" )
i = self .textwidget .index ( "@0,0" )
while True :
dline= self .textwidget .dlineinfo ( i)
if dline is None : break
y = dline[ 1 ]
linenum = str ( i) .split ( "." ) [ 0 ]
self .create_text ( 2 , y, anchor= "nw" , text= linenum)
i = self .textwidget .index ( "%s+1line" % i)
class CustomText( tk.Text ) :
def __init__ ( self , *args, **kwargs) :
tk.Text .__init__ ( self , *args, **kwargs)
self .tk .eval ( '''
proc widget_proxy {widget widget_command args} {
# call the real tk widget command with the real args
set result [uplevel [linsert $args 0 $widget_command]]
# generate the event for certain types of commands
if {([lindex $args 0] in {insert replace delete}) ||
([lrange $args 0 2] == {mark set insert}) ||
([lrange $args 0 1] == {xview moveto}) ||
([lrange $args 0 1] == {xview scroll}) ||
([lrange $args 0 1] == {yview moveto}) ||
([lrange $args 0 1] == {yview scroll})} {
event generate $widget <<Change>> -when tail
}
# return the result from the real widget command
return $result
}
''' )
self .tk .eval ( '''
rename {widget} _{widget}
interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
''' .format ( widget= str ( self ) ) )
class ScrolledText ( Frame) :
def __init__ ( self , parent= None , text= '' , file = None , background= 'black' ) :
Frame.__init__ ( self , parent)
self .pack ( expand= True , fill= BOTH)
self .makewidgets ( )
self .settext ( text, file )
def makewidgets( self ) :
sbar = Scrollbar( self )
text = Text( self , relief= SUNKEN)
sbar.config ( command= text.yview )
sbar.config ( command= text.xview )
text.config ( yscrollcommand= sbar.set )
text.config ( xscrollcommand= sbar.set )
self .text = text
def settext( self , text= '' , file = None ) :
if file :
text = open ( file , 'r' ) .read ( )
self .text .delete ( '1.0' , END)
self .text .insert ( '1.0' , text)
self .text .mark_set ( INSERT, '1.0' )
self .text .focus ( )
def gettext ( self ) :
return self .text .get ( '1.0' , END+'-1c' )
class Example( tk.Frame ) :
def __init__ ( self , parent, file = None , *args, **kwargs) :
self .choosen = None
self .parent = parent
self .parent .option_add ( "*tearOff" , False )
self .parent .protocol ( "WM_DELETE_WINDOW" , self .onQuit )
tk.Frame .__init__ ( self , *args, **kwargs)
self .text = CustomText( self )
self .vsb = tk.Scrollbar ( orient= "vertical" , command= self .text .yview )
self .hsb = tk.Scrollbar ( orient= "horizontal" , command= self .text .xview )
self .text .configure ( yscrollcommand= self .vsb .set )
self .text .configure ( xscrollcommand= self .hsb .set )
self .text .tag_configure ( "bigfont" , font= ( "Helvetica" , "24" , "bold" ) )
self .linenumbers = TextLineNumbers( self , width= 30 )
self .linenumbers .attach ( self .text )
self .bindings ( )
self .vsb .pack ( side= "right" , fill= "y" )
self .hsb .pack ( side= "bottom" , fill= "y" )
self .linenumbers .pack ( side= "left" , fill= "y" )
self .text .pack ( side= "right" , fill= "both" , expand= True )
self .text .bind ( "<<Change>>" , self ._on_change)
self .text .bind ( "<Configure>" , self ._on_change)
frm= Frame( parent)
frm.pack ( fill= X)
Button( frm, text= 'Find' , command= self .onFind ) .pack ( side= LEFT)
Button( frm, text= 'Night-Mode' , command= self .onNightMode ) .pack ( side= LEFT)
menubar= Menu( Window)
wFile = Menu( menubar, tearoff= 0 , relief= "raised" )
wFile.add_command ( label= "New" , accelerator= "Ctrl+N" , command= self .onNew )
wFile.add_command ( label= "New Window" , accelerator= "Ctrl+Shift+N" , command= self .onNewWindow )
wFile.add_command ( label= "Open..." , accelerator= "Ctrl+O" , command= self .onOpen )
wFile.add_command ( label= "Save" , accelerator= "Ctrl+S" , command= self .onSave )
wFile.add_command ( label= "Save As..." , accelerator= "Ctrl+Shift+S" , command= self .onSaveAs )
wFile.add_separator ( )
wFile.add_command ( label= "Quit" , accelerator= "Ctrl+Q" , command= self .onQuit )
menubar.add_cascade ( label= "File" , menu= wFile)
wEdit = Menu( menubar, tearoff= 0 )
wEdit.add_command ( label= "Cut" , accelerator= "Ctrl+X" , command= self .onCut )
wEdit.add_command ( label= "Copy" , accelerator= "Ctrl+C" , command= self .onCopy )
wEdit.add_command ( label= "Paste" , accelerator= "Ctrl+V" , command= self .onPaste )
menubar.add_cascade ( label= "Edit" , menu= wEdit)
wFormat = Menu( menubar, tearoff= 0 , relief= "raised" )
wFormat.add_command ( label= "Font" , accelerator= "Control+F" , command= self .setFont )
menubar.add_cascade ( label= "Format" , menu= wFormat)
Window.config ( menu= menubar)
# label showing filename
self .choosen_label_var = StringVar( )
self .choosen_label = Label( Window,
textvariable= self .choosen_label_var , anchor= W,
justify= LEFT)
self .choosen_label_var .set ( "" )
self .choosen_label .pack ( )
def setFont( self ) :
fG = fontGUI( self .parent , self .text )
def bindings( self ) :
Window.focus_set ( )
Window.bind_all ( "<Control-C>" , self .onCopy )
Window.bind_all ( "<Control-X>" , self .onCut )
Window.bind_all ( "<Control-V>" , self .onPaste )
Window.bind_all ( "<Control-N>" , self .onNew )
Window.bind_all ( "<Control-F>" , self .setFont )
def onNew( self ) :
self .choosen = None
self .choosen_label_var .set ( "" )
self .text .delete ( 1.0 , END)
def onNewWindow( self ) :
t= newWindowThread( )
t.start ( )
def onSave( self ) :
if not self .choosen :
self .onSaveAs ( )
else :
file = open ( self .choosen , 'w' )
textoutput= self .text .get ( 0.0 , END)
file .write ( textoutput)
file .close ( )
def onSaveAs( self ) :
self .choosen = asksaveasfilename( initialfile= 'Untitled.txt' , defaultextension= ".txt" , filetypes= [ ( "All Files" , "*.*" ) , ( "Text Documents" , "*.txt" ) ] )
if self .choosen :
file = open ( self .choosen , 'w' )
textoutput = self .text .get ( 0.0 , END)
file .write ( textoutput)
file .close ( )
self .choosen_label_var .set ( self .choosen )
def onOpen( self ) :
self .choosen = askopenfilename( )
if self .choosen :
self .choosen_label_var .set ( self .choosen )
self .text .delete ( "1.0" , END)
self .text .insert ( END, open ( self .choosen ) .read ( ) )
def onCut( self ) :
text = self .text .get ( SEL_FIRST, SEL_LAST)
self .text .delete ( SEL_FIRST, SEL_LAST)
self .clipboard_clear ( )
self .clipboard_append ( text)
def onCopy( self ) :
text = self .text .get ( SEL_FIRST, SEL_LAST)
self .clipboard_clear ( )
self .clipboard_append ( text)
def onPaste( self ) :
try :
text = self .selection_get ( selection= 'CLIPBOARD' )
self .text .insert ( INSERT, text)
except TclError:
pass
def onFind( self ) :
fG = findGUI( self .parent , self .text )
def onNightMode( self ) :
if index[ 0 ] :
self .text .config ( font= ( 'courier' , 12 , 'normal' ) , background= 'black' , fg= 'white' , insertbackground= 'white' )
else :
self .text .config ( font= ( 'courier' , 12 , 'normal' ) , background= 'white' , fg= 'black' , insertbackground= 'black' )
index[ 0 ] = not index[ 0 ]
def onQuit( self ) :
ans = askokcancel( 'Verify exit' , "Really quit?" )
if ans:
Frame.quit ( self )
Window.destroy ( )
def _on_change( self , event) :
self .linenumbers .redraw ( )
class SimpleEditor( ScrolledText ) :
def __init__ ( self , parent= None , file = None ) :
Example( Window) .pack ( side= "left" , fill= "both" , expand= True )
ScrolledText .__init__ ( self , parent, file = None )
self .text .config ( font= ( 'courier' , 12 , 'normal' ) )
if __name__ == '__main__' :
try :
SimpleEditor( file = sys .argv [ 1 ] ) .mainloop ( )
except IndexError :
SimpleEditor( ) .mainloop ( )
IyEvdXNyL2Jpbi9lbnYgcHl0aG9uCgpmcm9tIFRraW50ZXIgaW1wb3J0ICoKCmZyb20gdGtTaW1wbGVEaWFsb2cgaW1wb3J0IGFza3N0cmluZwoKZnJvbSB0a0ZpbGVEaWFsb2cgICBpbXBvcnQgYXNrc2F2ZWFzZmlsZW5hbWUKCmZyb20gdGtGaWxlRGlhbG9nIGltcG9ydCBhc2tvcGVuZmlsZW5hbWUKCmZyb20gdGtNZXNzYWdlQm94IGltcG9ydCBhc2tva2NhbmNlbAoKaW1wb3J0IFRraW50ZXIgYXMgdGsKCmltcG9ydCB0dGsgCgppbXBvcnQgdGhyZWFkaW5nCgpmcm9tIFNjcm9sbGVkVGV4dCBpbXBvcnQgU2Nyb2xsZWRUZXh0CgpXaW5kb3cgPSBUaygpIApXaW5kb3cudGl0bGUoIlRla3N0RURJVCIpCmluZGV4ID0gWyJUcnVlIl0KCgpjbGFzcyBmb250R1VJOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHBhcmVudF93aW4sIHRleHQpOgogICAgICAgIHNlbGYucGFyZW50X3dpbiA9IHBhcmVudF93aW4KICAgICAgICBzZWxmLnRleHQgPSB0ZXh0CgogICAgICAgIHNlbGYuZm9udF93aW4gPSBUb3BsZXZlbChzZWxmLnBhcmVudF93aW4pCiAgICAgICAgc2VsZi5mb250X3dpbi50aXRsZSgiRm9udCBvcHRpb25zIikKCiAgICAgICAgc2VsZi5mcmFtZSA9IHR0ay5GcmFtZShzZWxmLmZvbnRfd2luLCBwYWRkaW5nPSgxMiwxMiwxMiwxMikpCiAgICAgICAgdHRrLlN0eWxlKCkuY29uZmlndXJlKCJUZnJhbWUiLCBiYWNrZ3JvdW5kPSJiZWlnZSIpCgogICAgICAgIHNlbGYuZm9udHNpemVfb3B0aW9ucyA9IFtdCiAgICAgICAgZm9yIGkgaW4gcmFuZ2UoNCwgNzQsIDIpOgogICAgICAgICAgICBzZWxmLmZvbnRzaXplX29wdGlvbnMuYXBwZW5kKHN0cihpKSkKICAgICAgICAKICAgICAgICBzZWxmLmZvbnRzdHlsZV9vcHRpb25zID0gWyJBcmlhbCIsICJDb21pYyBTYW5zIiwgIkNvdXJpZXIgTmV3IiwgIkhlbHZldGljYSIsICJUaW1lcyBOZXcgUm9tYW4iXQoKICAgICAgICBzZWxmLmZvbnRzaXplX3ZhciA9IFN0cmluZ1ZhcigpCiAgICAgICAgc2VsZi5mb250c3R5bGVfdmFyID0gU3RyaW5nVmFyKCkKCiAgICAgICAgc2VsZi5mb250c2l6ZV9vcHRib3ggPSBhcHBseShPcHRpb25NZW51LCAoc2VsZi5mcmFtZSwgc2VsZi5mb250c2l6ZV92YXIpICsgdHVwbGUoc2VsZi5mb250c2l6ZV9vcHRpb25zKSkKICAgICAgICBzZWxmLmZvbnRzdHlsZV9vcHRib3ggPSBhcHBseShPcHRpb25NZW51LCAoc2VsZi5mcmFtZSwgc2VsZi5mb250c3R5bGVfdmFyKSArIHR1cGxlKHNlbGYuZm9udHN0eWxlX29wdGlvbnMpKQoKICAgICAgICBzZWxmLmZvbnRzaXplX3Zhci5zZXQoIjEyIikKICAgICAgICBzZWxmLmZvbnRzdHlsZV92YXIuc2V0KCJBcmlhbCIpCgogICAgICAgIHNlbGYuZm9udHNpemVfbGFiZWwgPSB0dGsuTGFiZWwoc2VsZi5mcmFtZSwgdGV4dD0iRm9udCBTaXplIikKICAgICAgICBzZWxmLmZvbnRzdHlsZV9sYWJlbCA9IHR0ay5MYWJlbChzZWxmLmZyYW1lLCB0ZXh0PSJGb250IFN0eWxlIikKCiAgICAgICAgc2VsZi5hY2NlcHRfYnV0dCA9IHR0ay5CdXR0b24oc2VsZi5mcmFtZSwgdGV4dD0iQWNjZXB0IiwgY29tbWFuZD1zZWxmLmFjY2VwdCkKICAgICAgICBzZWxmLmNhbmNlbF9idXR0ID0gdHRrLkJ1dHRvbihzZWxmLmZyYW1lLCB0ZXh0PSJDYW5jZWwiLCBjb21tYW5kPWxhbWJkYTogc2VsZi5mb250X3dpbi5kZXN0cm95KCkpCgogICAgICAgIHNlbGYuZnJhbWUuZ3JpZChjb2x1bW49MCwgcm93PTAsIGNvbHVtbnNwYW49Mywgcm93c3Bhbj01KQogICAgICAgIHNlbGYuZm9udHNpemVfbGFiZWwuZ3JpZChjb2x1bW49MCwgcm93PTAsIHN0aWNreT0oTixXLEUsUykpCiAgICAgICAgc2VsZi5mb250c2l6ZV9vcHRib3guZ3JpZChjb2x1bW49MCwgcm93PTEsIHN0aWNreT0oTixXLEUsUykpCiAgICAgICAgc2VsZi5mb250c3R5bGVfbGFiZWwuZ3JpZChjb2x1bW49MCwgcm93PTIsIGNvbHVtbnNwYW49Miwgc3RpY2t5PShOLFcsRSxTKSkKICAgICAgICBzZWxmLmZvbnRzdHlsZV9vcHRib3guZ3JpZChjb2x1bW49MCwgcm93PTMsIGNvbHVtbnNwYW49Miwgc3RpY2t5PShOLFcsRSxTKSkKICAgICAgICBzZWxmLmFjY2VwdF9idXR0LmdyaWQoY29sdW1uPTEsIHJvdz00LCBwYWR4PTQsIHBhZHk9Niwgc3RpY2t5PShOLFcsRSxTKSkKICAgICAgICBzZWxmLmNhbmNlbF9idXR0LmdyaWQoY29sdW1uPTIsIHJvdz00LCBwYWR5PTYsIHN0aWNreT0oTixXLEUsUykpCgogICAgICAgIHNlbGYuZm9udF93aW4uY29sdW1uY29uZmlndXJlKCJhbGwiLCB3ZWlnaHQ9MSkKICAgICAgICBzZWxmLmZvbnRfd2luLnJvd2NvbmZpZ3VyZSgiYWxsIiwgd2VpZ2h0PTEpCiAgICAgICAgc2VsZi5mcmFtZS5jb2x1bW5jb25maWd1cmUoImFsbCIsIHdlaWdodD0xKQogICAgICAgIHNlbGYuZnJhbWUucm93Y29uZmlndXJlKCJhbGwiLCB3ZWlnaHQ9MSkKCiAgICBkZWYgYWNjZXB0KHNlbGYpOgogICAgICAgIGZvbnRzaXplID0gc2VsZi5mb250c2l6ZV92YXIuZ2V0KCkKICAgICAgICBmb250c3R5bGUgPSBzZWxmLmZvbnRzdHlsZV92YXIuZ2V0KCkKICAgICAgICBzZWxmLnRleHQuY29uZmlnKGZvbnQ9KGZvbnRzdHlsZSwgaW50KGZvbnRzaXplKSkpCiAgICAgICAgc2VsZi5mb250X3dpbi5kZXN0cm95KCkKICAgIAojU2V0cyB1cCB3aW5kb3cgZm9yIGZpbmQuIGZpbmROZXh0IGRvZXMgYWxsIHRoZSBhY3R1YWwgd29yawpjbGFzcyBmaW5kR1VJOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHBhcmVudF93aW4sIHRleHQpOgogICAgICAgIHNlbGYucGFyZW50X3dpbiA9IHBhcmVudF93aW4KICAgICAgICBzZWxmLnRleHQgPSB0ZXh0CiAgICAgICAgCiAgICAgICAgc2VsZi5maW5kX3dpbiA9IFRvcGxldmVsKHNlbGYucGFyZW50X3dpbikKICAgICAgICBzZWxmLmluZGV4ID0gIjEuMCIKICAgICAgICAKICAgICAgICBzZWxmLmZpbmRfd2luLnRpdGxlKCJGaW5kIikKICAgICAgICBzZWxmLmZyYW1lID0gdHRrLkZyYW1lKHNlbGYuZmluZF93aW4sIHBhZGRpbmc9KDEyLDEyLDEyLDEyKSkKICAgICAgICB0dGsuU3R5bGUoKS5jb25maWd1cmUoIlRmcmFtZSIsIGJhY2tncm91bmQ9ImJlaWdlIikKCiAgICAgICAgc2VsZi5zZWFyY2hfbGFiZWwgPSB0dGsuTGFiZWwoc2VsZi5mcmFtZSwgdGV4dD0iRmluZDoiKQogICAgICAgIHNlbGYuc2VhcmNoX2VudHJ5ID0gdHRrLkVudHJ5KHNlbGYuZnJhbWUpCgogICAgICAgIHNlbGYubmV4dF9idXQgPSB0dGsuQnV0dG9uKHNlbGYuZnJhbWUsIHRleHQ9Ik5leHQiLCBjb21tYW5kPWxhbWJkYTogc2VsZi5maW5kTmV4dChzZWxmLnNlYXJjaF9lbnRyeS5nZXQoKSkpCiAgICAgICAgc2VsZi5jYW5jZWxfYnV0ID0gdHRrLkJ1dHRvbihzZWxmLmZyYW1lLCB0ZXh0PSJDYW5jZWwiLCBjb21tYW5kPWxhbWJkYTogc2VsZi5maW5kX3dpbi5kZXN0cm95KCkpCgogICAgICAgIHNlbGYuZnJhbWUuZ3JpZChjb2x1bW49MCwgcm93PTAsIGNvbHVtbnNwYW49Mywgcm93c3Bhbj0yKQogICAgICAgIHNlbGYuc2VhcmNoX2xhYmVsLmdyaWQoY29sdW1uPTEsIHJvdz0wLCBzdGlja3k9KE4sVyxFLFMpKQogICAgICAgIHNlbGYuc2VhcmNoX2VudHJ5LmdyaWQoY29sdW1uPTAsIHJvdz0xLCBjb2x1bW5zcGFuPTMsIHBhZHk9NCwgc3RpY2t5PShOLFcsRSxTKSkKICAgICAgICBzZWxmLm5leHRfYnV0LmdyaWQoY29sdW1uPTIsIHJvdz0yLCBzdGlja3k9KE4sVyxFLFMpKQogICAgICAgIHNlbGYuY2FuY2VsX2J1dC5ncmlkKGNvbHVtbj0zLHJvdz0yLCBzdGlja3k9KE4sVyxFLFMpKQoKICAgICAgICBzZWxmLmZpbmRfd2luLmNvbHVtbmNvbmZpZ3VyZSgiYWxsIiwgd2VpZ2h0PTEpCiAgICAgICAgc2VsZi5maW5kX3dpbi5yb3djb25maWd1cmUoImFsbCIsIHdlaWdodD0xKQogICAgICAgIHNlbGYuZnJhbWUuY29sdW1uY29uZmlndXJlKCJhbGwiLCB3ZWlnaHQ9MSkKICAgICAgICBzZWxmLmZyYW1lLnJvd2NvbmZpZ3VyZSgiYWxsIiwgd2VpZ2h0PTEpCgogICAgI0VhY2ggY2xpY2sgb2YgbmV4dCBzaG91bGQgY2FsbCBmaW5kTmV4dCB3aGljaCB3aWxsIGhpZ2hsaWdodAogICAgI2FuZCBjeWNsZSB0aHJvdWdoIGZvdW5kIGluc3RhbmNlcyBvZiB0ZXh0CiAgICBkZWYgZmluZE5leHQoc2VsZiwgdGV4dCk6CiAgICAgICAgdHh0X2luZGV4ID0gc2VsZi50ZXh0LnNlYXJjaCh0ZXh0LCBzZWxmLmluZGV4LCAiZW5kIikKICAgICAgICBpZiB0eHRfaW5kZXg6CiAgICAgICAgICAgIHR4dF9lbmQgPSB0eHRfaW5kZXggKyAiKyVkYyIlbGVuKHRleHQpCiAgICAgICAgICAgIHNlbGYudGV4dC50YWdfcmVtb3ZlKCJzZWwiLCAiMS4wIiwgImVuZCIpCiAgICAgICAgICAgIHNlbGYudGV4dC50YWdfYWRkKCJzZWwiLCB0eHRfaW5kZXgsIHR4dF9lbmQpCiAgICAgICAgICAgIHNlbGYudGV4dC5tYXJrX3NldCgiaW5zZXJ0IiwgdHh0X2VuZCkKICAgICAgICAgICAgc2VsZi50ZXh0LnNlZSgiaW5zZXJ0IikKICAgICAgICAgICAgc2VsZi5pbmRleCA9IHR4dF9lbmQKICAgICAgICAgICAgaWYgc2VsZi50ZXh0LmNvbXBhcmUoc2VsZi5pbmRleCwgIj49IiwgImVuZC0xYyIpOgogICAgICAgICAgICAgICAgc2VsZi5pbmRleCA9ICIxLjAiCgpjbGFzcyBuZXdXaW5kb3dUaHJlYWQodGhyZWFkaW5nLlRocmVhZCk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgY2hvb3Nlbj0iIik6CiAgICAgICAgdGhyZWFkaW5nLlRocmVhZC5fX2luaXRfXyhzZWxmKQogICAgICAgIHNlbGYuY2hvb3NlbiA9IGNob29zZW4KCiAgICBkZWYgcnVuKHNlbGYpOgogICAgICAgIGlmIHNlbGYuY2hvb3NlbiA9PSAiIjoKICAgICAgICAgICAgcm9vdCA9IFRrKCkKICAgICAgICAgICAgbmV3RWRpdG9yID0gU2ltcGxlRWRpdG9yKHJvb3QpCiAgICAgICAgICAgIHJvb3QubWFpbmxvb3AoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJvb3QgPSBUaygpCiAgICAgICAgICAgIG5ld0VkaXRvciA9IFNpbXBsZUVkaXRvcihyb290LCBzZWxmLmNob29zZW4pCiAgICAgICAgICAgIHJvb3QubWFpbmxvb3AoKQoKY2xhc3MgVGV4dExpbmVOdW1iZXJzKHRrLkNhbnZhcyk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgKmFyZ3MsICoqa3dhcmdzKToKICAgICAgICB0ay5DYW52YXMuX19pbml0X18oc2VsZiwgKmFyZ3MsICoqa3dhcmdzKQogICAgICAgIHNlbGYudGV4dHdpZGdldCA9IE5vbmUKCiAgICBkZWYgYXR0YWNoKHNlbGYsIHRleHRfd2lkZ2V0KToKICAgICAgICBzZWxmLnRleHR3aWRnZXQgPSB0ZXh0X3dpZGdldAoKICAgIGRlZiByZWRyYXcoc2VsZiwgKmFyZ3MpOgogICAgICAgICcnJ3JlZHJhdyBsaW5lIG51bWJlcnMnJycKICAgICAgICBzZWxmLmRlbGV0ZSgiYWxsIikKCiAgICAgICAgaSA9IHNlbGYudGV4dHdpZGdldC5pbmRleCgiQDAsMCIpCiAgICAgICAgd2hpbGUgVHJ1ZSA6CiAgICAgICAgICAgIGRsaW5lPSBzZWxmLnRleHR3aWRnZXQuZGxpbmVpbmZvKGkpCiAgICAgICAgICAgIGlmIGRsaW5lIGlzIE5vbmU6IGJyZWFrCiAgICAgICAgICAgIHkgPSBkbGluZVsxXQogICAgICAgICAgICBsaW5lbnVtID0gc3RyKGkpLnNwbGl0KCIuIilbMF0KICAgICAgICAgICAgc2VsZi5jcmVhdGVfdGV4dCgyLHksYW5jaG9yPSJudyIsIHRleHQ9bGluZW51bSkKICAgICAgICAgICAgaSA9IHNlbGYudGV4dHdpZGdldC5pbmRleCgiJXMrMWxpbmUiICUgaSkKCmNsYXNzIEN1c3RvbVRleHQodGsuVGV4dCk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgKmFyZ3MsICoqa3dhcmdzKToKICAgICAgICB0ay5UZXh0Ll9faW5pdF9fKHNlbGYsICphcmdzLCAqKmt3YXJncykKCiAgICAgICAgc2VsZi50ay5ldmFsKCcnJwogICAgICAgICAgICBwcm9jIHdpZGdldF9wcm94eSB7d2lkZ2V0IHdpZGdldF9jb21tYW5kIGFyZ3N9IHsKCiAgICAgICAgICAgICAgICAjIGNhbGwgdGhlIHJlYWwgdGsgd2lkZ2V0IGNvbW1hbmQgd2l0aCB0aGUgcmVhbCBhcmdzCiAgICAgICAgICAgICAgICBzZXQgcmVzdWx0IFt1cGxldmVsIFtsaW5zZXJ0ICRhcmdzIDAgJHdpZGdldF9jb21tYW5kXV0KCiAgICAgICAgICAgICAgICAjIGdlbmVyYXRlIHRoZSBldmVudCBmb3IgY2VydGFpbiB0eXBlcyBvZiBjb21tYW5kcwogICAgICAgICAgICAgICAgaWYgeyhbbGluZGV4ICRhcmdzIDBdIGluIHtpbnNlcnQgcmVwbGFjZSBkZWxldGV9KSB8fAogICAgICAgICAgICAgICAgICAgIChbbHJhbmdlICRhcmdzIDAgMl0gPT0ge21hcmsgc2V0IGluc2VydH0pIHx8IAogICAgICAgICAgICAgICAgICAgIChbbHJhbmdlICRhcmdzIDAgMV0gPT0ge3h2aWV3IG1vdmV0b30pIHx8CiAgICAgICAgICAgICAgICAgICAgKFtscmFuZ2UgJGFyZ3MgMCAxXSA9PSB7eHZpZXcgc2Nyb2xsfSkgfHwKICAgICAgICAgICAgICAgICAgICAoW2xyYW5nZSAkYXJncyAwIDFdID09IHt5dmlldyBtb3ZldG99KSB8fAogICAgICAgICAgICAgICAgICAgIChbbHJhbmdlICRhcmdzIDAgMV0gPT0ge3l2aWV3IHNjcm9sbH0pfSB7CgogICAgICAgICAgICAgICAgICAgIGV2ZW50IGdlbmVyYXRlICAkd2lkZ2V0IDw8Q2hhbmdlPj4gLXdoZW4gdGFpbAogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICMgcmV0dXJuIHRoZSByZXN1bHQgZnJvbSB0aGUgcmVhbCB3aWRnZXQgY29tbWFuZAogICAgICAgICAgICAgICAgcmV0dXJuICRyZXN1bHQKICAgICAgICAgICAgfQogICAgICAgICAgICAnJycpCiAgICAgICAgc2VsZi50ay5ldmFsKCcnJwogICAgICAgICAgICByZW5hbWUge3dpZGdldH0gX3t3aWRnZXR9CiAgICAgICAgICAgIGludGVycCBhbGlhcyB7e319IDo6e3dpZGdldH0ge3t9fSB3aWRnZXRfcHJveHkge3dpZGdldH0gX3t3aWRnZXR9CiAgICAgICAgJycnLmZvcm1hdCh3aWRnZXQ9c3RyKHNlbGYpKSkKCmNsYXNzIFNjcm9sbGVkVGV4dChGcmFtZSk6CgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHBhcmVudD1Ob25lLCB0ZXh0PScnLCBmaWxlPU5vbmUsIGJhY2tncm91bmQ9J2JsYWNrJyk6CgogICAgICAgIEZyYW1lLl9faW5pdF9fKHNlbGYsIHBhcmVudCkKCiAgICAgICAgc2VsZi5wYWNrKGV4cGFuZD1UcnVlLCBmaWxsPUJPVEgpCgogICAgICAgIHNlbGYubWFrZXdpZGdldHMoKQoKICAgICAgICBzZWxmLnNldHRleHQodGV4dCwgZmlsZSkKCiAgICBkZWYgbWFrZXdpZGdldHMoc2VsZik6CgogICAgICAgIHNiYXIgPSBTY3JvbGxiYXIoc2VsZikKCiAgICAgICAgdGV4dCA9IFRleHQoc2VsZiwgcmVsaWVmPVNVTktFTikKCiAgICAgICAgc2Jhci5jb25maWcoY29tbWFuZD10ZXh0Lnl2aWV3KQoKICAgICAgICBzYmFyLmNvbmZpZyhjb21tYW5kPXRleHQueHZpZXcpCgogICAgICAgIHRleHQuY29uZmlnKHlzY3JvbGxjb21tYW5kPXNiYXIuc2V0KQoKICAgICAgICB0ZXh0LmNvbmZpZyh4c2Nyb2xsY29tbWFuZD1zYmFyLnNldCkKCiAgICAgICAgc2VsZi50ZXh0ID0gdGV4dAoKICAgIGRlZiBzZXR0ZXh0KHNlbGYsIHRleHQ9JycsIGZpbGU9Tm9uZSk6CgogICAgICAgIGlmIGZpbGU6CgogICAgICAgICAgICB0ZXh0ID0gb3BlbihmaWxlLCAncicpLnJlYWQoKQoKICAgICAgICBzZWxmLnRleHQuZGVsZXRlKCcxLjAnLCBFTkQpCgogICAgICAgIHNlbGYudGV4dC5pbnNlcnQoJzEuMCcsIHRleHQpCgogICAgICAgIHNlbGYudGV4dC5tYXJrX3NldChJTlNFUlQsICcxLjAnKQoKICAgICAgICBzZWxmLnRleHQuZm9jdXMoKQoKICAgIGRlZiBnZXR0ZXh0KHNlbGYpOgoKICAgICAgICByZXR1cm4gc2VsZi50ZXh0LmdldCgnMS4wJywgRU5EKyctMWMnKQoKCmNsYXNzIEV4YW1wbGUodGsuRnJhbWUpOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHBhcmVudCxmaWxlPU5vbmUsKmFyZ3MsICoqa3dhcmdzKToKICAgICAgICBzZWxmLmNob29zZW4gPSBOb25lCiAgICAgICAgc2VsZi5wYXJlbnQgPSBwYXJlbnQKICAgICAgICBzZWxmLnBhcmVudC5vcHRpb25fYWRkKCIqdGVhck9mZiIsIEZhbHNlKQogICAgICAgIHNlbGYucGFyZW50LnByb3RvY29sKCJXTV9ERUxFVEVfV0lORE9XIiwgc2VsZi5vblF1aXQpCiAgICAgICAgdGsuRnJhbWUuX19pbml0X18oc2VsZiwgKmFyZ3MsICoqa3dhcmdzKQogICAgICAgIHNlbGYudGV4dCA9IEN1c3RvbVRleHQoc2VsZikKICAgICAgICBzZWxmLnZzYiA9IHRrLlNjcm9sbGJhcihvcmllbnQ9InZlcnRpY2FsIiwgY29tbWFuZD1zZWxmLnRleHQueXZpZXcpCiAgICAgICAgc2VsZi5oc2IgPSB0ay5TY3JvbGxiYXIob3JpZW50PSJob3Jpem9udGFsIiwgY29tbWFuZD1zZWxmLnRleHQueHZpZXcpCiAgICAgICAgc2VsZi50ZXh0LmNvbmZpZ3VyZSh5c2Nyb2xsY29tbWFuZD1zZWxmLnZzYi5zZXQpCiAgICAgICAgc2VsZi50ZXh0LmNvbmZpZ3VyZSh4c2Nyb2xsY29tbWFuZD1zZWxmLmhzYi5zZXQpCiAgICAgICAgc2VsZi50ZXh0LnRhZ19jb25maWd1cmUoImJpZ2ZvbnQiLCBmb250PSgiSGVsdmV0aWNhIiwgIjI0IiwgImJvbGQiKSkKICAgICAgICBzZWxmLmxpbmVudW1iZXJzID0gVGV4dExpbmVOdW1iZXJzKHNlbGYsIHdpZHRoPTMwKQogICAgICAgIHNlbGYubGluZW51bWJlcnMuYXR0YWNoKHNlbGYudGV4dCkKICAgICAgICBzZWxmLmJpbmRpbmdzKCkKICAgICAgICAKICAgICAgICBzZWxmLnZzYi5wYWNrKHNpZGU9InJpZ2h0IiwgZmlsbD0ieSIpCiAgICAgICAgc2VsZi5oc2IucGFjayhzaWRlPSJib3R0b20iLCBmaWxsPSJ5IikKICAgICAgICBzZWxmLmxpbmVudW1iZXJzLnBhY2soc2lkZT0ibGVmdCIsIGZpbGw9InkiKQogICAgICAgIHNlbGYudGV4dC5wYWNrKHNpZGU9InJpZ2h0IiwgZmlsbD0iYm90aCIsIGV4cGFuZD1UcnVlKQoKICAgICAgICBzZWxmLnRleHQuYmluZCgiPDxDaGFuZ2U+PiIsIHNlbGYuX29uX2NoYW5nZSkKICAgICAgICBzZWxmLnRleHQuYmluZCgiPENvbmZpZ3VyZT4iLCBzZWxmLl9vbl9jaGFuZ2UpCgogICAgICAgIGZybT1GcmFtZShwYXJlbnQpCgogICAgICAgIGZybS5wYWNrKGZpbGw9WCkKCiAgICAgICAgQnV0dG9uKGZybSwgdGV4dD0nRmluZCcsICBjb21tYW5kPXNlbGYub25GaW5kKS5wYWNrKHNpZGU9TEVGVCkKCiAgICAgICAgQnV0dG9uKGZybSwgdGV4dD0nTmlnaHQtTW9kZScsICBjb21tYW5kPXNlbGYub25OaWdodE1vZGUpLnBhY2soc2lkZT1MRUZUKQoKICAgICAgICBtZW51YmFyPU1lbnUoV2luZG93KQoKICAgICAgICB3RmlsZSA9IE1lbnUobWVudWJhciwgdGVhcm9mZj0wLHJlbGllZj0icmFpc2VkIikKICAgICAgICB3RmlsZS5hZGRfY29tbWFuZChsYWJlbD0iTmV3IiwgYWNjZWxlcmF0b3I9IkN0cmwrTiIsIGNvbW1hbmQ9c2VsZi5vbk5ldykKICAgICAgICB3RmlsZS5hZGRfY29tbWFuZChsYWJlbD0iTmV3IFdpbmRvdyIsIGFjY2VsZXJhdG9yPSJDdHJsK1NoaWZ0K04iLCBjb21tYW5kPXNlbGYub25OZXdXaW5kb3cpCiAgICAgICAgd0ZpbGUuYWRkX2NvbW1hbmQobGFiZWw9Ik9wZW4uLi4iLCBhY2NlbGVyYXRvcj0iQ3RybCtPIiwgY29tbWFuZD1zZWxmLm9uT3BlbikKICAgICAgICB3RmlsZS5hZGRfY29tbWFuZChsYWJlbD0iU2F2ZSIsIGFjY2VsZXJhdG9yPSJDdHJsK1MiLCBjb21tYW5kPXNlbGYub25TYXZlKQogICAgICAgIHdGaWxlLmFkZF9jb21tYW5kKGxhYmVsPSJTYXZlIEFzLi4uIiwgYWNjZWxlcmF0b3I9IkN0cmwrU2hpZnQrUyIsIGNvbW1hbmQ9c2VsZi5vblNhdmVBcykKICAgICAgICB3RmlsZS5hZGRfc2VwYXJhdG9yKCkKICAgICAgICB3RmlsZS5hZGRfY29tbWFuZChsYWJlbD0iUXVpdCIsIGFjY2VsZXJhdG9yPSJDdHJsK1EiLCBjb21tYW5kPXNlbGYub25RdWl0KQoKICAgICAgICBtZW51YmFyLmFkZF9jYXNjYWRlKGxhYmVsPSJGaWxlIiwgbWVudT13RmlsZSkKCiAgICAgICAgd0VkaXQgPSBNZW51KG1lbnViYXIsIHRlYXJvZmY9MCkKICAgICAgICB3RWRpdC5hZGRfY29tbWFuZChsYWJlbD0iQ3V0IiwgYWNjZWxlcmF0b3I9IkN0cmwrWCIsIGNvbW1hbmQ9c2VsZi5vbkN1dCkKICAgICAgICB3RWRpdC5hZGRfY29tbWFuZChsYWJlbD0iQ29weSIsYWNjZWxlcmF0b3I9IkN0cmwrQyIsIGNvbW1hbmQ9c2VsZi5vbkNvcHkpCiAgICAgICAgd0VkaXQuYWRkX2NvbW1hbmQobGFiZWw9IlBhc3RlIiwgYWNjZWxlcmF0b3I9IkN0cmwrViIsY29tbWFuZD1zZWxmLm9uUGFzdGUpCgogICAgICAgIG1lbnViYXIuYWRkX2Nhc2NhZGUobGFiZWw9IkVkaXQiLCBtZW51PXdFZGl0KQoKICAgICAgICB3Rm9ybWF0ID0gTWVudShtZW51YmFyLCB0ZWFyb2ZmPTAscmVsaWVmPSJyYWlzZWQiKQogICAgICAgIHdGb3JtYXQuYWRkX2NvbW1hbmQobGFiZWw9IkZvbnQiLCBhY2NlbGVyYXRvcj0iQ29udHJvbCtGIiwgY29tbWFuZD1zZWxmLnNldEZvbnQpCgogICAgICAgIG1lbnViYXIuYWRkX2Nhc2NhZGUobGFiZWw9IkZvcm1hdCIsIG1lbnU9d0Zvcm1hdCkKCgogICAgICAgIFdpbmRvdy5jb25maWcobWVudT1tZW51YmFyKQoKICAgICAgICAjIGxhYmVsIHNob3dpbmcgZmlsZW5hbWUKICAgICAgICBzZWxmLmNob29zZW5fbGFiZWxfdmFyID0gU3RyaW5nVmFyKCkKICAgICAgICBzZWxmLmNob29zZW5fbGFiZWwgPSBMYWJlbChXaW5kb3csCiAgICAgICAgICAgICAgICB0ZXh0dmFyaWFibGU9c2VsZi5jaG9vc2VuX2xhYmVsX3ZhciwgYW5jaG9yPVcsIAogICAgICAgICAgICAgICAganVzdGlmeT1MRUZUKQogICAgICAgIHNlbGYuY2hvb3Nlbl9sYWJlbF92YXIuc2V0KCIiKQogICAgICAgIHNlbGYuY2hvb3Nlbl9sYWJlbC5wYWNrKCkKCiAgICBkZWYgc2V0Rm9udChzZWxmKToKICAgICAgICBmRyA9IGZvbnRHVUkoc2VsZi5wYXJlbnQsIHNlbGYudGV4dCkKICAgIAogICAgZGVmIGJpbmRpbmdzKHNlbGYpOgogICAgICAgIFdpbmRvdy5mb2N1c19zZXQoKQogICAgICAgIFdpbmRvdy5iaW5kX2FsbCgiPENvbnRyb2wtQz4iLCBzZWxmLm9uQ29weSkKICAgICAgICBXaW5kb3cuYmluZF9hbGwoIjxDb250cm9sLVg+Iiwgc2VsZi5vbkN1dCkKICAgICAgICBXaW5kb3cuYmluZF9hbGwoIjxDb250cm9sLVY+Iiwgc2VsZi5vblBhc3RlKQogICAgICAgIFdpbmRvdy5iaW5kX2FsbCgiPENvbnRyb2wtTj4iLCBzZWxmLm9uTmV3KQogICAgICAgIFdpbmRvdy5iaW5kX2FsbCgiPENvbnRyb2wtRj4iLCBzZWxmLnNldEZvbnQpCiAgICAgICAgCiAgICBkZWYgb25OZXcoc2VsZik6CiAgICAgICAgc2VsZi5jaG9vc2VuID0gTm9uZQogICAgICAgIHNlbGYuY2hvb3Nlbl9sYWJlbF92YXIuc2V0KCIiKQogICAgICAgIHNlbGYudGV4dC5kZWxldGUoMS4wLCBFTkQpCgogICAgZGVmIG9uTmV3V2luZG93KHNlbGYpOgogICAgICAgIHQ9bmV3V2luZG93VGhyZWFkKCkKICAgICAgICB0LnN0YXJ0KCkKICAgICAgICAKICAgIGRlZiBvblNhdmUoc2VsZik6CiAgICAgICAgaWYgbm90IHNlbGYuY2hvb3NlbjoKICAgICAgICAgICAgc2VsZi5vblNhdmVBcygpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgZmlsZSA9IG9wZW4oc2VsZi5jaG9vc2VuLCAndycpCiAgICAgICAgICAgIHRleHRvdXRwdXQ9c2VsZi50ZXh0LmdldCgwLjAsIEVORCkKICAgICAgICAgICAgZmlsZS53cml0ZSh0ZXh0b3V0cHV0KQogICAgICAgICAgICBmaWxlLmNsb3NlKCkKICAgIAogICAgZGVmIG9uU2F2ZUFzKHNlbGYpOgogICAgICAgIHNlbGYuY2hvb3NlbiA9IGFza3NhdmVhc2ZpbGVuYW1lKGluaXRpYWxmaWxlPSdVbnRpdGxlZC50eHQnLGRlZmF1bHRleHRlbnNpb249Ii50eHQiLGZpbGV0eXBlcz1bKCJBbGwgRmlsZXMiLCIqLioiKSwoIlRleHQgRG9jdW1lbnRzIiwiKi50eHQiKV0pCiAgICAgICAgaWYgc2VsZi5jaG9vc2VuOgogICAgICAgICAgICBmaWxlID0gb3BlbihzZWxmLmNob29zZW4sICd3JykKICAgICAgICAgICAgdGV4dG91dHB1dCA9IHNlbGYudGV4dC5nZXQoMC4wLCBFTkQpCiAgICAgICAgICAgIGZpbGUud3JpdGUodGV4dG91dHB1dCkKICAgICAgICAgICAgZmlsZS5jbG9zZSgpCiAgICAgICAgICAgIHNlbGYuY2hvb3Nlbl9sYWJlbF92YXIuc2V0KHNlbGYuY2hvb3NlbikKCiAgICBkZWYgb25PcGVuKHNlbGYpOgogICAgICAgIAogICAgICAgIHNlbGYuY2hvb3NlbiA9IGFza29wZW5maWxlbmFtZSgpCiAgICAgICAgaWYgc2VsZi5jaG9vc2VuOgogICAgICAgICAgICBzZWxmLmNob29zZW5fbGFiZWxfdmFyLnNldChzZWxmLmNob29zZW4pCiAgICAgICAgICAgIHNlbGYudGV4dC5kZWxldGUoIjEuMCIsRU5EKQogICAgICAgICAgICBzZWxmLnRleHQuaW5zZXJ0KEVORCwgb3BlbihzZWxmLmNob29zZW4pLnJlYWQoKSkKICAgIAogICAgZGVmIG9uQ3V0KHNlbGYpOgoKICAgICAgICB0ZXh0ID0gc2VsZi50ZXh0LmdldChTRUxfRklSU1QsIFNFTF9MQVNUKQoKICAgICAgICBzZWxmLnRleHQuZGVsZXRlKFNFTF9GSVJTVCwgU0VMX0xBU1QpCgogICAgICAgIHNlbGYuY2xpcGJvYXJkX2NsZWFyKCkKCiAgICAgICAgc2VsZi5jbGlwYm9hcmRfYXBwZW5kKHRleHQpCgogICAgZGVmIG9uQ29weShzZWxmKToKCiAgICAgICAgdGV4dCA9IHNlbGYudGV4dC5nZXQoU0VMX0ZJUlNULCBTRUxfTEFTVCkKCiAgICAgICAgc2VsZi5jbGlwYm9hcmRfY2xlYXIoKQoKICAgICAgICBzZWxmLmNsaXBib2FyZF9hcHBlbmQodGV4dCkKCgogICAgZGVmIG9uUGFzdGUoc2VsZik6CgogICAgICAgIHRyeToKCiAgICAgICAgICAgIHRleHQgPSBzZWxmLnNlbGVjdGlvbl9nZXQoc2VsZWN0aW9uPSdDTElQQk9BUkQnKQoKICAgICAgICAgICAgc2VsZi50ZXh0Lmluc2VydChJTlNFUlQsIHRleHQpCgogICAgICAgIGV4Y2VwdCBUY2xFcnJvcjoKCiAgICAgICAgICAgIHBhc3MKCiAgICBkZWYgb25GaW5kKHNlbGYpOgogICAgICAgIGZHID0gZmluZEdVSShzZWxmLnBhcmVudCwgc2VsZi50ZXh0KQoKICAgIGRlZiBvbk5pZ2h0TW9kZShzZWxmKToKICAgICAgICBpZiBpbmRleFswXToKICAgICAgICAgICAgc2VsZi50ZXh0LmNvbmZpZyhmb250PSgnY291cmllcicsIDEyLCAnbm9ybWFsJyksIGJhY2tncm91bmQ9J2JsYWNrJywgZmc9J3doaXRlJywgaW5zZXJ0YmFja2dyb3VuZD0nd2hpdGUnKQogICAgICAgIAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYudGV4dC5jb25maWcoZm9udD0oJ2NvdXJpZXInLCAxMiwgJ25vcm1hbCcpLCBiYWNrZ3JvdW5kPSd3aGl0ZScsIGZnPSdibGFjaycsIGluc2VydGJhY2tncm91bmQ9J2JsYWNrJykKICAgICAgICBpbmRleFswXSA9IG5vdCBpbmRleFswXQoKICAgIGRlZiBvblF1aXQoc2VsZik6CgogICAgICAgIGFucyA9IGFza29rY2FuY2VsKCdWZXJpZnkgZXhpdCcsICJSZWFsbHkgcXVpdD8iKQoKICAgICAgICBpZiBhbnM6CiAgICAgICAgICAgIEZyYW1lLnF1aXQoc2VsZikKICAgICAgICAgICAgV2luZG93LmRlc3Ryb3koKQoKCiAgICBkZWYgX29uX2NoYW5nZShzZWxmLCBldmVudCk6CiAgICAgICAgc2VsZi5saW5lbnVtYmVycy5yZWRyYXcoKQogICAgICAgIAoKY2xhc3MgU2ltcGxlRWRpdG9yKFNjcm9sbGVkVGV4dCk6CgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHBhcmVudD1Ob25lLCBmaWxlPU5vbmUpOgogICAgICAgIAogICAgICAgIEV4YW1wbGUoV2luZG93KS5wYWNrKHNpZGU9ImxlZnQiLCBmaWxsPSJib3RoIiwgZXhwYW5kPVRydWUpCgogICAgICAgIFNjcm9sbGVkVGV4dC5fX2luaXRfXyhzZWxmLCBwYXJlbnQsIGZpbGU9Tm9uZSkKCiAgICAgICAgc2VsZi50ZXh0LmNvbmZpZyhmb250PSgnY291cmllcicsIDEyLCAnbm9ybWFsJykpCiAgICAKaWYgX19uYW1lX18gPT0gJ19fbWFpbl9fJzoKCiAgICB0cnk6CgogICAgICAgIFNpbXBsZUVkaXRvcihmaWxlPXN5cy5hcmd2WzFdKS5tYWlubG9vcCgpCgogICAgZXhjZXB0IEluZGV4RXJyb3I6CgogICAgICAgIFNpbXBsZUVkaXRvcigpLm1haW5sb29wKCkKCg==