summaryrefslogtreecommitdiff
path: root/lib/spack/llnl/util/lang.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/llnl/util/lang.py')
-rw-r--r--lib/spack/llnl/util/lang.py183
1 files changed, 156 insertions, 27 deletions
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index 7189b6c0f3..0a86896f5b 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -1,27 +1,8 @@
-##############################################################################
-# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
+# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/spack/spack
-# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# 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 terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser 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
-##############################################################################
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
from __future__ import division
import os
@@ -29,7 +10,7 @@ import re
import functools
import collections
import inspect
-from datetime import datetime
+from datetime import datetime, timedelta
from six import string_types
# Ignore emacs backups when listing modules
@@ -165,6 +146,26 @@ def has_method(cls, name):
return False
+def union_dicts(*dicts):
+ """Use update() to combine all dicts into one.
+
+ This builds a new dictionary, into which we ``update()`` each element
+ of ``dicts`` in order. Items from later dictionaries will override
+ items from earlier dictionaries.
+
+ Args:
+ dicts (list): list of dictionaries
+
+ Return: (dict): a merged dictionary containing combined keys and
+ values from ``dicts``.
+
+ """
+ result = {}
+ for d in dicts:
+ result.update(d)
+ return result
+
+
class memoized(object):
"""Decorator that caches the results of a function, storing them
in an attribute of that function."""
@@ -282,8 +283,8 @@ class HashableMap(collections.MutableMapping):
def copy(self):
"""Type-agnostic clone method. Preserves subclass type."""
# Construct a new dict of my type
- T = type(self)
- clone = T()
+ self_type = type(self)
+ clone = self_type()
# Copy everything from this dict into it.
for key in self:
@@ -442,6 +443,65 @@ def pretty_date(time, now=None):
return str(diff) + " years ago"
+def pretty_string_to_date(date_str, now=None):
+ """Parses a string representing a date and returns a datetime object.
+
+ Args:
+ date_str (str): string representing a date. This string might be
+ in different format (like ``YYYY``, ``YYYY-MM``, ``YYYY-MM-DD``)
+ or be a *pretty date* (like ``yesterday`` or ``two months ago``)
+
+ Returns:
+ (datetime): datetime object corresponding to ``date_str``
+ """
+
+ pattern = {}
+
+ now = now or datetime.now()
+
+ # datetime formats
+ pattern[re.compile(r'^\d{4}$')] = lambda x: datetime.strptime(x, '%Y')
+ pattern[re.compile(r'^\d{4}-\d{2}$')] = lambda x: datetime.strptime(
+ x, '%Y-%m'
+ )
+ pattern[re.compile(r'^\d{4}-\d{2}-\d{2}$')] = lambda x: datetime.strptime(
+ x, '%Y-%m-%d'
+ )
+
+ pretty_regex = re.compile(
+ r'(a|\d+)\s*(year|month|week|day|hour|minute|second)s?\s*ago')
+
+ def _n_xxx_ago(x):
+ how_many, time_period = pretty_regex.search(x).groups()
+
+ how_many = 1 if how_many == 'a' else int(how_many)
+
+ # timedelta natively supports time periods up to 'weeks'.
+ # To apply month or year we convert to 30 and 365 days
+ if time_period == 'month':
+ how_many *= 30
+ time_period = 'day'
+ elif time_period == 'year':
+ how_many *= 365
+ time_period = 'day'
+
+ kwargs = {(time_period + 's'): how_many}
+ return now - timedelta(**kwargs)
+
+ pattern[pretty_regex] = _n_xxx_ago
+
+ # yesterday
+ callback = lambda x: now - timedelta(days=1)
+ pattern[re.compile('^yesterday$')] = callback
+
+ for regexp, parser in pattern.items():
+ if bool(regexp.match(date_str)):
+ return parser(date_str)
+
+ msg = 'date "{0}" does not match any valid format'.format(date_str)
+ raise ValueError(msg)
+
+
class RequiredAttributeError(ValueError):
def __init__(self, message):
@@ -458,5 +518,74 @@ class ObjectWrapper(object):
def __init__(self, wrapped_object):
wrapped_cls = type(wrapped_object)
wrapped_name = wrapped_cls.__name__
- self.__class__ = type(wrapped_name, (type(self), wrapped_cls), {})
+
+ # If the wrapped object is already an ObjectWrapper, or a derived class
+ # of it, adding type(self) in front of type(wrapped_object)
+ # results in an inconsistent MRO.
+ #
+ # TODO: the implementation below doesn't account for the case where we
+ # TODO: have different base classes of ObjectWrapper, say A and B, and
+ # TODO: we want to wrap an instance of A with B.
+ if type(self) not in wrapped_cls.__mro__:
+ self.__class__ = type(wrapped_name, (type(self), wrapped_cls), {})
+ else:
+ self.__class__ = type(wrapped_name, (wrapped_cls,), {})
+
self.__dict__ = wrapped_object.__dict__
+
+
+class Singleton(object):
+ """Simple wrapper for lazily initialized singleton objects."""
+
+ def __init__(self, factory):
+ """Create a new singleton to be inited with the factory function.
+
+ Args:
+ factory (function): function taking no arguments that
+ creates the singleton instance.
+ """
+ self.factory = factory
+ self._instance = None
+
+ @property
+ def instance(self):
+ if self._instance is None:
+ self._instance = self.factory()
+ return self._instance
+
+ def __getattr__(self, name):
+ return getattr(self.instance, name)
+
+ def __getitem__(self, name):
+ return self.instance[name]
+
+ def __contains__(self, element):
+ return element in self.instance
+
+ def __iter__(self):
+ return iter(self.instance)
+
+ def __str__(self):
+ return str(self.instance)
+
+ def __repr__(self):
+ return repr(self.instance)
+
+
+class LazyReference(object):
+ """Lazily evaluated reference to part of a singleton."""
+
+ def __init__(self, ref_function):
+ self.ref_function = ref_function
+
+ def __getattr__(self, name):
+ return getattr(self.ref_function(), name)
+
+ def __getitem__(self, name):
+ return self.ref_function()[name]
+
+ def __str__(self):
+ return str(self.ref_function())
+
+ def __repr__(self):
+ return repr(self.ref_function())