Source code for hyou.util

# Copyright 2015 Google Inc. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import (
    absolute_import, division, print_function, unicode_literals)

import json

import oauth2client.client
import oauth2client.service_account
import six
import string

from . import py3


SCOPES = (
    'https://spreadsheets.google.com/feeds',
    'https://www.googleapis.com/auth/drive',
)


def check_type(value, expected_type):
    if not isinstance(value, expected_type):
        raise TypeError(
            'Expected %s, got %s' % (
                expected_type.__name__, type(value).__name__))


def format_column_address(index_column):
    letters = []
    while index_column >= 0:
        letters.append(string.ascii_uppercase[index_column % 26])
        index_column = index_column // 26 - 1
    return ''.join(reversed(letters))


def format_range_a1_notation(
        worksheet_title, start_row, end_row, start_col, end_col):
    return '\'%s\'!%s%d:%s%d' % (
        worksheet_title.replace('\'', '\'\''),
        format_column_address(start_col),
        start_row + 1,
        format_column_address(end_col - 1),
        end_row)


def parse_credentials(json_text):
    json_data = json.loads(json_text)
    if '_module' in json_data:
        return oauth2client.client.Credentials.new_from_json(
            json_text)
    elif 'private_key' in json_data:
        return (
            oauth2client.service_account.ServiceAccountCredentials
            .from_json_keyfile_dict(
                json_data,
                scopes=SCOPES))
    raise ValueError('unrecognized credential format')


class LazyOrderedDictionary(object):

    def __init__(self, enumerator, constructor):
        self._enumerator = enumerator
        self._constructor = constructor
        self._cache_list = []   # [(key, value)]
        self._cache_index = {}  # key -> index of _cache_list
        self._enumerated = False

    def refresh(self):
        del self._cache_list[:]
        self._cache_index.clear()
        self._enumerated = False

    def __len__(self):
        self._ensure_enumerated()
        return len(self._cache_list)

    def __iter__(self):
        return self.iterkeys()

    def iterkeys(self):
        self._ensure_enumerated()
        for key, _ in self._cache_list:
            yield key

    def itervalues(self):
        for _, value in self.iteritems():
            yield value

    def iteritems(self):
        self._ensure_enumerated()
        for key, value in self._cache_list:
            yield (key, value)

    def keys(self):
        return list(self.iterkeys())

    def values(self):
        return list(self.itervalues())

    def items(self):
        return list(self.iteritems())

    def __getitem__(self, key):
        if isinstance(key, six.integer_types):
            self._ensure_enumerated()
            return self._cache_list[key][1]
        index = self._cache_index.get(key)
        if index is not None:
            return self._cache_list[index][1]
        if self._constructor:
            value = self._constructor(key)
            if value is None:
                raise KeyError(key)
            index = len(self._cache_list)
            self._cache_index[key] = index
            self._cache_list.append((key, value))
            return value
        self._ensure_enumerated()
        index = self._cache_index.get(key)
        if index is None:
            raise KeyError(key)
        return self._cache_list[index][1]

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

    def _ensure_enumerated(self):
        if self._enumerated:
            return
        # Save partially constructed entries.
        saves = self._cache_list[:]
        # Initialize cache with the enumerator.
        del self._cache_list[:]
        self._cache_index.clear()
        for key, value in self._enumerator():
            self._cache_index[key] = len(self._cache_list)
            self._cache_list.append((key, value))
        # Restore saved entries.
        for key, value in saves:
            index = self._cache_index.get(key)
            if index is None:
                index = len(self._cache_list)
                self._cache_list.append((None, None))
            self._cache_list[index] = (key, value)
        self._enumerated = True


class CustomMutableFixedList(object):
    """Provides methods to mimic a mutable fixed-size Python list.

    Subclasses need to provide implementation of at least following methods:
    - __getitem__
    - __setitem__
    - __iter__
    - __len__
    """

    def __bool__(self):
        return len(self) > 0

    __nonzero__ = __bool__  # For Python 2

    def __eq__(self, other):
        if len(self) != len(other):
            return False
        for a, b in py3.zip(self, other):
            if a != b:
                return False
        return True

    def __ne__(self, other):
        return not (self == other)

    def __lt__(self, other):
        for a, b in py3.zip(self, other):
            if a != b:
                return a < b
        return len(self) < len(other)

    def __le__(self, other):
        for a, b in py3.zip(self, other):
            if a != b:
                return a < b
        return len(self) <= len(other)

    def __gt__(self, other):
        return not (self <= other)

    def __ge__(self, other):
        return not (self < other)

    def __contains__(self, find_value):
        for value in self:
            if value == find_value:
                return True
        return False

    def count(self, find_value):
        result = 0
        for value in self:
            if value == find_value:
                result += 1
        return result

    def index(self, find_value):
        for i, value in enumerate(self):
            if value == find_value:
                return i
        raise ValueError('%r is not in list' % find_value)

    def reverse(self):
        for i, new_value in enumerate(list(reversed(self))):
            self[i] = new_value

    def sort(self, key=None, reverse=False):
        for i, new_value in enumerate(sorted(
                self, key=key, reverse=reverse)):
            self[i] = new_value

    def __delitem__(self, key):
        raise RuntimeError(
            'Methods changing the list size are unavailable')

    def append(self, x):
        raise RuntimeError(
            'Methods changing the list size are unavailable')

    def extend(self, x):
        raise RuntimeError(
            'Methods changing the list size are unavailable')

    def insert(self, i, x):
        raise RuntimeError(
            'Methods changing the list size are unavailable')

    def pop(self, i=None):
        raise RuntimeError(
            'Methods changing the list size are unavailable')

    def remove(self, x):
        raise RuntimeError(
            'Methods changing the list size are unavailable')