Logo coherent WaveBurst  
Config Reference Guide
Logo
converttonotebook.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Author: Pau Miquel i Mir <pau.miquel.mir@cern.ch> <pmm1g15@soton.ac.uk>>
3 # Date: July, 2016
4 #
5 # DISCLAIMER: This script is a prototype and a work in progress. Indeed, it is possible that
6 # it may not work for certain tutorials, and that it, or the tutorial, might need to be
7 # tweaked slightly to ensure full functionality. Please do not hesistate to email the author
8 # with any questions or with examples that do not work.
9 #
10 # HELP IT DOESN'T WORK: Two possible solutions:
11 # 1. Check that all the types returned by the tutorial are in the gTypesList. If they aren't,
12 # simply add them.
13 # 2. If the tutorial takes a long time to execute (more than 90 seconds), add the name of the
14 # tutorial to the list of long tutorials listLongTutorials, in the fucntion findTimeout.
15 #
16 # REQUIREMENTS: This script needs jupyter to be properly installed, as it uses the python
17 # package nbformat and calls the shell commands `jupyter nbconvert` and `jupyter trust`. The
18 # rest of the packages used should be included in a standard installation of python. The script
19 # is intended to be run on a UNIX based system.
20 #
21 #
22 # FUNCTIONING:
23 # -----------
24 # The converttonotebook script creates Jupyter notebooks from raw C++ or python files.
25 # Particulary, it is indicated to convert the ROOT tutorials found in the ROOT
26 # repository.
27 #
28 # The script should be called from bash with the following format:
29 # python /path/to/script/converttonotebook.py /path/to/<macro>.C /path/to/outdir
30 #
31 # Indeed the script takes two arguments, the path to the macro and the path to the directory
32 # where the notebooks will be created
33 #
34 # The script's general functioning is as follows. The macro to be converted is imported as a string.
35 # A series of modifications are made to this string, for instance delimiting where markdown and
36 # code cells begin and end. Then, this string is converted into ipynb format using a function
37 # in the nbconvert package. Finally, the notebook is executed and output.
38 #
39 # For converting python tutorials it is fairly straightforward. It extracts the decription and
40 # author information from the header and then removes it. It also converts any comment at the
41 # beginning of a line into a Markdown cell.
42 #
43 # For C++ files the process is slightly more complex. The script separates the functions from the
44 # main code. The main function is identified as it has the smae name as the macro file. The other
45 # functions are considered functions. The main function is "extracted" and presented as main code.
46 # The helper functions are placed in their own code cell with the %%cpp -d magic to enable function
47 # defintion. Finally, as with Python macros, relevant information is extracted from the header, and
48 # newline comments are converted into Markdown cells (unless they are in helper functions).
49 #
50 # The script creates an .ipynb version of the macro, with the full output included.
51 # The files are named:
52 # <macro>.<C or py>.nbconvert.ipynb
53 #
54 # It is called by filter.cxx, which in turn is called by doxygen when processing any file
55 # in the ROOT repository. filter.cxx only calls convertonotebook.py when the string \notebook
56 # is found in the header of the turorial, but this script checks for its presence as well.
57 
58 
59 import re
60 import os
61 import sys
62 import json
63 import time
64 import doctest
65 import textwrap
66 import subprocess
67 from nbformat import v3, v4
68 from datetime import datetime, date
69 
70 # List of types that will be considered when looking for a C++ function. If a macro returns a
71 # type not included on the list, the regular expression will not match it, and thus the function
72 # will not be properly defined. Thus, any other type returned by function must be added to this list
73 # for the script to work correctly.
74 gTypesList = ["void", "int", "Int_t", "TF1", "string", "bool", "double", "float", "char",
75  "TCanvas", "TTree", "TString", "TSeqCollection", "Double_t", "TFile", "Long64_t", "Bool_t", "TH1",
76  "RooDataSet", "RooWorkspace" , "HypoTestInverterResult" , "TVectorD" , "TArrayF", "UInt_t"]
77 
78 # -------------------------------------
79 # -------- Fuction definitions---------
80 # -------------------------------------
81 
82 def unindenter(string, spaces = 3):
83  """
84  Returns string with each line unindented by 3 spaces. If line isn't indented, it stays the same.
85  >>> unindenter(" foobar")
86  'foobar\\n'
87  >>> unindenter("foobar")
88  'foobar\\n'
89  >>> unindenter('''foobar
90  ... foobar
91  ... foobar''')
92  'foobar\\nfoobar\\nfoobar\\n'
93  """
94  newstring = ''
95  lines = string.splitlines()
96  for line in lines:
97  if line.startswith(spaces*' '):
98  newstring += (line[spaces:] + "\n")
99  else:
100  newstring += (line + "\n")
101 
102  return newstring
103 
104 
106  """
107  Extract author and description from header, eliminate header from text. Also returns
108  notebook boolean, which is True if the string \notebook is present in the header
109  Also determine options (-js, -nodraw, -header) passed in \notebook command, and
110  return their booleans
111  >>> readHeaderPython('''## \\file
112  ... ## \\ingroup tutorials
113  ... ## \\\\notebook
114  ... ## This is the description of the tutorial
115  ... ##
116  ... ## \\macro_image
117  ... ## \\macro_code
118  ... ##
119  ... ## \\\\author John Brown
120  ... def tutorialfuncion()''')
121  ('def tutorialfuncion()\\n', 'This is the description of the tutorial\\n\\n\\n', 'John Brown', True, False, False, False)
122  >>> readHeaderPython('''## \\file
123  ... ## \\ingroup tutorials
124  ... ## \\\\notebook -js
125  ... ## This is the description of the tutorial
126  ... ##
127  ... ## \\macro_image
128  ... ## \\macro_code
129  ... ##
130  ... ## \\\\author John Brown
131  ... def tutorialfuncion()''')
132  ('def tutorialfuncion()\\n', 'This is the description of the tutorial\\n\\n\\n', 'John Brown', True, True, False, False)
133  >>> readHeaderPython('''## \\file
134  ... ## \\ingroup tutorials
135  ... ## \\\\notebook -nodraw
136  ... ## This is the description of the tutorial
137  ... ##
138  ... ## \\macro_image
139  ... ## \\macro_code
140  ... ##
141  ... ## \\\\author John Brown
142  ... def tutorialfuncion()''')
143  ('def tutorialfuncion()\\n', 'This is the description of the tutorial\\n\\n\\n', 'John Brown', True, False, True, False)
144  """
145  lines = text.splitlines()
146 
147  description = ''
148  author = ''
149  isNotebook = False
150  isJsroot = False
151  nodraw = False
152  needsHeaderFile = False
153  for i, line in enumerate(lines):
154  if line.startswith("## \\aut"):
155  author = line[11:]
156  elif line.startswith("## \\note"):
157  isNotebook = True
158  if "-js" in line:
159  isJsroot = True
160  if "-nodraw" in line:
161  nodraw = True
162  if "-header" in line:
163  needsHeaderFile = True
164  elif line.startswith("##"):
165  if not line.startswith("## \\") and isNotebook:
166  description += (line[3:] + '\n')
167  else:
168  break
169  newtext = ''
170  for line in lines[i:]:
171  newtext += (line + "\n")
172 
173  return newtext, description, author, isNotebook, isJsroot, nodraw, needsHeaderFile
174 
175 
176 def pythonComments(text):
177  """
178  Converts comments delimited by # or ## and on a new line into a markdown cell.
179  For python files only
180  >>> pythonComments('''## This is a
181  ... ## multiline comment
182  ... def function()''')
183  '# <markdowncell>\\n## This is a\\n## multiline comment\\n# <codecell>\\ndef function()\\n'
184  >>> pythonComments('''def function():
185  ... variable = 5 # Comment not in cell
186  ... # Comment also not in cell''')
187  'def function():\\n variable = 5 # Comment not in cell\\n # Comment also not in cell\\n'
188  """
189  text = text.splitlines()
190  newtext = ''
191  inComment = False
192  for i, line in enumerate(text):
193  if line.startswith("#") and not inComment: # True if first line of comment
194  inComment = True
195  newtext += "# <markdowncell>\n"
196  newtext += (line + "\n")
197  elif inComment and not line.startswith("#"): # True if first line after comment
198  inComment = False
199  newtext += "# <codecell>\n"
200  newtext += (line+"\n")
201  else:
202  newtext += (line+"\n")
203  return newtext
204 
205 
207  lines = text.splitlines()
208  functionContentRe = re.compile('def %s\\(.*\\):' % tutName , flags = re.DOTALL | re.MULTILINE)
209  newtext = ''
210  inMainFunction = False
211  hasMainFunction = False
212  for line in lines:
213 
214  if hasMainFunction:
215  if line.startswith("""if __name__ == "__main__":""") or line.startswith("""if __name__ == '__main__':"""):
216  break
217  match = functionContentRe.search(line)
218  if inMainFunction and not line.startswith(" ") and line != "":
219  inMainFunction = False
220  if match:
221  inMainFunction = True
222  hasMainFunction = True
223  else:
224  if inMainFunction:
225  newtext += (line[4:] + '\n')
226  else:
227  newtext += (line + '\n')
228  return newtext
229 
230 
231 def readHeaderCpp(text):
232  """
233  Extract author and description from header, eliminate header from text. Also returns
234  notebook boolean, which is True if the string \notebook is present in the header
235  Also determine options (-js, -nodraw, -header) passed in \notebook command, and
236  return their booleans
237  >>> readHeaderCpp('''/// \\file
238  ... /// \\ingroup tutorials
239  ... /// \\\\notebook
240  ... /// This is the description of the tutorial
241  ... ///
242  ... /// \\macro_image
243  ... /// \\macro_code
244  ... ///
245  ... /// \\\\author John Brown
246  ... void tutorialfuncion(){}''')
247  ('void tutorialfuncion(){}\\n', '# This is the description of the tutorial\\n# \\n# \\n', 'John Brown', True, False, False, False)
248  >>> readHeaderCpp('''/// \\file
249  ... /// \\ingroup tutorials
250  ... /// \\\\notebook -js
251  ... /// This is the description of the tutorial
252  ... ///
253  ... /// \\macro_image
254  ... /// \\macro_code
255  ... ///
256  ... /// \\\\author John Brown
257  ... void tutorialfuncion(){}''')
258  ('void tutorialfuncion(){}\\n', '# This is the description of the tutorial\\n# \\n# \\n', 'John Brown', True, True, False, False)
259  >>> readHeaderCpp('''/// \\file
260  ... /// \\ingroup tutorials
261  ... /// \\\\notebook -nodraw
262  ... /// This is the description of the tutorial
263  ... ///
264  ... /// \\macro_image
265  ... /// \\macro_code
266  ... ///
267  ... /// \\\\author John Brown
268  ... void tutorialfuncion(){}''')
269  ('void tutorialfuncion(){}\\n', '# This is the description of the tutorial\\n# \\n# \\n', 'John Brown', True, False, True, False)
270  """
271  lines = text.splitlines()
272 
273  description = ''
274  author = ''
275  isNotebook = False
276  isJsroot = False
277  nodraw = False
278  needsHeaderFile = False
279  for i, line in enumerate(lines):
280  if line.startswith("/// \\aut"):
281  author = line[12:]
282  if line.startswith("/// \\note"):
283  isNotebook = True
284  if "-js" in line:
285  isJsroot = True
286  if "-nodraw" in line:
287  nodraw = True
288  if "-header" in line:
289  needsHeaderFile = True
290  if line.startswith("///"):
291  if not line.startswith("/// \\") and isNotebook:
292  description += ('# ' + line[4:] + '\n')
293  else:
294  break
295  newtext = ''
296  for line in lines[i:]:
297  newtext += (line + "\n")
298  description = description.replace("\\f$", "$")
299  description = description.replace("\\f[", "$$")
300  description = description.replace("\\f]", "$$")
301  return newtext, description, author, isNotebook, isJsroot, nodraw, needsHeaderFile
302 
303 
304 def cppFunction(text):
305  """
306  Extracts main function for the function enclosure by means of regular expression
307  >>> cppFunction('''void mainfunction(arguments = values){
308  ... content of function
309  ... which spans
310  ... several lines
311  ... }''')
312  '\\n content of function\\n which spans\\n several lines\\n'
313  >>> cppFunction('''void mainfunction(arguments = values)
314  ... {
315  ... content of function
316  ... which spans
317  ... several lines
318  ... }''')
319  '\\n content of function\\n which spans\\n several lines\\n'
320  >>> cppFunction('''void mainfunction(arguments = values
321  ... morearguments = morevalues)
322  ... {
323  ... content of function
324  ... which spans
325  ... several lines
326  ... }''')
327  '\\n content of function\\n which spans\\n several lines\\n'
328  """
329  functionContentRe = re.compile(r'(?<=\{).*(?=^\})', flags = re.DOTALL | re.MULTILINE)
330 
331  match = functionContentRe.search(text)
332 
333  if match:
334  return match.group()
335  else:
336  return text
337 
338 def cppComments(text):
339  """
340  Converts comments delimited by // and on a new line into a markdown cell. For C++ files only.
341  >>> cppComments('''// This is a
342  ... // multiline comment
343  ... void function(){}''')
344  '# <markdowncell>\\n# This is a\\n# multiline comment\\n# <codecell>\\nvoid function(){}\\n'
345  >>> cppComments('''void function(){
346  ... int variable = 5 // Comment not in cell
347  ... // Comment also not in cell
348  ... }''')
349  'void function(){\\n int variable = 5 // Comment not in cell\\n // Comment also not in cell\\n}\\n'
350  """
351  text = text.splitlines()
352  newtext = ''
353  inComment = False
354 
355  for line in text:
356  if line.startswith("//") and not inComment: # True if first line of comment
357  inComment = True
358  newtext += "# <markdowncell>\n"
359  if line[2:].lstrip().startswith("#"): # Don't use .capitalize() if line starts with hash, ie it is a header
360  newtext += ("# " + line[2:]+"\n")
361  else:
362  newtext += ("# " + line[2:].lstrip().capitalize()+"\n")
363  elif inComment and not line.startswith("//"): # True if first line after comment
364  inComment = False
365  newtext += "# <codecell>\n"
366  newtext += (line+"\n")
367  elif inComment and line.startswith("//"): # True if in the middle of a comment block
368  newtext += ("# " + line[2:] + "\n")
369  else:
370  newtext += (line+"\n")
371 
372  return newtext
373 
374 
375 def split(text):
376  """
377  Splits the text string into main, helpers, and rest. main is the main function,
378  i.e. the function tha thas the same name as the macro file. Helpers is a list of
379  strings, each a helper function, i.e. any other function that is not the main function.
380  Finally, rest is a string containing any top-level code outside of any function.
381  Comments immediately prior to a helper cell are converted into markdown cell,
382  added to the helper, and removed from rest.
383  Intended for C++ files only.
384  >>> split('''void tutorial(){
385  ... content of tutorial
386  ... }''')
387  ('void tutorial(){\\n content of tutorial\\n}', [], '')
388  >>> split('''void tutorial(){
389  ... content of tutorial
390  ... }
391  ... void helper(arguments = values){
392  ... helper function
393  ... content spans lines
394  ... }''')
395  ('void tutorial(){\\n content of tutorial\\n}', ['\\n# <markdowncell>\\n A helper function is created: \\n# <codecell>\\n%%cpp -d\\nvoid helper(arguments = values){\\n helper function\\n content spans lines\\n}'], '')
396  >>> split('''#include <header.h>
397  ... using namespace NAMESPACE
398  ... void tutorial(){
399  ... content of tutorial
400  ... }
401  ... void helper(arguments = values){
402  ... helper function
403  ... content spans lines
404  ... }''')
405  ('void tutorial(){\\n content of tutorial\\n}', ['\\n# <markdowncell>\\n A helper function is created: \\n# <codecell>\\n%%cpp -d\\nvoid helper(arguments = values){\\n helper function\\n content spans lines\\n}'], '#include <header.h>\\nusing namespace NAMESPACE')
406  >>> split('''void tutorial(){
407  ... content of tutorial
408  ... }
409  ... // This is a multiline
410  ... // description of the
411  ... // helper function
412  ... void helper(arguments = values){
413  ... helper function
414  ... content spans lines
415  ... }''')
416  ('void tutorial(){\\n content of tutorial\\n}', ['\\n# <markdowncell>\\n This is a multiline\\n description of the\\n helper function\\n \\n# <codecell>\\n%%cpp -d\\nvoid helper(arguments = values){\\n helper function\\n content spans lines\\n}'], '')
417  """
418  functionReString="("
419  for cpptype in gTypesList:
420  functionReString += ("^%s|") % cpptype
421 
422  functionReString = functionReString[:-1] + r")\s?\*?&?\s?[\w:]*?\s?\‍([^\‍)]*\‍)\s*\{.*?^\}"
423 
424  functionRe = re.compile(functionReString, flags = re.DOTALL | re.MULTILINE)
425  #functionre = re.compile(r'(^void|^int|^Int_t|^TF1|^string|^bool|^double|^float|^char|^TCanvas|^TTree|^TString|^TSeqCollection|^Double_t|^TFile|^Long64_t|^Bool_t)\s?\*?\s?[\w:]*?\s?\‍([^\‍)]*\‍)\s*\{.*?^\}', flags = re.DOTALL | re.MULTILINE)
426  functionMatches = functionRe.finditer(text)
427  helpers = []
428  main = ""
429  for matchString in [match.group() for match in functionMatches]:
430  if tutName == findFunctionName(matchString): # if the name of the function is that of the macro
431  main = matchString
432  else:
433  helpers.append(matchString)
434 
435  # Create rest by replacing the main and helper functions with blank strings
436  rest = text.replace(main, "")
437 
438  for helper in helpers:
439  rest = rest.replace(helper, "")
440 
441  newHelpers = []
442  lines = text.splitlines()
443  for helper in helpers: # For each helper function
444  for i, line in enumerate(lines): # Look through the lines until the
445  if line.startswith(helper[:helper.find("\n")]): # first line of the helper is found
446  j = 1
447  commentList = []
448  while lines[i-j].startswith("//"): # Add comment lines immediately prior to list
449  commentList.append(lines[i-j])
450  j += 1
451  if commentList: # Convert list to string
452  commentList.reverse()
453  helperDescription = ''
454  for comment in commentList:
455  if comment in ("//", "// "):
456  helperDescription += "\n\n" # Two newlines to create hard break in Markdown
457  else:
458  helperDescription += (comment[2:] + "\n")
459  rest = rest.replace(comment, "")
460  break
461  else: # If no comments are found create generic description
462  helperDescription = "A helper function is created:"
463  break
464 
465  if findFunctionName(helper) != "main": # remove void main function
466  newHelpers.append("\n# <markdowncell>\n " + helperDescription + " \n# <codecell>\n%%cpp -d\n" + helper)
467 
468  rest = rest.rstrip("\n /") # remove newlines and empty comments at the end of string
469 
470  return main, newHelpers, rest
471 
472 
474  """
475  Takes a string representation of a C++ function as an input,
476  finds and returns the name of the function
477  >>> findFunctionName('void functionName(arguments = values){}')
478  'functionName'
479  >>> findFunctionName('void functionName (arguments = values){}')
480  'functionName'
481  >>> findFunctionName('void *functionName(arguments = values){}')
482  'functionName'
483  >>> findFunctionName('void* functionName(arguments = values){}')
484  'functionName'
485  >>> findFunctionName('void * functionName(arguments = values){}')
486  'functionName'
487  >>> findFunctionName('void class::functionName(arguments = values){}')
488  'class::functionName'
489  """
490  functionNameReString="(?<="
491  for cpptype in gTypesList:
492  functionNameReString += ("(?<=%s)|") % cpptype
493 
494  functionNameReString = functionNameReString[:-1] + r")\s?\*?\s?[^\s]*?(?=\s?\‍()"
495 
496  functionNameRe = re.compile(functionNameReString, flags = re.DOTALL | re.MULTILINE)
497 
498  #functionnamere = re.compile(r'(?<=(?<=int)|(?<=void)|(?<=TF1)|(?<=Int_t)|(?<=string)|(?<=double)|(?<=Double_t)|(?<=float)|(?<=char)|(?<=TString)|(?<=bool)|(?<=TSeqCollection)|(?<=TCanvas)|(?<=TTree)|(?<=TFile)|(?<=Long64_t)|(?<=Bool_t))\s?\*?\s?[^\s]*?(?=\s?\‍()', flags = re.DOTALL | re.MULTILINE)
499  match = functionNameRe.search(text)
500  functionname = match.group().strip(" *\n")
501  return functionname
502 
503 
504 def processmain(text):
505  """
506  Evaluates whether the main function returns a TCanvas or requires input. If it
507  does then the keepfunction flag is True, meaning the function wont be extracted
508  by cppFunction. If the initial condition is true then an extra cell is added
509  before at the end that calls the main function is returned, and added later.
510  >>> processmain('''void function(){
511  ... content of function
512  ... spanning several
513  ... lines
514  ... }''')
515  ('void function(){\\n content of function\\n spanning several\\n lines\\n}', '')
516  >>> processmain('''void function(arguments = values){
517  ... content of function
518  ... spanning several
519  ... lines
520  ... }''')
521  ('void function(arguments = values){\\n content of function\\n spanning several\\n lines\\n}', '# <markdowncell> \\n Arguments are defined. \\n# <codecell>\\narguments = values;\\n# <codecell>\\n')
522  >>> processmain('''void function(argument1 = value1, //comment 1
523  ... argument2 = value2 /*comment 2*/ ,
524  ... argument3 = value3,
525  ... argument4 = value4)
526  ... {
527  ... content of function
528  ... spanning several
529  ... lines
530  ... }''')
531  ('void function(argument1 = value1, //comment 1\\n argument2 = value2 /*comment 2*/ ,\\n argument3 = value3, \\n argument4 = value4)\\n{\\n content of function\\n spanning several\\n lines\\n}', '# <markdowncell> \\n Arguments are defined. \\n# <codecell>\\nargument1 = value1;\\nargument2 = value2;\\nargument3 = value3;\\nargument4 = value4;\\n# <codecell>\\n')
532  >>> processmain('''TCanvas function(){
533  ... content of function
534  ... spanning several
535  ... lines
536  ... return c1
537  ... }''')
538  ('TCanvas function(){\\n content of function\\n spanning several \\n lines\\n return c1\\n}', '')
539  """
540 
541  argumentsCell = ''
542 
543  if text:
544  argumentsre = re.compile(r'(?<=\‍().*?(?=\‍))', flags = re.DOTALL | re.MULTILINE)
545  arguments = argumentsre.search(text)
546 
547  if len(arguments.group()) > 3:
548  argumentsCell = "# <markdowncell> \n Arguments are defined. \n# <codecell>\n"
549  individualArgumentre = re.compile(r'[^/\n,]*?=[^/\n,]*') #, flags = re.DOTALL) #| re.MULTILINE)
550  argumentList=individualArgumentre.findall(arguments.group())
551  for argument in argumentList:
552  argumentsCell += argument.strip("\n ") + ";\n"
553  argumentsCell += "# <codecell>\n"
554 
555  return text, argumentsCell
556 
557 # now define text transformers
559  code = code.replace("img->StartPaletteEditor();", "")
560  code = code.replace("Open the color editor", "")
561  return code
562 
563 
564 def runEventExe(code):
565  if "copytree" in tutName:
566  return "# <codecell> \n.! $ROOTSYS/test/eventexe 1000 1 1 1 \n" + code
567  return code
568 
569 
570 def getLibMathMore(code):
571  if "quasirandom" == tutName:
572  return "# <codecell> \ngSystem->Load(\"libMathMore\"); \n# <codecell> \n" + code
573  return code
574 
575 
577 
578  def changeString(matchObject):
579  matchString = matchObject.group()
580  matchString = matchString[0] + " " + matchString[1:]
581  matchString = matchString.replace(" " , "THISISASPACE")
582  matchString = matchString.replace(" " , "")
583  matchString = matchString.replace("THISISASPACE" , " ")
584  return matchString
585 
586  newcode = re.sub("#\s\s?\w\s[\w-]\s\w.*", changeString , code)
587  return newcode
588 
590  if "using namespace RooFit;\nusing namespace RooStats;" in code:
591  code = code.replace("using namespace RooFit;\nusing namespace RooStats;", "# <codecell>\n%%cpp -d\n// This is a workaround to make sure the namespace is used inside functions\nusing namespace RooFit;\nusing namespace RooStats;\n# <codecell>\n")
592 
593  else:
594  code = code.replace("using namespace RooFit;", "# <codecell>\n%%cpp -d\n// This is a workaround to make sure the namespace is used inside functions\nusing namespace RooFit;\n# <codecell>\n")
595  code = code.replace("using namespace RooStats;", "# <codecell>\n%%cpp -d\n// This is a workaround to make sure the namespace is used inside functions\nusing namespace RooStats;\n# <codecell>\n")
596  code = code.replace("using namespace ROOT::Math;", "# <codecell>\n%%cpp -d\n// This is a workaround to make sure the namespace is used inside functions\nusing namespace ROOT::Math;\n# <codecell>\n")
597 
598  return code
599 
600 
601 def rs401dGetFiles(code):
602  if tutName == "rs401d_FeldmanCousins":
603  code = code.replace(
604  """#if !defined(__CINT__) || defined(__MAKECINT__)\n#include "../tutorials/roostats/NuMuToNuE_Oscillation.h"\n#include "../tutorials/roostats/NuMuToNuE_Oscillation.cxx" // so that it can be executed directly\n#else\n#include "../tutorials/roostats/NuMuToNuE_Oscillation.cxx+" // so that it can be executed directly\n#endif""" , """TString tutDir = gROOT->GetTutorialDir();\nTString headerDir = TString::Format("#include \\\"%s/roostats/NuMuToNuE_Oscillation.h\\\"", tutDir.Data());\nTString impDir = TString::Format("#include \\\"%s/roostats/NuMuToNuE_Oscillation.cxx\\\"", tutDir.Data());\ngROOT->ProcessLine(headerDir);\ngROOT->ProcessLine(impDir);""")
605  return code
606 
607 
608 def declareIncludes(code):
609  if tutName != "fitcont":
610  code = re.sub(r"# <codecell>\s*#include", "# <codecell>\n%%cpp -d\n#include" , code)
611  return code
612 
613 
614 def tree4GetFiles(code):
615  if tutName == "tree4":
616  code = code.replace(
617  """#include \"../test/Event.h\"""" , """# <codecell>\nTString dir = "$ROOTSYS/test/Event.h";\ngSystem->ExpandPathName(dir);\nTString includeCommand = TString::Format("#include \\\"%s\\\"" , dir.Data());\ngROOT->ProcessLine(includeCommand);""")
618  return code
619 
620 
622  code = code.replace(":DrawProgressBar",":!DrawProgressBar")
623  return code
624 def fixes(code):
625  codeTransformers=[removePaletteEditor, runEventExe, getLibMathMore,
626  roofitRemoveSpacesComments, declareNamespace, rs401dGetFiles ,
627  declareIncludes, tree4GetFiles, disableDrawProgressBar]
628 
629  for transformer in codeTransformers:
630  code = transformer(code)
631 
632  return code
633 
634 
635 def changeMarkdown(code):
636  code = code.replace("~~~" , "```")
637  code = code.replace("{.cpp}", "cpp")
638  code = code.replace("{.bash}", "bash")
639  return code
640 
641 
642 def isCpp():
643  """
644  Return True if extension is a C++ file
645  """
646  return extension in ("C", "c", "cpp", "C++", "cxx")
647 
648 
650  listLongTutorials = ["OneSidedFrequentistUpperLimitWithBands", "StandardBayesianNumericalDemo",
651  "TwoSidedFrequentistUpperLimitWithBands" , "HybridStandardForm", "rs401d_FeldmanCousins",
652  "TMVAMultipleBackgroundExample", "TMVARegression", "TMVAClassification", "StandardHypoTestDemo"]
653  if tutName in listLongTutorials:
654  return 300
655  else:
656  return 90
657 # -------------------------------------
658 # ------------ Main Program------------
659 # -------------------------------------
660 def mainfunction(text):
661  """
662  Main function. Calls all other functions, depending on whether the macro input is in python or c++.
663  It adds the header information. Also, it adds a cell that draws all canvases. The working text is
664  then converted to a version 3 jupyter notebook, subsequently updated to a version 4. Then, metadata
665  associated with the language the macro is written in is attatched to he notebook. Finally the
666  notebook is executed and output as a Jupyter notebook.
667  """
668  # Modify text from macros to suit a notebook
669  if isCpp():
670  main, helpers, rest = split(text)
671  main, argumentsCell = processmain(main)
672  main = cppComments(unindenter(cppFunction(main))) # Remove function, Unindent, and convert comments to Markdown cells
673 
674  if argumentsCell:
675  main = argumentsCell + main
676 
677  rest = cppComments(rest) # Convert top level code comments to Markdown cells
678 
679  # Construct text by starting with top level code, then the helper functions, and finally the main function.
680  # Also add cells for headerfile, or keepfunction
681  if needsHeaderFile:
682  text = "# <markdowncell>\n# The header file must be copied to the current directory\n# <codecell>\n.!cp %s%s.h .\n# <codecell>\n" % (tutRelativePath, tutName)
683  text += rest
684  else:
685  text = "# <codecell>\n" + rest
686 
687  for helper in helpers:
688  text += helper
689 
690  text += ("\n# <codecell>\n" + main)
691 
692  if extension == "py":
693  text = pythonMainFunction(text)
694  text = pythonComments(text) # Convert comments into Markdown cells
695 
696 
697  # Perform last minute fixes to the notebook, used for specific fixes needed by some tutorials
698  text = fixes(text)
699 
700  # Change to standard Markdown
701  newDescription = changeMarkdown(description)
702 
703  # Add the title and header of the notebook
704  text = "# <markdowncell> \n# # %s\n%s# \n# \n# **Author:** %s \n# <i><small>This notebook tutorial was automatically generated " \
705  "with <a href= \"https://github.com/root-mirror/root/blob/master/documentation/doxygen/converttonotebook.py\">ROOTBOOK-izer (Beta)</a> " \
706  "from the macro found in the ROOT repository on %s.</small></i>\n# <codecell>\n%s" % (tutTitle, newDescription, author, date, text)
707 
708  # Add cell at the end of the notebook that draws all the canveses. Add a Markdown cell before explaining it.
709  if isJsroot and not nodraw:
710  if isCpp():
711  text += "\n# <markdowncell> \n# Draw all canvases \n# <codecell>\n%jsroot on\ngROOT->GetListOfCanvases()->Draw()"
712  if extension == "py":
713  text += "\n# <markdowncell> \n# Draw all canvases \n# <codecell>\n%jsroot on\nfrom ROOT import gROOT \ngROOT.GetListOfCanvases().Draw()"
714 
715  elif not nodraw:
716  if isCpp():
717  text += "\n# <markdowncell> \n# Draw all canvases \n# <codecell>\ngROOT->GetListOfCanvases()->Draw()"
718  if extension == "py":
719  text += "\n# <markdowncell> \n# Draw all canvases \n# <codecell>\nfrom ROOT import gROOT \ngROOT.GetListOfCanvases().Draw()"
720 
721  # Create a notebook from the working text
722  nbook = v3.reads_py(text)
723  nbook = v4.upgrade(nbook) # Upgrade v3 to v4
724 
725  # Load notebook string into json format, essentially creating a dictionary
726  json_data = json.loads(v4.writes(nbook))
727 
728  # add the corresponding metadata
729  if extension == "py":
730  json_data[u'metadata'] = {
731  "kernelspec": {
732  "display_name": "Python 2",
733  "language": "python",
734  "name": "python2"
735  },
736  "language_info": {
737  "codemirror_mode": {
738  "name": "ipython",
739  "version": 2
740  },
741  "file_extension": ".py",
742  "mimetype": "text/x-python",
743  "name": "python",
744  "nbconvert_exporter": "python",
745  "pygments_lexer": "ipython2",
746  "version": "2.7.10"
747  }
748  }
749  elif isCpp():
750  json_data[u'metadata'] = {
751  "kernelspec": {
752  "display_name": "ROOT C++",
753  "language": "c++",
754  "name": "root"
755  },
756  "language_info": {
757  "codemirror_mode": "text/x-c++src",
758  "file_extension": ".C",
759  "mimetype": " text/x-c++src",
760  "name": "c++"
761  }
762  }
763 
764  # write the json file with the metadata
765  with open(outPathName, 'w') as fout:
766  json.dump(json_data, fout, indent=1, sort_keys=True)
767 
768  print(time.time() - starttime)
769  timeout = findTimeout()
770  # Call commmand that executes the notebook and creates a new notebook with the output
771  r = subprocess.call(["jupyter", "nbconvert", "--ExecutePreprocessor.timeout=%d" % timeout, "--to=notebook", "--execute", outPathName])
772  if r != 0:
773  sys.stderr.write("NOTEBOOK_CONVERSION_WARNING: Nbconvert failed for notebook %s with return code %s\n" %(outname,r))
774  else:
775  if isJsroot:
776  subprocess.call(["jupyter", "trust", os.path.join(outdir, outnameconverted)])
777  # Only remove notebook without output if nbconvert succeedes
778  os.remove(outPathName)
779 
780 
781 if __name__ == "__main__":
782 
783  if str(sys.argv[1]) == "-test":
784  tutName = "tutorial"
785  doctest.testmod(verbose=True)
786 
787  else:
788  # -------------------------------------
789  # ----- Preliminary definitions--------
790  # -------------------------------------
791 
792  # Extract and define the name of the file as well as its derived names
793  tutPathName = str(sys.argv[1])
794  tutPath = os.path.dirname(tutPathName)
795  if tutPath.split("/")[-2] == "tutorials":
796  tutRelativePath = "$ROOTSYS/tutorials/%s/" % tutPath.split("/")[-1]
797  tutFileName = os.path.basename(tutPathName)
798  tutName, extension = tutFileName.split(".")
799  tutTitle = re.sub( r"([A-Z\d])", r" \1", tutName).title()
800  outname = tutFileName + ".ipynb"
801  outnameconverted = tutFileName + ".nbconvert.ipynb"
802 
803  # Extract output directory
804  try:
805  outdir = str(sys.argv[2])
806  except:
807  outdir = tutPath
808 
809  outPathName = os.path.join(outdir, outname)
810  # Find and define the time and date this script is run
811  date = datetime.now().strftime("%A, %B %d, %Y at %I:%M %p")
812 
813  # -------------------------------------
814  # -------------------------------------
815  # -------------------------------------
816 
817  # Set DYLD_LIBRARY_PATH. When run without root access or as a different user, epecially from Mac systems,
818  # it is possible for security reasons that the enviornment does not include this definition, so it is manually defined.
819  os.environ["DYLD_LIBRARY_PATH"] = os.environ["ROOTSYS"] + "/lib"
820 
821  # Open the file to be converted
822  with open(tutPathName) as fin:
823  text = fin.read()
824 
825  # Extract information from header and remove header from text
826  if extension == "py":
827  text, description, author, isNotebook, isJsroot, nodraw, needsHeaderFile = readHeaderPython(text)
828  elif isCpp():
829  text, description, author, isNotebook, isJsroot, nodraw, needsHeaderFile = readHeaderCpp(text)
830 
831  if isNotebook:
832  starttime = time.time()
833  mainfunction(text)
834  print(time.time() - starttime)
835  else:
836  pass
string title
Definition: cWB_conf.py:15
def disableDrawProgressBar(code)
def removePaletteEditor(code)
def pythonMainFunction(text)
def roofitRemoveSpacesComments(code)
def unindenter(string, spaces=3)