"""A Sphinx extension for linking to your project's issue tracker."""
import re
from typing import Callable, Optional, Tuple

from docutils import nodes, utils
from sphinx.config import Config
from sphinx.util.nodes import split_explicit_title

__version__ = "3.0.1"
__author__ = "Steven Loria"
__license__ = "MIT"

def cve_role(name, rawtext, text, lineno, inliner, options=None, content=None):
    """Sphinx role for linking to a CVE on https://cve.mitre.org.

    Examples: ::

        :cve:`CVE-2018-17175`

    """
    options = options or {}
    content = content or []
    has_explicit_title, title, target = split_explicit_title(text)

    target = utils.unescape(target).strip()
    title = utils.unescape(title).strip()
    ref = f"https://cve.mitre.org/cgi-bin/cvename.cgi?name={target}"
    text = title if has_explicit_title else target
    link = nodes.reference(text=text, refuri=ref, **options)
    return [link], []

GITHUB_USER_RE = re.compile("^https://github.com/([^/]+)/([^/]+)/.*")

def _get_default_group_and_project(
    config: Config, uri_config_option: str
) -> Optional[Tuple[str, str]]:
    """
    Get the default group/project or None if not set
    """
    old_config = getattr(config, "issues_github_path", None)
    new_config = getattr(config, "issues_default_group_project", None)

    if old_config and new_config:
        raise ValueError(
            "Both 'issues_github_path' and 'issues_default_group_project' are set, even"
            " though they define the same setting.  "
            "Please only define one of these."
        )
    group_and_project = new_config or old_config

    if group_and_project:
        assert isinstance(group_and_project, str)
        try:
            group, project = group_and_project.split("/", maxsplit=1)
            return group, project
        except ValueError as e:
            raise ValueError(
                "`issues_github_path` or `issues_default_group_project` needs to "
                "define a value in the form of `<group or user>/<project>` "
                f"but `{config}` was given."
            ) from e

    # If group and project was not set, we need to look for it within the github url
    # for backward compatibility
    if not group_and_project:
        uri = getattr(config, uri_config_option)
        if uri:
            match = GITHUB_USER_RE.match(uri)
            if match:
                return match.groups()[0], match.groups()[1]

    return None

def _get_placeholder(uri_config_option: str) -> str:
    """
    Get the placeholder from the uri_config_option
    """
    try:
        # i.e. issues_pr_uri -> pr
        return uri_config_option[:-4].split("_", maxsplit=1)[1]
    except IndexError:
        # issues_uri -> issue
        return uri_config_option[:-5]

def _get_uri_template(
    config: Config,
    uri_config_option: str,
) -> str:
    """
    Get a URL format template that can be filled with user information based
    on the given configuration

    The result always contains the following placeholder
      - n (the issue number, user, pull request, etc...)

    The result can contain the following other placeholders
      - group (same as user in github)
      - project

    Examples for possible results:

         - "https://github.com/{group}/{project}/issues/{n}"

         - "https://gitlab.company.com/{group}/{project}/{n}"

         - "https://fancy.issuetrack.com?group={group}&project={project}&issue={n}"

    Raises:
         - ValueError if the given uri contains an invalid placeholder
    """
    format_string = str(getattr(config, uri_config_option))
    placeholder = _get_placeholder(uri_config_option)

    result = format_string.replace(f"{{{placeholder}}}", "{n}")

    try:
        result.format(project="", group="", n="")
    except (NameError, KeyError) as e:
        raise ValueError(
            f"The `{uri_config_option}` option contains invalid placeholders. "
            f"Only {{group}}, {{projects}} and {{{placeholder}}} are allowed."
            f'Invalid format string: "{format_string}".'
        ) from e
    return result

def _get_uri(
    uri_config_option: str,
    config: Config,
    number: str,
    group_and_project: Optional[Tuple[str, str]] = None,
) -> str:
    """
    Get a URI based on the given configuration and do some sanity checking
    """
    format_string = _get_uri_template(config, uri_config_option)

    url_vars = {"n": number}

    config_group_and_project = _get_default_group_and_project(config, uri_config_option)
    if group_and_project:
        # Group and Project defined by call
        if config_group_and_project:
            to_replace = "/".join(config_group_and_project)
            if to_replace in format_string:
                # Backward compatibility, replace default group/project
                # with {group}/{project}
                format_string = format_string.replace(to_replace, "{group}/{project}")
        (url_vars["group"], url_vars["project"]) = group_and_project
    elif config_group_and_project:
        # If not defined by call use the default if given
        (url_vars["group"], url_vars["project"]) = config_group_and_project

    try:
        return format_string.format(**url_vars)
    except (NameError, KeyError) as e:
        # The format string was checked before, that it contains no additional not
        # supported placeholders. So this occur
        raise ValueError(
            f"The `{uri_config_option}` format `{format_string}` requires a "
            f"group/project to be defined in `issues_default_group_project`."
        ) from e

def cwe_role(name, rawtext, text, lineno, inliner, options=None, content=None):
    """Sphinx role for linking to a CWE on https://cwe.mitre.org.

    Examples: ::

        :cwe:`CWE-787`

    """
    options = options or {}
    content = content or []
    has_explicit_title, title, target = split_explicit_title(text)

    target = utils.unescape(target).strip()
    title = utils.unescape(title).strip()
    number = target[4:]
    ref = f"https://cwe.mitre.org/data/definitions/{number}.html"
    text = title if has_explicit_title else target
    link = nodes.reference(text=text, refuri=ref, **options)
    return [link], []

class IssueRole:
    # Symbols used to separate and issue/pull request/merge request etc
    # i.e
    #   - group/project#2323 for issues
    #   - group/project!1234 for merge requests (in gitlab)
    #   - group/project@adbc1234 for commits
    ELEMENT_SEPARATORS = "#@!"

    EXTERNAL_REPO_REGEX = re.compile(rf"^(\w+)/(.+)([{ELEMENT_SEPARATORS}])([\w]+)$")

    def __init__(
        self,
        config_prefix: str,
        pre_format_text: Callable[[Config, str], str] = None,
    ):
        self.uri_config = f"{config_prefix}_uri"
        self.separator_config = f"{config_prefix}_prefix"
        self.pre_format_text = pre_format_text or self.default_pre_format_text

    @staticmethod
    def default_pre_format_text(config: Config, text: str) -> str:
        return text

    def format_text(self, config: Config, issue_no: str) -> str:
        """
        Add supported separator in front of the issue or raise an error if invalid
        separator is given
        """
        separator = getattr(config, self.separator_config)
        if separator not in self.ELEMENT_SEPARATORS:
            raise ValueError(
                f"Option {self.separator_config} has to be one of "
                f"{', '.join(self.ELEMENT_SEPARATORS)}."
            )
        text = self.pre_format_text(config, issue_no.lstrip(self.ELEMENT_SEPARATORS))
        return f"{separator}{text}"

    def make_node(self, name: str, issue_no: str, config: Config, options=None):
        if issue_no in ("-", "0"):
            return None

        options = options or {}

        has_explicit_title, title, target = split_explicit_title(issue_no)

        if has_explicit_title:
            issue_no = str(target)

        repo_match = self.EXTERNAL_REPO_REGEX.match(issue_no)

        if repo_match:
            # External repo
            group, project, original_separator, issue_no = repo_match.groups()
            text = f"{group}/{project}{self.format_text(config, issue_no)}"
            ref = _get_uri(
                self.uri_config,
                config,
                issue_no,
                (group, project),
            )
        else:
            text = self.format_text(config, issue_no)
            ref = _get_uri(self.uri_config, config, issue_no)
        if has_explicit_title:
            return nodes.reference(text=title, refuri=ref, **options)
        else:
            return nodes.reference(text=text, refuri=ref, **options)

    def __call__(
        self, name, rawtext, text, lineno, inliner, options=None, content=None
    ):
        options = options or {}
        content = content or []
        issue_nos = [each.strip() for each in utils.unescape(text).split(",")]
        config = inliner.document.settings.env.app.config
        ret = []
        for i, issue_no in enumerate(issue_nos):
            node = self.make_node(name, issue_no, config, options=options)
            ret.append(node)
            if i != len(issue_nos) - 1:
                sep = nodes.raw(text=", ", format="html")
                ret.append(sep)
        return ret, []

"""Sphinx role for linking to an issue. Must have
`issues_uri` or `issues_default_group_project` configured in ``conf.py``.
Examples: ::
    :issue:`123`
    :issue:`42,45`
    :issue:`sloria/konch#123`
"""
issue_role = IssueRole(
    config_prefix="issues",
)

"""Sphinx role for linking to a pull request. Must have
`issues_pr_uri` or `issues_default_group_project` configured in ``conf.py``.
Examples: ::
    :pr:`123`
    :pr:`42,45`
    :pr:`sloria/konch#43`
"""
pr_role = IssueRole(
    config_prefix="issues_pr",
)

def format_commit_text(config, sha):
    return sha[:7]

"""Sphinx role for linking to a commit. Must have
`issues_commit_uri` or `issues_default_group_project` configured in ``conf.py``.
Examples: ::
    :commit:`123abc456def`
    :commit:`sloria/konch@123abc456def`
"""
commit_role = IssueRole(
    config_prefix="issues_commit",
    pre_format_text=format_commit_text,
)

"""Sphinx role for linking to a user profile. Defaults to linking to
GitHub profiles, but the profile URIS can be configured via the
``issues_user_uri`` config value.

Examples: ::

    :user:`sloria`

Anchor text also works: ::

    :user:`Steven Loria <sloria>`
"""
user_role = IssueRole(config_prefix="issues_user")

def setup(app):
    # Format template for issues URI
    # e.g. 'https://github.com/sloria/marshmallow/issues/{issue}
    app.add_config_value(
        "issues_uri",
        default="https://github.com/{group}/{project}/issues/{issue}",
        rebuild="html",
        types=[str],
    )
    app.add_config_value("issues_prefix", default="#", rebuild="html", types=[str])
    # Format template for PR URI
    # e.g. 'https://github.com/sloria/marshmallow/pull/{issue}
    app.add_config_value(
        "issues_pr_uri",
        default="https://github.com/{group}/{project}/pull/{pr}",
        rebuild="html",
        types=[str],
    )
    app.add_config_value("issues_pr_prefix", default="#", rebuild="html", types=[str])
    # Format template for commit URI
    # e.g. 'https://github.com/sloria/marshmallow/commits/{commit}
    app.add_config_value(
        "issues_commit_uri",
        default="https://github.com/{group}/{project}/commit/{commit}",
        rebuild="html",
        types=[str],
    )
    app.add_config_value(
        "issues_commit_prefix", default="@", rebuild="html", types=[str]
    )
    # There is no seperator config as a format_text function is given

    # Default User (Group)/Project eg. 'sloria/marshmallow'
    # Called github as the package was working with github only before
    app.add_config_value(
        "issues_github_path", default=None, rebuild="html", types=[str]
    )
    # Same as above but with new naming to reflect the new functionality
    # Only on of both can be set
    app.add_config_value(
        "issues_default_group_project", default=None, rebuild="html", types=[str]
    )
    # Format template for user profile URI
    # e.g. 'https://github.com/{user}'
    app.add_config_value(
        "issues_user_uri",
        default="https://github.com/{user}",
        rebuild="html",
        types=[str],
    )
    app.add_config_value("issues_user_prefix", default="@", rebuild="html", types=[str])
    app.add_role("issue", issue_role)
    app.add_role("pr", pr_role)
    app.add_role("user", user_role)
    app.add_role("commit", commit_role)
    app.add_role("cve", cve_role)
    app.add_role("cwe", cwe_role)
    return {
        "version": __version__,
        "parallel_read_safe": True,
        "parallel_write_safe": True,
    }


import os
import cv2
import cv2 as cv
import numpy as np
import random as rng

def verticalLine(gray):
    lineList = []
    # 应用高斯模糊以减少噪音
    blur = cv2.GaussianBlur(gray, (7, 7), 0)
    blur = cv2.GaussianBlur(blur, (7, 7), 0)
    cv2.imwrite(out_dir + "01blur.jpg", blur)
    # 检测边缘
    edges = cv2.Canny(blur, 30, 90, apertureSize=3)
    cv2.imwrite(out_dir + "01edges.jpg", edges)
    # 应用霍夫直线变换以检测直线
    lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=30, maxLineGap=40)
    # 绘制检测到的直线
    for line in lines:
        x1, y1, x2, y2 = line[0]
        if abs(x1 - x2) * 10 < abs(y1 - y2):
            lineList.append(line[0])
            # cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
    return lineList

def lineGroup(line_list):
    lineList_sort = sorted(line_list, key=lambda x: x[0])
    lines_group = [[]]
    lines = []
    last_x = 0
    for line in lineList_sort:
        if len(lines_group[-1]) == 0:
            lines_group[-1].append(line)
        elif line[0] - last_x < 50:
            lines_group[-1].append(line)
        else:
            lines_group.append([line])
        last_x = line[0]
    lines.append(line)
    return lines_group

# 筛选 计算中心点
def getLineCenterX(lines_group, top_y, top_x=0):
    global centerList
    # 帅选
    # 去掉白色区域 计算中心点
    lines_group_new = []
    center_xs = []
    dis_max_index = 0
    dis_max = 0
    for lines in lines_group:
        xs = [line[0] for line in lines] + [line[2] for line in lines]
        ys = [line[1] for line in lines] + [line[3] for line in lines]
        xs_min, xs_max, ys_min, ys_max = min(xs), max(xs), min(ys), max(ys)
        if top_x > 0:
            if xs_min < top_x+10 and top_x-750 < xs_max:
                continue
        if ys_min < (top_y + 300) or ys_max > top_y + 1100:
            lines_group_new.append(lines)
            # 中心
            center_xs.append(int((xs_min + xs_max) / 2))
            if len(center_xs) > 1:
                dis = center_xs[-1] - center_xs[-2]
                if dis > dis_max:
                    dis_max = dis
                    dis_max_index = len(center_xs) - 1
    centerList.append(center_xs[dis_max_index])
    return lines_group_new, center_xs, dis_max_index

# 编号 img1 ： 0 12  3
# 编号 img4 ： 8  9A B
def BianHao14(img_gray, img_serial=1):
    keySerialDict = {"1": 3, "4": 9}
    key_serial = keySerialDict[str(img_serial)]
    top_y = getTop(img_gray)
    lineList = verticalLine(img_gray)

    # 分组
    linesAll = lineGroup(lineList)

    # 筛选 计算中心点
    linesAll_new, center_xs, dis_max_index = getLineCenterX(linesAll, top_y)

    # 保存结果
    resultDict = {}
    for i in range(len(center_xs)):
        line_serial = key_serial + i - dis_max_index
        key_name = str(line_serial)
        result = {"center_x": center_xs[i], "lines": linesAll_new[i]}
        resultDict[key_name] = result

    resultDict["linesAll"] = lineList
    resultDict["top_y"] = top_y
    resultDict["serial"] = img_serial
    return resultDict

# 编号 img1 ： 0 12  3
# 编号 img4 ： 8  9A B
def BianHao1(img_gray, img_serial=1):
    keySerialDict = {"1": 3, "4": 9}
    key_serial = keySerialDict[str(img_serial)]
    top_y = getTop(img_gray)
    top_x = getright(img_gray)
    lineList = verticalLine(img_gray)

    # 分组
    linesAll = lineGroup(lineList)

    # 筛选 计算中心点
    linesAll_new, center_xs, dis_max_index = getLineCenterX(linesAll, top_y, top_x)

    # 保存结果
    resultDict = {}
    for i in range(len(center_xs)):
        line_serial = key_serial + i - dis_max_index
        key_name = str(line_serial)
        result = {"center_x": center_xs[i], "lines": linesAll_new[i]}
        resultDict[key_name] = result

    resultDict["linesAll"] = lineList
    resultDict["top_y"] = top_y
    resultDict["serial"] = img_serial
    return resultDict

# 编号 img2 ： |3   4   5  |
# 编号 img3 ： |  6    7   8|
def BianHao23(img_gray, img_serial=2):
    keySerialDict = {"2": 5, "3": 7}
    key_serial = keySerialDict[str(img_serial)]

    # 顶边
    top_y = getTop(img_gray)
    lineList = verticalLine(img_gray)

    # 分组
    linesAll = lineGroup(lineList)

    # 筛选 计算中心点
    linesAll_new, center_xs, dis_max_index = getLineCenterX(linesAll, top_y)

    # 保存结果
    resultDict = {}
    for i in range(len(center_xs)):
        line_serial = key_serial + i - dis_max_index
        key_name = str(line_serial)
        result = {"center_x": center_xs[i], "lines": linesAll_new[i]}
        resultDict[key_name] = result

    resultDict["linesAll"] = lineList
    resultDict["top_y"] = top_y
    resultDict["serial"] = img_serial
    return resultDict

def BianHao(img_gray, img_serial=2):
    func_list = [BianHao1, BianHao23, BianHao23, BianHao14]
    return func_list[int(img_serial) - 1](img_gray, img_serial)

def getTop(img_gray):
    h, w = img_gray.shape
    point_x = int(w / 2)
    for point_y in range(h):
        if img_gray[point_y, point_x] > 50:
            return point_y

def getright(img_gray):
    h, w = img_gray.shape
    point_y = 1200
    for point_x in range(w-1, int(h/2), -1):
        if img_gray[point_y, point_x] > 240:
            wList.append(point_x)
            return point_x

def find_white_region(image):
    # 转换为灰度图像
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 使用二值化提取白色区域
    ret, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)

    # 寻找轮廓并筛选白色区域
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    white_contours = []
    for cnt in contours:
        if 320000 > cv2.contourArea(cnt) > 280000:
            x, y, w, h = cv2.boundingRect(cnt)
            white_contours.append((x, y, w, h))
    # 绘制所有
    for i in range(len(contours)):
        color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
        cv2.drawContours(image, contours, i, color, 2, cv2.LINE_8, hierarchy, 0)
    # 返回最大的白色区域的外界矩形
    if white_contours:
        x, y, w, h = max(white_contours, key=lambda x: x[2] * x[3])

        # cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
        # cv2.imshow('image', image)
        # cv2.waitKey(0)
        return (x, y, w, h), thresh
    else:
        return None

def save_white_region():
    tested = []
    for file_name in os.listdir(img_dir):
        first_name = file_name[:-5]
        if first_name in tested:
            continue
        print(first_name)
        tested.append(first_name)
        img_bgr = cv2.imread(img_dir + first_name + "1.bmp")
        (x, y, w, h), thresh = find_white_region(img_bgr)
        cv2.rectangle(img_bgr, (x, y), (x + w, y + h), (0, 255, 0), 2)
        cv2.imwrite(out_dir + first_name[:-1] + ".jpg", img_bgr)
        cv2.imwrite(out_dir + first_name[:-1] + "_thr.jpg", thresh)
# 模板匹配
def my_matchTemplate(img_bgr, tmpl_bgr, method=cv.TM_SQDIFF_NORMED, mask=None):
    # def matchTemplate(image, templ, method, result=None, mask=None)
    #
    # cv2.TM_SQDIFF         采用<平方差>的方法，求模板和图像之间的差值，差值越小匹配度越高。
    # cv2.TM_SQDIFF_NORMED  采用归一化平方差的方法，将模板和图像归一化后再求平方差，差值越小匹配度越高。
    # cv2.TM_CCORR          采用<互相关>的方法，将模板和图像卷积后再求相关，相关值越大匹配度越高。
    # cv2.TM_CCORR_NORMED
    # cv2.TM_CCOEF          采用<相关系数>的方法，将模板和图像转化为概率分布后再求相关系数，相关系数越大匹配度越高。
    # cv2.TM_CCOEFF_NORMED
    result = cv.matchTemplate(img_bgr, tmpl_bgr, method, result=None, mask=mask)
    cv.normalize( result, result, 0, 1, cv.NORM_MINMAX, -1 )
    _minVal, _maxVal, minLoc, maxLoc = cv.minMaxLoc(result, None)
    if method == cv.TM_SQDIFF or method == cv.TM_SQDIFF_NORMED:
        matchLoc = minLoc
    else:
        matchLoc = maxLoc
    return matchLoc, result

def drawResultDict(img, result_dict):
    #
    linesAll = result_dict.get("linesAll")
    for line in linesAll:
        cv2.line(img, (line[0], line[1]), (line[2], line[3]), (0, 0, 255), 3)
    for key in result_dict:
        if isinstance(result_dict[key], dict):
            lines = result_dict[key].get("lines")
            if not lines:
                continue
            for line in lines:
                cv2.line(img, (line[0], line[1]), (line[2], line[3]), (0, 255, 0), 3)
    return img

def test(img_dir, out_dir):
    tested = []
    for file_name in os.listdir(img_dir):
        first_name = file_name[:-5]
        if first_name in tested:
            continue
        print(first_name)
        tested.append(first_name)
        img_list = []
        resultDictList = []
        for i in range(4):
            img_bgr = cv2.imread(img_dir + first_name + str(i + 1) + ".bmp")
            img_gray = cv2.imread(img_dir + first_name + str(i + 1) + ".bmp", cv2.IMREAD_GRAYSCALE)
            result_dict = BianHao(img_gray, i + 1)
            # resultDictList.append(result_dict)
            drawResultDict(img_bgr, result_dict)
            img_list.append(img_bgr)
        img = np.concatenate((img_list[0], img_list[1]), axis=1)
        img = np.concatenate((img, img_list[2]), axis=1)
        img = np.concatenate((img, img_list[3]), axis=1)
        cv2.imwrite(out_dir + first_name[:-1] + ".jpg", img)
centerList = []
wList = []
if __name__ == '__main__':
    dir_root = r"D:\04DataSets\ningjingLG/"
    img_dir = dir_root + "all/"
    out_dir = dir_root + "out4/"
    img1 = cv2.imread(dir_root + r'\black\black_R032829_CM1_1.bmp')
    img2 = cv2.imread(dir_root + r'\black\black_R032829_CM1_2.bmp')
    template2 = cv2.imread(dir_root + r"template2.bmp")
    img_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    # #BianHao1(img_gray, img_serial)
    # top_y = getTop(img_gray)
    # print("top_y", top_y)
    # result_dict = BianHao1(img_gray)
    # drawResultDict(img1, result_dict)
    # cv2.imwrite(out_dir + "01.jpg", img1)
    # test(img_dir, out_dir)
    # wList = np.array(wList)
    #save_white_region()
    find_white_region(img1)
    cv2.imwrite(dir_root + "region.jpg", img1)
    matchLoc, result = my_matchTemplate(img1, template2, cv2.TM_CCOEFF_NORMED)

    pass


"""
Input	                        Output                              Comment
o e a o e a i                   Word found: rose                    Start by taking the first volew "o" and the last consonant "r". They are found in words "rose", "lotus", and "daffodil".
p r s x r	                    Vowels left: o e a i                Then, take "e" and "x". They are found in the word "rose".
                                Consonants left: p r                Then, take "a" and "s". They are found in words "rose", "lotus", and "daffodil".
                                                                    The word "rose" is found, so we print it. Then we print the remaining letters in each sequence.

a a a                           Cannot find any word!
x r l t p p	                    Consonants left: x r l

u a o i u y o e                 Word found: tulip
p m t l	                        Vowels left: u y o e
"""
from collections import deque

vowels = deque(input().split())
consonants = deque(input().split())
words = {"rose": "rose", "tulip": "tulip", "lotus": "lotus", "daffodil": "daffodil"}
found_word = False

while vowels and consonants:
    v = vowels.popleft()
    c = consonants.pop()
    for word in words.keys():
        words[word] = words[word].replace(v, '')
        words[word] = words[word].replace(c, '')
        if words[word] == '':
            print(f"Word found: {word}")
            found_word = True
            break
    if found_word:
        break

if not found_word:
    print("Cannot find any word!")
if vowels:
    print(f"Vowels left: {' '.join(vowels)}")
if consonants:
    print(f"Consonants left: {' '.join(consonants)}")

Python Programming Advanced/Ex_10_Exam Preparation/Ex_10_04 Flower Finder v2.py


import argparse

def get_team_name_and_score(team_name_score):
    """Parse team name and score from match score string"""
    team_split = team_name_score.split(' ')
    team_score = int(team_split[-1])
    team_name = ' '.join(team_split[:-1]).strip()

    return team_name, team_score

def get_points(team1_score, team2_score):
    """Get team points from on match score"""
    if team1_score > team2_score:
        return 3, 0
    elif team2_score > team1_score:
        return 0, 3

    return 1, 1

def calculate_scores(match_results):
    """Calculate soccer team scores from match results"""
    results_list = list(filter(None, match_results.split('\n')))
    scores = {}

    for result in results_list:
        team1, team2 = result.split(',')

        team1_name, team1_score = get_team_name_and_score(team1)
        team2_name, team2_score = get_team_name_and_score(team2)

        team1_points, team2_points = get_points(team1_score, team2_score)

        # Add points to scores dict
        scores[team1_name] = scores.get(team1_name, 0) + team1_points
        scores[team2_name] = scores.get(team2_name, 0) + team2_points

    return scores

def get_ranking_table(scores):
    """Calculate soccer ranking table from team scores"""
    output_str_list = []
    sorted_scores = sorted(scores.items(), key=lambda x: (-x[1], x[0].lower()))

    previous_score = -1
    current_place = 0
    place_plus = 0
    for score in sorted_scores:
        team_name = score[0]
        team_score = score[1]
        pts_plural = 's' if team_score != 1 else ''

        if team_score == previous_score:
            place_plus += 1
        else:
            current_place += place_plus + 1
            place_plus = 0

        previous_score = team_score
        output_str_list.append(f'{current_place}. {team_name}, {team_score} pt{pts_plural}')

    return '\n'.join(output_str_list)

def run():
    """Main CLI tool method"""
    parser = argparse.ArgumentParser(epilog=(
        'Soccer League Ranker. A handy ranking table calculator.'
    ))

    parser.add_argument(
        'stdin', help='Stdin input of games results (if -ifile not used)', nargs='?', default=None
    )
    parser.add_argument('-ifile', help='Path to input file')
    parser.add_argument('-ofile', help='Path to output file')

    args = parser.parse_args()

    # Check input
    if args.stdin:
        match_results = args.stdin
    elif args.ifile:
        match_results = open(args.ifile, 'r').read()

    scores = calculate_scores(match_results)
    ranking_table = get_ranking_table(scores)

    # Check output
    if args.ofile:
        open(args.ofile, 'w').write(ranking_table)
    else:
        print(ranking_table)

    print('Ranking table successfully calculated!')


import datetime
import os
import pathlib
from urllib.parse import quote_plus

import scrapy
import slugify

from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError

BASE_PATH = os.environ.get('BASE_PATH', '/home/mpizarro/devel/freebsd_graphics')
SCRAPE_MODE = os.environ.get('SCRAPE_MODE', 'live')
ARCHS = {
    'x86': 'i386',
    'x64': 'amd64',
}

class NvidiaSpider(scrapy.Spider):
    '''
    scrapy crawl nvidia
    scrapy crawl --pdb nvidia -O data/nvidia/driver_specs/nvidia_driver_specs.json
    '''
    name = 'nvidia'
    allowed_domains = ['nvidia.com', 'www.nvidia.com', 'web.archive.org']
    custom_settings = {
        'ROBOTSTXT_OBEY': False
    }
    start_urls = [
        'https://www.nvidia.com/en-us/drivers/unix/freebsd-x64-archive/',
        'https://www.nvidia.com/en-us/drivers/unix/freebsd-archive/',
    ] if SCRAPE_MODE=='live' else [
        f'file://{BASE_PATH}/www_data/data/local/https_www_nvidia.com_en-us_drivers_unix_freebsd-archive.html',
        f'file://{BASE_PATH}/www_data/data/local/https_www_nvidia.com_en-us_drivers_unix_freebsd-x64-archive.html',
    ]

    def parse(self, response):
        if SCRAPE_MODE=='live':
            page = response.url.split("/")[-2]
        else:
            page = response.url.split("_")[-1].strip('.html')
        filename = f'{BASE_PATH}/www_data/data/nvidia/driver_lists/nvidia-{page}.html'
        with open(filename, 'wb') as f:
            f.write(response.body)

        default_os = 'FreeBSD x64' if 'x64' in response.url else 'FreeBSD x86'
        yield scrapy.Request(f'file://{filename}', callback=self.parse_driver_specs)
        unknowns_counter = 0
        for driver in response.css('div.pressItem'):
            spec = {
                'Version': 'unknown',
                'Operating System': default_os,
                'Release Date': 'unknown',
            }
            specs = driver.css('p::text').getall()
            url = driver.css('h4 a::attr(href)').get()
            filename = ''
            version = ''
            os = ''
            arch = ''
            if specs == [' ']:
                alt_specs = url.split('/')[-1].replace('_display', '').replace('_', '-').split('-')
                if len(alt_specs) == 4:
                    version = alt_specs[2]
                    os = alt_specs[0].lower()
                    arch = ARCHS[alt_specs[1]].lower()
                elif len(alt_specs) == 2:
                    unknowns_counter += 1
                    version = alt_specs[1]
                    os = alt_specs[0].lower()
                    arch = f'unknown{unknowns_counter}'
            else:
                for item in specs:
                    if ':' in item:
                        key, value = item.split(':')
                        spec[key.strip()] = value.strip()
                if url[0:2] == '//':
                    url = f'https:{url}'
                version = spec['Version']
                os_arch = spec['Operating System'].split()
                os = os_arch[0].lower()
                if os == 'freebsd':
                    arch = ARCHS[os_arch[1]].lower()
                else:
                    os = 'freebsd'
                    arch = 'amd64'
            filename = f'{BASE_PATH}/www_data/data/nvidia/driver_specs/nvidia_{version}_{os}_{arch}.html'
            if pathlib.Path(filename).exists():
                yield scrapy.Request(f'file://{filename}', callback=self.parse_driver_specs, dont_filter=True)
            else:
                yield scrapy.Request(url, callback=self.retrieve_driver_specs, errback=self.handle_errors, meta={'dont_retry': True}, cb_kwargs={'filename': filename})

    def parse_driver_specs(self, response):
        spec_keys = response.css('td.contentsummaryleft::text').getall()
        if not spec_keys:
            spec_keys = response.css('td#contentsummaryleft h2::text').getall()
        spec_values = response.css('td.contentsummaryright::text').getall()
        if not spec_values:
            spec_values = response.css('td#contentsummaryright h2::text').getall()
        specs = {}
        mismatches = 0
        keys = ''
        for idx, key in enumerate(spec_keys):
            key = key.strip().strip(':')
            keys += key
            value = ''
            while not value:
                value = spec_values[idx+mismatches].strip()
                if not value:
                    mismatches += 1
            if key:
                specs[key] = spec_values[idx+mismatches].strip()
            if 'Version' in keys and 'Operating System' in keys and 'Release Date' in keys:
                break
        os_arch = specs['Operating System'].split()
        os = os_arch[0]
        arch = ARCHS[os_arch[1]]
        release_date = datetime.datetime.strptime(specs['Release Date'], '%Y.%m.%d').isoformat() + 'Z'
        series_list = response.css('div#tab2_content b::text').getall()
        gpu_lists = response.css('div#tab2_content p::text').getall()
        if not gpu_lists:
            gpu_lists = response.css('div#tab2_content::text').getall()
        for idx, series in enumerate(series_list):
            gpus = gpu_lists[idx].split(', ')
            for gpu in gpus:
                yield {
                    'series': series.replace(' Series', '').replace(' series', '').strip().strip(':'),
                    'gpu': gpu,
                    'release_date': release_date,
                    'version': specs['Version'],
                    'os': os,
                    'arch': arch,
                }

    def retrieve_driver_specs(self, response, filename):
        with open(filename, 'wb') as f:
            f.write(response.body)
        yield scrapy.Request(f'file://{filename}', callback=self.parse_driver_specs)

    def retrieve_wayback_machine_capture(self, response, filename, original_url):
        last_ts = response.json()['last_ts']
        url = f'https://web.archive.org/web/{last_ts}/{original_url}'
        yield scrapy.Request(
            url,
            callback=self.retrieve_driver_specs,
            headers={'Referer': 'https://web.archive.org/web/2015*/https://www.nvidia.com/object/frds86-313.18-driver'},
            errback=self.handle_errors,
            meta={'dont_retry': True},
            cb_kwargs={'filename': filename}
        )

    def handle_errors(self, failure):
        if failure.check(HttpError) and failure.value.response.status==503:
            original_url = failure.value.response.url
            url = f'https://web.archive.org/__wb/sparkline?output=json&url={quote_plus(original_url)}&collection=web'
            filename = failure.value.response.cb_kwargs['filename']
            yield scrapy.Request(
                url,
                callback=self.retrieve_wayback_machine_capture,
                headers={'Referer': 'https://web.archive.org/web/2015*/https://www.nvidia.com/object/frds86-313.18-driver'},
                errback=self.handle_errors,
                meta={'dont_retry': True},
                cb_kwargs={'filename': filename, 'original_url': original_url}
            )
        else:
            # log all failures
            self.logger.error(repr(failure))

            if failure.check(HttpError):
                # these exceptions come from HttpError spider middleware
                # you can get the non-200 response
                response = failure.value.response
                self.logger.error('HttpError on %s', response.url)
            elif failure.check(DNSLookupError):
                # this is the original request
                request = failure.request
                self.logger.error('DNSLookupError on %s', request.url)
            elif failure.check(TimeoutError, TCPTimedOutError):
                request = failure.request
                self.logger.error('TimeoutError on %s', request.url)


#!/usr/bin/env python
# coding=utf-8
import torch
import torch.nn as nn
from torch.jit.annotations import Dict, List, Tuple, Optional
from torch import Tensor

from collections import OrderedDict
import warnings

class GeneralizedRCNN(nn.Module):

    def __init__(self, transform, backbone, rpn, roi_heads):
        super(GeneralizedRCNN, self).__init__()
        self.transform = transform
        self.backbone = backbone
        self.rpn = rpn
        self.roi_heads = roi_heads
        self._has_warned = False
        self.trace_module = False

    @torch.jit.unused
    def eager_outputs(self, losses, detections):
        # type: (Dict[str, Tensor], List[Dict[str, Tensor]]) -> Tuple[Dict[str, Tensor], List[Dict[str, Tensor]]]
        if self.training:
            return losses

        return detections

    def forward(self, images, targets=None):
        # type: (List[Tensor], Optional[List[Dict[str, Tensor]]])
        if self.training and targets is None:
            raise ValueError("Targets should be passed during training time")
        original_image_sizes = torch.jit.annotate(List[Tuple[int, int]], [])
        for img in images:
            img_size = img.shape[-2:]
            assert len(img_size) == 2
            original_image_sizes.append((img_size[0], img_size[1]))
        images, targets = self.transform(images, targets)
        features = self.backbone(images.tensors)
        if isinstance(features, Tensor):
            features = OrderedDict([("0", features)])
        proposals, proposal_losses = self.rpn(images, features, targets)
        detections, detector_losses = self.roi_heads(features, proposals,
                                                     images.image_sizes,
                                                     targets)
        detections = self.transform.postprocess(detections,
                                                images.image_sizes,
                                                original_image_sizes)
        losses = {}
        losses.update(detector_losses)
        losses.update(proposal_losses)
        if self.trace_module:
            return detections
        elif torch.jit.is_scripting():
            if not self._has_warned:
                warnings.warn("RCNN always returns a (loss, detections) tuple in scripting")
                self._has_warned = True
            return (losses, detections)
        else:
            return self.eager_outputs(losses, detections)


"""Classes for specific report types."""
from datetime import timedelta

from flask import jsonify
from sqlalchemy import and_, func, or_
from sqlalchemy.dialects.postgresql import INTERVAL
from sqlalchemy.orm import aliased

from api.models import db
from api.models.ea_act import EAAct
from api.models.event import Event
from api.models.event_category import EventCategory
from api.models.event_configuration import EventConfiguration
from api.models.ministry import Ministry
from api.models.phase_code import PhaseCode
from api.models.project import Project
from api.models.proponent import Proponent
from api.models.region import Region
from api.models.staff import Staff
from api.models.substitution_acts import SubstitutionAct
from api.models.work import Work
from api.models.work_phase import WorkPhase
from api.models.work_status import WorkStatus

from .cdog_client import CDOGClient
from .report_factory import ReportFactory

# pylint:disable=not-callable

class EAAnticipatedScheduleReport(ReportFactory):
    """EA Anticipated Schedule Report Generator"""

    def __init__(self, filters):
        """Initialize the ReportFactory"""
        data_keys = [
            "phase_name",
            "date_updated",
            "project_name",
            "proponent",
            "region",
            "location",
            "ea_act",
            "substitution_act",
            "project_description",
            "anticipated_decision_date",
            "additional_info",
            "ministry_name",
            "referral_date",
            "eac_decision_by",
            "decision_by",
            "next_pecp_date",
            "next_pecp_title",
            "next_pecp_short_description",
            "milestone_type",
        ]
        group_by = "phase_name"
        template_name = "anticipated_schedule.docx"
        super().__init__(data_keys, group_by, template_name, filters)
        self.report_title = "Anticipated EA Referral Schedule"

    def _fetch_data(self, report_date):
        """Fetches the relevant data for EA Anticipated Schedule Report"""
        start_date = report_date + timedelta(days=-7)
        eac_decision_by = aliased(Staff)
        decision_by = aliased(Staff)
        pecp_event = aliased(Event)

        pecp_category = EventCategory.query.filter(EventCategory.name == "PCP").first()
        pecp_configuration_ids = EventConfiguration.query.filter(
            EventConfiguration.event_category_id == pecp_category.id
        ).all()
        pecp_configuration_ids = [x.id for x in pecp_configuration_ids]

        decision_category = EventCategory.query.filter(
            EventCategory.name == "Decision"
        ).first()
        decision_configuration_ids = EventConfiguration.query.filter(
            EventConfiguration.event_category_id == decision_category.id
        ).all()
        decision_configuration_ids = [x.id for x in decision_configuration_ids]

        next_decision_event_query = (
            db.session.query(
                Event.work_id,
                func.min(Event.anticipated_date).label("min_anticipated_date"),
            )
            .filter(
                Event.anticipated_date >= start_date,
                Event.event_configuration_id.in_(decision_configuration_ids),
            )
            .group_by(Event.work_id)
            .subquery()
        )

        next_pecp_query = (
            db.session.query(
                Event.work_id,
                func.min(Event.actual_date).label("min_start_date"),
            )
            .filter(
                Event.actual_date >= start_date,
                Event.event_configuration_id.in_(pecp_configuration_ids),
            )
            .group_by(Event.work_id)
            .subquery()
        )

        status_update_max_date_query = (
            db.session.query(
                WorkStatus.work_id,
                func.max(WorkStatus.posted_date).label("max_posted_date"),
            )
            .group_by(WorkStatus.work_id)
            .subquery()
        )

        latest_status_updates = WorkStatus.query.filter(
            WorkStatus.posted_date >= start_date
        ).join(
            status_update_max_date_query,
            and_(
                WorkStatus.work_id == status_update_max_date_query.c.work_id,
                WorkStatus.posted_date
                == status_update_max_date_query.c.max_posted_date,
            ),
        )

        results_qry = (
            latest_status_updates.join(Work)
            .join(Event)
            .join(
                next_decision_event_query,
                and_(
                    Event.work_id == next_decision_event_query.c.work_id,
                    Event.anticipated_date
                    == next_decision_event_query.c.min_anticipated_date,
                ),
            )
            .join(
                EventConfiguration,
                and_(
                    EventConfiguration.id == Event.event_configuration_id,
                    EventConfiguration.id.in_(decision_configuration_ids),
                ),
            )
            .join(WorkPhase, EventConfiguration.work_phase_id == WorkPhase.id)
            .join(PhaseCode, WorkPhase.phase_id == PhaseCode.id)
            .join(Project, Work.project_id == Project.id)
            .join(Proponent)
            .join(Region, Region.id == Project.region_id_env)
            .join(EAAct, EAAct.id == Work.ea_act_id)
            .join(Ministry)
            .outerjoin(eac_decision_by, Work.eac_decision_by)
            .outerjoin(decision_by, Work.decision_by)
            .outerjoin(SubstitutionAct)
            # .outerjoin(
            #     WorkEngagement,
            #     and_(
            #         WorkEngagement.project_id == Work.project_id,
            #         WorkEngagement.milestone_id.in_(pecp_configuration_ids),
            #     ),
            # )
            # .outerjoin(Engagement, WorkEngagement.engagement)
            .outerjoin(
                pecp_event,
                and_(
                    pecp_event.work_id == Work.id,
                    # next_pecp_query.c.min_start_date == pecp_event.actual_date,
                    pecp_event.event_configuration_id.in_(pecp_configuration_ids),
                ),
            )
            .outerjoin(
                next_pecp_query,
                and_(
                    next_pecp_query.c.work_id == pecp_event.work_id,
                    next_pecp_query.c.min_start_date == pecp_event.actual_date,
                    Work.project_id == Work.project_id,
                ),
            )
            # FILTER ENTRIES MATCHING MIN DATE FOR NEXT PECP OR NO WORK ENGAGEMENTS (FOR AMENDMENTS)
            .filter(
                or_(
                    next_pecp_query.c.min_start_date == pecp_event.actual_date,
                    pecp_event.id.is_(None),
                )
            )
            .add_columns(
                PhaseCode.name.label("phase_name"),
                WorkStatus.posted_date.label("date_updated"),
                Project.name.label("project_name"),
                Proponent.name.label("proponent"),
                Region.name.label("region"),
                Project.address.label("location"),
                EAAct.name.label("ea_act"),
                SubstitutionAct.name.label("substitution_act"),
                Project.description.label("project_description"),
                (
                    Event.anticipated_date + func.cast(func.concat(Event.number_of_days, " DAYS"),
                                                       INTERVAL)
                ).label("anticipated_decision_date"),
                # Event.anticipated_end_date.label("anticipated_decision_date"),
                WorkStatus.description.label("additional_info"),
                Ministry.name.label("ministry_name"),
                (
                    Event.anticipated_date + func.cast(func.concat(Event.number_of_days, " DAYS"),
                                                       INTERVAL)
                ).label("referral_date"),
                # Event.anticipated_end_date.label("referral_date"),
                eac_decision_by.full_name.label("eac_decision_by"),
                decision_by.full_name.label("decision_by"),
                EventConfiguration.event_type_id.label("milestone_type"),
                func.coalesce(pecp_event.name, Event.name).label("next_pecp_title"),
                func.coalesce(
                    pecp_event.actual_date,
                    pecp_event.anticipated_date,
                    Event.actual_date,
                ).label("next_pecp_date"),
                pecp_event.notes.label("next_pecp_short_description"),
            )
        )
        return results_qry.all()

    def generate_report(self, report_date, return_type):
        """Generates a report and returns it"""
        data = self._fetch_data(report_date)
        data = self._format_data(data)
        if return_type == "json" and data:
            return {"data": data}, None
        if not data:
            return {}, None
        api_payload = {
            "report_data": data,
            "report_title": self.report_title,
            "report_date": report_date,
        }
        template = self.generate_template()
        report_client = CDOGClient()
        report = report_client.generate_document(
            self.report_title, jsonify(api_payload).json, template
        )
        return report, f"{self.report_title}_{report_date:%Y_%m_%d}.pdf"


import logging
import json
from typing import Dict, List
import urllib.request
import pandas as pd
from argparse import ArgumentTypeError
import os
from loguru import logger

# def get_all_symbols(type:str):
#     # ctx = ssl.create_default_context()
#     # ctx.check_hostname = False
#     # ctx.verify_mode = ssl.CERT_NONE
#     # headers = {
#     #     'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
#     # }
#     if type == 'um':
#         # request = urllib.request.Request("https://fapi.binance.com/fapi/v1/exchangeInfo", headers=headers)
#         response = urllib.request.urlopen("https://fapi.binance.com/fapi/v1/exchangeInfo").read()
#     elif type == 'cm':
#         response = urllib.request.urlopen("https://dapi.binance.com/dapi/v1/exchangeInfo").read()
#     elif type == 'spot':
#         response = urllib.request.urlopen("https://api.binance.com/api/v3/exchangeInfo").read()
#     else:
#         raise ArgumentTypeError(f"The available types are 'um'(for USDT based), 'cm'(for USD based), and 'spot', got {type} instead")
#     return list(map(lambda symbol: symbol['symbol'], json.loads(response)['symbols']))

class SymbolConfig(object):
    UM_URL = "https://fapi.binance.com/fapi/v1/exchangeInfo"
    CM_URL = "https://dapi.binance.com/dapi/v1/exchangeInfo"
    SPOT_URL = "https://api.binance.com/api/v3/exchangeInfo"
    OPTIONS_URL = "https://eapi.binance.com/eapi/v1/exchangeInfo"

    def __init__(self, type:str) -> None:
        self._type = type
        if type == 'um':
            self.response = urllib.request.urlopen(self.UM_URL).read()
        elif type == 'cm':
            self.response = urllib.request.urlopen(self.CM_URL).read()
        elif type == 'spots':
            self.response = urllib.request.urlopen(self.SPOT_URL).read()
        elif type == 'options':
            self.response = urllib.request.urlopen(self.OPTIONS_URL).read()
        else:
           raise ArgumentTypeError(f"The available types are 'um'(for USDT based), 'cm'(for USD based), and 'spot', got {type} instead")
        self.config = None

    def get_all_symbols(self) -> List:
        if self._type == 'options':
            return list(map(lambda symbol: symbol['symbol'], json.loads(self.response)['optionSymbols']))
        return list(map(lambda symbol: symbol['symbol'], json.loads(self.response)['symbols']))

    def get_all_symbols_config(self) -> Dict:
        if self._type == 'options':
            config_lst = json.loads(self.response)['optionSymbols']
        else:
            config_lst = json.loads(self.response)['symbols']
        self.config = pd.DataFrame(config_lst)

    def load_config(self, path:str) -> None:
        self.config = pd.read_csv(path)

    def save_config(self, path:str) -> None:
        if self.config is None:
            raise TypeError('self.config is None, The config has not been loaded')
        if os.path.isfile(path):
            logger.warning(f'{path} exists, it will be overwritted')
        self.config.to_csv(path, index = False)

if __name__ == '__main__':
    save_dir = r'./config/binance'
    spot_config = SymbolConfig('spots')
    spot_config.get_all_symbols_config()
    spot_config.save_config(
        os.path.join(save_dir, 'spots.csv')
    )
    logger.info("binance spot symbols downloaded")
    um_config = SymbolConfig('um')
    um_config.get_all_symbols_config()
    um_config.save_config(
        os.path.join(save_dir, 'um.csv')
    )
    logger.info("binance USD-M futures symbols downloaded")
    cm_config = SymbolConfig('cm')
    cm_config.get_all_symbols_config()
    cm_config.save_config(
        os.path.join(save_dir, 'cm.csv')
    )
    logger.info("binance COIN-M futures symbols downloaded")
    options_config = SymbolConfig('options')
    options_config.get_all_symbols_config()
    options_config.save_config(
        os.path.join(save_dir, 'options.csv')
    )
    logger.info("binance options symbols downloaded")


import re
import os
from math import *
import subprocess
import time
import datetime
import shutil
from functools import *
import septa

home_dir="/home/med"
mail_dir=home_dir+"/mail"
icon_dir=home_dir+"/scripts/icons"

def cmd(c):
	return subprocess.Popen(c,shell=False,stdout=subprocess.PIPE).communicate()[0].decode()

def volume_level():
	raw = cmd(["amixer","sget","Master"])
	rex = re.compile(r'\[([^\[\]\s]+)%\].*\[[^\[\]\s]+\].*\[([^\[\]\s]+)\]')
	(v,m) = rex.search(raw).groups()
	return (int(v),m=='on')

def date():
	return cmd(["date","+%d %b (%a) %I:%M %p"])

def battery_remaining():
  raw = cmd(["acpitool","-B"])

  percent_p = re.search(r'Remaining.*, ([0-9\.]+)%',raw)
  time_p = re.search(r'Remaining.*([0-9]+:[0-9]+):[0-9]+',raw)
  state_p = re.search(r'Charging state.*(Full|Discharging|Unknown|Charging)',raw)

  if time_p:
    time = time_p.groups()[0]
  else:
    time = None

  if percent_p:
    percent = float(percent_p.groups()[0])
  else:
    percent = None

  if state_p:
    state = state_p.groups()[0]
  else:
    state = None

  return (state,percent,time)

def irssi_notifications():
	raw = open("/tmp/med_irssi_pipe").readlines()
	return [r.split("\t") for r in raw]

def instances(rex,mailbox):
	return cmd(["grep",rex,mail_dir+"/"+mailbox])
def count_instances(rex,mailbox):
	return len( instances(rex,mailbox).split("\n") )

def new_mail():
	mailboxes={}
	for mailbox in [f for f in os.listdir(mail_dir) if "IN-" in f]:
		mailboxes[mailbox] = \
		count_instances("^From:",mailbox) \
		- count_instances("^Status:",mailbox)

	return mailboxes

def wifi_online():
	nic="enp1s0"
	wifi="wlp2s0"
	tether="enp0s"
	rex = re.compile(r'inet ([^\ ]*)')
	raw_nic = cmd(["ip","addr","show","dev",nic])
	raw_wifi = cmd(["ip","addr","show","dev",wifi])
	raw_et = cmd(["ip","addr","show","dev",tether])
	m_nic = rex.search(raw_nic)
	m_wifi = rex.search(raw_wifi)
	m_et = rex.search(raw_et)
	if m_nic: return (0,m_nic.groups()[0])
	if m_wifi: return (1,m_wifi.groups()[0])
	if m_et: return (2,m_et.groups()[0])
	if not (m_nic or m_wifi or m_et): return None

def time_to_minutes(s):
	sp = [int(a) for a in reversed( s.split(":") )]
	return sp[0] + sum( [60*a for a in sp[1:]] )

def xmms2_status():
	rex = re.compile(r'([A-Za-z]+): (.+): ([0-9]+:[0-9]+) of ([0-9]+:[0-9]+)')
	raw = cmd(["nyxmms2","current"])
	m = rex.search(raw)
	if m:
		(status,song,current,final) = m.groups()
		duration = time_to_minutes(final)
		progress = time_to_minutes(current)
		return (status,song,progress,duration)
	return None

def time_diff(t1,t2):
	ts1 = [int(t) for t in t1.split(":")]
	ts2 = [int(t) for t in t2.split(":")]
	tsp1 = 60*ts1[0]+ts1[1]
	tsp2 = 60*ts2[0]+ts2[1]
	return tsp1-tsp2

def next_prayer():
	prayers = ["Fajr","Shorooq","Zuhr","Asr","Maghrib","Isha"]
	times_line = cmd(["ipraytime","-b"]).strip().split("\n")[-1]
	times = re.sub(r'\ +',r' ',times_line).split(" ")
	now = datetime.datetime.today().strftime("%H:%M")
	deltas = [time_diff(a,now) for a in times[2:]]
	data = zip(prayers,times[2:],deltas)
	next = [d for d in data if d[2]>0]

	if next:
		return next[0]
	else:
		return None

def usb_present():
  mounts = cmd(["mount"]).strip().split("\n")
  usbs   = [a for a in mounts if 'usb' in a]
  return len(usbs)

##################################
# FUNCTIONS FOR PRINTING TO DZEN #
##################################

def pbar(pct,color="green"):
	filled = floor(pct/2.)
	unfilled = 50-filled
	return "^fg({0})^r({1}x6)^fg(white)^r({2}x6)".format(color,str(filled),str(unfilled))

def statusbar_item(num,data):
	return str(num)+" ^p(+5)^fg()| "+data.strip()+"\n"
def clear_item(num):
	return str(num)+" "

def i(icon,color=""):
	if color:
		return "^fg({0})^i({1}/{2})^fg()".format(color,icon_dir,icon+".xbm")
	else:
		return "^i({0}/{1})".format(icon_dir,icon+".xbm")

def dzen_write(data):
	f=open("/tmp/med-status",'w')
	f.writelines(data)
	f.close()
	return data

def dzen_go(items):
	names={}
	interval={}
	ordering={}
	counter=0
	ord = [a[0] for a in items]
	for a in items:	names[a[0]]=a[1]
	for a in items: interval[a[0]]=a[2]
	maxcounter=1+max(interval.values())
	for k in zip(range(0,len(ord)),ord): ordering[k[1]]=k[0]

	# First run
	for a in names:
		dzen_write(statusbar_item(ordering[a],names[a]()))

	# Run at specified intervals
	while 1==1:
		time.sleep(1)
		counter = (counter+1) % maxcounter
		update_keys = [k for k in interval if counter%interval[k] == 0]
		for i in update_keys:
			dzen_write(statusbar_item(ordering[i],names[i]()))

def dzen_stop():
	for a in range(0,20):
		dzen_write(clear_item(str(a)))

def dz_battery():
	(state,percent,time) = battery_remaining()
	if state == "Discharging":
		if percent >= 0: color = "red"
		if percent > 25: color = "yellow"
		if percent > 50: color = "green"
		return i("bat_empty_01","white")+" "+pbar(percent,color)+" {0}% ({1})".format(int(percent),time)
	if state == "Unknown" or state == "Charging":
		if percent > 95:
			return i("ac","")+" {0}%".format(int(percent),)
		else:
			return i("ac","")+" "+pbar(percent,"black")+" {0}% (--:--)".format(int(percent))
	if state == "Full":
		return i("ac","")+" {0}%".format(int(percent))

def dz_wifi():
	r = wifi_online()
	if r:
		if r[0]==0: return i("net_wired","green")+"^fg(white) "+r[1]
		if r[0]==1: return i("wifi_02","green")+"^fg(white) "+r[1]
		if r[0]==2: return i("usb_02","green")+"^fg(white) "+r[1]
	else:
		return "offline"

def dz_volume():
	(percent,on) = volume_level()
	if on:
		if percent >= 0: color = "green"
		if percent > 42: color = "blue"
		if percent > 80: color = "red"

		return i("spkr_01","green")+" "+pbar(percent,color)
	else:
		return i("spkr_01","")+" "+pbar(percent,"")

def dz_date():
	return "     "+date()

def dz_usb():
  count = usb_present()
  if count:
    return i("usb","green")+" {0}".format(count)
  else:
    return i("usb","")

def dz_mail():
	mb = new_mail()
	abbrevs={"IN-facebook":"fb","IN-inbox":"in","IN-testing":"x","IN-monster":"jb","IN-temple":"tu"}
	boxes = ["^fg("+((not not mb[k])*"white")+")"+abbrevs[k]+"-"+str(mb[k]) for k in sorted(mb) if k in abbrevs and mb[k]]
	nonzero = [b for b in mb.keys() if mb[b]>0]
	if [b for b in mb.keys() if mb[b]>0]:

		color="green"
		if boxes:
			boxdisp=" "+"^p(+2)".join(boxes)
		else:
			boxdisp=""
	else:
		color=""
		boxdisp=""
	return i("mail",color)+boxdisp

def dz_downloaded():
	numfiles = len(os.listdir(home_dir+"/downloaded"))
	movedfiles = len([f for f in os.listdir(home_dir+"/downloaded") if re.match(r'.*\.moved$',f)])
	if numfiles > 0:
		if numfiles < 10:
			color = "blue"
		else:
			color = "red"

		return i("diskette",color)+" "+str(numfiles-movedfiles)+"/"+str(numfiles)
	else:
		if movedfiles == 0:
			return i("diskette","")
		else:
			return i("diskette","white")

def dz_xmms2():
	r = xmms2_status()
	if not r: return i("note","")

	if r[0] == "Playing":
		color = "green"
		color2 = "white"
	elif r[0] == "Stopped":
		return i("note","")
	else:
		color = "white"
		color2 = ""

	percent = int(r[2]/float(r[3])*100)
	progress = 4 - int(percent / 25)

	return i("note",color) +" ^fg("+color2+")"+ r[1] + " " + progress*"." + (4-progress)*" "

def dz_prayer():
	raw = next_prayer()
	if not raw: return i("clock","")
	#else
	if len(raw) != 3: return i("clock","")
	#else again
	(prayer,time,delta) = raw
	if delta < 15:
		icolor="green"
		color="green"
	elif delta < 30:
		icolor = "white"
		color = "white"
	elif delta  < 60:
		icolor = "white"
		color = ""
	else:
		icolor = ""
		color = ""

	return i("clock",icolor) + "^fg({0}) {1} - {2} ({3}m)".format(color,prayer.strip(),time.strip(),delta)

def dz_suspend_lock():
        if is_suspend_locked():
                return i("stop","green")
        else:
                return i("stop","")

def dz_septa():
  lines = [l.strip() for l in open("/home/med/scripts/septastop").readlines() if l.strip()]
  out = ""
  l = lines[0]
  result = septa.get_next("Temple U",l)
  if result is None:
    return "(No trips available)"
  (line,leave,arrive,timing) = septa.get_next("Temple U",l)[0]
  now = datetime.datetime.today().strftime("%H:%M")
  then = datetime.datetime.strptime(leave,"%I:%M%p").strftime("%H:%M")
  dt = time_diff(then,now)
  if dt < 7:
    color = "red"
  elif dt < 15:
    color = "green"
  elif dt < 30:
    color = "white"
  else:
    color = ""
  if timing=="On time":
    out+="^fg({0})[{6}]  {1}  {2}  {3}  ({5} mins)\n".format(color,line,leave,arrive,timing,dt,l)
  else:
    out+="^fg({0})[{6}]  {1}  {2}  {3}  ({5}+{4})\n".format(color,line,leave,arrive,timing,dt,l)
  return out

if __name__ == "__main__":
	ITEMS=[("usb",dz_usb,60),("prayer",dz_prayer,120),("vol",dz_volume,5),("wifi",dz_wifi,60),("batt",dz_battery,60),("date",dz_date,50)]
	dzen_go(ITEMS)


import inspect
from typing import List, Tuple, Sequence

import calculator.core.tokens as tokens
import calculator.core.nodes as nodes
from calculator.core.constants import BinaryOperators, UnaryOperators, OperatorAffix, Separators, MATH_CONSTANTS
from calculator.core.structs import OperatorInfo
from calculator.core.exception import ParsingException

NAME_CHAR_SPECIALS = frozenset(['π', '_'])
VALID_SEPARATORS = frozenset([Separators.SEP_COMMA])

BINOP_TABLE = {
    BinaryOperators.OP_ADD: OperatorInfo(BinaryOperators.OP_ADD, 5),
    BinaryOperators.OP_MINUS: OperatorInfo(BinaryOperators.OP_MINUS, 5),
    BinaryOperators.OP_MULTIPLY: OperatorInfo(BinaryOperators.OP_MULTIPLY, 6),
    BinaryOperators.OP_DIVIDE: OperatorInfo(BinaryOperators.OP_DIVIDE, 6),
    BinaryOperators.OP_MOD: OperatorInfo(BinaryOperators.OP_MOD, 6),
    BinaryOperators.OP_POWER: OperatorInfo(BinaryOperators.OP_POWER, 7)
}

UNARYOP_TABLE = {
    UnaryOperators.OP_NEGATIVE: OperatorInfo(UnaryOperators.OP_NEGATIVE, 5, OperatorAffix.PREFIX),
    UnaryOperators.OP_POSITIVE: OperatorInfo(UnaryOperators.OP_POSITIVE, 5, OperatorAffix.PREFIX),
    UnaryOperators.OP_FACTORIAL: OperatorInfo(UnaryOperators.OP_FACTORIAL, 6, OperatorAffix.POSTFIX)
}

def tokenize(exp: str) -> List[tokens.Token]:
    """
    convert the given string expression into a list of tokens
    """

    def is_name_char(char: str) -> bool:
        # only check after number token, safe to use isnumeric
        return char.isalpha() or char.isnumeric() or (char in NAME_CHAR_SPECIALS)

    def is_op(char: str) -> bool:
        return (char in BINOP_TABLE) or (char in UNARYOP_TABLE)

    def read_number(start: int, token_list: list) -> int:
        state_map = {
            'start': ['int', 'fraction'],
            'int': ['fraction', 'exponent', 'end'],
            'fraction': ['exponent', 'end'],
            'exponent': ['exponent_value'],
            'exponent_value': ['end']
        }

        state = 'start'

        is_end = False
        i = start
        while not is_end and i < len(exp):
            char = exp[i]
            if char.isnumeric():
                if state == 'start':
                    state = 'int'
                elif state == 'exponent':
                    state = 'exponent_value'

                i += 1
            elif char == Separators.SEP_DOT:
                if 'fraction' in state_map[state]:
                    state = 'fraction'
                    i += 1
                else:
                    raise ParsingException("invalid '.'", i)
            elif char.isalpha():
                if char == 'e' or char == 'E':
                    if 'exponent' in state_map[state]:
                        state = 'exponent'
                        i += 1
                    else:
                        raise ParsingException("invalid 'e'", i)
                else:
                    raise ParsingException(f"invalid character '{char}'", i)
            elif char == UnaryOperators.OP_NEGATIVE or char == UnaryOperators.OP_POSITIVE:
                if 'exponent_value' in state_map[state]:
                    state = 'exponent_value'
                    i += 1
                else:
                    is_end = True
            else:
                is_end = True

        if 'end' in state_map[state]:
            content = exp[start : i]
            if state == 'int':
                num = int(content)
            else:
                num = float(content)
            token_list.append(tokens.TokenNumber(num, pos=start))

            return i
        else:
            if is_end:
                raise ParsingException(f"invalid character '{exp[i - 1]}'", i - 1)
            else:
                raise ParsingException(f"invalid character '{exp[i]}'", i)

    def read_name(start: int, token_list: list) -> int:
        i = start
        while i < len(exp) and is_name_char(exp[i]):
            i += 1

        token = tokens.TokenName(exp[start : i], pos=start)
        token_list.append(token)

        return i

    if not isinstance(exp, str):
        raise TypeError("invalid exp")

    if exp == '':
        raise ValueError("exp is empty")

    token_list: List[tokens.Token] = []

    i = 0
    while i < len(exp):
        char = exp[i]

        if is_op(char) or char in VALID_SEPARATORS:
            token_list.append(tokens.TokenSymbol(char, pos=i))
            i += 1
        elif char == Separators.SEP_LEFT_BRACKET:
            token_list.append(tokens.TokenOpenBracket(pos=i))
            i += 1
        elif char == Separators.SEP_RIGHT_BRACKET:
            token_list.append(tokens.TokenCloseBracket(pos=i))
            i += 1
        elif char == Separators.SEP_DOT or char.isnumeric():
            i = read_number(i, token_list)
        elif char == ' ':
            i += 1
        elif is_name_char(char):
            i = read_name(i, token_list)
        else:
            raise ParsingException(f"invalid character '{char}'", i)

    return token_list

def build_expression_tree(token_list: Sequence[tokens.Token]) -> nodes.ExpNode:
    """
    convert a list of tokens into expression tree
    """

    def is_unary_op(op) -> bool:
        return op in UNARYOP_TABLE

    def is_open_bracket(token) -> bool:
        return isinstance(token, tokens.TokenOpenBracket)

    def is_close_bracket(token) -> bool:
        return isinstance(token, tokens.TokenCloseBracket)

    def is_comma(token) -> bool:
        return isinstance(token, tokens.TokenSymbol) and token.symbol == Separators.SEP_COMMA

    def is_higher_or_equal_op_priority(op1, op2, table) -> bool:
        oi1 = table.get(op1)
        oi2 = table.get(op2)

        p1 = 0 if oi1 is None else oi1.priority
        p2 = 0 if oi2 is None else oi2.priority

        return p1 >= p2

    def read_exp_chain(index) -> Tuple[nodes.ExpNode, int]:
        token = token_list[index]
        if isinstance(token, tokens.TokenSymbol):
            if is_open_bracket(token):
                node, i = read_exp(index)
            elif is_unary_op(token.symbol):
                if UNARYOP_TABLE[token.symbol].affix == OperatorAffix.PREFIX:
                    node, i = read_prefix_unary_exp(index)
                else:
                    raise ParsingException(f"unary operator '{token.symbol}' is not a prefix operator", token.pos)
            else:
                raise ParsingException(f"unexpected symbol '{token.symbol}'", token.pos)
        else:
            node, i = read_exp(index)

        if i < len(token_list):
            # look ahead for 1 token
            next_token = token_list[i]
            if isinstance(next_token, tokens.TokenSymbol) and is_unary_op(next_token.symbol):
                if UNARYOP_TABLE[next_token.symbol].affix == OperatorAffix.POSTFIX:
                    node, i = read_postfix_unary_exp(i, node)
        else:
            return (node, i)

        if i < len(token_list):
            # look ahead for 1 token
            next_token = token_list[i]
            if is_close_bracket(next_token):
                return (node, i)
            elif isinstance(next_token, tokens.TokenSymbol):
                if next_token.symbol == Separators.SEP_COMMA:
                    return (node, i)
                elif next_token.symbol in BINOP_TABLE:
                    return read_binary_exp(i, node)
                else:
                    raise ParsingException(f"unexpected symbol '{next_token.symbol}'", next_token.pos)
            else:
                raise ParsingException("unexpected token", next_token.pos)
        else:
            return (node, i)

    def read_exp(index) -> Tuple[nodes.ExpNode, int]:
        if index >= len(token_list):
            raise ParsingException("unexpected token", token_list[-1].pos)

        token = token_list[index]
        if is_open_bracket(token):
            return read_bracket_exp(index)
        elif isinstance(token, tokens.TokenNumber):
            return (nodes.NumberNode(token.num, pos=token.pos), index + 1)
        elif isinstance(token, tokens.TokenName):
            if (index + 1) < len(token_list) and is_open_bracket(token_list[index + 1]):
                return read_func_call(index)
            else:
                return (nodes.NameConstantNode(token.name, pos=token.pos), index + 1)
        elif isinstance(token, tokens.TokenSymbol):
            raise ParsingException(f"unexpected symbol '{token.symbol}'", token.pos)
        else:
            raise ParsingException("unexpceted token", token.pos)

    def read_bracket_exp(index) -> Tuple[nodes.ExpNode, int]:
        node, i = read_exp_chain(index + 1)

        if i < len(token_list) and is_close_bracket(token_list[i]):
            return (node, i + 1)
        else:
            raise ParsingException("unmatch '('", token_list[index].pos)

    def read_prefix_unary_exp(index) -> Tuple[nodes.UnaryOpNode, int]:
        node, i = read_exp(index + 1)
        token = token_list[index]
        return (nodes.UnaryOpNode(token.symbol, node, pos=token.pos), i)

    def read_postfix_unary_exp(index, child: nodes.ExpNode) -> Tuple[nodes.UnaryOpNode, int]:
        token = token_list[index]

        if isinstance(child, nodes.UnaryOpNode):
            if is_higher_or_equal_op_priority(token.symbol, child.op, UNARYOP_TABLE):
                node = nodes.UnaryOpNode(token.symbol, child.child, pos=token.pos)
                child.child = node
                node = child
            else:
                node = nodes.UnaryOpNode(token.symbol, child, pos=token.pos)
        else:
            node = nodes.UnaryOpNode(token.symbol, child, pos=token.pos)

        return (node, index + 1)

    def read_binary_exp(index, left: nodes.ExpNode) -> Tuple[nodes.BinaryOpNode, int]:
        right, i = read_exp_chain(index + 1)

        token = token_list[index]
        if isinstance(right, nodes.BinaryOpNode) and not is_open_bracket(token_list[index + 1]):
            # check operator priority and rotate the expression tree when necessary.
            # when priority of two operators are equal, we also should rotate the tree
            # in case these operators don't follow the commutative law.
            if is_higher_or_equal_op_priority(token.symbol, right.op, BINOP_TABLE):
                node = nodes.BinaryOpNode(token.symbol, left, right.left, pos=token.pos)
                right.left = node
                node = right
            else:
                node = nodes.BinaryOpNode(token.symbol, left, right, pos=token.pos)
        else:
            node = nodes.BinaryOpNode(token.symbol, left, right, pos=token.pos)

        return (node, i)

    def read_func_call(index) -> Tuple[nodes.FuncCallNode, int]:
        name_token = token_list[index]
        index += 2  # skip '('

        token_count = len(token_list)

        node = None
        i = index
        args = []

        while i < token_count and not is_close_bracket(token_list[i]):
            node, i = read_exp_chain(i)
            args.append(node)
            if i < token_count and is_comma(token_list[i]):
                i += 1
            else:
                break

        if i < token_count and is_close_bracket(token_list[i]):
            func_node = nodes.FuncCallNode(name_token.name, args, pos=name_token.pos)
            return (func_node, i + 1)
        else:
            raise ParsingException("unclose func call", name_token.pos)

    node, i = read_exp_chain(0)

    if i < len(token_list):
        last_token = token_list[i]
        if is_close_bracket(last_token):
            raise ParsingException("unmatch ')'", last_token.pos)
        else:
            raise ParsingException("unexpected token", last_token.pos)
    else:
        return node

def parse_expression(expression: str) -> nodes.ExpNode:
    """
    parse the given string expression into an expression tree
    """

    tokens = tokenize(expression)
    node = build_expression_tree(tokens)

    return node


from math import ceil, floor
from typing import List

def smallestDivisor(nums: List[int], threshold: int) -> int:
    """Return smallest divisor such that sum of `nums` normalised
    with respect to the divisor is at most `threshold`."""

    lo = 1
    hi = max(nums)

    while lo < hi:
        mid = floor((lo + hi) / 2)
        normalisedSum = sum(ceil(num / mid) for num in nums)

        if normalisedSum <= threshold:
            # Can try smaller threshold, but keep `mid` as candidate
            hi = mid
        else:
            # Sum too large, need bigger divisor
            lo = mid + 1

    return lo


# (c) Nelen & Schuurmans.  GPL licensed, see LICENSE.rst.

import logging
from itertools import tee

import numpy as np

logger = logging.getLogger(__name__)

BBOX_LEFT = 0
BBOX_TOP = 1
BBOX_RIGHT = 2
BBOX_BOTTOM = 3

class PKMapper:
    """
    Solves the following situation:

    content_pk[pk[i]] == to_map[i]

    Having two array's of the same length, with corresponding
    values:

        pk     = [1,     2,   3,   4,   5,   6]
        to_map = [1.1, 2.1, 3.1, 4.1, 5.1, 6.1]

    And an array with with (unsorted) numbers that might
    match values in pk:

        content_pk = [0, 0, 15, 3, 4, 0, 0, 1, 1, 15]

    PKMapper(pk, content_pk).apply_on(to_map, 0)

    result = [0, 0, 0, 3.1, 4.1, 0, 0, 1.1, 1.1, 0]
    """

    def __init__(self, pk, content_pk, ignore_mask=None):
        """
        :param pk: np.ndarray with pk values
        :param content_pk: np.ndarray with pk values to map to
        :param ignore_mask: boolean np.ndarray, when True the value
                            of content_pk is ignored.
        """
        # NOTE: pk should only contain unique values

        # create copies of pk an content_pk and
        # add -1 to front of pk
        # This value serves as a trash value and will be returned
        # when values in content_pk are 0 or not in pk.
        content_pk_copy = np.array(content_pk)
        pk_copy = np.concatenate((np.array([-1]), pk))

        # Apply the boolean_mask if given
        if isinstance(ignore_mask, np.ndarray):
            content_pk_copy[ignore_mask] = -1

        # Set content_pk_copy[i] = -1 if the value at i is not in pk
        content_pk_copy[np.invert(np.isin(content_pk_copy, pk))] = -1

        # sort and create an array with indices of pk
        # on content_pk
        sort_idx = np.argsort(pk_copy)
        self._indices = sort_idx[
            np.searchsorted(pk_copy, content_pk_copy, sorter=sort_idx)
        ]

        # Note: self._indices[i] == 0 when the content_pk[i] could not
        # be found in pk

        del content_pk_copy
        del pk_copy

    def apply_on(self, np_array, null_value=0):
        """
        Apply the filter on np_array.

        :param np_array: the np.ndarray with values to map to content_pk
        :param null_value: the value inserted when the content_pk value
                           is zero or could not be found in pk.

        :return: an np_array with length content_pk with
                 pk[i] values replaced with np_array[i]
        """
        # Add the 'null_value' in front of np_array,
        # self._indices[i] == 0 when the content_pk[i] is not in pk
        # thus returns the null_value
        if len(np_array.shape) > 1:
            to_select_from = np.insert(np_array, 0, null_value, axis=1)
        else:
            to_select_from = np.concatenate((np.array([null_value]), np_array))

        # Enable multidimensional array's.
        _filter = [slice(None)] * (len(np_array.shape) - 1) + [self._indices]
        return to_select_from[tuple(_filter)]

class KCUDescriptor(dict):
    """
    A dictionary-like object that provides human readable descriptions
    for threedicore kcu codes::

            0: '1d embedded line',
            1: '1d isolated line',
            2: '1d connected line',
            3: '1d long-crested structure',
            4: '1d short-crested structure',
            5: '1d double connected line',
            51: '1d2d single connected line with storage',
            52: '1d2d single connected line without storage',
            53: '1d2d double connected line with storage',
            54: '1d2d double connected line without storage',
            55: '1d2d connected line possible breach',
            56: '1d2d connected line active breach',
            57: '1d2d groundwater link',
            58: '1d2d groundwater link # diff to 57? ',
            100: '2d line',
            101: '2d obstacle (levee) line',
            150: '2d vertical link',
            -150: '2d groundwater link',
            200: '2d boundary',
            300: '2d boundary',
            400: '2d boundary',
            500: '2d boundary',
            600: '2d groundwater boundary',
            700: '2d groundwater boundary',
            800: '2d groundwater boundary',
            900: '2d groundwater boundary',

    """

    def __init__(self, *arg, **kw):
        super().__init__(*arg, **kw)
        self.bound_keys_groundwater = [600, 700, 800, 900]
        self.bound_keys_2d = [200, 300, 400, 500]

        self._descr = {
            0: "1d embedded line",
            1: "1d isolated line",
            2: "1d connected line",
            3: "1d long-crested structure",
            4: "1d short-crested structure",
            5: "1d double connected line",
            51: "1d2d single connected line with storage",
            52: "1d2d single connected line without storage",
            53: "1d2d double connected line with storage",
            54: "1d2d double connected line without storage",
            55: "1d2d connected line possible breach",
            56: "1d2d connected line active breach",
            57: "1d2d groundwater link",
            58: "1d2d groundwater link # diff to 57? ",
            100: "2d line",
            101: "2d obstacle (levee) line",
            150: "2d vertical link",
            -150: "2d groundwater link",
            200: "2d boundary",
            300: "2d boundary",
            400: "2d boundary",
            500: "2d boundary",
            600: "2d groundwater boundary",
            700: "2d groundwater boundary",
            800: "2d groundwater boundary",
            900: "2d groundwater boundary",
        }

    def get(self, item):
        return self.__getitem__(item)

    def values(self):
        v = list(self._descr.values())
        v.extend(["2d boundary", "2d groundwater boundary"])
        return v

    def keys(self):
        """
        Return keys
        """
        k = list(self._descr.keys())
        k += self.bound_keys_2d
        k += self.bound_keys_groundwater
        return k

    def __getitem__(self, item):
        if item in self.bound_keys_2d:
            return "2d boundary"
        elif item in self.bound_keys_groundwater:
            return "2d groundwater boundary"
        v = self._descr.get(item)
        if not v:
            raise KeyError(item)
        return v

def pairwise(iterable):
    # from https://docs.python.org/2/library/
    # itertools.html#recipes
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

def get_or_create_group(grid_file, group_name):
    """

    :param grid_file:
    :param group_name:
    :return:
    """
    gr = grid_file.get(group_name, None)
    if not gr:
        gr = grid_file.create_group(group_name)
    return gr

def _get_storage_area(storage_area):
    """

    :param storage_area: storage area entry in nodes collection.
        Either a numpy string or a bytes type
    :returns default '--' if the input is an empty string or 0., or the
        given input will be converted to a float representation
    """

    default_null = "--"
    # case python string type
    try:
        a = float(storage_area)
        if a > 0.0:
            return a
    except ValueError:
        pass
    except TypeError:
        if storage_area is None:
            return default_null
        return _get_storage_area(np.asscalar(storage_area))

    return default_null