bertdida/DeclareThatColor

View on GitHub
libs/css.py

Summary

Maintainability
A
0 mins
Test Coverage
import re
import sys
from .hexcode import hexutils, hexname
from .ruleset import RuleSet
from .hexdeclaration import HexDeclaration

try:
    case_conversion = sys.modules['Case Conversion.case_conversion']
except KeyError:
    class CaseConversion:

        @staticmethod
        def __getattr__(attr):
            return lambda text, **kwargs: text

    case_conversion = CaseConversion()

CASE_FUNC_MAP = {
    'dash': case_conversion.to_dash_case,
    'snake': case_conversion.to_snake_case,
    'camel': case_conversion.to_camel_case,
    'pascal': case_conversion.to_pascal_case,
    'screaming_snake': case_conversion.to_screaming_snake_case
}


def key_exists(key: str, dict_):
    try:
        dict_[key.lower()]
    except (KeyError, AttributeError):
        return False

    return True


def is_supported_type_case(type_case):
    return key_exists(type_case, CASE_FUNC_MAP)


class Vanilla:

    varname_prefix = '--'

    def __init__(self, settings):
        type_case = settings.get('type_case')
        type_case = type_case.lower()

        self.name_case_func = CASE_FUNC_MAP[type_case]
        self.name_prefix = settings.get('color_name_prefix')

        self.ruleset = RuleSet(settings.get('css_selector'))
        self.hexdeclaration = HexDeclaration()

    @property
    def color_name_prefix(self):
        return re.sub(r'[^a-zA-Z0-9-_]', '', self.name_prefix)

    def declare_hexcodes(self, css):
        if not hexutils.find_all(css):
            return css

        css = self.undeclare_hexcodes(css)

        colorname_hex_map = self.get_colorname_hex_map(css)
        set_variable_name = self.set_variable_name(colorname_hex_map)

        css = hexutils.HEX_CODE_RE.sub(set_variable_name, css)
        declarations = self.get_declarations(colorname_hex_map)

        return '{}{}'.format(declarations, css)

    def undeclare_hexcodes(self, css):
        varname_hex_map = self.get_varname_hex_map(css)

        css = self.remove_hexcode_declarations(css)
        css = self.replace_varnames_with_hexcodes(css, varname_hex_map)

        return css

    def get_varname_hex_map(self, css):
        dict_ = {}
        get_declarations = self.hexdeclaration.find_all

        for rule_set in self.get_rulesets(css):
            varname_hex = get_declarations(rule_set)
            varname_hex = [(n, hexutils.normalize(h))
                           for n, h in varname_hex if hexutils.is_valid(h)]

            dict_.update({n: h for n, h in varname_hex})

        return dict_

    def get_rulesets(self, css):
        return self.ruleset.find_all(css)

    def remove_hexcode_declarations(self, css):
        remove_declarations = self.hexdeclaration.remove

        for rule_set in self.get_rulesets(css):
            css = css.replace(rule_set, remove_declarations(rule_set))

        return self.ruleset.remove_empty(css)

    @staticmethod
    def replace_varnames_with_hexcodes(css, varname_hex_map: dict):
        for name, hex_code in varname_hex_map.items():
            css = css.replace('var({})'.format(name), hex_code)

        return css

    def get_colorname_hex_map(self, css):
        dict_ = {}
        hex_codes = self.get_unique_hexcodes(css)

        for hex_code in hex_codes:
            name = hexname.get_unique(hex_code, dict_)
            dict_[name] = hex_code

        new_dict = {}

        for name, hex_code in dict_.items():
            name = self.convert_case(name)
            name = self.prepend_color_name_prefix(name)
            new_dict[name] = hex_code

        return new_dict

    @staticmethod
    def get_unique_hexcodes(css):
        hex_codes = []

        for hex_code in hexutils.find_all(css):
            hex_code = hexutils.normalize(hex_code)

            if hex_code not in hex_codes:
                hex_codes.append(hex_code)

        return tuple(hex_codes)

    def convert_case(self, color_name):
        return self.name_case_func(
            text=color_name, detectAcronyms=False, acronyms=[])

    def prepend_color_name_prefix(self, color_name):
        return '{}{}'.format(self.color_name_prefix, color_name)

    def set_variable_name(self, colorname_hex_map: dict):
        def variable_name(match):
            match = match.group()
            hex_code = hexutils.normalize(match)

            for name, _hex_code in colorname_hex_map.items():
                if _hex_code == hex_code:
                    return self.format_variable_name(name)

            return match

        return variable_name

    def format_variable_name(self, color_name):
        return 'var({}{})'.format(self.varname_prefix, color_name)

    def get_declarations(self, colorname_hex_map: dict):
        create_declaration = self.hexdeclaration.create
        sorted_names = sorted(colorname_hex_map, key=self.natural_sort)

        declarations = [create_declaration(n, colorname_hex_map[n])
                        for n in sorted_names]

        return self.format_declarations(declarations)

    @staticmethod
    def natural_sort(string):
        return [int(s) if s.isdigit() else s.lower()
                for s in re.split(r'([0-9]+)', string)]

    def format_declarations(self, declarations: list):
        return self.ruleset.create(declarations)


PREPROCESSOR_PREFIX_MAP = {
    'stylus': '$',
    'sass': '$',
    'scss': '$',
    'less': '@'
}


class Preprocessor(Vanilla):

    def __init__(self, settings):
        type_case = settings.get('type_case')
        type_case = type_case.lower()

        self.name_case_func = CASE_FUNC_MAP[type_case]
        self.name_prefix = settings.get('color_name_prefix')

        language = settings.get('css_preprocessor')
        language = language.lower()

        assignment_operator = ' = ' if language == 'stylus' else ': '
        statement_separator = '' if language == 'sass' else ';'

        self.varname_prefix = PREPROCESSOR_PREFIX_MAP[language]
        self.hexdeclaration = HexDeclaration(
            self.varname_prefix, assignment_operator, statement_separator)

    @staticmethod
    def is_supported(preprocessor):
        return key_exists(preprocessor, PREPROCESSOR_PREFIX_MAP)

    def get_varname_hex_map(self, css):
        return {n: hexutils.normalize(h)
                for n, h in self.hexdeclaration.find_all(css)
                if hexutils.is_valid(h)}

    def remove_hexcode_declarations(self, css):
        return self.hexdeclaration.remove(css)

    @staticmethod
    def replace_varnames_with_hexcodes(css, varname_hex_map: dict):
        for name, hex_code in varname_hex_map.items():
            name_re = r'{}{}'.format(re.escape(name), '(?![a-z0-9-_:])')
            css = re.sub(name_re, hex_code, css)

        return css

    def format_variable_name(self, color_name):
        return '{}{}'.format(self.varname_prefix, color_name)

    @staticmethod
    def format_declarations(declarations: list):
        return '{0}{1}{1}'.format('\n'.join(declarations), '\n')