Skip to content

DOC: script to build single docstring page #19840

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 75 additions & 6 deletions doc/make.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import argparse
from contextlib import contextmanager
import jinja2
import webbrowser


DOC_PATH = os.path.dirname(os.path.abspath(__file__))
Expand All @@ -26,7 +27,7 @@
BUILD_DIRS = ['doctrees', 'html', 'latex', 'plots', '_static', '_templates']


def _generate_index(include_api, single_doc=None):
def _generate_index(include_api=True, single_doc=None):
"""Create index.rst file with the specified sections.

Parameters
Expand All @@ -48,6 +49,37 @@ def _generate_index(include_api, single_doc=None):
single_doc=single_doc))


def _generate_exclude_pattern(include_api=True, single_doc=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not major, but the name "include_api" threw me off. Are we including it in output, or are we including it in the list of exclusions, thus excluding it? :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah :) I agree it might be confusing, but it is the same keyword name as for _generate_index, and there it is more clear that include_api means to include it in the index.rst file (and thus here, to not put it in the excluded files)


if not include_api:
rst_files = ['api.rst', 'generated/*.rst']
elif single_doc is not None:
rst_files = [f for f in os.listdir(SOURCE_PATH)
if ((f.endswith('.rst') or f.endswith('.ipynb'))
and (f != 'index.rst') and (f != single_doc))]
rst_files += ['generated/*.rst']
else:
rst_files = []

exclude_patterns = ",".join(
['{!r}'.format(i) for i in ['**.ipynb_checkpoints'] + rst_files])

return exclude_patterns


def _write_temp_file(classtype, module, function):

s = """{1}.{2}
=================================

.. currentmodule:: {1}

.. auto{0}:: {2}""".format(classtype, module, function)

with open(os.path.join(SOURCE_PATH, "temp.rst"), 'w') as f:
f.write(s)


@contextmanager
def _maybe_exclude_notebooks():
"""Skip building the notebooks if pandoc is not installed.
Expand Down Expand Up @@ -96,8 +128,9 @@ class DocBuilder:
All public methods of this class can be called as parameters of the
script.
"""
def __init__(self, num_jobs=1):
def __init__(self, num_jobs=1, exclude_patterns=None):
self.num_jobs = num_jobs
self.exclude_patterns = exclude_patterns

@staticmethod
def _create_build_structure():
Expand Down Expand Up @@ -142,8 +175,8 @@ def _sphinx_build(self, kind):
self._run_os('sphinx-build',
'-j{}'.format(self.num_jobs),
'-b{}'.format(kind),
'-d{}'.format(os.path.join(BUILD_PATH,
'doctrees')),
'-d{}'.format(os.path.join(BUILD_PATH, 'doctrees')),
# TODO integrate exclude_patterns
SOURCE_PATH,
os.path.join(BUILD_PATH, kind))

Expand Down Expand Up @@ -199,6 +232,23 @@ def zip_html(self):
'-q',
*fnames)

def build_docstring(self):
"""Build single docstring page"""
self._create_build_structure()

args = ('sphinx-build',
'-bhtml',
'-d{}'.format(os.path.join(BUILD_PATH, 'doctrees')),
'-Dexclude_patterns={}'.format(self.exclude_patterns),
SOURCE_PATH,
os.path.join(BUILD_PATH, 'html'),
os.path.join(SOURCE_PATH, 'temp.rst')
)
# for some reason it does not work with run_os, but it does if I
# directly call the joined command
# self._run_os(*args)
os.system(" ".join(args))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@datapythonista here I had the problem that if I did self._run_os(), that for some reason it did not work as I wanted (the exclude_patterns seemed to be ignored). While when running the exact same (joined) command in the console of with os.system(..) does what I want.
Any idea?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to take a look, I think Popen and the functions using it like check_call have some problems if there are spaces in the arguments, may be the exclude patterns need to be between quotes. I'll take a look later. But the reason I wrapped the check_call in a method was exactly to be able to move quickly to os.system or something else. check_call is the preferred way, as it's safer, but it's not the first time I see problems with it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally there should be no space in the exclude_patterns argument.



def main():
cmds = [method for method in dir(DocBuilder) if not method.startswith('_')]
Expand Down Expand Up @@ -228,15 +278,34 @@ def main():
type=str,
default=os.path.join(DOC_PATH, '..'),
help='path')
argparser.add_argument('--docstring',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this argument is not used anymore, is it?

metavar='FILENAME',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much does it complicate things to do --nargs='*'? I'm thinking you might have a shared doc and want to do --docstring Series.fillna DataFrame.fillna

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, not, that wouldn't be to hard I think. Good idea

type=str,
default=None,
help=('method or function name to compile, '
'e.g. "DataFrame.join"'))
args = argparser.parse_args()

if args.command not in cmds:
raise ValueError('Unknown command {}. Available options: {}'.format(
args.command, ', '.join(cmds)))

os.environ['PYTHONPATH'] = args.python_path
_generate_index(not args.no_api, args.single)
getattr(DocBuilder(args.num_jobs), args.command)()

if args.docstring is not None:
_write_temp_file('method', 'pandas', args.docstring)
exclude_patterns = _generate_exclude_pattern(single_doc='temp.rst')
_generate_index(single_doc='temp.rst')
DocBuilder(args.num_jobs, exclude_patterns).build_docstring()
url = "file://" + os.getcwd() + "/build/html/temp.html"
webbrowser.open(url, new=2)
os.remove('source/temp.rst')

else:
_generate_index(not args.no_api, args.single)
exclude_patterns = _generate_exclude_pattern(
not args.no_api, args.single)
getattr(DocBuilder(args.num_jobs, exclude_patterns), args.command)()


if __name__ == '__main__':
Expand Down
30 changes: 0 additions & 30 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,36 +83,6 @@
if any(re.match("\s*api\s*", l) for l in index_rst_lines):
autosummary_generate = True

files_to_delete = []
for f in os.listdir(os.path.dirname(__file__)):
if (not f.endswith(('.ipynb', '.rst')) or
f.startswith('.') or os.path.basename(f) == 'index.rst'):
continue

_file_basename = os.path.splitext(f)[0]
_regex_to_match = "\s*{}\s*$".format(_file_basename)
if not any(re.match(_regex_to_match, line) for line in index_rst_lines):
files_to_delete.append(f)

if files_to_delete:
print("I'm about to DELETE the following:\n%s\n" % list(sorted(files_to_delete)))
sys.stdout.write("WARNING: I'd like to delete those to speed up processing (yes/no)? ")
if PY3:
answer = input()
else:
answer = raw_input()

if answer.lower().strip() in ('y','yes'):
for f in files_to_delete:
f = os.path.join(os.path.join(os.path.dirname(__file__),f))
f= os.path.abspath(f)
try:
print("Deleting %s" % f)
os.unlink(f)
except:
print("Error deleting %s" % f)
pass

# Add any paths that contain templates here, relative to this directory.
templates_path = ['../_templates']

Expand Down