Example #1
0
 def __init__(self, parent=None, base_dir=None, short_name=None):
     # Save parent #
     self.parent = parent
     # If we got a file #
     if isinstance(base_dir, FilePath):
         self.base_dir = base_dir.directory
         short_name    = base_dir.short_prefix
     # If no parent and no directory #
     if base_dir is None and parent is None:
         file_name     = os.path.abspath((inspect.stack()[1])[1])
         self.base_dir = os.path.dirname(os.path.abspath(file_name)) + '/'
         self.base_dir = Path(self.base_dir)
     # If no directory but a parent is present #
     if base_dir is None:
         if hasattr(self.parent, 'p'):
             self.base_dir = self.parent.p.graphs_dir
         if hasattr(self.parent, 'paths'):
             self.base_dir = self.parent.paths.graphs_dir
     else:
         self.base_dir = Path(self.base_dir)
     self.base_dir.create_if_not_exists()
     # Short name #
     if short_name: self.short_name = short_name
     # Use the base class name #
     if not hasattr(self, 'short_name'):
         self.short_name = camel_to_snake(self.__class__.__name__)
Example #2
0
 def __init__(self, source_path, dest_path, **kwargs):
     # Record attributes #
     self.source = Path(source_path)
     self.dest = Path(dest_path)
     # Keep the kwargs too #
     self.kwargs = kwargs
     # Check directory case #
     if self.dest.endswith('/'):
         self.dest = self.dest + self.source.filename
         self.dest = self.dest.replace_extension('csv')
Example #3
0
 def __init__(self,
              path=None,
              caption=None,
              label=None,
              table=None,
              **kwargs):
     # Check inputs #
     if path is None and table is None:
         raise Exception("You need to specify a table or a path.")
     # Path #
     if path is not None: self.path = Path(path)
     else: self.path = table.path
     # Caption #
     if caption is not None: self.caption = caption
     else: self.caption = ''
     if table is not None and hasattr(table, 'caption'):
         self.caption = table.caption
     # Label #
     if label is not None: self.label = r"\label{" + label + "}\n"
     elif table is not None:
         self.label = r"\label{" + table.short_name + "}\n"
     else:
         self.label = ''
     # Graph #
     if table is not None: self.table = table
     # Keyword arguments #
     self.kwargs = kwargs
     # Call the graph if it's not generated #
     if table is not None and not table: table.save()
     # Check the file was created #
     if not self.path.exists:
         raise Exception("No file at '%s'." % self.path)
Example #4
0
 def __init__(self, continent):
     # Save parent #
     self.continent = continent
     # The combos dir used for all output #
     self.combos_dir = self.continent.combos_dir
     # The base dir for our output #
     self.base_dir = Path(self.combos_dir + self.short_name + '/')
Example #5
0
class ConvertExcelToCSV:
    """
    Will convert an excel file into its CSV equivalent.
    Can support multiple work sheets into a single CSV.
    """
    def __init__(self, source_path, dest_path, **kwargs):
        # Record attributes #
        self.source = Path(source_path)
        self.dest = Path(dest_path)
        # Keep the kwargs too #
        self.kwargs = kwargs
        # Check directory case #
        if self.dest.endswith('/'):
            self.dest = self.dest + self.source.filename
            self.dest = self.dest.replace_extension('csv')

    def __call__(self):
        """Are we mono or multi sheet?"""
        if len(self.handle.sheet_names) > 1: self.multi_sheet()
        else: self.mono_sheet()

    @property_cached
    def handle(self):
        """Pandas handle to the excel file."""
        return pandas.ExcelFile(str(self.source))

    def mono_sheet(self):
        """Supports only one work sheet per file."""
        xls = pandas.read_excel(str(self.source))
        xls.to_csv(str(self.dest), **self.kwargs)

    def multi_sheet(self):
        """
        Supports multiple work sheets per file.
        Will concatenate sheets together by adding an extra column
        containing the original sheet name.
        """
        # Initialize #
        all_sheets = []
        # Loop #
        for name in self.handle.sheet_names:
            sheet = self.handle.parse(name)
            sheet.insert(0, "nace", name)
            all_sheets.append(sheet)
        # Write #
        df = pandas.concat(all_sheets)
        df.to_csv(str(self.dest), **self.kwargs)
Example #6
0
 def __init__(self, input_path, output_path=None, builtin_template=None):
     # Input #
     self.input_path = Path(input_path)
     # Output #
     if output_path is None: self.output_path = self.default_output_name
     else:                   self.output_path = output_path
     # Template #
     self.builtin_template = builtin_template if builtin_template else 'sinclair_bio'
Example #7
0
 def __init__(self, parent=None, base_dir=None, short_name=None):
     # Save parent if it was given #
     self.parent = parent
     # If we got a file #
     if isinstance(base_dir, FilePath):
         self.base_dir = base_dir.directory
         short_name    = base_dir.short_prefix
     else:
         self.base_dir = Path(base_dir)
     # Short name #
     if short_name: self.short_name = short_name
     # Use the parents name or the base class name #
     if not hasattr(self, 'short_name'):
         if hasattr(self.parent, 'short_name'):
             self.short_name = self.parent.short_name
         else:
             self.short_name = camel_to_snake(self.__class__.__name__)
Example #8
0
def handle_destination(url, destination):
    """
    The destination can be either unspecified or can contain either a file path
    or a directory path.
    """
    # Choose a default for destination #
    if destination is None:
        destination = autopaths.tmp_path.new_temp_file()
    # Directory case - choose a filename #
    elif destination.endswith('/'):
        filename = url.split("/")[-1].split("?")[0]
        destination = Path(destination + filename)
        destination.directory.create_if_not_exists()
    # Normal case #
    else:
        destination = Path(destination)
        destination.directory.create_if_not_exists()
    # Return #
    return destination
 def link_to(self, path, safe=False, absolute=False):
     """
     Create a link somewhere else pointing to this file.
     The destination is hence *path* and the source is self.path.
     """
     # Get source and destination #
     from autopaths import Path
     source      = self.path
     destination = Path(path)
     # Call method #
     self._symlink(source, destination, safe, absolute)
 def link_from(self, path, safe=False, absolute=False):
     """
     Make a link here pointing to another file/directory somewhere else.
     The destination is hence self.path and the source is *path*.
     """
     # Get source and destination #
     from autopaths import Path
     source      = Path(path)
     destination = self.path
     # Call method #
     self._symlink(source, destination, safe, absolute)
 def get_pickle_path(self, instance):
     # First check if an `at` parameter was specified #
     if self.at is not None:
         path = Path(getattr(instance, self.at))
         # Secondly check if the instance has a cache_dir specified #
     elif 'cache_dir' in instance.__dict__:
         path = Path(instance.cache_dir + self.name + '.pickle')
     # Otherwise we go the default route (no instance passed) #
     else:
         path = Path(self.get_default_path())
     # Make the directory #
     path.make_directory()
     return path
Example #12
0
 def __init__(self, path_one, path_two, caption_one, caption_two, label_one,
              label_two, caption_main, label_main):
     # Both paths #
     self.path_one, self.path_two = Path(path_one), Path(path_two)
     # Both subcaptions #
     self.caption_one = self.escape_underscore(caption_one)
     self.caption_two = self.escape_underscore(caption_two)
     # The main caption #
     self.caption_main = self.escape_underscore(caption_main)
     # Both reference labels #
     self.label_one = r"\label{" + label_one + "}\n" if label_one is not None else ''
     self.label_two = r"\label{" + label_two + "}\n" if label_two is not None else ''
     # The main reference label #
     self.label_main = r"\label{" + label_main + "}\n" if label_main is not None else ''
     # Check existance #
     if not self.path_one.exists or not self.path_two.exists:
         raise Exception("File missing.")
     # Check extension #
     if self.path_one.filename.count(
             '.') > 1 or self.path_two.filename.count('.') > 1:
         raise Exception(
             "Can't have several extension in a LaTeX figure file name.")
Example #13
0
 def get_one_xls(self, row):
     """Download one xls file and put it in the cache directory."""
     # We are not interested in all countries #
     if row['country'] not in country_codes['country'].values: return
     # Get the matching iso2_code #
     iso2_code = country_codes.query("country == '%s'" % row['country'])
     iso2_code = iso2_code['iso2_code'].iloc[0]
     # The destination directory #
     destination = Path(self.cache_dir + iso2_code + '.xls')
     # Save to disk #
     result = download_from_url(row['xls'], destination, user_agent=None)
     # We don't want to flood the server #
     time.sleep(2)
     # Return #
     return result
Example #14
0
 def get_one_zip(self, row):
     """Download one zip file and put it in the right directory."""
     # We are not interested in all countries #
     if row['country'] not in country_codes['country'].values: return
     # Get the matching iso2_code #
     iso2_code = country_codes.query("country == '%s'" % row['country'])
     iso2_code = iso2_code['iso2_code'].iloc[0]
     # The destination directory #
     destination = Path(self.cache_dir + iso2_code + '/')
     # Save to disk #
     result = download_via_browser(row['zip'], destination, uncompress=False)
     # Check if we were blocked #
     check_blocked_request(result)
     # We don't want to flood the server #
     time.sleep(2)
def make_driver():
    """
    Create a headless webdriver with selenium.

    Note: #TODO Change the download dir to a temp dir #
    Otherwise it will append (1) and (2) etc. to the filename before the
    extension e.g. 'data.zip (1).crdownload'
    """
    # Paths #
    chrome_driver_path = shutil.which('chromedriver')
    # Start a service #
    service = webdriver.chrome.service.Service(chrome_driver_path)
    service.start()
    # Add options #
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    options = options.to_capabilities()
    # Create the driver #
    driver = webdriver.Remote(service.service_url, options)
    # #TODO Change the download dir to a temp dir #
    dl_dir = Path(os.getcwd() + '/')
    # Return #
    return driver, dl_dir
Example #16
0
 def __init__(self,
              path=None,
              caption=None,
              label=None,
              graph=None,
              **kwargs):
     # Check inputs #
     if path is None and graph is None:
         raise Exception("You need to specify a graph or a path.")
     # Path #
     if path is not None: self.path = Path(path)
     else: self.path = graph.path
     # Caption #
     if caption is not None: self.caption = caption
     else: self.caption = ''
     if graph is not None and hasattr(graph, 'caption'):
         self.caption = graph.caption
     # Label #
     if label is not None: self.label = r"\label{" + label + "}\n"
     elif graph is not None:
         self.label = r"\label{" + graph.short_name + "}\n"
     else:
         self.label = ''
     # Graph #
     if graph is not None: self.graph = graph
     # Keyword arguments #
     self.kwargs = kwargs
     # Call the graph if it's not generated #
     if graph is not None and not graph: graph()
     # Check the file was created #
     if not self.path.exists:
         raise Exception("No file at '%s'." % self.path)
     # Check unique extension #
     if self.path.filename.count('.') > 1:
         raise Exception(
             "Can't have several extensions in a LaTeX figure file name.")
Example #17
0
class Table:
    """
    A table with different headers and values that is destined to be
    exported both to CSV and to TeX format, for instance for display in
    publications or manuscripts.
    """

    # Default options #
    formats      = ('csv', 'tex')
    index        = True
    bold_rows    = True
    na_rep       = '-'

    # Float formatting #
    float_format_csv = '%g'
    float_format_tex = '%g'

    # Extra formatting #
    capital_index = True
    upper_columns = False
    column_format = None
    escape_tex    = True

    def __init__(self, parent=None, base_dir=None, short_name=None):
        # Save parent if it was given #
        self.parent = parent
        # If we got a file #
        if isinstance(base_dir, FilePath):
            self.base_dir = base_dir.directory
            short_name    = base_dir.short_prefix
        else:
            self.base_dir = Path(base_dir)
        # Short name #
        if short_name: self.short_name = short_name
        # Use the parents name or the base class name #
        if not hasattr(self, 'short_name'):
            if hasattr(self.parent, 'short_name'):
                self.short_name = self.parent.short_name
            else:
                self.short_name = camel_to_snake(self.__class__.__name__)

    @property_cached
    def path(self):
        return Path(self.base_dir + self.short_name + '.tex')

    @property
    def csv_path(self): return self.path.replace_extension('csv')

    #-------------------------------- Other ----------------------------------#
    def split_thousands(self, number):
        """This method will determine how numbers are displayed in the table."""
        # Case is NaN #
        if numpy.isnan(number): return self.na_rep
        # Round #
        number = int(round(number))
        # Format #
        from plumbing.common import split_thousands
        number = split_thousands(number)
        # Return #
        return number

    def make_percent(self, row):
        # Remember where the NaNs are located #
        nans = row.isna()
        # Multiply for percentage #
        row *= 100
        # Express difference as a percentage #
        row = row.apply(lambda f: "%.1f%%" % f)
        # Restore NaNs #
        row[nans] = self.na_rep
        # Return #
        return row

    #--------------------------------- Save ----------------------------------#
    def save(self, **kw):
        # Load #
        df = self.df.copy()
        # Modify the index name#
        if self.capital_index and df.index.name is not None:
            df.index.name = df.index.name.capitalize()
        # Modify column names #
        if self.upper_columns: df.columns = df.columns.str.upper()
        # Possibility to overwrite path #
        if 'path' in kw: path = FilePath(kw['path'])
        else:            path = self.path
        # Special cases for float formatting #
        if self.float_format_tex == 'split_thousands':
            self.float_format_tex = self.split_thousands
        # Make sure the directory exists #
        self.base_dir.create_if_not_exists()
        # Latex version #
        if 'tex' in self.formats:
            df.to_latex(str(path),
                        float_format  = self.float_format_tex,
                        na_rep        = self.na_rep,
                        index         = self.index,
                        bold_rows     = self.bold_rows,
                        column_format = self.column_format,
                        escape        = self.escape_tex)
        # CSV version (plain text) #
        if 'csv' in self.formats:
            path = path.replace_extension('csv')
            df.to_csv(str(path),
                      float_format = self.float_format_csv,
                      index        = self.index)
        # Return the path #
        return path
Example #18
0
Written by Lucas Sinclair.
MIT Licenced.
"""

# Built-in modules #

# Internal modules #
import inspect, os

# First party modules #
from autopaths import Path

# Third party modules #

# Constants #
file_name = inspect.getframeinfo(inspect.currentframe()).filename
this_dir  = os.path.dirname(os.path.abspath(file_name)) + '/'
destin    = Path(this_dir + 'user_agent_strings.txt')

################################################################################
def get_popular_user_agents():
    """
    Retrieve most popular user agent strings.
    Can look at https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
    """
    return ''

################################################################################
if __name__ == '__main__':
    content = get_popular_user_agents()
    destin.write(content)
Example #19
0
__version__ = '0.2.2'

# Built-in modules #
import os, sys

# First party modules #
from autopaths import Path
from autopaths.dir_path import DirectoryPath
from plumbing.git import GitRepo

# Constants #
project_name = 'libcbm_runner'
project_url  = 'https://github.com/xapple/libcbm_runner'

# Get paths to module #
self       = sys.modules[__name__]
module_dir = Path(os.path.dirname(self.__file__))

# The repository directory #
repos_dir = module_dir.directory

# The module is maybe in a git repository #
git_repo = GitRepo(repos_dir, empty=True)

# Where is the data, default case #
libcbm_data_dir = DirectoryPath("~/repos/libcbm_data/")

# But you can override that with an environment variable #
if os.environ.get("LIBCBM_DATA"):
    libcbm_data_dir = DirectoryPath(os.environ['LIBCBM_DATA'])
Example #20
0
 def __init__(self, sheet_to_dfs, path):
     self.sheet_to_dfs = sheet_to_dfs
     self.path = Path(path)
Example #21
0
class Document(object):
    """The main object is the document to be generated from markdown text."""

    def __repr__(self): return '<%s object on %s>' % (self.__class__.__name__, self.parent)

    def __init__(self, input_path, output_path=None, builtin_template=None):
        # Input #
        self.input_path = Path(input_path)
        # Output #
        if output_path is None: self.output_path = self.default_output_name
        else:                   self.output_path = output_path
        # Template #
        self.builtin_template = builtin_template if builtin_template else 'sinclair_bio'

    def __call__(self, *args, **kwargs): return self.generate(*args, **kwargs)

    def generate(self):
        self.load_markdown()
        self.make_body()
        self.make_latex(params=self.params)
        self.make_pdf()

    @property
    def default_options(self):
        return {
            'name':        os.environ.get('USER_FULL_NAME'),
            'status':      os.environ.get('USER_STATUS'),
            'company':     os.environ.get('USER_COMPANY'),
            'subcompany':  os.environ.get('USER_SUBCOMPANY'),
            'title':       "Auto-generated report",
            'image_path':  logo_dir + 'logo.png',
        }

    @property
    def default_output_name(self):
        return os.path.splitext(self.input_path)[0] + '.pdf'

    def load_markdown(self):
        """Load file in memory and separate the options and body"""
        # Read the file #
        self.input_path.must_exist()
        self.input = self.input_path.contents_utf8
        # Separate the top params and the rest of the markdown #
        find_results = re.findall('\A---(.+?)---(.+)', self.input, re.M|re.DOTALL)
        # We did not find any parameters #
        if not find_results:
            self.params = {}
            self.markdown = self.input
            return
        # We did find a set of parameters #
        self.params, self.markdown = find_results[0]
        self.params = [i.partition(':')[::2] for i in self.params.strip().split('\n')]
        self.params = dict([(k.strip(),v.strip()) for k,v in self.params])

    def make_body(self):
        """Convert the body to LaTeX."""
        kwargs = dict(_in=self.markdown, read='markdown', write='latex')
        self.body = pbs3.Command('pandoc')(**kwargs).stdout

    def make_latex(self, params=None, header=None, footer=None):
        """Add the header and footer."""
        options = self.default_options.copy()
        if params: options.update(params)
        # Load the right templates #
        subpackage = importlib.import_module('pymarktex.templates.' + self.builtin_template)
        # Header and Footer #
        self.header = subpackage.HeaderTemplate(options) if header is None else header
        self.footer = subpackage.FooterTemplate()        if footer is None else footer
        self.latex = str(self.header) + self.body + str(self.footer)

    def make_pdf(self, safe=False, include_src=False):
        """Call XeLaTeX (twice for cross-referencing)"""
        # Paths #
        self.tmp_dir    = new_temp_dir()
        self.tmp_path   = Path(self.tmp_dir + 'main.tex')
        self.tmp_stdout = Path(self.tmp_dir + 'stdout.txt')
        self.tmp_stderr = Path(self.tmp_dir + 'stderr.txt')
        self.tmp_log    = Path(self.tmp_dir + 'main.log')
        # Prepare #
        with codecs.open(self.tmp_path, 'w', encoding='utf8') as handle: handle.write(self.latex)
        self.cmd_params  = ["--interaction=nonstopmode", '-output-directory']
        self.cmd_params += [self.tmp_dir, self.tmp_path]
        # Call twice for references #
        self.call_xelatex(safe)
        self.call_xelatex(safe)
        # Move into place #
        shutil.move(self.tmp_dir + 'main.pdf', self.output_path)
        # Show the latex source #
        if include_src: self.output_path.replace_extension('tex').write(self.latex, encoding='utf-8')

    def call_xelatex(self, safe=False):
        """Here we use the `pbs3` library under Windows and the sh` library under Unix.
        There is a cross-compatible library called `plumbum` but has an awkward syntax:

            cmd = plumbum.local['xelatex']
            ((cmd > self.tmp_stderr) >= self.tmp_stdout)()

        See https://github.com/tomerfiliba/plumbum/issues/441
        """
        if os.name == "posix":  cmd = pbs3.Command('xelatex')
        if os.name == "nt":     cmd = pbs3.Command('xelatex.exe')
        try:
            cmd(*self.cmd_params,
                _ok_code=[0] if not safe else [0,1],
                _err=str(self.tmp_stderr),
                _out=str(self.tmp_stdout))
        except pbs3.ErrorReturnCode_1:
            print('-'*60)
            print("Xelatex exited with return code 1.")
            if self.tmp_stdout.exists:
                print("Here is the tail of the stdout at '%s':" % self.tmp_stdout.unix_style)
                print(self.tmp_stdout.pretty_tail)
            elif self.tmp_log.exists:
                print("Here is the tail of the log at '%s':" % self.tmp_log.unix_style)
                print(self.tmp_log.pretty_tail)
            print('-'*60)
            raise

    def copy_to_outbox(self):
        """Copy the report to the outbox directory where it can be viewed by anyone."""
        self.outbox.directory.create(safe=True)
        shutil.copy(self.output_path, self.outbox)

    def purge_cache(self):
        """Some reports used pickled properties to avoid recalculations."""
        if not hasattr(self, 'cache_dir'): raise Exception("No cache directory to purge.")
        self.cache_dir.remove()
        self.cache_dir.create()
Example #22
0
 def __call__(self):
     for item in self.csv_list:
         csv_path = Path(self.country.data_dir + 'orig/csv/' + item)
         self.rename(csv_path)
Example #23
0
 def path(self):
     return Path(self.base_dir + self.short_name + '.tex')
Example #24
0
JRC Biomass Project.
Unit D1 Bioeconomy.
"""

# Built-in modules #
import os, inspect

# Internal modules #
from pymarktex.templates import Template

# First party modules #
from autopaths import Path

# Get current directory #
file_name = os.path.abspath((inspect.stack()[0])[1])
this_dir = Path(os.path.dirname(os.path.abspath(file_name)) + '/')


###############################################################################
class Header(Template):
    """All the parameters to be rendered in the LaTeX header template."""
    def image_path(self):
        return (this_dir + 'logo.png').unix_style


###############################################################################
class Footer(Template):
    """All the parameters to be rendered in the LaTeX footer template."""
    pass
Example #25
0
 def __init__(self, zip_cache_dir):
     # Record where the cache will be located on disk #
     self.cache_dir = zip_cache_dir
     # Where the file should be downloaded to #
     self.zip_path = Path(self.cache_dir + self.zip_name)
Example #26
0
 def __init__(self, output_path=None):
     # Paths #
     if output_path is None:
         self.output_path = Path(cache_dir + 'reports/comparison.pdf')
     else:
         self.output_path = Path(output_path)
Example #27
0
class Graph(object):
    """ A nice class to make graphs with matplotlib. Example usage:
            from plumbing.graphs import Graph
            class RegressionGraph(Graph):
                def plot(self, **kwargs):
                    fig = pyplot.figure()
                    seaborn.regplot(self.x_data, self.y_data, fit_reg=True);
                    self.save_plot(fig, **kwargs)
            for x_name in x_names:
                graph            = PearsonGraph(short_name = x_name)
                graph.title      = "Regression between y and '%s'" % (x_name)
                graph.x_data     = x_data[x_name]
                graph.y_data     = y_data
                graph.plot()
    """

    default_params = OrderedDict((
        ('width'  , None),
        ('height' , None),
        ('bottom' , None),
        ('top'    , None),
        ('left'   , None),
        ('right'  , None),
        ('x_grid' , None), # Vertical lines
        ('y_grid' , None), # Horizontal lines
        ('x_scale', None),
        ('y_scale', None),
        ('x_label', None),
        ('y_label', None),
        ('title'  , None),
        ('y_lim_min', None), # Minium (ymax - ymin) after autoscale
        ('x_lim_min', None), # Minium (xmax - xmin) after autoscale
        ('sep'    , ()),
        ('formats', ('pdf',)),
    ))

    def __bool__(self): return bool(self.path)
    __nonzero__ = __bool__

    def __init__(self, parent=None, base_dir=None, short_name=None):
        # Save parent #
        self.parent = parent
        # If we got a file #
        if isinstance(base_dir, FilePath):
            self.base_dir = base_dir.directory
            short_name    = base_dir.short_prefix
        # If no parent and no directory #
        if base_dir is None and parent is None:
            file_name     = os.path.abspath((inspect.stack()[1])[1])
            self.base_dir = os.path.dirname(os.path.abspath(file_name)) + '/'
            self.base_dir = Path(self.base_dir)
        # If no directory but a parent is present #
        if base_dir is None:
            if hasattr(self.parent, 'p'):
                self.base_dir = self.parent.p.graphs_dir
            if hasattr(self.parent, 'paths'):
                self.base_dir = self.parent.paths.graphs_dir
        else:
            self.base_dir = Path(self.base_dir)
        self.base_dir.create_if_not_exists()
        # Short name #
        if short_name: self.short_name = short_name
        # Use the base class name #
        if not hasattr(self, 'short_name'):
            self.short_name = camel_to_snake(self.__class__.__name__)

    @property_cached
    def path(self):
        return Path(self.base_dir + self.short_name + '.pdf')

    def __call__(self, *args, **kwargs):
        """Plot the graph if it doesn't exist. Then return the path to it.
        Force the reruning with rerun=True"""
        if not self or kwargs.get('rerun'): self.plot(*args, **kwargs)
        return self.path

    def save_plot(self, fig=None, axes=None, **kwargs):
        # Missing figure #
        if fig is None:   fig = pyplot.gcf()
        # Missing axes #
        if axes is None: axes = pyplot.gca()
        # Parameters #
        self.params = {}
        for key in self.default_params:
            if key in kwargs:                          self.params[key] = kwargs[key]
            elif hasattr(self, key):                   self.params[key] = getattr(self, key)
            elif self.default_params[key] is not None: self.params[key] = self.default_params[key]
        # Backwards compatibility #
        if kwargs.get('x_log', False): self.params['x_scale'] = 'symlog'
        if kwargs.get('y_log', False): self.params['y_scale'] = 'symlog'
        # Log #
        if 'x_scale' in self.params: axes.set_xscale(self.params['x_scale'])
        if 'y_scale' in self.params: axes.set_yscale(self.params['y_scale'])
        # Axis limits #
        if 'x_min' in self.params: axes.set_xlim(self.params['x_min'], axes.get_xlim()[1])
        if 'x_max' in self.params: axes.set_xlim(axes.get_xlim()[0], self.params['x_max'])
        if 'y_min' in self.params: axes.set_ylim(self.params['y_min'], axes.get_ylim()[1])
        if 'y_max' in self.params: axes.set_ylim(axes.get_ylim()[0], self.params['y_max'])
        # Minimum delta on axis limits #
        if 'y_lim_min' in self.params:
            top, bottom = axes.get_ylim()
            minimum     = self.params['y_lim_min']
            delta       = top - bottom
            if delta < minimum:
                center = bottom + delta/2
                axes.set_ylim(center - minimum/2, center + minimum/2)
        # Title #
        title = self.params.get('title', False)
        if title: axes.set_title(title)
        # Axes labels  #
        if self.params.get('x_label'): axes.set_xlabel(self.params['x_label'])
        if self.params.get('y_label'): axes.set_ylabel(self.params['y_label'])
        # Set height and width #
        if self.params.get('width'):  fig.set_figwidth(self.params['width'])
        if self.params.get('height'): fig.set_figheight(self.params['height'])
        # Adjust #
        if self.params.get('bottom'):
            fig.subplots_adjust(hspace=0.0, bottom = self.params['bottom'], top   = self.params['top'],
                                            left   = self.params['left'],   right = self.params['right'])
        # Grid #
        if 'x_grid' in self.params: axes.xaxis.grid(self.params['x_grid'])
        if 'y_grid' in self.params: axes.yaxis.grid(self.params['y_grid'])
        # Data and source extra text #
        if hasattr(self, 'dev_mode') and self.dev_mode is True:
            fig.text(0.99, 0.98, time.asctime(), horizontalalignment='right')
            job_name = os.environ.get('SLURM_JOB_NAME', 'Unnamed')
            user_msg = 'user: %s, job: %s' % (getpass.getuser(), job_name)
            fig.text(0.01, 0.98, user_msg, horizontalalignment='left')
        # Nice digit grouping #
        if 'x' in self.params['sep']:
            separate = lambda x,pos: split_thousands(x)
            axes.xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(separate))
        if 'y' in self.params['sep']:
            separate = lambda y,pos: split_thousands(y)
            axes.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(separate))
        # Add custom labels #
        if 'x_labels' in self.params: axes.set_xticklabels(self.params['x_labels'])
        if 'x_labels_rot' in self.params: pyplot.setp(axes.xaxis.get_majorticklabels(), rotation=self.params['x_labels_rot'])
        # Possibility to overwrite path #
        if 'path' in self.params:   path = FilePath(self.params['path'])
        elif hasattr(self, 'path'): path = FilePath(self.path)
        else:                       path = FilePath(self.short_name + '.pdf')
        # Save it as different formats #
        for ext in self.params['formats']: fig.savefig(path.replace_extension(ext))
        # Close it #
        pyplot.close(fig)

    def plot_and_save(self, **kwargs):
        """Used when the plot method defined does not create a figure nor calls save_plot
        Then the plot method has to use self.fig"""
        self.fig = pyplot.figure()
        self.plot()
        self.axes = pyplot.gca()
        self.save_plot(self.fig, self.axes, **kwargs)
        pyplot.close(self.fig)

    def plot(self, bins=250, **kwargs):
        """An example plot function. You have to subclass this method."""
        # Data #
        counts = [sum(map(len, b.contigs)) for b in self.parent.bins]
        # Linear bins in logarithmic space #
        if 'log' in kwargs.get('x_scale', ''):
            start, stop = numpy.log10(1), numpy.log10(max(counts))
            bins = list(numpy.logspace(start=start, stop=stop, num=bins))
            bins.insert(0, 0)
        # Plot #
        fig = pyplot.figure()
        pyplot.hist(counts, bins=bins, color='gray')
        axes = pyplot.gca()
        # Information #
        title = 'Distribution of the total nucleotide count in the bins'
        axes.set_title(title)
        axes.set_xlabel('Number of nucleotides in a bin')
        axes.set_ylabel('Number of bins with that many nucleotides in them')
        # Save it #
        self.save_plot(fig, axes, **kwargs)
        pyplot.close(fig)
        # For convenience #
        return self

    def save_anim(self, fig, animate, init, bitrate=10000, fps=30):
        """Not functional -- TODO"""
        from matplotlib import animation
        anim = animation.FuncAnimation(fig, animate, init_func=init, frames=360, interval=20)
        FFMpegWriter = animation.writers['ffmpeg']
        writer = FFMpegWriter(bitrate= bitrate, fps=fps)
        # Save #
        self.avi_path = self.base_dir + self.short_name + '.avi'
        anim.save(self.avi_path, writer=writer, codec='x264')