From 8d42980c65697063f8630e52ed44a71a98335acc Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 14:53:03 -0800 Subject: [PATCH 01/18] RF+DOC: refactor doc build * Remove our own copies of numpydoc and IPython console highlighting; * update conf.py to latest Sphinx; * refactor search for nipy and version getting; * add doc and dev requirements files. --- dev-requirements.txt | 4 + doc-requirements.txt | 8 + doc/README.txt | 7 + doc/conf.py | 93 ++-- doc/devel/guidelines/howto_document.rst | 4 + doc/links_names.txt | 4 +- doc/sphinxext/ipython_console_highlighting.py | 100 ---- doc/sphinxext/numpy_ext/__init__.py | 0 doc/sphinxext/numpy_ext/docscrape.py | 505 ------------------ doc/sphinxext/numpy_ext/docscrape_sphinx.py | 227 -------- doc/sphinxext/numpy_ext/numpydoc.py | 169 ------ 11 files changed, 79 insertions(+), 1042 deletions(-) create mode 100644 dev-requirements.txt create mode 100644 doc-requirements.txt delete mode 100644 doc/sphinxext/ipython_console_highlighting.py delete mode 100644 doc/sphinxext/numpy_ext/__init__.py delete mode 100644 doc/sphinxext/numpy_ext/docscrape.py delete mode 100644 doc/sphinxext/numpy_ext/docscrape_sphinx.py delete mode 100644 doc/sphinxext/numpy_ext/numpydoc.py diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000000..f63af96cf4 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,4 @@ +# Requirements for running tests +-r requirements.txt +nose +mock diff --git a/doc-requirements.txt b/doc-requirements.txt new file mode 100644 index 0000000000..8040e1fcd0 --- /dev/null +++ b/doc-requirements.txt @@ -0,0 +1,8 @@ +# Requirements for building docs +# Check these dependencies against doc/conf.py +-r dev-requirements.txt +sphinx>=1.0 +numpydoc +matplotlib +texext +ipython diff --git a/doc/README.txt b/doc/README.txt index 71e31f09af..0a0c66b186 100644 --- a/doc/README.txt +++ b/doc/README.txt @@ -18,6 +18,13 @@ you must have: * latex (for the PNG mathematics graphics) * graphviz (for the inheritance diagrams) +For the Python dependencies, do:: + + pip install -r ../doc-requirements.txt + +Files and directories +===================== + This directory contains: * Makefile - the build script to build the HTML or PDF docs. Type diff --git a/doc/conf.py b/doc/conf.py index 483c2a29c9..7329bfed1a 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -13,36 +13,41 @@ # serve to show the default value. import sys, os +from importlib import import_module + +import sphinx + +# Doc generation depends on being able to import project +project = 'nipy' +try: + project_module = import_module(project) +except ImportError: + raise RuntimeError('Cannot import {}, please investigate'.format(project)) # If your extensions are in another directory, add it 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')) -# Get project related strings. Please do not change this line to use -# execfile because execfile is not available in Python 3 -_info_fname = os.path.join('..', 'nipy', 'info.py') -rel = {} -exec(open(_info_fname, 'rt').read(), {}, rel) - -# Import support for ipython console session syntax highlighting (lives -# in the sphinxext directory defined above) -import ipython_console_highlighting - # General configuration # --------------------- +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + # 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.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.mathjax', - 'sphinx.ext.autosummary', - 'sphinx.ext.inheritance_diagram', - 'numpy_ext.numpydoc', - 'matplotlib.sphinxext.plot_directive', - 'matplotlib.sphinxext.only_directives', # needed for above - ] +extensions = [ + 'texext.mathcode', + 'sphinx.ext.autodoc', + 'texext.math_dollar', + 'sphinx.ext.doctest', + 'sphinx.ext.mathjax', + 'sphinx.ext.autosummary', + 'sphinx.ext.inheritance_diagram', + 'matplotlib.sphinxext.plot_directive', + 'IPython.sphinxext.ipython_console_highlighting', +] # Autosummary on autosummary_generate=True @@ -56,17 +61,15 @@ # The master toctree document. master_doc = 'index' -# General substitutions. -project = 'nipy' - -#copyright = ':ref:`2005-2010, Neuroimaging in Python team. `' +# copyright = ':ref:`2005-2017, Neuroimaging in Python team. +# `' copyright = '2005-2017, Neuroimaging in Python team' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. -version = rel['__version__'] +version = project_module.__version__ # The full version, including alpha/beta/rc tags. release = version @@ -164,11 +167,29 @@ # Options for LaTeX output # ------------------------ -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# Additional stuff for the LaTeX preamble. +_latex_preamble = """ + \usepackage{amsmath} + \usepackage{amssymb} + % Uncomment these two if needed + %\usepackage{amsfonts} + %\usepackage{txfonts} +""" + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', + 'preamble': _latex_preamble, +} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class @@ -183,18 +204,12 @@ # the title page. #latex_logo = None -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -latex_use_parts = True - -# Additional stuff for the LaTeX preamble. -latex_preamble = """ - \usepackage{amsmath} - \usepackage{amssymb} - % Uncomment these two if needed - %\usepackage{amsfonts} - %\usepackage{txfonts} -""" +if sphinx.version_info[:2] < (1, 4): + # For "manual" documents, if this is true, then toplevel headings are parts, + # not chapters. + latex_use_parts = True +else: # Sphinx >= 1.4 + latex_toplevel_sectioning = 'part' # Documents to append as an appendix to all manuals. #latex_appendices = [] diff --git a/doc/devel/guidelines/howto_document.rst b/doc/devel/guidelines/howto_document.rst index 1fc3f4e93d..34edc04495 100644 --- a/doc/devel/guidelines/howto_document.rst +++ b/doc/devel/guidelines/howto_document.rst @@ -10,6 +10,10 @@ our documents and docstrings are in reST format, this allows us to have both human-readable docstrings when viewed in ipython_, and web and print quality documentation. +========================== +Getting build dependencies +========================== + Building the documentation -------------------------- diff --git a/doc/links_names.txt b/doc/links_names.txt index 851924d5f9..d3aeb5ba07 100644 --- a/doc/links_names.txt +++ b/doc/links_names.txt @@ -11,7 +11,7 @@ __not_case_sensitive__, so only one target definition is needed for nipy, NIPY, Nipy, etc... -.. nipy +.. Nipy .. _nipy: http://nipy.org/nipy .. _`NIPY developer resources`: http://nipy.sourceforge.net/devel .. _`NIPY data packages`: http://nipy.sourceforge.net/data-packages @@ -25,7 +25,7 @@ .. _`nipy launchpad`: https://launchpad.net/nipy .. _nipy on travis: https://travis-ci.org/nipy/nipy -.. other related projects +.. Related projects .. _nipy community: http://nipy.org .. _dipy: http://nipy.org/dipy .. _`dipy github`: http://github.com/Garyfallidis/dipy diff --git a/doc/sphinxext/ipython_console_highlighting.py b/doc/sphinxext/ipython_console_highlighting.py deleted file mode 100644 index d018e1759f..0000000000 --- a/doc/sphinxext/ipython_console_highlighting.py +++ /dev/null @@ -1,100 +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) - tblexer = PythonTracebackLexer(**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/sphinxext/numpy_ext/__init__.py b/doc/sphinxext/numpy_ext/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc/sphinxext/numpy_ext/docscrape.py b/doc/sphinxext/numpy_ext/docscrape.py deleted file mode 100644 index bbd3fcaccc..0000000000 --- a/doc/sphinxext/numpy_ext/docscrape.py +++ /dev/null @@ -1,505 +0,0 @@ -"""Extract reference documentation from the NumPy source tree. - -""" - -import inspect -import textwrap -import re -import pydoc -from StringIO import StringIO -from warnings import warn - -class Reader(object): - """A line-based string reader. - - """ - def __init__(self, data): - """ - Parameters - ---------- - data : str - String with lines separated by '\n'. - - """ - if isinstance(data,list): - self._str = data - else: - self._str = data.split('\n') # store string as list of lines - - self.reset() - - def __getitem__(self, n): - return self._str[n] - - def reset(self): - self._l = 0 # current line nr - - def read(self): - if not self.eof(): - out = self[self._l] - self._l += 1 - return out - else: - return '' - - def seek_next_non_empty_line(self): - for l in self[self._l:]: - if l.strip(): - break - else: - self._l += 1 - - def eof(self): - return self._l >= len(self._str) - - def read_to_condition(self, condition_func): - start = self._l - for line in self[start:]: - if condition_func(line): - return self[start:self._l] - self._l += 1 - if self.eof(): - return self[start:self._l+1] - return [] - - def read_to_next_empty_line(self): - self.seek_next_non_empty_line() - def is_empty(line): - return not line.strip() - return self.read_to_condition(is_empty) - - def read_to_next_unindented_line(self): - def is_unindented(line): - return (line.strip() and (len(line.lstrip()) == len(line))) - return self.read_to_condition(is_unindented) - - def peek(self,n=0): - if self._l + n < len(self._str): - return self[self._l + n] - else: - return '' - - def is_empty(self): - return not ''.join(self._str).strip() - - -class NumpyDocString(object): - def __init__(self, docstring, config={}): - docstring = textwrap.dedent(docstring).split('\n') - - self._doc = Reader(docstring) - self._parsed_data = { - 'Signature': '', - 'Summary': [''], - 'Extended Summary': [], - 'Parameters': [], - 'Returns': [], - 'Raises': [], - 'Warns': [], - 'Other Parameters': [], - 'Attributes': [], - 'Methods': [], - 'See Also': [], - 'Notes': [], - 'Warnings': [], - 'References': '', - 'Examples': '', - 'index': {} - } - - self._parse() - - def __getitem__(self,key): - return self._parsed_data[key] - - def __setitem__(self,key,val): - if not self._parsed_data.has_key(key): - warn("Unknown section %s" % key) - else: - self._parsed_data[key] = val - - def _is_at_section(self): - self._doc.seek_next_non_empty_line() - - if self._doc.eof(): - return False - - l1 = self._doc.peek().strip() # e.g. Parameters - - if l1.startswith('.. index::'): - return True - - l2 = self._doc.peek(1).strip() # ---------- or ========== - return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) - - def _strip(self,doc): - i = 0 - j = 0 - for i,line in enumerate(doc): - if line.strip(): break - - for j,line in enumerate(doc[::-1]): - if line.strip(): break - - return doc[i:len(doc)-j] - - def _read_to_next_section(self): - section = self._doc.read_to_next_empty_line() - - while not self._is_at_section() and not self._doc.eof(): - if not self._doc.peek(-1).strip(): # previous line was empty - section += [''] - - section += self._doc.read_to_next_empty_line() - - return section - - def _read_sections(self): - while not self._doc.eof(): - data = self._read_to_next_section() - name = data[0].strip() - - if name.startswith('..'): # index section - yield name, data[1:] - elif len(data) < 2: - yield StopIteration - else: - yield name, self._strip(data[2:]) - - def _parse_param_list(self,content): - r = Reader(content) - params = [] - while not r.eof(): - header = r.read().strip() - if ' : ' in header: - arg_name, arg_type = header.split(' : ')[:2] - else: - arg_name, arg_type = header, '' - - desc = r.read_to_next_unindented_line() - desc = dedent_lines(desc) - - params.append((arg_name,arg_type,desc)) - - return params - - - _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" - r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) - def _parse_see_also(self, content): - """ - func_name : Descriptive text - continued text - another_func_name : Descriptive text - func_name1, func_name2, :meth:`func_name`, func_name3 - - """ - items = [] - - def parse_item_name(text): - """Match ':role:`name`' or 'name'""" - m = self._name_rgx.match(text) - if m: - g = m.groups() - if g[1] is None: - return g[3], None - else: - return g[2], g[1] - raise ValueError("%s is not a item name" % text) - - def push_item(name, rest): - if not name: - return - name, role = parse_item_name(name) - items.append((name, list(rest), role)) - del rest[:] - - current_func = None - rest = [] - - for line in content: - if not line.strip(): continue - - m = self._name_rgx.match(line) - if m and line[m.end():].strip().startswith(':'): - push_item(current_func, rest) - current_func, line = line[:m.end()], line[m.end():] - rest = [line.split(':', 1)[1].strip()] - if not rest[0]: - rest = [] - elif not line.startswith(' '): - push_item(current_func, rest) - current_func = None - if ',' in line: - for func in line.split(','): - if func.strip(): - push_item(func, []) - elif line.strip(): - current_func = line - elif current_func is not None: - rest.append(line.strip()) - push_item(current_func, rest) - return items - - def _parse_index(self, section, content): - """ - .. index: default - :refguide: something, else, and more - - """ - def strip_each_in(lst): - return [s.strip() for s in lst] - - out = {} - section = section.split('::') - if len(section) > 1: - out['default'] = strip_each_in(section[1].split(','))[0] - for line in content: - line = line.split(':') - if len(line) > 2: - out[line[1]] = strip_each_in(line[2].split(',')) - return out - - def _parse_summary(self): - """Grab signature (if given) and summary""" - if self._is_at_section(): - return - - summary = self._doc.read_to_next_empty_line() - summary_str = " ".join([s.strip() for s in summary]).strip() - if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): - self['Signature'] = summary_str - if not self._is_at_section(): - self['Summary'] = self._doc.read_to_next_empty_line() - else: - self['Summary'] = summary - - if not self._is_at_section(): - self['Extended Summary'] = self._read_to_next_section() - - def _parse(self): - self._doc.reset() - self._parse_summary() - - for (section,content) in self._read_sections(): - if not section.startswith('..'): - section = ' '.join([s.capitalize() for s in section.split(' ')]) - if section in ('Parameters', 'Returns', 'Raises', 'Warns', - 'Other Parameters', 'Attributes', 'Methods'): - self[section] = self._parse_param_list(content) - elif section.startswith('.. index::'): - self['index'] = self._parse_index(section, content) - elif section == 'See Also': - self['See Also'] = self._parse_see_also(content) - else: - self[section] = content - - # string conversion routines - - def _str_header(self, name, symbol='-'): - return [name, len(name)*symbol] - - def _str_indent(self, doc, indent=4): - out = [] - for line in doc: - out += [' '*indent + line] - return out - - def _str_signature(self): - if self['Signature']: - return [self['Signature'].replace('*','\*')] + [''] - else: - return [''] - - def _str_summary(self): - if self['Summary']: - return self['Summary'] + [''] - else: - return [] - - def _str_extended_summary(self): - if self['Extended Summary']: - return self['Extended Summary'] + [''] - else: - return [] - - def _str_param_list(self, name): - out = [] - if self[name]: - out += self._str_header(name) - for param,param_type,desc in self[name]: - out += ['%s : %s' % (param, param_type)] - out += self._str_indent(desc) - out += [''] - return out - - def _str_section(self, name): - out = [] - if self[name]: - out += self._str_header(name) - out += self[name] - out += [''] - return out - - def _str_see_also(self, func_role): - if not self['See Also']: return [] - out = [] - out += self._str_header("See Also") - last_had_desc = True - for func, desc, role in self['See Also']: - if role: - link = ':%s:`%s`' % (role, func) - elif func_role: - link = ':%s:`%s`' % (func_role, func) - else: - link = "`%s`_" % func - if desc or last_had_desc: - out += [''] - out += [link] - else: - out[-1] += ", %s" % link - if desc: - out += self._str_indent([' '.join(desc)]) - last_had_desc = True - else: - last_had_desc = False - out += [''] - return out - - def _str_index(self): - idx = self['index'] - out = [] - out += ['.. index:: %s' % idx.get('default','')] - for section, references in idx.iteritems(): - if section == 'default': - continue - out += [' :%s: %s' % (section, ', '.join(references))] - return out - - def __str__(self, func_role=''): - out = [] - out += self._str_signature() - out += self._str_summary() - out += self._str_extended_summary() - for param_list in ('Parameters', 'Returns', 'Other Parameters', - 'Raises', 'Warns'): - out += self._str_param_list(param_list) - out += self._str_section('Warnings') - out += self._str_see_also(func_role) - for s in ('Notes','References','Examples'): - out += self._str_section(s) - for param_list in ('Attributes', 'Methods'): - out += self._str_param_list(param_list) - out += self._str_index() - return '\n'.join(out) - - -def indent(str,indent=4): - indent_str = ' '*indent - if str is None: - return indent_str - lines = str.split('\n') - return '\n'.join(indent_str + l for l in lines) - -def dedent_lines(lines): - """Deindent a list of lines maximally""" - return textwrap.dedent("\n".join(lines)).split("\n") - -def header(text, style='-'): - return text + '\n' + style*len(text) + '\n' - - -class FunctionDoc(NumpyDocString): - def __init__(self, func, role='func', doc=None, config={}): - self._f = func - self._role = role # e.g. "func" or "meth" - - if doc is None: - if func is None: - raise ValueError("No function or docstring given") - doc = inspect.getdoc(func) or '' - NumpyDocString.__init__(self, doc) - - if not self['Signature'] and func is not None: - func, func_name = self.get_func() - try: - # try to read signature - argspec = inspect.getargspec(func) - argspec = inspect.formatargspec(*argspec) - argspec = argspec.replace('*','\*') - signature = '%s%s' % (func_name, argspec) - except TypeError, e: - signature = '%s()' % func_name - self['Signature'] = signature - - def get_func(self): - func_name = getattr(self._f, '__name__', self.__class__.__name__) - if inspect.isclass(self._f): - func = getattr(self._f, '__call__', self._f.__init__) - else: - func = self._f - return func, func_name - - def __str__(self): - out = '' - - func, func_name = self.get_func() - signature = self['Signature'].replace('*', '\*') - - roles = {'func': 'function', - 'meth': 'method'} - - if self._role: - if not roles.has_key(self._role): - print "Warning: invalid role %s" % self._role - out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''), - func_name) - - out += super(FunctionDoc, self).__str__(func_role=self._role) - return out - - -class ClassDoc(NumpyDocString): - - extra_public_methods = ['__call__'] - - def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, - config={}): - if not inspect.isclass(cls) and cls is not None: - raise ValueError("Expected a class or None, but got %r" % cls) - self._cls = cls - - if modulename and not modulename.endswith('.'): - modulename += '.' - self._mod = modulename - - if doc is None: - if cls is None: - raise ValueError("No class or documentation string given") - doc = pydoc.getdoc(cls) - - NumpyDocString.__init__(self, doc) - - if config.get('show_class_members', True): - if not self['Methods']: - self['Methods'] = [(name, '', '') - for name in sorted(self.methods)] - if not self['Attributes']: - self['Attributes'] = [(name, '', '') - for name in sorted(self.properties)] - - @property - def methods(self): - if self._cls is None: - return [] - return [name for name,func in inspect.getmembers(self._cls) - if ((not name.startswith('_') - or name in self.extra_public_methods) - and callable(func))] - - @property - def properties(self): - if self._cls is None: - return [] - return [name for name,func in inspect.getmembers(self._cls) - if not name.startswith('_') and func is None] diff --git a/doc/sphinxext/numpy_ext/docscrape_sphinx.py b/doc/sphinxext/numpy_ext/docscrape_sphinx.py deleted file mode 100644 index e44e770ef8..0000000000 --- a/doc/sphinxext/numpy_ext/docscrape_sphinx.py +++ /dev/null @@ -1,227 +0,0 @@ -import re, inspect, textwrap, pydoc -import sphinx -from docscrape import NumpyDocString, FunctionDoc, ClassDoc - -class SphinxDocString(NumpyDocString): - def __init__(self, docstring, config={}): - self.use_plots = config.get('use_plots', False) - NumpyDocString.__init__(self, docstring, config=config) - - # string conversion routines - def _str_header(self, name, symbol='`'): - return ['.. rubric:: ' + name, ''] - - def _str_field_list(self, name): - return [':' + name + ':'] - - def _str_indent(self, doc, indent=4): - out = [] - for line in doc: - out += [' '*indent + line] - return out - - def _str_signature(self): - return [''] - if self['Signature']: - return ['``%s``' % self['Signature']] + [''] - else: - return [''] - - def _str_summary(self): - return self['Summary'] + [''] - - def _str_extended_summary(self): - return self['Extended Summary'] + [''] - - def _str_param_list(self, name): - out = [] - if self[name]: - out += self._str_field_list(name) - out += [''] - for param,param_type,desc in self[name]: - out += self._str_indent(['**%s** : %s' % (param.strip(), - param_type)]) - out += [''] - out += self._str_indent(desc,8) - out += [''] - return out - - @property - def _obj(self): - if hasattr(self, '_cls'): - return self._cls - elif hasattr(self, '_f'): - return self._f - return None - - def _str_member_list(self, name): - """ - Generate a member listing, autosummary:: table where possible, - and a table where not. - - """ - out = [] - if self[name]: - out += ['.. rubric:: %s' % name, ''] - prefix = getattr(self, '_name', '') - - if prefix: - prefix = '~%s.' % prefix - - autosum = [] - others = [] - for param, param_type, desc in self[name]: - param = param.strip() - if not self._obj or hasattr(self._obj, param): - autosum += [" %s%s" % (prefix, param)] - else: - others.append((param, param_type, desc)) - - if autosum: - out += ['.. autosummary::', ' :toctree:', ''] - out += autosum - - if others: - maxlen_0 = max([len(x[0]) for x in others]) - maxlen_1 = max([len(x[1]) for x in others]) - hdr = "="*maxlen_0 + " " + "="*maxlen_1 + " " + "="*10 - fmt = '%%%ds %%%ds ' % (maxlen_0, maxlen_1) - n_indent = maxlen_0 + maxlen_1 + 4 - out += [hdr] - for param, param_type, desc in others: - out += [fmt % (param.strip(), param_type)] - out += self._str_indent(desc, n_indent) - out += [hdr] - out += [''] - return out - - def _str_section(self, name): - out = [] - if self[name]: - out += self._str_header(name) - out += [''] - content = textwrap.dedent("\n".join(self[name])).split("\n") - out += content - out += [''] - return out - - def _str_see_also(self, func_role): - out = [] - if self['See Also']: - see_also = super(SphinxDocString, self)._str_see_also(func_role) - out = ['.. seealso::', ''] - out += self._str_indent(see_also[2:]) - return out - - def _str_warnings(self): - out = [] - if self['Warnings']: - out = ['.. warning::', ''] - out += self._str_indent(self['Warnings']) - return out - - def _str_index(self): - idx = self['index'] - out = [] - if len(idx) == 0: - return out - - out += ['.. index:: %s' % idx.get('default','')] - for section, references in idx.iteritems(): - if section == 'default': - continue - elif section == 'refguide': - out += [' single: %s' % (', '.join(references))] - else: - out += [' %s: %s' % (section, ','.join(references))] - return out - - def _str_references(self): - out = [] - if self['References']: - out += self._str_header('References') - if isinstance(self['References'], str): - self['References'] = [self['References']] - out.extend(self['References']) - out += [''] - # Latex collects all references to a separate bibliography, - # so we need to insert links to it - if sphinx.__version__ >= "0.6": - out += ['.. only:: latex',''] - else: - out += ['.. latexonly::',''] - items = [] - for line in self['References']: - m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I) - if m: - items.append(m.group(1)) - out += [' ' + ", ".join(["[%s]_" % item for item in items]), ''] - return out - - def _str_examples(self): - examples_str = "\n".join(self['Examples']) - - if (self.use_plots and 'import matplotlib' in examples_str - and 'plot::' not in examples_str): - out = [] - out += self._str_header('Examples') - out += ['.. plot::', ''] - out += self._str_indent(self['Examples']) - out += [''] - return out - else: - return self._str_section('Examples') - - def __str__(self, indent=0, func_role="obj"): - out = [] - out += self._str_signature() - out += self._str_index() + [''] - out += self._str_summary() - out += self._str_extended_summary() - for param_list in ('Parameters', 'Returns', 'Other Parameters', - 'Raises', 'Warns'): - out += self._str_param_list(param_list) - out += self._str_warnings() - out += self._str_see_also(func_role) - out += self._str_section('Notes') - out += self._str_references() - out += self._str_examples() - for param_list in ('Attributes', 'Methods'): - out += self._str_member_list(param_list) - out = self._str_indent(out,indent) - return '\n'.join(out) - -class SphinxFunctionDoc(SphinxDocString, FunctionDoc): - def __init__(self, obj, doc=None, config={}): - self.use_plots = config.get('use_plots', False) - FunctionDoc.__init__(self, obj, doc=doc, config=config) - -class SphinxClassDoc(SphinxDocString, ClassDoc): - def __init__(self, obj, doc=None, func_doc=None, config={}): - self.use_plots = config.get('use_plots', False) - ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config) - -class SphinxObjDoc(SphinxDocString): - def __init__(self, obj, doc=None, config={}): - self._f = obj - SphinxDocString.__init__(self, doc, config=config) - -def get_doc_object(obj, what=None, doc=None, config={}): - if what is None: - if inspect.isclass(obj): - what = 'class' - elif inspect.ismodule(obj): - what = 'module' - elif callable(obj): - what = 'function' - else: - what = 'object' - if what == 'class': - return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, - config=config) - elif what in ('function', 'method'): - return SphinxFunctionDoc(obj, doc=doc, config=config) - else: - if doc is None: - doc = pydoc.getdoc(obj) - return SphinxObjDoc(obj, doc, config=config) diff --git a/doc/sphinxext/numpy_ext/numpydoc.py b/doc/sphinxext/numpy_ext/numpydoc.py deleted file mode 100644 index 41950dda45..0000000000 --- a/doc/sphinxext/numpy_ext/numpydoc.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -======== -numpydoc -======== - -Sphinx extension that handles docstrings in the Numpy standard format. [1] - -It will: - -- Convert Parameters etc. sections to field lists. -- Convert See Also section to a See also entry. -- Renumber references. -- Extract the signature from the docstring, if it can't be determined otherwise. - -.. [1] https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt - -""" - -import sphinx - -if sphinx.__version__ < '1.0.1': - raise RuntimeError("Sphinx 1.0.1 or newer is required") - -import os, re, pydoc -from docscrape_sphinx import get_doc_object, SphinxDocString -from sphinx.util.compat import Directive -import inspect - -def mangle_docstrings(app, what, name, obj, options, lines, - reference_offset=[0]): - - cfg = dict(use_plots=app.config.numpydoc_use_plots, - show_class_members=app.config.numpydoc_show_class_members) - - if what == 'module': - # Strip top title - title_re = re.compile(ur'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', - re.I|re.S) - lines[:] = title_re.sub(u'', u"\n".join(lines)).split(u"\n") - else: - doc = get_doc_object(obj, what, u"\n".join(lines), config=cfg) - lines[:] = unicode(doc).split(u"\n") - - if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ - obj.__name__: - if hasattr(obj, '__module__'): - v = dict(full_name=u"%s.%s" % (obj.__module__, obj.__name__)) - else: - v = dict(full_name=obj.__name__) - lines += [u'', u'.. only:: html', ''] - lines += [u' %s' % x for x in - (app.config.numpydoc_edit_link % v).split("\n")] - - # replace reference numbers so that there are no duplicates - references = [] - for line in lines: - line = line.strip() - m = re.match(ur'^.. \[([a-z0-9_.-])\]', line, re.I) - if m: - references.append(m.group(1)) - - # start renaming from the longest string, to avoid overwriting parts - references.sort(key=lambda x: -len(x)) - if references: - for i, line in enumerate(lines): - for r in references: - if re.match(ur'^\d+$', r): - new_r = u"R%d" % (reference_offset[0] + int(r)) - else: - new_r = u"%s%d" % (r, reference_offset[0]) - lines[i] = lines[i].replace(u'[%s]_' % r, - u'[%s]_' % new_r) - lines[i] = lines[i].replace(u'.. [%s]' % r, - u'.. [%s]' % new_r) - - reference_offset[0] += len(references) - -def mangle_signature(app, what, name, obj, options, sig, retann): - # Do not try to inspect classes that don't define `__init__` - if (inspect.isclass(obj) and - (not hasattr(obj, '__init__') or - 'initializes x; see ' in pydoc.getdoc(obj.__init__))): - return '', '' - - if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return - if not hasattr(obj, '__doc__'): return - - doc = SphinxDocString(pydoc.getdoc(obj)) - if doc['Signature']: - sig = re.sub(u"^[^(]*", u"", doc['Signature']) - return sig, u'' - -def setup(app, get_doc_object_=get_doc_object): - global get_doc_object - get_doc_object = get_doc_object_ - - app.connect('autodoc-process-docstring', mangle_docstrings) - app.connect('autodoc-process-signature', mangle_signature) - app.add_config_value('numpydoc_edit_link', None, False) - app.add_config_value('numpydoc_use_plots', None, False) - app.add_config_value('numpydoc_show_class_members', True, True) - - # Extra mangling domains - app.add_domain(NumpyPythonDomain) - app.add_domain(NumpyCDomain) - -#------------------------------------------------------------------------------ -# Docstring-mangling domains -#------------------------------------------------------------------------------ - -from docutils.statemachine import ViewList -from sphinx.domains.c import CDomain -from sphinx.domains.python import PythonDomain - -class ManglingDomainBase(object): - directive_mangling_map = {} - - def __init__(self, *a, **kw): - super(ManglingDomainBase, self).__init__(*a, **kw) - self.wrap_mangling_directives() - - def wrap_mangling_directives(self): - for name, objtype in self.directive_mangling_map.items(): - self.directives[name] = wrap_mangling_directive( - self.directives[name], objtype) - -class NumpyPythonDomain(ManglingDomainBase, PythonDomain): - name = 'np' - directive_mangling_map = { - 'function': 'function', - 'class': 'class', - 'exception': 'class', - 'method': 'function', - 'classmethod': 'function', - 'staticmethod': 'function', - 'attribute': 'attribute', - } - -class NumpyCDomain(ManglingDomainBase, CDomain): - name = 'np-c' - directive_mangling_map = { - 'function': 'function', - 'member': 'attribute', - 'macro': 'function', - 'type': 'class', - 'var': 'object', - } - -def wrap_mangling_directive(base_directive, objtype): - class directive(base_directive): - def run(self): - env = self.state.document.settings.env - - name = None - if self.arguments: - m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0]) - name = m.group(2).strip() - - if not name: - name = self.arguments[0] - - lines = list(self.content) - mangle_docstrings(env.app, objtype, name, None, None, lines) - self.content = ViewList(lines, self.content.parent) - - return base_directive.run(self) - - return directive - From 732036f87a72933f1cf00edcf646d72611035c98 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 14:58:10 -0800 Subject: [PATCH 02/18] DOC: fix plot using Heaviside function Heaviside(0.0) doesn't resolve to a number (it's undefined); substitute it for 1, to allow matplotlib to use the result for the plots. --- doc/users/plots/neuronal_event.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/users/plots/neuronal_event.py b/doc/users/plots/neuronal_event.py index 4ba3a8742a..7e18521766 100644 --- a/doc/users/plots/neuronal_event.py +++ b/doc/users/plots/neuronal_event.py @@ -1,17 +1,16 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """ -This figure is meant to represent the neuronal event-related model - -and a coefficient of +1 for Faces, -2 for Objects. - +This figure is meant to represent the neuronal event-related model and a +coefficient of +1 for Faces, -2 for Objects. """ -import pylab import numpy as np +import matplotlib.pyplot as plt from sympy import Symbol, Heaviside, lambdify + ta = [0,4,8,12,16]; tb = [2,6,10,14,18] ba = Symbol('ba'); bb = Symbol('bb'); t = Symbol('t') fa = sum([Heaviside(t-_t) for _t in ta]) * ba @@ -24,11 +23,15 @@ Nn = lambdify(t, Nn) tt = np.linspace(-1,21,1201) -pylab.step(tt, [Nn(_t) for _t in tt]) +neuronal = [Nn(_t) for _t in tt] +# Deal with undefined Heaviside at 0 +neuronal = [n.subs(Heaviside(0.0), 1) for n in neuronal] + +plt.step(tt, neuronal) -a = pylab.gca() +a = plt.gca() a.set_ylim([-5.5,1.5]) a.set_ylabel('Neuronal (cumulative)') a.set_xlabel('Time') -pylab.show() +plt.show() From fc1a97ed21b660ac2a94b969c68dc7037405d1c0 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 14:59:50 -0800 Subject: [PATCH 03/18] DOC: fix docstring markup for slice timing Missing terminators for math_dollar and default markup. --- nipy/algorithms/slicetiming/timefuncs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nipy/algorithms/slicetiming/timefuncs.py b/nipy/algorithms/slicetiming/timefuncs.py index 035c2c47db..adb5f43b24 100644 --- a/nipy/algorithms/slicetiming/timefuncs.py +++ b/nipy/algorithms/slicetiming/timefuncs.py @@ -2,8 +2,8 @@ Slice timing routines in nipy need a vector of slice times. -Slice times are vectors $t_i i = 0 ... N$ of times, one for each slice, where -$t_i% gives the time at which slice number $i$ was acquired, relative to the +Slice times are vectors $t_i$ with $i = 0 ... N$ of times, one for each slice, where +$t_i$ gives the time at which slice number $i$ was acquired, relative to the beginning of the volume acquisition. We like these vectors because they are unambiguous; the indices $i$ refer to @@ -195,8 +195,8 @@ def st_odd0_even1(n_slices, TR): seem to have this behavior as default - see: https://mri.radiology.uiowa.edu/fmri_images.html - This means we use the :func:`st_02413` algorithm if `n_slices` is odd, - and the :func:`st_13024` algorithm if `n_slices is even. + This means we use the :func:`st_02413` algorithm if `n_slices` is odd, and + the :func:`st_13024` algorithm if `n_slices` is even. For example, for 4 slices and a TR of 1: @@ -243,8 +243,8 @@ def st_03142(n_slices, TR): def st_41302(n_slices, TR): """Descend alternate, where alternation is by half the volume - Collect slice (n_slices - 1) then slice ``floor(nslices / 2.) - 1`` then slice - (n_slices - 2) then slice ``floor(nslices / 2.) - 2`` etc. + Collect slice ``(n_slices - 1)`` then slice ``floor(nslices / 2.) - 1`` + then slice ``(n_slices - 2)`` then slice ``floor(nslices / 2.) - 2`` etc. For example, for 5 slices and a TR of 1: From 24027d5dffe6adf57d12e985f759aa7dc3b52799 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 15:02:33 -0800 Subject: [PATCH 04/18] MAINT: add doc build to travis test matrix. Build the docs to check for errors. --- .travis.yml | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93c1e9f618..b24a7ea132 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,8 @@ env: - PRE_WHEELS="https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" - EXTRA_PIP_FLAGS="--find-links=$EXTRA_WHEELS" - PRE_PIP_FLAGS="--pre $EXTRA_PIP_FLAGS --find-links $PRE_WHEELS" + - INSTALL_TYPE="pip" + python: - 3.4 - 3.5 @@ -54,11 +56,17 @@ matrix: env: - INSTALL_TYPE=requirements - DEPENDS= - - python: 3.6 # test 3.5 against pre-release builds of everything - python: 3.5 env: - EXTRA_PIP_FLAGS="$PRE_PIP_FLAGS" + # test python setup.py install + - python: 3.5 + env: + - INSTALL_TYPE=setup + - python: 3.5 + env: + - DOC_BUILD=1 before_install: - source tools/travis_tools.sh @@ -77,11 +85,14 @@ before_install: pip install coverage; pip install coveralls codecov; fi + # command to install dependencies # e.g. pip install -r requirements.txt # --use-mirrors install: - | - if [ "$INSTALL_TYPE" == "setup" ]; then + if [ "$INSTALL_TYPE" == "pip" ]; then + pip install . + elif [ "$INSTALL_TYPE" == "setup" ]; then python setup.py install elif [ "$INSTALL_TYPE" == "sdist" ]; then python setup_egg.py egg_info # check egg_info while we're here @@ -96,15 +107,23 @@ install: pip install -r requirements.txt python setup.py install fi -# command to run tests, e.g. python setup.py test + +# command to run tests script: - # Change into an innocuous directory and find tests from installation - - mkdir for_testing - - cd for_testing - - if [ "${COVERAGE}" == "1" ]; then - cp ../.coveragerc .; - COVER_ARGS="--with-coverage --cover-package nipy"; + - | + if [ "$DOC_BUILD" ]; then # doc build + pip install -r doc-requirements.txt + make html-stamp pdf-stamp + else + # Change into an innocuous directory and find tests from installation + mkdir for_testing + cd for_testing + if [ "${COVERAGE}" == "1" ]; then + cp ../.coveragerc . + COVER_ARGS="--with-coverage --cover-package nipy" + fi + $PYTHON ../tools/nipnost --verbosity=3 $COVER_ARGS nipy fi - - $PYTHON ../tools/nipnost --verbosity=3 $COVER_ARGS nipy + after_success: - if [ "${COVERAGE}" == "1" ]; then coveralls; codecov; fi From bbd6e8e2cf531904713d9edd89e613968f0e3034 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 15:10:36 -0800 Subject: [PATCH 05/18] MAINT: add graphviz to apt requirements for docs Need to install graphviz for docs. --- .travis.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index b24a7ea132..1417341882 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,19 +5,21 @@ # - There can't be any leading "-"s - All newlines will be removed, so use # ";"s sudo: false # To use travis container infrastructure + language: python + cache: directories: - $HOME/.cache/pip + env: - global: - - DEPENDS="numpy scipy sympy matplotlib nibabel" - - INSTALL_TYPE="setup" - - EXTRA_WHEELS="https://5cf40426d9f06eb7461d-6fe47d9331aba7cd62fc36c7196769e4.ssl.cf2.rackcdn.com" - - PRE_WHEELS="https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" - - EXTRA_PIP_FLAGS="--find-links=$EXTRA_WHEELS" - - PRE_PIP_FLAGS="--pre $EXTRA_PIP_FLAGS --find-links $PRE_WHEELS" - - INSTALL_TYPE="pip" + global: + - DEPENDS="numpy scipy sympy matplotlib nibabel" + - EXTRA_WHEELS="https://5cf40426d9f06eb7461d-6fe47d9331aba7cd62fc36c7196769e4.ssl.cf2.rackcdn.com" + - PRE_WHEELS="https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" + - EXTRA_PIP_FLAGS="--find-links=$EXTRA_WHEELS" + - PRE_PIP_FLAGS="--pre $EXTRA_PIP_FLAGS --find-links $PRE_WHEELS" + - INSTALL_TYPE="pip" python: - 3.4 @@ -67,6 +69,10 @@ matrix: - python: 3.5 env: - DOC_BUILD=1 + addons: + apt: + packages: + - graphviz before_install: - source tools/travis_tools.sh From 271f928d9477de7a3676b6909e8449f1683aa928 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 15:26:30 -0800 Subject: [PATCH 06/18] MAINT: update gitwash Update gitwash from upstream. --- .../guidelines/gitwash/branch_dropdown.png | Bin 0 -> 16311 bytes doc/devel/guidelines/gitwash/branch_list.png | Bin 13361 -> 0 bytes .../gitwash/branch_list_compare.png | Bin 10679 -> 0 bytes .../guidelines/gitwash/configure_git.rst | 71 ++- .../gitwash/development_workflow.rst | 420 +++++++++++++----- doc/devel/guidelines/gitwash/dot2_dot3.rst | 28 -- .../guidelines/gitwash/following_latest.rst | 6 +- doc/devel/guidelines/gitwash/forking_hell.rst | 37 +- .../guidelines/gitwash/git_development.rst | 2 +- doc/devel/guidelines/gitwash/git_install.rst | 14 +- doc/devel/guidelines/gitwash/git_intro.rst | 14 +- doc/devel/guidelines/gitwash/git_links.inc | 109 ++--- .../guidelines/gitwash/git_resources.rst | 29 +- doc/devel/guidelines/gitwash/index.rst | 2 +- .../guidelines/gitwash/known_projects.inc | 41 ++ doc/devel/guidelines/gitwash/links.inc | 4 + .../gitwash/maintainer_workflow.rst | 98 ++++ doc/devel/guidelines/gitwash/patching.rst | 71 +-- doc/devel/guidelines/gitwash/set_up_fork.rst | 19 +- doc/devel/guidelines/gitwash/this_project.inc | 3 + tools/gitwash_dumper.py | 134 ++++-- 21 files changed, 771 insertions(+), 331 deletions(-) create mode 100644 doc/devel/guidelines/gitwash/branch_dropdown.png delete mode 100644 doc/devel/guidelines/gitwash/branch_list.png delete mode 100644 doc/devel/guidelines/gitwash/branch_list_compare.png delete mode 100644 doc/devel/guidelines/gitwash/dot2_dot3.rst create mode 100644 doc/devel/guidelines/gitwash/known_projects.inc create mode 100644 doc/devel/guidelines/gitwash/links.inc create mode 100644 doc/devel/guidelines/gitwash/maintainer_workflow.rst create mode 100644 doc/devel/guidelines/gitwash/this_project.inc diff --git a/doc/devel/guidelines/gitwash/branch_dropdown.png b/doc/devel/guidelines/gitwash/branch_dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..1bb7a577732c91ee6aa31de8f81045b5622126b2 GIT binary patch literal 16311 zcmY+LWmr|+^Y;M}4&5o;ol19icQ+#4hpt0+cL}IScO5{GPDQ0VrMv6dc>n${o)>U& z9rj*(owaAyn)%FkqSaJn(U6IeVPIg;4b9FGh%= z&Ue3i?|Sd5oT=s>!a}#RuHeguyJ_ zr%&ZC_KtNTNV!#Woc5Pkm>gf)rK^TW-b`%T3@M_Wlg+>oW~XY>r;f3s3X9@Al`Hh5 z&iso&m4;=JUdmn+qAF3D*7$%WvNG;+kSA(|^=* zonu2g&8mLXIa3IAN#Yx^G7ok)kW-yea=K}cd>ueHgHyBat)sr?lZa!x{CnwGdLFKZ z1DzC=7&Qk?P7PBU-aM;TO)Nr)nDcGDn<#Y!!kbh-E>D^d9Uc}^b>eow9oexA)W@DyeowcL4v~TY?EwF z)9zK#vAjCDnYND~<+6#+3k*+uE&_g)@4ra)o=veKLbvho$N~j`5xf)gevf>G~N|bKw(L|B7MpJ zGG_hC$Qb9>h-4{DZQmWS&=^x&kkrtap`Ec-#Ba#fw%GNW&HhE|L{iKzaozV>!8-M> z<`k>;u~y^uwo+p*P=$CpB=gSs8}w278#p!^Z~!D1Iem8+7!;hBA6S^2JVF>43K)4Q zaV;?HVLoCWuJqL)x!o{7y?m$@Cg2WIuT{tBx8Vy4klMO^_wxA{vo|OaqyT1mOf31jpFqiv?DerZ(X!qRg zyuzmaRVzA0(wP}9CYY)SWQ7eUZRzNcOeGBwrIi-LQiI2ZmHw6}HZ}r@12b^)aXkrK zv;fyJz{QAB%2Kdd!{P7c%LVg#TSe9Bh|(orApac8f|XY zj2c=kPV#RD3<~~@H!MLxXT8oOo6J|XUQN3m#T!ahu2vk1%Upe2M+2wHD*3WzArDe7 zYhyXAK?kN`5qz>#{jD5#jzeCh_}_|BAi!G`1b?oyV!$3*sUk-7My8I8id%{s-i9mRugkk*sSI5Nrm!>2hFJCHS

)6PRZ_jTr)9djL4I)$bAGt{;Hy1$y<{qLd2H6zG^8pzYt9uj@NRV~-9$WY1_ zkp3|9H6I(w?{d_2?^N-Z&>L#D_qSywFmRtp!e*M8p z_;xcj)a4v|bXCyfFU255k553v(f=K?}WObW$#Sw}~RCho5&UN~=1Z;QTtfOR$26d4l zI?gxN&HnO2J@a1nk*)5$n*Z{M*}^41&AZx}F&yGW5mpiO{B)Hc7t7Wzm&4=8)L=WA zF=OJV?9_3{!+*0HR_3_SjA)=7vaL#EvJ!Y-g-R}BlpuJ-cYkt*$!655w(Nhd>~l3| zyY(s8DP^imk>I`G9Oz<7kv*MGIcGMI^l8^+ce%4eo24B0Y-8t+fNkbmV*hD)ELJh1gWM0m)gwV+Pw~mhjQ#?%M{WjcKHUs zyUs>BW#Lfa?&P_v{KZ~TCl>JdLb}f|qxJa}5jl9sx$E?k5qK?JquD{fBXvlTy^{t^ zAxj?6DBN|u?01!`kuT`oJV1;UQ`2>)@bOY|Y^3k#ue}7C5KFlV13DQxWWD?O@iQjL4B+&!QCA zD{`7F>b}*!Klj^ZC-lGD{p_|LO+RA#e5V^bJl|x0C}#OBo>Xu;kSrK6HS#FmdnNhy zeA^J^&1ju&jmb3HGSsI5l}u>%>FI2got5oyzNz8W1z^qf&c`zqx|0^c7BRfS1=!O# z?3L@{KTS6V6I<4(YfDVKpSJDwIf6p8stmMMgfF%17F+B31w^0oU3!tYd@tVb&X$+F z{V>bm@JHweL!N2SA0thI9z6vMCIyd$?(gr9 zyEFS^2^0%O0xCbo{1)|hK|cl=4aC2(9TbrGquBlY)bT?%PWz5W6zLeICiGM;tJZUW zx_L>6{^GGnJZv*Xk*#h;Ae6m;85h049V%ta@aD4^5&Kf&KHK*QbQC{d8p=T)=dterRacNk6<_VTf?RoyYx;1(a=acVwgQZjmmp zZiUTz;rA~&K(l9C^`mU5fYYx`h<-!Nz?dPF+FV*=ssI(o*`=Z9`)IxhbiOj z$v`mw-j@=q^NjH;*zaZ(?3{m7RYmx56?zrtG8&ZC3RVT|rf6;t8YUeK=9O-OdBUF8 zBFUW=u4gS1_19ED=0875PxlrdKucx8VtRF8rYeIbY&DZ zRP6MKI}RZ=RA=Z@#5<_b++x_xxSm+@#lf7Wioh;SERqcJ^M_f4bz`|tkw)V$ zuxwYN7|F5I*3*toC3?)pYTe#`0-<{rnqy+x)+sI$p(2kVDv3?vmtme368dF&fukq8 zPEZg!LLXFmdO%Kon($brm}%flM)t*aeDB?rpa$eNi+Ox5$1=!WMNob)i;mTtV<@rw zo;1}q^jpcQ2W4f#es#yh2Td}er>FasX9N4VS^|9DV$y9Sx!AJNU@^g8!>Xc}jUIx{ zhDPJoX88eEb2{=?HKWa*7N>mXegj0dda98ESm=5oz^+?wg0AD+;)|F%75;vM`o?jrnc9q67dn-^_q?B^zLnIf zH8QZn!}P)c4yePdLm|7qH&$3Qwif9-n!#+pu?$HA{{Vx|ayfL=8o{1me-nwJA~_K0 z6gnz??d{2T4979D#OVbdArlGI#@A~xmkFHp4!c}}Ug|&0hFT@Er1!}8Le$O(;Xs;a z5)eK$8VF_$^h}}S2W-Jvs#?rRVqS>u0FtBd*%0+>SWpP843#DJI!Wa^?cTaR;;Sb$ zg*2b@ab81e#JsROP^EfTqR?5CHd&nT$hX-LS=r>9cpd}}f9iPK=bI?+18VPCad=B~ z3zh7hpN#0${NczLgd~k&UBNYy==^;N7*n@)rlXJER1cqCrHXrepxVu`udbua#G;k~ z6~l<_teMwitsQ>r%lE$bFGf|PYYlMvHZXz{yy7`yj1TS=#x#egd!*c^!E}ItYb_{n zG0oqBW^yB=k_=f;;IYFq4a-7KT(a~c{GT(}^gjrruJ5CY?a3x_gOGywp&&_45K;vP zCeZ^M+B92Owwj5_FHLqI<3JAUDct?&Bw7rGbNultMH6VBiFFD?rGFCS5y>>}b+0`8 zn?%C3L-5uR;iiaX94C@9tTt@yv*NpTXa~6b<~Wk>6)~=O19b91O9+w<5LLv znI=6xKjeBy&m#u%H0CT(goRM{*!bdycotV?Texo=wun>7Z!$&2qT311O^{*^%mU1 zzWK#dj2#8L7IlG%K|~Q*A~)j`MRbE%B&nk2o`CI|Iy<3IN(K%m6npTwt>dM7P{cJk zLBqzH7vk_*mgdgU_76RldCov~k z&BwDlScl6^3nJtufg~%9J;~2q*X`G2Gq-33oqpHNeC>^CgZ{nxLBgZNx#9tR=lgdf z!YFa>vdaSeJCmc%jhtfqKL;9^c{3VKy1)r*o--518SH7Tsk#ZyQ!b>Evp$ZE1WGY@ zOSYhPwDld@_zZn)z-|JiSFyKH$!U%e;Juk*vR%SKd@&;QT zO0dy<=f}6^J40q=VHxVb;kWHV7~KMPWpp12y)>3sh)X9Hs4wa~6;q5soz7Z6WD`Op z)apVaR7`>Sd-5^GMU4ey!wVsdpX=}~ah=5&Z z(bpnPwoUvoOwDC;Fq4C3O$8r9q4tP#&SqMl$dEFg%kx}LeC@z_^$jFJL=IBN-{`|y zd$V1lV3Xdchj58Ci8>iEv){AsjT^u6-V%mCgeA&aXpsBLM~9Qn=nG09(lPfobeUlz zPK_XDDjsiuB=zd3#lGTNkAZ%*?yR;ECMO+KnX>)}5j<-B-kdM5N|#aBmn~%I8y;)X2MRv#k(m4oo=lV#;q=JSY7c(kym0 z;W%0t`8iRddwh=eu zO7^ZBi#L;l#FD$USLBq@KL)7VRzp#)+}F-yy%7Y@)%3Io5%u&igfEit8bR=BB*!?M z>IO;h5#Vk@9?Z2@sE8Tq^$=q7Z?B$Ox*x}hDIk))O=#w_ev7Y^zC>U$%BEo>Z;RbW zx{$;imn4eRPr`bZ!Euu57fFDL$G{o2^PS7nA!oZ1Qw*=uq}9l=+P+b2>aY5gVdV1I zGcjk>)H+CQ@XixbERw!0_#U6AHz;!M06t)#0hPI((h`i0vUif4IWQL1ZA!QkIyNRJ zeVt*bdkucV<9tQ87e$F=5KA52HrxF<@0Eb`s$iz5f%toE38K0bLyI9ZSX9yD#%R5_ z7m2B135+<=-9Ui?V#Uf*yAWCCf_S5^! zfHWVACt-MdCK9; zC4!zsU~k>Ll(mv!p_MX-KD7t_7+nMkkIn!;fYqlLYUK0LahU&mSI`_r zYmUPHa`s!H3O)>N+y(E9M8n0wb9i2YM^&czZ(0N6v=GT~A5!q4UPtqcv@w~r1tBet zal%1@o!0XYbTvT^!L)0n19XoBRf(;t0(%x~s0vwfVf%>`fot>zx8fUfq9UPFz6%qx z{tu^vRdpy42@Dv{u~+ADQ*1NO;PD|&lbfpqoXiac=G8KD{cC;HyH$v9L77}If;JF@XBGyGl7BJYdBeyMfw0LL1Gl*iAiV?fjJlIuKl>Dz8L#z1lk?h7&nPNIMUxX&j$&RyOrO;=M z=e`klq1`jTMd^L&I}FML6OD@&THY3RyA#nl^_vPOCGc= z?;9i%_&LsY-2%Lpbzwpc!B)*0&26d8+sKC32ZfjA&!bCIqUe6kq&KUA)9nXHCVT14RzitWLu2nSqx7?B za*fdzXx^B!n!bD*vPqfbg>y0-CZPxrpBG9v#0>XU=`1F1`yX}`!vawjnO9@t`u+os z!Bk1Q@ar0MP>#PQIivRB$!EJAILS#r_y5tPQV}>;##AewNK(f1e`Be^PvL~5snZ#b zo7^e<4>;{czaUQz+~XXY{rK|}@?|AewRHP`Q|Cg1H6Bm56Rtkg{)aq~Kz#?x;%g5oc~h0-nE$DMxDk(VT6DKHL4b$YfsEbOyffzu~?cg_HR>7QHXrr%;1O z#(-liQ5u^mK*snwpE8U2LnIN6UbzfZ3j6Z8R(fgAdwGu7`5hz#tyoQXWXzDpz)7pQ zdTaT6GlCV}xi)uOSTE(9uc^Z`1Ilj>KZksuVM)o)&#x~;S#GpTJ-DUjuj$fos=q3Y zg?LuZ+^QMKM)AcQ4f*&A>`!=d{~-xXR7@Q6k&-mOSXLywbE~f@#6H!&@xJ6#^*mel1SD=M8Run6ht2*|$9s9aZz^o2++JXzNGgMgej_ z@?INhxZC9?mCxU`$TI)iGkoSWk6)jZx26=?k~L@*=}OUqD1VLJ+*~a?8~$qT020_U>%IrtGQNJVYT1Oeb9O$_KCc5C z4F*LedK3Z{;BjB&ey;ah*-!A?u8*<e6*|x~eQ%-7EQQ+hKvu zav&l7NLVYoG>(h23#{}CN!4m&AbuK~Rx+OW-R6YgG4A5y)8qXp?LaU8LGAa=X+!7q z`2nsCVmO-=qDp|O#^F7n#J%e<|BgV!VHPX|WH@J~b#g$+X33~qooD3I^LpyDD)}hN zrIF88`m~#)BLbd|pN`HZV*N{%q3MJ6KIJLdYYa~7-!-PDPOWPQ6)@^=)c`W!@TjK* za=W?h*Gtz+`mH&oXLovbx$JkWpBS$;Yx;CX&^E27*?PUoy#yKq7{C7OpUsYjqhZ%8 zwdUV9pYD%aGBJM`cX&0K5D?TZcfSq&8d*1X3J2r>j}V4+z5B({&)5DPK1nUvqV|zl zr<(H&(20D3VJAzMIP&YZ6AhY;4SP%e4DJGp`>R9i=(^v!M6XH<)ag(N*wQW$yd}gp zM9H05ABG38AIHIzv#)%DWcrCM5(H^50KfLjUB!XR*&7qx$XWRgg4zG^&Yz#_aqnZuwG9^GDvif7xJnG0_o`; zA@yFdfqfdL80TJFiSB3El9c3Q2kFEgaJieBF&q?BQ zFUY<_8-K>PnIyUS3MYZYzSr%Sa!~rvNPTEa9#RQr`>Z9T&&{2tQkqmzNp$LZA^x;( zcr|pY?~P0tme}=FD@@e0>MDztfi~e-zmvhAs(=8v2J8nx z_Im@%KY4MFz>Qy?xDO=g8R!ErIoy5;+6f@JxRPI!eBX%)PS#ix-RGN@H`$%%dymof zd1t-aJV1Zn&DkGzD&R5evn+Ooia7BotkRy7oLrw!C(gDFU(pFE4Mo*?)~c`47l1z= z7M9D*SzIi+jk0nLGhj;i@Y9h_UL(ndJ*^}K zfbdyO(4(SGjR|W*yxZl5n80&Wl?;9oS$OE0+k#`uTpiblkZx%|xF2+ATVNWrtaP8x zHPUCmtcQ4%SP(CU3W7iTNyqbaJ}EkC_c`C@HHSHiMV>={D1FGg;Zm*{O#C&Fc*52= z3F4_23!1H~#jrP51{*%P1d8}EJv>%#u&59*E*2#(r~(t#q}c!U{(MPgRxEh0{BtFR zo*jZHBJ(mK2ew)kf-V6(_%In?t~&k)*~8AKPMMUXj*-W|Ho=>*@pPZ3Q0 zOm$Q1dhsF0M$a<0out%K62CzBh`+4b^ z0o!m1ydzm>pEr@qSY?Vn%H;6Do~ISuhgsS)!P-FUjd6(vv@B$;1Vxa zT#hJ_%}3g9A8BqM<#!w$$H>%~$+h{t4(RI`{JxcFo3(Q>YV@PG;&wVwcLze5(#}Olu;i^gCYx!5u_BSRM5fR4-$EBDJy1wb zktG$OVpeohv1H_Y&+w}l&oJ5GU^1Xgb^l}SCxn{JrIJrJok<(nvB8=AXz`8G6^_Um z4hDikTsLB{kkQ?fU`~cHl#nXm&FbhMz&g( zf8F&*VL~Pg#Pdi>WFVr$X)sj&IPg9(?XWY$5jub|{t;`W#wLw90Cr6u_uW=UBLm_D zbIv^>J#B-+d>h1dpv2Q|5i53X6cGWVAfqFZS1KxQMS3jjBgTt2_M%YoJ|zh3F$f|K z<{?2S*KZ6zd<0$RhQG6q7ynUfF1ZkSWD;E&a9A8*WJ{8HwaIRIH@0Rt2f;Kpd1dr> z(Rc&o6V%~eJ@a-W5(NLSh9qiT`)Wdg!e&H2N!1Wy}k668&T2D5RNchjL=$K zve&OJtv3|B4`zSTqLCtni}z`(i#&WHffwoJWA8JwaSBmODm;=pZ zSCvgTs2~<>;mGAev1kJi3PrV#PnTP2_iVUc6s5Bs_S=Z(9eF90nV%6UCb@xrVV*^& zgMVbtdP7b+?~ghTOFKITlEM6BxbD;-$8B)!L-swAY#I@UDP-`=8*8~+rEexcpsFK} z=`O>8tx_cNOa&+CY7b#qV{?&gTNf|>K@vh);RKF4C~fS(+K$qohsyFXL|Gi3%UPST z_Kxb6Jp2`$k5}&@EIye3q--CjZP)`=1QX?U-zn(z_X}U^j({0Y?#tR_MWDnisw3u{ z@TrXns>eUKuFtoXH;y>h5;%LH=!)hGRa(*|H&W&e&=i>nvi(Dyp$~Itk{$2fbQ*ooriD9wu_N?G452!;(C~Zx9>6Lea{y zbw|k9z&XxI&wYNw_hWrvwomap19iOO&V(Uh|9M+BGjs;x(UbZf_HqBH$4?IyUkZXx?sves?*m^J>y`M&2{D(yObUyVbpSSo(xkV(@i=00 zyI;)wDda@{W0b%Fm>U^}3bkngM7t-=Nb#V@^0QbHao;*A!UqdtUdnkJdun!)2dy|? zC6)e*md~Z{uXH5wYDAe{;^T!D$A`EjzoxBOv(Egh{x$!zm;fEVDB9YRj(yjS+6N@ zvVV&jA{&)}sYAuU9za%Z!Xm|>YE8UmxcJZp?OQrG$`~iwE@bSk#B!-dhdkm_Gl=pk zn%1>)a-B#l3L(-ak-7OCU`&cGqx5dr*+DgUceZv&?F(WNsKld}blM(u$zhaL_ zCjOR(vw0?^keRvfbmZAxufE(yre-q2!s4Sbz)gv8K)dXau$>M5wSOBzE%wmmAAV+@ zj#btGO$oKcNn~Ai`<`Al zgBwBt4~&Rpp>e#00ml$S(Hp9xzyfC-C%te3llGy}(N9mHq7<}=_`(g`bbWSYbDI)y z#l`N0qy;4A)Znp$0a}66VZ{cEHbpfB3JEp6PnI!btcVf&K%K-jV%3dQ^?N|6O%W3g zfDLdOvFh*|FfTl1t=3FJul$6n)@7}?ccB%;dd@zVkfg-U3}+W9w#SglL5Xoc?KCg` z6?HIFjPB~NX1fy;ZD)W>;H|O%RZ?q9{Z!rMNW*_3E-?vAIig9ajUA+2RaTmQtRJog z>!hUzTSPApg0_~Gf^fayJ@N>y$R-xA%Hp`pbJ5xBJR?)6)MhbH63=_5MI2SPsesw` zb+h_zdqp=xIjD;^pd8muHZ98ZtM2zf3lmHAmNQ3iq-f>eBtzhe(g8)B*gRFyNT0`2 zkcbh5tq7m=^3#O>NJH>nG4K#)%t>3_sMQG7`A^#agj>ikA#u8N9bU)Dx6>-0_|?doj~^sO?gGmm+8q?@^?tkIy+ACG6ovJ+5?tHGVqT+J36#UP+~b3~ zepf*@tF;1ts$%<0vTm0iWd*N8rikoJ#xL{=HjF^1nvRuo<{+Eh2az=cts{Tiu3H;v zthUF>#}?t8y9DE)p@($Ct^2mz%~CH~t0Kmrb>}ywQgu{QUGlHnx@N?=98c+p_}rpB zJ}sNp(^g1v@^{;_?pmZ_UYx2{Y?YAfME_KtYB%@UU8yJ#60wjk0P~ee$`f&!xA}`W zR^f75dpjCSvmKk{vbRTU9a8?%d0)C#?DR(?#rh`D^+4mcTl0-VtbnznzUcS@!>jvu znT4o>Q-fR=8W$VzpT;mSah+QV>$ zoq1(!j-ez@O{6&B9e-DVHqkq~CVxTOFo(IW!D&IS5#rtH_OF#It&$P(rcUQJ&hw0u z@kS0^MCw`zoS{Lzzu(D-)ucnuPAcqvb(q#9O^h=uUN>)RR<2a1c?ap?B)4oR<*T`e ziKp&umnbfHjn=orvW$IvlneBo-=PjZkgc`v=*>Ss<~t>;|do$5b= zNr)vUIN8^2iz6WZZH2DN({xGo?brt_pXXe>`Z>_k#)4Lc`9=nzbgjvr zF0*xa`*ex6wU5IMm3}MJAin!ro#*DzfM55uSdPpeMMXzyvtxmp@-a@hfW6Rho+Xlv zCK3(5{vEzA1A-A$iNef4D90i4Y+H?1;H*%i0 zzxJK~f!a{aAv{5G`KjXqO{^v5o56^_KtuLHLT9*ksGo6L%Vf`0&60S~6Y|m+ZSHGY zcgf>0tIzthcqpDi;A7sILh=@+PdRPrfzP>Y&H)`GHsdH5%JBq!rM}cS^?M(1=R>s( zl|jlh*v{3RbGhC~qp|kd^A!vO<3l{i42GY^S57|L;0}-DmRE~f|3LYNmEkFpnhWH` zawS?4-npYs1~kutwuPrkJhy&a7Nz3MYp0Ws?Nv9B=TJG0=b^5BO5bme>@`|DY8 zfGlR2uGNGf@#g=k`=p#hKsK>&q^VQ2LK!B*!ZTu?NMbdzKie3JHWR*4_3^aXfViK} zZJnvyQYc~BraE#3=TmjMJe`nE%LhCCTCN9$8adB!`_Cr-X?6k0?-K{^u1%ov^H!$m zCZJ$iu4zB+el{^^d|$WH1P(g5wM@M5+a@S?)Zu&FS$rJDV6unXf~ zSwf>TPZoYQvDd3mL9su>@Pn2keX42w^CE%@M;=sk?v!>vv3UPjFyiNYw zyx^2-(&;-KgU5`mVO)8*VA&i`C~5-6_>$-)E3juxnc zegv?eD7ige!|R`9K4-)9)(g#!w+7DU6)gB9+S$0xCNo#1Z{Yx$XSK)vwDR5cF}ppG zVM8afI3=swwuzZ95=5WCw}2dp%}5YXEIQ1KharT|_j!)+UCmloiyHN$en&s%p`6+;H7r2Z8X54M^qP4r{{<8d{D}7OGy$CCX z&d0df{Qk-rBFQiL?pM|Ox6Qd9olkDD2i?Y!0CfzvemvcwuiAbq!#VHlGx}evhVtAo zehEmP_t;9;owt|}efFPg_pFdo*r{SJc#6^@vwm24ek>Ha{H^J>=sGz3!aDAM|G{Ad zxT7~fD@K6nu{w8NW}p&se6wz_hU`Cs!3n=J#f9!&Q8_pH)vecIB~|f4tf*anN)rH@ z5vVz<4TP@IdKCZmMRp6w;W7c8pea6^5ksc2S2o_4=Z789s$dzrlgN|9sj^s9A}%T5 zEz$8VgG*Sc1qBVAx(~gWTQNn*gC6QSkDepLZ*Mjd75PLkHCTqp0i*_fe!AxZ>J-es zL-+Cl)#ktI1)wn<6~11y3}0DnbHA>*ig@ExdrtFwH`5JC3@g^cYy?yuP~H2 z__-qV^s-&|M}o{s?41Wwk0YQDrfRugP}+~X?hhW+OPl?eW1B=n_sUSy=nCy*dH{&D zw5}p7eyavv^%$nj1i|_L{ZY*2zO6KBq9zK|85_B%P7nI)w8{f&M8K|!V@g>qeeea& zvnM;eEjW5m{4&s{ARK+6fBnIHlOi#%6N~02C8R%-Qnx@o#xeZpMccQ7f5K4&Xb#l? zG|2wzvuY;syAMNvV9K^;1t>GP^&-*h#op&ScO?TdTfNbOTtGyQBZMG|bwo*C5MPJ^ zzx<*^O%%ENaFhzo=9DT>jB510;~4hzP~+-@lSTmzYB(;Zysnz0d($(fR)Abrbs+WwbXujncuq z%fJG4_%3p0JiP~$k!C(DJVmOCX$+JB_m}A%N6=Ax@;>&%gWu(;d7jhyJgfyISrX&{ z7wUb1tAQ*+i1CyoQGE>rgCC`@uf++O8HUny=m521S%5-}Sg{yPPcT={NVQShSs+}4 zLON5iwhE|lK$$ple(d`wJQvw0po`wjvP?i)+7LHsu!(Q_yU=7$(5?bv%;RFz{1i8q z%d1M}`?t8(|3wu%y)Rxzu~}T?i?kL;DyYNOx(dhq7i4Peci2QJ@<7kf25{nVwwbTm zqsZb=t|;Hf7|1@DC3&8pN_jKVLRofR?~dBQ2W)&ns|66eiZMQnk;q8jAL94Rv4TMP zL1KjF3poQXenCk(R9uEvo=E74fQ0K95h}_80V2o` zX!@$WvCB<*xd6KS8O=DJTy#0Y4G?dC*ijSu6QOd73H7gUXJ%#|_5GmvS*3|giJ@+X z=1aiO`OTQ zD_7nL62K=iU1e%3oRRC)E`Oqii3y1a1@g#c*FjQ^E`J}v?x|18Z%&aVMR87U0lGZb z27eUtDuyYC0`O3U0NU|^DBD1K?pSF@D(d)=!c-hw&brybCwt3HdI2JzUKc_oA!zwqa41}j} zC;hqSDfCVBP`0?$=e0NcU&7yAMLcVs+SQHTJ10dR;-0eYnKNg-Td#1Q^eTgSIovko zI`$5MtsG68yqsJD$%1d#p2&pc@y{2oQ|gc1cp)^D!CXI$!&2d$C>b-_63-_DRe^G) zJ^gQWDxe9a;TXNl7sqpgMgn8ag3ba3A;T2=Uh7|toq`w+!j5#m)@JU1V)50Gij37+ ze1-k(2;|*tUonpKG4wnkUoEZ)DtCE~&&jSp8>1KgJp;G0Ul+JLq&E`A9S1rzEXWj{ zW;ZPJ^#?<$)vI*mt@CD1=fQf%*9*=J|1y*$CQxZUmw7n}r<6&?x?&uFmu3ok<)Bi( z+Li6iv#A!;`6L#&>-xVE)L2iB8du`4#p^8JdG<*W3f6F`4*F6GYa#3Aj?Pr2; zz~ws$Xv$faBLnWgOrf(lglDV1y(IQFYnR-#$`%g*cN2K$Y+!6~Wf zBkmnezbgmxAok{>)hf;AdNadUPkNe@u^N?vwo|b!-jr8Z>ty>Ar)<7g2X-}@^n?8q*cx-cK z=WX5Ds0l{_g6h;8HrO?`5lZsh#kG7ss>*jZ7la+u%*BjXyrJt=gXx1A8(>+7;vU1g z{}d;sRB(3XN;eS~>awKPEyUlrT~<6`%z90L=9F|}jYu) zWRQN7Z3V4dG|U=sMKu`GTj>vg@{`_mxZZ)J$-#7GBd0QPOQK@nDQea>#~CoJ|4! zQVs%XVk~_&jt*fxLJ@^0SvN&!l+E?lqetF?%f3c7CPlxay(49O$u7}uE)ICN1bE=D zGQZSq$wzB}&Rk;Px7Bb}l1~;Gu_vgv*6y(%`W3_#B85E{;XAvb;Cn%bC0rUgy!;2 zivK^~ImA@6PVqASg!-T1`hvc2$#;sVC;YovP7q$h7p}nb_Wy)F<%wb=OgP6X-vS9u z{SM!CWLpBVd`M@_4Gf-hZ-YJ+J9o87=X~I`B_OQu;vRI_^-+8*jwDL~qUd@2$*U8G zvR)It1n^w);r1;BURr)W>njA*BudGMS#SN>{%fF8*y!BWAdTWY;e9x-JzZ^VzcLa@ zaCH$%Ro28S{Jd%lgx|e9cMf{|KsQW>uOlE0@e;&qaV@1+e2)m@MX((o)SP9$kGDJ) ziP)U{(g+sN;|4UjodJQT;ZxXAzd@HG0dST`Mn*V}j-)eh0p~u3`Ir|URBygJD9G^b zit}!Og7fZTxw{l?G02^|%O*Y}FvQlW z0hP9>%Ql{Wo^VB(RPX%l*pl;sC|B_8TPCwEn?KyjAMmgHdfA6Ir$+((%+84}RHDQF zUcFx*`4$n9p<9z=rVaw~e$-fnx0_o^Qxr;TWmdx}Jp8$;8BE3sOuBdHirM1VP|w%t z$I1jAWcG%kBcMpiLJYlDmtrGekM%Nr=i;KUn2whaL6Mis>uh@53~)qI!M=1vv%B+F z|1bh}d@viD%G;0s>;DA)%an?G;Ne*Rk4#3(h^5Pa#QJmgpKXf4Yh_!ReYQ{i=Vfwl z0hJnd{FjXX`b-f3nn?3X&^Y(MhR%;zfT6yZY7+YQ7%og$?f?I&OsP`TU$_1{he7}= z9{#2>L-(IizXu*eL#)pFA6r6|qB!L7%@g!?LhwrEc@gxzJX&Oku0sQ+ESKp9zBKtM!5 zQli4D9>AA+kh-cPOW%wg7jqbK1^yrybO3yt6%*MDdYnS>+_xy^>mjuJt~BFG6;u`d zg79!9lgO?-@de9^2=VY0blL)c<)Q|e)nQ+YtE>J%q7G=l$t0rNb+*@$-=8dQXY(t) z_#6fAha9 zssG#ccZH~~*}koEdQ0Zcj?S>`{{+wLWUgRApK7BCJrL5|)(NTZp^5CE#P*IZj zn{SV?!z#!BTmr}VvqNdxjIqMzPV3zjwh#DFiD{E^hvIp+}tmb7P)k=@)NRz7EjE#-mJUoaB2>}%; zmlum!+XyTjCncfnT2gD=v8P05ImGm;gb5{6d7AgJf*aK6ca{xc;7Vo;Fe$icGh**Ih9CIQ=VS6mb?_OfPGMP_pLTG*wFH z`v07Si{xd{d(s;#Tn#U6B13emVzwT$>QeMv{2hDR6FAwO=^wqt~8}qG=pIC*dVO zGD!1G13gPde5+(TW6rf=EY~+MjbU{(1X}^0$Nrda%?!k-XFlXE%9Jaqza?wbN!V!- zCx=dU&h(SMcJ_pI#bqAs^hWwqXc=+OpQFjp$q=n5=uPdSXC{BXvW%MBRhe5J?OQVE zht|0T4CA0Ho$t;uA?4@e%Z$YgEvi`$hvoU8u_ym-eE?r!bte*lo~D~M9qe|XyTOgs z>*{!w?#W&+QQ;^t4>}62n+C2OO(k zHGLj`<0jpf*3!`t1eL`2+dobVnercyI^CO#)`c}Qo-kQB>72-g<=$NzS$J?QTv{7F z)ru=x%R!s%r~7!(HV?k+HZkKX8dp>Cz8l2I@wA}xDzyAKXT%k%ERgEz&q!KnvB70% zO7mAjU^zINyQi*%Q0CNiP0|{F4VYRb8uSP zJE5UL<$?unUW3u8o`jP+b|e01ESRKuw58aofUr}vaxp7O#V*&_B^f))C2qPwztvC! z_Jd~HSZRU5m8XDOOdnpm4A#uz#|XfrkQ3AJD`m;_QYNiZzDr&W57b$YK=T=WL1b$4 z{E!hd?cu=+4w4uDt}$Iwz6aKuliSAmRMM(xR&7Jin}Z=; zJaRoC$dUfGvt}?4Y%C&qHfZC?g=W0(55~`8sY~=!_MSP@EwMK}_XVfsFdj<6*c_Be z8Etp^_?b$2*HBVwn`m%pf&0B{uBrr+JAk32XxmK1oFnZK2ZK1CC=dJQh8TiGIROm1 zQkE-nt%c! zIf_n#EpJpdGip_K0Ek}Q56p?IT_QED(t~pL@GLqW>3l^!SRPV>%KpfKZhg4KLVqFEYpgY>0xbrT#}k(8H3`{q={;W1@B#Wq10bc|GFK>TE3b$z#pRRk540vC}>j!(;wHhEu z%s=8QYUrVzoRFWmGH}oCm$UXlw7S}Cn9q>(BXd%=@viqs-Cs7y@|5+>S+hoirk(6< zf}K$4{K|FiV<2>z&4}0q+BdMLp?(T}xYL|7| zL`<68-oH*TJfV>_pwhn{^EFI&=K`2M+*s6SEadg+nJf>G*j!se7!FZLm$9=uktG7Ft_V9nHWI&SjB2(x)igrK~SCz%bD~ZZuuyR zT&X+E^ADHRN5p@J>vgW`jF(jZSWAQ%M-89unG%bo@O&ZFj)+$Y?akCBhHIdY*E(T; zQfqX=giWi_#td=WAI{G%t=8QV%J#2Ke&5|AY?6(VcGx_Cs9%Es$)rn4Z08M?ILnw@Dn5njrrkw>j#ym z>7BD4lb0-vfQ~0h-53d5rAb%}qg&tXqY+@KQ9>n?513R%PwjQ8mAIhpxiP zdFp`&y-Pft&Xu@PtEJZ4n(GfG5~E}ePUypMoG<<^&C-5fn}bb{9_;1^`brxmOkYhm zd7KQ=98KsgCoHl~%ifOy&^HC_XdnutVWNW5o0WibFl8AZHWRYW;JOqklW6VR^I*=A zlKsp_B`zmscws#=fMy3y%O|9vOIeL#&|4pOrIz_L(tL>WgN?k|+fBC{S(NoUKNj+vre3QAqpz?z@8-(VF7z#g^OOO1M^VBT3~A zb-vTg8?vK=d4$t@7pn~`0!`6z1Wl$WUsfeMTBd^;S|^;o{7&JHuVa+R=;$GU7hREG zB=mc+_`T_@BXMyOGII03M7fUsq($C%>@@#?HGA-SAovt`5*vZ8F&LeUDm=WR#JagQ zPy!<7=-figWdfi6xoANm@RQO!3Oj(muF>;`OT-sJj?-V}nV?K!oQ-omZwDM@bno=@ z2+!wT$&X7)l&i-IxH&P<{d$J5P~s_+>lB9LqZWxXnN8m*AwDkNKk*%rbbuEuJEM*0 za2&W&6FKypqx)p7oO%I!RmdbLCInsf;uL^LjNP>5IX+;lLz zcR#!frDsk^Kt-yv5UjS&+TGs=r7X;h^xQ=uSuz~TxgUKv3n%8s9SB#)I}*-D3*ud< zc+vyOUq;LGPip(s3*^J|j7#@n1EUKty(e;e5}WAFfx1I0^xyD({Rz*KFdt0!fJNAq zA_C|AJ?v1L=Db{Qs4<>uudkt|doIk9n-NgiC~IU>zg;&vIvNsF`PLuEtJ!Qw|0kZa z_&iR7snVN&gS#QD@*+_YRq%dJ(ykDZGzyI`3pcMiHnUcc7=_teJ>WC)c%O-SQ~WcO zWN-ponU#3qxRf}a#_@^(h7rda#;6iK=y;)(9%rsG!Qqg{g+<47Q))PykHrQL?qkNR z6uAdAPHKNnKOx|z1{2yA+y&pi`%>3|G~z_p9fzktB^xI!s>9R4#f=IFva&mqn`HE| z*dqx~JJPdB>n^4$Y^N#jP!mH=9fo@5^}!zIQetY*uM{yz<4Ve>j!qlk>-dH;iL*{n zE|F?@;ysZUn-h!QqW=RIOW)7tiaarWTk&+yTkymCiuYt?4$)gMmu7*M@bK3!gXz$$oKvCucy%ER3d2*#>-p?@8ikZ~gf^jMi`aa*za z%K5whT_j`9NdV$`%K-46)5Yw4gtRbcy_+b2pGk-&MCVQvIg)1v(Z!4T>nt&ra}&f>U7n|Z{uMrN` z9Res*tqzdt<$7Vtl(HxddK~u)5u?UE6&5XJG}An5NcCip#C8mrpdDlMqFByRhx<>>yG|M#BXOMnlCTb~^q=n3`x`f`TYkhdG13 zp>^yNyo87jDb=L4Hg9g=xu+>)nQez_L_aaI6kUzviOiIIh%K%byzunI4wNLXjTMUG zASfJNo;%njf|Br#N2RSx+l!7u=;%UcXscTITQLIGGy)Mr zCrT7El8f`zVr}a%?1nI+`vqb$)KL+^tmxxTn>=dEp!Po%wU*BUy5koR3WXbPxSXh@ zqj`CU^v!h{cgA(jr*>Jg$oV6K=~!FCs2O~3A-sCb39n<%V1*2=`nE=u<#JpYN_#Qw zb|fq)i&Un(11!xkwE-*QWQNtLSmy=Lnr1ocVfQr1Z(Z-~Z%e7d6{i8_1hhAw)!Rsg zXCT<Z}Iz(InhhHeL77Lyix4y$Hz;Y(~S9zDi-f+ut#f!!a%ZF(D_e=PC(E&Z;zOQD8 zRDLztO*z+BucF;u>9?o|Z=lgh>B{C2`@#7ovD}t%B^$wdiccVvwzOUhlMRwZPERP^ zgTI+P?t~RBC#4o+8yOo*ef2n8V^I;ag1B@_+(u&vqYDjry)$*aI%;s-T~~R2YAN(Hqw1wFOk@5|WX=2Cl)&PD1*PW5FHwly299lp(Oa0U6?{U`FN8H%^AWUBLQAVRMT7^)$N+pZb&336e6HZwAq1^CMG~L+!Uv#& z^oj-y_IRUCq&sVF){x~Kgy{$8(~FWsV#yjc8Ba0J-eN#?5LVt|9ggSdVNae7E6 zxeb@$V}WelR*VKpc3;9UrvYMz#%#&206{5lNo1C#)LIB4qZKx<~jt_6P< zJ_2(4f`;Xj&{Mn^Qbz<&xHmTFy3;oQ9O(71~#+%$=|spmQSr8Ib?R3V*!+z%It9sS3Qiea47{=59u`aY9$^tj7UVml(}cT;QyRo; z?s(7oB!nHzY3tr3M~Y;#(}iZ{vs%pMj?gj~_MUxz`r;^PJp3~qLv_X8q)Z_h4FrYD z_85!N)beHJC?`NHMXwT`Mn#0_8CO{J(e7KRYF@>Pjm*$OA>C0eK`Z6!k}23q2Fec& zc7&Xew$gTF0i%lRAslCt;SEpeAnY$Lk<{}ijfxeGrmG1k6!*=Fa)I)+V8n#w48lQ5 za(VR*PXV~_UhtP$8E*sQ)(fgzl9(!=bXAh7r~OH(O*AOpMhlhLt-r3wejaWP|I zzj;9+_rJxP>s~yFD$W@p_jj0+C_(Tu4F~;!=w59Jefc*#TD5Hv<}8s!#Ib%ti74Y# z8f@Y7^y^5}R1i%>jb$UHJuUDSIO1|WLms)o|lW-AK=}c&UFO>ysFC zUjb*15)9#@`f6KB-J)geo=RPdNO!p-(rOU&UHCQ9@Zg&e_1~nMxHK_sow`hFbIPz+ z$S8bp+){*dV5NTg2Azhvn4(%P(LhH3|}gl%ZE<#Y3*WZJqufNHmx`7>+aq z9}=S*V$Ju!e`W@zC1@#9J1gyIN`5LP!d8Lute_PHM2gmam7|K8J+{kkaN^U{3nA!f zW&VIg_e-ilo}jWuiDOBqj2Z8&#mb$eG?Q0M!IX?rVBLM>UT`Ejid%g|&E z{dgu8-YVVQ$6>2y<$ewqGmM>Kjj2nRF>ygei}dS_#)%T5kC{8+<0AB7E|(;c-S{|m zhX^?)zT#x1Pv*r!kqX8xG=JiRu^d2%L5EF+hQ15#)AR((mfsO9XxS8>7NG4Kv}>8r zJ_nz+sez;3{5Vrh;K1Yti7?poL!!m_==ZhQo+is_QOe~GWh^C~T5EDzv=HHd(DL#2 z2)(i_g0drYKy5xbap5!>S;zumfW8XYW6)Ry;8GmpEI7J!Z)4KTp2}cDFG#duFVrg< zeO6pkc$~X(9Y6M%0&e8MF35;+YGh39zLq@3S2{ty49!$9MvkBLESyce(4s^5c zqwX3>Ne(pC2CQoZ-8D-T;A$C0o%r@+n!_AAv(-e$fvXCE&+;H4U%@e?|6uU!)@})g z%Mpz?1|%o{*RIwGbhPN}3Z^)6{S(v!l89@pTx;x3G%|y_Ydi_XP;sj3klz<83g@z4 z=wYe5;qep@oQDaY|Dgl*_O3RCky2QT5qfUMI%qZSS)bMbZfj+FpUxe6f=(Y zT>(VUa$BM)RHq7+Z~Wj*)~h5naq`I2SEONk#g&8+WE+&!j^>YAL}4~wCa&5jlg;>a zWBQ(+8E`(JU#i&zvuOg2u%XeovAfe@hLal(`N%y1T|4LNkc5pE=`6RG2U}R*SLY|J zH~chx5L2*NveMg|(QHJpN(RQGg2U*=xFi$rEaJ=Wv`0P+5&zaWzLcnM|I)n1UlPjTlULdwtP$eBT^9-(ceXT976Z>bnsq$tlS)8JMd@w#A7Fbv^Cq-#mKS&Nupilr;LPLlMV0F zEyQq5eY?E*iVMGW@GuiR0Ao}(T=>ro=VGNm5l8ucdsV?w-{*j@fw5< zFe$4`iH~cx8}R(Xci)V#Xo){045z?&U6#g5!~5ba(HyVNC;AHFkp>)hl4vB&dnZ$9 zYZv^1rBuyqM4X74{tacgd=E1BuElb;S?3FOSc(jeU4Q zmyzz!Cg}y#nnYphaL03-ru0d;D>i0her@P!l&!(Waw{4hebYoqw;F|!K6jf2?=a_E z#%r6rvgxJME zLumPwqqtzeA7{!}1tj6aDnQ zK)YYRFhz?;k$9d!UGDPWY3?Q(3}qtJropxjI?TE$Q4P}FwarIMDb18!{bTIpzzTI^%a%C-pk&1lew}0?r(7!;yV%QIc$XejoY)U54 zCsN8d(r^+!F2+12YMqx!2QMB)uS>->|6+3Y<)(W99Hkx{oI_39Is6#rv1kHlKQ=J zO3vH{I`MP*PYva8&k7!Roy5)jhyBg|u!!OsxZ&BuZrpsZCJ{-FqGx(}r_vuYFu028 zY9&NCIG50~H;ydRb(`WhvfDGjxA(r8QRPt9N)Y=*>V8Wk_8=lR-zqctkP+R67lCv# zTz1%F!9b3SkLxO;X7eW)4mfVkf`!T8Fg;jJ&j!3(dA#P=J_wz5L#)%%&`RugxYr=k zww=gq%u&qw`_hg#4l45%aFX=neCynN{8Yq&qQhx&K^I-8CSKdx$$TIZF0S=0BLTn1 z4NzYngv@H-@4p{#;^x4t#bj~bUIUAUP{~xkiyFER(QsB07WY)9?}-iU++Vm+D52@q*d-;9Wt3eqxpGdG=kKW#c2VDULaak2_V5_u7b*^nl8o{_c zNN)I`Z-$?rA-^qP>Ok?g<$E_5SLw3%*MSz z{X-77ockgl%c)~@bCXDgHm-!prgHqiE>7lDQm-AP*?gJqS>#ZRrL}GT52@{-Ig-7m zo-)rwR*uMs?3e+yvr7S?ALY5URHp84kb@)C*i&Lc&bKOH$a-_t2-$%F?x-6YL=Fch z=fWv(cKneEYNK?RUn#43iYJf*eGttY+G{f<0$ekp6rf5PLL;vyk3dN@1<*4&9H)4u z##F3xC!-{`HXW@4v(0f=tihyF%`prQKnS+V%1| zCh0L50^fYmboA1M{wDFUR4pm^7*pw-PY=mOFyukg

_7R111LPD;|HP^UiQJ< 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 VTJiw>+XDn7B_=Og{lhTu{{jB^XlDQb diff --git a/doc/devel/guidelines/gitwash/branch_list_compare.png b/doc/devel/guidelines/gitwash/branch_list_compare.png deleted file mode 100644 index 336afa3746040f118c073e7d02a5843bc8eed152..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10679 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.7 you can ensure that the link is correctly set by using the +In git >= 1.7 you can ensure that the link is correctly set by using the ``--set-upstream`` option:: git push --set-upstream origin my-new-feature - -From now on git_ will know that ``my-new-feature`` is related to the -``my-new-feature`` branch in the github_ repo. + +From now on git will know that ``my-new-feature`` is related to the +``my-new-feature`` branch in the github repo. + +.. _edit-flow: The editing workflow ==================== @@ -66,7 +118,9 @@ In more detail #. Make some changes #. See which files have changed with ``git status`` (see `git status`_). - You'll see a listing like this one:: + You'll see a listing like this one: + + .. code-block:: none # On branch ny-new-feature # Changed but not updated: @@ -83,102 +137,46 @@ In more detail #. Check what the actual changes are with ``git diff`` (`git diff`_). #. Add any new files to version control ``git add new_file_name`` (see - `git add`_). + `git add`_). #. To commit all modified files into the local copy of your repo,, do ``git commit -am 'A commit message'``. Note the ``-am`` options to ``commit``. The ``m`` flag just signals that you're going to type a - message on the command line. The ``a`` flag - you can just take on - faith - or see `why the -a flag?`_ - and the helpful use-case description in - the `tangled working copy problem`_. The `git commit`_ manual + message on the command line. The ``a`` flag |emdash| you can just take on + faith |emdash| or see `why the -a flag?`_ |emdash| and the helpful use-case + description in the `tangled working copy problem`_. The `git commit`_ manual page might also be useful. -#. To push the changes up to your forked repo on github_, do a ``git - push`` (see `git push`). - -Asking for code review -====================== - -#. Go to your repo URL - e.g. ``http://github.com/your-user-name/nipy``. -#. Click on the *Branch list* button: - - .. image:: branch_list.png - -#. Click on the *Compare* button for your feature branch - here ``my-new-feature``: +#. To push the changes up to your forked repo on github, do a ``git + push`` (see `git push`_). - .. image:: branch_list_compare.png +Ask for your changes to be reviewed or merged +============================================= -#. If asked, select the *base* and *comparison* branch names you want to - compare. Usually these will be ``master`` and ``my-new-feature`` - (where that is your feature branch name). -#. At this point you should get a nice summary of the changes. Copy the - URL for this, and post it to the `nipy mailing list`_, asking for - review. The URL will look something like: - ``http://github.com/your-user-name/nipy/compare/master...my-new-feature``. - There's an example at - http://github.com/matthew-brett/nipy/compare/master...find-install-data - See: http://github.com/blog/612-introducing-github-compare-view for - more detail. +When you are ready to ask for someone to review your code and consider a merge: -The generated comparison, is between your feature branch -``my-new-feature``, and the place in ``master`` from which you branched -``my-new-feature``. In other words, you can keep updating ``master`` -without interfering with the output from the comparison. More detail? -Note the three dots in the URL above (``master...my-new-feature``) and -see :ref:`dot2-dot3`. - -Asking for your changes to be merged with the main repo -======================================================= +#. Go to the URL of your forked repo, say + ``https://github.com/your-user-name/nipy``. +#. Use the 'Switch Branches' dropdown menu near the top left of the page to + select the branch with your changes: -When you are ready to ask for the merge of your code: + .. image:: branch_dropdown.png -#. Go to the URL of your forked repo, say - ``http://github.com/your-user-name/nipy.git``. #. Click on the 'Pull request' button: .. image:: pull_button.png - Enter a message; we suggest you select only ``nipy`` as the - recipient. The message will go to the `nipy mailing list`_. Please - feel free to add others from the list as you like. + Enter a title for the set of changes, and some explanation of what you've + done. Say if there is anything you'd like particular attention for - like a + complicated change or some code you are not happy with. -Merging from trunk -================== + If you don't think your request is ready to be merged, just say so in your + pull request message. This is still a good way of getting some preliminary + code review. -This updates your code from the upstream `nipy github`_ repo. +Some other things you might want to do +====================================== -Overview --------- - -:: - - # go to your master branch - git checkout master - # pull changes from github - git fetch upstream - # merge from upstream - git merge upstream/master - -In detail ---------- - -We suggest that you do this only for your ``master`` branch, and leave -your 'feature' branches unmerged, to keep their history as clean as -possible. This makes code review easier:: - - git checkout master - -Make sure you have done :ref:`linking-to-upstream`. - -Merge the upstream code into your current development by first pulling -the upstream repo to a copy on your local machine:: - - git fetch upstream - -then merging into your current branch:: - - git merge upstream/master - -Deleting a branch on github_ -============================ +Delete a branch on github +------------------------- :: @@ -188,20 +186,20 @@ Deleting a branch on github_ # delete branch on github git push origin :my-unwanted-branch -(Note the colon ``:`` before ``test-branch``. See also: -http://github.com/guides/remove-a-remote-branch +Note the colon ``:`` before ``my-unwanted-branch``. See also: +https://help.github.com/articles/pushing-to-a-remote/#deleting-a-remote-branch-or-tag Several people sharing a single repository -========================================== +------------------------------------------ If you want to work on some stuff with other people, where you are all committing into the same repository, or even the same branch, then just -share it via github_. +share it via github. First fork nipy into your account, as from :ref:`forking`. Then, go to your forked repository github page, say -``http://github.com/your-user-name/nipy`` +``https://github.com/your-user-name/nipy`` Click on the 'Admin' button, and add anyone else to the repo as a collaborator: @@ -221,8 +219,8 @@ usual:: git commit -am 'ENH - much better code' git push origin master # pushes directly into your repo -Exploring your repository -========================= +Explore your repository +----------------------- To see a graphical representation of the repository branches and commits:: @@ -233,7 +231,193 @@ To see a linear list of commits for this branch:: git log -You can also look at the `network graph visualizer`_ for your github_ +You can also look at the `network graph visualizer`_ for your github repo. -.. include:: git_links.inc +Finally the :ref:`fancy-log` ``lg`` alias will give you a reasonable text-based +graph of the repository. + +.. _rebase-on-trunk: + +Rebasing on trunk +----------------- + +Let's say you thought of some work you'd like to do. You +:ref:`update-mirror-trunk` and :ref:`make-feature-branch` called +``cool-feature``. At this stage trunk is at some commit, let's call it E. Now +you make some new commits on your ``cool-feature`` branch, let's call them A, B, +C. Maybe your changes take a while, or you come back to them after a while. In +the meantime, trunk has progressed from commit E to commit (say) G: + +.. code-block:: none + + A---B---C cool-feature + / + D---E---F---G trunk + +At this stage you consider merging trunk into your feature branch, and you +remember that this here page sternly advises you not to do that, because the +history will get messy. Most of the time you can just ask for a review, and not +worry that trunk has got a little ahead. But sometimes, the changes in trunk +might affect your changes, and you need to harmonize them. In this situation +you may prefer to do a rebase. + +rebase takes your changes (A, B, C) and replays them as if they had been made to +the current state of ``trunk``. In other words, in this case, it takes the +changes represented by A, B, C and replays them on top of G. After the rebase, +your history will look like this: + +.. code-block:: none + + A'--B'--C' cool-feature + / + D---E---F---G trunk + +See `rebase without tears`_ for more detail. + +To do a rebase on trunk:: + + # Update the mirror of trunk + git fetch upstream + # go to the feature branch + git checkout cool-feature + # make a backup in case you mess up + git branch tmp cool-feature + # rebase cool-feature onto trunk + git rebase --onto upstream/master upstream/master cool-feature + +In this situation, where you are already on branch ``cool-feature``, the last +command can be written more succinctly as:: + + git rebase upstream/master + +When all looks good you can delete your backup branch:: + + git branch -D tmp + +If it doesn't look good you may need to have a look at +:ref:`recovering-from-mess-up`. + +If you have made changes to files that have also changed in trunk, this may +generate merge conflicts that you need to resolve - see the `git rebase`_ man +page for some instructions at the end of the "Description" section. There is +some related help on merging in the git user manual - see `resolving a merge`_. + +.. _recovering-from-mess-up: + +Recovering from mess-ups +------------------------ + +Sometimes, you mess up merges or rebases. Luckily, in git it is +relatively straightforward to recover from such mistakes. + +If you mess up during a rebase:: + + git rebase --abort + +If you notice you messed up after the rebase:: + + # reset branch back to the saved point + git reset --hard tmp + +If you forgot to make a backup branch:: + + # look at the reflog of the branch + git reflog show cool-feature + + 8630830 cool-feature@{0}: commit: BUG: io: close file handles immediately + 278dd2a cool-feature@{1}: rebase finished: refs/heads/my-feature-branch onto 11ee694744f2552d + 26aa21a cool-feature@{2}: commit: BUG: lib: make seek_gzip_factory not leak gzip obj + ... + + # reset the branch to where it was before the botched rebase + git reset --hard cool-feature@{2} + +.. _rewriting-commit-history: + +Rewriting commit history +------------------------ + +.. note:: + + Do this only for your own feature branches. + +There's an embarrassing typo in a commit you made? Or perhaps the you +made several false starts you would like the posterity not to see. + +This can be done via *interactive rebasing*. + +Suppose that the commit history looks like this:: + + git log --oneline + eadc391 Fix some remaining bugs + a815645 Modify it so that it works + 2dec1ac Fix a few bugs + disable + 13d7934 First implementation + 6ad92e5 * masked is now an instance of a new object, MaskedConstant + 29001ed Add pre-nep for a copule of structured_array_extensions. + ... + +and ``6ad92e5`` is the last commit in the ``cool-feature`` branch. Suppose we +want to make the following changes: + +* Rewrite the commit message for ``13d7934`` to something more sensible. +* Combine the commits ``2dec1ac``, ``a815645``, ``eadc391`` into a single one. + +We do as follows:: + + # make a backup of the current state + git branch tmp HEAD + # interactive rebase + git rebase -i 6ad92e5 + +This will open an editor with the following text in it:: + + pick 13d7934 First implementation + pick 2dec1ac Fix a few bugs + disable + pick a815645 Modify it so that it works + pick eadc391 Fix some remaining bugs + + # Rebase 6ad92e5..eadc391 onto 6ad92e5 + # + # Commands: + # p, pick = use commit + # r, reword = use commit, but edit the commit message + # e, edit = use commit, but stop for amending + # s, squash = use commit, but meld into previous commit + # f, fixup = like "squash", but discard this commit's log message + # + # If you remove a line here THAT COMMIT WILL BE LOST. + # However, if you remove everything, the rebase will be aborted. + # + +To achieve what we want, we will make the following changes to it:: + + r 13d7934 First implementation + pick 2dec1ac Fix a few bugs + disable + f a815645 Modify it so that it works + f eadc391 Fix some remaining bugs + +This means that (i) we want to edit the commit message for +``13d7934``, and (ii) collapse the last three commits into one. Now we +save and quit the editor. + +Git will then immediately bring up an editor for editing the commit +message. After revising it, we get the output:: + + [detached HEAD 721fc64] FOO: First implementation + 2 files changed, 199 insertions(+), 66 deletions(-) + [detached HEAD 0f22701] Fix a few bugs + disable + 1 files changed, 79 insertions(+), 61 deletions(-) + Successfully rebased and updated refs/heads/my-feature-branch. + +and the history looks now like this:: + + 0f22701 Fix a few bugs + disable + 721fc64 ENH: Sophisticated feature + 6ad92e5 * masked is now an instance of a new object, MaskedConstant + +If it went wrong, recovery is again possible as explained :ref:`above +`. + +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/dot2_dot3.rst b/doc/devel/guidelines/gitwash/dot2_dot3.rst deleted file mode 100644 index 7759e2e60d..0000000000 --- a/doc/devel/guidelines/gitwash/dot2_dot3.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. _dot2-dot3: - -======================================== - Two and three dots in difference specs -======================================== - -Thanks to Yarik Halchenko for this explanation. - -Imagine a series of commits A, B, C, D... Imagine that there are two -branches, *topic* and *master*. You branched *topic* off *master* when -*master* was at commit 'E'. The graph of the commits looks like this:: - - - A---B---C topic - / - D---E---F---G master - -Then:: - - git diff master..topic - -will output the difference from G to C (i.e. with effects of F and G), -while:: - - git diff master...topic - -would output just differences in the topic branch (i.e. only A, B, and -C). diff --git a/doc/devel/guidelines/gitwash/following_latest.rst b/doc/devel/guidelines/gitwash/following_latest.rst index 9256bef3ba..63a0aee658 100644 --- a/doc/devel/guidelines/gitwash/following_latest.rst +++ b/doc/devel/guidelines/gitwash/following_latest.rst @@ -1,3 +1,5 @@ +.. highlight:: bash + .. _following-latest: ============================= @@ -10,7 +12,7 @@ These are the instructions if you just want to follow the latest The steps are: * :ref:`install-git` -* get local copy of the git repository from github_ +* get local copy of the `nipy github`_ git repository * update local copy from time to time Get the local copy of the code @@ -33,4 +35,4 @@ From time to time you may want to pull down the latest code. Do this with:: The tree in ``nipy`` will now have the latest changes from the initial repository. -.. include:: git_links.inc +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/forking_hell.rst b/doc/devel/guidelines/gitwash/forking_hell.rst index 29c464c28d..6beff8559c 100644 --- a/doc/devel/guidelines/gitwash/forking_hell.rst +++ b/doc/devel/guidelines/gitwash/forking_hell.rst @@ -1,33 +1,34 @@ +.. highlight:: bash + .. _forking: -========================================== +====================================================== Making your own copy (fork) of nipy -========================================== +====================================================== You need to do this only once. The instructions here are very similar -to the instructions at http://help.github.com/forking/ - please see that -page for more detail. We're repeating some of it here just to give the -specifics for the nipy_ project, and to suggest some default names. +to the instructions at https://help.github.com/forking/ |emdash| please see +that page for more detail. We're repeating some of it here just to give the +specifics for the `nipy`_ project, and to suggest some default names. -Set up and configure a github_ account -====================================== +Set up and configure a github account +===================================== -If you don't have a github_ account, go to the github_ page, and make one. +If you don't have a github account, go to the github page, and make one. -You then need to configure your account to allow write access - see the -``Generating SSH keys`` help on `github help`_. +You then need to configure your account to allow write access |emdash| see +the ``Generating SSH keys`` help on `github help`_. -Create your own forked copy of nipy_ -========================================= +Create your own forked copy of `nipy`_ +====================================================== -#. Log into your github_ account. -#. Go to the nipy_ github home at `nipy github`_. +#. Log into your github account. +#. Go to the `nipy`_ github home at `nipy github`_. #. Click on the *fork* button: .. image:: forking_button.png - Now, after a short pause and some 'Hardcore forking action', you - should find yourself at the home page for your own forked copy of nipy_. - -.. include:: git_links.inc + Now, after a short pause, you should find yourself at the home page for + your own forked copy of `nipy`_. +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/git_development.rst b/doc/devel/guidelines/gitwash/git_development.rst index 64522c6581..c5b910d863 100644 --- a/doc/devel/guidelines/gitwash/git_development.rst +++ b/doc/devel/guidelines/gitwash/git_development.rst @@ -13,4 +13,4 @@ Contents: set_up_fork configure_git development_workflow - + maintainer_workflow diff --git a/doc/devel/guidelines/gitwash/git_install.rst b/doc/devel/guidelines/gitwash/git_install.rst index 422bdf66ac..66eca8c29b 100644 --- a/doc/devel/guidelines/gitwash/git_install.rst +++ b/doc/devel/guidelines/gitwash/git_install.rst @@ -1,3 +1,5 @@ +.. highlight:: bash + .. _install-git: ============= @@ -8,8 +10,8 @@ Overview ======== ================ ============= -Debian / Ubuntu ``sudo apt-get install git-core`` -Fedora ``sudo yum install git-core`` +Debian / Ubuntu ``sudo apt-get install git`` +Fedora ``sudo yum install git`` Windows Download and install msysGit_ OS X Use the git-osx-installer_ ================ ============= @@ -17,10 +19,10 @@ OS X Use the git-osx-installer_ In detail ========= -See the git_ page for the most recent information. +See the git page for the most recent information. -Have a look at the github_ install help pages available from `github help`_ +Have a look at the github install help pages available from `github help`_ -There are good instructions here: http://book.git-scm.com/2_installing_git.html +There are good instructions here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git -.. include:: git_links.inc +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/git_intro.rst b/doc/devel/guidelines/gitwash/git_intro.rst index a2c7d41d09..2c633af766 100644 --- a/doc/devel/guidelines/gitwash/git_intro.rst +++ b/doc/devel/guidelines/gitwash/git_intro.rst @@ -1,18 +1,20 @@ +.. highlight:: bash + ============== Introduction ============== -These pages describe a git_ and github_ workflow for the nipy_ +These pages describe a git_ and github_ workflow for the `nipy`_ project. There are several different workflows here, for different ways of working with *nipy*. -This is not a comprehensive git_ reference, it's just a workflow for our -own project. It's tailored to the github_ hosting service. You may well -find better or quicker ways of getting stuff done with git_, but these +This is not a comprehensive git reference, it's just a workflow for our +own project. It's tailored to the github hosting service. You may well +find better or quicker ways of getting stuff done with git, but these should get you started. -For general resources for learning git_ see :ref:`git-resources`. +For general resources for learning git, see :ref:`git-resources`. -.. include:: git_links.inc +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/git_links.inc b/doc/devel/guidelines/gitwash/git_links.inc index c7e5e280a6..d01ad7833c 100644 --- a/doc/devel/guidelines/gitwash/git_links.inc +++ b/doc/devel/guidelines/gitwash/git_links.inc @@ -2,81 +2,58 @@ and name substitutions. It may be included in many files, therefore it should only contain link targets and name substitutions. Try grepping for "^\.\. _" to find plausible - candidates for this list. + candidates for this list. .. NOTE: reST targets are __not_case_sensitive__, so only one target definition is needed for nipy, NIPY, Nipy, etc... -.. PROJECTNAME placeholders -.. _PROJECTNAME: http://neuroimaging.scipy.org -.. _`PROJECTNAME github`: http://github.com/nipy -.. _`PROJECTNAME mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. nipy -.. _nipy: http://nipy.org/nipy -.. _`nipy github`: http://github.com/nipy/nipy -.. _`nipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. ipython -.. _ipython: http://ipython.scipy.org -.. _`ipython github`: http://github.com/ipython/ipython -.. _`ipython mailing list`: http://mail.scipy.org/mailman/listinfo/IPython-dev - -.. dipy -.. _dipy: http://nipy.org/dipy -.. _`dipy github`: http://github.com/Garyfallidis/dipy -.. _`dipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. nibabel -.. _nibabel: http://nipy.org/nibabel -.. _`nibabel github`: http://github.com/nipy/nibabel -.. _`nibabel mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. marsbar -.. _marsbar: http://marsbar.sourceforge.net -.. _`marsbar github`: http://github.com/matthew-brett/marsbar -.. _`MarsBaR mailing list`: https://lists.sourceforge.net/lists/listinfo/marsbar-users - .. git stuff -.. _git: http://git-scm.com/ -.. _github: http://github.com -.. _github help: http://help.github.com -.. _msysgit: http://code.google.com/p/msysgit/downloads/list -.. _git-osx-installer: http://code.google.com/p/git-osx-installer/downloads/list +.. _git: https://git-scm.com/ +.. _github: https://github.com +.. _github help: https://help.github.com +.. _msysgit: https://git-scm.com/download/win +.. _git-osx-installer: https://git-scm.com/download/mac .. _subversion: http://subversion.tigris.org/ -.. _git cheat sheet: http://github.com/guides/git-cheat-sheet -.. _pro git book: http://progit.org/ -.. _git svn crash course: http://git-scm.com/course/svn.html -.. _learn.github: http://learn.github.com/ -.. _network graph visualizer: http://github.com/blog/39-say-hello-to-the-network-graph-visualizer -.. _git user manual: http://www.kernel.org/pub/software/scm/git/docs/user-manual.html -.. _git tutorial: http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html -.. _git community book: http://book.git-scm.com/ -.. _git ready: http://www.gitready.com/ -.. _git casts: http://www.gitcasts.com/ +.. _git cheat sheet: https://help.github.com/git-cheat-sheets/ +.. _pro git book: https://progit.org/ +.. _git svn crash course: https://git-scm.com/course/svn.html +.. _network graph visualizer: https://github.com/blog/39-say-hello-to-the-network-graph-visualizer +.. _git user manual: https://schacon.github.io/git/user-manual.html +.. _git tutorial: https://schacon.github.io/git/gittutorial.html +.. _git community book: https://git-scm.com/book/en/v2 +.. _git ready: http://gitready.com/ .. _Fernando's git page: http://www.fperez.org/py4science/git.html .. _git magic: http://www-cs-students.stanford.edu/~blynn/gitmagic/index.html -.. _git concepts: http://www.eecs.harvard.edu/~cduan/technical/git/ -.. _git clone: http://www.kernel.org/pub/software/scm/git/docs/git-clone.html -.. _git checkout: http://www.kernel.org/pub/software/scm/git/docs/git-checkout.html -.. _git commit: http://www.kernel.org/pub/software/scm/git/docs/git-commit.html -.. _git push: http://www.kernel.org/pub/software/scm/git/docs/git-push.html -.. _git pull: http://www.kernel.org/pub/software/scm/git/docs/git-pull.html -.. _git add: http://www.kernel.org/pub/software/scm/git/docs/git-add.html -.. _git status: http://www.kernel.org/pub/software/scm/git/docs/git-status.html -.. _git diff: http://www.kernel.org/pub/software/scm/git/docs/git-diff.html -.. _git log: http://www.kernel.org/pub/software/scm/git/docs/git-log.html -.. _git branch: http://www.kernel.org/pub/software/scm/git/docs/git-branch.html -.. _git remote: http://www.kernel.org/pub/software/scm/git/docs/git-remote.html -.. _git config: http://www.kernel.org/pub/software/scm/git/docs/git-config.html -.. _why the -a flag?: http://www.gitready.com/beginner/2009/01/18/the-staging-area.html -.. _git staging area: http://www.gitready.com/beginner/2009/01/18/the-staging-area.html -.. _tangled working copy problem: http://tomayko.com/writings/the-thing-about-git -.. _git management: http://kerneltrap.org/Linux/Git_Management -.. _linux git workflow: http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html +.. _git concepts: https://www.sbf5.com/~cduan/technical/git/ +.. _git clone: https://schacon.github.io/git/git-clone.html +.. _git checkout: https://schacon.github.io/git/git-checkout.html +.. _git commit: https://schacon.github.io/git/git-commit.html +.. _git push: https://schacon.github.io/git/git-push.html +.. _git pull: https://schacon.github.io/git/git-pull.html +.. _git add: https://schacon.github.io/git/git-add.html +.. _git status: https://schacon.github.io/git/git-status.html +.. _git diff: https://schacon.github.io/git/git-diff.html +.. _git log: https://schacon.github.io/git/git-log.html +.. _git branch: https://schacon.github.io/git/git-branch.html +.. _git remote: https://schacon.github.io/git/git-remote.html +.. _git rebase: https://schacon.github.io/git/git-rebase.html +.. _git config: https://schacon.github.io/git/git-config.html +.. _why the -a flag?: http://gitready.com/beginner/2009/01/18/the-staging-area.html +.. _git staging area: http://gitready.com/beginner/2009/01/18/the-staging-area.html +.. _tangled working copy problem: http://2ndscale.com/rtomayko/2008/the-thing-about-git +.. _git management: https://web.archive.org/web/20090224195437/http://kerneltrap.org/Linux/Git_Management +.. _linux git workflow: https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html .. _git parable: http://tom.preston-werner.com/2009/05/19/the-git-parable.html -.. _git foundation: http://matthew-brett.github.com/pydagogue/foundation.html +.. _git foundation: https://matthew-brett.github.io/pydagogue/foundation.html +.. _deleting master on github: https://matthew-brett.github.io/pydagogue/gh_delete_master.html +.. _rebase without tears: https://matthew-brett.github.io/pydagogue/rebase_without_tears.html +.. _resolving a merge: https://schacon.github.io/git/user-manual.html#resolving-a-merge +.. _ipython git workflow: https://mail.scipy.org/pipermail/ipython-dev/2010-October/006746.html .. other stuff -.. _python: http://www.python.org +.. _python: https://www.python.org + +.. |emdash| unicode:: U+02014 + +.. vim: ft=rst diff --git a/doc/devel/guidelines/gitwash/git_resources.rst b/doc/devel/guidelines/gitwash/git_resources.rst index ae350806eb..2787a575cc 100644 --- a/doc/devel/guidelines/gitwash/git_resources.rst +++ b/doc/devel/guidelines/gitwash/git_resources.rst @@ -1,32 +1,33 @@ +.. highlight:: bash + .. _git-resources: -================ - git_ resources -================ +============= +git resources +============= Tutorials and summaries ======================= * `github help`_ has an excellent series of how-to guides. -* `learn.github`_ has an excellent series of tutorials -* The `pro git book`_ is a good in-depth book on git. +* The `pro git book`_ is a good in-depth book on git. * A `git cheat sheet`_ is a page giving summaries of common commands. -* The `git user manual`_ +* The `git user manual`_ * The `git tutorial`_ * The `git community book`_ -* `git ready`_ - a nice series of tutorials -* `git casts`_ - video snippets giving git how-tos. -* `git magic`_ - extended introduction with intermediate detail +* `git ready`_ |emdash| a nice series of tutorials +* `git magic`_ |emdash| extended introduction with intermediate detail * The `git parable`_ is an easy read explaining the concepts behind git. -* Our own `git foundation`_ expands on the `git parable`_. -* Fernando Perez' git page - `Fernando's git page`_ - many links and tips +* `git foundation`_ expands on the `git parable`_. +* Fernando Perez' git page |emdash| `Fernando's git page`_ |emdash| many + links and tips * A good but technical page on `git concepts`_ -* `git svn crash course`_: git_ for those of us used to subversion_ +* `git svn crash course`_: git for those of us used to subversion_ Advanced git workflow ===================== -There are many ways of working with git_; here are some posts on the +There are many ways of working with git; here are some posts on the rules of thumb that other projects have come up with: * Linus Torvalds on `git management`_ @@ -55,4 +56,4 @@ online manual pages for some common commands: * `git remote`_ * `git status`_ -.. include:: git_links.inc +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/index.rst b/doc/devel/guidelines/gitwash/index.rst index f6960f1bfa..ff6357f122 100644 --- a/doc/devel/guidelines/gitwash/index.rst +++ b/doc/devel/guidelines/gitwash/index.rst @@ -1,7 +1,7 @@ .. _using-git: Working with *nipy* source code -====================================== +================================================ Contents: diff --git a/doc/devel/guidelines/gitwash/known_projects.inc b/doc/devel/guidelines/gitwash/known_projects.inc new file mode 100644 index 0000000000..710abe08e4 --- /dev/null +++ b/doc/devel/guidelines/gitwash/known_projects.inc @@ -0,0 +1,41 @@ +.. Known projects + +.. PROJECTNAME placeholders +.. _PROJECTNAME: http://nipy.org +.. _`PROJECTNAME github`: https://github.com/nipy +.. _`PROJECTNAME mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging + +.. numpy +.. _numpy: http://www.numpy.org +.. _`numpy github`: https://github.com/numpy/numpy +.. _`numpy mailing list`: https://mail.scipy.org/mailman/listinfo/numpy-discussion + +.. scipy +.. _scipy: https://www.scipy.org +.. _`scipy github`: https://github.com/scipy/scipy +.. _`scipy mailing list`: https://mail.scipy.org/mailman/listinfo/scipy-dev + +.. nipy +.. _nipy: http://nipy.org/nipy/ +.. _`nipy github`: https://github.com/nipy/nipy +.. _`nipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging + +.. ipython +.. _ipython: https://ipython.org +.. _`ipython github`: https://github.com/ipython/ipython +.. _`ipython mailing list`: https://mail.scipy.org/mailman/listinfo/IPython-dev + +.. dipy +.. _dipy: http://nipy.org/dipy/ +.. _`dipy github`: https://github.com/Garyfallidis/dipy +.. _`dipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging + +.. nibabel +.. _nibabel: http://nipy.org/nibabel/ +.. _`nibabel github`: https://github.com/nipy/nibabel +.. _`nibabel mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging + +.. marsbar +.. _marsbar: http://marsbar.sourceforge.net +.. _`marsbar github`: https://github.com/matthew-brett/marsbar +.. _`MarsBaR mailing list`: https://lists.sourceforge.net/lists/listinfo/marsbar-users diff --git a/doc/devel/guidelines/gitwash/links.inc b/doc/devel/guidelines/gitwash/links.inc new file mode 100644 index 0000000000..20f4dcfffd --- /dev/null +++ b/doc/devel/guidelines/gitwash/links.inc @@ -0,0 +1,4 @@ +.. compiling links file +.. include:: known_projects.inc +.. include:: this_project.inc +.. include:: git_links.inc diff --git a/doc/devel/guidelines/gitwash/maintainer_workflow.rst b/doc/devel/guidelines/gitwash/maintainer_workflow.rst new file mode 100644 index 0000000000..ff0cc529c4 --- /dev/null +++ b/doc/devel/guidelines/gitwash/maintainer_workflow.rst @@ -0,0 +1,98 @@ +.. highlight:: bash + +.. _maintainer-workflow: + +################### +Maintainer workflow +################### + +This page is for maintainers |emdash| those of us who merge our own or other +peoples' changes into the upstream repository. + +Being as how you're a maintainer, you are completely on top of the basic stuff +in :ref:`development-workflow`. + +The instructions in :ref:`linking-to-upstream` add a remote that has read-only +access to the upstream repo. Being a maintainer, you've got read-write access. + +It's good to have your upstream remote have a scary name, to remind you that +it's a read-write remote:: + + git remote add upstream-rw git@github.com:nipy/nipy.git + git fetch upstream-rw + +******************* +Integrating changes +******************* + +Let's say you have some changes that need to go into trunk +(``upstream-rw/master``). + +The changes are in some branch that you are currently on. For example, you are +looking at someone's changes like this:: + + git remote add someone git://github.com/someone/nipy.git + git fetch someone + git branch cool-feature --track someone/cool-feature + git checkout cool-feature + +So now you are on the branch with the changes to be incorporated upstream. The +rest of this section assumes you are on this branch. + +A few commits +============= + +If there are only a few commits, consider rebasing to upstream:: + + # Fetch upstream changes + git fetch upstream-rw + # rebase + git rebase upstream-rw/master + +Remember that, if you do a rebase, and push that, you'll have to close any +github pull requests manually, because github will not be able to detect the +changes have already been merged. + +A long series of commits +======================== + +If there are a longer series of related commits, consider a merge instead:: + + git fetch upstream-rw + git merge --no-ff upstream-rw/master + +The merge will be detected by github, and should close any related pull requests +automatically. + +Note the ``--no-ff`` above. This forces git to make a merge commit, rather than +doing a fast-forward, so that these set of commits branch off trunk then rejoin +the main history with a merge, rather than appearing to have been made directly +on top of trunk. + +Check the history +================= + +Now, in either case, you should check that the history is sensible and you have +the right commits:: + + git log --oneline --graph + git log -p upstream-rw/master.. + +The first line above just shows the history in a compact way, with a text +representation of the history graph. The second line shows the log of commits +excluding those that can be reached from trunk (``upstream-rw/master``), and +including those that can be reached from current HEAD (implied with the ``..`` +at the end). So, it shows the commits unique to this branch compared to trunk. +The ``-p`` option shows the diff for these commits in patch form. + +Push to trunk +============= + +:: + + git push upstream-rw my-new-feature:master + +This pushes the ``my-new-feature`` branch in this repository to the ``master`` +branch in the ``upstream-rw`` repository. + +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/patching.rst b/doc/devel/guidelines/gitwash/patching.rst index 6232e4c5a3..11baee2c87 100644 --- a/doc/devel/guidelines/gitwash/patching.rst +++ b/doc/devel/guidelines/gitwash/patching.rst @@ -1,17 +1,21 @@ +.. highlight:: bash + ================ Making a patch ================ -You've discovered a bug or something else you want to change in nipy_ - excellent! +You've discovered a bug or something else you want to change +in `nipy`_ .. |emdash| excellent! -You've worked out a way to fix it - even better! +You've worked out a way to fix it |emdash| even better! -You want to tell us about it - best of all! +You want to tell us about it |emdash| best of all! -The easiest way is to make a *patch* or set of patches. Here we explain -how. Making a patch is the simplest and quickest, but if you're going -to be doing anything more than simple quick things, please consider -following the :ref:`git-development` model instead. +The easiest way is to make a *patch* or set of patches. Here +we explain how. Making a patch is the simplest and quickest, +but if you're going to be doing anything more than simple +quick things, please consider following the +:ref:`git-development` model instead. .. _making-patches: @@ -42,24 +46,28 @@ Overview # make the patch files git format-patch -M -C master -Then, send the generated patch files to the `nipy mailing list`_ - where we will thank you warmly. +Then, send the generated patch files to the `nipy +mailing list`_ |emdash| where we will thank you warmly. In detail --------- -#. Tell git_ who you are so it can label the commits you've made:: +#. Tell git who you are so it can label the commits you've + made:: git config --global user.email you@yourdomain.example.com git config --global user.name "Your Name Comes Here" -#. If you don't already have one, clone a copy of the nipy_ repository:: +#. If you don't already have one, clone a copy of the + `nipy`_ repository:: git clone git://github.com/nipy/nipy.git cd nipy -#. Make a 'feature branch'. This will be where you work on your bug - fix. It's nice and safe and leaves you with access to an unmodified - copy of the code in the main branch:: +#. Make a 'feature branch'. This will be where you work on + your bug fix. It's nice and safe and leaves you with + access to an unmodified copy of the code in the main + branch:: git branch the-fix-im-thinking-of git checkout the-fix-im-thinking-of @@ -74,39 +82,45 @@ In detail # hack hack, hack git commit -am 'BF - added fix for Funny bug' - Note the ``-am`` options to ``commit``. The ``m`` flag just signals - that you're going to type a message on the command line. The ``a`` - flag - you can just take on faith - or see `why the -a flag?`_. + Note the ``-am`` options to ``commit``. The ``m`` flag just + signals that you're going to type a message on the command + line. The ``a`` flag |emdash| you can just take on faith |emdash| + or see `why the -a flag?`_. -#. When you have finished, check you have committed all your changes:: +#. When you have finished, check you have committed all your + changes:: git status -#. Finally, make your commits into patches. You want all the commits - since you branched from the ``master`` branch:: +#. Finally, make your commits into patches. You want all the + commits since you branched from the ``master`` branch:: git format-patch -M -C master - You will now have several files named for the commits:: + You will now have several files named for the commits: + + .. code-block:: none 0001-BF-added-tests-for-Funny-bug.patch 0002-BF-added-fix-for-Funny-bug.patch Send these files to the `nipy mailing list`_. -When you are done, to switch back to the main copy of the code, just -return to the ``master`` branch:: +When you are done, to switch back to the main copy of the +code, just return to the ``master`` branch:: git checkout master Moving from patching to development =================================== -If you find you have done some patches, and you have one or more feature -branches, you will probably want to switch to development mode. You can -do this with the repository you have. +If you find you have done some patches, and you have one or +more feature branches, you will probably want to switch to +development mode. You can do this with the repository you +have. -Fork the nipy_ repository on github_ - :ref:`forking`. Then:: +Fork the `nipy`_ repository on github |emdash| :ref:`forking`. +Then:: # checkout and refresh master branch from main repo git checkout master @@ -118,6 +132,7 @@ Fork the nipy_ repository on github_ - :ref:`forking`. Then:: # push up any branches you've made and want to keep git push origin the-fix-im-thinking-of -Then you can, if you want, follow the :ref:`development-workflow`. +Then you can, if you want, follow the +:ref:`development-workflow`. -.. include:: git_links.inc +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/set_up_fork.rst b/doc/devel/guidelines/gitwash/set_up_fork.rst index 06cd8aa4c7..d75cccfa0e 100644 --- a/doc/devel/guidelines/gitwash/set_up_fork.rst +++ b/doc/devel/guidelines/gitwash/set_up_fork.rst @@ -1,10 +1,12 @@ +.. highlight:: bash + .. _set-up-fork: ================== Set up your fork ================== -First you follow the instructions for :ref:`forking`. +First you follow the instructions for :ref:`forking`. Overview ======== @@ -25,7 +27,9 @@ Clone your fork git@github.com:your-user-name/nipy.git`` #. Investigate. Change directory to your new repo: ``cd nipy``. Then ``git branch -a`` to show you all branches. You'll get something - like:: + like: + + .. code-block:: none * master remotes/origin/master @@ -33,7 +37,7 @@ Clone your fork This tells you that you are currently on the ``master`` branch, and that you also have a ``remote`` connection to ``origin/master``. What remote repository is ``remote/origin``? Try ``git remote -v`` to - see the URLs for the remote. They will point to your github_ fork. + see the URLs for the remote. They will point to your github fork. Now you want to connect to the upstream `nipy github`_ repository, so you can merge in changes from trunk. @@ -49,7 +53,7 @@ Linking your repository to the upstream repo git remote add upstream git://github.com/nipy/nipy.git ``upstream`` here is just the arbitrary name we're using to refer to the -main nipy_ repository at `nipy github`_. +main `nipy`_ repository at `nipy github`_. Note that we've used ``git://`` for the URL rather than ``git@``. The ``git://`` URL is read only. This means we that we can't accidentally @@ -57,12 +61,13 @@ Note that we've used ``git://`` for the URL rather than ``git@``. The use it to merge into our own code. Just for your own satisfaction, show yourself that you now have a new -'remote', with ``git remote -v show``, giving you something like:: +'remote', with ``git remote -v show``, giving you something like: + +.. code-block:: none upstream git://github.com/nipy/nipy.git (fetch) upstream git://github.com/nipy/nipy.git (push) origin git@github.com:your-user-name/nipy.git (fetch) origin git@github.com:your-user-name/nipy.git (push) -.. include:: git_links.inc - +.. include:: links.inc diff --git a/doc/devel/guidelines/gitwash/this_project.inc b/doc/devel/guidelines/gitwash/this_project.inc new file mode 100644 index 0000000000..cdb92e6aaa --- /dev/null +++ b/doc/devel/guidelines/gitwash/this_project.inc @@ -0,0 +1,3 @@ +.. nipy +.. _`nipy`: http://nipy.org/nipy +.. _`nipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging diff --git a/tools/gitwash_dumper.py b/tools/gitwash_dumper.py index 000245a920..93d4cbb021 100644 --- a/tools/gitwash_dumper.py +++ b/tools/gitwash_dumper.py @@ -1,6 +1,8 @@ #!/usr/bin/env python ''' Checkout gitwash repo into directory and do search replace on name ''' +from __future__ import (absolute_import, division, print_function) + import os from os.path import join as pjoin import shutil @@ -10,7 +12,7 @@ import fnmatch import tempfile from subprocess import call - +from optparse import OptionParser verbose = False @@ -54,37 +56,35 @@ def filename_search_replace(sr_pairs, filename, backup=False): ''' Search and replace for expressions in files ''' - in_txt = open(filename, 'rt').read(-1) + with open(filename, 'rt') as in_fh: + in_txt = in_fh.read(-1) out_txt = in_txt[:] for in_exp, out_exp in sr_pairs: in_exp = re.compile(in_exp) out_txt = in_exp.sub(out_exp, out_txt) if in_txt == out_txt: return False - open(filename, 'wt').write(out_txt) + with open(filename, 'wt') as out_fh: + out_fh.write(out_txt) if backup: - open(filename + '.bak', 'wt').write(in_txt) + with open(filename + '.bak', 'wt') as bak_fh: + bak_fh.write(in_txt) return True - + def copy_replace(replace_pairs, + repo_path, out_path, - repo_url, - repo_branch = 'master', cp_globs=('*',), rep_globs=('*',), renames = ()): - repo_path = clone_repo(repo_url, repo_branch) - try: - out_fnames = cp_files(repo_path, cp_globs, out_path) - finally: - shutil.rmtree(repo_path) + out_fnames = cp_files(repo_path, cp_globs, out_path) renames = [(re.compile(in_exp), out_exp) for in_exp, out_exp in renames] fnames = [] for rep_glob in rep_globs: fnames += fnmatch.filter(out_fnames, rep_glob) if verbose: - print '\n'.join(fnames) + print('\n'.join(fnames)) for fname in fnames: filename_search_replace(replace_pairs, fname, False) for in_exp, out_exp in renames: @@ -93,7 +93,70 @@ def copy_replace(replace_pairs, os.rename(fname, new_fname) break - + +def make_link_targets(proj_name, + user_name, + repo_name, + known_link_fname, + out_link_fname, + url=None, + ml_url=None): + """ Check and make link targets + + If url is None or ml_url is None, check if there are links present for these + in `known_link_fname`. If not, raise error. The check is: + + Look for a target `proj_name`. + Look for a target `proj_name` + ' mailing list' + + Also, look for a target `proj_name` + 'github'. If this exists, don't write + this target into the new file below. + + If we are writing any of the url, ml_url, or github address, then write new + file with these links, of form: + + .. _`proj_name` + .. _`proj_name`: url + .. _`proj_name` mailing list: url + """ + with open(known_link_fname, 'rt') as link_fh: + link_contents = link_fh.readlines() + have_url = not url is None + have_ml_url = not ml_url is None + have_gh_url = None + for line in link_contents: + if not have_url: + match = re.match(r'..\s+_`%s`:\s+' % proj_name, line) + if match: + have_url = True + if not have_ml_url: + match = re.match(r'..\s+_`%s mailing list`:\s+' % proj_name, line) + if match: + have_ml_url = True + if not have_gh_url: + match = re.match(r'..\s+_`%s github`:\s+' % proj_name, line) + if match: + have_gh_url = True + if not have_url or not have_ml_url: + raise RuntimeError('Need command line or known project ' + 'and / or mailing list URLs') + lines = [] + if not url is None: + lines.append('.. _`%s`: %s\n' % (proj_name, url)) + if not have_gh_url: + gh_url = 'https://github.com/%s/%s\n' % (user_name, repo_name) + lines.append('.. _`%s github`: %s\n' % (proj_name, gh_url)) + if not ml_url is None: + lines.append('.. _`%s mailing list`: %s\n' % (proj_name, ml_url)) + if len(lines) == 0: + # Nothing to do + return + # A neat little header line + lines = ['.. %s\n' % proj_name] + lines + with open(out_link_fname, 'wt') as out_links: + out_links.writelines(lines) + + USAGE = ''' If not set with options, the repository name is the same as the Date: Tue, 7 Mar 2017 15:29:41 -0800 Subject: [PATCH 07/18] MAINT: add back numpydoc to extensions Also - try to reduce number of complaints during API build. --- doc/conf.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 7329bfed1a..a164abcbdc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -40,10 +40,11 @@ extensions = [ 'texext.mathcode', 'sphinx.ext.autodoc', - 'texext.math_dollar', 'sphinx.ext.doctest', 'sphinx.ext.mathjax', 'sphinx.ext.autosummary', + 'texext.math_dollar', + 'numpydoc', 'sphinx.ext.inheritance_diagram', 'matplotlib.sphinxext.plot_directive', 'IPython.sphinxext.ipython_console_highlighting', @@ -216,3 +217,10 @@ # If false, no module index is generated. latex_use_modindex = True + +# Numpy extensions +# ---------------- +# Worked out by Steven Silvester in +# https://github.com/scikit-image/scikit-image/pull/1356 +numpydoc_show_class_members = False +numpydoc_class_members_toctree = False From 0e1dc9a5c16dcf52ed5495893be6628120d82442 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 15:30:17 -0800 Subject: [PATCH 08/18] DOC: silence some warnings about not-included docs Add some docs to hidden toctree to silence warnings. --- doc/index.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 770a4ba417..d78b72f523 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,6 +15,15 @@ high ideals of :ref:`nipy-mission`. .. We need the following toctree directive to include the documentation .. in the document heirarchy - see http://sphinx.pocoo.org/concepts.html .. toctree:: - :hidden: + :hidden: - documentation + documentation + devel/code_discussions/coordmap_notes + devel/doctests_preprocessor + devel/guidelines/compiling_windows + devel/images + devel/install/windows_scipy_build + faq/johns_bsd_pitch + references/brainpy_abstract + users/install_data + users/math_coordmap From b3fdd75263d239955cc7610d6da82235401d6b9e Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 15:56:19 -0800 Subject: [PATCH 09/18] MAINT: remove build target from Makefile Doc build already checks for presence of compiled nipy. --- Makefile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 0ab83842c8..68b3c2e4c9 100644 --- a/Makefile +++ b/Makefile @@ -43,9 +43,6 @@ dev: cythonize test: cd .. && $(PYTHON) -c 'import nipy; nipy.test()' -build: - $(PYTHON) setup.py build - install: $(PYTHON) setup.py install @@ -113,10 +110,10 @@ recythonize: $(WWW_DIR): if [ ! -d $(WWW_DIR) ]; then mkdir -p $(WWW_DIR); fi -htmldoc: build +htmldoc: cd $(DOCSRC_DIR) && PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(MAKE) html -pdfdoc: build +pdfdoc: cd $(DOCSRC_DIR) && PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(MAKE) latex cd $(LATEX_DIR) && $(MAKE) all-pdf From 6a495ae14101fa9272dccd560268f28e5add12f9 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 15:56:53 -0800 Subject: [PATCH 10/18] DOC: docstring fixes Make sections names standard, fix some parameter docs. --- nipy/algorithms/graph/field.py | 8 ++--- nipy/algorithms/graph/graph.py | 8 ++--- .../bayesian_structural_analysis.py | 4 +-- nipy/labs/spatial_models/discrete_domain.py | 34 +++++++++---------- nipy/labs/spatial_models/hroi.py | 20 +++++------ nipy/modalities/fmri/design_matrix.py | 21 ++++++------ nipy/modalities/fmri/experimental_paradigm.py | 5 ++- nipy/modalities/fmri/glm.py | 24 ++++++------- 8 files changed, 61 insertions(+), 63 deletions(-) diff --git a/nipy/algorithms/graph/field.py b/nipy/algorithms/graph/field.py index 815b956120..ba9acee326 100644 --- a/nipy/algorithms/graph/field.py +++ b/nipy/algorithms/graph/field.py @@ -158,8 +158,8 @@ def dilation(self, nbiter=1, fast=True): ---------- nbiter: int, optional, the number of iterations required - Note - ---- + Notes + ----- When data dtype is not float64, a slow version of the code is used """ nbiter = int(nbiter) @@ -435,9 +435,9 @@ def constrained_voronoi(self, seed): ------- label: The resulting labelling of the data - Fixme + Notes ----- - deal with graphs with several ccs + FIXME: deal with graphs with several ccs """ if np.size(self.field) == 0: raise ValueError('No field has been defined so far') diff --git a/nipy/algorithms/graph/graph.py b/nipy/algorithms/graph/graph.py index 60f4c3dc0d..9c85f61623 100644 --- a/nipy/algorithms/graph/graph.py +++ b/nipy/algorithms/graph/graph.py @@ -114,10 +114,10 @@ def __set_E(self, E): def set_edges(self, edges): """Sets the graph's edges - Preconditions - ------------- - - edges has a correct size - - edges take values in [1..V] + Preconditions: + + * edges has a correct size + * edges take values in [1..V] """ if (not isinstance(edges, None.__class__) and (edges.size != 0)): if ((np.shape(edges)[0] != self.E) or (np.shape(edges)[1] != 2)): diff --git a/nipy/labs/spatial_models/bayesian_structural_analysis.py b/nipy/labs/spatial_models/bayesian_structural_analysis.py index ed71a8c131..a94054364b 100644 --- a/nipy/labs/spatial_models/bayesian_structural_analysis.py +++ b/nipy/labs/spatial_models/bayesian_structural_analysis.py @@ -115,9 +115,9 @@ def _compute_individual_regions(domain, stats, threshold=3.0, smin=5, coords: array of shape (nr, coord.shape[1]), the coordinates of the of the terminal regions - Fixme + Notes ----- - Should allow for subject specific domains + FIXME: should allow for subject specific domains """ hrois = [] coords = [] diff --git a/nipy/labs/spatial_models/discrete_domain.py b/nipy/labs/spatial_models/discrete_domain.py index f6d95edf80..bfb29cd202 100644 --- a/nipy/labs/spatial_models/discrete_domain.py +++ b/nipy/labs/spatial_models/discrete_domain.py @@ -452,21 +452,20 @@ def __init__(self, dim, coord, local_volume, id='', referential=''): Parameters ---------- - dim: int, - the (physical) dimension of the domain - coord: array of shape(size, em_dim), - explicit coordinates of the domain sites - local_volume: array of shape(size), - yields the volume associated with each site - id: string, optional, - domain identifier - referential: string, optional, - identifier of the referential of the coordinates system - - Caveat - ------ - em_dim may be greater than dim e.g. (meshes coordinate in 3D) - + dim: int + the (physical) dimension of the domain. + coord: array of shape(size, em_dim) + explicit coordinates of the domain sites. + local_volume: array of shape(size) + yields the volume associated with each site. + id: string, optional + domain identifier. + referential: string, optional + identifier of the referential of the coordinates system. + + Notes + ----- + Caveat: em_dim may be greater than dim e.g. (meshes coordinate in 3D) """ # dimension self.dim = dim @@ -693,10 +692,9 @@ def __init__(self, dim, ijk, shape, affine, local_volume, topology, referential: string, optional, identifier of the referential of the coordinates system - Fixme + Notes ----- - local_volume might be computed on-the-fly as |det(affine)| - + FIXME: local_volume might be computed on-the-fly as |det(affine)| """ # shape if len(shape) != dim: diff --git a/nipy/labs/spatial_models/hroi.py b/nipy/labs/spatial_models/hroi.py index 47542af117..3465fb9dd0 100644 --- a/nipy/labs/spatial_models/hroi.py +++ b/nipy/labs/spatial_models/hroi.py @@ -350,12 +350,12 @@ def merge_ascending(self, id_list, pull_features=None): c_pos = self.select_id(c_id) p_pos = self.parents[c_pos] p_id = self.get_id()[p_pos] - + if p_pos != c_pos: # this will be used in many places mask_pos = np.ones(self.k, np.bool) mask_pos[c_pos] = False - + # set new parents self.parents = self.parents[mask_pos] self.parents[self.parents == c_pos] = p_pos @@ -395,26 +395,26 @@ def merge_descending(self, pull_features=None): methods indicates the way possible features are dealt with (not implemented yet) - Caveat - ------ - if roi_features have been defined, they will be removed + Notes + ----- + Caveat: if roi_features have been defined, they will be removed """ if pull_features is None: pull_features = [] if self.k == 0: return - + # relabel maps old labels to new labels relabel = np.arange(self.k) # merge nodes, one at a time id_list = self.get_id()[:: - 1] - + for p_id in id_list: p_pos = self.select_id(p_id) p_children = np.nonzero(self.parents == p_pos)[0] - + if p_pos in p_children: # remove current node from its children list p_children = p_children[p_children != p_pos] @@ -435,7 +435,7 @@ def merge_descending(self, pull_features=None): # merge labels relabel[relabel == p_pos] = relabel[c_pos] self.k -= 1 - + # compute new features for fid in list(self.features): # replace feature @@ -453,7 +453,7 @@ def merge_descending(self, pull_features=None): # modify only if `pull` requested dj[c_pos] = dj[p_pos] self.roi_features[fid] = dj[mask_pos] - + # update HROI structure self.label[self.label > -1] = relabel[self.label[self.label > - 1]] self.recompute_labels() diff --git a/nipy/modalities/fmri/design_matrix.py b/nipy/modalities/fmri/design_matrix.py index c5bd0bb069..7a7435b8ee 100644 --- a/nipy/modalities/fmri/design_matrix.py +++ b/nipy/modalities/fmri/design_matrix.py @@ -240,16 +240,17 @@ def _full_rank(X, cmax=1e15): class DesignMatrix(): """ This is a container for a light-weight class for design matrices - This class is only used to make IO and visualization - - Class members - ------------- - matrix: array of shape(n_scans, n_regressors), - the numerical specification of the matrix - names: list of len (n_regressors); - the names associated with the columns - frametimes: array of shape(n_scans), optional, - the occurrence time of the matrix rows + + This class is only used to make IO and visualization. + + Attributes + ---------- + matrix: array of shape (n_scans, n_regressors) + the numerical specification of the matrix. + names: list of len (n_regressors) + the names associated with the columns. + frametimes: array of shape (n_scans), optional + the occurrence time of the matrix rows. """ def __init__(self, matrix, names, frametimes=None): diff --git a/nipy/modalities/fmri/experimental_paradigm.py b/nipy/modalities/fmri/experimental_paradigm.py index 6eb0087ddc..353e63be9c 100644 --- a/nipy/modalities/fmri/experimental_paradigm.py +++ b/nipy/modalities/fmri/experimental_paradigm.py @@ -163,9 +163,8 @@ def load_paradigm_from_csv_file(path, session=None): plus possibly (duration) and/or (amplitude). If all the durations are 0, the paradigm will be handled as event-related. - Fixme - ----- - would be much clearer if amplitude was put before duration in the .csv + FIXME: would be much clearer if amplitude was put before duration in the + .csv """ import csv with open4csv(path, 'r') as csvfile: diff --git a/nipy/modalities/fmri/glm.py b/nipy/modalities/fmri/glm.py index 38c18e65ff..a6e3f0c241 100644 --- a/nipy/modalities/fmri/glm.py +++ b/nipy/modalities/fmri/glm.py @@ -342,13 +342,13 @@ def p_value(self, baseline=0.0): with the null hypothesis: (H0) 'contrast equals baseline' Parameters - ========== - baseline: float, optional, - Baseline value for the test statistic + ---------- + baseline: float, optional + Baseline value for the test statistic - Note - ==== - the value of 0.5 is used where the stat is not defined + Notes + ----- + The value of 0.5 is used where the stat is not defined """ if self.stat_ is None or not self.baseline == baseline: self.stat_ = self.stat(baseline) @@ -370,13 +370,13 @@ def z_score(self, baseline=0.0): with the null hypothesis: (H0) 'contrast equals baseline' Parameters - ========== - baseline: float, optional, - Baseline value for the test statistic + ---------- + baseline: float, optional + Baseline value for the test statistic - Note - ==== - the value of 0 is used where the stat is not defined + Notes + ----- + The value of 0 is used where the stat is not defined """ if self.p_value_ is None or not self.baseline == baseline: self.p_value_ = self.p_value(baseline) From 9c4dc21750964560a57d4f94bdc7dea8c19ff831 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 17:06:11 -0800 Subject: [PATCH 11/18] MAINT: refactor doc build to use installed nipy Makefile was using PYTHONPATH to pick up nipy source; we can use in-place build for that use - just pick up the installed nipy. Also - check in-place build. --- .travis.yml | 9 +++++++-- Makefile | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1417341882..e81da7cecf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -66,6 +66,9 @@ matrix: - python: 3.5 env: - INSTALL_TYPE=setup + - python: 3.5 + env: + - INSTALL_TYPE="pip_e" - python: 3.5 env: - DOC_BUILD=1 @@ -97,9 +100,11 @@ before_install: install: - | if [ "$INSTALL_TYPE" == "pip" ]; then - pip install . + pip install . + elif [ "$INSTALL_TYPE" == "pip_e" ]; then + pip install -e . elif [ "$INSTALL_TYPE" == "setup" ]; then - python setup.py install + python setup.py install elif [ "$INSTALL_TYPE" == "sdist" ]; then python setup_egg.py egg_info # check egg_info while we're here python setup_egg.py sdist diff --git a/Makefile b/Makefile index 68b3c2e4c9..fe9e4e7d02 100644 --- a/Makefile +++ b/Makefile @@ -111,10 +111,10 @@ $(WWW_DIR): if [ ! -d $(WWW_DIR) ]; then mkdir -p $(WWW_DIR); fi htmldoc: - cd $(DOCSRC_DIR) && PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(MAKE) html + cd $(DOCSRC_DIR) && $(MAKE) html pdfdoc: - cd $(DOCSRC_DIR) && PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(MAKE) latex + cd $(DOCSRC_DIR) && $(MAKE) latex cd $(LATEX_DIR) && $(MAKE) all-pdf html: html-stamp From a7ecae19d0d332bf295f2b9e25c77414c893b864 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 17:07:07 -0800 Subject: [PATCH 12/18] RF: enforce integer for np.random.normal size Numpy is more aggressive about insisting on integers for arguments like sizes of arrays (as here). --- doc/labs/plots/enn_demo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/labs/plots/enn_demo.py b/doc/labs/plots/enn_demo.py index 5a1e944348..3934bb90f2 100644 --- a/doc/labs/plots/enn_demo.py +++ b/doc/labs/plots/enn_demo.py @@ -4,8 +4,8 @@ from nipy.algorithms.statistics.empirical_pvalue import NormalEmpiricalNull -x = np.c_[np.random.normal(size=1e4), - np.random.normal(scale=4, size=1e4)] +x = np.c_[np.random.normal(size=10000), + np.random.normal(scale=4, size=10000)] enn = NormalEmpiricalNull(x) enn.threshold(verbose=True) From 24fceec757ce1c2c887e65096e9ead7429fde2c3 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 17:13:28 -0800 Subject: [PATCH 13/18] DOC: refactor docstrings from build warnings Attempt to fix some build warnings by clearing up the docstrings; not much success. --- nipy/pkg_info.py | 10 +++++----- nipy/testing/nosetester.py | 23 ++++++++++++----------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/nipy/pkg_info.py b/nipy/pkg_info.py index 75576c593c..f82e2bc2d6 100644 --- a/nipy/pkg_info.py +++ b/nipy/pkg_info.py @@ -12,19 +12,19 @@ def pkg_commit_hash(pkg_path): ''' Get short form of commit hash given directory `pkg_path` There should be a file called 'COMMIT_INFO.txt' in `pkg_path`. This is a - file in INI file format, with at least one section: ``commit hash``, and two - variables ``archive_subst_hash`` and ``install_hash``. The first has a + file in INI file format, with at least one section: ``commit hash``, and + two variables ``archive_subst_hash`` and ``install_hash``. The first has a substitution pattern in it which may have been filled by the execution of ``git archive`` if this is an archive generated that way. The second is filled in by the installation, if the installation is from a git archive. We get the commit hash from (in order of preference): - * A substituted value in ``archive_subst_hash`` - * A written commit hash value in ``install_hash` + * A substituted value in ``archive_subst_hash``; + * A written commit hash value in ``install_hash``; * git's output, if we are in a git repository - If all these fail, we return a not-found placeholder tuple + If all these fail, we return a not-found placeholder tuple. Parameters ------------- diff --git a/nipy/testing/nosetester.py b/nipy/testing/nosetester.py index fe5a4b4113..765dbba020 100644 --- a/nipy/testing/nosetester.py +++ b/nipy/testing/nosetester.py @@ -10,8 +10,8 @@ def fpw_opt_str(): """ Return first-package-wins option string for this version of nose - Versions of nose prior to 1.1.0 needed ``=True`` for ``first-package-wins``, - versions after won't accept it. + Versions of nose prior to 1.1.0 needed ``=True`` for + ``first-package-wins``, versions after won't accept it. changeset: 816:c344a4552d76 http://code.google.com/p/python-nose/issues/detail?id=293 @@ -39,8 +39,8 @@ def fpw_opt_str(): def prepare_imports(): """ Prepare any imports for testing run - At the moment, we prepare matplotlib by trying to make it use a backend that - does not need a display + At the moment, we prepare matplotlib by trying to make it use a backend + that does not need a display. """ try: import matplotlib as mpl @@ -53,9 +53,9 @@ def prepare_imports(): class NipyNoseTester(NoseTester): """ Numpy-like testing class - * Removes some numpy-specific excludes - * Disables numpy's fierce clearout of module import context for doctests - * Run doctests by default + * Removes some numpy-specific excludes; + * Disables numpy's fierce clearout of module import context for doctests; + * Run doctests by default. """ excludes = [] @@ -77,13 +77,14 @@ def test(self, label='fast', verbose=1, extra_argv=None, doctests=True, ---------- label : {'fast', 'full', '', attribute identifier}, optional Identifies the tests to run. This can be a string to pass to - directly the nosetests executable with the '-A' option (an attribute - identifier), or one of several special values. Special values are: + directly the nosetests executable with the '-A' option (an + attribute identifier), or one of several special values. Special + values are: * 'fast' - the default - which corresponds to the ``nosetests -A`` - option of 'not slow'. + option of 'not slow'; * 'full' - fast (as above) and slow tests as in the - 'no -A' option to nosetests - this is the same as ''. + 'no -A' option to nosetests - this is the same as ''; * None or '' - run all tests. verbose : int, optional From baf10b609580ab097b56791dd15499c84d12b80a Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 17:27:03 -0800 Subject: [PATCH 14/18] TST: add requirement for pdflatex for doc build Add tex distribution for pdf doc build. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e81da7cecf..60bdeb343c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,6 +76,7 @@ matrix: apt: packages: - graphviz + - texlive-latex-base before_install: - source tools/travis_tools.sh From caf7356a6efe693cac61f7158177de802b128c39 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 18:11:57 -0800 Subject: [PATCH 15/18] RF: refactor fix_longtable utility for codec Specify utf8 codec for tex file processing. --- tools/fix_longtable.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/fix_longtable.py b/tools/fix_longtable.py index 0c1e483272..ca36c3e728 100644 --- a/tools/fix_longtable.py +++ b/tools/fix_longtable.py @@ -3,6 +3,7 @@ """ import re import sys +import codecs lt_LL = re.compile( r"longtable}{(L+)}") @@ -16,8 +17,9 @@ def replacer(match): raise RuntimeError("Enter path to tex file only") file_path = sys.argv[1] -unfixed_tex = open(file_path,'rt').readlines() -write_file = open(file_path, 'wt') -for line in unfixed_tex: - line = lt_LL.sub(replacer, line, 1) - write_file.write(line) +with codecs.open(file_path, 'r', encoding='utf8') as fobj: + unfixed_tex = fobj.readlines() +with codecs.open(file_path, 'w', encoding='utf8') as fobj: + for line in unfixed_tex: + line = lt_LL.sub(replacer, line, 1) + fobj.write(line) From b3fbda91703fbe2f3bc7102e857ef4668ef2744b Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 7 Mar 2017 18:12:42 -0800 Subject: [PATCH 16/18] TST: add extra apt packages for doc build pdf doc build needs LaTeX packages. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 60bdeb343c..9470a3fe5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,6 +77,9 @@ matrix: packages: - graphviz - texlive-latex-base + - texlive-latex-extra + - texlive-fonts-recommended + - texlive-latex-recommended before_install: - source tools/travis_tools.sh From 3f058e4e9330ea00536c81d87f6a114a6666156a Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Mon, 13 Mar 2017 12:48:36 -0700 Subject: [PATCH 17/18] DOC: update installation instructions Installation instructions were horribly out of date. --- README.rst | 27 +--- doc/links_names.txt | 6 +- doc/users/installation.rst | 299 +++++++++++++++++++++---------------- nipy/info.py | 27 +--- 4 files changed, 192 insertions(+), 167 deletions(-) diff --git a/README.rst b/README.rst index 5f3db5a6b8..36a8e2aca8 100644 --- a/README.rst +++ b/README.rst @@ -92,23 +92,18 @@ directory of the nipy distribution:: is part of the nose package. Try ``nipnost --help`` to see a large number of command-line options. -Dependencies +Installation ============ -To run NIPY, you will need: +See the latest `installation instructions`_. -* python_ >= 2.6 (tested with 2.6, 2.7, 3.2 through 3.5) -* numpy_ >= 1.6.0 -* scipy_ >= 0.9.0 -* sympy_ >= 0.7.0 -* nibabel_ >= 1.2 - -You will probably also like to have: +License +======= -* ipython_ for interactive work -* matplotlib_ for 2D plotting -* mayavi_ for 3D plotting +We use the 3-clause BSD license; the full license is in the file ``LICENSE`` in +the nipy distribution. +.. links: .. _python: http://python.org .. _numpy: http://numpy.scipy.org .. _scipy: http://www.scipy.org @@ -116,12 +111,6 @@ You will probably also like to have: .. _nibabel: http://nipy.org/nibabel .. _ipython: http://ipython.org .. _matplotlib: http://matplotlib.org -.. _mayavi: http://code.enthought.com/projects/mayavi/ .. _nose: http://nose.readthedocs.org/en/latest .. _mock: https://pypi.python.org/pypi/mock - -License -======= - -We use the 3-clause BSD license; the full license is in the file ``LICENSE`` -in the nipy distribution. +.. _installation instructions: http://nipy.org/nipy/users/installation.html diff --git a/doc/links_names.txt b/doc/links_names.txt index d3aeb5ba07..1830d7ebd6 100644 --- a/doc/links_names.txt +++ b/doc/links_names.txt @@ -70,13 +70,15 @@ .. _pypi: http://pypi.python.org .. _example pypi: http://packages.python.org/an_example_pypi_project/setuptools.html#intermezzo-pypirc-file-and-gpg .. _github bdist_mpkg: https://github.com/matthew-brett/bdist_mpkg +.. _wheel: https://pypi.python.org/pypi/wheel +.. _install pip with get-pip.py: https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py .. Code support stuff .. _pychecker: http://pychecker.sourceforge.net/ .. _pylint: http://www.logilab.org/project/pylint .. _pyflakes: http://divmod.org/trac/wiki/DivmodPyflakes .. _virtualenv: http://pypi.python.org/pypi/virtualenv -.. _git: http://git.or.cz/ +.. _git: https://git-scm.com .. _flymake: http://flymake.sourceforge.net/ .. _rope: http://rope.sourceforge.net/ .. _pymacs: http://pymacs.progiciels-bpi.ca/pymacs.html @@ -107,7 +109,7 @@ .. _python (x, y): https://python-xy.github.io/ .. _EPD: http://www.enthought.com/products/epd.php .. _EPD free: http://www.enthought.com/products/epd_free.php -.. _Anaconda CE: https://store.continuum.io/ +.. _Anaconda: https://www.continuum.io/downloads .. _Unofficial Windows binaries: http://www.lfd.uci.edu/~gohlke/pythonlibs .. Python imaging projects diff --git a/doc/users/installation.rst b/doc/users/installation.rst index 193996ceb9..698dcb982e 100644 --- a/doc/users/installation.rst +++ b/doc/users/installation.rst @@ -4,191 +4,236 @@ Download and Install #################### -This page covers the necessary steps to install and run NIPY. Below is a list -of required dependencies, along with additional software recommendations. +******* +Summary +******* -************************ -Dependencies for install -************************ +* if you don't have it, install Python using the instructions below; +* if you don't have it, install Pip_ using the instructions below; +* if you don't have them, install NumPy_ >= 1.6 and Scipy >= 0.9 using the + instructions below; +* install Nipy with something like: -Must Have -========= + .. code-block:: bash -* Python_ 2.6 or later -* NumPy_ 1.6 or later: Numpy is an array library for Python -* SciPy_ 0.9 or later: Scipy contains scientific computing libraries based on - numpy -* Sympy_ 0.7.0 or later: Sympy is a symbolic mathematics library for Python. - We use it for statistical formulae. -* Nibabel_ 1.2.0 or later. Nibabel loads and saves images in neuroimaging - formats. + pip3 install --user nipy -Strong Recommendations -====================== +.. note:: -* IPython_: Interactive Python environment. -* Matplotlib_: python plotting library. + These instructions are for Python 3. If you are using Python 2.7, use + ``python2`` instead of ``python3`` and ``pip2`` instead of ``pip3``, for + the commands below. -Installing from binary packages -=============================== +******* +Details +******* -For Debian or Ubuntu --------------------- +Install Python, Pip, Numpy and Scipy +==================================== -Please use the NeuroDebian_ repository, and install with:: +First install Python 3, then install the Python package installer Pip. - sudo apt-get install python-nipy +Install Python 3 on Linux +------------------------- -This will install the dependencies for you. +We recommend: -For Fedora, CentOS ------------------- +* ``sudo apt-get install -y python3 python3-tk`` (Debian, Ubuntu); +* ``sudo dnf install -y python3 python3-tkinter`` (Fedora). -:: +These are the bare minimum installs. You will almost certainly want to +install the development tools for Python to allow you to compile other +Python packages: - sudo yum install numpy scipy sympy python-pip - sudo yum install python-devel gcc - sudo pip install nibabel - sudo pip install nipy +* ``sudo apt-get install -y python3-dev`` (Debian, Ubuntu); +* ``sudo dnf install -y python3-devel`` (Fedora). -Recommended:: +Now :ref:`install-pip`. - sudo yum install ipython +Install Python 3 on macOS +------------------------- -For OSX -^^^^^^^ +We recommend you install Python 3.5 or later using Homebrew +(http://brew.sh/): -We recommend you install Python either via the installers available via -https://python.org, or via Homebrew (http://brew.sh/). +.. code-block:: bash -Next install numpy, scipy and sympy:: + brew install python3 - pip install numpy scipy sympy +Homebrew is an excellent all-round package manager for macOS that you can use +to install many other free / open-source packages. -Finally, install nipy:: +Now :ref:`install-pip`. - pip install nipy +.. _install-pip: -For Windows -^^^^^^^^^^^ +Install Pip on Linux or macOS +----------------------------- -Option 1 -"""""""" +Pip can install packages into your main system directories (a *system* +install), or into your own user directories (a *user* install). We strongly +recommend *user* installs. -You can make your life much easier by using `Python (X, Y)`_. This will install -Python, Numpy, Scipy, IPython, Matplotlib, Sympy and many other useful things. +To get ready for user installs, put the user local install ``bin`` +directory on your user's executable program ``PATH``. First find the location +of the user ``bin`` directory with: -Then go to `nipy pypi`_ and download the ``.exe`` installer for nipy. Double click -to install. +.. code-block:: bash -Option 2 -"""""""" + python3 -c 'import site; print(site.USER_BASE + "/bin")' -* Download Python_ and install with the ``exe`` or ``msi`` installer -* Download and install the "Scipy stack" from Christophe Gohlke's `unofficial - windows binaries`_. -* If the nipy version on the `unofficial windows binaries`_ page is current, use - that, otherwise, go to `nipy pypi`_, download and install the ``exe`` - installer for nipy +This will give you a result like ``/home/your_username/.local/bin`` (Linux) or +``/Users/your_username/Library/Python/3.5/bin`` (macOS). -Option 3 -"""""""" +Use your favorite text editor to open the ``~/.bashrc`` file (Linux) or +``.bash_profile`` (macOSX) in your home directory. -Consider one of the big Python bundles such as `EPD free`_ or `Anaconda CE`_ for -the dependencies. Install nipy from the ``exe`` installer at `nipy pypi`_. +Add these lines to end of the file: -Option 4 -"""""""" +.. code-block:: bash -Do all the installs by hand: + # Put the path to the local bin directory into a variable + py3_local_bin=$(python3 -c 'import site; print(site.USER_BASE + "/bin")') + # Put the directory at the front of the system PATH + export PATH="$py3_local_bin:$PATH" -* Download Python_ and install with the ``exe`` or ``msi`` installer. Make sure - your python and the scripts directory (say, ``c:\Python27\Scripts``) are on - your windows path. -* Download Numpy and Scipy ``exe`` installers for your Python version from their - respective Numpy and Scipy download sites. -* Install pip_ using by following the instructions at : http://pip.readthedocs.org/en/stable/installing -* Install sympy and nibabel using pip from a window ``cmd`` shell:: +Save the file, and restart your terminal to load the configuration from your +``~/.bashrc`` (Linux) or ``~/.bash_profile`` (macOS) file. Confirm that you +have the user install directory in your PATH, with: - pip install sympy - pip install nibabel +.. code-block:: bash -* On 32-bit Windows, download and install the ``.exe`` installer from `nipy - pypi`_. For 64-bits install use the installer at the `unofficial windows - binaries`_ site. + echo $PATH -Otherwise -^^^^^^^^^ +Now install the Python package installer Pip into your user directories (see: +`install pip with get-pip.py`_): -I'm afraid you might need to build from source... +.. code-block:: bash -.. _building_source: + # Download the get-pip.py installer + curl -LO https://bootstrap.pypa.io/get-pip.py + # Execute the installer for Python 3 and a user install + python3 get-pip.py --user -************************* -Building from source code -************************* +Check you have the right version of the ``pip3`` command with: -Dependencies for build -====================== +.. code-block:: bash -* A C compiler: NIPY does contain a few C extensions for optimized routines. - Therefore, you must have a compiler to build from source. XCode_ (OSX) and - MinGW_ (Windows) both include a C compiler. On Linux, try ``sudo apt-get - build-essential`` on Debian / Ubuntu, ``sudo yum install gcc`` on Fedora and - related distributions. + which pip3 -Recommended for build -===================== +This should give you something like ``/home/your_username/.local/bin/pip3`` +(Linux) or ``/Users/your_username/Library/Python/3.5/bin`` (macOS). -* Cython_ 0.12.1 or later: Cython is a language that is a fusion of Python and - C. It allows us to write fast code using Python and C syntax, so that it - easier to read and maintain. You don't need it to build a release, unless you - modify the Cython ``*.pyx`` files in the nipy distribution. +Now :ref:`install-numpy-scipy`. -Procedure -========= +.. _install-numpy-scipy: -Developers should look through the -:ref:`development quickstart ` -documentation. There you will find information on building NIPY, the -required software packages and our developer guidelines. +Install Numpy and Scipy on Linux or macOS +----------------------------------------- -If you are primarily interested in using NIPY, download the source -tarball from `nipy pypi`_ and follow these instructions for building. The -installation process is similar to other Python packages so it will be familiar -if you have Python experience. +Now you've followed the instructions above, install Numpy and Scipy with: -Unpack the source tarball and change into the source directory. Once in the -source directory, you can build the neuroimaging package using:: +.. code-block:: bash - python setup.py build + pip3 install --user numpy scipy -To install, simply do:: +Install Python 3, Pip, NumPy and Scipy on Windows +------------------------------------------------- - sudo python setup.py install +It's worth saying here that very few scientific Python developers use Windows, +so if you're thinking of making the switch to Linux or macOS, now you have +another reason to do that. -.. note:: +That said, if you are installing on Windows, we recommend the Python 3 version +of `Anaconda`_. This is a large installer that will install many scientific +Python packages, as well as Python itself, and Pip, the package manager. + +The machinery for the Anaconda bundle is not completely open-source, and is +owned by a company, Continuum Analytics. If you would prefer to avoid using +the Anaconda installer: + +1. Download and install the Python 3 installer from the https://python.org website; +2. Download and install Pip following the instructions at `install pip with get-pip.py`; +3. Download and install the `Visual C++ redistributable packages for VC++ 2015 + `_; +4. Download the following packages from Christoph Gohlke's `unofficial Windows + binaries`_: + + * numpy (MKL version); + * scipy (MKL version); + + and install these packages with Pip. + +Install Nipy +============ + +Now you have Pip: + +.. code-block:: bash - As with any Python installation, this will install the modules in your - system Python *site-packages* directory (which is why you need *sudo*). - Many of us prefer to install development packages in a local directory so as - to leave the system python alone. This is merely a preference, nothing will - go wrong if you install using the *sudo* method. + pip3 install --user nipy - If you have Python 2.6 or later, you might want to do a `user install - `_ +On Windows, macOS, and nearly all Linux versions on Intel, this will install a +binary (Wheel_) package of NiPy. - python setup.py install --user +*************************** +Other packages we recommend +*************************** - To install nipy in some other local directory, use the **--prefix** option. - For example, if you created a ``local`` directory in your home directory, - you would install nipy like this:: +* IPython_: Interactive Python environment; +* Matplotlib_: Python plotting library. - python setup.py install --prefix=$HOME/local +******************************** +Building from latest source code +******************************** +Dependencies for build +====================== + +* A C compiler: Nipy does contain a few C extensions for optimized routines. + Therefore, you must have a compiler to build from source. Use XCode_ for + your C compiler on macOS. On Windows, you will need the Microsoft Visual + C++ version corresponding to your Python version - see `using MSCV with + Python `_. On + Linux you should have the packages you need after you install the + ``python3-dev`` (Debian / Ubuntu) or ``python3-devel`` (Fedora) packages + using the instructions above; +* Cython_ 0.12.1 or later: Cython is a language that is a fusion of Python + and C. It allows us to write fast code using Python and C syntax, so that + it is easier to read and maintain than C code with the same functionality; +* Git_ version control software: follow the instructions on the `main git + website `_ to install Git on Linux, macOS or Windows. + +Procedure +========= + +Please look through the :ref:`development quickstart ` +documentation. There you will find information on building NIPY, the required +software packages and our developer guidelines. Then: + +.. code-block:: bash + # install Cython + pip3 install --user cython + +.. code-block:: bash + + # Clone the project repository + git clone https://github.com/nipy/nipy + +to get the latest development version, and: + +.. code-block:: bash + + # Build the latest version in-place + cd nipy + pip3 install --user --editable . + +to install the code in the development tree into your Python path. + +**************************** Installing useful data files ------------------------------ +**************************** See :ref:`data-files` for some instructions on installing data packages. diff --git a/nipy/info.py b/nipy/info.py index d60eae008a..e93c00d09e 100644 --- a/nipy/info.py +++ b/nipy/info.py @@ -118,23 +118,18 @@ is part of the nose package. Try ``nipnost --help`` to see a large number of command-line options. -Dependencies +Installation ============ -To run NIPY, you will need: +See the latest `installation instructions`_. -* python_ >= 2.6 (tested with 2.6, 2.7, 3.2 through 3.5) -* numpy_ >= 1.6.0 -* scipy_ >= 0.9.0 -* sympy_ >= 0.7.0 -* nibabel_ >= 1.2 - -You will probably also like to have: +License +======= -* ipython_ for interactive work -* matplotlib_ for 2D plotting -* mayavi_ for 3D plotting +We use the 3-clause BSD license; the full license is in the file ``LICENSE`` in +the nipy distribution. +.. links: .. _python: http://python.org .. _numpy: http://numpy.scipy.org .. _scipy: http://www.scipy.org @@ -142,15 +137,9 @@ .. _nibabel: http://nipy.org/nibabel .. _ipython: http://ipython.org .. _matplotlib: http://matplotlib.org -.. _mayavi: http://code.enthought.com/projects/mayavi/ .. _nose: http://nose.readthedocs.org/en/latest .. _mock: https://pypi.python.org/pypi/mock - -License -======= - -We use the 3-clause BSD license; the full license is in the file ``LICENSE`` -in the nipy distribution. +.. _installation instructions: http://nipy.org/nipy/users/installation.html """ # minimum versions From 4fa797f08462d4c6ce39b8c1aed9f36ab016ad76 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 14 Mar 2017 22:45:00 -0700 Subject: [PATCH 18/18] DOC: fix typo in history page --- doc/history.rst | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/doc/history.rst b/doc/history.rst index f47bf0897c..d4740caa96 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -1,30 +1,28 @@ -=================== - A history of NIPY -=================== +################# +A history of NIPY +################# -Sometime around 2002, Jonthan Taylor started writing BrainSTAT, a -Python version of Keith Worsley's FmriSTAT package. +Sometime around 2002, Jonthan Taylor started writing BrainSTAT, a Python +version of Keith Worsley's FmriSTAT package. In 2004, Jarrod Millman and Matthew Brett decided that they wanted to -write a grant to build a new neuoimaging analysis package in Python. -Soon afterwards, they found that Jonathan had already started, and -merged efforts. At first we called this project *BrainPy*. Later we -changed the name to NIPY. +write a grant to build a new neuroimaging analysis package in Python. Soon +afterwards, they found that Jonathan had already started, and merged efforts. +At first we called this project *BrainPy*. Later we changed the name to NIPY. -In 2005, Jarrod, Matthew and Jonathan, along with Mark D'Esposito, -Fernando Perez, John Hunter, Jean-Baptiste Poline, and Tom Nichols, -submitted the first NIPY grant to the NIH. It was not successful. +In 2005, Jarrod, Matthew and Jonathan, along with Mark D'Esposito, Fernando +Perez, John Hunter, Jean-Baptiste Poline, and Tom Nichols, submitted the first +NIPY grant to the NIH. It was not successful. -In 2006, Jarrod and Mark submitted a second grant, based on the first. -The NIH gave us 3 years of funding for two programmers. We hired two -programmers in 2007 - Christopher Burns and Tom Waite - and began work on -refactoring the code. +In 2006, Jarrod and Mark submitted a second grant, based on the first. The +NIH gave us 3 years of funding for two programmers. We hired two programmers +in 2007 - Christopher Burns and Tom Waite - and began work on refactoring the +code. -Meanwhile, the team at Neurospin, Paris, started to refactor their FFF -code to work better with python and NIPY. This work was by Alexis -Roche, Bertrand Thirion, and Benjamin Thyreau, with some help and -advice from Fernando Perez. +Meanwhile, the team at Neurospin, Paris, started to refactor their FFF code to +work better with Python and NIPY. This work was by Alexis Roche, Bertrand +Thirion, and Benjamin Thyreau, with some help and advice from Fernando Perez. -In 2008, Fernando Perez and Matthew Brett started work full-time at -the UC Berkeley `Brain Imaging Center `_. -Matthew in particular came to work on NIPY. +In 2008, Fernando Perez and Matthew Brett started work full-time at the UC +Berkeley `Brain Imaging Center `_. Matthew in +particular came to work on NIPY.