14
14
import sys
15
15
import os
16
16
import shutil
17
- import subprocess
17
+ # import subprocess
18
18
import argparse
19
19
from contextlib import contextmanager
20
- import jinja2
21
- import shutil
22
20
import webbrowser
21
+ import jinja2
22
+ import pandas
23
23
24
24
25
25
DOC_PATH = os .path .dirname (os .path .abspath (__file__ ))
28
28
BUILD_DIRS = ['doctrees' , 'html' , 'latex' , 'plots' , '_static' , '_templates' ]
29
29
30
30
31
- def _generate_index (include_api = True , single_doc = None ):
32
- """Create index.rst file with the specified sections.
33
-
34
- Parameters
35
- ----------
36
- include_api : bool
37
- Whether API documentation will be built.
38
- single_doc : str or None
39
- If provided, this single documentation page will be generated.
40
- """
41
- if single_doc is not None :
42
- single_doc = os .path .splitext (os .path .basename (single_doc ))[0 ]
43
- include_api = False
44
-
45
- with open (os .path .join (SOURCE_PATH , 'index.rst.template' )) as f :
46
- t = jinja2 .Template (f .read ())
47
-
48
- with open (os .path .join (SOURCE_PATH , 'index.rst' ), 'w' ) as f :
49
- f .write (t .render (include_api = include_api ,
50
- single_doc = single_doc ))
51
-
52
-
53
- def _generate_exclude_pattern (include_api = True , single_doc = None ):
54
- """
55
-
56
- """
57
- if not include_api :
58
- rst_files = ['api.rst' , 'generated/*.rst' ]
59
- elif single_doc is not None :
60
- rst_files = [f for f in os .listdir (SOURCE_PATH )
61
- if ((f .endswith ('.rst' ) or f .endswith ('.ipynb' ))
62
- and (f != 'index.rst' ) and (f != single_doc ))]
63
- rst_files += ['generated/*.rst' ]
64
- else :
65
- rst_files = []
66
-
67
- exclude_patterns = "," .join (
68
- ['{!r}' .format (i ) for i in ['**.ipynb_checkpoints' ] + rst_files ])
69
-
70
- return exclude_patterns
71
-
72
-
73
- def _generate_temp_docstring_file (methods ):
74
- """
75
- """
76
- fnames = [os .path .join (SOURCE_PATH , 'generated' , '{}.rst' .format (method ))
77
- for method in methods ]
78
-
79
- # # remove the target file to make sure it is updated again (to build
80
- # # latest version)
81
- # try:
82
- # os.remove(fname)
83
- # except OSError:
84
- # pass
85
- #
86
- # # generate docstring pages
87
- # print("Running sphinx-autogen to generate docstring stub pages")
88
- # os.system("sphinx-autogen -o source/generated source/*.rst")
89
-
90
- # create the temporary directory in which we will link the target file
91
- try :
92
- os .makedirs (os .path .join (SOURCE_PATH , 'generated_temp' ))
93
- except OSError :
94
- pass
95
-
96
- for fname in fnames :
97
- if os .path .exists (fname ):
98
- # link the target file
99
- try :
100
- # os.symlink(fname, os.path.join(SOURCE_PATH, 'generated_temp',
101
- # '{}.rst'.format(method)),
102
- # target_is_directory=False)
103
- # copying to make sure sphinx always thinks it is new
104
- # and needs to be re-generated (to pick source code changes)
105
- shutil .copy (fname , os .path .join (SOURCE_PATH , 'generated_temp' ))
106
- linked = True
107
- except : # noqa
108
- linked = False
109
- else :
110
- linked = False
111
-
112
- s = """Built docstrings
113
- ================
114
-
115
- .. autosummary::
116
- :toctree: generated_temp/
117
-
118
- {name}
119
-
120
- """ .format (name = '\n ' .join (methods ))
121
-
122
- with open (os .path .join (SOURCE_PATH , "temp.rst" ), 'w' ) as f :
123
- f .write (s )
124
-
125
- if not linked :
126
- print ("Running sphinx-autogen on manually created file" )
127
- os .system ("sphinx-autogen -o source/generated_temp source/temp.rst" )
128
-
129
-
130
31
@contextmanager
131
32
def _maybe_exclude_notebooks ():
132
33
"""Skip building the notebooks if pandoc is not installed.
@@ -137,6 +38,7 @@ def _maybe_exclude_notebooks():
137
38
1. nbconvert isn't installed, or
138
39
2. nbconvert is installed, but pandoc isn't
139
40
"""
41
+ # TODO move to exclude_pattern
140
42
base = os .path .dirname (__file__ )
141
43
notebooks = [os .path .join (base , 'source' , nb )
142
44
for nb in ['style.ipynb' ]]
@@ -175,9 +77,73 @@ class DocBuilder:
175
77
All public methods of this class can be called as parameters of the
176
78
script.
177
79
"""
178
- def __init__ (self , num_jobs = 1 , exclude_patterns = None ):
80
+ def __init__ (self , num_jobs = 1 , include_api = True , single_doc = None ):
179
81
self .num_jobs = num_jobs
180
- self .exclude_patterns = exclude_patterns
82
+ self .include_api = include_api
83
+ self .single_doc = single_doc
84
+ self .single_doc_type = self ._single_doc_type
85
+ self .exclude_patterns = self ._exclude_patterns
86
+
87
+ self ._generate_index ()
88
+ if self .single_doc_type == 'api' :
89
+ self ._run_os ('sphinx-autogen' , '-o' ,
90
+ 'source/generated_single' , 'source/index.rst' )
91
+
92
+ @property
93
+ def _exclude_patterns (self ):
94
+ """Docs source files that will be excluded from building."""
95
+ # TODO move maybe_exclude_notebooks here
96
+ if self .single_doc is not None :
97
+ rst_files = [f for f in os .listdir (SOURCE_PATH )
98
+ if ((f .endswith ('.rst' ) or f .endswith ('.ipynb' ))
99
+ and (f != 'index.rst' )
100
+ and (f != self .single_doc ))]
101
+ rst_files += ['generated/*.rst' ]
102
+ elif not self .include_api :
103
+ rst_files = ['api.rst' , 'generated/*.rst' ]
104
+ else :
105
+ rst_files = ['generated_single/*.rst' ]
106
+
107
+ exclude_patterns = ',' .join (
108
+ '{!r}' .format (i ) for i in ['**.ipynb_checkpoints' ] + rst_files )
109
+
110
+ return exclude_patterns
111
+
112
+ @property
113
+ def _single_doc_type (self ):
114
+ if self .single_doc :
115
+ if os .path .exists (os .path .join (SOURCE_PATH , self .single_doc )):
116
+ return 'rst'
117
+ try :
118
+ obj = pandas
119
+ for name in self .single_doc .split ('.' ):
120
+ obj = getattr (obj , name )
121
+ except AttributeError :
122
+ raise ValueError ('Single document not understood, it should '
123
+ 'be a file in doc/source/*.rst (e.g. '
124
+ '"contributing.rst" or a pandas function or '
125
+ 'method (e.g. "pandas.DataFrame.head")' )
126
+ else :
127
+ return 'api'
128
+
129
+ def _generate_index (self ):
130
+ """Create index.rst file with the specified sections."""
131
+ if self .single_doc_type == 'rst' :
132
+ single_doc = os .path .splitext (os .path .basename (self .single_doc ))[0 ]
133
+ self .include_api = False
134
+ elif self .single_doc_type == 'api' and \
135
+ self .single_doc .startswith ('pandas.' ):
136
+ single_doc = self .single_doc [len ('pandas.' ):]
137
+ else :
138
+ single_doc = self .single_doc
139
+
140
+ with open (os .path .join (SOURCE_PATH , 'index.rst.template' )) as f :
141
+ t = jinja2 .Template (f .read ())
142
+
143
+ with open (os .path .join (SOURCE_PATH , 'index.rst' ), 'w' ) as f :
144
+ f .write (t .render (include_api = self .include_api ,
145
+ single_doc = single_doc ,
146
+ single_doc_type = self .single_doc_type ))
181
147
182
148
@staticmethod
183
149
def _create_build_structure ():
@@ -201,7 +167,10 @@ def _run_os(*args):
201
167
--------
202
168
>>> DocBuilder()._run_os('python', '--version')
203
169
"""
204
- subprocess .check_call (args , stderr = subprocess .STDOUT )
170
+ # TODO check_call should be more safe, but it fails with
171
+ # exclude patterns, needs investigation
172
+ # subprocess.check_call(args, stderr=subprocess.STDOUT)
173
+ os .system (' ' .join (args ))
205
174
206
175
def _sphinx_build (self , kind ):
207
176
"""Call sphinx to build documentation.
@@ -223,10 +192,16 @@ def _sphinx_build(self, kind):
223
192
'-j{}' .format (self .num_jobs ),
224
193
'-b{}' .format (kind ),
225
194
'-d{}' .format (os .path .join (BUILD_PATH , 'doctrees' )),
226
- # TODO integrate exclude_patterns
195
+ '-Dexclude_patterns={}' . format ( self . exclude_patterns ),
227
196
SOURCE_PATH ,
228
197
os .path .join (BUILD_PATH , kind ))
229
198
199
+ def _open_browser (self ):
200
+ url = os .path .join (
201
+ 'file://' , DOC_PATH , 'build' , 'html' ,
202
+ 'generated_single' , '{}.html' .format (self .single_doc ))
203
+ webbrowser .open (url , new = 2 )
204
+
230
205
def html (self ):
231
206
"""Build HTML documentation."""
232
207
self ._create_build_structure ()
@@ -236,6 +211,9 @@ def html(self):
236
211
if os .path .exists (zip_fname ):
237
212
os .remove (zip_fname )
238
213
214
+ if self .single_doc is not None :
215
+ self ._open_browser ()
216
+
239
217
def latex (self , force = False ):
240
218
"""Build PDF documentation."""
241
219
self ._create_build_structure ()
@@ -279,24 +257,6 @@ def zip_html(self):
279
257
'-q' ,
280
258
* fnames )
281
259
282
- def build_docstring (self ):
283
- """Build single docstring page"""
284
- self ._create_build_structure ()
285
-
286
- args = ('sphinx-build' ,
287
- '-bhtml' ,
288
- '-d{}' .format (os .path .join (BUILD_PATH , 'doctrees' )),
289
- '-Dexclude_patterns={}' .format (self .exclude_patterns ),
290
- SOURCE_PATH ,
291
- os .path .join (BUILD_PATH , 'html' ),
292
- os .path .join (SOURCE_PATH , 'temp.rst' ),
293
- os .path .join (SOURCE_PATH , 'generated_temp/*.rst' ),
294
- )
295
- # for some reason it does not work with run_os, but it does if I
296
- # directly call the joined command
297
- # self._run_os(*args)
298
- os .system (" " .join (args ))
299
-
300
260
301
261
def main ():
302
262
cmds = [method for method in dir (DocBuilder ) if not method .startswith ('_' )]
@@ -341,32 +301,9 @@ def main():
341
301
342
302
os .environ ['PYTHONPATH' ] = args .python_path
343
303
344
- if args .docstring is not None :
345
- shutil .rmtree (os .path .join (BUILD_PATH , 'html' , 'generated_temp' ),
346
- ignore_errors = True )
347
- _generate_temp_docstring_file (args .docstring )
348
- exclude_patterns = _generate_exclude_pattern (single_doc = 'temp.rst' )
349
- _generate_index (single_doc = 'temp.rst' )
350
- DocBuilder (args .num_jobs , exclude_patterns ).build_docstring ()
351
- # open generated page in new browser tab
352
- if len (args .docstring ) == 1 :
353
- url = os .path .join (
354
- "file://" , DOC_PATH , "build" , "html" ,
355
- "generated_temp" , "{}.html" .format (args .docstring [0 ]))
356
- else :
357
- url = os .path .join (
358
- "file://" , DOC_PATH , "build" , "html" , "temp.html" )
359
- webbrowser .open (url , new = 2 )
360
- # clean-up generated files
361
- os .remove ('source/temp.rst' )
362
- shutil .rmtree (os .path .join (SOURCE_PATH , 'generated_temp' ),
363
- ignore_errors = True )
364
-
365
- else :
366
- _generate_index (not args .no_api , args .single )
367
- exclude_patterns = _generate_exclude_pattern (
368
- not args .no_api , args .single )
369
- getattr (DocBuilder (args .num_jobs , exclude_patterns ), args .command )()
304
+ getattr (DocBuilder (args .num_jobs ,
305
+ args .no_api ,
306
+ args .single ), args .command )()
370
307
371
308
372
309
if __name__ == '__main__' :
0 commit comments