import os import sys import select import tempfile import pty import itertools import re import fnmatch from hashlib import sha1 from contextlib import contextmanager from subprocess import check_output from IPython import get_ipython from IPython.display import HTML from IPython.core.extensions import ExtensionManager import IPython.display import ROOT import cpptransformer import cppcompleter # We want iPython to take over the graphics ROOT.gROOT.SetBatch() cppMIME = 'text/x-c++src' ipyMIME = 'text/x-ipython' _jsDefaultHighlight = """ // Set default mode for code cells IPython.CodeCell.options_default.cm_config.mode = '{mimeType}'; // Set CodeMirror's current mode var cells = IPython.notebook.get_cells(); cells[cells.length-1].code_mirror.setOption('mode', '{mimeType}'); // Set current mode for newly created cell cells[cells.length-1].cm_config.mode = '{mimeType}'; """ _jsMagicHighlight = "IPython.CodeCell.config_defaults.highlight_modes['magic_{cppMIME}'] = {{'reg':[/^%%cpp/]}};" _jsNotDrawableClassesPatterns = ["TGraph[23]D","TH3*","TGraphPolar","TProf*","TEve*","TF[23]","TGeo*","TPolyLine3D"] _jsROOTSourceDir = "https://root.cern.ch/js/dev/" _jsCanvasWidth = 800 _jsCanvasHeight = 600 _jsCode = """
""" _enableJSVis = False _enableJSVisDebug = False def enableJSVis(): global _enableJSVis _enableJSVis = True def disableJSVis(): global _enableJSVis _enableJSVis = False def enableJSVisDebug(): global _enableJSVis global _enableJSVisDebug _enableJSVis = True _enableJSVisDebug = True def disableJSVisDebug(): global _enableJSVis global _enableJSVisDebug _enableJSVis = False _enableJSVisDebug = False def _getPlatform(): return sys.platform def _getLibExtension(thePlatform): '''Return appropriate file extension for a shared library >>> _getLibExtension('darwin') '.dylib' >>> _getLibExtension('win32') '.dll' >>> _getLibExtension('OddPlatform') '.so' ''' pExtMap = { 'darwin' : '.dylib', 'win32' : '.dll' } return pExtMap.get(thePlatform, '.so') def welcomeMsg(): print "Welcome to ROOTaaS %s" %ROOT.gROOT.GetVersion() @contextmanager def _setIgnoreLevel(level): originalLevel = ROOT.gErrorIgnoreLevel ROOT.gErrorIgnoreLevel = level yield ROOT.gErrorIgnoreLevel = originalLevel def commentRemover( text ): ''' >>> s="// hello" >>> commentRemover(s) '' >>> s="int /** Test **/ main() {return 0;}" >>> commentRemover(s) 'int main() {return 0;}' ''' def blotOutNonNewlines( strIn ) : # Return a string containing only the newline chars contained in strIn return "" + ("\n" * strIn.count('\n')) def replacer( match ) : s = match.group(0) if s.startswith('/'): # Matched string is //...EOL or /*...*/ ==> Blot out all non-newline chars return blotOutNonNewlines(s) else: # Matched string is '...' or "..." ==> Keep unchanged return s pattern = re.compile(\ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE) return re.sub(pattern, replacer, text) # Here functions are defined to process C++ code def processCppCodeImpl(code): #code = commentRemover(code) ROOT.gInterpreter.ProcessLine(code) def declareCppCodeImpl(code): #code = commentRemover(code) ROOT.gInterpreter.Declare(code) def processCppCode(code): processCppCodeImpl(code) def declareCppCode(code): declareCppCodeImpl(code) def _checkOutput(command,errMsg=None): out = "" try: out = check_output(command.split()) except: if errMsg: sys.stderr.write("%s (command was %s)\n" %(errMsg,command)) return out def _invokeAclicMac(fileName): '''FIXME! This function is a workaround. On osx, it is impossible to link against libzmq.so, among the others. The error is known and is "ld: can't link with bundle (MH_BUNDLE) only dylibs (MH_DYLIB)" We cannot at the moment force Aclic to change the linker command in order to exclude these libraries, so we launch a second root session to compile the library, which we then load. ''' command = 'root -l -q -b -e gSystem->CompileMacro(\"%s\",\"k\")*0'%fileName out = _checkOutput(command, "Error ivoking ACLiC") libNameBase = fileName.replace(".C","_C") ROOT.gSystem.Load(libNameBase) def _codeToFilename(code): '''Convert code to a unique file name >>> _codeToFilename("int f(i){return i*i;}") 'dbf7e731.C' ''' fileNameBase = sha1(code).hexdigest()[0:8] return fileNameBase + ".C" def _dumpToUniqueFile(code): '''Dump code to file whose name is unique >>> _codeToFilename("int f(i){return i*i;}") 'dbf7e731.C' ''' fileName = _codeToFilename(code) with open (fileName,'w') as ofile: ofile.write(code) return fileName def invokeAclic(cell): fileName = _dumpToUniqueFile(cell) if _getPlatform() == 'darwin': _invokeAclicMac(fileName) else: processCppCode(".L %s+" %fileName) class StreamCapture(object): def __init__(self, stream, ip=get_ipython()): nbStreamsPyStreamsMap={sys.stderr:sys.__stderr__,sys.stdout:sys.__stdout__} self.shell = ip self.nbStream = stream self.pyStream = nbStreamsPyStreamsMap[stream] self.pipe_out, self.pipe_in = pty.openpty() os.dup2(self.pipe_in, self.pyStream.fileno()) # Platform independent flush # With ctypes, the name of the libc library is not known a priori # We use jitted function flushFunctionName='_ROOTaaS_Flush' if (not hasattr(ROOT,flushFunctionName)): declareCppCode("void %s(){fflush(nullptr);};" %flushFunctionName) self.flush = getattr(ROOT,flushFunctionName) def more_data(self): r, _, _ = select.select([self.pipe_out], [], [], 0) return bool(r) def post_execute(self): out = '' if self.pipe_out: while self.more_data(): out += os.read(self.pipe_out, 8192) self.flush() self.nbStream.write(out) # important to print the value printing output return 0 def register(self): self.shell.events.register('post_execute', self.post_execute) class CaptureDrawnCanvases(object): ''' Capture the canvas which is drawn to display it. ''' def __init__(self, ip=get_ipython()): self.shell = ip def _pre_execute(self): pass def _post_execute(self): for can in ROOT.gROOT.GetListOfCanvases(): if can.IsDrawn(): can.Draw() can.ResetDrawn() def register(self): self.shell.events.register('pre_execute', self._pre_execute) self.shell.events.register('post_execute', self._post_execute) captures = [StreamCapture(sys.stderr), StreamCapture(sys.stdout), CaptureDrawnCanvases()] def toCpp(): ''' Change the mode of the notebook to CPP. It is preferred to use cell magic, but this option is handy to set up servers and for debugging purposes. ''' ip = get_ipython() cpptransformer.load_ipython_extension(ip) # Change highlight mode IPython.display.display_javascript(_jsDefaultHighlight.format(mimeType = cppMIME), raw=True) print "Notebook is in Cpp mode" class CanvasDrawer(object): ''' Capture the canvas which is drawn and decide if it should be displayed using jsROOT. ''' jsUID = 0 def __init__(self, thePad): self.thePad = thePad def _getListOfPrimitivesNamesAndTypes(self): """ Get the list of primitives in the pad, recursively descending into histograms and graphs looking for fitted functions. """ primitives = self.thePad.GetListOfPrimitives() primitivesNames = map(lambda p: p.ClassName(), primitives) #primitivesWithFunctions = filter(lambda primitive: hasattr(primitive,"GetListOfFunctions"), primitives) #for primitiveWithFunctions in primitivesWithFunctions: # for function in primitiveWithFunctions.GetListOfFunctions(): # primitivesNames.append(function.GetName()) return sorted(primitivesNames) def _getUID(self): ''' Every DIV containing a JavaScript snippet must be unique in the notebook. This methods provides a unique identifier. ''' CanvasDrawer.jsUID += 1 return CanvasDrawer.jsUID def _canJsDisplay(self): # to be optimised if not _enableJSVis: return False primitivesTypesNames = self._getListOfPrimitivesNamesAndTypes() for unsupportedPattern in _jsNotDrawableClassesPatterns: for primitiveTypeName in primitivesTypesNames: if fnmatch.fnmatch(primitiveTypeName,unsupportedPattern): print >> sys.stderr, "The canvas contains an object of a type jsROOT cannot currently handle (%s). Falling back to a static png." %primitiveTypeName return False return True def _jsDisplay(self): # Workaround to have ConvertToJSON work pad = ROOT.gROOT.GetListOfCanvases().FindObject(ROOT.gPad.GetName()) json = ROOT.TBufferJSON.ConvertToJSON(pad, 3) #print "JSON:",json # Here we could optimise the string manipulation divId = 'root_plot_' + str(self._getUID()) thisJsCode = _jsCode.format(jsCanvasWidth = _jsCanvasWidth, jsCanvasHeight = _jsCanvasHeight, jsROOTSourceDir = _jsROOTSourceDir, jsonContent=json.Data(), jsDrawOptions="", jsDivId = divId) # display is the key point of this hook IPython.display.display(HTML(thisJsCode)) return 0 def _pngDisplay(self): ofile = tempfile.NamedTemporaryFile(suffix=".png") with _setIgnoreLevel(ROOT.kError): self.thePad.SaveAs(ofile.name) img = IPython.display.Image(filename=ofile.name, format='png', embed=True) IPython.display.display(img) return 0 def _display(self): if _enableJSVisDebug: self._pngDisplay() self._jsDisplay() else: if self._canJsDisplay(): self._jsDisplay() else: self._pngDisplay() def Draw(self): self._display() return 0 def _PyDraw(thePad): """ Invoke the draw function and intercept the graphics """ drawer = CanvasDrawer(thePad) drawer.Draw() def setStyle(): style=ROOT.gStyle style.SetFuncWidth(3) style.SetHistLineWidth(3) style.SetMarkerStyle(8) style.SetMarkerSize(.5) style.SetMarkerColor(ROOT.kBlue) style.SetPalette(57) def loadExtensionsAndCapturers(): extNames = ["ROOTaaS.iPyROOT." + name for name in ["cppmagic"]] ip = get_ipython() extMgr = ExtensionManager(ip) for extName in extNames: extMgr.load_extension(extName) cppcompleter.load_ipython_extension(ip) for capture in captures: capture.register() def enhanceROOTModule(): ROOT.toCpp = toCpp ROOT.enableJSVis = enableJSVis ROOT.disableJSVis = disableJSVis ROOT.enableJSVisDebug = enableJSVisDebug ROOT.disableJSVisDebug = disableJSVisDebug ROOT.TCanvas.DrawCpp = ROOT.TCanvas.Draw ROOT.TCanvas.Draw = _PyDraw def enableCppHighlighting(): ipDispJs = IPython.display.display_javascript #Make sure clike JS lexer is loaded ipDispJs("require(['codemirror/mode/clike/clike'], function(Clike) { console.log('ROOTaaS - C++ CodeMirror module loaded'); });", raw=True) # Define highlight mode for %%cpp magic ipDispJs(_jsMagicHighlight.format(cppMIME = cppMIME), raw=True) def iPythonize(): setStyle() loadExtensionsAndCapturers() enableCppHighlighting() enhanceROOTModule() welcomeMsg()