<commit_before>from copy import deepcopy
from textwrap import dedent
from typing import Dict, Iterable, Optional, Set, Type, Union
import re

from sqlalchemy import DDL, event
from sqlalchemy.dialects.postgresql.base import (
    RESERVED_WORDS as POSTGRESQL_RESERVED_WORDS,
)

from flask import current_app

from furl import furl
from zxcvbn import zxcvbn
import pymdownx.superfences

from coaster.utils import (
    default_markdown_extension_configs,
    default_markdown_extensions,
    make_name,
)

from ..typing import T
from . import UrlType, db

__all__ = [
    'RESERVED_NAMES',
    'password_policy',
    'markdown_content_options',
    'add_to_class',
    'add_search_trigger',
    'visual_field_delimiter',
    'add_search_trigger',
    'password_policy',
    'valid_name',
    'valid_username',
    'ImgeeFurl',
    'ImgeeType',
]

RESERVED_NAMES: Set[str] = {
    '_baseframe',
    'about',
    'account',
    'admin',
    'api',
    'app',
    'apps',
    'auth',
    'blog',
    'boxoffice',
    'brand',
    'brands',
    'by',
    'client',
    'clients',
    'comments',
    'confirm',
    'contact',
    'contacts',
    'crew',
    'dashboard',
    'delete',
    'edit',
    'email',
    'emails',
    'embed',
    'event',
    'events',
    'ftp',
    'funnel',
    'funnels',
    'hacknight',
    'hacknights',
    'hgtv',
    'imap',
    'in',
    'json',
    'kharcha',
    'login',
    'logout',
    'members',
    'membership',
    'new',
    'news',
    'notification',
    'notifications',
    'org',
    'organization',
    'organizations',
    'orgs',
    'pop',
    'pop3',
    'profile',
    'profiles',
    'project',
    'projects',
    'proposal',
    'proposals',
    'register',
    'reset',
    'search',
    'siteadmin',
    'smtp',
    'static',
    'ticket',
    'tickets',
    'token',
    'tokens',
    'update',
    'updates',
    'venue',
    'venues',
    'video',
    'videos',
    'workshop',
    'workshops',
    'www',
}


class PasswordPolicy:
    def __init__(self, min_length: int, min_score: int) -> None:
        self.min_length = min_length
        self.min_score = min_score

    def test_password(
        self, password: str, user_inputs: Optional[Iterable] = None
    ) -> Dict[str, Union[bool, str]]:
        result = zxcvbn(password, user_inputs)
        return {
            'is_weak': (
                len(password) < self.min_length
                or result['score'] < self.min_score
                or bool(result['feedback']['warning'])
            ),
            'score': result['score'],
            'warning': result['feedback']['warning'],
            'suggestions': result['feedback']['suggestions'],
        }


# Strong passwords require a strength of at least 3 as per the zxcvbn
# project documentation.
password_policy = PasswordPolicy(min_length=8, min_score=3)

# re.IGNORECASE needs re.ASCII because of a quirk in the characters it matches.
# https://docs.python.org/3/library/re.html#re.I
_username_valid_re = re.compile('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$', re.I | re.A)
_name_valid_re = re.compile('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$', re.A)


visual_field_delimiter = ' ¦ '

markdown_content_options: dict = {
    'extensions': deepcopy(default_markdown_extensions),
    'extension_configs': deepcopy(default_markdown_extension_configs),
}

markdown_content_options['extensions'].append('toc')  # Allow a table of contents
markdown_content_options['extension_configs']['toc'] = {
    # Make headings link to themselves, for easier sharing
    'anchorlink': True,
    # Add a `h-` prefix to the heading id, to avoid conflict with template identifiers
    'slugify': lambda value, separator: (
        'h' + separator + make_name(value, delim=separator)
    ),
}

# Custom fences must use <pre><code> blocks and not <div> blocks, as linkify will mess
# with links inside <div> blocks
markdown_content_options['extension_configs'].setdefault('pymdownx.superfences', {})[
    'custom_fences'
] = [
    {
        'name': 'mermaid',
        'class': 'language-placeholder language-mermaid',
        'format': pymdownx.superfences.fence_code_format,
    },
    {
        'name': 'vega-lite',
        'class': 'language-placeholder language-vega-lite',
        'format': pymdownx.superfences.fence_code_format,
    },
]


def add_to_class(cls: Type, name: Optional[str] = None):
    """<commit_msg>Use h: prefix for better disambiguation<commit_after>from copy import deepcopy
from textwrap import dedent
from typing import Dict, Iterable, Optional, Set, Type, Union
import re

from sqlalchemy import DDL, event
from sqlalchemy.dialects.postgresql.base import (
    RESERVED_WORDS as POSTGRESQL_RESERVED_WORDS,
)

from flask import current_app

from furl import furl
from zxcvbn import zxcvbn
import pymdownx.superfences

from coaster.utils import (
    default_markdown_extension_configs,
    default_markdown_extensions,
    make_name,
)

from ..typing import T
from . import UrlType, db

__all__ = [
    'RESERVED_NAMES',
    'password_policy',
    'markdown_content_options',
    'add_to_class',
    'add_search_trigger',
    'visual_field_delimiter',
    'add_search_trigger',
    'password_policy',
    'valid_name',
    'valid_username',
    'ImgeeFurl',
    'ImgeeType',
]

RESERVED_NAMES: Set[str] = {
    '_baseframe',
    'about',
    'account',
    'admin',
    'api',
    'app',
    'apps',
    'auth',
    'blog',
    'boxoffice',
    'brand',
    'brands',
    'by',
    'client',
    'clients',
    'comments',
    'confirm',
    'contact',
    'contacts',
    'crew',
    'dashboard',
    'delete',
    'edit',
    'email',
    'emails',
    'embed',
    'event',
    'events',
    'ftp',
    'funnel',
    'funnels',
    'hacknight',
    'hacknights',
    'hgtv',
    'imap',
    'in',
    'json',
    'kharcha',
    'login',
    'logout',
    'members',
    'membership',
    'new',
    'news',
    'notification',
    'notifications',
    'org',
    'organization',
    'organizations',
    'orgs',
    'pop',
    'pop3',
    'profile',
    'profiles',
    'project',
    'projects',
    'proposal',
    'proposals',
    'register',
    'reset',
    'search',
    'siteadmin',
    'smtp',
    'static',
    'ticket',
    'tickets',
    'token',
    'tokens',
    'update',
    'updates',
    'venue',
    'venues',
    'video',
    'videos',
    'workshop',
    'workshops',
    'www',
}


class PasswordPolicy:
    def __init__(self, min_length: int, min_score: int) -> None:
        self.min_length = min_length
        self.min_score = min_score

    def test_password(
        self, password: str, user_inputs: Optional[Iterable] = None
    ) -> Dict[str, Union[bool, str]]:
        result = zxcvbn(password, user_inputs)
        return {
            'is_weak': (
                len(password) < self.min_length
                or result['score'] < self.min_score
                or bool(result['feedback']['warning'])
            ),
            'score': result['score'],
            'warning': result['feedback']['warning'],
            'suggestions': result['feedback']['suggestions'],
        }


# Strong passwords require a strength of at least 3 as per the zxcvbn
# project documentation.
password_policy = PasswordPolicy(min_length=8, min_score=3)

# re.IGNORECASE needs re.ASCII because of a quirk in the characters it matches.
# https://docs.python.org/3/library/re.html#re.I
_username_valid_re = re.compile('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$', re.I | re.A)
_name_valid_re = re.compile('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$', re.A)


visual_field_delimiter = ' ¦ '

markdown_content_options: dict = {
    'extensions': deepcopy(default_markdown_extensions),
    'extension_configs': deepcopy(default_markdown_extension_configs),
}

markdown_content_options['extensions'].append('toc')  # Allow a table of contents
markdown_content_options['extension_configs']['toc'] = {
    # Make headings link to themselves, for easier sharing
    'anchorlink': True,
    # Add a `h-` prefix to the heading id, to avoid conflict with template identifiers
    'slugify': lambda value, separator: ('h:' + make_name(value, delim=separator)),
}

# Custom fences must use <pre><code> blocks and not <div> blocks, as linkify will mess
# with links inside <div> blocks
markdown_content_options['extension_configs'].setdefault('pymdownx.superfences', {})[
    'custom_fences'
] = [
    {
        'name': 'mermaid',
        'class': 'language-placeholder language-mermaid',
        'format': pymdownx.superfences.fence_code_format,
    },
    {
        'name': 'vega-lite',
        'class': 'language-placeholder language-vega-lite',
        'format': pymdownx.superfences.fence_code_format,
    },
]


def add_to_class(cls: Type, name: Optional[str] = None):
    """
{'fraction_non_alphanumeric': 0.14605970490678258, 'fraction_numerical': 0.0038888253459910785, 'mean_word_length': 2.9673321234119783, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '84564', 'n_tokens_mistral': 3074, 'n_tokens_neox': 2995, 'n_words': 640}