import argparse
import logging
import os
import re
import shutil
import subprocess
import sys
import tempfile
from datetime import datetime

try:
    import yaml
except ImportError:
    import ruamel.yaml as yaml

import json

from pkg_resources import parse_version

try:
    import markdown

    HAVE_MARKDOWN = True
except ImportError:
    HAVE_MARKDOWN = False

DEFAULT_GIT_SITE = "https://github.com"
DEFAULT_GIT_ORG = "linux-system-roles"
CHANGELOG_HEADER = "Changelog\n=========\n\n"

if HAVE_MARKDOWN:

    class LSRMarkdown(markdown.Markdown):
        def convert(self, source):
            self.lines = source.split("\n")
            return self.parser.parseDocument(self.lines).getroot()

    class ChangelogManager(object):
        def __init__(self):
            # The keys of this dict are "New Features", "Bug Fixes",
            # and "Other Changes".  The value for each of these is a
            # dict.  The dict key is the rolename.  The dict value is the
            # list of changelog entries.
            self.updates = {}

        def addRoleChangelog(self, args, rolename, commit_msgs, cur_ref, new_ref):
            """Add a changelog for the role since the given tag."""
            if comp_versions(cur_ref, new_ref) >= 0:
                return
            if commit_msgs:
                # make a fake changelog since there are no new changelog entries
                self.updates.setdefault("Bug Fixes", {}).setdefault(
                    rolename, []
                ).extend(commit_msgs)
                return
            elif cur_ref is None:
                # just new feature new role
                self.updates.setdefault("New Features", {}).setdefault(
                    rolename, []
                ).insert(0, "New Role")
                return
            changelogmd = os.path.join(args.src_path, rolename, "CHANGELOG.md")
            if not os.path.exists(changelogmd):
                return
            data = open(changelogmd, encoding="utf-8").read()
            root = LSRMarkdown().convert(data)
            cur_tag_match = "[" + cur_ref + "]"
            new_tag_match = "[" + new_ref + "]"
            gathering = False
            current_hdr = None
            for elem in root:
                if elem.tag == "h2":  # tag + date
                    if elem.text.startswith(new_tag_match):
                        gathering = True
                    if elem.text.startswith(cur_tag_match):
                        return
                elif not gathering:
                    continue
                elif elem.tag == "h3":  # New Features, etc.
                    current_hdr = elem.text
                elif elem.tag == "ul":  # The list of entries
                    for entry in elem:
                        if entry.text != "none":
                            self.updates.setdefault(current_hdr, {}).setdefault(
                                rolename, []
                            ).insert(0, entry.text)

        def formatChangelogUpdate(self, tag, date):
            """Generate a new changelog update."""
            if not self.updates:
                return None
            rv = f"{CHANGELOG_HEADER}[{tag}] - {date}\n---------------------\n"
            hdrs = [hdr for hdr in ("New Features", "Bug Fixes") if hdr in self.updates]
            for hdr in hdrs:
                rv = rv + f"\n### {hdr}\n\n"
                for rolename in sorted(self.updates[hdr]):
                    for entry in self.updates[hdr][rolename]:
                        rv = rv + f"- {rolename} - {entry}\n"
            if not hdrs:
                rv = rv + "\n### Other Changes\n\n- no user-visible changes\n"
            return rv + "\n"

def run_cmd(cmdlist, cwd=None, env=None):
    """Run the given cmdlist using subprocess.  Debug log the output.
    If check is true, this function will work like check_call.
    The return value is like subprocess.run"""
    kwargs = {"env": os.environ}
    if env:
        kwargs["env"].update(env)
    rc = subprocess.run(
        cmdlist,
        cwd=cwd,
        encoding="utf-8",
        check=False,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        **kwargs,
    )
    logging.debug(
        f"{' '.join(cmdlist)} returned {rc.returncode} stdout {rc.stdout} stderr {rc.stderr}"
    )
    if rc.returncode != 0:
        raise subprocess.CalledProcessError(
            rc.returncode, cmdlist, rc.stdout, rc.stderr
        )
    return rc

def lsr_parse_version(v_str):
    test_v = parse_version("1")  # guaranteed to work
    if v_str is None:
        v = parse_version("0.0.0")
    else:
        v = parse_version(v_str)
    if not isinstance(v, test_v.__class__):
        raise ValueError(f"Error: {v_str} is not a proper version number")
    if not hasattr(v, "release"):
        if hasattr(v, "_version"):
            setattr(v, "release", v._version.release)
    return v

def check_versions_updated(cur_ref, new_ref, versions_updated):
    """Compare versions and update list.

    If cur_ref and new_ref are both valid semantic versions,
    compare them, and indicate in versions_updated if the major,
    minor, and/or micro versions were updated.  One exception is
    when the major version is upgraded from 0 to 1 - in that case,
    do not mark this as requiring a major version change.
    Another special case is adding a new role - in that case, cur_ref
    is None - we do not want to bump the major version, only the
    minor version."""

    if cur_ref is None:
        versions_updated[1] = True
        return
    try:
        cur_v = lsr_parse_version(cur_ref)
        new_v = lsr_parse_version(new_ref)
        for idx in range(0, 3):
            if cur_v.release[idx] != new_v.release[idx]:
                if idx > 0 or cur_v.release[idx] > 0:
                    versions_updated[idx] = True
    except ValueError as exc:
        logging.debug(f"Could not compare version {cur_ref} to {new_ref}: {exc}")
        if cur_ref != new_ref:
            versions_updated[3] = True

def comp_versions(cur_ref, new_ref):
    """Compare versions.

    Return values:
    -1 - if cur_ref < new_ref
     0 - if cur_ref == new_ref
     1 - if cur_ref > new_ref"""

    try:
        v_cur_ref = lsr_parse_version(cur_ref)
        v_new_ref = lsr_parse_version(new_ref)
        if v_cur_ref < v_new_ref:
            return -1
        elif v_cur_ref == v_new_ref:
            return 0
        else:
            return 1
    except ValueError as exc:
        logging.debug(f"Could not compare version {cur_ref} to {new_ref}: {exc}")
        if cur_ref == new_ref:
            return 0
        else:
            return -1  # assume new is "newer" than cur

def get_latest_tag_hash(args, rolename, cur_ref, org, repo, use_commit_hash):
    """
    Get the latest tag, hash, and tag_is_latest from the upstream repo.

    Clone and/or update the local copies of the repos.  Get
    the latest tag and/or commit hash for each repo.  Indicate if
    the tag is the latest commit.
    """
    roledir = os.path.join(args.src_path, rolename)
    # clone and/or update role repo
    if os.path.isdir(roledir):
        _ = run_cmd(["git", "fetch"], roledir)
    else:
        _ = run_cmd(
            [
                "git",
                "-c",
                "advice.detachedHead=false",
                "clone",
                "-q",
                f"{DEFAULT_GIT_SITE}/{org}/{repo}",
                roledir,
            ],
        )
    branch_output = run_cmd(["git", "branch", "-r"], roledir)
    # determine what is the main branch, check it out, and update it
    mmatch = re.search(r"origin/HEAD -> origin/(\w+)", branch_output.stdout)
    main_branch = mmatch.group(1)
    _ = run_cmd(["bash", "-c", f"git checkout {main_branch}; git pull --tags"], roledir)
    commit_msgs = ""
    # see if there have been any commits since the last time we checked
    if cur_ref:
        count_output = run_cmd(
            ["bash", "-c", f"git log --oneline {cur_ref}.. | wc -l"],
            roledir,
        )
    else:
        count_output = run_cmd(
            ["bash", "-c", "git log --oneline | wc -l"],
            roledir,
        )
    tag, commit_hash, n_commits, prev_tag = None, None, "0", None
    if count_output.stdout == "0":
        logging.debug(f"no changes to role {rolename} since ref {cur_ref}")
    else:
        # get latest tag and commit hash
        describe_cmd = ["git", "describe", "--tags", "--long", "--abbrev=40"]
        describe_output = run_cmd(describe_cmd, roledir)
        tag, n_commits, g_hash = describe_output.stdout.strip().rsplit("-", 2)
        # commit hash - skip leading "g"
        commit_hash = g_hash[1:]
        if n_commits != "0" and use_commit_hash:
            # get commit messages to use for changelog
            log_cmd = [
                "git",
                "log",
                "--oneline",
                "--no-merges",
                "--reverse",
                "--pretty=format:%s",
            ]
            if cur_ref:
                log_cmd.append(f"{cur_ref}..")
            log_output = run_cmd(log_cmd, roledir)
            commit_msgs = log_output.stdout.replace("\\r", "")
        # get previous tag in case cur_ref is a commit hash
        if cur_ref:
            try:
                describe_cmd = [
                    "git",
                    "describe",
                    "--tags",
                    "--long",
                    "--abbrev=40",
                    cur_ref,
                ]
                describe_output = run_cmd(describe_cmd, roledir)
                prev_tag = describe_output.stdout.strip().split("-")[0]
                if prev_tag == cur_ref:
                    prev_tag = None  # cur_ref was already a valid tag
            except subprocess.CalledProcessError:
                prev_tag = None  # no previous tag
        else:
            prev_tag = None  # no previous tag
    if args.no_update and cur_ref:
        # make sure cur_ref is checked out
        _ = run_cmd(["git", "checkout", cur_ref], roledir)
    return (tag, commit_hash, n_commits == "0", commit_msgs, prev_tag)

def process_ignore_and_lint_files(args, coll_dir):
    """Create collection ignore-VER.txt and .ansible-lint files from roles."""
    ignore_file_dir = os.path.join(coll_dir, "tests", "sanity")
    # role2collection is currently done using ruamel rt with a long line length,
    # because we can't ensure converted lines will be wrapped preserving
    # Jinja and ansible syntax.  This means we trigger a lot of warnings like
    # this when importing into Galaxy:
    # roles/file.yml:61: yaml line too long (187 > 160 characters)
    # the only thing we can do is suppress these
    ansible_lint = {"skip_list": ["yaml[line-length]"]}
    for role_name in os.listdir(args.src_path):
        roledir = os.path.join(args.src_path, role_name)
        if (
            os.path.isdir(roledir)
            and os.path.isfile(os.path.join(roledir, "tasks", "main.yml"))
            and role_name in args.include
        ):
            for file_name in os.listdir(roledir):
                if file_name.startswith(".sanity-ansible-ignore-"):
                    if not os.path.isdir(ignore_file_dir):
                        os.mkdir(ignore_file_dir)
                    match = re.match(
                        r"^[.]sanity-ansible-ignore-(\d[\d.]+)[.]txt$", file_name
                    )
                    if match and match.groups() and match.group(1):
                        ver = match.group(1)
                        ignore_file = os.path.join(ignore_file_dir, f"ignore-{ver}.txt")
                        role_ignore_file = os.path.join(roledir, file_name)
                        with open(ignore_file, "a") as ign_fd:
                            with open(role_ignore_file, "r") as role_ign_fd:
                                ign_fd.write(role_ign_fd.read())
                elif file_name == ".ansible-lint":
                    role_ansible_lint = yaml.safe_load(
                        open(os.path.join(roledir, file_name))
                    )
                    # ensure we suppress line-length checks
                    for key, items in role_ansible_lint.items():
                        if key not in ansible_lint:
                            ansible_lint[key] = role_ansible_lint[key]
                            continue
                        for item in items:
                            if item not in ansible_lint[key]:
                                if isinstance(ansible_lint[key], list):
                                    ansible_lint[key].append(item)
                                elif isinstance(ansible_lint[key], dict):
                                    ansible_lint[key][item] = items[item]
    yaml.safe_dump(ansible_lint, open(os.path.join(coll_dir, ".ansible-lint"), "w"))

def role_to_collection(
    args,
    rolename,
    namespace,
    collection_name,
    collection_readme,
):
    """Convert the role to collection format."""
    subrole_prefix = f"private_{rolename}_subrole_"
    cmd = [
        sys.executable,
        "lsr_role2collection.py",
        "--src-owner",
        args.src_owner,
        "--role",
        rolename,
        "--src-path",
        args.src_path,
        "--dest-path",
        args.dest_path,
        "--namespace",
        namespace,
        "--collection",
        collection_name,
        "--subrole-prefix",
        subrole_prefix,
        "--readme",
        collection_readme,
    ]
    extra_mapping = ""
    comma = ""
    if rolename == "sshd":
        # HACK - special case for ansible-sshd - not fully qualified
        extra_mapping = f"ansible-sshd:{namespace}.{collection_name}.{rolename}"
        comma = ","
    if args.extra_mapping:
        extra_mapping = extra_mapping + comma + args.extra_mapping
    if extra_mapping:
        cmd.extend(["--extra-mapping", extra_mapping])
    _ = run_cmd(cmd)

def update_galaxy_version(args, galaxy, versions_updated):
    if args.no_update:
        return
    if (
        not versions_updated[3]
        and any(versions_updated[0:3])
        and not args.no_auto_version
    ):
        galaxy_ver = lsr_parse_version(galaxy["version"])
        major, minor, micro = galaxy_ver.release
        if versions_updated[0]:
            major = major + 1
            minor = 0
            micro = 0
        elif versions_updated[1]:
            minor = minor + 1
            micro = 0
        elif versions_updated[2]:
            micro = micro + 1
        galaxy["version"] = f"{major}.{minor}.{micro}"
    elif args.new_version:
        galaxy["version"] = args.new_version
    else:
        raise Exception(
            "No new-version given and cannot auto-update version.  "
            "Please examine the changes to the collection, determine what "
            "the new semantic version will be, and re-run this script with "
            "the --new-version argument"
        )
    # not worth trying to figure out how to do a round-trip yaml write here
    # not work using ruamel here
    # this is simple enough for re.sub
    with open(args.galaxy_yml.name) as gf:
        galaxy_str = gf.read()
        galaxy_str = re.sub(
            r"^version:.*$", f'version: "{galaxy["version"]}"', galaxy_str, flags=re.M
        )
    with open(args.galaxy_yml.name, "w") as gf:
        gf.write(galaxy_str)

def create_collection_extra_files(args, coll_dir, galaxy=None):
    collection_requirements = os.path.join(
        "lsr_role2collection", "collection_requirements.txt"
    )
    collection_requirements_dest = os.path.join(coll_dir, "requirements.txt")
    collection_bindep = os.path.join("lsr_role2collection", "collection_bindep.txt")
    collection_bindep_dest = os.path.join(coll_dir, "bindep.txt")
    collection_req_yml_dest = os.path.join(coll_dir, "requirements.yml")
    collection_aee_dest = os.path.join(coll_dir, "execution-environment.yml")
    collection_yamllint = os.path.join("lsr_role2collection", "yamllint.yml")
    collection_yamllint_dest = os.path.join(coll_dir, ".yamllint.yml")
    shutil.copy(collection_yamllint, collection_yamllint_dest)
    # If --rpm, files such as galaxy.yml in coll_dir are being used.
    process_ignore_and_lint_files(args, coll_dir)
    if not args.rpm:
        if galaxy:
            with open(os.path.join(coll_dir, "galaxy.yml"), "w") as galaxy_fd:
                yaml.safe_dump(galaxy, galaxy_fd, explicit_start=True)
        else:
            shutil.copy(args.galaxy_yml.name, coll_dir)
        shutil.copy(args.collection_release_yml.name, coll_dir)
        ee_dependencies = []
        if os.path.exists(collection_requirements):
            shutil.copy(collection_requirements, collection_requirements_dest)
            ee_dependencies.append("python: requirements.txt")
        if os.path.exists(collection_bindep):
            shutil.copy(collection_bindep, collection_bindep_dest)
            ee_dependencies.append("system: bindep.txt")
        if galaxy["dependencies"]:
            with open(collection_req_yml_dest, "w") as crf:
                crf.write("---\ncollections:\n")
                for k in galaxy["dependencies"].keys():
                    crf.write("  - name: {0}\n".format(k))
            ee_dependencies.append("galaxy: requirements.yml")
        with open(collection_aee_dest, "w") as eef:
            eef.write("---\nversion: 1\n\n")
            eef.write("build_arg_defaults:\n")
            eef.write("  EE_BASE_IMAGE: {0}\n\n".format(args.ee_base_image))
            eef.write("ansible_config: 'ansible.cfg'\n\n")
            if len(ee_dependencies):
                eef.write("dependencies:\n")
                for eed in ee_dependencies:
                    eef.write("  {0}\n".format(eed))

def build_collection(args, coll_dir, galaxy=None):
    create_collection_extra_files(args, coll_dir, galaxy)
    # removing dot files/dirs
    keep_files = set([".ansible-lint", ".yamllint.yml"])
    for file_name in os.listdir(coll_dir):
        if file_name in keep_files:
            continue
        if re.match(r"^[.][a-zA-Z]+", file_name):
            full_name = os.path.join(coll_dir, file_name)
            if os.path.isdir(full_name):
                shutil.rmtree(full_name)
            else:
                os.unlink(full_name)

    if shutil.which("ansible-galaxy"):
        build_args = ["ansible-galaxy", "collection", "build", "-v"]
        if args.force:
            build_args.append("-f")
        build_args.append(coll_dir)
        _ = run_cmd(build_args, args.dest_path)
    else:
        logging.info("ansible-galaxy is skipped since it is not available.")

def conv_md2rest(changelog_md_path, changelog_rest_path):
    """
    Convert the md format to the reStructuredText format
    """
    rval = ""
    try:
        import pypandoc

        rval = pypandoc.convert_file(changelog_md_path, "rst", format="md")
    except (IOError, ImportError):
        raise Exception("Failed to import pypandoc module")
    with open(changelog_rest_path, "w", encoding="utf-8") as f:
        f.write(rval)
    # Verify the converted reST
    try:
        from rst2html import rst2html

        with open(changelog_rest_path, "r") as f:
            rst_text = f.read()
        html, warning = rst2html(rst_text, report_level=3)
        if len(warning) > 0:
            raise Exception(
                "Converted CHANGELOG.rst has a problem as html - {}".format(warning)
            )
    except (IOError, ImportError):
        logging.warning("Failed to import rst2html module")
        pass

def convert_md2rst(coll_dir):
    """
    Convert CHANGELOG.md to CHANGELOG.rst
    """
    coll_changelog_path = os.path.join(coll_dir, "docs", "CHANGELOG.md")
    if os.path.exists(coll_changelog_path):
        coll_changelog_rest_path = os.path.join(coll_dir, "CHANGELOG.rst")
        conv_md2rest(coll_changelog_path, coll_changelog_rest_path)
    else:
        logging.warning(
            "Missing collection CHANGELOG.md {0}".format(coll_changelog_path)
        )

def update_collection(args, galaxy, coll_rel):
    """
    Update refs in collection_release.yml.

    Use the latest tag for the ref.  If use_commit_hash is True
    and the latest commit is not tagged, use the commit hash of
    the latest commit for the ref.  Or if the role is specified
    using use_commit_hash_role.
    """
    coll_dir = os.path.join(
        args.dest_path, "ansible_collections", galaxy["namespace"], galaxy["name"]
    )
    if os.path.isdir(coll_dir):
        if args.keep:
            pass  # do nothing - keep whatever is there
        elif args.force:
            shutil.rmtree(coll_dir)
        else:
            raise Exception(
                "collection dest_path {} already exists - remove it, use --force "
                "to remove, or use --keep".format(coll_dir)
            )
    os.makedirs(coll_dir, exist_ok=True)
    collection_readme = os.path.join("lsr_role2collection", "collection_readme.md")
    if not args.skip_changelog:
        cl_manager = ChangelogManager()
    # major, minor, micro, hash
    versions_updated = [False, False, False, False]
    for rolename in args.include:
        if not args.skip_git:
            if args.use_commit_hash and rolename not in args.use_commit_hash_role:
                args.use_commit_hash_role.append(rolename)
            cur_ref = coll_rel[rolename]["ref"]
            tag, cm_hash, tag_is_latest, commit_msgs, prev_tag = get_latest_tag_hash(
                args,
                rolename,
                coll_rel[rolename]["ref"],
                coll_rel[rolename].get("org", args.src_owner),
                coll_rel[rolename].get("repo", rolename),
                rolename in args.use_commit_hash_role,
            )
            if tag or cm_hash:
                if tag_is_latest or rolename not in args.use_commit_hash_role:
                    coll_rel[rolename]["ref"] = tag
                else:
                    coll_rel[rolename]["ref"] = cm_hash
                if not tag_is_latest and not args.no_auto_version:
                    logging.debug(
                        f"role {rolename} tag {tag} is not the latest commit {cm_hash}"
                    )
                check_versions_updated(
                    cur_ref, coll_rel[rolename]["ref"], versions_updated
                )
            # If a version update is detected, retrieve the new changelogs from CHANGELOG.md
            if not args.skip_changelog:
                new_ref = coll_rel[rolename]["ref"]
                if comp_versions(cur_ref, new_ref) < 0:
                    if prev_tag is None:
                        prev_tag = cur_ref
                    logging.info(
                        "The role [%s] is updated. Getting changelog entries from [%s] to [%s].",
                        rolename,
                        prev_tag,
                        new_ref,
                    )
                    cl_manager.addRoleChangelog(
                        args, rolename, commit_msgs, prev_tag, new_ref
                    )
                else:
                    logging.info(
                        "No updates for role [%s] since [%s]", rolename, cur_ref
                    )
        role_to_collection(
            args,
            rolename,
            galaxy["namespace"],
            galaxy["name"],
            collection_readme,
        )
        this_collection = galaxy["namespace"] + "." + galaxy["name"]
        legacy_rqf = "requirements.yml"
        coll_rqf = "collection-requirements.yml"
        for rqf in [legacy_rqf, coll_rqf]:
            req_yml = os.path.join(args.src_path, rolename, "meta", rqf)
            if os.path.isfile(req_yml):
                req_yml_hsh = yaml.safe_load(open(req_yml))
                if isinstance(req_yml_hsh, list):
                    continue  # legacy role format
                if rqf == legacy_rqf:
                    logging.warning(
                        "The role %s is still using %s - please convert to %s instead",
                        rolename,
                        rqf,
                        coll_rqf,
                    )
                for coll in req_yml_hsh.get("collections", []):
                    if isinstance(coll, dict):
                        coll_name = coll["name"]
                        coll_ver = coll.get("version", "*")
                    else:
                        coll_name = coll
                        coll_ver = "*"
                    if coll_name != this_collection:
                        galaxy_deps = galaxy.setdefault("dependencies", {})
                        galaxy_deps[coll_name] = coll_ver
    # Existing changelogs
    orig_cl_file = "lsr_role2collection/COLLECTION_CHANGELOG.md"
    # Collection changelog file path
    coll_changelog_path = os.path.join(coll_dir, "docs", "CHANGELOG.md")
    if not args.no_update:
        if not any(versions_updated):
            logging.info(
                "Nothing in the collection was changed - current collection is up-to-date"
            )
            if not args.skip_changelog:
                shutil.copy(orig_cl_file, coll_changelog_path)
                # Convert CHANGELOG.md to CHANGELOG.rst
                if args.changelog_rst:
                    convert_md2rst(coll_dir)
            create_collection_extra_files(args, coll_dir, galaxy)
            return
        update_galaxy_version(args, galaxy, versions_updated)
        if not args.no_update:
            with open(args.collection_release_yml.name, "w") as crf:
                yaml.safe_dump(coll_rel, crf, sort_keys=False)

    # If to-be-appended changelogs are found, update COLLECTION_CHANGELOG.md
    # and copy it to the collection docs dir.
    if not args.skip_changelog:
        new_changelog_str = cl_manager.formatChangelogUpdate(
            galaxy["version"], datetime.now().date()
        )
        if new_changelog_str:
            changelog_str = open(orig_cl_file).read()
            changelog_str = changelog_str.replace(CHANGELOG_HEADER, new_changelog_str)
            open(orig_cl_file, "w").write(changelog_str)
            if args.save_current_changelog:
                # used for the release notes for the github release
                changelog_str = new_changelog_str.replace(CHANGELOG_HEADER, "")
                open("CURRENT_VER_CHANGELOG.md", "w").write(changelog_str)
            # Copy the new changelog to the docs dir in collection
            shutil.copy(orig_cl_file, coll_changelog_path)
        else:
            shutil.copy(orig_cl_file, coll_changelog_path)

        # Convert CHANGELOG.md to CHANGELOG.rst
        if args.changelog_rst:
            convert_md2rst(coll_dir)
    build_collection(args, coll_dir, galaxy)

def find(path, name):
    """Find a file 'name' in or under the directory 'path'."""
    for root, dirs, files in os.walk(path):
        if name in files:
            return os.path.join(root, name)

def process_rpm(args, default_galaxy, coll_rel):
    """
    Extract the contents of rpm.
    If the rpm contains galaxy.yml, use the file.
    Else if MANIFEST.json is found, use the info.
    Otherwise, it fails.
    Generate the collection artifact from the collection part
    of the extracted files.
    """
    workdir = tempfile.mkdtemp(suffix=".lsr", prefix="collection")
    cmdlist0 = ["rpm2cpio", args.rpm]
    p0 = subprocess.Popen(cmdlist0, cwd=workdir, stdout=subprocess.PIPE)
    cmdlist1 = ["cpio", "-id"]
    p1 = subprocess.Popen(cmdlist1, cwd=workdir, stdin=p0.stdout)
    p1.communicate()
    p0.communicate()
    logging.debug(f"{' '.join(cmdlist0)} returned {p0.returncode}")
    logging.debug(f"{' '.join(cmdlist1)} returned {p1.returncode}")
    if p0.returncode != 0:
        raise subprocess.CalledProcessError(
            p0.returncode, cmdlist0, p0.stdout, p0.stderr
        )
    if p1.returncode != 0:
        raise subprocess.CalledProcessError(
            p1.returncode, cmdlist1, p1.stdout, p1.stderr
        )

    tmp_coll = "{}/usr/share/ansible/collections/ansible_collections".format(workdir)
    if not os.path.exists(tmp_coll):
        raise Exception("Failed to extract {} from {}".format(tmp_coll, args.rpm))
    shutil.move(tmp_coll, workdir)
    shutil.rmtree("{}/usr".format(workdir))

    # If exists, use galaxy.yml in rpm
    galaxy_yml = find(workdir, "galaxy.yml")
    if galaxy_yml:
        args.galaxy_yml.close()
        args.galaxy_yml = open(galaxy_yml, "r")
        coll_dir = os.path.dirname(galaxy_yml)
        # override galaxy with the one in rpm
        galaxy = yaml.safe_load(args.galaxy_yml)
    # Otherwise, use the values in MANIFEST.json, if any.
    else:
        manifest_json = find(workdir, "MANIFEST.json")
        if manifest_json:
            with open(manifest_json) as mj:
                mj_contents = json.load(mj)
            galaxy = default_galaxy
            galaxy["namespace"] = mj_contents["collection_info"]["namespace"]
            galaxy["name"] = mj_contents["collection_info"]["name"]
            galaxy["version"] = mj_contents["collection_info"]["version"]
            galaxy["authors"] = mj_contents["collection_info"]["authors"]
            galaxy["description"] = mj_contents["collection_info"]["description"]
            coll_dir = os.path.dirname(manifest_json)
            new_galaxy_yml = "{}/galaxy.yml".format(coll_dir)
            args.galaxy_yml.close()
            args.galaxy_yml = open(new_galaxy_yml, "w")
            yaml.dump(galaxy, args.galaxy_yml)
            os.remove(manifest_json)
            files_json = "{}/FILES.json".format(coll_dir)
            if os.path.exists(files_json):
                os.remove(files_json)
        else:
            raise Exception("No galaxy.yml nor MANIFEST.json in {}".format(args.rpm))

    args.src_path = coll_dir
    build_collection(args, coll_dir)
    shutil.rmtree(workdir)

    return galaxy

def check_collection(args, galaxy):
    coll_file = (
        f"{args.dest_path}/{galaxy['namespace']}-{galaxy['name']}-"
        f"{galaxy['version']}.tar.gz"
    )
    gi_config = "lsr_role2collection/galaxy-importer.cfg"
    if os.path.exists(coll_file) and os.path.exists(gi_config):
        _ = run_cmd(
            [sys.executable, "-m", "galaxy_importer.main", coll_file],
            args.dest_path,
            {"GALAXY_IMPORTER_CONFIG": gi_config},
        )

def publish_collection(args, galaxy):
    coll_file = (
        f"{args.dest_path}/{galaxy['namespace']}-{galaxy['name']}-"
        f"{galaxy['version']}.tar.gz"
    )
    if os.path.exists(coll_file):
        cmd = ["ansible-galaxy", "collection", "publish"]
        if args.no_wait:
            cmd.append("--no-wait")
        else:
            cmd.append("-vv")
        cmd.append(coll_file)
        _ = run_cmd(cmd)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--galaxy-yml",
        type=open,
        default=os.environ.get("GALAXY_YML", "galaxy.yml"),
        help="Path/filename for galaxy.yml",
    )
    parser.add_argument(
        "--collection-release-yml",
        type=open,
        default=os.environ.get("COLLECTION_RELEASE_YML", "collection_release.yml"),
        help="Path/filename for collection_release.yml",
    )
    parser.add_argument(
        "--src-path",
        type=str,
        default=os.environ.get("COLLECTION_SRC_PATH"),
        help="Path to the directory containing the local clone of the role repos",
    )
    parser.add_argument(
        "--src-owner",
        type=str,
        default=os.environ.get("COLLECTION_SRC_OWNER", DEFAULT_GIT_ORG),
        help="Name of the role owner, in legacy role format OWNER.ROLENAME",
    )
    parser.add_argument(
        "--dest-path",
        type=str,
        default=os.environ.get(
            "COLLECTION_DEST_PATH", os.environ.get("HOME") + "/.ansible/collections"
        ),
        help=(
            "Path to collection base directory; collection directory structure "
            "will be created in DIR/ansible_collection/NAMESPACE/COLLECTION_NAME; "
            "collection package file will be created in DIR; "
            "default to ${HOME}/.ansible/collections"
        ),
    )
    parser.add_argument(
        "--force",
        default=False,
        action="store_true",
        help="Remove collection destination dir and file before creating",
    )
    parser.add_argument(
        "--keep",
        default=False,
        action="store_true",
        help="Keep collection destination dir and file before creating",
    )
    parser.add_argument(
        "--no-auto-version",
        default=False,
        action="store_true",
        help=(
            "The script will try to automatically determine the new collection version based "
            "on the semantic version changes in the versions of the tags in the "
            "role repos.  If you do not want this, add this parameter."
        ),
    )
    parser.add_argument(
        "--new-version",
        default=None,
        type=str,
        help="The new semantic version to use for the collection.",
    )
    parser.add_argument(
        "--no-update",
        default=False,
        action="store_true",
        help=(
            "By default, this script will update collection-release-yml "
            "to the latest tags or commit hashes in the upstream role.  Set "
            "this flag if you want to manually update collection-release-yml "
            "and build the collection from that file."
        ),
    )
    parser.add_argument(
        "--publish",
        default=False,
        action="store_true",
        help="Publish the collection to Galaxy.",
    )
    parser.add_argument(
        "--no-wait",
        default=False,
        action="store_true",
        help="Do not wait for the collection to be published.  Default is to wait.",
    )
    parser.add_argument(
        "--use-commit-hash",
        default=False,
        action="store_true",
        help=(
            "By default, only version tags are used for refs - if True, use latest HEAD"
            " commit hash if not tagged"
        ),
    )
    parser.add_argument(
        "--debug",
        default=False,
        action="store_true",
        help="Turn on debug logging.",
    )
    parser.add_argument(
        "--exclude",
        default=[],
        action="append",
        help="Roles to exclude",
    )
    parser.add_argument(
        "--include",
        default=[],
        action="append",
        help="Roles to include",
    )
    parser.add_argument(
        "--rpm",
        default=None,
        type=str,
        help="Path to the rpm file for the input.",
    )
    parser.add_argument(
        "--skip-git",
        default=False,
        action="store_true",
        help="True when work with local src.",
    )
    parser.add_argument(
        "--skip-check",
        default=False,
        action="store_true",
        help="True when skip check with galaxy-importer.",
    )
    parser.add_argument(
        "--use-commit-hash-role",
        default=[],
        action="append",
        help=(
            "Use the latest commit hash instead of the tag for these roles."
            "  Use this option when you want to use the tag for every role "
            "except the named roles e.g. --use-commit-hash-role sshd "
            "--use-commit-hash-role network will use the tag for every role"
            " except sshd and network, which will use the latest commit hash."
            "  Using --use-commit-hash is the same as using --use-commit-hash-role"
            " and specifying every role."
        ),
    )
    parser.add_argument(
        "--ee-base-image",
        default=os.environ.get(
            "EE_BASE_IMAGE", "quay.io/ansible/ansible-runner:latest"
        ),
        help="Base image for Ansible Execution Environment",
    )
    parser.add_argument(
        "--skip-changelog",
        default=False,
        action="store_true",
        help="If true, do not generate changelog or convert to rst.",
    )
    parser.add_argument(
        "--extra-mapping",
        type=str,
        default="",
        help=(
            "This is a comma delimited list of extra mappings to apply when "
            "converting roles to collection - this replaces the given role "
            "name with collection format with the optional given namespace "
            "and collection as well as the given FQCN with other FQCN."
        ),
    )
    parser.add_argument(
        "--changelog-rst",
        default=False,
        action="store_true",
        help="If true, generate CHANGELOG.rst in collection root directory.",
    )
    parser.add_argument(
        "--save-current-changelog",
        default=False,
        action="store_true",
        help="If true, save the changelog for the current collection version as CURRENT_VER_CHANGELOG.md",
    )
    args = parser.parse_args()

    if args.debug:
        logging.getLogger().setLevel(logging.DEBUG)
    else:
        logging.getLogger().setLevel(logging.INFO)

    galaxy = yaml.safe_load(args.galaxy_yml)
    args.galaxy_yml.close()
    coll_rel = yaml.safe_load(args.collection_release_yml)
    args.collection_release_yml.close()
    if not args.include:
        args.include = sorted(list(coll_rel.keys()))
    for item in args.exclude:
        del args.include[item]

    # --rpm rpm_file is given.
    if args.rpm:
        # Check rpm_file exists.
        if not os.path.exists(args.rpm):
            logging.error("{}: No such file given with --rpm".format(args.rpm))
            return
        # If rpm_file is relative path, convert it to absolute path.
        if not os.path.isabs(args.rpm):
            rpm_dir = os.path.abspath(os.getcwd())
            args.rpm = "{0}/{1}".format(rpm_dir, args.rpm)

    workdir = None
    if not args.rpm and not args.src_path:
        workdir = tempfile.mkdtemp(suffix=".lsr", prefix="collection")
        args.src_path = workdir
    try:
        if args.rpm:
            galaxy = process_rpm(args, galaxy, coll_rel)
        else:
            update_collection(args, galaxy, coll_rel)
        if not args.skip_check:
            check_collection(args, galaxy)
        else:
            logging.debug("check_collection is skipped.")
        if args.publish:
            publish_collection(args, galaxy)
        logging.info("Done.")
    finally:
        if workdir:
            shutil.rmtree(workdir)

if __name__ == "__main__":
    sys.exit(main())


import requests
from bs4 import BeautifulSoup
import json

url = "https://www.ptt.cc/bbs/Gossiping/index.html"
#只有首次點選才會post cookie
form_data={
    'from': '/bbs/Gossiping/index.html',
    'yes' : 'yes'
}
#session->在此程式中持續記住header的東東
#基本參數
r = requests.session()
#詢問是否18頁網址 點選'是'後會post一個from_data 把這個data存到session裡
response = r.post("https://www.ptt.cc/ask/over18", data=form_data)
response = r.get(url)
soup = BeautifulSoup(response.text,"html.parser")
original_page = soup
#article_num = 1
all_dict=[]
for i in range(2):
    #開始找標題
    links = soup.find_all("div",{"class":"title"})
    for link in links:
        if link.a != None:
            #檢查發現抓下來的連結會少前面的部分 所以補上
            page_url = "https://www.ptt.cc"+link.a["href"]
            #進入頁面
            response = r.get(page_url)
            soup = BeautifulSoup(response.text,"html.parser")

            #抓作者、標題與時間

            datas = soup.find_all("div", {"class":"article-metaline"})
            print("totaldatas:{}".format(datas))
            if datas == None:
                break
            author = datas[0].find("span", {"class":"article-meta-value"}).string
            title = datas[1].find("span", {"class":"article-meta-value"}).string
            time = datas[2].find("span", {"class":"article-meta-value"}).string
            #內文
            main_content = soup.find("div", {"id":"main-content"})
            all_text = main_content.text
            #因為內文最後都有一個-- 以--為分割條件
            pretext = all_text.split("--")[:-1]
            pre_all = "--".join(pretext)
            #不要第一行作者等資訊
            backtext = pre_all.split("\n")[1:]
            finaltext = "\n".join(backtext)
            #後續發文
            messages = soup.find_all("div", {"class":"push"})
            #comment_num = 1

            push=[]
            normal=[]
            ssh=[]
            for message in messages:
                user_tag = message.find("span", {"class":"push-tag"}).string
                user_id = message.find("span", {"class":"push-userid"}).string
                user_content = message.find("span", {"class":"push-content"}).string
                user_time = message.find("span", {"class":"push-ipdatetime"}).string
                # print(user_tag)
                # print(user_id)
                # print(user_content)
                # print(user_time)

                #在字典內新增 一則留言一個dict
                dict1={"user_id" : user_id, "user_content" : user_content, "user_time" : user_time}
                # comment_title = "comment "+str(comment_num)
                if user_tag == "推 ":
                    push.append(dict1)
                elif user_tag == "→ ":
                    normal.append(dict1)
                elif user_tag == "噓 ":
                    ssh.append(dict1)
                #comment_num = comment_num+1
            print("Author: "+author)
            print("Title: "+title)
            print("Time: "+time)
            print(finaltext)
            print("")
            print("推 :")
            for i in push:
                print(i)
            print("→ :")
            for i in normal:
                print(i)
            print("噓 :")
            for i in ssh:
                print(i)

            #全存在一個dic內
            # article_title = "article "+str(article_num)
            comment_dict = {"push":push, "→":normal, "ssh":ssh}
            end_dict = {"author":author, "title":title, "time":time, "main-content":finaltext, "comments":comment_dict}
            all_dict.append(end_dict)
            # clear會出問題!!? 因為是有連接的?
            # push.clear()
            # normal.clear()
            # ssh.clear()
            # comment_dict.clear()
            # end_dict.clear()
            #article_num = article_num+1
    #換頁
    next_page = original_page.find_all("a", {"class":"btn wide"})
    #print(next_page)
    next_utl = "https://www.ptt.cc"+next_page[1]["href"]
    response = r.get(next_utl)
    soup = BeautifulSoup(response.text,"html.parser")
    original_page = soup
#最終dict
print(all_dict)
with open('data.json', 'w' , encoding='utf-8') as f:
    json.dump(all_dict, f)


#!./venv/bin/python
import time
import numpy as np
# import matplotlib.pyplot as plt
import asyncio
from time import *
from serial import Serial
from random import randrange
from pyramid import *
from dodecahedron import *
from enum import IntEnum
from time import time
from math import sqrt
import random
from head import *
from joint import *
# from simonoAnimacijos import *

# from audio import AudioFile, AudioMic, smooth_fft_values
# from control_socket import ControlSocket

# from examples import argb_strip_example, argb_fft_example, argb_fft_pygame_example

# soc = ControlSocket("My awesome installation", "http://localhost:8080")

s = Serial('/dev/ttyUSB0', 460800)
# s = Serial('COM7', 460800)
s.timeout = 0.01

head_init(s)
edges_init(allPixels)
# jointai.append(SegmentJoint([allEdges[0], allEdges[1]]))

head_draw_all(s, 0, 0, 0)
head_clear_all(s)
update_all_argb(s)
sleep(0.5)

# song = AudioFile('sound2.wav')
# song.play()
randomAnimationBack = 0
randomAnimation = 0
while True:
    r = random.randint(0, 255)
    g = random.randint(0, 255)
    b = random.randint(0, 255)

    while (randomAnimation == randomAnimationBack):
        randomAnimation = random.randint(0, 5)
    randomAnimationBack = randomAnimation

    if randomAnimation == 0:
        blinks = random.randint(1, 5)
        blink_EYEMou(s, blinks, r, g, b)
        sleep(0.1)
    elif randomAnimation == 1:
        head_draw_all(s, r, g, b)
        update_all_argb(s)
        sleep(0.1)
    elif randomAnimation == 2:
        duration = random.random()
        draw_all_exc_EyeMou(s, duration, r, g, b)
        sleep(0.1)
    elif randomAnimation == 3:
        rr = random.randint(0, 6)
        if rr == 0:
            FlagsUkraine(s)
            sleep(0.1)
        elif rr == 1:
            FlagsLTU(s)
            sleep(0.1)
    elif randomAnimation == 4:
        gen_rand_stripes(s, 5, r, g, b)
        sleep(0.1)
        # SongAnimations(s, song)


from sqlalchemy import create_engine, exc
from sqlalchemy.orm import sessionmaker

from database import DBUpdate, DBBase, Game, User, GameKill, Item, GameMod, TeamStats, \
        GameMap, GameType, EndCause, Score, Model, HModel, Say, Tell, SayTeam, \
        Pk3Files, GameParameter, Team, Config

class DBHandler(object):

    engine = None
    game_id = None
    user_dict = {}
    team_dict = {}
    team_leader_dict = {}
    user_team_dict = {}
    item_dict = {}
    game_param_dict = [
        'timelimit',
        'fraglimit',
        'capturelimit',
        'bot_minplayer'
    ]

    const_blue = 2
    const_red = 1

    def __init__(self):
        pass

    def renew_db_connection(self):
        if self.engine:
            # maybe a better soluton exists for this?
            try:
                self.create_session()
                if self.game_id:
                    self.game_now = self.session.query(Game).filter(Game.id==self.game_id).first()
                    self.game_id = self.game_now.id
                return 0
            except exc.SQLAlchemyError as ex:
                log.error("SQLAlchemyError: %s" % ex)
                return 1
        else:
            return 0

    def get_config(self, key):
        config = self.session.query(Config).filter(key=key).first()
        if not config:
            return ""
        return config.value

    def set_config(self, key, value):
        config = Config(parameter=key,
                    value=value
        )
        self.session.add(config)

    def get_db_version(self):
        db_version = self.get_config('database_version')

        if not db_version:
            db_version = 1

        return db_version

    def set_db_version(version):
        set_config('database_version', version)

    def db_update(self):
        db = DBUpdate()
        version = self.get_db_version

        while version < db.max_version:
            new_version = version + 1

            if hasattr(db, version + '_to_' + new_version):
                getattr(db, version + '_to_' + new_version)
            else:
                log.error("missing update Rule from Databaseversion %d to Version %d" % (version, new_version))
                return

            version = self.get_db_version

    def get_team_id(self, teamname):
        #if key not in dictionary,
        #try to load from database,
        #otherwise write in database,
        #now load into dictionary for future use in game
        if teamname not in self.team_dict:
            if not self.session.query(Team).filter(Team.name==teamname).count():
                new_team = Team(name=teamname)
                self.session.add(new_team)
                self.session.commit()
            team = self.session.query(Team).filter(Team.name==teamname).first()
            self.team_dict[teamname] = team.id

        return self.team_dict[teamname]

    def get_user_id(self, username):
        #if key not in dictionary,
        #try to load from database,
        #otherwise write in database,
        #now load into dictionary for future use in game
        if username not in self.user_dict:
            if not self.session.query(User).filter(User.username==username).count():
                new_user = User(username=username)
                self.session.add(new_user)
                self.session.commit()
            user = self.session.query(User).filter(User.username==username).first()
            self.user_dict[username] = user.id

        return self.user_dict[username]

    def get_item_id(self, item, weapon=False):
        #if key not in dictionary,
        #try to load from database,
        #otherwise write in database,
        #now load into dictionary for future use in game
        if item not in self.item_dict:
            if not self.session.query(Item).filter(Item.item==item).count():
                new_item = Item(item=item, weapon=weapon)
                self.session.add(new_item)
                self.session.commit()
            dict_item = self.session.query(Item).filter(Item.item==item).first()
            self.item_dict[item] = [dict_item.id, dict_item.weapon]

        # if killed once with this item an enemy, mark as weapon
        if weapon and self.item_dict[item][1] != weapon:
            item_type_change = self.session.query(Item).filter(Item.item==item).first()
            item_type_change.weapon = weapon
            del self.item_dict[item]
            self.item_dict[item] = [dict_item.id, dict_item.weapon]

        return self.item_dict[item][0]

    def set_team_leader(self, teamid, username):
        self.team_leader_dict[teamid] = username

    def get_team_leader(self, teamid):
        if teamid in self.team_leader_dict:
            return self.team_leader_dict[teamid]
        return ''

    def set_user_teamid(self, username, teamid):
        self.user_team_dict[username] = teamid

    def get_user_teamid(self, username):
        if username not in self.user_team_dict:
            return ''
        return self.user_team_dict[username]

    def connect(self, db, debug=False):
        try:
            self.engine = create_engine(db, echo=debug)
            return True
        except ImportError as ex:
            log.error(ex.message + "\n -> maybe missed pip installation of database game_modul like 'pymysql'?\n maybe need the Package like 'python-mysqldb' from your Distribution also?")
            self.db_help()
            return False

    def db_create_models(self):
        DBBase.metadata.create_all(self.engine)

    def set_engine(self, engine):
        self.engine = engine

    def get_engine(self):
        return self.engine

    def create_session(self):
        session = sessionmaker()
        session.configure(bind=self.engine)
        self.session = session()
        self.db_update()

    def commit_session(self):
        self.session.commit()

    def close_session(self):
        try:
            self.session.close()
        except:
            pass

    def db_help(self):
        print ('sqlite:////absolute/path/to/sqlite.db')
        print ('mysql://username:password@hostname/mydatabase')
        print ('postgresql://username:password@localhost/mydatabase')
        print ('mssql+pymssql://username:password@hostname:port/dbname')
        print ('oracle://username:password@hostname:1521/sidname')

    def init_game(self, data):
        #{'protocol': '68', 'sv_privateClients': '0', 'sv_maxRate': '0', 'bot_minplayers': '4', 'g_maxGameClients': '0', 'version': 'Q3 1.32c linux-i386 May  8 2006', 'game_mod_name': 'baseq3', 'sv_minPing': '0', '.Admin': 'Mr.T', 'timestamp': '1402165340', 'g_needpass': '1', 'sv_punkbuster': '0', 'sv_allowDownload': '1', 'sv_maxclients': '8', 'sv_hostname': 'Wotans Daddelbude', 'fraglimit': '20', 'capturelimit': '6', 'dmflags': '0', 'game_type': '3', 'timelimit': '10', 'time': '2059:48', 'map_name': 'japandm', 'sv_maxPing': '0', 'runtime': 0, 'sv_floodProtect': '1'}

        if not self.session.query(GameMod).filter(GameMod.name==data['mod_name']).count():
            new_game_mod = GameMod(name=data['mod_name'])
            self.session.add(new_game_mod)
            self.session.commit()

        if not self.session.query(GameMap).filter(GameMap.name==data['map_name']).count():
            new_map = GameMap(name=data['map_name'])
            self.session.add(new_map)
            self.session.commit()

        if not self.session.query(GameType).filter(GameType.type==data['game_type']).count():
            new_game_type = GameType(type=data['game_type'])
            self.session.add(new_game_type)
            self.session.commit()

        game_mod = self.session.query(GameMod).filter(GameMod.name==data['mod_name']).first()
        game_map = self.session.query(GameMap).filter(GameMap.name==data['map_name']).first()
        game_type = self.session.query(GameType).filter(GameType.type==data['game_type']).first()

        game = Game(starttime=data['timestamp'],
                            game_mod_id=game_mod.id,
                            game_map_id=game_map.id,
                            game_type_id=game_type.id,
        )
        self.session.add(game)
        self.session.commit()

        #remember for actual game
        self.game_now = self.session.query(Game).filter(Game.starttime==data['timestamp']).first()
        self.game_id = self.game_now.id

        # save additional game parameters
        for key in data.iterkeys():
            if key in self.game_param_dict:
                game_param = GameParameter(game_id=self.game_now.id,
                                        game_key=key,
                                        game_value=data[key],
                )
                self.session.add(game_param)

    def exit(self, data):
        # {'endcause': 'Fraglimit', 'timestamp': 1402184024, 'runtime': 520, 'time': '2055:50'}
        if not self.session.query(EndCause).filter(EndCause.reason==data['endcause']).count():
            new_end_reason = EndCause(reason=data['endcause'])
            self.session.add(new_end_reason)
            self.session.commit()

        reason = self.session.query(EndCause).filter(EndCause.reason==data['endcause']).first()
        self.game_now.endtime = data['timestamp']
        self.game_now.end_cause_id = reason.id

    def shutdown_game(self, data):
        self.close_session()

    def score(self, data):
        # {'username': 'Xaero', 'timestamp': 1402403195, 'ping': '0', 'score': '3', 'time': '2055:50', 'runtime': 520, 'id': '3'}
        team_id = self.get_user_teamid(data['username'])

        if self.get_team_leader(team_id) == data['username']:
            team_leader=True
        else:
            team_leader=False

        user_id = self.get_user_id(data['username'])
        user_score = Score(user_id=user_id,
                            game_id=self.game_now.id,
                            count=data['score'],
                            team_id=team_id,
                            team_leader=team_leader,
        )
        self.session.add(user_score)

    def red(self, data):
        #{'blue_score': '19', 'red_score': '20', 'runtime': 297, 'timestamp': 1402180740, 'time': '1089:59'}

        blue_team_id = self.get_team_id(self.const_blue)
        red_team_id = self.get_team_id(self.const_red)

        team_blue = TeamStats(team_id=blue_team_id,
                            score=data['blue_score'],
                            game_id=self.game_now.id,
        )
        team_red = TeamStats(team_id=red_team_id,
                            score=data['red_score'],
                            game_id=self.game_now.id,
        )
        self.session.add(team_blue)
        self.session.add(team_red)

    def blue(self, data):
        self.red(data)

    def client_connect(self, data):
        pass

    def client_userinfo_changed(self, data):
        #{'hmodel': 'crash', 'skill': '    3.00', 'timestamp': 1402689747, 'team_task': '0', 'looses': '0', 'health_count': '90', 'n': 'Crash', 'team_leader': '0', 'wins': '0', 'time': '14856:4', 'color2': '5', 'model': 'crash', 'runtime': -36, 'color1': '4', 'id': '0', 'team': '2'}

        if not self.session.query(Model).filter(Model.name==data['model']).count():
            new_model = Model(name=data['model'])
            self.session.add(new_model)
            self.session.commit()
        if not self.session.query(HModel).filter(HModel.name==data['hmodel']).count():
            new_model = HModel(name=data['hmodel'])
            self.session.add(new_model)
            self.session.commit()
        model = self.session.query(Model).filter(Model.name==data['model']).first()
        hmodel = self.session.query(HModel).filter(HModel.name==data['hmodel']).first()

        user_id = self.get_user_id(data['n'])
        user = self.session.query(User).filter(User.id==user_id).first()

        if 'skill' in data:
            user.bot = True
            skill_value, sub_value = data['skill'].strip(' \t').split('.')
            self.game_now.bot_skill = int(skill_value)
        else:
            user.bot = False

        user.model_id = model.id
        user.hmodel_id = hmodel.id
        team_id = self.get_team_id(data['team'])
        self.set_user_teamid(data['n'], team_id)

        if data['team_leader']:
            self.set_team_leader(team_id, data['n'])

    def client_begin(self, data):
        pass

    def client_disconnect(self, data):
        pass

    def item(self, data):
        # {'item': 'item_armor_shard', 'runtime': 13, 'id': '0', 'timestamp': 1403357083, 'time': '0:13'}
        item_id = self.get_item_id(data['item'])

    def kill(self, data):
        #{'timestamp': 1402174290, 'weapon': 'MOD_PLASMA', 'killer': 'Visor', 'id_2': '8', 'id_1': '1', 'time': '4965:24', 'runtime': 406, 'killee': 'Major', 'id': '4'}

        killer_id = self.get_user_id(data['killer'])
        killee_id = self.get_user_id(data['killee'])
        weapon_id = self.get_item_id(data['weapon'], weapon=True)

        kill_action = GameKill(time=data['runtime'],
                            killer_id=killer_id,
                            killee_id=killee_id,
                            weapon_id=weapon_id,
                            game_id=self.game_now.id
        )
        self.session.add(kill_action)

    def say(self, data):
        #{'timestamp': 1403358308, 'message': 'team blue', 'runtime': 7061, 'user': 'Eye', 'time': '1085:01'}
        talker_id = self.get_user_id(data['user'])

        say_action = Say(time=data['runtime'],
                            talker_id=talker_id,
                            game_id=self.game_now.id,
                            message=data['message']
        )
        self.session.add(say_action)

    def tell(self, data):
        #{'timestamp': 1403351164, 'listener': 'Uriel', 'talker': 'Razor', 'time': '969:59', 'message': "it's agreed. I'll follow you around uriel.", 'runtime': 159}
        talker_id = self.get_user_id(data['talker'])
        listener_id = self.get_user_id(data['listener'])

        tell_action = Tell(time=data['runtime'],
                            talker_id=talker_id,
                            game_id=self.game_now.id,
                            listener_id=listener_id,
                            message=data['message']
        )
        self.session.add(tell_action)

    def sayteam(self, data):
        #{'timestamp': 1403351037, 'message': 'Who is the team leader', 'runtime': 35, 'user': 'Keel', 'time': '963:35'}

        sayteam_action = SayTeam(time=data['runtime'],
                            talker_id=self.get_user_id(data['user']),
                            game_id=self.game_now.id,
                            team_id=self.get_user_teamid(data['user']),
                            message=data['message']
        )
        self.session.add(sayteam_action)

    def empty(self, data):
        pass


# Easter eggs

# Броят на боядисаните яйца – цяло число в интервала [1 ... 100]
# За всяко яйце се чете:
# Цветът на яйцето – текст с възможности: "red", "orange", "blue", "green"

eggs_amount = int(input())

red = 0
orange = 0
blue = 0
green = 0

x = 0
while x < eggs_amount:
    x += 1
    egg_color = input()

    if egg_color == "red":
        red += 1
    if egg_color == "orange":
        orange += 1
    if egg_color == "blue":
        blue += 1
    if egg_color == "green":
        green += 1

max_eggs = 0
max_eggs_color = ""

if red > orange and  red > blue and red > green:
    max_eggs = red
    max_eggs_color = "red"
elif orange > red and orange > blue and orange > green:
    max_eggs = orange
    max_eggs_color = "orange"
elif blue > red and blue > orange and blue > green:
    max_eggs = blue
    max_eggs_color = "blue"
else:
    max_eggs = green
    max_eggs_color = "green"

print(f"Red eggs: {red}")
print(f"Orange eggs: {orange}")
print(f"Blue eggs: {blue}")
print(f"Green eggs: {green}")
print(f"Max eggs: {max_eggs} -> {max_eggs_color}")


#!/usr/bin/env python
# coding: utf-8

#
Taller #2: Cálculo del número de generadores del Grupo Cíclico de G

#
 Presentado por: David Santiago Cruz Hernandez

#
 Asignatura: Matemáticas Discretas II

# In[ ]:

import time
numGeneradoresGCiclico = 0
dicDescomposicionPrimosGrupo = {}

# In[ ]:

def bold(texto):
    return "\033[1m" + texto + "\033[0m"

# In[ ]:

def imprimirSuperIndice(indice):
    if indice == 1:
        return str(chr(0x00b9))
    elif 2 <= indice <= 3:
        return str(chr(0x00b0 + indice))
    else:
        return str(chr(0x2070 + indice))

# In[ ]:

n = int(input("Ingrese el Orden de G:"))

# In[ ]:

Grupo = []
for num in range(1,n+1):
    Grupo.append(num)

# ### Metodo Alternativo:
# ##### Comprobación por medio del siguiente *Lema*:

# ###### • **i)** φ(I) = 1
# ###### • **ii)** Si *p* es **primo**, entonces el **φ($p^{a}$) =** $p^{a}$ - $p^{a-1}$
# ###### • **iii)** Si *mcd(m,n)* = 1, entonces el **φ(m•n) =** φ(m) + φ(n)

# In[ ]:

def calcularDescomposicionPrimos(resultadoDivision,dicDescomposicionPrimos):
    potencia = 1
    for divisor in range(2,len(Grupo)):
        while resultadoDivision%divisor==0:
            resultadoDivision = resultadoDivision/divisor
            if divisor not in dicDescomposicionPrimos:
                potencia = 1
            dicDescomposicionPrimos[divisor] = potencia
            potencia+=1
        if resultadoDivision ==1:
            break
    return dicDescomposicionPrimos

# In[ ]:

def LemaTotientEulerPrimos():
    resultado = 1
    for primo in dicDescomposicionPrimosGrupo:
        indice = dicDescomposicionPrimosGrupo.get(primo)
        resultado = resultado * (pow(primo,indice) - pow(primo,indice-1))
    return resultado

# In[ ]:

inicio2 = time.time()
dicDescomposicionPrimosGrupo = calcularDescomposicionPrimos(len(Grupo), {})
numGeneradoresGCiclico = LemaTotientEulerPrimos()

fin2 = time.time()
Tiempo2 = round(fin2-inicio2,4)

# In[ ]:

def imprimirCadenaDescomposicion():
    strDescomposicion = ""
    for base in dicDescomposicionPrimosGrupo:
        strIndice = imprimirSuperIndice(dicDescomposicionPrimosGrupo[base])
        strDescomposicion = strDescomposicion + str(base) + strIndice + "•"
    return strDescomposicion[0:-1]

# In[ ]:

def imprimirCadenaDescomposicionTotient():
    strLemaIII = ""
    for potencia in imprimirCadenaDescomposicion().split("•"):
        strLemaIII = strLemaIII + "φ(" + potencia + ")•"

    strLemaII = ""
    for base in dicDescomposicionPrimosGrupo:
        strLemaII = strLemaII + "(" + str(base) + imprimirSuperIndice(dicDescomposicionPrimosGrupo[base]) + "-" + str(base) + imprimirSuperIndice(dicDescomposicionPrimosGrupo[base] - 1) + ")•"
    return strLemaIII[0:-1] + " = " + strLemaII[0:-1]

# In[ ]:

print("                    " + bold("Orden del Grupo G: " + str(n)) + "\n")
print("Tiempo de ejecución del Metodo Alternativo: " + str(Tiempo2) + " segundos.")
print("→ Descomposición de Factores Primos de " + bold("n") + ": " + imprimirCadenaDescomposicion())
print(bold("→ Número de Generadores Cíclicos de G:"))
print("      φ(" + str(n) + ") = φ(" + imprimirCadenaDescomposicion() + ") = " + imprimirCadenaDescomposicionTotient() + " = " + str(numGeneradoresGCiclico) )
print("→ Por este Metodo, no se obtiene la lista de los Generadores Cíclicos de G.")

# ### Metodo Tradicional:
# ##### Comprobar si el subgrupo generado por cada elemento es el Grupo Cíclico de G.

# In[ ]:

def calcularMCDconGrupo(numGenerador):
    listaPrimosComunes = []
    mcd = 1
    descPrimosNumGenerador = calcularDescomposicionPrimos(numGenerador, {})
    menorRepeticion = lambda x, y: x if x <= y else y
    for primo in descPrimosNumGenerador:
        if primo in dicDescomposicionPrimosGrupo:
            listaPrimosComunes.append(
                pow(primo, menorRepeticion(descPrimosNumGenerador[primo], dicDescomposicionPrimosGrupo[primo])) )
    for primo in listaPrimosComunes:
        mcd = mcd * primo
    return mcd

# In[ ]:

###### Se calcula cada subgrupo generado por cada elemento (y se almacena en un Set). Luego, se comprueba su tamaño:

# In[ ]:

def generador(numGenerador):
    SubGrupo = set()
    strIndice = ""
    for indice in Grupo:
        SubGrupo.add(indice * numGenerador % n)
        if indice * numGenerador % n == 0:
            break
    for digit in str(numGenerador):
        strIndice = strIndice + imprimirSuperIndice(int(digit))
    if len(SubGrupo) == len(Grupo):
        print(" = " + bold("--- Grupo Cíclico de G ---"))
        return True
    else:
        mcd = calcularMCDconGrupo(numGenerador)
        if mcd == numGenerador or mcd == 1:
            print(" = ", end="")
            print(SubGrupo)
        else:
            print(" = " + "Subgrupo Cíclico generado por " + str(mcd))

        return False

# In[ ]:

GeneradoresGCiclico = []
numGeneradoresGCiclico=0
inicio1 = time.time()

for num in Grupo:
    if generador(num):
        numGeneradoresGCiclico+=1
        GeneradoresGCiclico.append(num)

fin1 = time.time()
Tiempo1 = round(fin1-inicio1,2)

# In[ ]:

print("                                   " + bold("Orden del Grupo G: " + str(n)) + "\n")
print(bold("→ Generadores Cíclicos de G:"))
print(bold("|") + " { " + ', '.join(str(x) for x in GeneradoresGCiclico) + " } " + bold("|" + " = " + str(numGeneradoresGCiclico))+ "\n")

print("Tiempo de ejecución del " + bold("Metodo Tradicional") + ": " + str(Tiempo1) + " segundos.")
print(bold("→ Número de Generadores Cíclicos de G: --- φ(" + str(n) + ") = " + str(numGeneradoresGCiclico) + " ---"))

# In[ ]:

##### Muchas Gracias. Desarrollado por David Santiago Cruz Hernandez. Correo: dcruzhe@unal.edu.co


from datetime import datetime
import math
import numpy as np
import random as rd
import sys

def anchor(year):
    century = math.floor(year/100)
    century_anchor = (5*(century%4))%7+2
    decade = year-century*100
    if decade%2 != 0: decade += 11
    decade /= 2
    if decade%2 != 0: decade += 11
    decade_anchor = 7-(decade%7)
    return int((decade_anchor+century_anchor)%7)

def is_leap(year):
    if year%4 != 0:
        return False
    if year%100 == 0 and year%400 != 0:
        return False
    return True

def rd_date(min_year, max_year):
    rd_year = rd.randint(min_year, max_year)
    rd_month = rd.randint(1,12)
    offset = 0
    if is_leap(rd_year):
        offset = 1
    if rd_month in [1, 3, 5, 7, 8, 10, 12]:
        rd_day = rd.randint(1,31)
    if rd_month in [4, 6, 9, 11]:
        rd_day = rd.randint(1,30)
    if rd_month == 2:
        rd_day = rd.randint(1, 28+offset)
    return [rd_day, rd_month, rd_year]

def print_date(date):
    print(str(date[0])+"."+str(date[1])+"."+str(date[2]))

def weekday(date):
    date = datetime(date[2], date[1], date[0])
    return date.weekday()

def get_seconds(time):
    time_digits = list(str(time))
    return int(time_digits[0])*60*60+int(time_digits[2])*10*60+int(time_digits[3])*60+int(time_digits[5])*10+int(time_digits[6])+int(time_digits[8])*0.1+int(time_digits[9])*0.01

def ask_date(max_attempts, min_year, max_year):
    weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    times = []
    print("Push ENTER to start the timer!")
    inp = input()
    if len(inp) == 0:
        start_global = datetime.now()
        count = 0
        global_count = 0
        for i in range(max_attempts):
            date = rd_date(min_year, max_year)
            start = datetime.now()
            print_date(date)
            inp = input()
            try:
                test_int = int(inp)
            except:
                print("Attempt abortet! The correct weekday would have been:", weekdays[weekday(date)]+".", "Anchor:", str(anchor(date[2]))+".")
                break
            if int(inp) > 7 or int(inp) < 0:
                print("Attempt abortet! The correct weekday would have been:", weekdays[weekday(date)]+".", "Anchor:", str(anchor(date[2]))+".")
                break
            if int(inp)%7 == (weekday(date)+1)%7:
                stop = datetime.now()
                count += 1
                global_count += 1
                times.append(round(get_seconds(stop-start),2))
                print("Correct! It is a", weekdays[weekday(date)]+".", "You took:", round(get_seconds(stop-start),2), "seconds.", "Anchor:", str(anchor(date[2]))+".\n")
            else:
                stop = datetime.now()
                global_count += 1
                times.append("DNF")
                print("Wrong! The correct weekday is:", weekdays[weekday(date)]+".", "You took:", round(get_seconds(stop-start),2), "seconds.", "Anchor:", str(anchor(date[2]))+".\n")
        stop_global = datetime.now()
        print("\n")
        print("You got", count, "of", global_count, "correct in", stop_global-start_global)
        if count > 1:
            print("Ratio:", round(get_seconds(stop_global-start_global)/count, 2), "Seconds/Date")
            print("Average of", len(times), "is:", average(times))
    else: return print("Attempt aborted!")

def give_weekday(day, month, year):
    weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    date = datetime(year, month, day)
    print("The", str(day)+"."+str(month)+"."+str(year), "is a", str(weekdays[date.weekday()])+".")

def count_list(lst, x):
    count = 0
    for ele in lst:
        if ele == x:
            count += 1
    return count

def average(values):
    length = len(values)
    number = math.ceil(0.05*length)
    num_dnf = count_list(values, "DNF")
    if num_dnf > number:
        return "DNF"
    if len(values) <= 2:
        return "To short for average."
    num_bad = number - num_dnf
    for i in range(num_dnf):
        values.remove("DNF")
    values.sort()
    for i in range(num_bad):
        values.pop(-1)
    for i in range(number):
        values.pop(0)
    return round(sum(values)/len(values),2)

def help():
    print("""Arguments must be passed in the order as shown:    [attempts] [min_year] [max_year]
             - [attempts] is the number of attempts (default=5).
             - [min_year] is the lower bound of the year interval (default=1600).
             - [max_year] is the upper bound of the year interval (default=2300).""")

if(len(sys.argv) == 4 and sys.argv[1].isdigit() and sys.argv[2].isdigit() and sys.argv[3].isdigit()):
    ask_date(int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]))
else:
    help()


from tuning import Tuning
import usb.core
import usb.util
import time
import sounddevice as sd
import numpy as np
import socket

# now support python 3
# adding networking

#V0.1

"""
requires

sudo apt-get install libportaudio2
sounddevice
numpy
pyusb
"""

#config
addr = ("192.168.1.67", 12000)
average_val = .8

#main

client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

dev = usb.core.find(idVendor=0x2886, idProduct=0x0018)
doa_buff = []
val_buff = []
count = 0

# print dev

with sd.InputStream(device=2, channels=6) as stream:
    Mic_tuning = Tuning(dev)
    start = time.time()
    for i in range(10):
        try:
            doa_buff = []
            val_buff = []
            for i in range(20):
                val_buff.append(np.average(np.absolute(stream.read(160)[0][:, 0]) * 10))
                doa_buff.append(Mic_tuning.direction)

            if average_val < np.mean(np.array(val_buff)):
                doa_avg = np.mean(np.array(doa_buff))
                #doa_avg = (180 - doa_avg) % 380
                val_avg = np.mean(np.array(val_buff))
                message = "0" + "," + str(int(doa_avg)) + "," + str(val_avg)
                message = message.encode('utf-8')
            else:
                message = "0" + "," + "ND"
                message = message.encode('utf-8')

            client_socket.sendto(message, addr)

        except KeyboardInterrupt:
            break
print(time.time() - start)


from datetime import datetime
import unittest
import asyncio
import time

from ocpp.routing import on, after
from ocpp.v16.enums import Action, RegistrationStatus
from ocpp.v16 import call_result, call
import ocpp.exceptions

import default_platform
import default_central

from test_runner import run_test

class TestChangeAvailability(unittest.TestCase):
    def test_set_unavailable_accepted(test):
        @default_central.addTester(test)
        class TestCP(default_central.DefaultChargePoint):
            @after(Action.BootNotification)
            async def after_boot_notification(self, *args, **kwargs):
                result = await self.call(call.ChangeAvailabilityPayload(1, "Inoperative"))
                test.assertTrue(result.status == "Accepted")
                self.done = True

        _, c = run_test(TestCP, sim_len_secs=2, speedup=100)
        test.assertTrue(c.done)

    """
    A Charge Point is considered unavailable when it does not
    allow any charging.
    """
    def test_no_charging_while_unavailable(test):
        @default_central.addTester(test)
        class TestCP(default_central.DefaultChargePoint):
            @after(Action.BootNotification)
            async def after_boot_notification(self, *args, **kwargs):
                result = await self.call(call.ChangeAvailabilityPayload(1, "Inoperative"))
                test.assertTrue(result.status == "Accepted")
                default_platform.show_tag(test, 1, "C0:FF:EE")
                default_platform.connector_state[1] = default_platform.CONNECTOR_STATE_CONNECTED

            @after(Action.StartTransaction)
            def after_start_transaction(self, *args, **kwargs):
                test.fail("Starting a transaction not allowed while unavailable!")

        _, c = run_test(TestCP, sim_len_secs=2, speedup=100)
        test.assertFalse(default_platform.connector_locked[1])
        test.assertEqual(default_platform.charging_current[1], 0)

    """
    A Charge Point is considered available
    (“operative”) when it is charging or ready for charging.
    """
    def test_charging_works_again_after_unavailable(test):
        @default_central.addTester(test)
        class TestCP(default_central.DefaultChargePoint):
            unavail_done = False
            @after(Action.BootNotification)
            async def after_boot_notification(self, *args, **kwargs):
                result = await self.call(call.ChangeAvailabilityPayload(1, "Inoperative"))
                test.assertTrue(result.status == "Accepted")
                default_platform.show_tag(test, 1, "C0:FF:EE")
                default_platform.connector_state[1] = default_platform.CONNECTOR_STATE_CONNECTED

            @after(Action.StatusNotification)
            async def after_status_notification(self, connector_id, error_code, status, **kwargs):
                print(connector_id, error_code, status)
                if connector_id == 1 and status == "Unavailable":
                    result = await self.call(call.ChangeAvailabilityPayload(1, "Operative"))
                    test.assertTrue(result.status == "Accepted")
                    self.unavail_done = True
                if self.unavail_done:
                    default_platform.show_tag(test, 1, "C0:FF:EE")

            @after(Action.StartTransaction)
            def after_start_transaction(self, *args, **kwargs):
                self.done = True

        _, c = run_test(TestCP, sim_len_secs=2, speedup=100)
        test.assertTrue(c.done)

    """
    In the event that Central System requests Charge Point to change to a status it is already in, Charge Point SHALL
    respond with availability status ‘Accepted’.
    """
    def test_accepted_if_already_in_state(test):
        @default_central.addTester(test)
        class TestCP(default_central.DefaultChargePoint):
            @after(Action.BootNotification)
            async def after_boot_notification(self, *args, **kwargs):
                result = await self.call(call.ChangeAvailabilityPayload(1, "Operative"))
                test.assertTrue(result.status == "Accepted")

                result = await self.call(call.ChangeAvailabilityPayload(1, "Inoperative"))
                test.assertTrue(result.status == "Accepted")

                result = await self.call(call.ChangeAvailabilityPayload(1, "Inoperative"))
                test.assertTrue(result.status == "Accepted")
                self.done = True

        _, c = run_test(TestCP, sim_len_secs=2, speedup=100)
        test.assertTrue(c.done)

    def test_state_stays_the_same_if_already_operative(test):
        @default_central.addTester(test)
        class TestCP(default_central.DefaultChargePoint):
            auth_responded = False
            @after(Action.BootNotification)
            def after_boot_notification(self, *args, **kwargs):
                default_platform.show_tag(test, 1, "C0:FF:EE")

            @after(Action.Authorize)
            async def after_authorize(self, *args, **kwargs):
                auth_responded = True
                result = await self.call(call.ChangeAvailabilityPayload(1, "Operative"))
                test.assertTrue(result.status == "Accepted")

        _, c = run_test(TestCP, sim_len_secs=2, speedup=100)
        test.assertEqual(c.status[1], ["Available", "Preparing"])

    """
    When a transaction is in progress Charge Point SHALL respond with availability status 'Scheduled' to
    indicate that it is scheduled to occur after the transaction has finished.

    When an availability change requested with a ChangeAvailability.req PDU has happened, the Charge Point SHALL
    inform Central System of its new availability status with a StatusNotification.req as described there.
    """
    def test_scheduled_if_txn_is_running(test):
        @default_central.addTester(test)
        class TestCP(default_central.DefaultChargePoint):
            txn_stopped = False
            @after(Action.BootNotification)
            def after_boot_notification(self, *args, **kwargs):
                default_platform.show_tag(test, 1, "C0:FF:EE")
                default_platform.connector_state[1] = default_platform.CONNECTOR_STATE_CONNECTED

            @after(Action.StartTransaction)
            async def after_start_transaction(self, *args, **kwargs):
                result = await self.call(call.ChangeAvailabilityPayload(1, "Inoperative"))
                test.assertTrue(result.status == "Scheduled")
                default_platform.show_tag(test, 1, "C0:FF:EE")
                default_platform.connector_state[1] = default_platform.CONNECTOR_STATE_PLUG_DETECTED

            @after(Action.StopTransaction)
            def after_stop_transaction(self, *args, **kwargs):
                self.txn_stopped = True

            @after(Action.StatusNotification)
            def after_status_notification(self, connector_id, error_code, status, **kwargs):
                if connector_id == 1 and status == "Unavailable":
                    test.assertTrue(self.txn_stopped)
                    self.done = True

        _, c = run_test(TestCP, sim_len_secs=3, speedup=100)
        test.assertTrue(c.done)

    """
    Tn the case the ChangeAvailability.req contains ConnectorId = 0, the status change applies to
    the Charge Point and all Connectors.
    """
    def test_connector_id_zero(test):
        @default_central.addTester(test)
        class TestCP(default_central.DefaultChargePoint):
            seen = 0
            num_connectors = 0
            @after(Action.BootNotification)
            async def after_boot_notification(self, *args, **kwargs):
                self.num_connectors = int((await self.call(call.GetConfigurationPayload(["NumberOfConnectors"]))).configuration_key[0]["value"])

                result = await self.call(call.ChangeAvailabilityPayload(0, "Inoperative"))
                test.assertTrue(result.status == "Accepted")

            @after(Action.StatusNotification)
            def after_status_notification(self, connector_id, error_code, status, **kwargs):
                if status == "Unavailable":
                    self.seen += 1 << connector_id
                    print(self.seen)
                if self.seen == (1 << (self.num_connectors + 1)) - 1:
                    self.done = True

        _, c = run_test(TestCP, sim_len_secs=10, speedup=100)
        test.assertTrue(c.done)


import argparse

from loguru import logger

from tweepipe import settings
from tweepipe.db import db_schema
from tweepipe.legacy import client

def main(env_file):
    """Run the hydration process with the legacy client."""
    settings.load_config(env_file=env_file)

    datasets = ["panacea", "echen", "geocov"]
    sn_client = client.Client(
        include_relations=True, include_users=False, schema=db_schema.INDEX_V3
    )
    sn_client.set_env_file(env_file)

    for dataset in datasets:
        logger.info(f"Hydrating for {dataset}.")
        issue = f"{dataset}_keyword_study_v1"
        sn_client.set_issue(issue=issue)
        sn_client.hydrate_tweets_from_db(
            issue=issue, api_count=6, include_relations=True, include_users=False
        )

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Hydrate tweets from large scale datasets."
    )
    parser.add_argument(
        "--env-file",
        type=str,
        help="Environment variable file.",
        required=False,
        default=None,
    )

    args = parser.parse_args()
    main(
        env_file=args.env_file,
    )


from cffi import FFI
ffibuilder = FFI()

ffibuilder.set_source(
    "_hello",
    """
    #include <hello.h>

    """,
    extra_objects=["build-meson/libhello.a"],
    include_dirs=["."],
)

ffibuilder.cdef("""
int answer(void);
char* greet(void);
""")

if __name__ == "__main__":
    ffibuilder.compile(verbose=True)


#!/usr/bin/python

# Python中列表是可变的，这是它区别于字符串和元组的最重要的特点，一句话概括即：列表可以修改，而字符串和元组不能。
a = [66.25, 333, 333, 1, 1234.5]
print(a.count(333), a.count(66.25), a.count('x'))
a.insert(2, -1)
a.append(333)
a.index(333)
a.remove(333)
a.reverse()
a.sort()

'''list.append(x)	把一个元素添加到列表的结尾，相当于 a[len(a):] = [x]。
list.extend(L)	通过添加指定列表的所有元素来扩充列表，相当于 a[len(a):] = L。
list.insert(i, x)	在指定位置插入一个元素。第一个参数是准备插入到其前面的那个元素的索引，例如 a.insert(0, x) 会插入到整个列表之前，而 a.insert(len(a), x) 相当于 a.append(x) 。
list.remove(x)	删除列表中值为 x 的第一个元素。如果没有这样的元素，就会返回一个错误。
list.pop([i])	从列表的指定位置删除元素，并将其返回。如果没有指定索引，a.pop()返回最后一个元素。元素随即从列表中被删除。（方法中 i 两边的方括号表示这个参数是可选的，而不是要求你输入一对方括号，你会经常在 Python 库参考手册中遇到这样的标记。）
list.clear()	移除列表中的所有项，等于del a[:]。
list.index(x)	返回列表中第一个值为 x 的元素的索引。如果没有匹配的元素就会返回一个错误。
list.count(x)	返回 x 在列表中出现的次数。
list.sort()	对列表中的元素进行排序。
list.reverse()	倒排列表中的元素。
list.copy()	返回列表的浅复制，等于a[:]。'''

'''注意：类似 insert, remove 或 sort 等修改列表的方法没有返回值'''

# 将列表当做堆栈使用
'''列表方法使得列表可以很方便的作为一个堆栈来使用，堆栈作为特定的数据结构，最先进入的元素最后一个被释放（后进先出）。
用 append() 方法可以把一个元素添加到堆栈顶。用不指定索引的 pop() 方法可以把一个元素从堆栈顶释放出来。'''

stack = [3, 4, 5]
stack.append(6)
stack.append(7)
print(stack)
print(stack.pop())
print(stack.pop())
print(stack.pop())

# 将列表当作队列使用
'''可以把列表当做队列用，只是在队列里第一加入的元素，第一个取出来；但是拿列表用作这样的目的效率不高。
在列表的最后添加或者弹出元素速度快，然而在列表里插入或者从头部弹出速度却不快（因为所有其他的元素都得一个一个地移动）。'''
from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry")           # Terry arrives
queue.append("Graham")          # Graham arrives
queue.popleft()                 # The first to arrive now leaves
queue.popleft()                 # The second to arrive now leaves
print(queue)                    # Remaining queue in order of arrival

# 列表推导式
'''列表推导式提供了从序列创建列表的简单途径。通常应用程序将一些操作应用于某个序列的每个元素，用其获得的结果作为生成新列表的元素，
或者根据确定的判定条件创建子序列。
每个列表推导式都在 for 之后跟一个表达式，然后有零到多个 for 或 if 子句。
返回结果是一个根据表达从其后的 for 和 if 上下文环境中生成出来的列表。如果希望表达式推导出一个元组，就必须使用括号。'''
vec = [2, 4, 6]
print([3 * x for x in vec])

print([[x, x ** 2] for x in vec])
''''''
freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
print([weapon.strip() for weapon in freshfruit])

''''''
print([3 * x for x in vec if x > 3])

''''''
vec1 = [2, 4, 6]
vec2 = [4, 3, -9]
print([x * y for x in vec1 for y in vec2])
# [8, 6, -18, 16, 12, -36, 24, 18, -54]
print([x + y for x in vec1 for y in vec2])
# [6, 5, -7, 8, 7, -5, 10, 9, -3]
print([vec1[i] * vec2[i] for i in range(len(vec1))])
# [8, 12, -54]

''''''
print([str(round(355/113, i)) for i in range(1, 6)])
# ['3.1', '3.14', '3.142', '3.1416', '3.14159']

# 嵌套列表解析

# 3X4的矩阵列表
matrix = [[1, 2, 3, 4],
          [5, 6, 7, 8],
          [9, 10, 11, 12],
          ]

# 将3X4的矩阵列表转换为4X3列表
print([[row[i] for row in matrix] for i in range(4)])
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

# 也可以使用以下方法来实现
transposed = []
for i in range(4):
    transposed.append([row[i] for row in matrix])

print(transposed)
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

# 另外一种实现方法
transposed = []
for i in range(4):
    # the following 3 lines implement the nested listcomp
    transposed_row = []
    for row in matrix:
        transposed_row.append(row[i])
        transposed.append(transposed_row)

print(transposed)
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

'''del 语句
使用 del 语句可以从一个列表中依索引而不是值来删除一个元素。这与使用 pop() 返回一个值不同。
可以用 del 语句从列表中删除一个切割，或清空整个列表（我们以前介绍的方法是给该切割赋一个空列表）'''

a = [-1, 1, 66.25, 333, 333, 1234.5]
del a[0]
print(a)
# [1, 66.25, 333, 333, 1234.5]
del a[2:4]
print(a)
# [1, 66.25, 1234.5]
del a[:]
print(a)
# []

# 也可以用 del 删除实体变量：
del a

'''元组和序列
元组由若干逗号分隔的值组成'''
t = 12345, 54321, 'hello!'
print(t[0])
# 12345
print(t)
# (12345, 54321, 'hello!')
# Tuples may be nested:
u = t, (1, 2, 3, 4, 5)
print(u)
# ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

'''如你所见，元组在输出时总是有括号的，以便于正确表达嵌套结构。
在输入时可能有或没有括号， 不过括号通常是必须的（如果元组是更大的表达式的一部分）。'''

# 集合
a = {x for x in 'abracadabra' if x not in 'abc'}
print(a)
# {'r', 'd'}

# 字典

# 遍历技巧
'''在字典中遍历时，关键字和对应的值可以使用 items() 方法同时解读出来'''
knights = {'gallahad': 'the pure', 'robin': 'the brave'}
for k, v in knights.items():
    print(k, v)

'''同时遍历两个或更多的序列，可以使用 zip() 组合'''
questions = ['name', 'quest', 'favorite color']
answers = ['lancelot', 'the holy grail', 'blue']
for q, a in zip(questions, answers):
    print('What is your {0}?  It is {1}.'.format(q, a))

'''要反向遍历一个序列，首先指定这个序列，然后调用 reversed() 函数'''
for i in reversed(range(1, 10, 2)):
    print(i)

'''要按顺序遍历一个序列，使用 sorted() 函数返回一个已排序的序列，并不修改原值'''
basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
    print(f)


from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer

@api_view(['GET'])
def index(request):

    return Response({"Success": "The setup was successfully"})

@api_view(['GET'])
def getAllPosts(request):
    getPosts = Post.objects.all()
    serializers = PostSerializer(getPosts, many=True)

    return Response(serializers.data)

@api_view(['POST'])
def createPost(request):
    data = request.data
    serializer = PostSerializer(data=data)
    if serializer.is_valid():
        serializer.save()
        return Response({"Success": "The post was successfully created"}, status=201)

    else:
        return Response(serializer.errors, status=400)

@api_view(['DELETE'])
def deletePost(request, id):
    try:
        post = Post.objects.get(id=id)
        post.delete()
        return Response({"Success": "Deleted"})

    except Post.DoesNotExit:
        return Response({"Error": "The post does not exit"})

@api_view(['GET'])
def getDetailPost(request, id):
    try:
        post = Post.objects.get(id=id)
        serializer = PostSerializer(post)
        return Response(serializer.data)

    except Post.DoesNotExit:
        return Response({"Error": "The post does not exit"})

@api_view(['PUT'])
def updatePost(request,id):

    new_title = request.data.get('title')
    new_content = request.data.get('content')

    try:
        post = Post.objects.get(id=id)

        if new_title:
            post.title = new_title
        if new_content:
            post.content = new_content

        post.save()

        return Response({"Success": "Updated"})

    except Post.DoesNotExit:
        return Response({"Error": "The post does not exit"})


""" function to normalize libraries to translate DNA sequences into amino
# acid sequences of peptides
#
# inputs:
#       mer: number of peptides per sequence
#
#       filenamefastq: string containing the filepath, filename, and
#       extension of the raw data file from deep sequencing
#
#       startflank: sequence prior to the beginning of the peptide sequence
#
#       endflank: sequence following the end of the peptide sequence
#
#       filenameoutput: string containing the filepath, filename, and
#       extension of the excel file to be created
#
#       PhD7: boolean identifier of whether the files correspond to screens
#       completed using NEB's PhD library
#
# output:
#       libraryexcel: table of sequences and frequencies to be exported to excel
#
# Created by Lindsey Brinton at the University of Virginia, 2015
"""

import os
import numpy as np
import collections
import pandas as pd
import math
import time
# must pip install openpyxl

def readFastq(filename):
    """Reads FASTQ file and remove the special characters!"""
    sequences = []
    qualities = []
    with open(filename) as fh:
        while True:
            fh.readline() # skip name line
            seq = fh.readline().rstrip() # read base sequence
            fh.readline() # skip placeholder line
            qual = fh.readline().rstrip() #base quality line
            if len(seq) == 0:
                break
            sequences.append(seq)
            qualities.append(qual)
    return sequences, qualities

def translate(seq):

    table = {
        'ATA':'I', 'ATC':'I', 'ATT':'I', 'ATG':'M',
        'ACA':'T', 'ACC':'T', 'ACG':'T', 'ACT':'T',
        'AAC':'N', 'AAT':'N', 'AAA':'K', 'AAG':'K',
        'AGC':'S', 'AGT':'S', 'AGA':'R', 'AGG':'R',
        'CTA':'L', 'CTC':'L', 'CTG':'L', 'CTT':'L',
        'CCA':'P', 'CCC':'P', 'CCG':'P', 'CCT':'P',
        'CAC':'H', 'CAT':'H', 'CAA':'Q', 'CAG':'Q',
        'CGA':'R', 'CGC':'R', 'CGG':'R', 'CGT':'R',
        'GTA':'V', 'GTC':'V', 'GTG':'V', 'GTT':'V',
        'GCA':'A', 'GCC':'A', 'GCG':'A', 'GCT':'A',
        'GAC':'D', 'GAT':'D', 'GAA':'E', 'GAG':'E',
        'GGA':'G', 'GGC':'G', 'GGG':'G', 'GGT':'G',
        'TCA':'S', 'TCC':'S', 'TCG':'S', 'TCT':'S',
        'TTC':'F', 'TTT':'F', 'TTA':'L', 'TTG':'L',
        'TAC':'Y', 'TAT':'Y', 'TAA':'_', 'TAG':'_',
        'TGC':'C', 'TGT':'C', 'TGA':'_', 'TGG':'W',
        'CCN':'P', 'GGN':'G', 'ACN':'T', 'CGN':'R',
        'GCN':'A', 'CTN':'L', 'TCN':'S', 'GTN':'V',
    }
    protein =""
    if len(seq)%3 == 0:
        for i in range(0, len(seq), 3):
            codon = seq[i:i + 3]
            if codon in table:
                protein+= table[codon]
            else:
                protein+='X'
    return protein

def translatefastq(mer,filenamefastq,startflank,endflank,filenameoutput,PhD7):

    print("Importing file " + filenamefastq)
    # import fastq file
    RawData, Qual = readFastq(filenamefastq)

    print('Input file successfully imported')
    basepairs=3*mer                                               # calculate number of basepairs

    # put nucleotide sequences in array
    print('Isolating peptide sequences')
    First_idx = [s.find(endflank) for idx, s in enumerate(RawData)] # find end, PhD7 = 'GGTGGAGGT'
    Second_idx = [s.rfind(endflank) for idx, s in enumerate(RawData)] # find end, PhD7 = 'GGTGGAGGT'

    print('Eliminating misreads with double endflank')
    # misreads
    for j in range(len(First_idx)):
        if First_idx[j] != Second_idx[j]:                                   # deal with misreads that contain endflank twice
            First_idx[j]=0
        elif First_idx[j]<(3*mer+3):                                         # deal with reads that have endflank at beginning
            First_idx[j]=0
    indices = First_idx

    tic = time.time()
    print('Eliminating misreads with no endflank')
    # reads without end flanking region
    RawData_df = pd.DataFrame(RawData)
    indices_df = pd.DataFrame(indices)

    values = np.array(indices,dtype=object)
    searchval = 0
    ii = np.where(values == searchval)[0]

    RawData_df = RawData_df.drop(ii)
    RawData_df = RawData_df.reset_index(drop=True)
    indices_df = indices_df.drop(ii)
    indices_df = indices_df.reset_index(drop=True)

    indicesMat = indices_df.iloc[:,0].tolist()
    RawData = RawData_df.iloc[:,0].tolist()
    toc = time.time()
    print(toc-tic)

    tic = time.time()
    print('Finding indices with startflank')
    # find indices with startflank
    rep1 = np.ones(len(indicesMat))
    indstart1 = [s-((basepairs+len(startflank))*rep1[idx]) for idx, s in enumerate(indicesMat)]        # start of where startflank should be
    indstart2 = [s-(basepairs+1)*rep1[idx] for idx, s in enumerate(indicesMat)]                           # end of where startflank should be
    startMat = np.chararray([len(indstart1),3])
    startMatCell = []
    for idx, s in enumerate(RawData):
        a = RawData[idx]
        startMatCell.append((a[int(indstart1[idx]):int(indstart2[idx])+1]))

    toc = time.time()
    print(toc-tic)

    print('Isolating indices of correct reads')
    tic = time.time()
    # isolate indices of correct reads
    indicesMat_df = pd.DataFrame(indicesMat)
    RawData_df = pd.DataFrame(RawData)
    startMatCell_df = pd.DataFrame(startMatCell)

    values = np.array(startMatCell,dtype=object)
    searchval = startflank
    ii = np.where(values != searchval)[0]
    RawData_df = RawData_df.drop(ii)
    RawData_df = RawData_df.reset_index(drop=True)
    indices_df = indices_df.drop(ii)
    indices_df = indices_df.reset_index(drop=True)
    startMatCell_df = startMatCell_df.drop(ii)
    startMatCell_df = startMatCell_df.reset_index(drop=True)

    indicesMat = indices_df.to_numpy()
    RawData = RawData_df.iloc[:,0].tolist()
    startMatCell = startMatCell_df.to_numpy()
    toc = time.time()
    print(toc-tic)
    tic = time.time()
    print('Making array of indices of sequences')
    # make array of indices of sequences
    rep1 = np.ones(len(indicesMat))
    indSeq1 = [s-(basepairs*rep1[idx]) for idx, s in enumerate(indicesMat)]
    indSeq2 = [s-rep1[idx] for idx, s in enumerate(indicesMat)]

    # pull out sequence
    a = []
    for idx, s in enumerate(RawData):
        a.append(list(s))
    RawData = a
    NukeArray= []
    i=0
    for i in range(len(RawData)):                                # Store Peptide Sequences as Charcater List
        NukeArray.append(RawData[i][int(indSeq1[i]):(int(indSeq2[i])+1)])
    toc = time.time()
    print(toc-tic)

    print('Getting rid of codons not used by PhD7 library')
    tic = time.time()
    # If PhD7 library, get rid of codons not used by PhD7 library
    if PhD7 == 1:
        badRead1= [row[2] for row in NukeArray]
        badRead2= [row[5] for row in NukeArray]
        badRead3=[row[8] for row in NukeArray]
        badRead4=[row[11] for row in NukeArray]
        badRead5=[row[14] for row in NukeArray]
        badRead6=[row[17] for row in NukeArray]
        badRead7=[row[20] for row in NukeArray]
        brRow = []
        for idx in range(len(badRead1)):
            if (badRead1[idx] == 'A' or badRead2[idx]=='A' or badRead2[idx]=='A' or badRead3[idx]=='A'
            or badRead4[idx]=='A' or badRead5[idx]=='A' or badRead6[idx]=='A' or badRead7[idx]=='A'
            or badRead1[idx] == 'C' or badRead2[idx]=='C' or badRead2[idx]=='C' or badRead3[idx]=='C'
            or badRead4[idx]=='C' or badRead5[idx]=='C' or badRead6[idx]=='C' or badRead7[idx]=='C'):
                brRow.append(idx)                                # find indices of instances of bad reads
        for i in sorted(brRow, reverse=True):
            NukeArray.pop(i)                                     # delete bad codon reads
    toc = time.time()
    print(toc-tic)
    # convert to amino acids
    print('Converting to amino acid sequences')
    AAarray = []
    text = ""
    for idx in range(len(NukeArray)):
        text.join(NukeArray[idx])
        AA = translate(text.join(NukeArray[idx]))
        AA = AA.replace('_', 'Q')
        AAarray.append(AA)

    print('Determining frequencies')
    # determine frequencies
    tableAA = collections.Counter(AAarray)                                # calculate frequencies
    df = pd.DataFrame.from_dict(tableAA, orient='index')
    SeqFreqTable = df.sort_values(by=0)                                   # sort table                                       # Sequences

    # display stats
    print("Number of total valid reads: ", len(AAarray))
    print("Number of unique reads: ", len(SeqFreqTable))

    print('Starting export')
    # export to excel
    iterationsXLS = int(math.ceil(len(SeqFreqTable)/1000000))              # Determine if for loops necessary
    p=1                                                                    # initialize counter
    if iterationsXLS==1:
        print('Exporting to excel: one sheet')
        SeqFreqTable.to_excel(filenameoutput, header=False) # write excel file--> only 1e6 rows each sheet
    else:
        for w in range(iterationsXLS):
            print('Exporting to excel: multiple sheets')
            sheetI = str(p)                                                   # determine sheet to use
            ind2 = int((w+1)*1e6)
            ind1 = int((ind2-1e6)+1)                                                # find indexes within Sequence Array
            ind3 = len(SeqFreqTable)
            with pd.ExcelWriter(filenameoutput) as writer:
                if (ind3-ind1)>(1e6-1):
                    df = SeqFreqTable.iloc[ind1:ind2,:]
                    df2 = df.copy()
                    df2.to_excel(writer, sheet_name="Try1", header=False)
                else:
                    df = SeqFreqTable.iloc[ind1:ind3,:]
                    df3 = df.copy()
                    df3.to_excel(writer, sheet_name="Try2", header=False) # write excel file--> only 1e6 rows each sheet
                p=p+1                                                              # count up
    libraryexcel=SeqFreqTable                                              # output
    print('Part 1 of program finished')
    return

print(snakemake.input[0])
print(snakemake.output[0])
for i in range(len(snakemake.input)):
    print(i)
    translatefastq(snakemake.config["mer"],snakemake.input[i],snakemake.config["startflank"],snakemake.config["endflank"],snakemake.output[i],snakemake.config["PHD7"])


# -*- coding: utf-8 -*-
"""
@Time ： 2021/12/15 23:21
@Auth ： maomao
@File ：jyu疫情.py
@IDE ：PyCharm
@Motto：ABC(Always Be Coding)

"""
import re
import random
import base64

list_nv = ['黄若菡', '张盼灵', '陈语蓉', '黄巧晴', '黄芷梦', '黄晓萱', '黄紫汶']
list_nan = ['黄昭南', '黄玄羽', '黄泽禹', '黄绅绮', '黄沐锋', '黄志文', '黄翔宇']
nanNAME = list_nan[random.randint(0, 6)]
nanNV = list_nan[random.randint(0, 6)]
print(nanNAME)
print(nanNV)

jpg_random = random.randint(0, 4)
img_path_nan = 'nan{name}.jpg'.format(name=jpg_random)
img_path_nv = 'nv{name}.jpg'.format(name=jpg_random)

print(img_path_nan)
with open(img_path_nan, 'rb') as f:
    base64_data = base64.b64encode(f.read())
    tupian = base64_data.decode()
    print('data:image/jpeg;base64,%s'%tupian)