commit a234c0e8eace186f213e89e53ff2140e8da577e6
Author: Carles <carles.marti-aliod@ens-lyon.fr>
Date:   Tue Apr 14 11:57:00 2020 +0200

    Refactor file dos_input.py, every option is read and checked its value with a specific function for the purpose.
    Included the possibility to run the file as standalone program.

diff --git a/modules/dos_input.py b/modules/dos_input.py
index 224a478..32b6711 100644
--- a/modules/dos_input.py
+++ b/modules/dos_input.py
@@ -5,11 +5,71 @@ str2lst: Converts a string of integers, and groups of them, to a list.
 check_expect_val: Checks whether an option lies within its expected values.
 read_input: Sets up the variables of DockOnSurf by reading from an input file.
 """
+import os.path
 import logging
+from configparser import ConfigParser, NoSectionError, NoOptionError, \
+    MissingSectionHeaderError, DuplicateOptionError
+
+logger = logging.getLogger('DockOnSurf')
+
+dos_inp = ConfigParser(inline_comment_prefixes='#',
+                       empty_lines_in_values=False)
+
+new_answers = {'n': False, 'none': False, 'nay': False,
+               'y': True, 'sí': True, 'aye': True, 'sure': True}
+for answer, val in new_answers.items():
+    dos_inp.BOOLEAN_STATES[answer] = val
+turn_false_answers = [answer for answer in dos_inp.BOOLEAN_STATES
+                      if dos_inp.BOOLEAN_STATES[answer] is False]
+
+no_sect_err = "Section '%s' not found on input file"
+no_opt_err = "Option '%s' not found on section '%s'"
+num_error = "'%s' value must be a %s"
+unexp_error = "An unexpected error occurred"
+
+
+def try_command(command, expct_error_types: list, *args, **kwargs):
+    """Try to run a command and record exceptions (expected and not) on a log.
+    
+    @param command: method or function, the command to be executed.
+    @param expct_error_types: tuple of tuples, every inner tuple is supposed to
+    contain an exception type (eg. ValueError, TypeError, etc.) to be caught and
+    a message to print in the log and on the screen explaining the exception.
+    Error types that are not allow to be called with a custom message as only
+    error argument are not supported.
+    The outer tuple encloses all couples of error types and their relative
+    messages.
+    *args and **kwargs: arguments and keyword-arguments of the command to be
+    executed.
+    When trying to run 'command' with its args and kwargs, if an exception
+    present on the 'error_types' occurs, its relative error message is recorded
+    on the log and a same type exception is raised with the custom message.
+    """
+
+    err = False
+    try:
+        return_val = command(*args, **kwargs)
+    except Exception as e:
+        print('I am in exce')
+        for expct_err in expct_error_types:
+            if isinstance(e, expct_err[0]):
+                logger.error(expct_err[1])
+                err = expct_err[0](expct_err[1])
+                break
+        else:
+            logger.exception(unexp_error)
+            err = e
+    else:
+        print('Here I am')
+        err = False
+        return return_val
+    finally:
+        print(err)
+        if isinstance(err, BaseException):
+            raise err
 
 
-def str2lst(cmplx_str):  # TODO: enable deeper level of nested lists \
-                         # TODO: (eg. '3 ((6 7) 8) 4')
+def str2lst(cmplx_str):  # TODO: enable deeper level of nested lists
     """Converts a string of integers, and groups of them, to a list.
 
     Keyword arguments:
@@ -25,23 +85,21 @@ def str2lst(cmplx_str):  # TODO: enable deeper level of nested lists \
     eg. '128,(135 138;141] 87 {45, 68}' -> [128, 87, [135, 138, 141], [45, 68]]
     """
 
-    logger = logging.getLogger('DockOnSurf')
-
-    error_msg = "Function argument should be a sequence of integer numbers " \
-                "separated by ',' ';' or ' '." \
+    # Checks
+    error_msg = "Function argument should be a str,sequence of integer " \
+                "numbers separated by ',' ';' or ' '." \
                 "\nThey can be grouped in parentheses-like enclosers: '()', " \
                 "'[]' or {}. Nested groups are not allowed. \n" \
                 "eg. 128,(135 138;141) 87 {45, 68}"
-    try:
-        cmplx_str = cmplx_str.replace(',', ' ').replace(';', ' ').replace(
-            '[', '(').replace(']', ')').replace('{', '(').replace('}', ')')
-        list(map(int, cmplx_str.replace(')', '').replace('(', '').split()))
-    except AttributeError:
-        logger.error(error_msg)
-        raise AttributeError(error_msg)
-    except ValueError: # TODO Improve error handling
-        logger.error(error_msg)
-        raise ValueError(error_msg)
+    cmplx_str = try_command(cmplx_str.replace, [(AttributeError, error_msg)],
+                            ',', ' ')
+
+    cmplx_str = cmplx_str.replace(';', ' ').replace('[', '(').replace(
+        ']', ')').replace('{', '(').replace('}', ')')
+
+    try_command(list, [(ValueError, error_msg)], map(int, cmplx_str.replace(
+        ')', '').replace('(', '').split()))
+
     deepness = 0
     for el in cmplx_str.split():
         if '(' in el:
@@ -85,9 +143,6 @@ def check_expect_val(value, expect_vals):
     @raise ValueError: if the value is not among the expected ones.
     @return True if the value is among the expected ones.
     """
-
-    logger = logging.getLogger('DockOnSurf')
-
     adeq_val_err = "'%s' is not an adequate value.\n" \
                    "Adequate values: %s"
     if not any([exp_val in value for exp_val in expect_vals]):
@@ -97,81 +152,7 @@ def check_expect_val(value, expect_vals):
     return True
 
 
-def read_input(in_file):
-    """Sets up the variables of DockOnSurf by reading from an input file.
-
-    The Format of the input file should be of a .INI type like most config files
-    able to be managed by the configparser standard library.
-    Sections should be capitalized ('Global' is ok but 'global' or 'GLOBAL'
-    are not).
-    After setting up the logger and the ConfifParser options, this function
-    checks that mandatory sections exists ('Global'). Options are read one at a
-    time following an internal order (not necessarily the input order).
-    For every section, section-mandatory options are checked to be present.
-    All the other options have a default/fallback value if the option is not
-    specified.
-    The values of every option are checked and errors are recorded in the
-    program log and an exception is raised.
-    @param in_file: str, absolute or relative path to the input file to be read.
-    @return: return_vars: dict, list of all the input variables together with
-    their values
-    """
-    import os.path
-    from configparser import ConfigParser, NoSectionError, NoOptionError, \
-        MissingSectionHeaderError, DuplicateOptionError
-
-    from ase.data import chemical_symbols
-
-    logger = logging.getLogger('DockOnSurf')
-
-    dos_inp = ConfigParser(inline_comment_prefixes='#',
-                           empty_lines_in_values=False)
-
-    new_answers = {'n': False, 'none': False, 'nay': False,
-                   'y': True, 'sí': True, 'aye': True, 'sure': True}
-
-    for answer, val in new_answers.items():
-        dos_inp.BOOLEAN_STATES[answer] = val
-
-    try:
-        dos_inp.read(in_file)
-    except MissingSectionHeaderError as e:
-        logger.error('There are options in the input file without a Section '
-                     'header')
-        raise e
-    except DuplicateOptionError as e:
-        logger.error('There is an option in the input file that has been '
-                     'specified more than once, possibly due to the lack of a '
-                     'Section header')
-        raise e
-
-    turn_false_answers = [opt for opt in dos_inp.BOOLEAN_STATES
-                          if dos_inp.BOOLEAN_STATES[opt] is False]
-
-    return_vars = {}
-
-    no_sect_err = "Section '%s' not found on input file"
-    no_opt_err = "Option '%s' not found on section '%s'"
-    num_error = "'%s' value must be a %s"
-
-    ##############
-    ### Global ###
-    ##############
-    ### Mandatory options ###
-    if not dos_inp.has_section('Global'):
-        logger.error(no_sect_err % 'Global')
-        raise NoSectionError('Global')
-
-    # Checks whether the mandatory options 'run_type', 'code', etc. are
-    # present in the Global section and ensures they have an adequate
-    # value
-    mand_opts = ['run_type', 'code', 'batch_q_sys']
-    for opt in mand_opts:
-        if not dos_inp.has_option('Global', opt):
-            logger.error(no_opt_err % (opt, 'Global'))
-            raise NoOptionError(opt, 'Global')
-
-    # run_type
+def get_run_type():
     isolated, screening, refinement = (False, False, False)
     run_type_vals = ['isolated', 'screening', 'refinement', 'adsorption',
                      'full']
@@ -189,51 +170,49 @@ def read_input(in_file):
     if 'full' in run_type:
         isolated, screening, refinement = (True, True, True)
 
-    return_vars['isolated'] = isolated
-    return_vars['screening'] = screening
-    return_vars['refinement'] = refinement
-    # end run_type 
+    return isolated, screening, refinement
 
-    # code
+
+def get_code():
     code_vals = ['cp2k']
     check_expect_val(dos_inp.get('Global', 'code').lower(), code_vals)
     code = dos_inp.get('Global', 'code').lower()
-    return_vars['code'] = code
-    # end code
+    return code
+
 
-    # batch_q_sys
+def get_batch_q_sys():
     batch_q_sys_vals = ['sge']
     check_expect_val(dos_inp.get('Global', 'batch_q_sys').lower(),
                      batch_q_sys_vals)
     batch_q_sys = dos_inp.get('Global', 'batch_q_sys').lower()
-    return_vars['batch_q_sys'] = batch_q_sys
-    # end batch_q_sys
+    return batch_q_sys
 
-    ### Facultative options (Default value present) ###
-    # relaunch_err
+
+def get_relaunch_err():
     relaunch_err_vals = ['geo_not_conv', 'false']
     relaunch_err = dos_inp.get('Global', 'relaunch_err',
                                fallback="False")
     if relaunch_err.lower() in turn_false_answers:
-        relaunch_err = False
+        return False
     else:
         check_expect_val(relaunch_err.lower(), relaunch_err_vals)
-    return_vars['relaunch_err'] = relaunch_err
-    # end relaunch_err
+    return relaunch_err
+
+
+def get_max_qw():
+    err_msg = num_error % ('max_qw', 'positive integer')
+    max_qw = try_command(dos_inp.getint, [(ValueError, err_msg)],
+                         'Global', 'max_qw', fallback=3)
 
-    # max_qw
-    try:
-        max_qw = dos_inp.getint('Global', 'max_qw', fallback=3)
-    except ValueError:
-        logger.error(num_error % ('max_qw', 'positive integer'))
-        raise ValueError(num_error % ('max_qw', 'positive integer'))
     if max_qw < 1:
         logger.error(num_error % ('max_qw', 'positive integer'))
         raise ValueError(num_error % ('max_qw', 'positive integer'))
-    return_vars['max_qw'] = max_qw
-    # end max_qw
+    return max_qw
+
+
+def get_special_atoms():
+    from ase.data import chemical_symbols
 
-    # special_atoms
     spec_at_err = '\'special_atoms\' does not have an adequate format.\n' \
                   'Adequate format: (Fe1 Fe) (O1 O)'
     special_atoms = dos_inp.get('Global', 'special_atoms', fallback="False")
@@ -265,285 +244,317 @@ def read_input(in_file):
                     continue
                 if tup2[0] == tup[0]:
                     label_err = f'You have specified the label {tup[0]} to ' \
-                                 f'more than one special atom'
+                                f'more than one special atom'
                     logger.error(label_err)
                     raise ValueError(label_err)
         special_atoms = lst_tple
-    return_vars['special_atoms'] = special_atoms
-    # end special_atoms
+    return special_atoms
+
+
+def get_isol_inp_file():
+    isol_inp_file = dos_inp.get('Isolated', 'isol_inp_file')
+    if not os.path.isfile(isol_inp_file):
+        logger.error(f'File {isol_inp_file} not found')
+        raise FileNotFoundError(f'File {isol_inp_file} not found')
+    return isol_inp_file
+
+
+def get_cluster_magns():
+    clust_magns_vals = ['energy', 'moi']
+    cluster_magns_str = dos_inp.get('Isolated', 'cluster_magns',
+                                    fallback='energy')
+    cluster_magns_str.replace(',', ' ').replace(';', ' ')
+    cluster_magns = cluster_magns_str.split(' ')
+    cluster_magns = [m.lower() for m in cluster_magns]
+    for m in cluster_magns:
+        check_expect_val(m, clust_magns_vals)
+    return cluster_magns
+
+
+def get_num_conformers():
+    err_msg = num_error % ('num_conformers', 'positive integer')
+    num_conformers = try_command(dos_inp.getint, [(ValueError, err_msg)],
+                                 'Isolated', 'num_conformers', fallback=100)
+    if num_conformers < 1:
+        logger.error(err_msg)
+        raise ValueError(err_msg)
+    return num_conformers
+
+
+def get_num_prom_cand():
+    err_msg = num_error % ('num_prom_cand', 'positive integer')
+    num_prom_cand = try_command(dos_inp.getint, [(ValueError, err_msg)],
+                                'Isolated', 'num_prom_cand', fallback=3)
+    if num_prom_cand < 1:
+        logger.error(err_msg)
+        raise ValueError(err_msg)
+    return num_prom_cand
+
+
+def get_iso_rmsd():
+    err_msg = num_error % ('iso_rmsd', 'positive decimal number')
+    iso_rmsd = try_command(dos_inp.getfloat, [(ValueError, err_msg)],
+                           'Isolated', 'iso_rmsd', fallback=0.05)
+    if iso_rmsd <= 0.0:
+        logger.error(err_msg)
+        raise ValueError(err_msg)
+    return iso_rmsd
+
+
+def get_screen_inp_file():
+    screen_inp_file = dos_inp.get('Screening', 'screen_inp_file')
+    if not os.path.isfile(screen_inp_file):
+        logger.error(f'File {screen_inp_file} not found')
+        raise FileNotFoundError(f'File {screen_inp_file} not found')
+    return screen_inp_file
+
+
+def get_sites():
+    err_msg = 'The value of sites should be a list of atom numbers ' \
+              '(ie. positive integers) or groups of atom numbers ' \
+              'grouped by parentheses-like enclosers. \n' \
+              'eg. 128,(135 138;141) 87 {45, 68}'
+    # Convert the string into a list of lists
+    sites = try_command(str2lst,
+                        [(ValueError, err_msg), (AttributeError, err_msg)],
+                        dos_inp.get('Screening', 'sites'))
+    # Check all elements of the list (of lists) are positive integers
+    for site in sites:
+        if type(site) is list:
+            for atom in site:
+                if atom < 0:
+                    logger.error(err_msg)
+                    raise ValueError(err_msg)
+        elif type(site) is int:
+            if site < 0:
+                logger.error(err_msg)
+                raise ValueError(err_msg)
+        else:
+            logger.error(err_msg)
+            raise ValueError(err_msg)
+
+    return sites
+
+
+def get_molec_ads_ctrs():
+    err_msg = 'The value of molec_ads_ctrs should be a list of atom' \
+              ' numbers (ie. positive integers) or groups of atom ' \
+              'numbers enclosed by parentheses-like characters. \n' \
+              'eg. 128,(135 138;141) 87 {45, 68}'
+    # Convert the string into a list of lists
+    molec_ads_ctrs = try_command(str2lst,
+                                 [(ValueError, err_msg),
+                                  (AttributeError, err_msg)],
+                                 dos_inp.get('Screening', 'molec_ads_ctrs'))
+    # Check all elements of the list (of lists) are positive integers
+    for ctr in molec_ads_ctrs:
+        if type(ctr) is list:
+            for atom in ctr:
+                if atom < 0:
+                    logger.error(err_msg)
+                    raise ValueError(err_msg)
+        elif type(ctr) is int:
+            if ctr < 0:
+                logger.error(err_msg)
+                raise ValueError(err_msg)
+        else:
+            logger.error(err_msg)
+            raise ValueError(err_msg)
+
+    return molec_ads_ctrs
+
+
+def get_try_disso():
+    err_msg = "try_disso should be have a boolean value (True or False)"
+    try_disso = try_command(dos_inp.getboolean,
+                            [(ValueError, err_msg)],
+                            'Screening', 'try_disso', fallback=False)
+    return try_disso
+
+
+def get_pts_per_angle():
+    err_msg = num_error % ('sample_points_per_angle',
+                           'positive integer')
+    pts_per_angle = try_command(dos_inp.getint,
+                                [(ValueError, err_msg)],
+                                'Screening', 'sample_points_per_angle',
+                                fallback=3)
+
+    return pts_per_angle
+
+
+def get_coll_thrsld():
+    err_msg = num_error % ('collision_threshold',
+                           'positive decimal number')
+
+    coll_thrsld = try_command(dos_inp.getfloat,
+                              [(ValueError, err_msg)],
+                              'Screening', 'collision_threshold', fallback=1.2)
+    if coll_thrsld <= 0:
+        logger.error(err_msg)
+        raise ValueError(err_msg)
+
+    return coll_thrsld
+
+
+def get_screen_rmsd():
+    err_msg = num_error % ('screen_rmsd', 'positive decimal number')
+    screen_rmsd = try_command(dos_inp.getfloat,
+                              [(ValueError, err_msg)],
+                              'Screening', 'screen_rmsd', fallback=0.05)
+    if screen_rmsd <= 0:
+        logger.error(err_msg)
+        raise ValueError(err_msg)
+
+    return screen_rmsd
+
+
+def get_coll_bottom_z():
+    err_msg = num_error % ('collision_bottom_z', 'decimal number')
+    coll_bottom_z = dos_inp.get('Screening', 'collision_bottom_z',
+                                fallback="False")
+    if coll_bottom_z.lower() in turn_false_answers:
+        coll_bottom_z = False
+    else:
+        try_command(float, [(ValueError, err_msg)], coll_bottom_z)
+
+    return coll_bottom_z
+
+
+def get_refine_inp_file():
+    refine_inp_file = dos_inp.get('Refinement', 'refine_inp_file')
+    if not os.path.isfile(refine_inp_file):
+        logger.error(f'File {refine_inp_file} not found')
+        raise FileNotFoundError(f'File {refine_inp_file} not found')
+
+    return refine_inp_file
+
+
+def get_energy_cutoff():
+    err_msg = num_error % ('energy_cutoff', 'positive decimal number')
+    energy_cutoff = try_command(dos_inp.getfloat,
+                                [(ValueError, err_msg)],
+                                'Refinement', 'energy_cutoff', fallback=0.5)
+    if energy_cutoff < 0:
+        logger.error(err_msg)
+        raise ValueError(err_msg)
+    return energy_cutoff
+
+
+def read_input(in_file):
+    err = False
+    try:
+        dos_inp.read(in_file)
+    except MissingSectionHeaderError as e:
+        logger.error('There are options in the input file without a Section '
+                     'header')
+        err = e
+    except DuplicateOptionError as e:
+        logger.error('There is an option in the input file that has been '
+                     'specified more than once, possibly due to the lack of a '
+                     'Section header')
+        err = e
+    except Exception as e:
+        err = e
+    else:
+        err = False
+    finally:
+        if isinstance(err, BaseException):
+            raise err
 
-    ################
-    ### Isolated ###
-    ################
+    return_vars = {}
+
+    # Global
+    if not dos_inp.has_section('Global'):
+        logger.error(no_sect_err % 'Global')
+        raise NoSectionError('Global')
+
+    # Mandatory options
+    # Checks whether the mandatory options 'run_type', 'code', etc. are present.
+    screen_mand_opts = ['run_type', 'code', 'batch_q_sys']
+    for opt in screen_mand_opts:
+        if not dos_inp.has_option('Global', opt):
+            logger.error(no_opt_err % (opt, 'Global'))
+            raise NoOptionError(opt, 'Global')
+
+    # Gets which sections are to be carried out
+    isolated, screening, refinement = get_run_type()
+    return_vars['isolated'] = isolated
+    return_vars['screening'] = screening
+    return_vars['refinement'] = refinement
+    return_vars['code'] = get_code()
+    return_vars['batch_q_sys'] = get_batch_q_sys()
+
+    # Facultative options (Default/Fallback value present)
+    return_vars['relaunch_err'] = get_relaunch_err()
+    return_vars['max_qw'] = get_max_qw()
+    return_vars['special_atoms'] = get_special_atoms()
+
+    # Isolated
     if isolated:
-        ### Mandatory options if Isolated ###
         if not dos_inp.has_section('Isolated'):
             logger.error(no_sect_err % 'Isolated')
             raise NoSectionError('Isolated')
-
-        # isol_inp_file
-        if 'isol_inp_file' not in dos_inp.options('Isolated'):
-            logger.error(no_opt_err % ('isol_inp_file', 'Isolated'))
-            raise NoOptionError('isol_inp_file', 'Isolated')
-        isol_inp_file = dos_inp.get('Isolated', 'isol_inp_file')
-        if not os.path.isfile(isol_inp_file):
-            logger.error(f'File {isol_inp_file} not found')
-            raise FileNotFoundError(f'File {isol_inp_file} not found')
-        return_vars['isol_inp_file'] = isol_inp_file
-        # end isol_inp_file
-
-        ### Facultative options (Default/Fallback value present) ###
-        # cluster_magns
-        clust_magns_vals = ['energy', 'moi']
-        cluster_magns_str = dos_inp.get('Isolated', 'cluster_magns',
-                                        fallback='energy')
-        cluster_magns_str.replace(',', ' ').replace(';', ' ')
-        cluster_magns = cluster_magns_str.split(' ')
-        cluster_magns = [m.lower() for m in cluster_magns]
-        for m in cluster_magns:
-            check_expect_val(m, clust_magns_vals)
-        return_vars['cluster_magns'] = cluster_magns
-        # end cluster_magns
-
-        # num_conformers
-        try:
-            num_conformers = dos_inp.getint('Isolated', 'num_conformers',
-                                            fallback=100)
-        except ValueError:
-            logger.error(num_error % ('num_conformers', 'positive integer'))
-            raise ValueError(num_error % ('num_conformers', 'positive integer'))
-        if num_conformers < 1:
-            logger.error(num_error % ('num_conformers', 'positive integer'))
-            raise ValueError(num_error % ('num_conformers', 'positive integer'))
-        return_vars['num_conformers'] = num_conformers
-        # end num_conformers
-
-        # num_prom_cand
-        try:
-            num_prom_cand = dos_inp.getint('Isolated', 'num_prom_cand',
-                                       fallback=3)
-        except ValueError:
-            logger.error(num_error % ('num_prom_cand', 'positive integer'))
-            raise ValueError(num_error % ('num_prom_cand', 'positive integer'))
-        if num_prom_cand < 1:
-            logger.error(num_error % ('num_prom_cand', 'positive integer'))
-            raise ValueError(num_error % ('num_prom_cand', 'positive integer'))
-        return_vars['num_prom_cand'] = num_prom_cand
-        # end num_prom_cand
-
-        # iso_rmsd
-        try:
-            iso_rmsd = dos_inp.getfloat('Isolated', 'iso_rmsd',
-                                                  fallback=0.05)
-        except ValueError:
-            logger.error(num_error % ('iso_rmsd', 'positive decimal number'))
-            raise ValueError(num_error % ('iso_rmsd', 'positive decimal '
-                                                      'number'))
-        if iso_rmsd <= 0.0:
-            logger.error(num_error % ('iso_rmsd', 'positive decimal number'))
-            raise ValueError(num_error % ('iso_rmsd', 'positive decimal '
-                                                      'number'))
-        return_vars['iso_rmsd'] = iso_rmsd
-        # end iso_rmsd
-
-    #################
-    ### Screening ###
-    #################
+        # Mandatory options
+        # Checks whether the mandatory options are present.
+        iso_mand_opts = ['iso_inp_file']
+        for opt in iso_mand_opts:
+            if not dos_inp.has_option('Isolated', opt):
+                logger.error(no_opt_err % (opt, 'Isolated'))
+                raise NoOptionError(opt, 'Isolated')
+        return_vars['isol_inp_file'] = get_isol_inp_file()
+
+        # Facultative options (Default/Fallback value present)
+        return_vars['cluster_magns'] = get_cluster_magns()
+        return_vars['num_conformers'] = get_num_conformers()
+        return_vars['num_prom_cand'] = get_num_prom_cand()
+        return_vars['iso_rmsd'] = get_iso_rmsd()
+
+    # Screening
     if screening:
-        ### Mandatory options ###
         if not dos_inp.has_section('Screening'):
             logger.error(no_sect_err % 'Screening')
             raise NoSectionError('Screening')
-
-        # screen_inp_file
-        if 'screen_inp_file' not in dos_inp.options('Screening'):
-            logger.error(no_opt_err % ('screen_inp_file', 'Screening'))
-            raise NoOptionError('screen_inp_file', 'Screening')
-        screen_inp_file = dos_inp.get('Screening', 'screen_inp_file')
-        if not os.path.isfile(screen_inp_file):
-            logger.error(f'File {screen_inp_file} not found')
-            raise FileNotFoundError(f'File {screen_inp_file} not found')
-        return_vars['screen_inp_file'] = screen_inp_file
-        # end screen_inp_file
-
-        # Checks whether the mandatory options 'sites', 'molec_ads_ctrs', etc.
-        # are present in the Screening section
-        mand_opts = ['sites', 'molec_ads_ctrs']
-        for opt in mand_opts:
+        # Mandatory options:
+        # Checks whether the mandatory options are present.
+        screen_mand_opts = ['sites', 'molec_ads_ctrs', 'screen_inp_file']
+        for opt in screen_mand_opts:
             if not dos_inp.has_option('Screening', opt):
                 logger.error(no_opt_err % (opt, 'Screening'))
                 raise NoOptionError(opt, 'Screening')
-
-        # sites
-        sites_err = 'The value of sites should be a list of atom numbers ' \
-                    '(ie. positive integers) or groups of atom numbers ' \
-                    'grouped by parentheses-like enclosers. \n' \
-                    'eg. 128,(135 138;141) 87 {45, 68}'
-        try:
-            sites = str2lst(dos_inp.get('Screening', 'sites'))
-        except (ValueError, AttributeError):
-            logger.error(sites_err)
-            raise ValueError(sites_err)
-
-        # Check all elements of the list (of lists) are positive integers
-        for site in sites:
-            if type(site) is list:
-                for atom in site:
-                    if atom < 0:
-                        logger.error(sites_err)
-                        raise ValueError(sites_err)
-            elif type(site) is int:
-                if site < 0:
-                    logger.error(sites_err)
-                    raise ValueError(sites_err)
-            else:
-                logger.error(sites_err)
-                raise ValueError(sites_err)
-        return_vars['sites'] = sites
-        # end sites
-
-        # molec_ads_ctrs
-        molec_ads_err = 'The value of molec_ads_ctrs should be a list of atom' \
-                        ' numbers (ie. positive integers) or groups of atom ' \
-                        'numbers enclosed by parentheses-like characters. \n' \
-                        'eg. 128,(135 138;141) 87 {45, 68}'
-        try:
-            molec_ads_ctrs = str2lst(dos_inp.get('Screening', 'molec_ads_ctrs'))
-        except (ValueError, AttributeError):
-            logger.error(molec_ads_err)
-            raise ValueError(molec_ads_err)
-
-        # Check all elements of the list (of lists) are positive integers
-        for ctr in molec_ads_ctrs:
-            if type(ctr) is list:
-                for atom in ctr:
-                    if atom < 0:
-                        logger.error(molec_ads_err)
-                        raise ValueError(molec_ads_err)
-            elif type(ctr) is int:
-                if ctr < 0:
-                    logger.error(molec_ads_err)
-                    raise ValueError(molec_ads_err)
-            else:
-                logger.error(molec_ads_err)
-                raise ValueError(molec_ads_err)
-        return_vars['molec_ads_ctrs'] = molec_ads_ctrs
-        # end molec_ads_ctrs
-
-        ### Facultative options (Default value present) ###
-
-        # try_disso
-        try:
-            try_disso = dos_inp.getboolean('Screening', 'try_disso',
-                                           fallback=False)
-        except ValueError:
-            disso_err = 'try_disso should be have a boolean value (True or ' \
-                        'False) '
-            logger.error(disso_err)
-            raise ValueError(disso_err)
-        return_vars['try_disso'] = try_disso
-        # end try_disso
-
-        # sample_points_per_angle
-        try:
-            sample_points_per_angle = dos_inp.getint('Screening',
-                                                     'sample_points_per_angle',
-                                                     fallback=3)
-        except ValueError:
-            logger.error(num_error % ('sample_points_per_angle',
-                                      'positive integer'))
-            raise ValueError(num_error % ('sample_points_per_angle',
-                                          'positive integer'))
-        if sample_points_per_angle < 1:
-            logger.error(num_error % ('sample_points_per_angle',
-                                      'positive integer'))
-            raise ValueError(num_error % ('sample_points_per_angle',
-                                          'positive integer'))
-        return_vars['sample_points_per_angle'] = sample_points_per_angle
-        # end sample_points_per_angle
-
-        # collision_threshold
-        try:
-            collision_threshold = dos_inp.getfloat('Screening',
-                                                   'collision_threshold',
-                                                   fallback=1.2)
-        except ValueError:
-            logger.error(num_error % ('collision_threshold',
-                                      'positive decimal number'))
-            raise ValueError(num_error % ('collision_threshold',
-                                          'positive decimal number'))
-        return_vars['collision_threshold'] = collision_threshold
-        if collision_threshold <= 0:
-            logger.error(num_error % ('collision_threshold',
-                                      'positive decimal number'))
-            raise ValueError(num_error % ('collision_threshold',
-                                          'positive decimal number'))
-        # end collision_threshold
-
-        # screen_rmsd
-        try:
-            screen_rmsd = dos_inp.getfloat('Screening',
-                                                     'screen_rmsd',
-                                                     fallback=0.05)
-        except ValueError:
-            logger.error(num_error % ('screen_rmsd', 'positive decimal number'))
-            raise ValueError(num_error % ('screen_rmsd', 'positive decimal '
-                                                         'number'))
-        if screen_rmsd <= 0:
-            logger.error(num_error % ('screen_rmsd', 'positive decimal number'))
-            raise ValueError(num_error % ('screen_rmsd', 'positive decimal '
-                                                         'number'))
-        return_vars['screen_rmsd'] = screen_rmsd
-        # end screen_rmsd
-
-        # collision_bottom_z
-        collision_bottom_z = dos_inp.get('Screening', 'collision_bottom_z',
-                                         fallback="False")
-        if collision_bottom_z.lower() in turn_false_answers:
-            collision_bottom_z = False
-        else:
-            try:
-                collision_bottom_z = float(collision_bottom_z)
-            except ValueError:
-                logger.error(num_error % ('collision_bottom_z',
-                                          'decimal number'))
-                raise ValueError(num_error % ('collision_bottom_z',
-                                              'decimal number'))
-        return_vars['collision_bottom_z'] = collision_bottom_z
-        # end collision_bottom_z
-
-    ##################
-    ### Refinement ###
-    ##################
+        return_vars['screen_inp_file'] = get_screen_inp_file()
+        return_vars['sites'] = get_sites()
+        return_vars['molec_ads_ctrs'] = get_molec_ads_ctrs()
+
+        # Facultative options (Default value present)
+        return_vars['try_disso'] = get_try_disso()
+        return_vars['sample_points_per_angle'] = get_pts_per_angle()
+        return_vars['collision_threshold'] = get_coll_thrsld()
+        return_vars['screen_rmsd'] = get_screen_rmsd()
+        return_vars['collision_bottom_z'] = get_coll_bottom_z()
+
+    # Refinement
     if refinement:
-        ### Mandatory options ###
-        # refine_inp_file
-        if 'refine_inp_file' not in dos_inp.options('Refinement'):
-            logger.error(no_opt_err % ('refine_inp_file', 'Refinement'))
-            raise NoOptionError('refine_inp_file', 'Refinement')
-        refine_inp_file = dos_inp.get('Refinement', 'refine_inp_file')
-        if not os.path.isfile(refine_inp_file):
-            logger.error(f'File {refine_inp_file} not found')
-            raise FileNotFoundError(f'File {refine_inp_file} not found')
-        return_vars['refine_inp_file'] = refine_inp_file
-        # end refine_inp_file
-
-        ### Facultative options (Default value present) ###
-        # energy_cutoff
-        try:
-            energy_cutoff = dos_inp.getfloat('Refinement', 'energy_cutoff',
-                                             fallback=0.5)
-        except ValueError:
-            logger.error(num_error % ('energy_cutoff',
-                                      'positive decimal number'))
-            raise ValueError(num_error % ('energy_cutoff',
-                                          'positive decimal number'))
-        if energy_cutoff < 0:
-            logger.error(num_error % ('energy_cutoff',
-                                      'positive decimal number'))
-            raise ValueError(num_error % ('energy_cutoff',
-                                          'positive decimal number'))
-        return_vars['energy_cutoff'] = energy_cutoff
+        if not dos_inp.has_section('Refinement'):
+            logger.error(no_sect_err % 'Refinement')
+            raise NoSectionError('Refinement')
+        # Mandatory options
+        # Checks whether the mandatory options are present.
+        ref_mand_opts = ['refine_inp_file']
+        for opt in ref_mand_opts:
+            if not dos_inp.has_option('Refinement', opt):
+                logger.error(no_opt_err % (opt, 'Refinement'))
+                raise NoOptionError(opt, 'Refinement')
+        return_vars['refine_inp_file'] = get_refine_inp_file()
+
+        # Facultative options (Default value present)
+        return_vars['energy_cutoff'] = get_energy_cutoff()
         # end energy_cutoff
 
     return return_vars
+
+
+if __name__ == "__main__":
+    import sys
+
+    print(read_input(sys.argv[1]))
