nemo_bdy_setup.py 11 KB
Newer Older
James Harle's avatar
James Harle committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
# ===================================================================
# The contents of this file are dedicated to the public domain.  To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================

'''
Created on Wed Sep 12 08:02:46 2012

Parses a file to find out which nemo boundary settings to use 

@author John Kazimierz Farey
@author James Harle
$Last commit on:$
'''

# pylint: disable=E1103
# pylint: disable=no-name-in-module

#External imports
from collections import OrderedDict
import os
import logging

class Setup(object):
    '''
    Invoke with a text file location, class init reads and deciphers variables.

    attribute <settings> is a dict holding all the vars.
    '''
    """ This class holds the settings information """
    def __init__(self, setfile):
        """ Constructor, reads the settings file and sets the dictionary with setting name/key and
        it's value """
        #Logging for class
        self.logger = logging.getLogger(__name__)
        self.filename = setfile
        if not setfile: # debug
            self.filename = '../data/namelist.bdy'
        self._load_settings()
        self.variable_info = self.variable_info_reader(self.filename+'.info')

    def refresh(self):
        """ Re loads the settings from file """
        self._load_settings()
        
    def _load_settings(self):
        """ Loads the settings from file """
        try:
            namelist = open(self.filename, 'r')
        except:
            self.logger.error("Cannot open the file:"+self.filename)
            raise
        data = namelist.readlines()
        # Dictionary of all the vars in the file and a seperate settings for boolean values
        self.settings, self.bool_settings = _assign(_trim(data))
        namelist.close()
                
    def _get_var_name_value(self, line):
        """ splits the line into key value pair. """
        key_value = line.split("=", 2)
        name_prefix = key_value[0][0:2].lower()
        name = key_value[0][3:].lower().strip() # 3 -> 0 to keep type info
        value_str = key_value[1].strip()
        index = '-1'
        value = ''
        if name_prefix == 'ln':
            if value_str.find('true') is not -1:
                value = True
            elif value_str.find('false') is not -1:
                value = False
            else:
                raise ValueError('Cannot assign %s to %s, must be boolean' %(value_str, name))

        elif name_prefix == 'rn' or name_prefix == 'nn':
            if value_str.find('.') > -1 or value_str.find('e') > -1:
                try:
                    value = float(value_str)
                except ValueError:
                    self.logger.error('Cannot assign %s to %s, must be float')
                    raise
            else:
                try:
                    value = int(value_str)
                except ValueError:
                    self.logger.error('Cannot assign %s to %s, must be integer')
                    raise
        elif name_prefix == 'cn' or name_prefix == 'sn':
            value = value_str.strip("'")
102 103
            if name == 'dst_dir':
                value = os.path.join(value, '')
James Harle's avatar
James Harle committed
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
        elif name_prefix == 'cl':
            name = key_value[0].split('(')
            index = name[1].split(')')
            name = name[0].strip()
            index = index[0].strip()
            value = value_str
        else:
            raise ValueError('%s data type is ambiguous' %key_value)
        return name, index, value

    def write(self):
        '''
        This method write backs the variable data back into the file
        '''
        try:
            namelist = open(self.filename, 'r')
        except:
            self.logger.error("Cannot open the file:"+self.filename+" to write ")
            raise
        data = namelist.readlines()

        for name in self.settings:
            values = self.settings[name]
            if type(values).__name__ != 'dict':
                values = {'-1': values}
            for index, value in values.iteritems():
                count = -1
                for line in data:
                    count = count + 1
                    #find the variable
                    line_without_comments = strip_comments(line)
                    if line_without_comments == '':
                        continue
                    data_name, data_index, data_value = self._get_var_name_value(line_without_comments)

                    if data_name == name:
                        #found the variable line
                        if data_index == index:
                            if type(data_value).__name__ == 'bool' \
                                and \
                               type(value).__name__ != 'bool':
                                data[count] = _replace_var_value(line, data_value,\
                                                                 self.bool_settings[name])
                                continue

                            if data_value == value:
                                break
                            else:
                                data[count] = _replace_var_value(line, data_value, value)
                                break
        namelist.close()
        namelist = open(self.filename, 'w')
        namelist.truncate()
        namelist.writelines(data)
        namelist.close()

    def variable_info_reader(self, filename):
        """ This method reads the variable description data from 'variable.info' file in the pynemo installation path
        if it can't find the file with the same name as input bdy file with extension .info
        Keyword arguments:
        filename -- filename of the variables information
        returns a dictionary with variable name and its description
        """
        variable_info = {}               
        if filename is None or not os.path.exists(filename):
            #Read the default file
            file_path, dummy = os.path.split(__file__)
            filename = os.path.join(file_path,'variable.info')
        try:
            namelist = open(filename, 'r')
            data = namelist.readlines()
            for line in data:
                name = _get_var_name(line)
                value = line.split("=", 1)[1]
                variable_info[name[0]] = value.strip()                           
        except IOError:
            self.logger.error("Cannot open the  variable file:"+filename)
        return variable_info
        
            
def _trim(data):
    """ Trims the sets of lines removing empty lines/whitespaces and removing comments
    which start with ! """
    newdata = []
    while data:
        line = data.pop(0)
        line = line.rsplit('!')[0].strip()
        if line is not '':
            newdata.append(line)
    return newdata

def _get_val(vars_dictionary, bool_vars_dictionary, line):
    """ traverses input string and appends the setting name and its value to dictionary
    of settings and also if the setting name holds a boolean value then to the dictionary
    of boolean variables. checks the type and raises error for ambiguous values"""

    logger = logging.getLogger(__name__)
    name_prefix = line[0][0:2].lower()
    name = line[0][3:].lower().strip() # 3 -> 0 to keep type info
    value = line[1].strip()

    if name_prefix == 'ln':
        if value.find('true') is not -1:
            if vars_dictionary.has_key(name) != True:
                vars_dictionary[name] = True
            bool_vars_dictionary[name] = True
        elif value.find('false') is not -1:
            if vars_dictionary.has_key(name) != True:
                vars_dictionary[name] = False
            bool_vars_dictionary[name] = False
        else:
            raise ValueError('Cannot assign %s to %s, must be boolean' %(value, name))

    elif name_prefix == 'rn' or name_prefix == 'nn':
        if value.find('.') > -1 or value.find('e') > -1:
            try:
                vars_dictionary[name] = float(value)
            except ValueError:
                logger.error('Cannot assign %s to %s, must be float')
                raise
        else:
            try:
                vars_dictionary[name] = int(value)
            except ValueError:
                logger.error('Cannot assign %s to %s, must be integer')
                raise
    elif name_prefix == 'cn' or name_prefix == 'sn':
        vars_dictionary[name] = value.strip("'")
232 233
        if name == 'dst_dir':
            vars_dictionary[name] = os.path.join(vars_dictionary[name], '')
James Harle's avatar
James Harle committed
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
    elif name_prefix == 'cl':
        name = line[0].split('(')
        index = name[1].split(')')
        if name[0].strip() not in vars_dictionary.keys():
            vars_dictionary[name[0].strip()] = {}
        vars_dictionary[name[0].strip()][index[0].strip()] = value.strip()
    else:
        raise ValueError('%s data type is ambiguous' %line)

def _replace_var_value(original_line, value, new_value):
    """ replaces the variable name value with new_value in the original_line"""
    if type(value).__name__ == 'bool':
        value = str(value).lower()
        new_value = str(new_value).lower()
    elif type(value).__name__ == 'str': #an empty string need to replace with ''
        if value == '':
            value = '\'\''
            new_value = '\''+new_value+'\''
    return original_line.replace(str(value), str(new_value), 1)

def _get_var_name(line):
    """ parses the line to find the name and if it is part of the array '()'
    then returns name of variable and index of the array. if variable is not
    array then only variable name and -1 in index"""
    name_value = line.split("=", 1)
    name_prefix = name_value[0][0:2].lower()
    name = name_value[0][3:].lower().strip() # 3 -> 0 to keep type info
    if name_prefix in ['ln', 'rn', 'nn', 'cn', 'sn']:
        return name, -1
    elif name_prefix == 'cl':
        name = name_value[0].split('(')
        index = name[1].split(')')
        return name[0], index[0]

    # Returns tidy dictionary of var names and values
def _assign(data):
    """ return a dictionary of variables and also special dictionary for boolean variable """
    vars_dictionary = OrderedDict({})
    bool_vars_dictionary = OrderedDict({})
    for line in data:
        keyvalue = line.split('=', 1)
        _get_val(vars_dictionary, bool_vars_dictionary, keyvalue)
    return vars_dictionary, bool_vars_dictionary

def strip_comments(line):
    """ strips the comments in the line. removes text after ! """
    line = line.rsplit('!')[0].strip()
    return line