#!/usr/bin/env python
# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
"""
BitBake 'Data' implementations

Functions for interacting with the data structure used by the
BitBake build tools.

Copyright (C) 2003, 2004  Chris Larson

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA. 

Based on functions from the base bb module, Copyright 2003 Holger Schurig
"""

import sys, os, re, time, types
if sys.argv[0][-5:] == "pydoc":
    path = os.path.dirname(os.path.dirname(sys.argv[1]))
else:
    path = os.path.dirname(os.path.dirname(sys.argv[0]))
sys.path.append(path)

from bb import note, debug

def init():
    return {}

_data = init()

def initVar(var, d = _data):
    """Non-destructive var init for data structure"""
    if not var in d:
        d[var] = {}

    if not "flags" in d[var]:
        d[var]["flags"] = {}

__setvar_regexp__ = {}
__setvar_regexp__["_append"]  = re.compile('(?P<base>.*?)%s(_(?P<add>.*))?' % "_append")
__setvar_regexp__["_prepend"] = re.compile('(?P<base>.*?)%s(_(?P<add>.*))?' % "_prepend")
__setvar_regexp__["_delete"]  = re.compile('(?P<base>.*?)%s(_(?P<add>.*))?' % "_delete")

def setVar(var, value, d = _data):
    """Set a variable to a given value

    Example:
        >>> setVar('TEST', 'testcontents')
        >>> print getVar('TEST')
        testcontents
    """
    for v in ["_append", "_prepend", "_delete"]:
        match = __setvar_regexp__[v].match(var)
        if match:
            base = match.group('base')
            override = match.group('add')
            l = getVarFlag(base, v, d) or []
            if override == 'delete':
                if l.count([value, None]):
                    del l[l.index([value, None])]
            l.append([value, override])
            setVarFlag(base, v, l, d)
            return

    if not var in d:
        initVar(var, d)
    if getVarFlag(var, 'matchesenv', d):
        delVarFlag(var, 'matchesenv', d)
        setVarFlag(var, 'export', 1, d)
    d[var]["content"] = value

def getVar(var, d = _data, exp = 0):
    """Gets the value of a variable

    Example:
        >>> setVar('TEST', 'testcontents')
        >>> print getVar('TEST')
        testcontents
    """
    if not var in d or not "content" in d[var]:
        return None
    if exp:
        return expand(d[var]["content"], d, var)
    return d[var]["content"]

def delVar(var, d = _data):
    """Removes a variable from the data set

    Example:
        >>> setVar('TEST', 'testcontents')
        >>> print getVar('TEST')
        testcontents
        >>> delVar('TEST')
        >>> print getVar('TEST')
        None
    """
    if var in d:
        del d[var]

def setVarFlag(var, flag, flagvalue, d = _data):
    """Set a flag for a given variable to a given value

    Example:
        >>> setVarFlag('TEST', 'python', 1)
        >>> print getVarFlag('TEST', 'python')
        1
    """
#   print "d[%s][\"flags\"][%s] = %s" % (var, flag, flagvalue)
    if not var in d:
        initVar(var, d)
    d[var]["flags"][flag] = flagvalue

def getVarFlag(var, flag, d = _data):
    """Gets given flag from given var

    Example:
        >>> setVarFlag('TEST', 'python', 1)
        >>> print getVarFlag('TEST', 'python')
        1
    """
    if var in d and "flags" in d[var] and flag in d[var]["flags"]:
        return d[var]["flags"][flag]
    return None

def delVarFlag(var, flag, d = _data):
    """Removes a given flag from the variable's flags

    Example:
        >>> setVarFlag('TEST', 'testflag', 1)
        >>> print getVarFlag('TEST', 'testflag')
        1
        >>> delVarFlag('TEST', 'testflag')
        >>> print getVarFlag('TEST', 'testflag')
        None

    """
    if var in d and "flags" in d[var] and flag in d[var]["flags"]:
        del d[var]["flags"][flag]

def setVarFlags(var, flags, d = _data):
    """Set the flags for a given variable

    Example:
        >>> myflags = {}
        >>> myflags['test'] = 'blah'
        >>> setVarFlags('TEST', myflags)
        >>> print getVarFlag('TEST', 'test')
        blah
    """
    if not var in d:
        initVar(var, d)
    d[var]["flags"] = flags

def getVarFlags(var, d = _data):
    """Gets a variable's flags

    Example:
        >>> setVarFlag('TEST', 'test', 'blah')
        >>> print getVarFlags('TEST')['test']
        blah
    """
    if var in d and "flags" in d[var]:
        return d[var]["flags"]
    return None

def delVarFlags(var, d = _data):
    """Removes a variable's flags

    Example:
        >>> setVarFlag('TEST', 'testflag', 1)
        >>> print getVarFlag('TEST', 'testflag')
        1
        >>> delVarFlags('TEST')
        >>> print getVarFlags('TEST')
        None

    """
    if var in d and "flags" in d[var]:
        del d[var]["flags"]

def getData(d = _data):
    """Returns the data object used"""
    return d

def setData(newData, d = _data):
    """Sets the data object to the supplied value"""
    d = newData

__expand_var_regexp__ = re.compile(r"\${[^{}]+}")
__expand_python_regexp__ = re.compile(r"\${@.+?}")

def expand(s, d = _data, varname = None):
    """Variable expansion using the data store.

    Example:
        Standard expansion:
        >>> setVar('A', 'sshd')
        >>> print expand('/usr/bin/${A}')
        /usr/bin/sshd

        Python expansion:
        >>> print expand('result: ${@37 * 72}')
        result: 2664
    """
    def var_sub(match):
        key = match.group()[2:-1]
        if varname and key:
            if varname == key:
                raise Exception("variable %s references itself!" % varname)
        var = getVar(key, d, 1)
        if var is not None:
            return var
        else:
            return match.group()

    def python_sub(match):
        import bb
        code = match.group()[3:-1]
        locals()['d'] = d
        s = eval(code)
        if type(s) == types.IntType: s = str(s)
        return s

    if type(s) is not types.StringType: # sanity check
        return s

    while s.find('$') != -1:
        olds = s
        try:
            s = __expand_var_regexp__.sub(var_sub, s)
            s = __expand_python_regexp__.sub(python_sub, s)
            if s == olds: break
            if type(s) is not types.StringType: # sanity check
                import bb
                bb.error('expansion of %s returned non-string %s' % (olds, s))
        except KeyboardInterrupt:
            raise
        except:
            note("%s:%s while evaluating:\n%s" % (sys.exc_info()[0], sys.exc_info()[1], s))
            raise
    return s

def expandKeys(alterdata = _data, readdata = None):
    if readdata == None:
        readdata = alterdata

    for key in alterdata.keys():
        ekey = expand(key, readdata)
        if key == ekey:
            continue
        val = getVar(key, alterdata)
        if val is None:
            continue
#        import copy
#        setVarFlags(ekey, copy.copy(getVarFlags(key, readdata)), alterdata)
        setVar(ekey, val, alterdata)

        for i in ('_append', '_prepend', '_delete'):
            dest = getVarFlag(ekey, i, alterdata) or []
            src = getVarFlag(key, i, readdata) or []
            dest.extend(src)
            setVarFlag(ekey, i, dest, alterdata)

        delVar(key, alterdata)

def expandData(alterdata = _data, readdata = None):
    """For each variable in alterdata, expand it, and update the var contents.
       Replacements use data from readdata.

    Example:
        >>> a=init()
        >>> b=init()
        >>> setVar("dlmsg", "dl_dir is ${DL_DIR}", a)
        >>> setVar("DL_DIR", "/path/to/whatever", b)
        >>> expandData(a, b)
        >>> print getVar("dlmsg", a)
        dl_dir is /path/to/whatever
       """
    if readdata == None:
        readdata = alterdata

    for key in alterdata.keys():
        val = getVar(key, alterdata)
        if type(val) is not types.StringType:
            continue
        expanded = expand(val, readdata)
#       print "key is %s, val is %s, expanded is %s" % (key, val, expanded)
        if val != expanded:
            setVar(key, expanded, alterdata)

import os

def inheritFromOS(d = _data):
    """Inherit variables from the environment."""
#   fakeroot needs to be able to set these
    non_inherit_vars = [ "LD_LIBRARY_PATH", "LD_PRELOAD" ]
    for s in os.environ.keys():
        if not s in non_inherit_vars:
            try:
                setVar(s, os.environ[s], d)
                setVarFlag(s, 'matchesenv', '1', d)
            except TypeError:
                pass

import sys

def emit_var(var, o=sys.__stdout__, d = _data, all=False):
    """Emit a variable to be sourced by a shell."""
    if getVarFlag(var, "python", d):
        return 0

    try:
        if all:
            oval = getVar(var, d, 0)
        val = getVar(var, d, 1)
    except KeyboardInterrupt:
        raise
    except:
        excname = str(sys.exc_info()[0])
        if excname == "bb.build.FuncFailed":
            raise
        o.write('# expansion of %s threw %s\n' % (var, excname))
        return 0

    if all:
        o.write('# %s=%s\n' % (var, oval))

    if type(val) is not types.StringType:
        return 0

    if getVarFlag(var, 'matchesenv', d):
        return 0

    if (var.find("-") != -1 or var.find(".") != -1 or var.find('{') != -1 or var.find('}') != -1 or var.find('+') != -1) and not all:
        return 0

    val.rstrip()
    if not val:
        return 0

    if getVarFlag(var, "func", d):
#       NOTE: should probably check for unbalanced {} within the var
        o.write("%s() {\n%s\n}\n" % (var, val))
    else:
        if getVarFlag(var, "export", d):
            o.write('export ')
        else:
            if not all:
                return 0
#       if we're going to output this within doublequotes,
#       to a shell, we need to escape the quotes in the var
        alter = re.sub('"', '\\"', val.strip())
        o.write('%s="%s"\n' % (var, alter))
    return 1


def emit_env(o=sys.__stdout__, d = _data, all=False):
    """Emits all items in the data store in a format such that it can be sourced by a shell."""

    env = d.keys()

    for e in env:
        if getVarFlag(e, "func", d):
            continue
        emit_var(e, o, d, all) and o.write('\n')

    for e in env:
        if not getVarFlag(e, "func", d):
            continue
        emit_var(e, o, d) and o.write('\n')

def update_data(d = _data):
    """Modifies the environment vars according to local overrides and commands.
    Examples:
        Appending to a variable:
        >>> setVar('TEST', 'this is a')
        >>> setVar('TEST_append', ' test')
        >>> setVar('TEST_append', ' of the emergency broadcast system.')
        >>> update_data()
        >>> print getVar('TEST')
        this is a test of the emergency broadcast system.

        Prepending to a variable:
        >>> setVar('TEST', 'virtual/libc')
        >>> setVar('TEST_prepend', 'virtual/tmake ')
        >>> setVar('TEST_prepend', 'virtual/patcher ')
        >>> update_data()
        >>> print getVar('TEST')
        virtual/patcher virtual/tmake virtual/libc

        Overrides:
        >>> setVar('TEST_arm', 'target')
        >>> setVar('TEST_ramses', 'machine')
        >>> setVar('TEST_local', 'local')
        >>> setVar('OVERRIDES', 'arm')

        >>> setVar('TEST', 'original')
        >>> update_data()
        >>> print getVar('TEST')
        target

        >>> setVar('OVERRIDES', 'arm:ramses:local')
        >>> setVar('TEST', 'original')
        >>> update_data()
        >>> print getVar('TEST')
        local
    """

    debug(2, "update_data()")

#   can't do delete env[...] while iterating over the dictionary, so remember them
    dodel = []
    overrides = (getVar('OVERRIDES', d, 1) or "").split(':') or []

    def applyOverrides(var, d = _data):
        if not overrides:
            debug(1, "OVERRIDES not defined, nothing to do")
            return
        val = getVar(var, d)
        for o in overrides:
            if var.endswith("_" + o):
                l = len(o)+1
                name = var[:-l]
                d[name] = d[var]

    for s in d.keys():
        applyOverrides(s, d)
        sval = getVar(s, d) or ""

#       Handle line appends:
        for (a, o) in getVarFlag(s, '_append', d) or []:
            # maybe the OVERRIDE was not yet added so keep the append
            if (o and o in overrides) or not o:
                delVarFlag(s, '_append', d)
            if o:
                if not o in overrides:
                    continue
            sval+=a
            setVar(s, sval, d)

#       Handle line prepends
        for (a, o) in getVarFlag(s, '_prepend', d) or []:
            # maybe the OVERRIDE was not yet added so keep the append
            if (o and o in overrides) or not o:
                delVarFlag(s, '_prepend', d)
            if o:
                if not o in overrides:
                    continue
            sval=a+sval
            setVar(s, sval, d)

#       Handle line deletions
        name = s + "_delete"
        nameval = getVar(name, d)
        if nameval:
            sval = getVar(s, d)
            if sval:
                new = ''
                pattern = nameval.replace('\n','').strip()
                for line in sval.split('\n'):
                    if line.find(pattern) == -1:
                        new = new + '\n' + line
                setVar(s, new, d)
                dodel.append(name)

#   delete all environment vars no longer needed
    for s in dodel:
        delVar(s, d)

def inherits_class(klass, d):
    val = getVar('__inherit_cache', d) or ""
    if os.path.join('classes', '%s.bbclass' % klass) in val.split():
        return True
    return False

def _test():
    """Start a doctest run on this module"""
    import doctest
    from bb import data
    doctest.testmod(data)

if __name__ == "__main__":
    _test()
