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

# The Displaybot should show a window on a small wall-mounted display that plays gifs and videos from a telegram group or tunes to a web radio station.
#
# First, I need to create a Telegram bot. For this, install the Python Telegram Bot library and the peewee database ORM with
#
#     $ pip install peewee python-telegram-bot sqlite3 ffmpy --upgrade
#
# and then setup logging. I follow the [echobot example](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot2.py).
#
# Also, setup your Telegram api token below. Get a token by talking to [this bot](https://telegram.me/botfather) on Telegram.

# In[1]:

import os
import logging

from telegram.ext import Updater, CommandHandler, MessageHandler

# Use appdata to store all persistent application state
appdata = dict()

DATA_DIR = os.path.expanduser(os.path.join("~", ".displayBot"))

logger = logging.getLogger("oxo")
logger.setLevel(logging.DEBUG)

jqlogger = logging.getLogger("JobQueue")
jqlogger.setLevel(logging.WARNING)

# create a file handler
log_dir = os.path.join(DATA_DIR, "hello.log")
handler = logging.FileHandler(log_dir)
handler.setLevel(logging.DEBUG)

# console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# create a logging format
formatter = logging.Formatter('%(asctime)s %(levelname)s\t: %(message)s')
handler.setFormatter(formatter)

formatter1 = logging.Formatter('%(asctime)s %(levelname)s\t: %(message)s')
console_handler.setFormatter(formatter1)

# add the handlers to the logger
logger.addHandler(handler)
jqlogger.addHandler(handler)
logger.addHandler(console_handler)
jqlogger.addHandler(console_handler)

logger.info("Logging to {}".format(log_dir))

TELEGRAM_API_TOKEN = "YOUR TOKEN HERE"
with open(os.path.join(DATA_DIR, "TELEGRAM_API_TOKEN")) as f:
    TELEGRAM_API_TOKEN = f.read().strip()

# As anyone will be able to add the bot and add pictures to your display,
# you can filter telegram usernames here
ALLOWED_USERS = []

SUPPORTED_TYPES = ["video/mp4", "video/webm", "image/gif"]

SERVER_URL = "http://localhost:3000"

playnext = None

# ## Basic commands for a bot
#
# Define a few command handlers for Telegram. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
#
# The start command is sent when the bot is started.

# In[2]:

def start(bot, update):
    update.message.reply_text('Gimme dat gif. Send an .mp4 link!')

# Handle errors, just in case

# In[3]:

def error(bot, update, error):
    logger.warn('Update "%s" caused error "%s"' % (update, error))

# ## Receive clips from Telegram
#
# Next ist the receiver for our app. It will look at incoming messages and determine, whether they contain a link and then wether that link points at an mp4 video. This will then be added to the database for display.
#
# There are special cases:
# - if `url` ends in `gifv`, that is rewritten to `mp4`
# - if `url` ends in `gif`, the gif is downloaded and converted to a local `mp4` (see code for that below)

# In[4]:

import requests

def receive(bot, update):
    elems = update.message.parse_entities(types=["url"])
    logger.info("Receiving message with {} url entities".format(len(elems)))

    for elem in elems:
        url = update.message.text[elem.offset:(elem.offset + elem.length)]

        # Rewrite gifv links extension and try that
        if url[-4:] == "gifv":
            url = url[:-4] + "mp4"
            logger.debug("Rewrite .gifv to {}".format(url))

        try:
            link = requests.head(url)
            logger.debug(link)

        except requests.exceptions.RequestException:
            logger.warning("Link not valid")
            update.message.reply_text("Link not valid")

        else:
            if "Content-Type" in link.headers and link.headers["Content-Type"] in SUPPORTED_TYPES:
                if download_clip(url=url, author=update.message.from_user.first_name):
                    update.message.reply_text("👾 Added video to database.")
                else:
                    update.message.reply_text("👾 Reposter!")

            else:
                logger.info("Link not supported: {}".format(link.headers))

# ## Download and file clips
#
# Then write a handler to store received videos in the database and computes a cached JSON response on disk with all current videos

# In[5]:

import os
import tempfile
import datetime

from sh import rm

def download_clip(url, author):
    global appdata
    if duplicate(url):
        logger.info("Detected duplicate {}".format(url))
        rv = False
    else:
        fname = url.split("/")[-1]
        fpath = os.path.join(DATA_DIR, "clips", fname)
        logger.debug("Downloading clip to {}...".format(fpath))

        with open(fpath, "w+") as f:
            r = requests.get(url, stream=True)
            if r.ok:
                for block in r.iter_content(1024):
                    f.write(block)
            else:
                logger.error("Download failed {}".format(r))

        # Convert gif files using ffmpeg
        if url[-3:] == "gif":
            fpath = convert_gif(fpath)
            fname = os.path.basename(fpath)

        clip = {
            "url": url,
            "author": author,
            "filename": fname,
            "created": datetime.datetime.now().isoformat()
        }
        appdata["clips"].append(clip)
        appdata["incoming"] = clip
        save()

        rv = True
        logger.info("Saved new clip {} from {}".format(fname, author))
    return rv

def duplicate(url):
    return len([c for c in appdata["clips"] if "url" in c and c["url"] == url]) > 0

# ## Converting gifs
#
# In order to convert gifs to the less ressource intensive mp4 format, we can use the ffmpy library, which calls ffmpeg for us outside of python, to make the conversion.
#
# This function creates a temporary file and writes the gif to it. Then ffmpeg is called with settings for converting a gif to an mp4 and the result is stored in `frontend/public/videos/`, where the frontend script will be able to access it.

# In[6]:

import ffmpy

def convert_gif(fpath):
    logger.info("Converting gif to mp4...")

    new_fpath = fpath + ".mp4"

    ff = ffmpy.FFmpeg(
        inputs={fpath: None},
        outputs={new_fpath: '-pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2"'}
    )
    ff.run()
    return new_fpath

# ## Config persistence
#
# We use a dictionary to store all application data and serialize it in a JSON file.

# In[7]:

import json

config_fname = os.path.join(DATA_DIR, "data.json")
def load():
    global appdata

    try:
        with open(config_fname) as f:
            appdata = json.load(f)
    except IOError, ValueError:
        logger.info("Bootstrap config loaded")
        appdata = {
            "clips": [],
            "config": {}
        }

    logger.debug("@LOAD {} clips".format(len(appdata["clips"])))
    return appdata

def save():
    global appdata

    logger.debug("@SAVE {} clips".format(len(appdata["clips"])))
    with open(config_fname, "w") as f:
        json.dump(appdata, f, indent=2, sort_keys=True)

# ## Timeout
#
# This is for the timeout

# In[8]:

def toggle_timeout(bot, update, args=list()):
    """Toggle the timeout config option."""
    global appdata

    if len(args) > 0:
        # user has submitted a parameter
        try:
            timeout = int(args[0])
        except ValueError:
            update.message.reply_text("What")
        else:
            timeout = max(timeout, MIN_TIMEOUT)
            appdata["config"]["timeout"] = timeout
    else:
        # toggle timeout
        if config["timeout"] > 0:
            config["timeout"] = 0
        else:
            config["timeout"] = 10

    save()

    update.message.reply_text('Timeout {}'.format(config["timeout"]))
    logger.info('Timeout {}'.format(config["timeout"]))

# ## Videoplayer

# In[9]:

from sh import mplayer
from time import sleep
from random import choice

def get_next():
    global appdata

    while len(appdata["clips"]) < 1:
        logger.debug("Waiting for clips")
        sleep(10)

    if "incoming" in appdata.keys() and appdata["incoming"]:
        rv = appdata["incoming"]
        appdata["incoming"] = None
        save()
        logger.info("Playing shortlisted clip {}".format(rv["filename"]))
    else:
        rv = choice(appdata["clips"])
    return rv

def play_video():
    clip = get_next()

    while True:
        logger.info("Playing {}".format(clip["filename"]))
        mplayer(os.path.join(DATA_DIR, "clips", clip["filename"]), "-fs", "-really-quiet")
        logger.debug("Finished {}".format(clip["filename"]))
        clip = get_next()
        sleep(0.1)

# ## Radio player
from threading import Thread
from telegram.ext import Job

class Radio(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.url = None
        self.player = None
        self.logger = logging.getLogger("oxo")

    def run(self):
        self.stopped = False
        current_url = None
        title = None
        self.reset_title()

        while not self.stopped:
            self.update()
            if current_url != self.url:
                self.logger.debug("Station changed")
                if self.player is not None:
                    self.player.terminate()
                    self.logger.info("Stopped running radio")
                    self.player = None
                else:
                    self.logger.debug("No radio playing previously")

                if self.url is not None:
                    self.logger.info("Playing {}".format(self.url))
                    self.player = mplayer(self.url, "-quiet",
                        _bg=True,
                        _out=self.interact,
                        _done=lambda c,s,e: self.logger.info("Completed {} - {} with {}".format(c,s,e))
                    )
                current_url = self.url

            elif title != self.title:
                title = self.title
                self.logger.info("Title is {}".format(title))

            sleep(1)

    @classmethod
    def interact(cls, line, stdin):
        logger = logging.getLogger("oxo")
        if line.startswith("ICY"):
            logger.debug("Found ICY data: {}".format(line))
            s = "StreamTitle="
            start = line.find(s) + len(s) + 1
            end = line.find(";", start) - 1
            if start and end:
                title = line[start:end]
                logger.debug("Found title in ICY: {}".format(title))
                if len(title) > 0:
                    global appdata
                    appdata["station_title"] = title

    def update(self):
        global appdata
        self.url = appdata["stations"].get(appdata["station_playing"])
        self.title = appdata.get("station_title")

    @classmethod
    def send_title(cls, bot, job):
        logger = logging.getLogger("oxo")
        global appdata
        t = appdata["station_title"]
        t0 = appdata["station_title_sent"]
        if t != t0:
            logger.debug("Title changed from '{}' to '{}'".format(t0, t))
            msg = "▶ Now playing {}".format(t)
            bot.sendMessage(chat_id=job.context, text=msg)
            appdata["station_title_sent"] = t
            save()

    def reset_title(self):
        global appdata
        appdata["station_title"] = None

    def stop(self):
        self.logger.info("Stopping radio player...")
        self.stopped = True
        if self.player is not None:
            self.player.terminate()
            self.logger.info("Radio stopped")
        else:
            self.logger.info("Radio did not play")

def radio_command(bot, update, job_queue, args=list()):
    global appdata

    if len(args) > 0:
        requested_radio = appdata["stations"].get(args[0])
        if requested_radio:
            logger.info("Requesting station {}".format(args[0]))
            appdata["station_playing"] = args[0]
            update.message.reply_text("📻 Changed station to {}".format(args[0]))

            # Setup title relay
            relay_job = Job(Radio.send_title, 1.0, repeat=True, context=update.message.chat_id)
            job_queue.put(relay_job)
            logger.info("Job {} enqueued".format(relay_job))
            save()
        else:
            update.message.reply_text("📻 I don't know about {}".format(args[0]))
    else:
        appdata["station_playing"] = None
        appdata["station_playing_sent"] = None
        station_data = "\n".join(["/radio {}".format(k) for k in sorted(appdata["stations"].keys())])
        update.message.reply_text(
            "⏹ Radio turned off.\n\nAvailable stations:\n{}".format(station_data))
        save()

# ## Main function
#
# Add the main  function, where the handler functions above are registered with the Telegram Bot API

# In[10]:

def main():

    # Load configuration and video database
    load()

    # Create the EventHandler and pass it your bot's token.
    updater = Updater(TELEGRAM_API_TOKEN)

    # Get the dispatcher to register handlers
    dp = updater.dispatcher

    # on different commands - answer in Telegram
    dp.add_handler(CommandHandler("start", start))

    # on different commands - answer in Telegram
    dp.add_handler(CommandHandler("timeout", toggle_timeout, pass_args=True))

    dp.add_handler(CommandHandler("radio", radio_command,
        pass_args=True, pass_job_queue=True))

    # on noncommand i.e message - echo the message on Telegram
    dp.add_handler(MessageHandler(None, receive))

    # log all errors
    dp.add_error_handler(error)

    # Start the Bot
    updater.start_polling()

    # Start the player
    gif_player = Thread(target=play_video)
    gif_player.setDaemon(True)
    gif_player.start()

    radio = Radio()
    radio.setDaemon(True)
    radio.start()

    # Run the bot until the you presses Ctrl-C or the process receives SIGINT,
    # SIGTERM or SIGABRT. This should be used most of the time, since
    # start_polling() is non-blocking and will stop the bot gracefully.
    updater.idle()

    radio.stop()

# Start your bot by saving this notebook as `display-bot.py` and running `$ python display-bot.py`.
#
# Before the main loop is started, database contents are dumped to the cache file, accessible by the frontend script.

# In[11]:

if __name__ == '__main__':
    main()

# Ok now we have a database of clips that we want to play. We will open them in a subprocess with the default player you have associated with the clips' filetype.
#
# We could just do this with the built-in `subprocess` module, but there's a pythonic alternative called `sh`. Get it with `pip install sh`.

# In[12]:

# main()

# In[ ]:

# In[ ]:

<commit_msg>Better video player<commit_after>#!/usr/bin/env python
# coding: utf-8

# The Displaybot should show a window on a small wall-mounted display that plays gifs and videos from a telegram group or tunes to a web radio station.
#
# First, I need to create a Telegram bot. For this, install the Python Telegram Bot library and the peewee database ORM with
#
#     $ pip install peewee python-telegram-bot sqlite3 ffmpy --upgrade
#
# and then setup logging. I follow the [echobot example](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot2.py).
#
# Also, setup your Telegram api token below. Get a token by talking to [this bot](https://telegram.me/botfather) on Telegram.

# In[1]:

import os
import logging

from telegram.ext import Updater, CommandHandler, MessageHandler

# Use appdata to store all persistent application state
appdata = dict()

DATA_DIR = os.path.expanduser(os.path.join("~", ".displayBot"))

logger = logging.getLogger("oxo")
logger.setLevel(logging.DEBUG)

jqlogger = logging.getLogger("JobQueue")
jqlogger.setLevel(logging.WARNING)

# create a file handler
log_dir = os.path.join(DATA_DIR, "hello.log")
handler = logging.FileHandler(log_dir)
handler.setLevel(logging.DEBUG)

# console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# create a logging format
logfmt = '%(asctime)s %(levelname)s\t: %(message)s'
tmfmt = "%m/%d %H:%M:%S"
formatter = logging.Formatter(logfmt, tmfmt)
handler.setFormatter(formatter)

logfmt1 = '%(asctime)s: %(message)s'
formatter1 = logging.Formatter(logfmt1, tmfmt)
console_handler.setFormatter(formatter1)

# add the handlers to the logger
logger.addHandler(handler)
jqlogger.addHandler(handler)
logger.addHandler(console_handler)
jqlogger.addHandler(console_handler)

logger.info("Logging to {}".format(log_dir))

TELEGRAM_API_TOKEN = "YOUR TOKEN HERE"
with open(os.path.join(DATA_DIR, "TELEGRAM_API_TOKEN")) as f:
    TELEGRAM_API_TOKEN = f.read().strip()

# As anyone will be able to add the bot and add pictures to your display,
# you can filter telegram usernames here
ALLOWED_USERS = []

SUPPORTED_TYPES = ["video/mp4", "video/webm", "image/gif"]

SERVER_URL = "http://localhost:3000"

playnext = None

# ## Basic commands for a bot
#
# Define a few command handlers for Telegram. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
#
# The start command is sent when the bot is started.

# In[2]:

def start(bot, update):
    update.message.reply_text('Gimme dat gif. Send an .mp4 link!')

# Handle errors, just in case

# In[3]:

def error(bot, update, error):
    logger.warn('Update "%s" caused error "%s"' % (update, error))

# ## Receive clips from Telegram
#
# Next ist the receiver for our app. It will look at incoming messages and determine, whether they contain a link and then wether that link points at an mp4 video. This will then be added to the database for display.
#
# There are special cases:
# - if `url` ends in `gifv`, that is rewritten to `mp4`
# - if `url` ends in `gif`, the gif is downloaded and converted to a local `mp4` (see code for that below)

# In[4]:

import requests

def receive(bot, update):
    elems = update.message.parse_entities(types=["url"])
    logger.info("Receiving message with {} url entities".format(len(elems)))

    for elem in elems:
        url = update.message.text[elem.offset:(elem.offset + elem.length)]

        # Rewrite gifv links extension and try that
        if url[-4:] == "gifv":
            url = url[:-4] + "mp4"
            logger.debug("Rewrite .gifv to {}".format(url))

        try:
            link = requests.head(url)
            logger.debug(link)

        except requests.exceptions.RequestException:
            logger.warning("Link not valid")
            update.message.reply_text("Link not valid")

        else:
            if "Content-Type" in link.headers and link.headers["Content-Type"] in SUPPORTED_TYPES:
                if download_clip(url=url, author=update.message.from_user.first_name):
                    update.message.reply_text("👾 Added video to database.")
                else:
                    update.message.reply_text("👾 Reposter!")

            else:
                logger.info("Link not supported: {}".format(link.headers))

# ## Download and file clips
#
# Then write a handler to store received videos in the database and computes a cached JSON response on disk with all current videos

# In[5]:

import os
import tempfile
import datetime

from sh import rm

def download_clip(url, author):
    global appdata
    if duplicate(url):
        logger.info("Detected duplicate {}".format(url))
        rv = False
    else:
        fname = url.split("/")[-1]
        fpath = os.path.join(DATA_DIR, "clips", fname)
        logger.debug("Downloading clip to {}...".format(fpath))

        with open(fpath, "w+") as f:
            r = requests.get(url, stream=True)
            if r.ok:
                for block in r.iter_content(1024):
                    f.write(block)
            else:
                logger.error("Download failed {}".format(r))

        # Convert gif files using ffmpeg
        if url[-3:] == "gif":
            fpath = convert_gif(fpath)
            fname = os.path.basename(fpath)

        clip = {
            "url": url,
            "author": author,
            "filename": fname,
            "created": datetime.datetime.now().isoformat()
        }
        appdata["clips"].append(clip)
        appdata["incoming"] = clip
        save()

        rv = True
        logger.info("Saved new clip {} from {}".format(fname, author))
    return rv

def duplicate(url):
    return len([c for c in appdata["clips"] if "url" in c and c["url"] == url]) > 0

# ## Converting gifs
#
# In order to convert gifs to the less ressource intensive mp4 format, we can use the ffmpy library, which calls ffmpeg for us outside of python, to make the conversion.
#
# This function creates a temporary file and writes the gif to it. Then ffmpeg is called with settings for converting a gif to an mp4 and the result is stored in `frontend/public/videos/`, where the frontend script will be able to access it.

# In[6]:

import ffmpy

def convert_gif(fpath):
    logger.info("Converting gif to mp4...")

    new_fpath = fpath + ".mp4"

    ff = ffmpy.FFmpeg(
        inputs={fpath: None},
        outputs={new_fpath: '-pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2"'}
    )
    ff.run()
    return new_fpath

# ## Config persistence
#
# We use a dictionary to store all application data and serialize it in a JSON file.

# In[7]:

import json

config_fname = os.path.join(DATA_DIR, "data.json")
def load():
    global appdata

    try:
        with open(config_fname) as f:
            appdata = json.load(f)
    except IOError, ValueError:
        logger.info("Bootstrap config loaded")
        appdata = {
            "clips": [],
            "config": {}
        }

    logger.debug("@LOAD {} clips".format(len(appdata["clips"])))
    return appdata

def save():
    global appdata

    logger.debug("@SAVE {} clips".format(len(appdata["clips"])))
    with open(config_fname, "w") as f:
        json.dump(appdata, f, indent=2, sort_keys=True)

# ## Videoplayer

# In[9]:

from sh import mplayer
from time import sleep
from random import choice

# ## Radio player
from threading import Thread
from telegram.ext import Job

class Radio(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.url = None
        self.player = None
        self.logger = logging.getLogger("oxo")

    def run(self):
        self.stopped = False
        current_url = None
        title = None
        self.reset_title()

        while not self.stopped:
            self.update()
            if current_url != self.url:
                self.logger.debug("Station changed")
                if self.player is not None:
                    self.player.terminate()
                    self.logger.info("Stopped running radio")
                    self.player = None
                else:
                    self.logger.debug("No radio playing previously")

                if self.url is not None:
                    self.logger.info("Playing {}".format(self.url))
                    self.player = mplayer(self.url, "-quiet",
                        _bg=True,
                        _out=self.interact)
                current_url = self.url

            elif title != self.title:
                title = self.title
                self.logger.info("Title is {}".format(title))

            sleep(1)

    @classmethod
    def interact(cls, line, stdin):
        logger = logging.getLogger("oxo")
        if line.startswith("ICY"):
            logger.debug("Found ICY data: {}".format(line))
            s = "StreamTitle="
            start = line.find(s) + len(s) + 1
            end = line.find(";", start) - 1
            if start and end:
                title = line[start:end]
                logger.debug("Found title in ICY: {}".format(title))
                if len(title) > 0:
                    global appdata
                    appdata["station_title"] = title

    def update(self):
        global appdata
        self.url = appdata["stations"].get(appdata["station_playing"])
        self.title = appdata.get("station_title")

    @classmethod
    def send_title(cls, bot, job):
        logger = logging.getLogger("oxo")
        global appdata
        t = appdata["station_title"]
        t0 = appdata["station_title_sent"]
        if t != t0:
            logger.debug("Title changed from '{}' to '{}'".format(t0, t))
            msg = "▶ Now playing {}".format(t)
            bot.sendMessage(chat_id=job.context, text=msg)
            appdata["station_title_sent"] = t
            save()

    def reset_title(self):
        global appdata
        appdata["station_title"] = None

    def stop(self):
        self.logger.debug("Stopping radio player...")
        self.stopped = True
        if self.player is not None:
            self.player.terminate()
            self.logger.info("Radio stopped")
        else:
            self.logger.debug("Radio did not play")

    @classmethod
    def telegram_command(cls, bot, update, job_queue, args=list()):
        global appdata

        if len(args) > 0:
            requested_radio = appdata["stations"].get(args[0])
            if requested_radio:
                logger.info("Requesting station {}".format(args[0]))
                appdata["station_playing"] = args[0]
                update.message.reply_text("📻 Changed station to {}".format(args[0]))

                # Setup title relay
                relay_job = Job(Radio.send_title, 1.0, repeat=True, context=update.message.chat_id)
                job_queue.put(relay_job)
                logger.info("Job {} enqueued".format(relay_job))
                save()
            else:
                update.message.reply_text("📻 I don't know about {}".format(args[0]))
        else:
            appdata["station_playing"] = None
            appdata["station_playing_sent"] = None
            station_data = "\n".join(["/radio {}".format(k) for k in sorted(appdata["stations"].keys())])
            update.message.reply_text(
                "⏹ Radio turned off.\n\nAvailable stations:\n{}".format(station_data))
            save()

class VideoPlayer(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.logger = logging.getLogger("oxo")
        self.close_player = False

    @classmethod
    def filepath(self, current_clip):
        return os.path.join(DATA_DIR, "clips", current_clip["filename"])

    def run(self):
        current_clip = VideoPlayer.get_next()
        self.logger.info("Playing clip {}".format(current_clip["filename"]))
        self.player = mplayer(self.filepath(current_clip), "-slave", "-fs", _bg=True, _out=self.interact)
        self.player.wait()

    def stop(self):
        self.logger.debug("Stopping video player")
        if self.player is not None:
            self.player.terminate()
            self.logger.info("Video player stopped")
        else:
            self.logger.debug("Video player did not play")

    @classmethod
    def interact(cls, line, stdin):
        START_PLAYBACK = "Starting playback..."

        logger = logging.getLogger("oxo")

        if START_PLAYBACK in line:
            path = cls.filepath(cls.get_next())
            cmd = "loadfile {} 1\nvo_fullscreen 1\n".format(path)
            logger.info("Enqueuing clip '{}'".format(path))
            stdin.put(cmd)

    @classmethod
    def get_next(cls):
        global appdata
        logger = logging.getLogger("oxo")

        if "incoming" in appdata.keys() and appdata["incoming"]:
            rv = appdata["incoming"]
            appdata["incoming"] = None
            save()
            logger.info("Enqueuing shortlisted clip {}".format(rv["filename"]))
        elif len(appdata["clips"]) > 0:
            rv = choice(appdata["clips"])
        else:
            rv = None
        return rv

# ## Main function
#
# Add the main  function, where the handler functions above are registered with the Telegram Bot API

# In[10]:

def main():

    # Load configuration and video database
    load()

    # Create the EventHandler and pass it your bot's token.
    updater = Updater(TELEGRAM_API_TOKEN)

    # Get the dispatcher to register handlers
    dp = updater.dispatcher

    # on different commands - answer in Telegram
    dp.add_handler(CommandHandler("start", start))

    dp.add_handler(CommandHandler("radio", Radio.telegram_command,
        pass_args=True, pass_job_queue=True))

    # on noncommand i.e message - echo the message on Telegram
    dp.add_handler(MessageHandler(None, receive))

    # log all errors
    dp.add_error_handler(error)

    # Start the Bot
    updater.start_polling()

    # Start the player
    gif_player = VideoPlayer()
    gif_player.setDaemon(True)
    gif_player.start()

    radio = Radio()
    radio.setDaemon(True)
    radio.start()

    # Run the bot until the you presses Ctrl-C or the process receives SIGINT,
    # SIGTERM or SIGABRT. This should be used most of the time, since
    # start_polling() is non-blocking and will stop the bot gracefully.
    updater.idle()

    gif_player.stop()
    radio.stop()

# Start your bot by saving this notebook as `display-bot.py` and running `$ python display-bot.py`.
#
# Before the main loop is started, database contents are dumped to the cache file, accessible by the frontend script.

# In[11]:

if __name__ == '__main__':
    main()

# Ok now we have a database of clips that we want to play. We will open them in a subprocess with the default player you have associated with the clips' filetype.
#
# We could just do this with the built-in `subprocess` module, but there's a pythonic alternative called `sh`. Get it with `pip install sh`.

# In[12]:

# main()

# In[ ]:

# In[ ]:

<|endoftext|>


<commit_before>"""
pydagman.dagfile
Provides the dagfile class to represent a DAGman file
Classes:
Dagfile: Represents a DAGman file
InvalidJobObjectError: Thrown when add_job is called with an invalid pydagman.job.Job object
CircularDependencyError: Thrown when add_job would result in a circular dependency because
the new_job already has the existing job as a parent
"""
import job

class Dagfile:
    """
    Class to represent a DAGman file
    Attributes:
    jobs (list[pydagman.job.Job]): A list of Job objects
    """
    def __init__(self):
        """
        Initialize a new Dagman object
        :rtype : object
        """
        self.jobs = []

    def add_job(self, new_job):
        """
        Add a job to the Dagfile object
        Args:
        new_job (pydagman.job.Job): The Job object to add
        Raises:
        InvalidJobObjectError: if new_job is not a valid Job object
        """
        if isinstance(new_job, job.Job) is not True:
            raise InvalidJobObjectError("Error in Dagman.add_job", new_job)
        try:
            self.__dependency_check(new_job)
        except Exception as e:
            raise(e)
        self.jobs.append(new_job)

    def save(self, filename):
        """Save the dagfile to a provided path
        Args:
        filename (string): Path to save the dagfile to
        """
        with open(filename, 'w') as dagfile:
            # First write out all of the job descriptions, including variables and pre/post scripts
            for job in self.jobs:
                job_string = 'JOB %s %s' % (job.name, job.submit_file)
                if job.noop is not False:
                    dagfile.write(job_string + ' NOOP\n')
                else:
                    dagfile.write(job_string + '\n')
                if job.pre:
                    dagfile.write('SCRIPT PRE %s %s\n' % (job.name, ' '.join(job.pre)))
                if job.post:
                    dagfile.write('SCRIPT POST %s %s\n' % (job.name, ' '.join(job.post)))
                for key in job.vars:
                    dagfile.write('VARS %s %s="%s"\n' % (job.name, key, job.vars[key]))
                if job.num_retries:
                    dagfile.write('RETRY %s %d\n' % (job.name, job.num_retries))
                dagfile.write('\n')
            # Next print out the parent/child relationships - these must come after all jobs are defined
            for job in self.jobs:
                for parent in job.parents:
                    dagfile.write('PARENT %s CHILD %s\n' % (parent, job.name))

    def __dependency_check(self, new_job):
        """Check for circular dependencies
         Args:
         new_job (pydagman.job.Job): Job to add to the Dagfile object
         Raises:
         CircularDependencyError: if adding the job would create a circular dependency
        """
        for job in self.jobs:
                if new_job.name in job.parents and job.name in new_job.parents:
                    raise CircularDependencyError("CircularDependencyError in \
dagman.__dependency_check: Jobs %s and %s have a circular dependency." \
                        % (job.name, new_job.name))

class InvalidJobObjectError(Exception):
    """Error if trying to add a job that is not a valid pydagman.job.Job object"""
    def __init__(self, msg, obj):
        self.msg = msg
        self.obj = obj

    def __str__(self):
        return "%s\nExpected Job object, instead got %s" % (self.msg, repr(self.obj))

class CircularDependencyError(Exception):
    """Error if addition of job would create circular dependency """
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg<commit_msg>Fixed unqualified reference to job in pydagman.dagfile<commit_after>"""
pydagman.dagfile
Provides the dagfile class to represent a DAGman file
Classes:
Dagfile: Represents a DAGman file
InvalidJobObjectError: Thrown when add_job is called with an invalid pydagman.job.Job object
CircularDependencyError: Thrown when add_job would result in a circular dependency because
the new_job already has the existing job as a parent
"""
from pydagman.job import Job

class Dagfile:
    """
    Class to represent a DAGman file
    Attributes:
    jobs (list[pydagman.job.Job]): A list of Job objects
    """
    def __init__(self):
        """
        Initialize a new Dagman object
        :rtype : object
        """
        self.jobs = []

    def add_job(self, new_job):
        """
        Add a job to the Dagfile object
        Args:
        new_job (pydagman.job.Job): The Job object to add
        Raises:
        InvalidJobObjectError: if new_job is not a valid Job object
        """
        if isinstance(new_job, Job) is not True:
            raise InvalidJobObjectError("Error in Dagman.add_job", new_job)
        try:
            self.__dependency_check(new_job)
        except Exception as e:
            raise(e)
        self.jobs.append(new_job)

    def save(self, filename):
        """Save the dagfile to a provided path
        Args:
        filename (string): Path to save the dagfile to
        """
        with open(filename, 'w') as dagfile:
            # First write out all of the job descriptions, including variables and pre/post scripts
            for job in self.jobs:
                job_string = 'JOB %s %s' % (job.name, job.submit_file)
                if job.noop is not False:
                    dagfile.write(job_string + ' NOOP\n')
                else:
                    dagfile.write(job_string + '\n')
                if job.pre:
                    dagfile.write('SCRIPT PRE %s %s\n' % (job.name, ' '.join(job.pre)))
                if job.post:
                    dagfile.write('SCRIPT POST %s %s\n' % (job.name, ' '.join(job.post)))
                for key in job.vars:
                    dagfile.write('VARS %s %s="%s"\n' % (job.name, key, job.vars[key]))
                if job.num_retries:
                    dagfile.write('RETRY %s %d\n' % (job.name, job.num_retries))
                dagfile.write('\n')
            # Next print out the parent/child relationships - these must come after all jobs are defined
            for job in self.jobs:
                for parent in job.parents:
                    dagfile.write('PARENT %s CHILD %s\n' % (parent, job.name))

    def __dependency_check(self, new_job):
        """Check for circular dependencies
         Args:
         new_job (pydagman.job.Job): Job to add to the Dagfile object
         Raises:
         CircularDependencyError: if adding the job would create a circular dependency
        """
        for job in self.jobs:
                if new_job.name in job.parents and job.name in new_job.parents:
                    raise CircularDependencyError("CircularDependencyError in \
dagman.__dependency_check: Jobs %s and %s have a circular dependency." \
                        % (job.name, new_job.name))

class InvalidJobObjectError(Exception):
    """Error if trying to add a job that is not a valid pydagman.job.Job object"""
    def __init__(self, msg, obj):
        self.msg = msg
        self.obj = obj

    def __str__(self):
        return "%s\nExpected Job object, instead got %s" % (self.msg, repr(self.obj))

class CircularDependencyError(Exception):
    """Error if addition of job would create circular dependency """
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg<|endoftext|>


<commit_before><commit_msg>Adopted vocx-fc comment.<commit_after><|endoftext|>


<commit_before>"""
Django settings for japper project.

For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""

import datetime

import os
import environ

from japper.monitoring.plugins import get_installed_apps

# Load environment
env = environ.Env(
    DEBUG=(bool, False),
    STATIC_ROOT=(str, None),
    ALLOWED_HOSTS=(list, []),
    BROKER_URL=(str, 'amqp://guest:guest@localhost:5672//'),
    RAVEN_DSN=(str, None),
    SITE_URL=(str, 'http://127.0.0.1:8000/'),
    REDIS_LOCATION=(str, '127.0.0.1:6379'),
    REDIS_DB=(int, 7),
)

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'rk)5ko4=78lci42-c1$-*#tgdph!5qzq^qqrfn-gpjvtcs&vd_'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG')

TEMPLATE_DEBUG = True

ALLOWED_HOSTS = env('ALLOWED_HOSTS')

SITE_URL = env('SITE_URL')

# Application definition
INSTALLED_APPS = get_installed_apps((
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'crispy_forms',
    'menu',
    'raven.contrib.django.raven_compat',

    'japper',
    'japper.monitoring',
    'japper.users',
))

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'japper.urls'

WSGI_APPLICATION = 'japper.wsgi.application'

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'django.core.context_processors.media',
    'django.core.context_processors.static',
    'django.core.context_processors.tz',
    'django.contrib.messages.context_processors.messages',
    'django.core.context_processors.request',

    'japper.monitoring.context_processors.search',
)

# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases

default_db_env = os.environ.get('DEFAULT_DB', 'postgres')
if default_db_env == 'postgres':
    default_db = 'postgres://localhost/japper'
elif default_db_env == 'mysql':
    default_db = 'mysql://localhost/japper'
elif default_db_env == 'sqlite':
    default_db = 'sqlite://:memory:'
else:
    raise ValueError('invald DEFAULT_DB environment variable: %s'
            % default_db_env)

DATABASES = {
    'default': env.db(default=default_db),
}

# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/

STATIC_URL = '/static/'
STATIC_ROOT = env('STATIC_ROOT')

# Crispy forms settings

CRISPY_TEMPLATE_PACK = 'bootstrap3'

# Celery settings

CELERYBEAT_SCHEDULE = {
    'monitoring:fetch_check_results': {
        'task': 'japper.monitoring.tasks.fetch_check_results',
        'schedule': datetime.timedelta(minutes=1),
    },
    'monitoring:cleanup': {
        'task': 'japper.monitoring.tasks.cleanup',
        'schedule': datetime.timedelta(minutes=5),
    },
}

# Email settings

globals().update(env.email_url(default='smtp://localhost'))

# Auth settings

LOGIN_REDIRECT_URL = 'monitoring_problems'
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'

# Proxy settings

SECURE_PROXY_SSL_HEADER = ('X-Forwarded-Proto', 'https')

# Celery settings

BROKER_URL = env('BROKER_URL')

RAVEN_CONFIG = {
    'dsn': env('RAVEN_DSN'),
}

# Cache settings

CACHES = {
    'default': {
        'BACKEND': 'redis_lock.django_cache.RedisCache',
        'LOCATION': env('REDIS_LOCATION'),
        'OPTIONS': {
            'DB': env('REDIS_DB')
        }
    }
}
<commit_msg>settings: use unix socket connection for postgres by default<commit_after>"""
Django settings for japper project.

For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""

import datetime

import os
import environ

from japper.monitoring.plugins import get_installed_apps

# Load environment
env = environ.Env(
    DEBUG=(bool, False),
    STATIC_ROOT=(str, None),
    ALLOWED_HOSTS=(list, []),
    BROKER_URL=(str, 'amqp://guest:guest@localhost:5672//'),
    RAVEN_DSN=(str, None),
    SITE_URL=(str, 'http://127.0.0.1:8000/'),
    REDIS_LOCATION=(str, '127.0.0.1:6379'),
    REDIS_DB=(int, 7),
)

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'rk)5ko4=78lci42-c1$-*#tgdph!5qzq^qqrfn-gpjvtcs&vd_'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG')

TEMPLATE_DEBUG = True

ALLOWED_HOSTS = env('ALLOWED_HOSTS')

SITE_URL = env('SITE_URL')

# Application definition
INSTALLED_APPS = get_installed_apps((
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'crispy_forms',
    'menu',
    'raven.contrib.django.raven_compat',

    'japper',
    'japper.monitoring',
    'japper.users',
))

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'japper.urls'

WSGI_APPLICATION = 'japper.wsgi.application'

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'django.core.context_processors.media',
    'django.core.context_processors.static',
    'django.core.context_processors.tz',
    'django.contrib.messages.context_processors.messages',
    'django.core.context_processors.request',

    'japper.monitoring.context_processors.search',
)

# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases

default_db_env = os.environ.get('DEFAULT_DB', 'postgres')
if default_db_env == 'postgres':
    default_db = 'postgres:///japper'
elif default_db_env == 'mysql':
    default_db = 'mysql://localhost/japper'
elif default_db_env == 'sqlite':
    default_db = 'sqlite://:memory:'
else:
    raise ValueError('invald DEFAULT_DB environment variable: %s'
            % default_db_env)

DATABASES = {
    'default': env.db(default=default_db),
}

# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/

STATIC_URL = '/static/'
STATIC_ROOT = env('STATIC_ROOT')

# Crispy forms settings

CRISPY_TEMPLATE_PACK = 'bootstrap3'

# Celery settings

CELERYBEAT_SCHEDULE = {
    'monitoring:fetch_check_results': {
        'task': 'japper.monitoring.tasks.fetch_check_results',
        'schedule': datetime.timedelta(minutes=1),
    },
    'monitoring:cleanup': {
        'task': 'japper.monitoring.tasks.cleanup',
        'schedule': datetime.timedelta(minutes=5),
    },
}

# Email settings

globals().update(env.email_url(default='smtp://localhost'))

# Auth settings

LOGIN_REDIRECT_URL = 'monitoring_problems'
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'

# Proxy settings

SECURE_PROXY_SSL_HEADER = ('X-Forwarded-Proto', 'https')

# Celery settings

BROKER_URL = env('BROKER_URL')

RAVEN_CONFIG = {
    'dsn': env('RAVEN_DSN'),
}

# Cache settings

CACHES = {
    'default': {
        'BACKEND': 'redis_lock.django_cache.RedisCache',
        'LOCATION': env('REDIS_LOCATION'),
        'OPTIONS': {
            'DB': env('REDIS_DB')
        }
    }
}
<|endoftext|>


<commit_before>#!/usr/bin/env python
"""
Convert all recipes into feedstocks.

This script is to be run in a TravisCI context, with all secret environment variables defined (BINSTAR_TOKEN, GH_TOKEN)
Such as:

    export GH_TOKEN=$(cat ~/.conda-smithy/github.token)

"""
from __future__ import print_function

from conda_build.metadata import MetaData
from conda_smithy.github import gh_token
from contextlib import contextmanager
from github import Github, GithubException, Team
import os.path
from random import choice
import shutil
import subprocess
import tempfile

# Enable DEBUG to run the diagnostics, without actually creating new feedstocks.
DEBUG = False

superlative = ['awesome', 'slick', 'formidable', 'awe-inspiring', 'breathtaking',
               'magnificent', 'wonderous', 'stunning', 'astonishing', 'superb',
               'splendid', 'impressive', 'unbeatable', 'excellent', 'top', 'outstanding',
               'exalted', 'standout', 'smashing']

def list_recipes():
    recipe_directory_name = 'recipes'
    if os.path.isdir(recipe_directory_name):
        recipes = os.listdir(recipe_directory_name)
    else:
        recipes = []

    for recipe_dir in recipes:
        # We don't list the "example" feedstock. It is an example, and is there
        # to be helpful.
        if recipe_dir.startswith('example'):
            continue
        path = os.path.abspath(os.path.join(recipe_directory_name, recipe_dir))
        yield path, recipe_dir

@contextmanager
def tmp_dir(*args, **kwargs):
    temp_dir = tempfile.mkdtemp(*args, **kwargs)
    try:
        yield temp_dir
    finally:
        shutil.rmtree(temp_dir)

def repo_exists(organization, name):
    token = gh_token()
    gh = Github(token)
    # Use the organization provided.
    org = gh.get_organization(organization)
    try:
        org.get_repo(name)
        return True
    except GithubException as e:
        if e.status == 404:
            return False
        raise

def create_team(org, name, description, repo_names):
    # PyGithub creates secret teams, and has no way of turning that off! :(
    post_parameters = {
        "name": name,
        "description": description,
        "privacy": "closed",
        "permission": "push",
        "repo_names": repo_names
    }
    headers, data = org._requester.requestJsonAndCheck(
        "POST",
        org.url + "/teams",
        input=post_parameters
    )
    return Team.Team(org._requester, headers, data, completed=True)

if __name__ == '__main__':
    is_merged_pr = (os.environ.get('TRAVIS_BRANCH') == 'master' and os.environ.get('TRAVIS_PULL_REQUEST') == 'false')

    smithy_conf = os.path.expanduser('~/.conda-smithy')
    if not os.path.exists(smithy_conf):
        os.mkdir(smithy_conf)

    def write_token(name, token):
        with open(os.path.join(smithy_conf, name + '.token'), 'w') as fh:
            fh.write(token)
    if 'APPVEYOR_TOKEN' in os.environ:
        write_token('appveyor', os.environ['APPVEYOR_TOKEN'])
    if 'CIRCLE_TOKEN' in os.environ:
        write_token('circle', os.environ['CIRCLE_TOKEN'])
    if 'GH_TOKEN' in os.environ:
        write_token('github', os.environ['GH_TOKEN'])

    owner_info = ['--organization', 'conda-forge']

    print('Calculating the recipes which need to be turned into feedstocks.')
    removed_recipes = []
    with tmp_dir('__feedstocks') as feedstocks_dir:
        feedstock_dirs = []
        for recipe_dir, name in list_recipes():
            feedstock_dir = os.path.join(feedstocks_dir, name + '-feedstock')
            os.mkdir(feedstock_dir)
            print('Making feedstock for {}'.format(name))

            subprocess.check_call(['conda', 'smithy', 'init', recipe_dir,
                                   '--feedstock-directory', feedstock_dir])
            if not is_merged_pr:
                # We just want to check that conda-smithy is doing its thing without having any metadata issues.
                continue

            feedstock_dirs.append([feedstock_dir, name, recipe_dir])

            subprocess.check_call(['git', 'remote', 'add', 'upstream_with_token',
                                   'https://conda-forge-admin:{}@github.com/conda-forge/{}'.format(os.environ['GH_TOKEN'],
                                                                                os.path.basename(feedstock_dir))],
                                  cwd=feedstock_dir)

            # Sometimes we already have the feedstock created. We need to deal with that case.
            if repo_exists('conda-forge', os.path.basename(feedstock_dir)):
                subprocess.check_call(['git', 'fetch', 'upstream_with_token'], cwd=feedstock_dir)
                subprocess.check_call(['git', 'branch', '-m', 'master', 'old'], cwd=feedstock_dir)
                try:
                    subprocess.check_call(['git', 'checkout', '-b', 'master', 'upstream_with_token/master'], cwd=feedstock_dir)
                except subprocess.CalledProcessError:
                    # Sometimes, we have a repo, but there are no commits on it! Just catch that case.
                    subprocess.check_call(['git', 'checkout', '-b' 'master'], cwd=feedstock_dir)
            else:
                subprocess.check_call(['conda', 'smithy', 'register-github', feedstock_dir] + owner_info)

        gh = None
        conda_forge = None
        teams = None
        if 'GH_TOKEN' in os.environ:
            gh = Github(os.environ['GH_TOKEN'])
            conda_forge = gh.get_organization('conda-forge')
            teams = {team.name: team for team in conda_forge.get_teams()}

        # Break the previous loop to allow the TravisCI registering to take place only once per function call.
        # Without this, intermittent failiures to synch the TravisCI repos ensue.
        all_maintainers = set()
        for feedstock_dir, name, recipe_dir in feedstock_dirs:
            subprocess.check_call(['conda', 'smithy', 'register-ci', '--feedstock_directory', feedstock_dir] + owner_info)

            subprocess.check_call(['conda', 'smithy', 'rerender'], cwd=feedstock_dir)
            subprocess.check_call(['git', 'commit', '-am', "Re-render the feedstock after CI registration."], cwd=feedstock_dir)
            # Capture the output, as it may contain the GH_TOKEN.
            out = subprocess.check_output(['git', 'push', 'upstream_with_token', 'master'], cwd=feedstock_dir,
                                          stderr=subprocess.STDOUT)

            # Add team members as maintainers.
            if conda_forge:
                meta = MetaData(recipe_dir)
                maintainers = set(meta.meta.get('extra', {}).get('recipe-maintainers', []))
                all_maintainers.update(maintainers)
                team_name = name.lower()
                repo_name = 'conda-forge/{}'.format(os.path.basename(feedstock_dir))

                # Try to get team or create it if it doesn't exist.
                team = teams.get(team_name)
                if not team:
                    team = create_team(
                        conda_forge,
                        team_name,
                        'The {} {} contributors!'.format(choice(superlative), team_name),
                        repo_names=[repo_name]
                    )
                    teams[team_name] = team

                # Add only the new maintainers to the team.
                current_maintainers = team.get_members()
                current_maintainers_handles = set([each_maintainers.login.lower() for each_maintainers in current_maintainers])
                for new_maintainer in maintainers - current_maintainers_handles:
                    headers, data = team._requester.requestJsonAndCheck(
                        "PUT",
                        team.url + "/memberships/" + new_maintainer
                    )
                # Mention any maintainers that need to be removed (unlikely here).
                for old_maintainer in current_maintainers_handles - maintainers:
                    print("AN OLD MEMBER ({}) NEEDS TO BE REMOVED FROM {}".format(old_maintainer, repo_name))

            # Remove this recipe from the repo.
            removed_recipes.append(name)
            if is_merged_pr:
                subprocess.check_call(['git', 'rm', '-r', recipe_dir])

    # Add new conda-forge members to all-members team. Welcome! :)
    if conda_forge:
        team_name = 'all-members'
        team = teams.get(team_name)
        if not team:
            team = create_team(
                conda_forge,
                team_name,
                'All of the awesome conda-forge contributors!',
                []
            )
            teams[team_name] = team

        # Add only the new members to the team.
        current_members = team.get_members()
        current_members_handles = set([each_member.login.lower() for each_member in current_members])
        for new_member in all_maintainers - current_members_handles:
            print("Adding a new member ({}) to conda-forge. Welcome! :)".format(new_member))
            headers, data = team._requester.requestJsonAndCheck(
                "PUT",
                team.url + "/memberships/" + new_member
            )

    # Commit any removed packages.
    subprocess.check_call(['git', 'status'])
    if removed_recipes:
        subprocess.check_call(['git', 'checkout', os.environ.get('TRAVIS_BRANCH')])
        msg = ('Removed recipe{s} ({}) after converting into feedstock{s}. '
               '[ci skip]'.format(', '.join(removed_recipes),
                         s=('s' if len(removed_recipes) > 1 else '')))
        if is_merged_pr:
            # Capture the output, as it may contain the GH_TOKEN.
            out = subprocess.check_output(['git', 'remote', 'add', 'upstream_with_token',
                                           'https://conda-forge-admin:{}@github.com/conda-forge/staged-recipes'.format(os.environ['GH_TOKEN'])],
                                          stderr=subprocess.STDOUT)
            subprocess.check_call(['git', 'commit', '-m', msg])
            # Capture the output, as it may contain the GH_TOKEN.
            out = subprocess.check_output(['git', 'push', 'upstream_with_token', os.environ.get('TRAVIS_BRANCH')],
                                          stderr=subprocess.STDOUT)
        else:
            print('Would git commit, with the following message: \n   {}'.format(msg))
<commit_msg>create_feedstocks: Only get the team members if the team existed before.<commit_after>#!/usr/bin/env python
"""
Convert all recipes into feedstocks.

This script is to be run in a TravisCI context, with all secret environment variables defined (BINSTAR_TOKEN, GH_TOKEN)
Such as:

    export GH_TOKEN=$(cat ~/.conda-smithy/github.token)

"""
from __future__ import print_function

from conda_build.metadata import MetaData
from conda_smithy.github import gh_token
from contextlib import contextmanager
from github import Github, GithubException, Team
import os.path
from random import choice
import shutil
import subprocess
import tempfile

# Enable DEBUG to run the diagnostics, without actually creating new feedstocks.
DEBUG = False

superlative = ['awesome', 'slick', 'formidable', 'awe-inspiring', 'breathtaking',
               'magnificent', 'wonderous', 'stunning', 'astonishing', 'superb',
               'splendid', 'impressive', 'unbeatable', 'excellent', 'top', 'outstanding',
               'exalted', 'standout', 'smashing']

def list_recipes():
    recipe_directory_name = 'recipes'
    if os.path.isdir(recipe_directory_name):
        recipes = os.listdir(recipe_directory_name)
    else:
        recipes = []

    for recipe_dir in recipes:
        # We don't list the "example" feedstock. It is an example, and is there
        # to be helpful.
        if recipe_dir.startswith('example'):
            continue
        path = os.path.abspath(os.path.join(recipe_directory_name, recipe_dir))
        yield path, recipe_dir

@contextmanager
def tmp_dir(*args, **kwargs):
    temp_dir = tempfile.mkdtemp(*args, **kwargs)
    try:
        yield temp_dir
    finally:
        shutil.rmtree(temp_dir)

def repo_exists(organization, name):
    token = gh_token()
    gh = Github(token)
    # Use the organization provided.
    org = gh.get_organization(organization)
    try:
        org.get_repo(name)
        return True
    except GithubException as e:
        if e.status == 404:
            return False
        raise

def create_team(org, name, description, repo_names):
    # PyGithub creates secret teams, and has no way of turning that off! :(
    post_parameters = {
        "name": name,
        "description": description,
        "privacy": "closed",
        "permission": "push",
        "repo_names": repo_names
    }
    headers, data = org._requester.requestJsonAndCheck(
        "POST",
        org.url + "/teams",
        input=post_parameters
    )
    return Team.Team(org._requester, headers, data, completed=True)

if __name__ == '__main__':
    is_merged_pr = (os.environ.get('TRAVIS_BRANCH') == 'master' and os.environ.get('TRAVIS_PULL_REQUEST') == 'false')

    smithy_conf = os.path.expanduser('~/.conda-smithy')
    if not os.path.exists(smithy_conf):
        os.mkdir(smithy_conf)

    def write_token(name, token):
        with open(os.path.join(smithy_conf, name + '.token'), 'w') as fh:
            fh.write(token)
    if 'APPVEYOR_TOKEN' in os.environ:
        write_token('appveyor', os.environ['APPVEYOR_TOKEN'])
    if 'CIRCLE_TOKEN' in os.environ:
        write_token('circle', os.environ['CIRCLE_TOKEN'])
    if 'GH_TOKEN' in os.environ:
        write_token('github', os.environ['GH_TOKEN'])

    owner_info = ['--organization', 'conda-forge']

    print('Calculating the recipes which need to be turned into feedstocks.')
    removed_recipes = []
    with tmp_dir('__feedstocks') as feedstocks_dir:
        feedstock_dirs = []
        for recipe_dir, name in list_recipes():
            feedstock_dir = os.path.join(feedstocks_dir, name + '-feedstock')
            os.mkdir(feedstock_dir)
            print('Making feedstock for {}'.format(name))

            subprocess.check_call(['conda', 'smithy', 'init', recipe_dir,
                                   '--feedstock-directory', feedstock_dir])
            if not is_merged_pr:
                # We just want to check that conda-smithy is doing its thing without having any metadata issues.
                continue

            feedstock_dirs.append([feedstock_dir, name, recipe_dir])

            subprocess.check_call(['git', 'remote', 'add', 'upstream_with_token',
                                   'https://conda-forge-admin:{}@github.com/conda-forge/{}'.format(os.environ['GH_TOKEN'],
                                                                                os.path.basename(feedstock_dir))],
                                  cwd=feedstock_dir)

            # Sometimes we already have the feedstock created. We need to deal with that case.
            if repo_exists('conda-forge', os.path.basename(feedstock_dir)):
                subprocess.check_call(['git', 'fetch', 'upstream_with_token'], cwd=feedstock_dir)
                subprocess.check_call(['git', 'branch', '-m', 'master', 'old'], cwd=feedstock_dir)
                try:
                    subprocess.check_call(['git', 'checkout', '-b', 'master', 'upstream_with_token/master'], cwd=feedstock_dir)
                except subprocess.CalledProcessError:
                    # Sometimes, we have a repo, but there are no commits on it! Just catch that case.
                    subprocess.check_call(['git', 'checkout', '-b' 'master'], cwd=feedstock_dir)
            else:
                subprocess.check_call(['conda', 'smithy', 'register-github', feedstock_dir] + owner_info)

        gh = None
        conda_forge = None
        teams = None
        if 'GH_TOKEN' in os.environ:
            gh = Github(os.environ['GH_TOKEN'])
            conda_forge = gh.get_organization('conda-forge')
            teams = {team.name: team for team in conda_forge.get_teams()}

        # Break the previous loop to allow the TravisCI registering to take place only once per function call.
        # Without this, intermittent failiures to synch the TravisCI repos ensue.
        all_maintainers = set()
        for feedstock_dir, name, recipe_dir in feedstock_dirs:
            subprocess.check_call(['conda', 'smithy', 'register-ci', '--feedstock_directory', feedstock_dir] + owner_info)

            subprocess.check_call(['conda', 'smithy', 'rerender'], cwd=feedstock_dir)
            subprocess.check_call(['git', 'commit', '-am', "Re-render the feedstock after CI registration."], cwd=feedstock_dir)
            # Capture the output, as it may contain the GH_TOKEN.
            out = subprocess.check_output(['git', 'push', 'upstream_with_token', 'master'], cwd=feedstock_dir,
                                          stderr=subprocess.STDOUT)

            # Add team members as maintainers.
            if conda_forge:
                meta = MetaData(recipe_dir)
                maintainers = set(meta.meta.get('extra', {}).get('recipe-maintainers', []))
                all_maintainers.update(maintainers)
                team_name = name.lower()
                repo_name = 'conda-forge/{}'.format(os.path.basename(feedstock_dir))

                # Try to get team or create it if it doesn't exist.
                team = teams.get(team_name)
                if not team:
                    team = create_team(
                        conda_forge,
                        team_name,
                        'The {} {} contributors!'.format(choice(superlative), team_name),
                        repo_names=[repo_name]
                    )
                    teams[team_name] = team
                    current_maintainers = []
                else:
                    current_maintainers = team.get_members()

                # Add only the new maintainers to the team.
                current_maintainers_handles = set([each_maintainers.login.lower() for each_maintainers in current_maintainers])
                for new_maintainer in maintainers - current_maintainers_handles:
                    headers, data = team._requester.requestJsonAndCheck(
                        "PUT",
                        team.url + "/memberships/" + new_maintainer
                    )
                # Mention any maintainers that need to be removed (unlikely here).
                for old_maintainer in current_maintainers_handles - maintainers:
                    print("AN OLD MEMBER ({}) NEEDS TO BE REMOVED FROM {}".format(old_maintainer, repo_name))

            # Remove this recipe from the repo.
            removed_recipes.append(name)
            if is_merged_pr:
                subprocess.check_call(['git', 'rm', '-r', recipe_dir])

    # Add new conda-forge members to all-members team. Welcome! :)
    if conda_forge:
        team_name = 'all-members'
        team = teams.get(team_name)
        if not team:
            team = create_team(
                conda_forge,
                team_name,
                'All of the awesome conda-forge contributors!',
                []
            )
            teams[team_name] = team
            current_members = []
        else:
            current_members = team.get_members()

        # Add only the new members to the team.
        current_members_handles = set([each_member.login.lower() for each_member in current_members])
        for new_member in all_maintainers - current_members_handles:
            print("Adding a new member ({}) to conda-forge. Welcome! :)".format(new_member))
            headers, data = team._requester.requestJsonAndCheck(
                "PUT",
                team.url + "/memberships/" + new_member
            )

    # Commit any removed packages.
    subprocess.check_call(['git', 'status'])
    if removed_recipes:
        subprocess.check_call(['git', 'checkout', os.environ.get('TRAVIS_BRANCH')])
        msg = ('Removed recipe{s} ({}) after converting into feedstock{s}. '
               '[ci skip]'.format(', '.join(removed_recipes),
                         s=('s' if len(removed_recipes) > 1 else '')))
        if is_merged_pr:
            # Capture the output, as it may contain the GH_TOKEN.
            out = subprocess.check_output(['git', 'remote', 'add', 'upstream_with_token',
                                           'https://conda-forge-admin:{}@github.com/conda-forge/staged-recipes'.format(os.environ['GH_TOKEN'])],
                                          stderr=subprocess.STDOUT)
            subprocess.check_call(['git', 'commit', '-m', msg])
            # Capture the output, as it may contain the GH_TOKEN.
            out = subprocess.check_output(['git', 'push', 'upstream_with_token', os.environ.get('TRAVIS_BRANCH')],
                                          stderr=subprocess.STDOUT)
        else:
            print('Would git commit, with the following message: \n   {}'.format(msg))
<|endoftext|>


<commit_before># mlag_config_check.py
# This script will check a switch's MLAG config to ensure that there is only one port in the ISC
# vlan, and that all vlans added to MLAG port exist on the ISC port as well. This does not ensure
# that the tagging on vlans matches across MLAG peers.
#
# Last updated: June 29, 2015

def main():
	# Find which vlan is the ISC vlan
	cli_output = exsh.clicmd('show mlag peer', True).split()
	isc_vlan = cli_output[10]

	# Check the number of ports added to the ISC vlan, and get the port number of the ISC link
	cli_output = exsh.clicmd('show vlan ' + isc_vlan, True).split()

	index = cli_output.index('Ports:')
	index = index + 1 # +1, since we found the item before the number of ports

	num_ports = cli_output[index]

	# Remove the trailing period from that output

	if num_ports.endswith('.'):
		num_ports = int(num_ports[:-1])

	#Check the number of ports added to the ISC vlan

	if num_ports > 1:
		print 'Multiple ports are added to the ISC vlan (' + isc_vlan + '). Please correct.'
		return
	elif num_ports == 0:
		print 'No ports are added to the ISC vlan (' + isc_vlan + '). Please correct.'
		return

	# At this point, we can assume that there is one port added to the ISC vlan,
	# and continue checking the MLAG configuration.

	# Determine what the port number of the ISC is

	if 'Tag:' in cli_output:
		index = cli_output.index('Tag:')
		index = index + 1
		isc_port = cli_output[index]
	elif 'Untag:' in cli_output:
		index = cli_output.index('Untag:')
		index = index + 1
		isc_port = cli_output[index]
	else:
		print 'Error: An error occurred. Unable to determine ISC port'
		return

	# Remove the flags around the port number

	if isc_port.endswith('g'):
		isc_port = isc_port[:-1]
	elif isc_port.endswith('G'):
		print "The ISC port is also configured as an MLAG port. Please correct."
		return

	if isc_port.startswith('*'):
		isc_port = isc_port[1:]
	elif isc_port.startswith('!'):
		isc_port = isc_port[1:]
		print 'The ISC port (' + isc_port + ') is disabled. Please correct.'

	# Create a list of the vlans that exist on the ISC port

	cli_output = exsh.clicmd('show port ' + isc_port + ' info detail', True).split()

	vlan_on_next_iteration = False

	isc_port_vlans = list()

	for i in cli_output:

		if vlan_on_next_iteration:
			#remove a trailing comma, if present
			if i.endswith(','):
				vlan = i[:-1]
			else:
				vlan = i
			isc_port_vlans.append(vlan)

		if i == 'Name:':
			vlan_on_next_iteration = True
		else :
			vlan_on_next_iteration = False

	# Now, isc_port_vlans contains all the vlans on the ISC port.
	# Now we can iterate through MLAG ports, and check if the vlans on them are
	# on the ISC port. If not, an error will be written to the CLI.

	cli_output = exsh.clicmd('show config vsm', True)

	lines = cli_output.split('\n')

	#remove an empty string that breaks the for loop below

	lines = lines[:-1]

	#create an empty list to be used in the loop

	mlag_ports = list()

	for l in lines:
		line = l.split()
		if line[0] == 'enable':
			mlag_ports.append(line[3])

	for p in mlag_ports:

		cli_output = exsh.clicmd('show port ' + p + ' info detail', True).split()

		vlan_on_next_iteration = False

		port_vlans = list()

		for i in cli_output:

			if vlan_on_next_iteration:
				#remove a trailing comma, if present
				if i.endswith(','):
					vlan = i[:-1]
				else:
					vlan = i
				if vlan not in isc_port_vlans:
					print 'Vlan ' + vlan + ' is not added to the ISC port (' + isc_port + '), and is found on MLAG port ' + p + '. Please correct.'

			if i == 'Name:':
				vlan_on_next_iteration = True
			else :
				vlan_on_next_iteration = False

	print 'MLAG config check completed.'

main()
<commit_msg>Added checksum checking to mlag config check<commit_after># mlag_config_check.py
# This script will check a switch's MLAG config to ensure that there is only one port in the ISC
# vlan, and that all vlans added to MLAG port exist on the ISC port as well. This does not ensure
# that the tagging on vlans matches across MLAG peers.
#
# Last updated: June 29, 2015

def main():
	# Find which vlan is the ISC vlan
	cli_output = exsh.clicmd('show mlag peer', True).split()
	isc_vlan = cli_output[10]

	# Check the number of ports added to the ISC vlan, and get the port number of the ISC link
	cli_output = exsh.clicmd('show vlan ' + isc_vlan, True).split()

	index = cli_output.index('Ports:')
	index = index + 1 # +1, since we found the item before the number of ports

	num_ports = cli_output[index]

	# Remove the trailing period from that output

	if num_ports.endswith('.'):
		num_ports = int(num_ports[:-1])

	#Check the number of ports added to the ISC vlan

	if num_ports > 1:
		print 'Multiple ports are added to the ISC vlan (' + isc_vlan + '). Please correct.'
		return
	elif num_ports == 0:
		print 'No ports are added to the ISC vlan (' + isc_vlan + '). Please correct.'
		return

	# At this point, we can assume that there is one port added to the ISC vlan,
	# and continue checking the MLAG configuration.

	# Determine what the port number of the ISC is

	if 'Tag:' in cli_output:
		index = cli_output.index('Tag:')
		index = index + 1
		isc_port = cli_output[index]
	elif 'Untag:' in cli_output:
		index = cli_output.index('Untag:')
		index = index + 1
		isc_port = cli_output[index]
	else:
		print 'Error: An error occurred. Unable to determine ISC port'
		return

	# Remove the flags around the port number

	if isc_port.endswith('g'):
		isc_port = isc_port[:-1]
	elif isc_port.endswith('G'):
		print "The ISC port is also configured as an MLAG port. Please correct."
		return

	if isc_port.startswith('*'):
		isc_port = isc_port[1:]
	elif isc_port.startswith('!'):
		isc_port = isc_port[1:]
		print 'The ISC port (' + isc_port + ') is disabled. Please correct.'

	# Create a list of the vlans that exist on the ISC port

	cli_output = exsh.clicmd('show port ' + isc_port + ' info detail', True).split()

	vlan_on_next_iteration = False

	isc_port_vlans = list()

	for i in cli_output:

		if vlan_on_next_iteration:
			#remove a trailing comma, if present
			if i.endswith(','):
				vlan = i[:-1]
			else:
				vlan = i
			isc_port_vlans.append(vlan)

		if i == 'Name:':
			vlan_on_next_iteration = True
		else :
			vlan_on_next_iteration = False

	# Now, isc_port_vlans contains all the vlans on the ISC port.
	# Now we can iterate through MLAG ports, and check if the vlans on them are
	# on the ISC port. If not, an error will be written to the CLI.

	cli_output = exsh.clicmd('show config vsm', True)

	lines = cli_output.split('\n')

	#remove an empty string that breaks the for loop below

	lines = lines[:-1]

	#create an empty list to be used in the loop

	mlag_ports = list()

	for l in lines:
		line = l.split()
		if line[0] == 'enable':
			mlag_ports.append(line[3])

	for p in mlag_ports:

		cli_output = exsh.clicmd('show port ' + p + ' info detail', True).split()

		vlan_on_next_iteration = False

		port_vlans = list()

		for i in cli_output:

			if vlan_on_next_iteration:
				#remove a trailing comma, if present
				if i.endswith(','):
					vlan = i[:-1]
				else:
					vlan = i
				if vlan not in isc_port_vlans:
					print 'Vlan ' + vlan + ' is not added to the ISC port (' + isc_port + '), and is found on MLAG port ' + p + '. Please correct.'

			if i == 'Name:':
				vlan_on_next_iteration = True
			else :
				vlan_on_next_iteration = False

    ## Check local and remote checksums to determine if FDB and VLANs match

    checksums = exsh.clicmd('debug fdb show globals | include LclCkhsum:', True).split()

    local = checksums[2]
    remote = checksums[3]

    if local is remote:
        print 'Local and remote FDB checksums match.'
    else:
        print 'Local and remote FDB checksums do not match. Please check config on the other MLAG peer.'
        print exsh.clicmd('debug fdb show globals', True)

	print 'MLAG config check completed.'

main()
<|endoftext|>


<commit_before># coding=utf-8
"""
Python implementation of git-up.

Why use git-up? ``git pull` has two problems:
  - It merges upstream changes by default, when it's really more polite to
    rebase over them, unless your collaborators enjoy a commit graph that
    looks like bedhead.
  - It only updates the branch you're currently on, which means git push
    will shout at you for being behind on branches you don't particularly
    care about right now.
(from the orignial git-up https://github.com/aanand/git-up/)

Project Author: Markus Siemens <markus@m-siemens.de>
Project URL: https://github.com/msiemens/PyGitUp
"""

from __future__ import print_function

__all__ = ['GitUp']

###############################################################################
# IMPORTS and LIBRARIES SETUP
###############################################################################

# Python libs
import sys
import os
from contextlib import contextmanager
import subprocess

# 3rd party libs
import colorama
from termcolor import colored

from git import Repo, GitCmdObjectDB

# PyGitUp libs
from PyGitUp.utils import execute, uniq
from PyGitUp.git_wrapper import GitWrapper, GitError

###############################################################################
# Setup of 3rd party libs
###############################################################################

colorama.init(autoreset=True)

###############################################################################
# Setup global vars
###############################################################################

# Initialize frequently used globals
try:
    path = execute('git rev-parse --show-toplevel')
except IndexError:
    sys.exit(1)

repo = Repo(path, odbt=GitCmdObjectDB)
git = GitWrapper(repo)

###############################################################################
# GitUp
###############################################################################

class GitUp(object):
    """ Conainter class for GitUp methods """

    def __init__(self):
        # remote_map: map local branch names to remote branches
        self.remote_map = dict()

        for branch in repo.branches:
            remote = git.remote_ref_for_branch(branch)
            if remote:
                self.remote_map[branch.name] = remote

        # branches: all local branches that has a corresponding remote branch
        self.branches = [
            branch for branch in repo.branches
            if branch.name in list(self.remote_map.keys())
        ]
        self.branches.sort(key=lambda b: b.name)

        # remotes: all remotes that are associated with local branches
        self.remotes = uniq(
            [r.name.split('/', 2)[0] for r in list(self.remote_map.values())]
        )

        # change_count
        self.change_count = len(
            git.status(porcelain=True, untracked_files='no').split('\n')
        )

    def run(self):
        """ Run all the git-up stuff. """
        try:
            self.fetch()

            with git.stash():
                with self.returning_to_current_branch():
                    self.rebase_all_branches()

            if self.with_bundler():
                self.check_bundler()

        except GitError as error:
            print(colored(error.message, 'red'))

            # Print more information about the error
            if error.stdout or error.stderr:
                print()
                print("Here's what git said:")
                print()

                if error.stdout:
                    print(error.stdout)
                if error.stderr:
                    print(error.stderr)

            if error.details:
                print()
                print("Here's what we know:")
                print(str(error.details))
                print()

    def rebase_all_branches(self):
        """ Rebase all branches, if possible. """
        col_width = max([len(b.name) for b in self.branches]) + 1

        for branch in self.branches:
            remote = self.remote_map[branch.name]
            print(colored(branch.name.ljust(col_width), attrs=['bold']),
                  end=' ')

            try:
                remote.commit
            except ValueError:
                # Remote branch doesn't exist!
                print(colored('error: remote branch doesn\'t exist', 'red'))
                continue

            if remote.commit.hexsha == branch.commit.hexsha:
                print(colored('up to date', 'green'))
                continue

            base = git.merge_base(branch.name, remote.name)

            if base == remote.commit.hexsha:
                print(colored('ahead of upstream', 'green'))
                continue

            if base == branch.commit.hexsha:
                print(colored('fast-forwarding...', 'yellow'))
            elif self.config('rebase.auto') == 'false':
                print(colored('diverged', 'red'))
                continue
            else:
                print(colored('rebasing', 'yellow'))

            self.log(branch, remote)
            git.checkout(branch.name)
            git.rebase(remote)

    def fetch(self):
        """
        Fetch the recent refs from the remotes.

        Unless git-up.fetch.all is set to true, all remotes with
        locally existent branches will be fetched.
        """
        fetch_kwargs = {'multiple': True}
        fetch_args = []

        if self.is_prune():
            fetch_kwargs['prune'] = True

        if self.config('fetch.all'):
            fetch_kwargs['all'] = True
        else:
            fetch_args.append(self.remotes)

        try:
            git.fetch(tostdout=True, *fetch_args, **fetch_kwargs)
        except GitError as error:
            error.message = "`git fetch` failed"
            raise error

    def log(self, branch, remote):
        """ Call a log-command, if set by git-up.fetch.all. """
        log_hook = self.config('rebase.log-hook')
        if log_hook:
            subprocess.call(
                [log_hook, 'git-up', branch.name, remote.name],
                shell=True
            )

    ###########################################################################
    # Helpers
    ###########################################################################

    @contextmanager
    def returning_to_current_branch(self):
        """ A contextmanager returning to the current branch. """
        if repo.head.is_detached:
            print(colored("You're not currently on a branch. I'm exiting in"
                          "case you're in the middle of something.", 'red'))
            sys.exit(1)

        branch_name = repo.active_branch.name

        yield

        if not repo.head.ref.name == branch_name:
            print(colored('returning to {0}'.format(branch_name), 'magenta'))
            git.checkout(branch_name)

    def config(self, key):
        """ Get a git-up-specific config value. """
        return git.config('git-up.{0}'.format(key))

    def is_prune(self):
        """
        Return True, if `git fetch --prune` is allowed.

        Because of possible incompatibilities, this requires special
        treatment.
        """
        required_version = "1.6.6"
        config_value = self.config("fetch.prune")

        if git.is_version_min(required_version):
            return config_value != 'false'
        else:
            if config_value == 'true':
                print(colored(
                    "Warning: fetch.prune is set to 'true' but your git "
                    "version doesn't seem to support it ({0} < {1}). Defaulting"
                    " to 'false'.".format(git.version(), required_version),
                    'yellow'
                ))

    ###########################################################################
    # Gemfile Checking
    ###########################################################################

    def with_bundler(self):
        """
        Check, if bundler check is requested.

        Check, if the user wants us to check for new gems and return True in
        this case.
        :rtype : bool
        """
        def gemfile_exists():
            """
            Check, if a Gemfile exists in the current repo.
            """
            return os.path.exists('Gemfile')

        if 'GIT_UP_BUNDLER_CHECK' in os.environ:
            print(colored(
                '''The GIT_UP_BUNDLER_CHECK environment variable is deprecated.
You can now tell git-up to check (or not check) for missing
gems on a per-project basis using git's config system. To
set it globally, run this command anywhere:

git config --global git-up.bundler.check true

To set it within a project, run this command inside that
project's directory:

git config git-up.bundler.check true

Replace 'true' with 'false' to disable checking.''', 'yellow'))

        if self.config('bundler.check') == 'true':
            return gemfile_exists()

        if ('GIT_UP_BUNDLER_CHECK' in os.environ
                and os.environ['GIT_UP_BUNDLER_CHECK'] == 'true'):
            return gemfile_exists()

        return False

    def check_bundler(self):
        """
        Run the bundler check.
        """
        def get_config(name):
            return name if self.config('bundler.' + name) else ''

        from pkg_resources import Requirement, resource_filename
        bundler_script = resource_filename(Requirement.parse('git-up'),
                                           'check-bundler.rb')
        subprocess.call(['ruby', bundler_script, get_config('autoinstall'),
                         get_config('local'), get_config('rbenv')])

###############################################################################

def run():
    GitUp().run()

if __name__ == '__main__':
    run()
<commit_msg>Incorporated aanand/git-up#50: Highlight current branch<commit_after># coding=utf-8
"""
Python implementation of git-up.

Why use git-up? ``git pull` has two problems:
  - It merges upstream changes by default, when it's really more polite to
    rebase over them, unless your collaborators enjoy a commit graph that
    looks like bedhead.
  - It only updates the branch you're currently on, which means git push
    will shout at you for being behind on branches you don't particularly
    care about right now.
(from the orignial git-up https://github.com/aanand/git-up/)

Project Author: Markus Siemens <markus@m-siemens.de>
Project URL: https://github.com/msiemens/PyGitUp
"""

from __future__ import print_function

__all__ = ['GitUp']

###############################################################################
# IMPORTS and LIBRARIES SETUP
###############################################################################

# Python libs
import sys
import os
from contextlib import contextmanager
import subprocess

# 3rd party libs
import colorama
from termcolor import colored

from git import Repo, GitCmdObjectDB

# PyGitUp libs
from PyGitUp.utils import execute, uniq
from PyGitUp.git_wrapper import GitWrapper, GitError

###############################################################################
# Setup of 3rd party libs
###############################################################################

colorama.init(autoreset=True)

###############################################################################
# Setup global vars
###############################################################################

# Initialize frequently used globals
try:
    path = execute('git rev-parse --show-toplevel')
except IndexError:
    sys.exit(1)

repo = Repo(path, odbt=GitCmdObjectDB)
git = GitWrapper(repo)

###############################################################################
# GitUp
###############################################################################

class GitUp(object):
    """ Conainter class for GitUp methods """

    def __init__(self):
        # remote_map: map local branch names to remote branches
        self.remote_map = dict()

        for branch in repo.branches:
            remote = git.remote_ref_for_branch(branch)
            if remote:
                self.remote_map[branch.name] = remote

        # branches: all local branches that has a corresponding remote branch
        self.branches = [
            branch for branch in repo.branches
            if branch.name in list(self.remote_map.keys())
        ]
        self.branches.sort(key=lambda b: b.name)

        # remotes: all remotes that are associated with local branches
        self.remotes = uniq(
            [r.name.split('/', 2)[0] for r in list(self.remote_map.values())]
        )

        # change_count
        self.change_count = len(
            git.status(porcelain=True, untracked_files='no').split('\n')
        )

    def run(self):
        """ Run all the git-up stuff. """
        try:
            self.fetch()

            with git.stash():
                with self.returning_to_current_branch():
                    self.rebase_all_branches()

            if self.with_bundler():
                self.check_bundler()

        except GitError as error:
            print(colored(error.message, 'red'))

            # Print more information about the error
            if error.stdout or error.stderr:
                print()
                print("Here's what git said:")
                print()

                if error.stdout:
                    print(error.stdout)
                if error.stderr:
                    print(error.stderr)

            if error.details:
                print()
                print("Here's what we know:")
                print(str(error.details))
                print()

    def rebase_all_branches(self):
        """ Rebase all branches, if possible. """
        col_width = max([len(b.name) for b in self.branches]) + 1

        for branch in self.branches:
            remote = self.remote_map[branch.name]
            if branch.name == self.repo.active_branch.name:
                attrs = ['bold']
            else:
                attrs = []
            print(colored(branch.name.ljust(col_width), attrs=attrs),
                  end=' ')

            try:
                remote.commit
            except ValueError:
                # Remote branch doesn't exist!
                print(colored('error: remote branch doesn\'t exist', 'red'))
                continue

            if remote.commit.hexsha == branch.commit.hexsha:
                print(colored('up to date', 'green'))
                continue

            base = git.merge_base(branch.name, remote.name)

            if base == remote.commit.hexsha:
                print(colored('ahead of upstream', 'green'))
                continue

            if base == branch.commit.hexsha:
                print(colored('fast-forwarding...', 'yellow'))
            elif self.config('rebase.auto') == 'false':
                print(colored('diverged', 'red'))
                continue
            else:
                print(colored('rebasing', 'yellow'))

            self.log(branch, remote)
            git.checkout(branch.name)
            git.rebase(remote)

    def fetch(self):
        """
        Fetch the recent refs from the remotes.

        Unless git-up.fetch.all is set to true, all remotes with
        locally existent branches will be fetched.
        """
        fetch_kwargs = {'multiple': True}
        fetch_args = []

        if self.is_prune():
            fetch_kwargs['prune'] = True

        if self.config('fetch.all'):
            fetch_kwargs['all'] = True
        else:
            fetch_args.append(self.remotes)

        try:
            git.fetch(tostdout=True, *fetch_args, **fetch_kwargs)
        except GitError as error:
            error.message = "`git fetch` failed"
            raise error

    def log(self, branch, remote):
        """ Call a log-command, if set by git-up.fetch.all. """
        log_hook = self.config('rebase.log-hook')
        if log_hook:
            subprocess.call(
                [log_hook, 'git-up', branch.name, remote.name],
                shell=True
            )

    ###########################################################################
    # Helpers
    ###########################################################################

    @contextmanager
    def returning_to_current_branch(self):
        """ A contextmanager returning to the current branch. """
        if repo.head.is_detached:
            print(colored("You're not currently on a branch. I'm exiting in"
                          "case you're in the middle of something.", 'red'))
            sys.exit(1)

        branch_name = repo.active_branch.name

        yield

        if not repo.head.ref.name == branch_name:
            print(colored('returning to {0}'.format(branch_name), 'magenta'))
            git.checkout(branch_name)

    def config(self, key):
        """ Get a git-up-specific config value. """
        return git.config('git-up.{0}'.format(key))

    def is_prune(self):
        """
        Return True, if `git fetch --prune` is allowed.

        Because of possible incompatibilities, this requires special
        treatment.
        """
        required_version = "1.6.6"
        config_value = self.config("fetch.prune")

        if git.is_version_min(required_version):
            return config_value != 'false'
        else:
            if config_value == 'true':
                print(colored(
                    "Warning: fetch.prune is set to 'true' but your git "
                    "version doesn't seem to support it ({0} < {1}). Defaulting"
                    " to 'false'.".format(git.version(), required_version),
                    'yellow'
                ))

    ###########################################################################
    # Gemfile Checking
    ###########################################################################

    def with_bundler(self):
        """
        Check, if bundler check is requested.

        Check, if the user wants us to check for new gems and return True in
        this case.
        :rtype : bool
        """
        def gemfile_exists():
            """
            Check, if a Gemfile exists in the current repo.
            """
            return os.path.exists('Gemfile')

        if 'GIT_UP_BUNDLER_CHECK' in os.environ:
            print(colored(
                '''The GIT_UP_BUNDLER_CHECK environment variable is deprecated.
You can now tell git-up to check (or not check) for missing
gems on a per-project basis using git's config system. To
set it globally, run this command anywhere:

git config --global git-up.bundler.check true

To set it within a project, run this command inside that
project's directory:

git config git-up.bundler.check true

Replace 'true' with 'false' to disable checking.''', 'yellow'))

        if self.config('bundler.check') == 'true':
            return gemfile_exists()

        if ('GIT_UP_BUNDLER_CHECK' in os.environ
                and os.environ['GIT_UP_BUNDLER_CHECK'] == 'true'):
            return gemfile_exists()

        return False

    def check_bundler(self):
        """
        Run the bundler check.
        """
        def get_config(name):
            return name if self.config('bundler.' + name) else ''

        from pkg_resources import Requirement, resource_filename
        bundler_script = resource_filename(Requirement.parse('git-up'),
                                           'check-bundler.rb')
        subprocess.call(['ruby', bundler_script, get_config('autoinstall'),
                         get_config('local'), get_config('rbenv')])

###############################################################################

def run():
    GitUp().run()

if __name__ == '__main__':
    run()
<|endoftext|>


<commit_before>from setuptools import setup, find_packages

setup(
    entry_points={'trac.plugins': "IRC REPORT TICKET MAN = ticket_reporter"},
    name='IRC REPORT TICKET MAN', version='ONE',
    description='REPORT TICKET CHANGE ON IRC CHANNEL',
    author="JEAN-PAUL CALDERONE", author_email="DO NOT SEND ME EMAIL I PROBABLY HATE YOU",
    license='YOU MAY NOT USE THIS SOFTWARE', url='THERE IS NO URL',
    packages=['ticket_reporter'])
<commit_msg>version bump<commit_after>from setuptools import setup, find_packages

setup(
    entry_points={'trac.plugins': "IRC REPORT TICKET MAN = ticket_reporter"},
    name='IRC REPORT TICKET MAN', version='0.2',
    description='REPORT TICKET CHANGE ON IRC CHANNEL',
    author="JEAN-PAUL CALDERONE", author_email="DO NOT SEND ME EMAIL I PROBABLY HATE YOU",
    license='YOU MAY NOT USE THIS SOFTWARE', url='THERE IS NO URL',
    packages=['ticket_reporter'])
<|endoftext|>