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 webbrowser
20
21
import jinja2
21
22
23
+ import pandas
24
+
22
25
23
26
DOC_PATH = os .path .dirname (os .path .abspath (__file__ ))
24
27
SOURCE_PATH = os .path .join (DOC_PATH , 'source' )
25
28
BUILD_PATH = os .path .join (DOC_PATH , 'build' )
26
29
BUILD_DIRS = ['doctrees' , 'html' , 'latex' , 'plots' , '_static' , '_templates' ]
27
30
28
31
29
- def _generate_index (include_api , single_doc = None ):
30
- """Create index.rst file with the specified sections.
31
-
32
- Parameters
33
- ----------
34
- include_api : bool
35
- Whether API documentation will be built.
36
- single_doc : str or None
37
- If provided, this single documentation page will be generated.
38
- """
39
- if single_doc is not None :
40
- single_doc = os .path .splitext (os .path .basename (single_doc ))[0 ]
41
- include_api = False
42
-
43
- with open (os .path .join (SOURCE_PATH , 'index.rst.template' )) as f :
44
- t = jinja2 .Template (f .read ())
45
-
46
- with open (os .path .join (SOURCE_PATH , 'index.rst' ), 'w' ) as f :
47
- f .write (t .render (include_api = include_api ,
48
- single_doc = single_doc ))
49
-
50
-
51
32
@contextmanager
52
33
def _maybe_exclude_notebooks ():
53
34
"""Skip building the notebooks if pandoc is not installed.
@@ -58,6 +39,7 @@ def _maybe_exclude_notebooks():
58
39
1. nbconvert isn't installed, or
59
40
2. nbconvert is installed, but pandoc isn't
60
41
"""
42
+ # TODO move to exclude_pattern
61
43
base = os .path .dirname (__file__ )
62
44
notebooks = [os .path .join (base , 'source' , nb )
63
45
for nb in ['style.ipynb' ]]
@@ -96,8 +78,110 @@ class DocBuilder:
96
78
All public methods of this class can be called as parameters of the
97
79
script.
98
80
"""
99
- def __init__ (self , num_jobs = 1 ):
81
+ def __init__ (self , num_jobs = 1 , include_api = True , single_doc = None ):
100
82
self .num_jobs = num_jobs
83
+ self .include_api = include_api
84
+ self .single_doc = None
85
+ self .single_doc_type = None
86
+ if single_doc is not None :
87
+ self ._process_single_doc (single_doc )
88
+ self .exclude_patterns = self ._exclude_patterns
89
+
90
+ self ._generate_index ()
91
+ if self .single_doc_type == 'docstring' :
92
+ self ._run_os ('sphinx-autogen' , '-o' ,
93
+ 'source/generated_single' , 'source/index.rst' )
94
+
95
+ @property
96
+ def _exclude_patterns (self ):
97
+ """Docs source files that will be excluded from building."""
98
+ # TODO move maybe_exclude_notebooks here
99
+ if self .single_doc is not None :
100
+ rst_files = [f for f in os .listdir (SOURCE_PATH )
101
+ if ((f .endswith ('.rst' ) or f .endswith ('.ipynb' ))
102
+ and (f != 'index.rst' )
103
+ and (f != '{0}.rst' .format (self .single_doc )))]
104
+ if self .single_doc_type != 'api' :
105
+ rst_files += ['generated/*.rst' ]
106
+ elif not self .include_api :
107
+ rst_files = ['api.rst' , 'generated/*.rst' ]
108
+ else :
109
+ rst_files = ['generated_single/*.rst' ]
110
+
111
+ exclude_patterns = ',' .join (
112
+ '{!r}' .format (i ) for i in ['**.ipynb_checkpoints' ] + rst_files )
113
+
114
+ return exclude_patterns
115
+
116
+ def _process_single_doc (self , single_doc ):
117
+ """Extract self.single_doc (base name) and self.single_doc_type from
118
+ passed single_doc kwarg.
119
+
120
+ """
121
+ self .include_api = False
122
+
123
+ if single_doc == 'api.rst' :
124
+ self .single_doc_type = 'api'
125
+ self .single_doc = 'api'
126
+ elif os .path .exists (os .path .join (SOURCE_PATH , single_doc )):
127
+ self .single_doc_type = 'rst'
128
+ self .single_doc = os .path .splitext (os .path .basename (single_doc ))[0 ]
129
+ elif os .path .exists (
130
+ os .path .join (SOURCE_PATH , '{}.rst' .format (single_doc ))):
131
+ self .single_doc_type = 'rst'
132
+ self .single_doc = single_doc
133
+ elif single_doc is not None :
134
+ try :
135
+ obj = pandas
136
+ for name in single_doc .split ('.' ):
137
+ obj = getattr (obj , name )
138
+ except AttributeError :
139
+ raise ValueError ('Single document not understood, it should '
140
+ 'be a file in doc/source/*.rst (e.g. '
141
+ '"contributing.rst" or a pandas function or '
142
+ 'method (e.g. "pandas.DataFrame.head")' )
143
+ else :
144
+ self .single_doc_type = 'docstring'
145
+ if single_doc .startswith ('pandas.' ):
146
+ self .single_doc = single_doc [len ('pandas.' ):]
147
+ else :
148
+ self .single_doc = single_doc
149
+
150
+ def _copy_generated_docstring (self ):
151
+ """Copy existing generated (from api.rst) docstring page because
152
+ this is more correct in certain cases (where a custom autodoc
153
+ template is used).
154
+
155
+ """
156
+ fname = os .path .join (SOURCE_PATH , 'generated' ,
157
+ 'pandas.{}.rst' .format (self .single_doc ))
158
+ temp_dir = os .path .join (SOURCE_PATH , 'generated_single' )
159
+
160
+ try :
161
+ os .makedirs (temp_dir )
162
+ except OSError :
163
+ pass
164
+
165
+ if os .path .exists (fname ):
166
+ try :
167
+ # copying to make sure sphinx always thinks it is new
168
+ # and needs to be re-generated (to pick source code changes)
169
+ shutil .copy (fname , temp_dir )
170
+ except : # noqa
171
+ pass
172
+
173
+ def _generate_index (self ):
174
+ """Create index.rst file with the specified sections."""
175
+ if self .single_doc_type == 'docstring' :
176
+ self ._copy_generated_docstring ()
177
+
178
+ with open (os .path .join (SOURCE_PATH , 'index.rst.template' )) as f :
179
+ t = jinja2 .Template (f .read ())
180
+
181
+ with open (os .path .join (SOURCE_PATH , 'index.rst' ), 'w' ) as f :
182
+ f .write (t .render (include_api = self .include_api ,
183
+ single_doc = self .single_doc ,
184
+ single_doc_type = self .single_doc_type ))
101
185
102
186
@staticmethod
103
187
def _create_build_structure ():
@@ -121,7 +205,10 @@ def _run_os(*args):
121
205
--------
122
206
>>> DocBuilder()._run_os('python', '--version')
123
207
"""
124
- subprocess .check_call (args , stderr = subprocess .STDOUT )
208
+ # TODO check_call should be more safe, but it fails with
209
+ # exclude patterns, needs investigation
210
+ # subprocess.check_call(args, stderr=subprocess.STDOUT)
211
+ os .system (' ' .join (args ))
125
212
126
213
def _sphinx_build (self , kind ):
127
214
"""Call sphinx to build documentation.
@@ -142,11 +229,21 @@ def _sphinx_build(self, kind):
142
229
self ._run_os ('sphinx-build' ,
143
230
'-j{}' .format (self .num_jobs ),
144
231
'-b{}' .format (kind ),
145
- '-d{}' .format (os .path .join (BUILD_PATH ,
146
- 'doctrees' ) ),
232
+ '-d{}' .format (os .path .join (BUILD_PATH , 'doctrees' )),
233
+ '-Dexclude_patterns={}' . format ( self . exclude_patterns ),
147
234
SOURCE_PATH ,
148
235
os .path .join (BUILD_PATH , kind ))
149
236
237
+ def _open_browser (self ):
238
+ base_url = os .path .join ('file://' , DOC_PATH , 'build' , 'html' )
239
+ if self .single_doc_type == 'docstring' :
240
+ url = os .path .join (
241
+ base_url ,
242
+ 'generated_single' , 'pandas.{}.html' .format (self .single_doc ))
243
+ else :
244
+ url = os .path .join (base_url , '{}.html' .format (self .single_doc ))
245
+ webbrowser .open (url , new = 2 )
246
+
150
247
def html (self ):
151
248
"""Build HTML documentation."""
152
249
self ._create_build_structure ()
@@ -156,6 +253,11 @@ def html(self):
156
253
if os .path .exists (zip_fname ):
157
254
os .remove (zip_fname )
158
255
256
+ if self .single_doc is not None :
257
+ self ._open_browser ()
258
+ shutil .rmtree (os .path .join (SOURCE_PATH , 'generated_single' ),
259
+ ignore_errors = True )
260
+
159
261
def latex (self , force = False ):
160
262
"""Build PDF documentation."""
161
263
self ._create_build_structure ()
@@ -222,8 +324,8 @@ def main():
222
324
metavar = 'FILENAME' ,
223
325
type = str ,
224
326
default = None ,
225
- help = ('filename of section to compile, '
226
- 'e.g. "indexing"' ))
327
+ help = ('filename of section or method name to '
328
+ 'compile, e.g. "indexing", "DataFrame.join "' ))
227
329
argparser .add_argument ('--python-path' ,
228
330
type = str ,
229
331
default = os .path .join (DOC_PATH , '..' ),
@@ -235,8 +337,10 @@ def main():
235
337
args .command , ', ' .join (cmds )))
236
338
237
339
os .environ ['PYTHONPATH' ] = args .python_path
238
- _generate_index (not args .no_api , args .single )
239
- getattr (DocBuilder (args .num_jobs ), args .command )()
340
+
341
+ getattr (DocBuilder (args .num_jobs ,
342
+ not args .no_api ,
343
+ args .single ), args .command )()
240
344
241
345
242
346
if __name__ == '__main__' :
0 commit comments