text: # -*- coding: utf-8 -*-
  from __future__ import absolute_import, print_function
  
  import sys, os
  import hashlib
  import base64
  from datetime import datetime, timedelta
  
  from github import Github, UnknownObjectException
  import click
  from jinja2 import Template
  from tqdm import tqdm
  from collections import OrderedDict
  
  __version__ = "0.5.1"
  
  try:
      ROOT = sys._MEIPASS
  except AttributeError:
      ROOT = os.path.dirname(os.path.realpath(__file__))
  
  PY3 = sys.version_info >= (3, 0)
  
  COLORED_LABELS = (
      ("1192FC", "investigating",),
      ("FFA500", "degraded performance"),
      ("FF4D4D", "major outage", )
  )
  
  STATUSES = [status for _, status in COLORED_LABELS]
  
  SYSTEM_LABEL_COLOR = "171717"
  
  TEMPLATES = [
      "template.html",
      "style.css"
  ]
  
  
  @click.group()
  @click.version_option(__version__, '-v', '--version')
  def cli():  # pragma: no cover
      pass
  
  
  @cli.command()
  @click.option('--name', prompt='Name', help='')
  @click.option('--token', prompt='GitHub API Token', help='')
  @click.option('--org', help='GitHub Organization', default=False)
  @click.option('--systems', prompt='Systems, eg (Website,API)', help='')
  def create(token, name, systems, org):
      run_create(name=name, token=token, systems=systems, org=org)
  
  
  @cli.command()
  @click.option('--name', prompt='Name', help='')
  @click.option('--org', help='GitHub Organization', default=False)
  @click.option('--token', prompt='GitHub API Token', help='')
  def update(name, token, org):
      run_update(name=name, token=token, org=org)
  
  
  def run_update(name, token, org):
      click.echo("Generating..")
  
      repo = get_repo(token=token, name=name, org=org)
      issues = get_issues(repo)
  
      # get the SHA of the current HEAD
      sha = repo.get_git_ref("heads/gh-pages").object.sha
  
      # get the template from the repo
      template_file = repo.get_file_contents(
          path="/template.html",
          ref=sha
      )
  
      systems = get_systems(repo, issues)
      incidents = get_incidents(repo, issues)
      panels = get_panels(systems)
  
      # render the template
      template = Template(template_file.decoded_content.decode("utf-8"))
      content = template.render({
          "systems": systems, "incidents": incidents, "panels": panels
      })
  
      # create/update the index.html with the template
      try:
          # get the index.html file, we need the sha to update it
          index = repo.get_file_contents(
              path="/index.html",
              ref=sha,
          )
  
          if is_same_content(content, base64.b64decode(index.content)):
              click.echo("Local status matches remote status, no need to commit.")
              return False
  
          repo.update_file(
              path="/index.html",
              sha=index.sha,
              message="update index",
              content=content,
              branch="gh-pages"
          )
      except UnknownObjectException:
          # index.html does not exist, create it
          repo.create_file(
              path="/index.html",
              message="initial",
              content=content,
              branch="gh-pages",
          )
  
  
  def run_create(name, token, systems, org):
      gh = Github(token)
  
      if org:
          entity = gh.get_organization(org)
      else:
          entity = gh.get_user()
  
      # create the repo
      repo = entity.create_repo(name=name)
  
      # get all labels an delete them
      for label in tqdm(list(repo.get_labels()), "Deleting initial labels"):
          label.delete()
  
      # create new status labels
      for color, label in tqdm(COLORED_LABELS, desc="Creating status labels"):
          repo.create_label(name=label, color=color)
  
      # create system labels
      for label in tqdm(systems.split(","), desc="Creating system labels"):
          repo.create_label(name=label.strip(), color=SYSTEM_LABEL_COLOR)
  
      # add an empty file to master, otherwise we won't be able to create the gh-pages
      # branch
      repo.create_file(
          path="/README.md",
          message="initial",
          content="Visit this site at https://{login}.github.io/{name}/".format(
              login=entity.login,
              name=name
          ),
      )
  
      # create the gh-pages branch
      ref = repo.get_git_ref("heads/master")
      repo.create_git_ref(ref="refs/heads/gh-pages", sha=ref.object.sha)
  
      # add all the template files to the gh-pages branch
      for template in tqdm(TEMPLATES, desc="Adding template files"):
          with open(os.path.join(ROOT, "template", template), "r") as f:
              repo.create_file(
                  path="/" + template,
                  message="initial",
                  content=f.read(),
                  branch="gh-pages"
              )
  
      # set the gh-pages branch to be the default branch
      repo.edit(name=name, default_branch="gh-pages")
  
      # run an initial update to add content to the index
      run_update(token=token, name=name, org=org)
  
      click.echo("Create new issues at https://github.com/{login}/{name}/issues".format(
          login=entity.login,
          name=name
      ))
      click.echo("Visit your new status page at https://{login}.github.io/{name}/".format(
          login=entity.login,
          name=name
      ))
  
      click.echo("")
      click.echo("###############################################################################")
      click.echo("# IMPORTANT: Whenever you add or close an issue you have to run the update    #")
      click.echo("# command to show the changes reflected on your status page.                  #")
      click.echo("# Here's a one-off command for this repo to safe it somewhere safe:           #")
      click.echo("# statuspage update --name={name} --token={token} {org}".format(
              name=name, token=token, org="--org=" + entity.login if org else ""))
      click.echo("###############################################################################")
  
  
  def iter_systems(labels):
      for label in labels:
          if label.color == SYSTEM_LABEL_COLOR:
              yield label.name
  
  
  def get_severity(labels):
      label_map = dict(COLORED_LABELS)
      for label in labels:
          if label.color in label_map:
              return label_map[label.color], label.name or label.color
      return None
  
  
  def get_panels(systems):
      # initialize and fill the panels with affected systems
      panels = OrderedDict()
      for system, data in systems.items():
          if data["status"] != "operational":
              if data["status"] in panels:
                  panels[','.join((data["status"], data['info']))].append(system)
              else:
                  panels[','.join((data["status"], data['info']))] = [system, ]
      return panels
  
  
  def get_repo(token, name, org):
      gh = Github(token)
      if org:
          return gh.get_organization(org).get_repo(name=name)
      return gh.get_user().get_repo(name=name)
  
  
  def get_collaborators(repo):
      return [col.login for col in repo.get_collaborators()]
  
  
  def get_systems(repo, issues):
      systems = OrderedDict()
      # get all systems and mark them as operational
      for name in sorted(iter_systems(labels=repo.get_labels())):
          systems[name] = {
              "status": "operational",
              "info": u"运行正常",
          }
  
      for issue in issues:
          if issue.state == "open":
              labels = issue.get_labels()
              severity, info = get_severity(labels)
              affected_systems = list(iter_systems(labels))
              # shit is hitting the fan RIGHT NOW. Mark all affected systems
              for affected_system in affected_systems:
                  systems[affected_system]["status"] = severity
                  systems[affected_system]["info"] = info
      return systems
  
  def to_beijing_time(datetime_obj):
      """From utc time to utc+8"""
      return datetime_obj + timedelta(hours=8)
  
  def get_incidents(repo, issues):
      # loop over all issues in the past 90 days to get current and past incidents
      incidents = []
      collaborators = get_collaborators(repo=repo)
      for issue in issues:
          labels = issue.get_labels()
          affected_systems = sorted(iter_systems(labels))
          severity, info = get_severity(labels)
  
          # make sure that non-labeled issues are not displayed
          if not affected_systems or severity is None:
              continue
  
          # make sure that the user that created the issue is a collaborator
          if issue.user.login not in collaborators:
              continue
  
          # create an incident
          incident = {
              "created": to_beijing_time(issue.created_at),
              "title": issue.title,
              "systems": affected_systems,
              "severity": severity,
              "info": info,
              "closed": issue.state == "closed",
              "body": issue.body,
              "updates": []
          }
  
          for comment in issue.get_comments():
              # add comments by collaborators only
              if comment.user.login in collaborators:
                  incident["updates"].append({
                      "created": to_beijing_time(comment.created_at),
                      "body": comment.body
                  })
  
          incidents.append(incident)
  
      # sort incidents by date
      return sorted(incidents, key=lambda i: i["created"], reverse=True)
  
  
  def get_issues(repo):
      return repo.get_issues(state="all", since=datetime.now() - timedelta(days=90))
  
  
  def is_same_content(c1, c2):
      def sha1(c):
          if PY3:
              if isinstance(c, str):
                  c = bytes(c, "utf-8")
          else:
              if isinstance(c, unicode):
                  c = bytes(c.encode("utf-8"))
              elif isinstance(c, str):
                  c = bytes(c)
          return hashlib.sha1(c)
      return sha1(c1).hexdigest() == sha1(c2).hexdigest()
  
  
  if __name__ == '__main__':  # pragma: no cover
      cli()
  