Repository: sunpy/sunpy

Issue:
Constants __doc__ creation
We currently add a list of all constants into an rst table manually:

https://github.com/sunpy/sunpy/blob/master/sunpy/sun/constants.py#L92
 
This means if a name changes and it becomes wider than the column, it will break the documentation build. We need a way to construct the table that can account for this.  


Discussion:
(thinking outloud) could we just chuck them all in an astropy table, and then render the astropy table?
> (thinking outloud) could we just chuck them all in an astropy table, and then render the astropy table?

I tried that but failed but that would be my end goal. 
Can I give this one a shot? I can try out the astropy table approach first, and fall back to computing the column widths if that doesn't work.
Sure.

Resolving patch:
diff --git a/sunpy/sun/constants.py b/sunpy/sun/constants.py
--- a/sunpy/sun/constants.py
+++ b/sunpy/sun/constants.py
@@ -1,6 +1,8 @@
 """
 This module provides fundamental solar physical constants.
 """
+import io
+
 from astropy.table import Table
 from astropy.time import Time
 
@@ -91,19 +93,27 @@ def print_all():
     return t
 
 
-# Add a list of constants to the docs
-_lines = [
-    'The following constants are available:\n',
-    '======================================================= ============== ================ =======================================================',  # NOQA
-    '                          Name                              Value            Unit                                 Description                  ',  # NOQA
-    '======================================================= ============== ================ =======================================================',  # NOQA
-]
-for key, const in constants.items():
-    _lines.append('{:^55} {:^14.9g} {:^16} {:^55}'.format(
-        key, const.value, const._unit_string, const.name))
-_lines.append(_lines[1])
+def _build_docstring():
+    """Build docstring containing RST-formatted table of constants."""
+    lines = ['The following constants are available:\n']
+
+    rows = []
+    for key, const in constants.items():
+        rows.append([key, const.value, const._unit_string, const.name])
+
+    table = Table(rows=rows, names=('Name', 'Value', 'Unit', 'Description'))
+    table['Value'].info.format = '14.9g'
+
+    f = io.StringIO()
+    table.write(f, format='ascii.rst')
+    lines.append(f.getvalue())
+
+    return '\n'.join(lines)
+
+
+# Add a table of constants to the docs
 if __doc__ is not None:
-    __doc__ += '\n'.join(_lines)
+    __doc__ += _build_docstring()
 
 # Spectral class is not included in physical constants since it is not a number
 spectral_classification = 'G2V'


Test patch:
diff --git a/sunpy/sun/tests/test_constants.py b/sunpy/sun/tests/test_constants.py
--- a/sunpy/sun/tests/test_constants.py
+++ b/sunpy/sun/tests/test_constants.py
@@ -55,3 +55,25 @@ def test_find_function3(test_input):
     Test that the find function fails as expected.
     """
     assert len(con.find(test_input)) == 0
+
+
+def test_docstring():
+    """
+    Test that the docstring RST table has the correct number of constants.
+    """
+    lines = con.__doc__.split('\n')
+    description = 'The following constants are available:'
+    assert description in lines
+
+    # After the description line, there are five lines before the actual table
+    # data begins (including a newline, RST headers, and column names). Count
+    # the number of rows in the table until the RST column footer is reached.
+    data_start_idx = lines.index(description) + 5
+    data_end_idx = data_start_idx
+    for idx, line in enumerate(lines[data_start_idx:], data_start_idx):
+        if line.startswith('='):
+            data_end_idx = idx
+            break
+
+    num_rows = data_end_idx - data_start_idx
+    assert num_rows == 34