..  #!/usr/bin/env python3

..  _`build`:

#########################
Stingray Build
#########################

The source for :py:mod:`stingray` is a Sphinx project that depends on PyLit.
Yes.  The documentation spawns the code.

..  note:: PyLit Feature

    The first line of each file should be ``..    #!/usr/bin/env python3``.
   
    The four spaces between ``..`` and ``#!`` defines the indent used for
    each line of code in the file.  
   
    Four spaces.

Stingray depends on the following

-   xlrd.  http://www.lexicon.net/sjmachin/xlrd.htm

    Version 0.9.2 is Python 3 compatible. https://pypi.python.org/pypi/xlrd/0.9.2

In addition to Python 3.3, there are two other projects used to build.

-   PyLit.  https://github.com/slott56/PyLit-3

-   Sphinx.  http://sphinx.pocoo.org/

The PyLit install is little more than a download and move the :file:`pylit.py` file to
the Python :file:`site-packages` directory.

Sphinx and XLRD should be
installed with `easy_install <http://peak.telecommunity.com/DevCenter/EasyInstall>`_.

..  code-block:: bash

    easy_install xlrd
    easy_install sphinx
   
In the case of having Python2 and Python3 installed, ``easy_install-3.3`` may be required.
On most systems, ``sudo`` is also required.

The diagrams are done with YUML. See http://yuml.me.

Build Procedure
==================

1.  Bootstrap the :file:`build.py` script by running PyLit.

    ..  code-block:: bash
   
        python3 -m pylit -t source/build.rst build.py

    This reports that an extract was written to :file:`build.py`.
   
2.  Use the :file:`build.py` script to create the ``stingray`` source, unit
    tests, demonstration applications.  
    Build the Sphinx documentation.  
    And run the unit test, too.

    ..  code-block:: bash
   
        python3 build.py
       
    At the end of this step, the directory tree will include the following.
   
    -   :file:`build`.  The documentation.  In HTML.
    -   :file:`demo`.   Some demonstration applications that use ``stingray``. 
        See :ref:`demo`.
    -   :file:`stingray`.  The Python library, ready for installation.
    -   :file:`test`.  The unit test script.

    This reports, also, that 174 tests were run.
   
In general (i.e., any OS except Windows), it's sensible to do this:

..    code-block:: bash

    chmod +x build.py

This allows us to use the following for a rebuild:

..    code-block:: bash

    ./build.py 
   
   
Build Script Design
=====================

This is a platform-independent :file:`build.py` file for the build script.
This can use ``from sphinx.application import Sphinx``
and ``import pylit`` to access these modules from within Python
instead of using command-line shell script techniques.

Overheads
-------------

We're going to make use of three "applications".

-   Sphinx top-level application.

-   PyLit top-level application.

-   Unittest top-level test runner.

::

  """Platform-independent build script"""
  from __future__ import print_function
  import os
  import sys
  import errno
  from sphinx.application import Sphinx
  import pylit
  import unittest
  import logging
  import shutil

Sphinx Build
---------------

..  py:function:: sphinx_build( srcdir, outdir, buildername='html' )

    Handle the simple use case for the ``sphinx-build`` script.

::

  def sphinx_build( srcdir, outdir, buildername='html' ):
      """Essentially: ``sphinx-build $* -b html source build/html``"""
      confdir= srcdir= os.path.abspath( srcdir )
      outdir= os.path.abspath( outdir )
      doctreedir = os.path.join(outdir, '.doctrees')
      app = Sphinx(srcdir, confdir, outdir, doctreedir, buildername)
      app.build(force_all=False, filenames=[])
      return app.statuscode

PyLit Build
---------------

..  py:function:: pylit_build( srcdir, outdir )

    Handle the simple use case for PyLit.

    This also handles the necessary rewrite to modify standard paths to Windows paths.

::

  def pylit_build( infile, outfile ):
      """Essentially: ``python3 -m pylit -t source/demo/data_quality.rst demo/test.py``
    
      The issue here is that we need to provide platform-specific paths.
      """
      if os.sep != '/':
          # Fix windows paths.
          infile= os.path.join( *infile.split('/') )
          outfile= os.path.join( *outfile.split('/') )
      pylit.main( txt2code= True, overwrite="yes", infile= infile, outfile= outfile )

Make Directories
-------------------

..  py:function:: mkdir( path )

    Handles the simple use case for assuring that the directory
    tree exists.

    This also handles a rewrite to modify standard paths to Windows paths.

::

  def mkdir( path ):
      if os.sep != '/':
          # Fix windows paths.
          path= os.path.join( *path.split('/') )
      try:
          os.makedirs( path )
      except OSError as e:
          if e.errno == errno.EEXIST: 
              pass
          else:
              raise

Copy Data File
---------------

..  py:function:: copy_file( srcdir, outdir )

    Handles the simple use case for copying a file.

::

  def copy_file( srcdir, outdir ):
      """Essentially: ``cp srcdir outdir``"""
      shutil.copy2( srcdir, outdir )

Run the Test Script
-----------------------

..  py:function:: run_test( )

    In effect, this does ``python3 test/main.py``

::

  def run_test():
      import test.main 
      result= test.main.main()
      if result.failures:
          sys.exit(result.failures)

The Build Sequence
---------------------

::
    
  def build():
      mkdir( 'stingray/schema' )
      mkdir( 'stingray/cobol' )
      mkdir( 'stingray/workbook' )
    
      pylit_build( 'source/stingray_init.rst', 'stingray/__init__.py' )
      pylit_build( 'source/cell.rst', 'stingray/cell.py' )
      pylit_build( 'source/sheet.rst', 'stingray/sheet.py' )
      pylit_build( 'source/workbook/init.rst', 'stingray/workbook/__init__.py' )
      pylit_build( 'source/workbook/base.rst', 'stingray/workbook/base.py' )
      pylit_build( 'source/workbook/csv.rst', 'stingray/workbook/csv.py' )
      pylit_build( 'source/workbook/xls.rst', 'stingray/workbook/xls.py' )
      pylit_build( 'source/workbook/xlsx.rst', 'stingray/workbook/xlsx.py' )
      pylit_build( 'source/workbook/ods.rst', 'stingray/workbook/ods.py' )
      pylit_build( 'source/workbook/numbers_09.rst', 'stingray/workbook/numbers_09.py' )
      pylit_build( 'source/workbook/numbers_13.rst', 'stingray/workbook/numbers_13.py' )
      pylit_build( 'source/workbook/fixed.rst', 'stingray/workbook/fixed.py' )
      pylit_build( 'source/schema.rst', 'stingray/schema/__init__.py' )
      pylit_build( 'source/schema_loader.rst', 'stingray/schema/loader.py' )
      pylit_build( 'source/cobol_init.rst', 'stingray/cobol/__init__.py' )
      pylit_build( 'source/cobol_loader.rst', 'stingray/cobol/loader.py' )
      pylit_build( 'source/cobol_defs.rst', 'stingray/cobol/defs.py' )
      pylit_build( 'source/snappy.rst', 'stingray/snappy.py' )
      pylit_build( 'source/protobuf.rst', 'stingray/protobuf.py' )
      pylit_build( 'source/installation.rst', 'setup.py' )
    
      copy_file( 'source/Numbers.json', 'stingray/Numbers.json' )
      copy_file( 'source/Common.json', 'stingray/Common.json' )
    
      mkdir( 'test' )
        
      pylit_build( 'source/testing/test_init.rst', 'test/__init__.py' )
      pylit_build( 'source/testing/main.rst', 'test/main.py' )
      pylit_build( 'source/testing/cell.rst', 'test/cell.py' )
      pylit_build( 'source/testing/sheet.rst', 'test/sheet.py' )
      pylit_build( 'source/testing/schema.rst', 'test/schema.py' )
      pylit_build( 'source/testing/schema_loader.rst', 'test/schema_loader.py' )
      pylit_build( 'source/testing/workbook.rst', 'test/workbook.py' )
      pylit_build( 'source/testing/cobol.rst', 'test/cobol.py' )
      pylit_build( 'source/testing/cobol_loader.rst', 'test/cobol_loader.py' )
      pylit_build( 'source/testing/cobol_2.rst', 'test/cobol_2.py' )
      pylit_build( 'source/testing/snappy_protobuf.rst', 'test/snappy_protobuf.py' )
    
      mkdir( 'demo' )
        
      pylit_build( 'source/demo/data_quality.rst', 'demo/test.py' )
      pylit_build( 'source/demo/validation.rst', 'demo/app.py' )
      pylit_build( 'source/demo/profile.rst', 'demo/profile.py' )
      pylit_build( 'source/demo/cobol_reader.rst', 'demo/cobol_reader.py' )
    
      run_test()

      sphinx_build( 'source', 'build/html', 'html' )
      sphinx_build( 'source', 'build/latex', 'latex' )

Main Program Switch
---------------------

When the :file:`build.py` script is run from the command line,
it will execute the :py:func:`build` function.  When it is imported,
however, it will do nothing special.

::

  if __name__ == "__main__":
      build()
    
Additional Builds
=====================

Sometimes it's desriable to refresh the documentation.

The HTML pages are built with this command.

..  code-block:: bash

    sphinx-build $* -b html source build/html
   
The LaTeX document is built with this command.

..  code-block:: bash

    sphinx-build $* -b latex source build/latex