From 4e1bb0380e6cdc324571aba28cc3d2838d4f8115 Mon Sep 17 00:00:00 2001 From: oesteban Date: Fri, 20 Dec 2019 17:11:25 -0800 Subject: [PATCH] DOC: Deep revision of documentation building This PR follows up on #3119 (merge that one first for a clean diff, or diff against ``oesteban:maint/dedup-apigen-code``). In practice, this PR fixes several broken points of our documentation (e.g., the workflows list was empty and now it has been updated, changelog not rendered, API of pure python code not rendered by the Nipype API parser was missing, etc.). CHANGES ------- * Replaced the ``numpydoc`` sphinx extension with ``sphinxcontrib-napoleon``. * Removed autosummary sphinx extension, required by numpydoc * Cleared up ``docs/sphinxext/*``, as nothing is now used from there * Use current sphinx-apidoc/autodoc/autosummary * Removed the modref generation tooling, as it is not necessary anymore after re-enabling apidoc. * Cut building warnings down to 321 - just those we incur because our API generator. This required some fixes of some docstrings. Beyond those corresponding to the Nipype API generator, only missing links remain as warnings (for sections in the navbar). * Updated changelogs to be reStructuredText. --- doc/Makefile | 13 +- doc/api/index.rst | 13 +- .../{0.X.X-changelog => 0.X.X-changelog.rst} | 8 +- .../{1.X.X-changelog => 1.X.X-changelog.rst} | 42 +-- doc/changes.rst | 4 +- doc/conf.py | 112 +++++--- doc/devel/cmd_interface_devel.rst | 2 +- doc/devel/interface_specs.rst | 3 +- doc/devel/testing_nipype.rst | 2 +- doc/documentation.rst | 31 ++- doc/interfaces/index.rst | 1 - doc/links_names.txt | 2 +- doc/sphinxext/README.txt | 16 -- doc/sphinxext/autosummary_generate.py | 240 ------------------ doc/sphinxext/ipython_console_highlighting.py | 101 -------- doc/users/install.rst | 6 +- doc/version.rst | 6 +- nipype/__init__.py | 8 +- nipype/pipeline/plugins/base.py | 54 ++-- nipype/utils/config.py | 4 +- nipype/utils/filemanip.py | 6 +- nipype/utils/nipype2boutiques.py | 51 ++-- tools/README | 15 -- tools/build_interface_docs.py | 15 +- tools/build_modref_templates.py | 44 ---- tools/update_changes.sh | 2 +- 26 files changed, 234 insertions(+), 567 deletions(-) rename doc/changelog/{0.X.X-changelog => 0.X.X-changelog.rst} (99%) rename doc/changelog/{1.X.X-changelog => 1.X.X-changelog.rst} (94%) delete mode 100644 doc/sphinxext/README.txt delete mode 100755 doc/sphinxext/autosummary_generate.py delete mode 100644 doc/sphinxext/ipython_console_highlighting.py delete mode 100644 tools/README delete mode 100755 tools/build_modref_templates.py diff --git a/doc/Makefile b/doc/Makefile index abe329a57a..25acfeb122 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -5,18 +5,19 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = +PYTHONPATH = $(PWD) # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html api htmlonly latex changes linkcheck doctest +.PHONY: help clean html nipypeapi htmlonly latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html make the HTML documentation" - @echo " api make API documents only" + @echo " nipypeapi make interface API documents only" @echo " latex make the LaTeX, you can set PAPER=a4 or PAPER=letter" @echo " pdf make and run the PDF generation" @echo " changes make an overview of all changed/added/deprecated" \ @@ -33,14 +34,12 @@ htmlonly: @echo @echo "Build finished. The HTML pages are in _build/html." -api: - rm -rf api/generated - python -u ../tools/build_modref_templates.py +nipypeapi: rm -rf interfaces/generated python -u ../tools/build_interface_docs.py @echo "Build API docs finished." -html: clean examples2rst api htmlonly +html: clean examples2rst nipypeapi htmlonly @echo "Build HTML and API finished." examples2rst: @@ -48,7 +47,7 @@ examples2rst: ../tools/make_examples.py --no-exec @echo "examples2rst finished." -latex: api +latex: nipypeapi $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." diff --git a/doc/api/index.rst b/doc/api/index.rst index 0e40dbf4ab..0cc9d87e32 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -1,10 +1,15 @@ .. _api-index: -### -API -### +########################################### +Library API (application program interface) +########################################### + +Information on specific functions, classes, and methods. :Release: |version| :Date: |today| -.. include:: generated/gen.rst +.. toctree:: + :glob: + + generated/* diff --git a/doc/changelog/0.X.X-changelog b/doc/changelog/0.X.X-changelog.rst similarity index 99% rename from doc/changelog/0.X.X-changelog rename to doc/changelog/0.X.X-changelog.rst index b1c16318b2..0c007cade7 100644 --- a/doc/changelog/0.X.X-changelog +++ b/doc/changelog/0.X.X-changelog.rst @@ -1,7 +1,7 @@ 0.14.0 (November 29, 2017) ========================== -###### [Full changelog](https://github.com/nipy/nipype/milestone/13) +(`Full changelog `__) * FIX+MAINT: Revision of the resource monitor (https://github.com/nipy/nipype/pull/2285) * FIX: MultiProc mishandling crashes (https://github.com/nipy/nipype/pull/2301) @@ -385,8 +385,10 @@ Release 0.9.0 (December 20, 2013) - camino.QBallMX - camino.LinRecon - camino.SFPeaks - One outdated interface no longer part of Camino was removed: + + One outdated interface no longer part of Camino was removed: - camino.Conmap + * ENH: Three new mrtrix interfaces were added: - mrtrix.GenerateDirections - mrtrix.FindShPeaks @@ -713,7 +715,7 @@ Features added * General: - - Type checking of inputs and outputs using Traits from ETS_. + - Type checking of inputs and outputs using Traits from ETS. - Support for nested workflows. - Preliminary Slicer and AFNI support. - New flexible DataGrabber node. diff --git a/doc/changelog/1.X.X-changelog b/doc/changelog/1.X.X-changelog.rst similarity index 94% rename from doc/changelog/1.X.X-changelog rename to doc/changelog/1.X.X-changelog.rst index 9282d261a2..488a15c285 100644 --- a/doc/changelog/1.X.X-changelog +++ b/doc/changelog/1.X.X-changelog.rst @@ -7,7 +7,7 @@ 1.3.0 (November 11, 2019) ========================= -##### [Full changelog](https://github.com/nipy/nipype/milestone/34?closed=1) +(`Full changelog `__) * FIX: Fixed typo in QwarpInputSpec Trait description (https://github.com/nipy/nipype/pull/3079) * FIX: Restore ``AFNICommand._get_fname``, required by some interfaces (https://github.com/nipy/nipype/pull/3071) @@ -33,7 +33,7 @@ Python 1.2.3 will be the last version to support Python 3.4. -##### [Full changelog](https://github.com/nipy/nipype/milestone/35?closed=1) +(`Full changelog `__) * FIX: Patch Path.mkdir for Python 2 (https://github.com/nipy/nipype/pull/3037) * FIX: Drop deprecated message argument to ``FileNotFoundError`` (https://github.com/nipy/nipype/pull/3035) @@ -52,7 +52,7 @@ Python 1.2.3 will be the last version to support Python 3.4. 1.2.2 (September 07, 2019) ========================== -##### [Full changelog](https://github.com/nipy/nipype/milestone/33?closed=1) +(`Full changelog `__) * FIX: Ensure ``loadpkl`` returns a not None value (https://github.com/nipy/nipype/pull/3020) * FIX: ``loadpkl`` failed when pklz file contained versioning info (https://github.com/nipy/nipype/pull/3017) @@ -68,7 +68,7 @@ Python 1.2.3 will be the last version to support Python 3.4. 1.2.1 (August 19, 2019) ======================= -##### [Full changelog](https://github.com/nipy/nipype/milestone/32?closed=1) +(`Full changelog `__) * FIX: Resolve/rebase paths from/to results files (https://github.com/nipy/nipype/pull/2971) * FIX: Use ``load_resultfile`` when loading a results pickle (https://github.com/nipy/nipype/pull/2985) @@ -78,7 +78,7 @@ Python 1.2.3 will be the last version to support Python 3.4. * FIX: Docker build (https://github.com/nipy/nipype/pull/2963) * FIX: Remove '=' signs from EddyQuad argument specifications (https://github.com/nipy/nipype/pull/2941) * FIX: Set input model to bedpostx for camino.TrackBedpostxProba (https://github.com/nipy/nipype/pull/2947) - * FIX: Allow ``max_sh``not to be set (auto mode) (https://github.com/nipy/nipype/pull/2940) + * FIX: Allow ``max_sh`` to not be set (auto mode) (https://github.com/nipy/nipype/pull/2940) * ENH: Update mrtrix reconst.py EstimateFOD max_sh to be able to accept list (https://github.com/nipy/nipype/pull/2990) * ENH: Let ``indirectory`` handle ``nipype.utils.filemanip.Path`` (https://github.com/nipy/nipype/pull/2989) * ENH: Add resolve/rebase ``BasePath`` traits methods & tests (https://github.com/nipy/nipype/pull/2970) @@ -95,7 +95,7 @@ Python 1.2.3 will be the last version to support Python 3.4. 1.2.0 (May 09, 2019) ==================== -##### [Full changelog](https://github.com/nipy/nipype/milestone/31?closed=1) +(`Full changelog `__) * FIX: Parsing of filename in AlignEpiAnatPy when filename does not have + (https://github.com/nipy/nipype/pull/2909) * FIX: Import nibabel reorientation bug fix (https://github.com/nipy/nipype/pull/2912) @@ -114,7 +114,7 @@ Python 1.2.3 will be the last version to support Python 3.4. 1.1.9 (February 25, 2019) ========================= -##### [Full changelog](https://github.com/nipy/nipype/milestone/30?closed=1) +(`Full changelog `__) * FIX: Make positional arguments to LaplacianThickness require previous argument (https://github.com/nipy/nipype/pull/2848) * FIX: Import math and csv modules for bids_gen_info (https://github.com/nipy/nipype/pull/2881) @@ -130,7 +130,7 @@ Python 1.2.3 will be the last version to support Python 3.4. 1.1.8 (January 28, 2019) ======================== -##### [Full changelog](https://github.com/nipy/nipype/milestone/29?closed=1) +(`Full changelog `__) * FIX: ANTS LaplacianThickness cmdline opts fixed up (https://github.com/nipy/nipype/pull/2846) * FIX: Resolve LinAlgError during SVD (https://github.com/nipy/nipype/pull/2838) @@ -152,7 +152,7 @@ Python 1.2.3 will be the last version to support Python 3.4. 1.1.7 (December 17, 2018) ========================= -##### [Full changelog](https://github.com/nipy/nipype/milestone/28?closed=1) +(`Full changelog `__) * FIX: Copy node list before generating a flat graph (https://github.com/nipy/nipype/pull/2828) * FIX: Update pytest req'd version to 3.6 (https://github.com/nipy/nipype/pull/2827) @@ -174,7 +174,7 @@ Python 1.2.3 will be the last version to support Python 3.4. 1.1.6 (November 26, 2018) ========================= -##### [Full changelog](https://github.com/nipy/nipype/milestone/27?closed=1) +(`Full changelog `__) * FIX: MapNodes fail when ``MultiProcPlugin`` passed by instance (https://github.com/nipy/nipype/pull/2786) * FIX: --fineTune arguments order for MeshFix command (https://github.com/nipy/nipype/pull/2780) @@ -197,7 +197,7 @@ Python 1.2.3 will be the last version to support Python 3.4. Hotfix release. -##### [Full changelog](https://github.com/nipy/nipype/milestone/26?closed=1) +(`Full changelog `__) * ENH: Allow timeouts during SLURM job status checks (https://github.com/nipy/nipype/pull/2767) * RF: Subclass non-daemon variants of all multiprocessing contexts (https://github.com/nipy/nipype/pull/2771) @@ -206,7 +206,7 @@ Hotfix release. 1.1.4 (October 31, 2018) ======================== -##### [Full changelog](https://github.com/nipy/nipype/milestone/25?closed=1) +(`Full changelog `__) * FIX: Python 2.7-3.7.1 compatible NonDaemonPool (https://github.com/nipy/nipype/pull/2754) * FIX: VRML typo (VMRL) in MeshFix (https://github.com/nipy/nipype/pull/2757) @@ -234,7 +234,7 @@ Hotfix release. 1.1.3 (September 24, 2018) ========================== -##### [Full changelog](https://github.com/nipy/nipype/milestone/24?closed=1) +(`Full changelog `__) * FIX: Return afni.Qwarp outputs as absolute paths (https://github.com/nipy/nipype/pull/2705) * FIX: Add informative error for interfaces that fail to return valid runtime object (https://github.com/nipy/nipype/pull/2692) @@ -253,7 +253,7 @@ Hotfix release. Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. -##### [Full changelog](https://github.com/nipy/nipype/milestone/23?closed=1) +(`Full changelog `__) * FIX: Read BIDS config.json under grabbids or layout (https://github.com/nipy/nipype/pull/2679) * FIX: Node __repr__ and detailed graph expansion (https://github.com/nipy/nipype/pull/2669) @@ -268,7 +268,7 @@ Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. 1.1.1 (July 30, 2018) ===================== -##### [Full changelog](https://github.com/nipy/nipype/milestone/22?closed=1) +(`Full changelog `__) * FIX: Un-set incorrect default options in TOPUP (https://github.com/nipy/nipype/pull/2637) * FIX: Copy FSCommand.version to ReconAll.version (https://github.com/nipy/nipype/pull/2656) @@ -290,7 +290,7 @@ Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. 1.1.0 (July 04, 2018) ===================== -###### [Full changelog](https://github.com/nipy/nipype/milestone/21?closed=1) +(`Full changelog `__) * RF: Futures-based MultiProc (https://github.com/nipy/nipype/pull/2598) * FIX: Avoid closing file descriptors on Windows (https://github.com/nipy/nipype/pull/2617) @@ -307,7 +307,7 @@ Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. 1.0.4 (May 29, 2018) ==================== -###### [Full changelog](https://github.com/nipy/nipype/milestone/20?closed=1) +(`Full changelog `__) * FIX: Update logging levels in enable_debug_mode (https://github.com/nipy/nipype/pull/2595) * FIX: Set default result in DistributedPluginBase._clean_queue (https://github.com/nipy/nipype/pull/2596) @@ -330,7 +330,7 @@ Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. 1.0.3 (April 30, 2018) ====================== -###### [Full changelog](https://github.com/nipy/nipype/milestone/19?closed=1) +(`Full changelog `__) * FIX: Propagate explicit Workflow config to Nodes (https://github.com/nipy/nipype/pull/2559) * FIX: Return non-enhanced volumes from dwi_flirt (https://github.com/nipy/nipype/pull/2547) @@ -357,7 +357,7 @@ Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. 1.0.2 (March 27, 2018) ====================== -###### [Full changelog](https://github.com/nipy/nipype/milestone/16?closed=1) +(`Full changelog `__) * FIX: dcm2niix interface (https://github.com/nipy/nipype/pull/2498) * FIX: mark .niml.dset as special extension in utils.filemanip (https://github.com/nipy/nipype/pull/2495) @@ -380,7 +380,7 @@ Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. 1.0.1 (February 27, 2018) ========================= -###### [Full changelog](https://github.com/nipy/nipype/milestone/16?closed=1) +(`Full changelog `__) * FIX: Small bug in freesurfer label2annot fill_thresh specs [#2377](https://github.com/nipy/nipype/pull/2377) * FIX: Error creating gradients in DTIRecon [#2460](https://github.com/nipy/nipype/pull/2460) @@ -413,7 +413,7 @@ Hot-fix release, resolving incorrect dependencies in 1.1.1 wheel. 1.0.0 (January 24, 2018) ======================== -###### [Full changelog](https://github.com/nipy/nipype/milestone/16?closed=1) +(`Full changelog `__) * FIX: Change to interface workdir within ``Interface.run()`` instead Node (https://github.com/nipy/nipype/pull/2384) * FIX: PBS plugin submissions (https://github.com/nipy/nipype/pull/2344) diff --git a/doc/changes.rst b/doc/changes.rst index 4585c58af8..858a907691 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -6,6 +6,8 @@ Changes in Nipype ================= -.. include:: ../CHANGES +.. include:: changelog/1.X.X-changelog.rst + +.. include:: changelog/0.X.X-changelog.rst .. include:: links_names.txt diff --git a/doc/conf.py b/doc/conf.py index c49f20e514..45bd46b97b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,49 +12,65 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os -from shutil import rmtree - -nipypepath = os.path.abspath('..') -sys.path.insert(1, nipypepath) - +from packaging.version import Version import nipype -if not os.path.exists('users/examples'): - os.mkdir('users/examples') -os.system('python ../tools/make_examples.py --no-exec') +# if not os.path.exists('users/examples'): +# os.mkdir('users/examples') +# os.system('python ../tools/make_examples.py --no-exec') -if os.path.exists('api/generated'): - rmtree('api/generated') -os.system('python ../tools/build_modref_templates.py') -if os.path.exists('interfaces/generated'): - rmtree('interfaces/generated') -os.system('python ../tools/build_interface_docs.py') +# if os.path.exists('interfaces/generated'): +# rmtree('interfaces/generated') +# os.system('python ../tools/build_interface_docs.py') # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('sphinxext')) +# sys.path.append(os.path.abspath('sphinxext')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.todo', - 'sphinx.ext.imgmath', - 'sphinx.ext.inheritance_diagram', - 'sphinx.ext.graphviz', - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.autosummary', - 'numpydoc', - 'matplotlib.sphinxext.plot_directive', - #'matplotlib.sphinxext.only_directives', - 'nipype.sphinxext.plot_workflow', - #'IPython.sphinxext.ipython_directive', - #'IPython.sphinxext.ipython_console_highlighting' - ] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.graphviz', + 'sphinx.ext.mathjax', + 'sphinx.ext.inheritance_diagram', + 'sphinx.ext.todo', + 'sphinxcontrib.apidoc', + 'sphinxcontrib.napoleon', + 'matplotlib.sphinxext.plot_directive', + 'nipype.sphinxext.plot_workflow', +] + +autodoc_mock_imports = [ + 'matplotlib', + 'nilearn', + 'nipy', + 'nitime', + 'numpy', + 'pandas', + 'seaborn', + 'skimage', + 'svgutils', + 'transforms3d', +] + +# Accept custom section names to be parsed for numpy-style docstrings +# of parameters. +# Requires pinning sphinxcontrib-napoleon to a specific commit while +# https://github.com/sphinx-contrib/napoleon/pull/10 is merged. +napoleon_use_param = False +napoleon_custom_sections = [ + ('Inputs', 'Parameters'), + ('Outputs', 'Parameters'), + ('Attributes', 'Parameters'), +] + + on_rtd = os.environ.get('READTHEDOCS') == 'True' if on_rtd: extensions.append('readthedocs_ext.readthedocs') @@ -80,9 +96,9 @@ # built documents. # # The short X.Y version. -version = nipype.__version__ +version = Version(nipype.__version__).public # The full version, including alpha/beta/rc tags. -release = "1.3.0-rc1" +release = nipype.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -101,6 +117,15 @@ # for source files. exclude_trees = ['_build'] +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [ + '_build', 'Thumbs.db', '.DS_Store', + 'api/generated/gen.rst', + 'interfaces/generated/gen.rst' +] + # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None @@ -248,8 +273,29 @@ # If false, no module index is generated. #latex_use_modindex = True +# -- apidoc extension configuration ------------------------------------------ +apidoc_module_dir = '../nipype' +apidoc_output_dir = 'api/generated' +apidoc_excluded_paths = [ + '*/tests/*', 'tests/*', + 'algorithms/*', + 'external/*', + 'fixes/*', + 'interfaces/*', + 'scripts/*', + 'sphinxext/*', + 'testing/*', + 'workflows/*', + 'conftest.py', + 'info.py', + 'pkg_info.py', + 'refs.py', +] +apidoc_separate_modules = True +apidoc_extra_args = ['--module-first', '-d 1', '-T'] + +# -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} -exclude_patterns = ['interfaces/generated/gen.rst', 'api/generated/gen.rst'] diff --git a/doc/devel/cmd_interface_devel.rst b/doc/devel/cmd_interface_devel.rst index b53dfe9f5b..e0153cf678 100644 --- a/doc/devel/cmd_interface_devel.rst +++ b/doc/devel/cmd_interface_devel.rst @@ -170,7 +170,7 @@ names as arguments on the command line. We have simplified this procedure with three additional metadata terms: ``name_source``, ``name_template``, ``keep_extension``. -For example in the :ref:`InvWarp ` class, the +For example in the :ref:`InvWarp ` class, the ``inverse_warp`` parameter is the name of the output file that is created by the routine. diff --git a/doc/devel/interface_specs.rst b/doc/devel/interface_specs.rst index 26623266c6..13d44e1528 100644 --- a/doc/devel/interface_specs.rst +++ b/doc/devel/interface_specs.rst @@ -470,8 +470,7 @@ If you used genfile: And optionally: -* ``_redirect_x``: If set to True it will make Nipype start Xvfb before running the interface and redirect X output to it. This is useful for -commandlines that spawn a graphical user interface. +* ``_redirect_x``: If set to True it will make Nipype start Xvfb before running the interface and redirect X output to it. This is useful for commandlines that spawn a graphical user interface. * ``_format_arg(name, spec, value)``: For extra formatting of the input values before passing them to generic ``_parse_inputs()`` method. diff --git a/doc/devel/testing_nipype.rst b/doc/devel/testing_nipype.rst index 0cce8b4671..5713f6727b 100644 --- a/doc/devel/testing_nipype.rst +++ b/doc/devel/testing_nipype.rst @@ -90,7 +90,7 @@ Testing Nipype using Docker Nipype is tested inside Docker containers and users can use nipype images to test local versions. First, install the `Docker Engine `_. Nipype has one base docker image called nipype/nipype:base, that contains several useful tools - (FreeSurfer, AFNI, FSL, ANTs, etc.), and additional test images +(FreeSurfer, AFNI, FSL, ANTs, etc.), and additional test images for specific Python versions: py27 for Python 2.7 and py36 for Python 3.6. Users can pull the nipype image for Python 3.6 as follows:: diff --git a/doc/documentation.rst b/doc/documentation.rst index 5b4216f8a7..1cf275d630 100644 --- a/doc/documentation.rst +++ b/doc/documentation.rst @@ -4,10 +4,8 @@ Documentation ============= -.. htmlonly:: - - :Release: |version| - :Date: |today| +:Release: |version| +:Date: |today| Previous versions: `1.3.0 `_ `1.2.3 `_ @@ -16,35 +14,42 @@ Previous versions: `1.3.0 `_ `1.2.3 `_. + Be sure to read `Michael's excellent tutorials `__. + + .. admonition:: Nipype Workflows - .. admonition:: Interfaces, Workflows and Examples + The workflows that used to live as a module under + ``nipype.workflows`` have been migrated to the + new project `NiFlows `__. + + .. admonition:: Interfaces and Examples .. hlist:: :columns: 2 - * Workflows + * *In-house* interfaces .. toctree:: :maxdepth: 1 :glob: - interfaces/generated/*workflows* - * Examples + interfaces/generated/*algorithms* + + * Interfaces to third-party tools .. toctree:: :maxdepth: 1 :glob: - users/examples/* - * Interfaces + interfaces/generated/*interfaces* + + * Examples .. toctree:: :maxdepth: 1 :glob: - interfaces/generated/*algorithms* - interfaces/generated/*interfaces* + users/examples/* .. admonition:: Developer Guides diff --git a/doc/interfaces/index.rst b/doc/interfaces/index.rst index 77b9541100..14deeec063 100644 --- a/doc/interfaces/index.rst +++ b/doc/interfaces/index.rst @@ -7,4 +7,3 @@ Interfaces and Algorithms :Release: |version| :Date: |today| -.. include:: generated/gen.rst diff --git a/doc/links_names.txt b/doc/links_names.txt index 4cf07795f7..1a51a6dea3 100644 --- a/doc/links_names.txt +++ b/doc/links_names.txt @@ -74,7 +74,7 @@ .. _EPD: http://www.enthought.com/products/epd.php .. _Traits: http://code.enthought.com/projects/traits/ .. _Miniconda: https://conda.io/miniconda.html -.. _neurodocker: https://github.com/kaczmarj/neurodocker +.. _NeuroDocker: https://github.com/kaczmarj/neurodocker .. Python imaging projects .. _PyMVPA: http://www.pymvpa.org diff --git a/doc/sphinxext/README.txt b/doc/sphinxext/README.txt deleted file mode 100644 index 08bcbe9a60..0000000000 --- a/doc/sphinxext/README.txt +++ /dev/null @@ -1,16 +0,0 @@ -=================== - Sphinx Extensions -=================== - -We've copied these sphinx extensions over from nipy-core. Any edits -should be done upstream in nipy-core, not here in nipype! - -These a are a few sphinx extensions we are using to build the nipy -documentation. In this file we list where they each come from, since we intend -to always push back upstream any modifications or improvements we make to them. - -* From numpy: - * numpy_ext - -* From ipython - * ipython_console_highlighting diff --git a/doc/sphinxext/autosummary_generate.py b/doc/sphinxext/autosummary_generate.py deleted file mode 100755 index 658c50e4a4..0000000000 --- a/doc/sphinxext/autosummary_generate.py +++ /dev/null @@ -1,240 +0,0 @@ -#!/usr/bin/env python -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -r""" -autosummary_generate.py OPTIONS FILES - -Generate automatic RST source files for items referred to in -autosummary:: directives. - -Each generated RST file contains a single auto*:: directive which -extracts the docstring of the referred item. - -Example Makefile rule:: - - generate: - ./ext/autosummary_generate.py -o source/generated source/*.rst - -""" -from __future__ import print_function, unicode_literals -from builtins import open - -import re -import inspect -import os -import optparse -import pydoc -from autosummary import import_by_name - -try: - from phantom_import import import_phantom_module -except ImportError: - import_phantom_module = lambda x: x - - -def main(): - p = optparse.OptionParser(__doc__.strip()) - p.add_option("-p", "--phantom", action="store", type="string", - dest="phantom", default=None, - help="Phantom import modules from a file") - p.add_option("-o", "--output-dir", action="store", type="string", - dest="output_dir", default=None, - help=("Write all output files to the given directory " - "(instead of writing them as specified in the " - "autosummary:: directives)")) - options, args = p.parse_args() - - if len(args) == 0: - p.error("wrong number of arguments") - - if options.phantom and os.path.isfile(options.phantom): - import_phantom_module(options.phantom) - - # read - names = {} - for name, loc in list(get_documented(args).items()): - for (filename, sec_title, keyword, toctree) in loc: - if toctree is not None: - path = os.path.join(os.path.dirname(filename), toctree) - names[name] = os.path.abspath(path) - - # write - for name, path in sorted(names.items()): - if options.output_dir is not None: - path = options.output_dir - - if not os.path.isdir(path): - os.makedirs(path) - - try: - obj, name = import_by_name(name) - except ImportError as e: - print("Failed to import '%s': %s" % (name, e)) - continue - - fn = os.path.join(path, '%s.rst' % name) - - if os.path.exists(fn): - # skip - continue - - f = open(fn, 'w') - - try: - f.write('%s\n%s\n\n' % (name, '=' * len(name))) - - if inspect.isclass(obj): - if issubclass(obj, Exception): - f.write(format_modulemember(name, 'autoexception')) - else: - f.write(format_modulemember(name, 'autoclass')) - elif inspect.ismodule(obj): - f.write(format_modulemember(name, 'automodule')) - elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj): - f.write(format_classmember(name, 'automethod')) - elif callable(obj): - f.write(format_modulemember(name, 'autofunction')) - elif hasattr(obj, '__get__'): - f.write(format_classmember(name, 'autoattribute')) - else: - f.write(format_modulemember(name, 'autofunction')) - finally: - f.close() - - -def format_modulemember(name, directive): - parts = name.split('.') - mod, name = '.'.join(parts[:-1]), parts[-1] - return ".. currentmodule:: %s\n\n.. %s:: %s\n" % (mod, directive, name) - - -def format_classmember(name, directive): - parts = name.split('.') - mod, name = '.'.join(parts[:-2]), '.'.join(parts[-2:]) - return ".. currentmodule:: %s\n\n.. %s:: %s\n" % (mod, directive, name) - - -def get_documented(filenames): - """ - Find out what items are documented in source/*.rst - See `get_documented_in_lines`. - - """ - documented = {} - for filename in filenames: - f = open(filename, 'r') - lines = f.read().splitlines() - documented.update(get_documented_in_lines(lines, filename=filename)) - f.close() - return documented - - -def get_documented_in_docstring(name, module=None, filename=None): - """ - Find out what items are documented in the given object's docstring. - See `get_documented_in_lines`. - - """ - try: - obj, real_name = import_by_name(name) - lines = pydoc.getdoc(obj).splitlines() - return get_documented_in_lines(lines, module=name, filename=filename) - except AttributeError: - pass - except ImportError as e: - print("Failed to import '%s': %s" % (name, e)) - return {} - - -def get_documented_in_lines(lines, module=None, filename=None): - """ - Find out what items are documented in the given lines - - Returns - ------- - documented : dict of list of (filename, title, keyword, toctree) - Dictionary whose keys are documented names of objects. - The value is a list of locations where the object was documented. - Each location is a tuple of filename, the current section title, - the name of the directive, and the value of the :toctree: argument - (if present) of the directive. - - """ - title_underline_re = re.compile("^[-=*_^#]{3,}\s*$") - autodoc_re = re.compile( - ".. auto(function|method|attribute|class|exception|module)::" - "\s*([A-Za-z0-9_.]+)\s*$") - autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*') - module_re = re.compile( - r'^\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$') - autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?') - toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') - - documented = {} - - current_title = [] - last_line = None - toctree = None - current_module = module - in_autosummary = False - - for line in lines: - try: - if in_autosummary: - m = toctree_arg_re.match(line) - if m: - toctree = m.group(1) - continue - - if line.strip().startswith(':'): - continue # skip options - - m = autosummary_item_re.match(line) - if m: - name = m.group(1).strip() - if current_module and not name.startswith( - current_module + '.'): - name = "%s.%s" % (current_module, name) - documented.setdefault(name, []).append( - (filename, current_title, 'autosummary', toctree)) - continue - if line.strip() == '': - continue - in_autosummary = False - - m = autosummary_re.match(line) - if m: - in_autosummary = True - continue - - m = autodoc_re.search(line) - if m: - name = m.group(2).strip() - if m.group(1) == "module": - current_module = name - documented.update(get_documented_in_docstring( - name, filename=filename)) - elif current_module and not name.startswith( - current_module + '.'): - name = "%s.%s" % (current_module, name) - documented.setdefault(name, []).append( - (filename, current_title, "auto" + m.group(1), None)) - continue - - m = title_underline_re.match(line) - if m and last_line: - current_title = last_line.strip() - continue - - m = module_re.match(line) - if m: - current_module = m.group(2) - continue - finally: - last_line = line - - return documented - - -if __name__ == "__main__": - main() diff --git a/doc/sphinxext/ipython_console_highlighting.py b/doc/sphinxext/ipython_console_highlighting.py deleted file mode 100644 index a400d3c9c1..0000000000 --- a/doc/sphinxext/ipython_console_highlighting.py +++ /dev/null @@ -1,101 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""reST directive for syntax-highlighting ipython interactive sessions. -""" - -# ----------------------------------------------------------------------------- -# Needed modules - -# Standard library -import re - -# Third party -from pygments.lexer import Lexer, do_insertions -from pygments.lexers.agile import (PythonConsoleLexer, PythonLexer, - PythonTracebackLexer) -from pygments.token import Comment, Generic - -from sphinx import highlighting - - -# ----------------------------------------------------------------------------- -# Global constants -line_re = re.compile('.*?\n') - -# ----------------------------------------------------------------------------- -# Code begins - classes and functions - - -class IPythonConsoleLexer(Lexer): - """ - For IPython console output or doctests, such as: - - .. sourcecode:: ipython - - In [1]: a = 'foo' - - In [2]: a - Out[2]: 'foo' - - In [3]: print a - foo - - In [4]: 1 / 0 - - Notes: - - - Tracebacks are not currently supported. - - - It assumes the default IPython prompts, not customized ones. - """ - - name = 'IPython console session' - aliases = ['ipython'] - mimetypes = ['text/x-ipython-console'] - input_prompt = re.compile("(In \[[0-9]+\]: )|( \.\.\.+:)") - output_prompt = re.compile("(Out\[[0-9]+\]: )|( \.\.\.+:)") - continue_prompt = re.compile(" \.\.\.+:") - tb_start = re.compile("\-+") - - def get_tokens_unprocessed(self, text): - pylexer = PythonLexer(**self.options) - - curcode = '' - insertions = [] - for match in line_re.finditer(text): - line = match.group() - input_prompt = self.input_prompt.match(line) - continue_prompt = self.continue_prompt.match(line.rstrip()) - output_prompt = self.output_prompt.match(line) - if line.startswith("#"): - insertions.append((len(curcode), - [(0, Comment, line)])) - elif input_prompt is not None: - insertions.append((len( - curcode), [(0, Generic.Prompt, input_prompt.group())])) - curcode += line[input_prompt.end():] - elif continue_prompt is not None: - insertions.append((len( - curcode), [(0, Generic.Prompt, continue_prompt.group())])) - curcode += line[continue_prompt.end():] - elif output_prompt is not None: - insertions.append((len( - curcode), [(0, Generic.Output, output_prompt.group())])) - curcode += line[output_prompt.end():] - else: - if curcode: - for item in do_insertions(insertions, - pylexer.get_tokens_unprocessed( - curcode)): - yield item - curcode = '' - insertions = [] - yield match.start(), Generic.Output, line - if curcode: - for item in do_insertions(insertions, - pylexer.get_tokens_unprocessed(curcode)): - yield item - -# ----------------------------------------------------------------------------- -# Register the extension as a valid pygments lexer -highlighting.lexers['ipython'] = IPythonConsoleLexer() diff --git a/doc/users/install.rst b/doc/users/install.rst index 3a710088e9..a16d41c5df 100644 --- a/doc/users/install.rst +++ b/doc/users/install.rst @@ -16,7 +16,7 @@ image from Docker hub:: docker pull nipype/nipype You may also build custom docker containers with specific versions of software -using Neurodocker_ (see the `Neurodocker tutorial +using NeuroDocker_ (see the `Neurodocker tutorial `_). Using conda @@ -61,7 +61,7 @@ listed below:: Debian and Ubuntu ~~~~~~~~~~~~~~~~~ -Add the `NeuroDebian `_ repository and install +Add the NeuroDebian_ repository and install the ``python-nipype`` package using ``apt-get`` or your favorite package manager. @@ -111,7 +111,7 @@ Interface Dependencies Nipype provides wrappers around many neuroimaging tools and contains some algorithms. These tools will need to be installed for Nipype to run. You can create containers with different versions of these tools installed using -Neurodocker_ (see the :doc:`neurodocker`). +NeuroDocker_. Installation for developers --------------------------- diff --git a/doc/version.rst b/doc/version.rst index c795d991c6..35e3e0a60f 100644 --- a/doc/version.rst +++ b/doc/version.rst @@ -1,6 +1,4 @@ .. _version: -.. htmlonly:: - - :Release: |version| - :Date: |today| +:Release: |version| +:Date: |today| diff --git a/nipype/__init__.py b/nipype/__init__.py index 76b2ba58f9..74c6a42dd7 100644 --- a/nipype/__init__.py +++ b/nipype/__init__.py @@ -67,10 +67,12 @@ def get_info(): def check_latest_version(raise_exception=False): - """Check for the latest version of the library + """ + Check for the latest version of the library. - parameters: - raise_exception: boolean + Parameters + ---------- + raise_exception: bool Raise a RuntimeError if a bad version is being used """ import etelemetry diff --git a/nipype/pipeline/plugins/base.py b/nipype/pipeline/plugins/base.py index f7fcb6dab1..4be8eb232b 100644 --- a/nipype/pipeline/plugins/base.py +++ b/nipype/pipeline/plugins/base.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: -"""Common graph operations for execution -""" +"""Common graph operations for execution.""" import sys from copy import deepcopy from glob import glob @@ -23,10 +22,7 @@ class PluginBase(object): - """ - Base class for plugins - - """ + """Base class for plugins.""" def __init__(self, plugin_args=None): if plugin_args is None: @@ -37,15 +33,20 @@ def __init__(self, plugin_args=None): def run(self, graph, config, updatehash=False): """ + Instruct the plugin to execute the workflow graph. + The core plugin member that should be implemented by all plugins. - graph: a networkx, flattened :abbr:`DAG (Directed Acyclic Graph)` - to be executed - - config: a nipype.config object - - updatehash: + Parameters + ---------- + graph : + a networkx, flattened :abbr:`DAG (Directed Acyclic Graph)` + to be executed + config : :obj:`~nipype.config` + a nipype.config object + updatehash : :obj:`bool` + whether cached nodes with stale hash should be just updated. """ raise NotImplementedError @@ -55,19 +56,7 @@ class DistributedPluginBase(PluginBase): """ Execute workflow with a distribution engine - Relevant class attributes - ------------------------- - - procs: list (N) of underlying interface elements to be processed - proc_done: a boolean numpy array (N,) signifying whether a process has been - submitted for execution - proc_pending: a boolean numpy array (N,) signifying whether a - process is currently running. - depidx: a boolean matrix (NxN) storing the dependency structure accross - processes. Process dependencies are derived from each column. - - Combinations of ``proc_done`` and ``proc_pending`` - -------------------------------------------------- + Combinations of ``proc_done`` and ``proc_pending``: +------------+---------------+--------------------------------+ | proc_done | proc_pending | outcome | @@ -80,6 +69,21 @@ class DistributedPluginBase(PluginBase): +------------+---------------+--------------------------------+ | False | True | INVALID COMBINATION | +------------+---------------+--------------------------------+ + + Attributes + ---------- + procs : :obj:`list` + list (N) of underlying interface elements to be processed + proc_done : :obj:`numpy.ndarray` + a boolean numpy array (N,) signifying whether a process has been + submitted for execution + proc_pending : :obj:`numpy.ndarray` + a boolean numpy array (N,) signifying whether a + process is currently running. + depidx : :obj:`numpy.matrix` + a boolean matrix (NxN) storing the dependency structure accross + processes. Process dependencies are derived from each column. + """ def __init__(self, plugin_args=None): diff --git a/nipype/utils/config.py b/nipype/utils/config.py index 999ee76307..6f1b385672 100644 --- a/nipype/utils/config.py +++ b/nipype/utils/config.py @@ -71,7 +71,7 @@ [check] interval = 1209600 -""".format +""" def mkdir_p(path): @@ -130,7 +130,7 @@ def cwd(self): def set_default_config(self): """Read default settings template and set into config object""" - default_cfg = DEFAULT_CONFIG_TPL( + default_cfg = DEFAULT_CONFIG_TPL.format( log_dir=os.path.expanduser("~"), # Get $HOME in a platform-agnostic way crashdump_dir=self.cwd, # Read cached cwd ) diff --git a/nipype/utils/filemanip.py b/nipype/utils/filemanip.py index b0d6c4a0c1..735cc610b6 100644 --- a/nipype/utils/filemanip.py +++ b/nipype/utils/filemanip.py @@ -284,7 +284,8 @@ def _generate_cifs_table(): def on_cifs(fname): - """ Checks whether a file path is on a CIFS filesystem mounted in a POSIX + """ + Checks whether a file path is on a CIFS filesystem mounted in a POSIX host (i.e., has the ``mount`` command). On Windows, Docker mounts host directories into containers through CIFS @@ -292,9 +293,10 @@ def on_cifs(fname): the CIFS driver exposes to the OS as symlinks. We have found that under concurrent access to the filesystem, this feature can result in failures to create or read recently-created symlinks, - leading to inconsistent behavior and ``FileNotFoundError``s. + leading to inconsistent behavior and ``FileNotFoundError``. This check is written to support disabling symlinks on CIFS shares. + """ # Only the first match (most recent parent) counts for fspath, fstype in _cifs_table: diff --git a/nipype/utils/nipype2boutiques.py b/nipype/utils/nipype2boutiques.py index 4013714bc2..0a12e59f28 100644 --- a/nipype/utils/nipype2boutiques.py +++ b/nipype/utils/nipype2boutiques.py @@ -34,25 +34,42 @@ def generate_boutiques_descriptor( tags=None, ): """ - Returns a JSON string containing a JSON Boutiques description of a - Nipype interface. - Arguments: - * module: module where the Nipype interface is declared. - * interface_name: name of Nipype interface. - * container_image: name of the container image where the tool is installed - * container_type: type of container image (Docker or Singularity) - * container_index: optional index where the image is available - * verbose: print information messages - * save: True if you want to save descriptor to a file - * save_path: file path for the saved descriptor (defaults to name of the + Generate a JSON Boutiques description of a Nipype interface. + + Arguments + --------- + module : + module where the Nipype interface is declared. + interface_name : + name of Nipype interface. + container_image : + name of the container image where the tool is installed + container_type : + type of container image (Docker or Singularity) + container_index : + optional index where the image is available + verbose : + print information messages + save : + True if you want to save descriptor to a file + save_path : + file path for the saved descriptor (defaults to name of the interface in current directory) - * author: author of the tool (required for publishing) - * ignore_inputs: list of interface inputs to not include in the descriptor - * tags: JSON object containing tags to include in the descriptor, - e.g. "{\"key1\": \"value1\"}" (note: the tags 'domain:neuroinformatics' - and 'interface-type:nipype' are included by default) - """ + author : + author of the tool (required for publishing) + ignore_inputs : + list of interface inputs to not include in the descriptor + tags : + JSON object containing tags to include in the descriptor, + e.g. ``{"key1": "value1"}`` (note: the tags 'domain:neuroinformatics' + and 'interface-type:nipype' are included by default) + + Returns + ------- + boutiques : str + string containing a Boutiques' JSON object + """ if not module: raise Exception("Undefined module.") diff --git a/tools/README b/tools/README deleted file mode 100644 index 8d987d4e00..0000000000 --- a/tools/README +++ /dev/null @@ -1,15 +0,0 @@ -============== - Nipype Tools -============== - -This directory contains various tools used by the nipype developers. -Only install tools here that are unique to the nipype project. Any -tools shared with our parent project, nipy, should go in the -nipy/tools directory. - -Exceptions ----------- - -* apigen.py: This is not importable from nipy, so I copied it. -* build_modref_templates.py: This was copied and modified to work with nipype. - diff --git a/tools/build_interface_docs.py b/tools/build_interface_docs.py index d21d19428a..f42adc7904 100755 --- a/tools/build_interface_docs.py +++ b/tools/build_interface_docs.py @@ -26,22 +26,25 @@ r"\.testing", r"\.caching", r"\.scripts", + r"\.sphinxext$", + r"\.workflows" ] # Modules that should not be included in generated API docs. docwriter.module_skip_patterns += [ - r"\.version$", + r"\.conftest", r"\.interfaces\.base$", r"\.interfaces\.matlab$", - r"\.interfaces\.rest$", r"\.interfaces\.pymvpa$", + r"\.interfaces\.rest$", r"\.interfaces\.slicer\.generate_classes$", r"\.interfaces\.spm\.base$", r"\.interfaces\.traits", r"\.pipeline\.alloy$", r"\.pipeline\.s3_node_wrapper$", - r"\.testing", + r"\.pkg_info" r"\.scripts", - r"\.conftest", + r"\.testing", + r"\.version$", ] docwriter.class_skip_patterns += [ "AFNICommand", @@ -52,12 +55,12 @@ "^SPM", "Tester", "Spec$", - "Numpy" + "Numpy", # NipypeTester raises an # exception when instantiated in # InterfaceHelpWriter.generate_api_doc "NipypeTester", ] docwriter.write_api_docs(outdir) - docwriter.write_index(outdir, "gen", relative_to="interfaces") + # docwriter.write_index(outdir, "gen") print("%d files written" % len(docwriter.written_modules)) diff --git a/tools/build_modref_templates.py b/tools/build_modref_templates.py deleted file mode 100755 index 8a4c480f51..0000000000 --- a/tools/build_modref_templates.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Script to auto-generate our API docs. -""" -# stdlib imports -import os -import sys - -# ***************************************************************************** -if __name__ == "__main__": - nipypepath = os.path.abspath("..") - sys.path.insert(1, nipypepath) - package = "nipype" - # local imports - from apigen import ApiDocWriter - - outdir = os.path.join("api", "generated") - docwriter = ApiDocWriter(package) - # Packages that should not be included in generated API docs. - docwriter.package_skip_patterns += [ - "\.external$", - "\.utils$", - "\.interfaces\.", - "\.workflows$", - "\.pipeline\.plugins$", - "\.testing$", - "\.fixes$", - "\.algorithms$", - "\.scripts$", - ] - # Modules that should not be included in generated API docs. - docwriter.module_skip_patterns += [ - "\.version$", - "info", - "\.interfaces\.(?!(base|matlab))", - "\.pipeline\.utils$", - "\.interfaces\.slicer\.generate_classes$", - "\.interfaces\.pymvpa$", - "\.scripts$", - ] - docwriter.write_api_docs(outdir) - docwriter.write_index(outdir, "gen", relative_to="api") - print("%d files written" % len(docwriter.written_modules)) diff --git a/tools/update_changes.sh b/tools/update_changes.sh index 1ba3528b1f..7a12a2d1b4 100755 --- a/tools/update_changes.sh +++ b/tools/update_changes.sh @@ -13,7 +13,7 @@ set -u # Treat unset variables as an error when substituting. set -x # Print command traces before executing command. ROOT=$( git rev-parse --show-toplevel ) -CHANGES=$ROOT/doc/changelog/1.X.X-changelog +CHANGES=$ROOT/doc/changelog/1.X.X-changelog.rst # Check whether the Upcoming release header is present head -1 $CHANGES | grep -q Upcoming