#!/usr/bin/python3

r'''Constructs README, README.org files

The README files are generated by this script. They are made from:

- The main module docstring, with some org markup applied to the README.org, but
  not to the README
- The docstrings from each API function in the module, with some org markup
  applied to the README.org, but not to the README
- README.footer.org, copied verbatim

The main module name must be passed in as the first cmdline argument.

If we want to regenerate the figures linked in the README.org documentation,
pass "DOCUMENTATION-PLOTS" as the first argument

'''

import sys
import os.path

def generate_plots():
    r'''Makes plots in the docstring of the gnuplotlib.py module'''

    import numpy      as np
    import gnuplotlib as gp

    x = np.arange(101) - 50
    gp.plot(x**2,
            hardcopy='basic-parabola-plot-pops-up.svg')

    g1 = gp.gnuplotlib(title = 'Parabola with error bars',
                       _with = 'xyerrorbars',
                       hardcopy = 'parabola-with-x-y-errobars-pops-up-in-a-new-window.svg')
    g1.plot( x**2 * 10, np.abs(x)/10, np.abs(x)*25,
             legend    = 'Parabola',
             tuplesize = 4 )

    x,y = np.ogrid[-10:11,-10:11]
    gp.plot( x**2 + y**2,
             title     = 'Heat map',
             unset     = 'grid',
             cmds      = 'set view map',
             square    = True,
             _with     = 'image',
             tuplesize = 3,
             hardcopy = 'Heat-map-pops-up-where-first-parabola-used-to-be.svg')

    theta = np.linspace(0, 6*np.pi, 200)
    z     = np.linspace(0, 5,       200)
    g2 = gp.gnuplotlib(_3d = True,
                       hardcopy = 'Two-3D-spirals-together-in-a-new-window.svg')
    g2.plot( np.cos(theta),
             np.vstack((np.sin(theta), -np.sin(theta))),
             z )

    x = np.arange(1000)
    gp.plot( (x*x, dict(histogram= True,
                        binwidth = 20000,
                        legend   = 'Frequency')),
             (x*x, dict(histogram='cumulative',
                        legend   = 'Cumulative',
                        y2       = True )),
             ylabel  = 'Histogram frequency',
             y2label = 'Cumulative sum',
             _set='key opaque',
             hardcopy = 'A-density-and-cumulative-histogram-of-x-2-are-plotted-on-the-same-plot.svg' )

    gp.plot( (x*x, dict(histogram=True,
                        binwidth =20000,
                        legend   = 'Frequency')),
             (x*x, dict(histogram='cumulative',
                        legend   = 'Cumulative')),
             _xmin=0, _xmax=1e6,
             multiplot='title "multiplot histograms" layout 2,1',
             _set=('lmargin at screen 0.05',
                   'key opaque'),
             hardcopy  = 'Same-histograms-but-plotted-on-two-separate-plots.svg')



try:
    arg1 = sys.argv[1]
except:
    raise Exception("Need main module name or 'DOCUMENTATION-PLOTS' as the first cmdline arg")

if arg1 == 'DOCUMENTATION-PLOTS':
    generate_plots()
    sys.exit(0)


modname = arg1
exec( 'import {} as mod'.format(modname) )

import inspect
import re
try:
    from StringIO import StringIO ## for Python 2
except ImportError:
    from io import StringIO ## for Python 3


def dirmod():
    r'''Returns all non-internal functions in a module

    Same as dir(mod), but returns only functions, in the order of definition.
    Anything starting with _ is skipped

    '''
    with open('{}.py'.format(modname), 'r') as f:
        for l in f:
            m = re.match(r'def +([a-zA-Z0-9][a-zA-Z0-9_]*)\(', l)
            if m:
                yield m.group(1)

with open('README.org', 'w') as f_target_org:
    with open('README', 'w') as f_target:

        f_target_org.write(r'''* TALK
I just gave a talk about this at [[https://www.socallinuxexpo.org/scale/18x][SCaLE 18x]]. Here are the [[https://www.youtube.com/watch?v=YOOapXNtUWw][video of the talk]] and
the [[https://github.com/dkogan/talk-numpysane-gnuplotlib/raw/master/numpysane-gnuplotlib.pdf]["slides"]].
''')

        def write(s, verbatim):
            r'''Writes the given string to README and README.org

            if verbatim: we simply write the string, and call it good

            Otherwise, we massage the string slightly for org:

            - we look for indented blocks (signifying examples), and wrap them
            in a #+BEGIN_SRC or #+BEGIN_EXAMPLE.

            - we find links, and add markup to make them valid org links

            '''

            if verbatim:
                f_target.    write(s)
                f_target_org.write(s)
                return


            # the non-org version is written as is
            f_target.write(s)

            # the org version neeeds massaging
            f = f_target_org

            in_quote = None # can be None or 'example' or 'src'
            queued_blanks = 0
            indent_size = 4

            prev_indented = False

            sio = StringIO(s)
            for l in sio:

                # if we have a figure made with DOCUMENTATION-PLOTS, place it
                m = re.match(r'^    \[ (.*) \]$', l)
                if m is not None:
                    tag = m.group(1)
                    tag = re.sub(r'[^a-zA-Z0-9_]+','-', tag)
                    plot_filename = f"{tag}.svg"
                    if os.path.isfile(plot_filename):
                        if in_quote is not None:
                            if in_quote == 'example': f.write('#+END_EXAMPLE\n')
                            else:                     f.write('#+END_SRC\n')
                        f.write(f"[[file:{plot_filename}]]\n")
                        if in_quote is not None:
                            if in_quote == 'example': f.write('#+BEGIN_EXAMPLE\n')
                            else:                     f.write('#+BEGIN_SRC python\n')
                        continue

                # handle links
                l = re.sub( r"([^ ]+) *\((https?://[^ ]+)\)",
                            r"[[\2][\1]]",
                            l)

                if in_quote is None:
                    if len(l) <= 1:
                        # blank line
                        f.write(l)
                        continue

                    if not re.match(' '*indent_size, l):
                        # don't have full indent. not quote start
                        prev_indented = re.match(' ', l)
                        f.write(l)
                        continue

                    if re.match(' '*indent_size + '-', l):
                        # Start of indented list. not quote start
                        prev_indented = re.match(' ', l)
                        f.write(l)
                        continue

                    if prev_indented:
                        # prev line(s) were indented, so this can't start a quote
                        f.write(l)
                        continue

                    # start of quote. What kind?
                    if re.match('    >>>', l):
                        in_quote = 'example'
                        f.write('#+BEGIN_EXAMPLE\n')
                    else:
                        in_quote = 'src'
                        f.write('#+BEGIN_SRC python\n')

                    f.write(l[indent_size:])
                    continue

                # we're in a quote. Skip blank lines for now
                if len(l) <= 1:
                    queued_blanks = queued_blanks+1
                    continue

                if re.match(' '*indent_size, l):
                    # still in quote. Write it out
                    f.write( '\n'*queued_blanks)
                    queued_blanks = 0
                    f.write(l[indent_size:])
                    continue

                # not in quote anymore
                if in_quote == 'example': f.write('#+END_EXAMPLE\n')
                else:                     f.write('#+END_SRC\n')
                f.write( '\n'*queued_blanks)
                f.write(l)
                queued_blanks = 0
                in_quote = None
                prev_indented = False

            f.write('\n')
            if   in_quote == 'example': f.write('#+END_EXAMPLE\n')
            elif in_quote == 'src':     f.write('#+END_SRC\n')




        header = '* NAME\n{}: '.format(modname)
        write( header, verbatim=True )






        write(inspect.getdoc(mod), verbatim=False)
        write( '\n', verbatim=True )

        # extract the global function docstrings. I'm doing that for the global
        # functions, but not for the class or methods because the methods have
        # very little of their own documentation
        write('* GLOBAL FUNCTIONS\n', verbatim=True)

        for func in dirmod():
            if not inspect.isfunction(mod.__dict__[func]):
                continue

            doc = inspect.getdoc(mod.__dict__[func])
            if doc:
                write('** {}()\n'.format(func), verbatim=True)
                write( doc, verbatim=False )
                write( '\n', verbatim=True )

        with open('README.footer.org', 'r') as f_footer:
            write( f_footer.read(), verbatim=True )
