<commit_before>#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
""" Install packages and setup home configs on fresh system. """
from __future__ import print_function
from BuildSrc import get_code
import argparse
import functools
import glob
import os
import shlex
import shutil
import subprocess
try:
    import apt
except ImportError:
    print("Don't use `debian` option.")
try:
    from argcomplete import autocomplete
except ImportError:
    def autocomplete(dummy):
        """ Dummy func. """
        pass

# Packages to install follow, broken down into categories.
MINIMUM = """ \
    vim build-essential git-core mercurial ccache fontconfig gdb lynx \
    colormake colordiff colorgcc exuberant-ctags dkms python-dev python-pip \
    tcl p7zip-full rar zip unzip gzip lzip xz-utils liblzma5 \
    htop dos2unix tree dfc flex bison sphinx-common gettext libfile-next-perl \
    libexpat-dev liblzma-dev libpcre3-dev libcurl4-dev libarchive-dev libevent-dev \
    """

PROGRAMS = """ \
    htop iotop itop mytop pkg-config aptitude synaptic dos2unix \
    oxygen-molecule kubuntu-restricted-extras kubuntu-restricted-addons \
    brother-cups-wrapper-laser brother-cups-wrapper-laser1 \
    brother-lpr-drivers-laser brother-lpr-drivers-laser1 \
    k3b kid3 krita kolourpaint4 kchmviewer yakuake plasma-widget-quickaccess \
    kdirstat keepassx screen tmux \
    chromium-browser firefox rekonq \
    abiword baobab dvdrip easytag chm2pdf dia catdoc \
    gimp gimp-plugin-registry \
    fuse gparted quicksynergy tree dfc sharutils sharutils-doc \
    libnotify-bin libnotify-dev \
    redshift geoclue geoclue-hostip numlockx nautilus-dropbox \
    samba samba-doc samba-dev samba-dbg cifs-utils wireshark libwireshark-dev \
    wondershaper vlc ffmpeg ffmpeg-doc mplayer mencoder \
    p7zip-full rar zip unzip gzip lzip xz-utils liblzma-dev liblzma5 \
    virtualbox-qt wine vagrant \
    ttf-xfree86-nonfree \
    """

KEYRINGS = """ \
    debian-keyring debian-archive-keyring gnome-keyring \
    debian-ports-archive-keyring python-gnomekeyring python-keyring \
    ubuntu-keyring \
    """

PROGRAMMING = """ \
    build-essential debianutils ubuntu-dev-tools mesa-utils openssh-server \
    automake automake1.9-doc ant ant-doc checkinstall checkbox \
    dkms docbook make-doc lynx kdiff3 kdiff3-doc patch rpm2cpio rpm \
    codeblocks kdevelop qt-sdk graphviz \
    colormake colordiff colorgcc jq nmap \
    vim vim-doc vim-gtk vim-rails vim-syntax-go vim-syntax-gtk vim-doc \
    re2c flex flex-doc bison bison-doc exuberant-ctags sphinx-common sphinx-doc \
    bash-doc bash-builtins bashdb \
    zsh zsh-dbg zsh-dev zsh-doc zsh-lovers zshdb \
    clisp clisp-doc clisp-dev clisp-module-gdbm \
    coffeescript coffeescript-doc \
    docutils-common docutils-doc asciidoc xmlto docbook2x \
    erlang erlang-eunit \
    gdc golang golang-doc golang-src golang-codesearch-dev \
    gcc gcc-doc gcc-4.7-source libcunit1 gdb gdb-doc cgdb xxgdb ccache \
    libboost-all-dev libglm-dev libglew-dev libglfw-dev ncurses-doc \
    libncurses5-dev libncursesw5-dev libpcre3-dev zlib1g-dev libbz2-dev \
    libgnome2-dev libgnomeui-dev libgtk2.0-dev libatk1.0-dev libbonoboui2-dev \
    libcairo2-dev libx11-dev libxpm-dev libxt-dev libgmp3-dev libmpc-dev \
    libmpfr-dev libcurl4-openssl-dev libevent-dev libarchive-dev \
    openmpi-bin openmpi-checkpoint openmpi-common \
    gfortran \
    ghc ghc-doc ghc-haddock ghc-prof haskell-debian-utils \
    groovy groovy-doc \
    haskell-devscripts haskell-doc cabal-install \
    junit junit-doc maven openjdk-7-doc openjdk-7-jdk openjdk-7-dbg \
    openjdk-7-source openjdk-7-demo icedtea-7-plugin \
    lua5.2 lua5.2-doc luadoc liblua5.2-dev \
    monodevelop-debugger-gdb monodevelop-nunit \
    nodejs nodejs-dev nodejs-legacy npm \
    perl perl-doc libperl-dev perl-modules libpadwalker-perl \
    libfile-next-perl \
    php5 php5-mysql phpunit php5-dev \
    swi-prolog swi-prolog-doc \
    python python-dev python-doc python-pip \
    python3 python3-dev python3-doc python3-pip jython jython-doc \
    pychecker pylint pep8 python-autopep8 ruby-full shunit2 \
    tcl-dev tcl-doc \
    tex-common texlive-latex-base texlive-font-utils \
    bzr bzr-builddeb bzr-doc python-bzrlib bzrtools git git-gui git-doc \
    mercurial subversion cvs \
    """

BABUN = """ \
    clisp lua perl python python3 ruby make cmake colordiff colorgcc \
    p7zip unzip zip gzip ncurses pcre mercurial bzr subversion \
    libboost-devel libboost_python-devel cppunit
    """

CABAL = "buildwrapper scion-browser hoogle terminfo happy hlint"

PY_PACKS = "argcomplete cram neovim Pygments pytest trash-cli"

class NotSudo(Exception):
    """ Throw this if we aren't sudo but need to be. """
    pass

def make_cmd(src, dst):
    """ Generate a function helper. """
    def cmd_when_dst_empty(files, command, opts=None):
        """ When dst doesn't have file do:
                command(src + file, dst + file, *opts)
        """
        if opts is None:
            opts = ()
        for fil in files:
            sfile, dfile = [x + fil for x in (src, dst)]
            if not os.path.exists(dfile):
                print("{0} >>>>> {1}".format(sfile, dfile))
                command(sfile, dfile, *opts)
    return cmd_when_dst_empty

def home_config():
    """ Setup the dev environment, stuff goes in the user's home folder. """
    script_path = os.path.relpath(__file__)
    script_dir = os.path.dirname(script_path)
    if script_dir == '':
        script_dir = '.'

    dot_dir = script_dir + os.sep + 'dot_files' + os.sep
    home = os.path.expanduser('~') + os.sep

    # Get shell utilities
    shell_dir = home + '.shell' + os.sep
    git_urls = [
        'https://github.com/magicmonty/bash-git-prompt.git',
        'https://github.com/starcraftman/zsh-git-prompt.git',
        'https://github.com/zsh-users/zsh-completions.git',
        'https://github.com/zsh-users/zsh-syntax-highlighting.git',
        'https://github.com/starcraftman/hhighlighter.git',
    ]

    for url in git_urls:
        target = '.' + url[url.rindex('/')+1:url.rindex('.git')]
        get_code(url, shell_dir + target)

    get_code('https://bitbucket.org/sjl/hg-prompt/', shell_dir + '.hg-prompt')

    # Setup powerline fonts if not done.
    ddir = home + '.fonts'
    dpow = ddir + os.sep + 'powerline-fonts'
    if not os.path.exists(ddir) and subprocess.call(['which', 'fc-cache']) == 0:
        os.mkdir(ddir)
        get_code('https://github.com/Lokaltog/powerline-fonts', dpow)
        subprocess.call(['fc-cache', '-vf', ddir])

    ddir = home + '.ccache'
    if not os.path.exists(ddir):
        os.mkdir(ddir)

    files = [os.path.basename(x) for x in glob.glob(dot_dir + '.*')]
    helper = make_cmd(dot_dir, home)
    helper(files, os.symlink)

    print("NOTE: Remember to add user to smb.\nsudo smbpasswd -a username")

def home_restore():
    """ Undo changes by home_config & restore backup if exists. """
    arc_dir = os.path.expanduser('~/.home_bak/')
    home = os.path.expanduser('~/')
    dot_dir = home + '.my_scripts' + os.path.sep + 'dot_files' + os.path.sep

    for folder in ('.shell', '.fonts', '.ccache',):
        try:
            shutil.rmtree(home + folder)
        except OSError:
            pass

    # Clear existing configs if they are symlinks
    files = [os.path.basename(x) for x in glob.glob(dot_dir + '.*')]
    for fil in [home + fil for fil in files]:
        if os.path.islink(fil):
            os.remove(fil)

    arc_files = [os.path.basename(x) for x in glob.glob(arc_dir + '.*')]
    helper = make_cmd(arc_dir, home)
    helper(arc_files, os.rename)

    try:
        os.rmdir(arc_dir)
    except OSError:
        pass

def home_save():
    """ Save existing home configs to a backup dir. """
    arc_dir = os.path.expanduser('~/.home_bak/')
    home = os.path.expanduser('~/')
    dot_dir = home + '.my_scripts' + os.path.sep + 'dot_files' + os.path.sep

    if not os.path.exists(arc_dir):
        os.makedirs(arc_dir)

    files = [os.path.basename(x) for x in glob.glob(dot_dir + '.*')]
    files = [x for x in files if os.path.exists(home + x)]

    helper = make_cmd(home, arc_dir)
    helper(files, os.rename)

def packs_babun():
    """ Setup a fresh babun install. """
    # Install packages
    cmd = 'pact install ' + BABUN
    subprocess.call(shlex.split(cmd))

    home_save()
    home_config()

def packs_cabal():
    """ Installs haskell packages for Eclipse Haskell plugin. """
    cmd = 'cabal update'
    subprocess.call(shlex.split(cmd))

    cmd = 'cabal install ' + CABAL
    subprocess.call(shlex.split(cmd))

def packs_debian(server=False):
    """ Install packages on the current system. """
    if os.getuid() != 0:
        raise NotSudo

    if server:
        packages = MINIMUM.split()
    else:
        packages = (PROGRAMS + PROGRAMMING + KEYRINGS).split()

    cache = apt.Cache()
    print("One moment while we update cache.")
    cache.update()
    cache.open(None)
    print("Update done.")

    cmd = 'sudo apt-get install'.split()
    for pack in packages:
        try:
            package = cache[pack]
            if not package.is_installed:
                cmd.append(pack)
        except KeyError:
            print("Package couldn't be selected: %s" % pack)

    print("Please wait, running: " + " ".join(cmd))
    subprocess.call(cmd)

def packs_py():
    """ Installs python packages using pip. """
    if os.getuid() != 0:
        raise NotSudo

    # Use python package manager.
    cmd = 'sudo pip install ' + PY_PACKS
    subprocess.call(shlex.split(cmd))

    # Install python completion to system bash_completion.d.
    cmd = 'activate-global-python-argcomplete --user'
    subprocess.call(shlex.split(cmd))

def install_jshint():
    """ Setup jshint for progrmaming javascript with vim. """
    cmd = 'npm install jshint -g'
    subprocess.call(shlex.split(cmd))

def install_pipelight():
    """ Silverlight plugin for firefox/chrome on linux.
    http://www.webupd8.org/2013/08/pipelight-use-silverlight-in-your-linux.html
    """
    if os.getuid() != 0:
        raise NotSudo

    cmds = [
        'sudo apt-get remove flashplugin-installer',
        'sudo apt-add-repository ppa:pipelight/stable',
        'sudo apt-get update',
        'sudo apt-get install pipelight-multi',
        'pipelight-plugin --accept --enable silverlight',
        'pipelight-plugin --accept --enable flash',
    ]
    cmds = [x.split() for x in cmds]
    for cmd in cmds:
        subprocess.call(cmd)
    print("Installation over, remember to use a useragent switcher.")

def main():
    """ Main function. """
    mesg = """This script sets up a dev environment.

    choice       effect
    ------------------------------------------------------
    home         Setup home config files.
    home_restore Restore home files and undo home_save.
    home_save    Save existing home files.
    babun        Install babun packages.
    cabal        Install haskell packages for eclipse.
    debian       Install debian packages.
    server       Install debian packages for server, minimal.
    jshint       Install jshint via npm for javascript vim.
    pip          Install python libraries via pip.
    pipelight    Install pipelight flash & silverlight.
    """
    # Use a dict of funcs to process args
    actions = {
        'home':         home_config,
        'home_restore': home_restore,
        'home_save':    home_save,
        'babun':        packs_babun,
        'cabal':        packs_cabal,
        'debian':       packs_debian,
        'server':       functools.partial(packs_debian, True),
        'jshint':       install_jshint,
        'pip':          packs_py,
        'pipelight':    install_pipelight,
    }
    parser = argparse.ArgumentParser(description=mesg,
                                     formatter_class=argparse.
                                     RawDescriptionHelpFormatter)
    parser.add_argument('stages', nargs='+', help='stages to execute',
                        choices=sorted(actions.keys()))

    autocomplete(parser)
    args = parser.parse_args()  # Default parses argv[1:]

    try:
        for stage in args.stages:
            actions[stage]()
    except NotSudo:
        print("Rerun this part of script with sudo.")

if __name__ == '__main__':
    main()
<commit_msg>Specify.<commit_after>#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
""" Install packages and setup home configs on fresh system. """
from __future__ import print_function
from BuildSrc import get_code
import argparse
import functools
import glob
import os
import shlex
import shutil
import subprocess
try:
    import apt
except ImportError:
    print("Don't use `debian` option.")
try:
    from argcomplete import autocomplete
except ImportError:
    def autocomplete(dummy):
        """ Dummy func. """
        pass

# Packages to install follow, broken down into categories.
MINIMUM = """ \
    vim build-essential git-core mercurial ccache fontconfig gdb lynx \
    colormake colordiff colorgcc exuberant-ctags dkms python-dev python-pip \
    tcl p7zip-full rar zip unzip gzip lzip xz-utils liblzma5 \
    htop dos2unix tree dfc flex bison sphinx-common gettext libfile-next-perl \
    libexpat-dev liblzma-dev libpcre3-dev libcurl4-dev libarchive-dev libevent-dev \
    """

PROGRAMS = """ \
    htop iotop itop mytop pkg-config aptitude synaptic dos2unix \
    oxygen-molecule kubuntu-restricted-extras kubuntu-restricted-addons \
    brother-cups-wrapper-laser brother-cups-wrapper-laser1 \
    brother-lpr-drivers-laser brother-lpr-drivers-laser1 \
    k3b kid3 krita kolourpaint4 kchmviewer yakuake plasma-widget-quickaccess \
    k4dirstat keepassx screen tmux \
    chromium-browser firefox rekonq \
    abiword baobab dvdrip easytag chm2pdf dia catdoc \
    gimp gimp-plugin-registry \
    fuse gparted quicksynergy tree dfc sharutils sharutils-doc \
    libnotify-bin libnotify-dev \
    redshift geoclue geoclue-hostip numlockx nautilus-dropbox \
    samba samba-doc samba-dev samba-dbg cifs-utils wireshark libwireshark-dev \
    wondershaper vlc ffmpeg ffmpeg-doc mplayer mencoder \
    p7zip-full rar zip unzip gzip lzip xz-utils liblzma-dev liblzma5 \
    virtualbox-qt wine vagrant \
    ttf-xfree86-nonfree \
    """

KEYRINGS = """ \
    debian-keyring debian-archive-keyring gnome-keyring \
    debian-ports-archive-keyring python-gnomekeyring python-keyring \
    ubuntu-keyring \
    """

PROGRAMMING = """ \
    build-essential debianutils ubuntu-dev-tools mesa-utils openssh-server \
    automake automake1.9-doc ant ant-doc checkinstall checkbox \
    dkms docbook make-doc lynx kdiff3 kdiff3-doc patch rpm2cpio rpm \
    codeblocks kdevelop qt-sdk graphviz \
    colormake colordiff colorgcc jq nmap \
    vim vim-doc vim-gtk vim-rails vim-syntax-go vim-syntax-gtk vim-doc \
    re2c flex flex-doc bison bison-doc exuberant-ctags sphinx-common sphinx-doc \
    bash-doc bash-builtins bashdb \
    zsh zsh-dbg zsh-dev zsh-doc zsh-lovers zshdb \
    clisp clisp-doc clisp-dev clisp-module-gdbm \
    coffeescript coffeescript-doc \
    docutils-common docutils-doc asciidoc xmlto docbook2x \
    erlang erlang-eunit \
    gdc golang golang-doc golang-src golang-codesearch-dev \
    gcc gcc-doc gcc-4.7-source libcunit1 gdb gdb-doc cgdb xxgdb ccache \
    libboost-all-dev libglm-dev libglew-dev libglfw-dev ncurses-doc \
    libncurses5-dev libncursesw5-dev libpcre3-dev zlib1g-dev libbz2-dev \
    libgnome2-dev libgnomeui-dev libgtk2.0-dev libatk1.0-dev libbonoboui2-dev \
    libcairo2-dev libx11-dev libxpm-dev libxt-dev libgmp3-dev libmpc-dev \
    libmpfr-dev libcurl4-openssl-dev libevent-dev libarchive-dev \
    openmpi-bin openmpi-checkpoint openmpi-common \
    gfortran \
    ghc ghc-doc ghc-haddock ghc-prof haskell-debian-utils \
    groovy groovy-doc \
    haskell-devscripts haskell-doc cabal-install \
    junit junit-doc maven openjdk-7-doc openjdk-7-jdk openjdk-7-dbg \
    openjdk-7-source openjdk-7-demo icedtea-7-plugin \
    lua5.2 lua5.2-doc luadoc liblua5.2-dev \
    monodevelop-debugger-gdb monodevelop-nunit \
    nodejs nodejs-dev nodejs-legacy npm \
    perl perl-doc libperl-dev perl-modules libpadwalker-perl \
    libfile-next-perl \
    php5 php5-mysql phpunit php5-dev \
    swi-prolog swi-prolog-doc \
    python python-dev python-doc python-pip \
    python3 python3-dev python3-doc python3-pip jython jython-doc \
    pychecker pylint pep8 python-autopep8 ruby-full shunit2 \
    tcl-dev tcl-doc \
    tex-common texlive-latex-base texlive-font-utils \
    bzr bzr-builddeb bzr-doc python-bzrlib bzrtools git git-gui git-doc \
    mercurial subversion cvs \
    """

BABUN = """ \
    clisp lua perl python python3 ruby make cmake colordiff colorgcc \
    p7zip unzip zip gzip ncurses pcre mercurial bzr subversion \
    libboost-devel libboost_python-devel cppunit
    """

CABAL = "buildwrapper scion-browser hoogle terminfo happy hlint"

PY_PACKS = "argcomplete cram neovim Pygments pytest trash-cli"

class NotSudo(Exception):
    """ Throw this if we aren't sudo but need to be. """
    pass

def make_cmd(src, dst):
    """ Generate a function helper. """
    def cmd_when_dst_empty(files, command, opts=None):
        """ When dst doesn't have file do:
                command(src + file, dst + file, *opts)
        """
        if opts is None:
            opts = ()
        for fil in files:
            sfile, dfile = [x + fil for x in (src, dst)]
            if not os.path.exists(dfile):
                print("{0} >>>>> {1}".format(sfile, dfile))
                command(sfile, dfile, *opts)
    return cmd_when_dst_empty

def home_config():
    """ Setup the dev environment, stuff goes in the user's home folder. """
    script_path = os.path.relpath(__file__)
    script_dir = os.path.dirname(script_path)
    if script_dir == '':
        script_dir = '.'

    dot_dir = script_dir + os.sep + 'dot_files' + os.sep
    home = os.path.expanduser('~') + os.sep

    # Get shell utilities
    shell_dir = home + '.shell' + os.sep
    git_urls = [
        'https://github.com/magicmonty/bash-git-prompt.git',
        'https://github.com/starcraftman/zsh-git-prompt.git',
        'https://github.com/zsh-users/zsh-completions.git',
        'https://github.com/zsh-users/zsh-syntax-highlighting.git',
        'https://github.com/starcraftman/hhighlighter.git',
    ]

    for url in git_urls:
        target = '.' + url[url.rindex('/')+1:url.rindex('.git')]
        get_code(url, shell_dir + target)

    get_code('https://bitbucket.org/sjl/hg-prompt/', shell_dir + '.hg-prompt')

    # Setup powerline fonts if not done.
    ddir = home + '.fonts'
    dpow = ddir + os.sep + 'powerline-fonts'
    if not os.path.exists(ddir) and subprocess.call(['which', 'fc-cache']) == 0:
        os.mkdir(ddir)
        get_code('https://github.com/Lokaltog/powerline-fonts', dpow)
        subprocess.call(['fc-cache', '-vf', ddir])

    ddir = home + '.ccache'
    if not os.path.exists(ddir):
        os.mkdir(ddir)

    files = [os.path.basename(x) for x in glob.glob(dot_dir + '.*')]
    helper = make_cmd(dot_dir, home)
    helper(files, os.symlink)

    print("NOTE: Remember to add user to smb.\nsudo smbpasswd -a username")

def home_restore():
    """ Undo changes by home_config & restore backup if exists. """
    arc_dir = os.path.expanduser('~/.home_bak/')
    home = os.path.expanduser('~/')
    dot_dir = home + '.my_scripts' + os.path.sep + 'dot_files' + os.path.sep

    for folder in ('.shell', '.fonts', '.ccache',):
        try:
            shutil.rmtree(home + folder)
        except OSError:
            pass

    # Clear existing configs if they are symlinks
    files = [os.path.basename(x) for x in glob.glob(dot_dir + '.*')]
    for fil in [home + fil for fil in files]:
        if os.path.islink(fil):
            os.remove(fil)

    arc_files = [os.path.basename(x) for x in glob.glob(arc_dir + '.*')]
    helper = make_cmd(arc_dir, home)
    helper(arc_files, os.rename)

    try:
        os.rmdir(arc_dir)
    except OSError:
        pass

def home_save():
    """ Save existing home configs to a backup dir. """
    arc_dir = os.path.expanduser('~/.home_bak/')
    home = os.path.expanduser('~/')
    dot_dir = home + '.my_scripts' + os.path.sep + 'dot_files' + os.path.sep

    if not os.path.exists(arc_dir):
        os.makedirs(arc_dir)

    files = [os.path.basename(x) for x in glob.glob(dot_dir + '.*')]
    files = [x for x in files if os.path.exists(home + x)]

    helper = make_cmd(home, arc_dir)
    helper(files, os.rename)

def packs_babun():
    """ Setup a fresh babun install. """
    # Install packages
    cmd = 'pact install ' + BABUN
    subprocess.call(shlex.split(cmd))

    home_save()
    home_config()

def packs_cabal():
    """ Installs haskell packages for Eclipse Haskell plugin. """
    cmd = 'cabal update'
    subprocess.call(shlex.split(cmd))

    cmd = 'cabal install ' + CABAL
    subprocess.call(shlex.split(cmd))

def packs_debian(server=False):
    """ Install packages on the current system. """
    if os.getuid() != 0:
        raise NotSudo

    if server:
        packages = MINIMUM.split()
    else:
        packages = (PROGRAMS + PROGRAMMING + KEYRINGS).split()

    cache = apt.Cache()
    print("One moment while we update cache.")
    cache.update()
    cache.open(None)
    print("Update done.")

    cmd = 'sudo apt-get install'.split()
    for pack in packages:
        try:
            package = cache[pack]
            if not package.is_installed:
                cmd.append(pack)
        except KeyError:
            print("Package couldn't be selected: %s" % pack)

    print("Please wait, running: " + " ".join(cmd))
    subprocess.call(cmd)

def packs_py():
    """ Installs python packages using pip. """
    if os.getuid() != 0:
        raise NotSudo

    # Use python package manager.
    cmd = 'sudo pip install ' + PY_PACKS
    subprocess.call(shlex.split(cmd))

    # Install python completion to system bash_completion.d.
    cmd = 'activate-global-python-argcomplete --user'
    subprocess.call(shlex.split(cmd))

def install_jshint():
    """ Setup jshint for progrmaming javascript with vim. """
    cmd = 'npm install jshint -g'
    subprocess.call(shlex.split(cmd))

def install_pipelight():
    """ Silverlight plugin for firefox/chrome on linux.
    http://www.webupd8.org/2013/08/pipelight-use-silverlight-in-your-linux.html
    """
    if os.getuid() != 0:
        raise NotSudo

    cmds = [
        'sudo apt-get remove flashplugin-installer',
        'sudo apt-add-repository ppa:pipelight/stable',
        'sudo apt-get update',
        'sudo apt-get install pipelight-multi',
        'pipelight-plugin --accept --enable silverlight',
        'pipelight-plugin --accept --enable flash',
    ]
    cmds = [x.split() for x in cmds]
    for cmd in cmds:
        subprocess.call(cmd)
    print("Installation over, remember to use a useragent switcher.")

def main():
    """ Main function. """
    mesg = """This script sets up a dev environment.

    choice       effect
    ------------------------------------------------------
    home         Setup home config files.
    home_restore Restore home files and undo home_save.
    home_save    Save existing home files.
    babun        Install babun packages.
    cabal        Install haskell packages for eclipse.
    debian       Install debian packages.
    server       Install debian packages for server, minimal.
    jshint       Install jshint via npm for javascript vim.
    pip          Install python libraries via pip.
    pipelight    Install pipelight flash & silverlight.
    """
    # Use a dict of funcs to process args
    actions = {
        'home':         home_config,
        'home_restore': home_restore,
        'home_save':    home_save,
        'babun':        packs_babun,
        'cabal':        packs_cabal,
        'debian':       packs_debian,
        'server':       functools.partial(packs_debian, True),
        'jshint':       install_jshint,
        'pip':          packs_py,
        'pipelight':    install_pipelight,
    }
    parser = argparse.ArgumentParser(description=mesg,
                                     formatter_class=argparse.
                                     RawDescriptionHelpFormatter)
    parser.add_argument('stages', nargs='+', help='stages to execute',
                        choices=sorted(actions.keys()))

    autocomplete(parser)
    args = parser.parse_args()  # Default parses argv[1:]

    try:
        for stage in args.stages:
            actions[stage]()
    except NotSudo:
        print("Rerun this part of script with sudo.")

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


<commit_before>#!/usr/bin/env python3
import logging
import os
import re
import sys
import traceback

from github3.exceptions import ForbiddenError
from github3 import login
from github3.repos.status import Status

try:
    GITHUB_API_TOKEN = os.environ['GITHUB_API_TOKEN']
    ORGANIZATION = os.environ['ORGANIZATION']
    # PROJECT-100, [project2-900], etc
    LABEL_EXTRACTING_REGEX = os.environ.get('LABEL_EXTRACTING_REGEX',r'\s*[\[]*([a-zA-Z0-9]{2,})[-|\s][0-9]+')
except KeyError as error:
    sys.stderr.write('Please set the environment variable {0}'.format(error))
    sys.exit(1)

# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)

def main():
    client = login(token=GITHUB_API_TOKEN)
    organization = client.organization(ORGANIZATION)
    repos_iterator = organization.repositories()

    info = logger.info
    debug = logger.debug

    info(f"Getting all repos in {ORGANIZATION}...")
    teams = {}
    for team in organization.teams():
        teams[team.name] = team

    if not teams['triage']:
        raise Exception("Unable to find `triage` team")
    triage_members = [x.login for x in teams['triage'].members()]
    triage_repos = [x for x in teams['triage'].repositories()]
    if not teams['committers']:
        raise Exception("Unable to find `triage` team")

    for repository in repos_iterator:
        if repository in triage_repos:
            print(f"Team triage already has {repository}")
        else:
            print(f"Adding triage to {repository}")
            teams['triage'].add_repository(repository, 'triage')

    print("Current triage_members", triage_members)
    for user in teams['committers'].members():
        if user.login not in triage_members:
            print(f'Adding {user} to triage team')
            teams['triage'].add_member(user)
        else:
            print(f'User {user} already in triage team')

    for repository in repos_iterator:
        labels = set(x.name for x in repository.labels())
        print(repository, labels)
        if 'merge-if-green' not in labels:
            try:
                repository.create_label('merge-if-green', '#00ff00', """\
Merge pull request if the CI system goes green.""")
            except ForbiddenError as e:
                print(e)

if __name__ == '__main__':
    main()
<commit_msg>maintain-repos: Add the kokoro:force-run label.<commit_after>#!/usr/bin/env python3
import logging
import os
import re
import sys
import traceback

from github3.exceptions import ForbiddenError
from github3 import login
from github3.repos.status import Status

try:
    GITHUB_API_TOKEN = os.environ['GITHUB_API_TOKEN']
    ORGANIZATION = os.environ['ORGANIZATION']
    # PROJECT-100, [project2-900], etc
    LABEL_EXTRACTING_REGEX = os.environ.get('LABEL_EXTRACTING_REGEX',r'\s*[\[]*([a-zA-Z0-9]{2,})[-|\s][0-9]+')
except KeyError as error:
    sys.stderr.write('Please set the environment variable {0}'.format(error))
    sys.exit(1)

# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)

def main():
    client = login(token=GITHUB_API_TOKEN)
    organization = client.organization(ORGANIZATION)
    repos_iterator = organization.repositories()

    info = logger.info
    debug = logger.debug

    info(f"Getting all repos in {ORGANIZATION}...")
    teams = {}
    for team in organization.teams():
        teams[team.name] = team

    if not teams['triage']:
        raise Exception("Unable to find `triage` team")
    triage_members = [x.login for x in teams['triage'].members()]
    triage_repos = [x for x in teams['triage'].repositories()]
    if not teams['committers']:
        raise Exception("Unable to find `triage` team")

    for repository in repos_iterator:
        if repository in triage_repos:
            print(f"Team triage already has {repository}")
        else:
            print(f"Adding triage to {repository}")
            teams['triage'].add_repository(repository, 'triage')

    print("Current triage_members", triage_members)
    for user in teams['committers'].members():
        if user.login not in triage_members:
            print(f'Adding {user} to triage team')
            teams['triage'].add_member(user)
        else:
            print(f'User {user} already in triage team')

    for repository in repos_iterator:
        labels = set(x.name for x in repository.labels())
        print(repository, labels)
        if 'merge-if-green' not in labels:
            try:
                repository.create_label('merge-if-green', '#00ff00', """\
Merge pull request if the CI system goes green.""")
            except ForbiddenError as e:
                print(e)
        if 'kokoro:force-run' not in labels:
            try:
                repository.create_label('kokoro:force-run', '#170e6d', """\
Force the Google Kokoro CI system to run on a pull request.""")
            except ForbiddenError as e:
                print(e)

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


<commit_before>"""
    twtxt.cli
    ~~~~~~~~~

    This module implements the command-line interface of twtxt.

    :copyright: (c) 2016-2017 by buckket.
    :license: MIT, see LICENSE for more details.
"""

import logging
import os
import sys
import textwrap
from itertools import chain

import click

from twtxt.cache import Cache
from twtxt.config import Config
from twtxt.helper import run_pre_tweet_hook, run_post_tweet_hook
from twtxt.helper import sort_and_truncate_tweets
from twtxt.helper import style_timeline, style_source, style_source_with_status
from twtxt.helper import validate_created_at, validate_text, validate_config_key
from twtxt.log import init_logging
from twtxt.mentions import expand_mentions
from twtxt.models import Tweet, Source
from twtxt.twfile import get_local_tweets, add_local_tweet
from twtxt.twhttp import get_remote_tweets, get_remote_status

logger = logging.getLogger(__name__)

@click.group()
@click.option("--config", "-c",
              type=click.Path(exists=True, file_okay=True, readable=True, writable=True, resolve_path=True),
              help="Specify a custom config file location.")
@click.option("--verbose", "-v",
              is_flag=True, default=False,
              help="Enable verbose output for debugging purposes.")
@click.version_option()
@click.pass_context
def cli(ctx, config, verbose):
    """Decentralised, minimalist microblogging service for hackers."""
    init_logging(debug=verbose)

    if ctx.invoked_subcommand == "quickstart":
        return  # Skip initializing config file

    try:
        if config:
            conf = Config.from_file(config)
        else:
            conf = Config.discover()
    except ValueError as e:
        if "Error in config file." in str(e):
            click.echo("✗ Please correct the errors mentioned above an run twtxt again.")
        else:
            click.echo("✗ Config file not found or not readable. You may want to run twtxt quickstart.")
        sys.exit()

    ctx.default_map = conf.build_default_map()
    ctx.obj = {'conf': conf}

@cli.command()
@click.option("--created-at",
              callback=validate_created_at,
              help="ISO 8601 formatted datetime string to use in Tweet, instead of current time.")
@click.option("--twtfile", "-f",
              type=click.Path(file_okay=True, writable=True, resolve_path=True),
              help="Location of your twtxt file. (Default: twtxt.txt)")
@click.argument("text", callback=validate_text, nargs=-1)
@click.pass_context
def tweet(ctx, created_at, twtfile, text):
    """Append a new tweet to your twtxt file."""
    text = expand_mentions(text)
    tweet = Tweet(text, created_at) if created_at else Tweet(text)

    pre_tweet_hook = ctx.obj["conf"].pre_tweet_hook
    if pre_tweet_hook:
        run_pre_tweet_hook(pre_tweet_hook, ctx.obj["conf"].options)

    if not add_local_tweet(tweet, twtfile):
        click.echo("✗ Couldn’t write to file.")
    else:
        post_tweet_hook = ctx.obj["conf"].post_tweet_hook
        if post_tweet_hook:
            run_post_tweet_hook(post_tweet_hook, ctx.obj["conf"].options)

@cli.command()
@click.option("--pager/--no-pager",
              is_flag=True,
              help="Use a pager to display content. (Default: False)")
@click.option("--limit", "-l",
              type=click.INT,
              help="Limit total number of shown tweets. (Default: 20)")
@click.option("--twtfile", "-f",
              type=click.Path(exists=True, file_okay=True, readable=True, resolve_path=True),
              help="Location of your twtxt file. (Default: twtxt.txt")
@click.option("--ascending", "sorting",
              flag_value="ascending",
              help="Sort timeline in ascending order.")
@click.option("--descending", "sorting",
              flag_value="descending",
              help="Sort timeline in descending order. (Default)")
@click.option("--timeout",
              type=click.FLOAT,
              help="Maximum time requests are allowed to take. (Default: 5.0)")
@click.option("--porcelain",
              is_flag=True,
              help="Style output in an easy-to-parse format. (Default: False)")
@click.option("--source", "-s",
              help="Only show feed of the given source. (Can be nick or URL)")
@click.option("--cache/--no-cache",
              is_flag=True,
              help="Cache remote twtxt files locally. (Default: True)")
@click.option("--force-update",
              is_flag=True,
              help="Force update even if cache is up-to-date. (Default: False)")
@click.pass_context
def timeline(ctx, pager, limit, twtfile, sorting, timeout, porcelain, source, cache, force_update):
    """Retrieve your personal timeline."""
    if source:
        source_obj = ctx.obj["conf"].get_source_by_nick(source)
        if not source_obj:
            logger.debug("Not following {0}, trying as URL".format(source))
            source_obj = Source(source, source)
        sources = [source_obj]
    else:
        sources = ctx.obj["conf"].following

    tweets = []

    if cache:
        try:
            with Cache.discover(update_interval=ctx.obj["conf"].timeline_update_interval) as cache:
                force_update = force_update or not cache.is_valid
                if force_update:
                    tweets = get_remote_tweets(sources, limit, timeout, cache)
                else:
                    logger.debug("Multiple calls to 'timeline' within {0} seconds. Skipping update".format(
                        cache.update_interval))
                    # Behold, almighty list comprehensions! (I might have gone overboard here…)
                    tweets = list(chain.from_iterable([cache.get_tweets(source.url) for source in sources]))
        except OSError as e:
            logger.debug(e)
            tweets = get_remote_tweets(sources, limit, timeout)
    else:
        tweets = get_remote_tweets(sources, limit, timeout)

    if twtfile and not source:
        source = Source(ctx.obj["conf"].nick, ctx.obj["conf"].twturl, file=twtfile)
        tweets.extend(get_local_tweets(source, limit))

    if not tweets:
        return

    tweets = sort_and_truncate_tweets(tweets, sorting, limit)

    if pager:
        click.echo_via_pager(style_timeline(tweets, porcelain))
    else:
        click.echo(style_timeline(tweets, porcelain))

@cli.command()
@click.option("--pager/--no-pager",
              is_flag=True,
              help="Use a pager to display content. (Default: False)")
@click.option("--limit", "-l",
              type=click.INT,
              help="Limit total number of shown tweets. (Default: 20)")
@click.option("--ascending", "sorting",
              flag_value="ascending",
              help="Sort timeline in ascending order.")
@click.option("--descending", "sorting",
              flag_value="descending",
              help="Sort timeline in descending order. (Default)")
@click.option("--timeout",
              type=click.FLOAT,
              help="Maximum time requests are allowed to take. (Default: 5.0)")
@click.option("--porcelain",
              is_flag=True,
              help="Style output in an easy-to-parse format. (Default: False)")
@click.option("--cache/--no-cache",
              is_flag=True,
              help="Cache remote twtxt files locally. (Default: True)")
@click.option("--force-update",
              is_flag=True,
              help="Force update even if cache is up-to-date. (Default: False)")
@click.argument("source")
@click.pass_context
def view(ctx, **kwargs):
    """Show feed of given source."""
    ctx.forward(timeline)

@cli.command()
@click.option("--check/--no-check",
              is_flag=True,
              help="Check if source URL is valid and readable. (Default: True)")
@click.option("--timeout",
              type=click.FLOAT,
              help="Maximum time requests are allowed to take. (Default: 5.0)")
@click.option("--porcelain",
              is_flag=True,
              help="Style output in an easy-to-parse format. (Default: False)")
@click.pass_context
def following(ctx, check, timeout, porcelain):
    """Return the list of sources you’re following."""
    sources = ctx.obj['conf'].following

    if check:
        sources = get_remote_status(sources, timeout)
        for (source, status) in sources:
            click.echo(style_source_with_status(source, status, porcelain))
    else:
        sources = sorted(sources, key=lambda source: source.nick)
        for source in sources:
            click.echo(style_source(source, porcelain))

@cli.command()
@click.argument("nick")
@click.argument("url")
@click.option("--force", "-f",
              flag_value=True,
              help="Force adding and overwriting nick")
@click.pass_context
def follow(ctx, nick, url, force):
    """Add a new source to your followings."""
    source = Source(nick, url)
    sources = ctx.obj['conf'].following

    if not force:
        if source.nick in (source.nick for source in sources):
            click.confirm("➤ You’re already following {0}. Overwrite?".format(
                click.style(source.nick, bold=True)), default=False, abort=True)

        _, status = get_remote_status([source])[0]
        if not status or status.status_code != 200:
            click.confirm("➤ The feed of {0} at {1} is not available. Follow anyway?".format(
                click.style(source.nick, bold=True),
                click.style(source.url, bold=True)), default=False, abort=True)

    ctx.obj['conf'].add_source(source)
    click.echo("✓ You’re now following {0}.".format(
        click.style(source.nick, bold=True)))

@cli.command()
@click.argument("nick")
@click.pass_context
def unfollow(ctx, nick):
    """Remove an existing source from your followings."""
    source = ctx.obj['conf'].get_source_by_nick(nick)

    try:
        with Cache.discover() as cache:
            cache.remove_tweets(source.url)
    except OSError as e:
        logger.debug(e)

    ret_val = ctx.obj['conf'].remove_source_by_nick(nick)
    if ret_val:
        click.echo("✓ You’ve unfollowed {0}.".format(
            click.style(source.nick, bold=True)))
    else:
        click.echo("✗ You’re not following {0}.".format(
            click.style(nick, bold=True)))

@cli.command()
def quickstart():
    """Quickstart wizard for setting up twtxt."""
    width = click.get_terminal_size()[0]
    width = width if width <= 79 else 79

    click.secho("twtxt - quickstart", fg="cyan")
    click.secho("==================", fg="cyan")
    click.echo()

    help_text = "This wizard will generate a basic configuration file for twtxt with all mandatory options set. " \
                "You can change all of these later with either twtxt itself or by editing the config file manually. " \
                "Have a look at the docs to get information about the other available options and their meaning."
    click.echo(textwrap.fill(help_text, width))

    click.echo()
    nick = click.prompt("➤ Please enter your desired nick", default=os.environ.get("USER", ""))

    def overwrite_check(path):
        if os.path.isfile(path):
            click.confirm("➤ '{0}' already exists. Overwrite?".format(path), abort=True)

    cfgfile = click.prompt("➤ Please enter the desired location for your config file",
                           os.path.join(Config.config_dir, Config.config_name),
                           type=click.Path(readable=True, writable=True, file_okay=True))
    cfgfile = os.path.expanduser(cfgfile)
    overwrite_check(cfgfile)

    twtfile = click.prompt("➤ Please enter the desired location for your twtxt file",
                           os.path.expanduser("~/twtxt.txt"),
                           type=click.Path(readable=True, writable=True, file_okay=True))
    twtfile = os.path.expanduser(twtfile)
    overwrite_check(twtfile)

    twturl = click.prompt("➤ Please enter the URL your twtxt file will be accessible from",
                          default="https://example.org/twtxt.txt")

    disclose_identity = click.confirm("➤ Do you want to disclose your identity? Your nick and URL will be shared when "
                                      "making HTTP requests", default=False)

    click.echo()
    add_news = click.confirm("➤ Do you want to follow the twtxt news feed?", default=True)

    conf = Config.create_config(cfgfile, nick, twtfile, twturl, disclose_identity, add_news)

    twtfile_dir = os.path.dirname(twtfile)
    if not os.path.exists(twtfile_dir):
        os.makedirs(twtfile_dir)
    open(twtfile, "a").close()

    click.echo()
    click.echo("✓ Created config file at '{0}'.".format(click.format_filename(conf.config_file)))
    click.echo("✓ Created twtxt file at '{0}'.".format(click.format_filename(twtfile)))

@cli.command()
@click.argument("key", required=False, callback=validate_config_key)
@click.argument("value", required=False)
@click.option("--remove",
              flag_value=True,
              help="Remove given item")
@click.option("--edit", "-e",
              flag_value=True,
              help="Open config file in editor")
@click.pass_context
def config(ctx, key, value, remove, edit):
    """Get or set config item."""
    conf = ctx.obj["conf"]

    if not edit and not key:
        raise click.BadArgumentUsage("You have to specify either a key or use --edit.")

    if edit:
        return click.edit(filename=conf.config_file)

    if remove:
        try:
            conf.cfg.remove_option(key[0], key[1])
        except Exception as e:
            logger.debug(e)
        else:
            conf.write_config()
        return

    if not value:
        try:
            click.echo(conf.cfg.get(key[0], key[1]))
        except Exception as e:
            logger.debug(e)
        return

    if not conf.cfg.has_section(key[0]):
        conf.cfg.add_section(key[0])

    conf.cfg.set(key[0], key[1], value)
    conf.write_config()

main = cli
<commit_msg>Add missing cache_interval argument<commit_after>"""
    twtxt.cli
    ~~~~~~~~~

    This module implements the command-line interface of twtxt.

    :copyright: (c) 2016-2017 by buckket.
    :license: MIT, see LICENSE for more details.
"""

import logging
import os
import sys
import textwrap
from itertools import chain

import click

from twtxt.cache import Cache
from twtxt.config import Config
from twtxt.helper import run_pre_tweet_hook, run_post_tweet_hook
from twtxt.helper import sort_and_truncate_tweets
from twtxt.helper import style_timeline, style_source, style_source_with_status
from twtxt.helper import validate_created_at, validate_text, validate_config_key
from twtxt.log import init_logging
from twtxt.mentions import expand_mentions
from twtxt.models import Tweet, Source
from twtxt.twfile import get_local_tweets, add_local_tweet
from twtxt.twhttp import get_remote_tweets, get_remote_status

logger = logging.getLogger(__name__)

@click.group()
@click.option("--config", "-c",
              type=click.Path(exists=True, file_okay=True, readable=True, writable=True, resolve_path=True),
              help="Specify a custom config file location.")
@click.option("--verbose", "-v",
              is_flag=True, default=False,
              help="Enable verbose output for debugging purposes.")
@click.version_option()
@click.pass_context
def cli(ctx, config, verbose):
    """Decentralised, minimalist microblogging service for hackers."""
    init_logging(debug=verbose)

    if ctx.invoked_subcommand == "quickstart":
        return  # Skip initializing config file

    try:
        if config:
            conf = Config.from_file(config)
        else:
            conf = Config.discover()
    except ValueError as e:
        if "Error in config file." in str(e):
            click.echo("✗ Please correct the errors mentioned above an run twtxt again.")
        else:
            click.echo("✗ Config file not found or not readable. You may want to run twtxt quickstart.")
        sys.exit()

    ctx.default_map = conf.build_default_map()
    ctx.obj = {'conf': conf}

@cli.command()
@click.option("--created-at",
              callback=validate_created_at,
              help="ISO 8601 formatted datetime string to use in Tweet, instead of current time.")
@click.option("--twtfile", "-f",
              type=click.Path(file_okay=True, writable=True, resolve_path=True),
              help="Location of your twtxt file. (Default: twtxt.txt)")
@click.argument("text", callback=validate_text, nargs=-1)
@click.pass_context
def tweet(ctx, created_at, twtfile, text):
    """Append a new tweet to your twtxt file."""
    text = expand_mentions(text)
    tweet = Tweet(text, created_at) if created_at else Tweet(text)

    pre_tweet_hook = ctx.obj["conf"].pre_tweet_hook
    if pre_tweet_hook:
        run_pre_tweet_hook(pre_tweet_hook, ctx.obj["conf"].options)

    if not add_local_tweet(tweet, twtfile):
        click.echo("✗ Couldn’t write to file.")
    else:
        post_tweet_hook = ctx.obj["conf"].post_tweet_hook
        if post_tweet_hook:
            run_post_tweet_hook(post_tweet_hook, ctx.obj["conf"].options)

@cli.command()
@click.option("--pager/--no-pager",
              is_flag=True,
              help="Use a pager to display content. (Default: False)")
@click.option("--limit", "-l",
              type=click.INT,
              help="Limit total number of shown tweets. (Default: 20)")
@click.option("--twtfile", "-f",
              type=click.Path(exists=True, file_okay=True, readable=True, resolve_path=True),
              help="Location of your twtxt file. (Default: twtxt.txt")
@click.option("--ascending", "sorting",
              flag_value="ascending",
              help="Sort timeline in ascending order.")
@click.option("--descending", "sorting",
              flag_value="descending",
              help="Sort timeline in descending order. (Default)")
@click.option("--timeout",
              type=click.FLOAT,
              help="Maximum time requests are allowed to take. (Default: 5.0)")
@click.option("--porcelain",
              is_flag=True,
              help="Style output in an easy-to-parse format. (Default: False)")
@click.option("--source", "-s",
              help="Only show feed of the given source. (Can be nick or URL)")
@click.option("--cache/--no-cache",
              is_flag=True,
              help="Cache remote twtxt files locally. (Default: True)")
@click.option("--force-update",
              is_flag=True,
              help="Force update even if cache is up-to-date. (Default: False)")
@click.pass_context
def timeline(ctx, pager, limit, twtfile, sorting, timeout, porcelain, source, cache, force_update):
    """Retrieve your personal timeline."""
    if source:
        source_obj = ctx.obj["conf"].get_source_by_nick(source)
        if not source_obj:
            logger.debug("Not following {0}, trying as URL".format(source))
            source_obj = Source(source, source)
        sources = [source_obj]
    else:
        sources = ctx.obj["conf"].following

    tweets = []

    if cache:
        try:
            with Cache.discover(update_interval=ctx.obj["conf"].timeline_update_interval) as cache:
                force_update = force_update or not cache.is_valid
                if force_update:
                    tweets = get_remote_tweets(sources, limit, timeout, cache)
                else:
                    logger.debug("Multiple calls to 'timeline' within {0} seconds. Skipping update".format(
                        cache.update_interval))
                    # Behold, almighty list comprehensions! (I might have gone overboard here…)
                    tweets = list(chain.from_iterable([cache.get_tweets(source.url) for source in sources]))
        except OSError as e:
            logger.debug(e)
            tweets = get_remote_tweets(sources, limit, timeout)
    else:
        tweets = get_remote_tweets(sources, limit, timeout)

    if twtfile and not source:
        source = Source(ctx.obj["conf"].nick, ctx.obj["conf"].twturl, file=twtfile)
        tweets.extend(get_local_tweets(source, limit))

    if not tweets:
        return

    tweets = sort_and_truncate_tweets(tweets, sorting, limit)

    if pager:
        click.echo_via_pager(style_timeline(tweets, porcelain))
    else:
        click.echo(style_timeline(tweets, porcelain))

@cli.command()
@click.option("--pager/--no-pager",
              is_flag=True,
              help="Use a pager to display content. (Default: False)")
@click.option("--limit", "-l",
              type=click.INT,
              help="Limit total number of shown tweets. (Default: 20)")
@click.option("--ascending", "sorting",
              flag_value="ascending",
              help="Sort timeline in ascending order.")
@click.option("--descending", "sorting",
              flag_value="descending",
              help="Sort timeline in descending order. (Default)")
@click.option("--timeout",
              type=click.FLOAT,
              help="Maximum time requests are allowed to take. (Default: 5.0)")
@click.option("--porcelain",
              is_flag=True,
              help="Style output in an easy-to-parse format. (Default: False)")
@click.option("--cache/--no-cache",
              is_flag=True,
              help="Cache remote twtxt files locally. (Default: True)")
@click.option("--force-update",
              is_flag=True,
              help="Force update even if cache is up-to-date. (Default: False)")
@click.argument("source")
@click.pass_context
def view(ctx, **kwargs):
    """Show feed of given source."""
    ctx.forward(timeline)

@cli.command()
@click.option("--check/--no-check",
              is_flag=True,
              help="Check if source URL is valid and readable. (Default: True)")
@click.option("--timeout",
              type=click.FLOAT,
              help="Maximum time requests are allowed to take. (Default: 5.0)")
@click.option("--porcelain",
              is_flag=True,
              help="Style output in an easy-to-parse format. (Default: False)")
@click.pass_context
def following(ctx, check, timeout, porcelain):
    """Return the list of sources you’re following."""
    sources = ctx.obj['conf'].following

    if check:
        sources = get_remote_status(sources, timeout)
        for (source, status) in sources:
            click.echo(style_source_with_status(source, status, porcelain))
    else:
        sources = sorted(sources, key=lambda source: source.nick)
        for source in sources:
            click.echo(style_source(source, porcelain))

@cli.command()
@click.argument("nick")
@click.argument("url")
@click.option("--force", "-f",
              flag_value=True,
              help="Force adding and overwriting nick")
@click.pass_context
def follow(ctx, nick, url, force):
    """Add a new source to your followings."""
    source = Source(nick, url)
    sources = ctx.obj['conf'].following

    if not force:
        if source.nick in (source.nick for source in sources):
            click.confirm("➤ You’re already following {0}. Overwrite?".format(
                click.style(source.nick, bold=True)), default=False, abort=True)

        _, status = get_remote_status([source])[0]
        if not status or status.status_code != 200:
            click.confirm("➤ The feed of {0} at {1} is not available. Follow anyway?".format(
                click.style(source.nick, bold=True),
                click.style(source.url, bold=True)), default=False, abort=True)

    ctx.obj['conf'].add_source(source)
    click.echo("✓ You’re now following {0}.".format(
        click.style(source.nick, bold=True)))

@cli.command()
@click.argument("nick")
@click.pass_context
def unfollow(ctx, nick):
    """Remove an existing source from your followings."""
    source = ctx.obj['conf'].get_source_by_nick(nick)

    try:
        with Cache.discover(update_interval=ctx.obj["conf"].timeline_update_interval) as cache:
            cache.remove_tweets(source.url)
    except OSError as e:
        logger.debug(e)

    ret_val = ctx.obj['conf'].remove_source_by_nick(nick)
    if ret_val:
        click.echo("✓ You’ve unfollowed {0}.".format(
            click.style(source.nick, bold=True)))
    else:
        click.echo("✗ You’re not following {0}.".format(
            click.style(nick, bold=True)))

@cli.command()
def quickstart():
    """Quickstart wizard for setting up twtxt."""
    width = click.get_terminal_size()[0]
    width = width if width <= 79 else 79

    click.secho("twtxt - quickstart", fg="cyan")
    click.secho("==================", fg="cyan")
    click.echo()

    help_text = "This wizard will generate a basic configuration file for twtxt with all mandatory options set. " \
                "You can change all of these later with either twtxt itself or by editing the config file manually. " \
                "Have a look at the docs to get information about the other available options and their meaning."
    click.echo(textwrap.fill(help_text, width))

    click.echo()
    nick = click.prompt("➤ Please enter your desired nick", default=os.environ.get("USER", ""))

    def overwrite_check(path):
        if os.path.isfile(path):
            click.confirm("➤ '{0}' already exists. Overwrite?".format(path), abort=True)

    cfgfile = click.prompt("➤ Please enter the desired location for your config file",
                           os.path.join(Config.config_dir, Config.config_name),
                           type=click.Path(readable=True, writable=True, file_okay=True))
    cfgfile = os.path.expanduser(cfgfile)
    overwrite_check(cfgfile)

    twtfile = click.prompt("➤ Please enter the desired location for your twtxt file",
                           os.path.expanduser("~/twtxt.txt"),
                           type=click.Path(readable=True, writable=True, file_okay=True))
    twtfile = os.path.expanduser(twtfile)
    overwrite_check(twtfile)

    twturl = click.prompt("➤ Please enter the URL your twtxt file will be accessible from",
                          default="https://example.org/twtxt.txt")

    disclose_identity = click.confirm("➤ Do you want to disclose your identity? Your nick and URL will be shared when "
                                      "making HTTP requests", default=False)

    click.echo()
    add_news = click.confirm("➤ Do you want to follow the twtxt news feed?", default=True)

    conf = Config.create_config(cfgfile, nick, twtfile, twturl, disclose_identity, add_news)

    twtfile_dir = os.path.dirname(twtfile)
    if not os.path.exists(twtfile_dir):
        os.makedirs(twtfile_dir)
    open(twtfile, "a").close()

    click.echo()
    click.echo("✓ Created config file at '{0}'.".format(click.format_filename(conf.config_file)))
    click.echo("✓ Created twtxt file at '{0}'.".format(click.format_filename(twtfile)))

@cli.command()
@click.argument("key", required=False, callback=validate_config_key)
@click.argument("value", required=False)
@click.option("--remove",
              flag_value=True,
              help="Remove given item")
@click.option("--edit", "-e",
              flag_value=True,
              help="Open config file in editor")
@click.pass_context
def config(ctx, key, value, remove, edit):
    """Get or set config item."""
    conf = ctx.obj["conf"]

    if not edit and not key:
        raise click.BadArgumentUsage("You have to specify either a key or use --edit.")

    if edit:
        return click.edit(filename=conf.config_file)

    if remove:
        try:
            conf.cfg.remove_option(key[0], key[1])
        except Exception as e:
            logger.debug(e)
        else:
            conf.write_config()
        return

    if not value:
        try:
            click.echo(conf.cfg.get(key[0], key[1]))
        except Exception as e:
            logger.debug(e)
        return

    if not conf.cfg.has_section(key[0]):
        conf.cfg.add_section(key[0])

    conf.cfg.set(key[0], key[1], value)
    conf.write_config()

main = cli
<|endoftext|>


<commit_before>#!/usr/bin/env python3

import argparse
import os.path
import re
import sys
from collections import namedtuple
from enum import Enum, unique
from typing import Mapping, Match

import iristk
import java_properties_files
import session_data as sd

PROPERTIES_FILE_ENCODING = "utf-8"

EVENT_LOG_FILENAME_PATTERN = re.compile("events-([^-].).*")
EVENT_LOG_PLAYER_A_INITIAL_ROLE_FORMAT_STRING = '"playerRoles":[["MOVE_SUBMISSION","{}"],'
EVENT_LOG_PLAYER_B_INITIAL_ROLE_FORMAT_STRING = '],["WAITING_FOR_NEXT_MOVE","{}"]]'
EVENT_LOG_PLAYER_INITIAL_ROLE_PATTERN = re.compile(r'.*?\"playerRoles\":\[\["MOVE_SUBMISSION\",\"([^\"]+)"\].*')
EVENT_LOG_PLAYER_ID_FORMAT_STRING = "\"PLAYER_ID\":\"{}\""

SESSION_DIR_FILENAME_FORMAT_STRINGS = ("events-{}.txt", "img-info-{}.txt", "system-{}.log")

RegexReplacementFormatter = namedtuple("RegexReplacementFormatter", "format_str pattern")

@unique
class ScreenshotFilenameFormatter(Enum):
	SELECTION = RegexReplacementFormatter("selection-piece-id{}-{}-{}.png",
										  re.compile("selection-piece-id([^-]+)-([^-]+)-([^\\.]+?).png"))
	TURN = RegexReplacementFormatter("turn-{}-{}-{}.png",
									 re.compile("turn-([^-]+)-([^-]+)-([^\\.]+?).png"))

class SessionAnonymizer(object):
	def __init__(self, initial_player_id: str):
		self.initial_player_id = initial_player_id
		self.screenshot_format_funcs = {
			ScreenshotFilenameFormatter.SELECTION: self.__anonymize_selection_screenshot_filename,
			ScreenshotFilenameFormatter.TURN: self.__anonymize_turn_screenshot_filename}

	def __call__(self, session_dir: str, session_data: sd.SessionData, player_event_log_filenames: Mapping[str, str]):
		self.anonymize_events(session_data)
		self.anonymize_events_metadata(session_data)
		self.anonymize_event_log_files(player_event_log_filenames, session_dir)

		self.rename_player_files(session_dir, player_event_log_filenames)
		self.rename_screenshot_files(os.path.join(session_dir, "screenshots"))

	def __anonymize_selection_screenshot_filename(self, match: Match, format_str: str) -> str:
		img_id = match.group(1)
		timestamp = match.group(2)
		player_id = match.group(3)
		anonymized_participant_id = self.__anonymize_player_id(player_id)
		return format_str.format(img_id, timestamp, anonymized_participant_id)

	def __anonymize_turn_screenshot_filename(self, match: Match, format_str: str) -> str:
		round_id = match.group(1)
		timestamp = match.group(2)
		player_id = match.group(3)
		anonymized_participant_id = self.__anonymize_player_id(player_id)
		return format_str.format(round_id, timestamp, anonymized_participant_id)

	def anonymize_screenshot_filename(self, filename: str) -> str:
		result = filename
		for screenshot_formatter, format_func in self.screenshot_format_funcs.items():
			result, number_of_subs_made = screenshot_formatter.value.pattern.subn(
				lambda match: format_func(match, screenshot_formatter.value.format_str), result, count=1)
			if number_of_subs_made > 0:
				break
		return result

	def anonymize_event_log_files(self, player_event_log_filenames: Mapping[str, str], session_dir: str):
		for player_event_log_filename in player_event_log_filenames.values():
			player_event_log_filepath = os.path.join(session_dir, player_event_log_filename)
			print("Anonymizing event log at \"{}\".".format(player_event_log_filepath))
			anonymized_lines = []
			with open(player_event_log_filepath, 'r', encoding=iristk.LOGFILE_ENCODING) as inf:
				for line in inf:
					anonymized_line = line
					for player_id in player_event_log_filenames:
						anonymized_participant_id = self.__anonymize_player_id(player_id)
						initial_role_log_message_format_str = EVENT_LOG_PLAYER_A_INITIAL_ROLE_FORMAT_STRING if anonymized_participant_id == "A" else EVENT_LOG_PLAYER_B_INITIAL_ROLE_FORMAT_STRING
						initial_role_replacee_pattern = initial_role_log_message_format_str.format(player_id)
						initial_role_replacement_pattern = initial_role_log_message_format_str.format(
							anonymized_participant_id)
						anonymized_line = anonymized_line.replace(initial_role_replacee_pattern,
																  initial_role_replacement_pattern)

						player_id_replacee_pattern = EVENT_LOG_PLAYER_ID_FORMAT_STRING.format(player_id)
						player_id_replacement_pattern = EVENT_LOG_PLAYER_ID_FORMAT_STRING.format(
							anonymized_participant_id)
						anonymized_line = anonymized_line.replace(player_id_replacee_pattern,
																  player_id_replacement_pattern)
					anonymized_lines.append(anonymized_line)

				# with open(player_event_log_filepath, 'w', encoding=iristk.LOGFILE_ENCODING) as outf:
				#	outf.writelines(anonymized_lines)

	def anonymize_events(self, session_data: sd.SessionData):
		print("Anonymizing event tabular data at \"{}\".".format(session_data.events))
		events = session_data.read_events()
		unique_submitter_ids = events["SUBMITTER"].unique()
		if len(unique_submitter_ids) != 2:
			raise ValueError(
				"There were not exactly 2 submitter IDs found in events file at \"{}\": {}".format(session_data.events,
																								   unique_submitter_ids))
		else:
			events["SUBMITTER"] = events["SUBMITTER"].transform(self.__anonymize_player_id)
		# events.to_csv(session_data.events, index=False, sep=csv.excel_tab.delimiter, encoding=sd.ENCODING)

	def anonymize_events_metadata(self, session_data: sd.SessionData):
		print("Anonymizing eventmetadata at \"{}\".".format(session_data.events_metadata))
		events_metadata = session_data.read_events_metadata()
		if sd.EventMetadataRow.INITIAL_INSTRUCTOR_ID.value not in events_metadata:
			raise ValueError(
				"Could not find attribute \"{}\" in \"{}\".".format(sd.EventMetadataRow.INITIAL_INSTRUCTOR_ID.value,
																	session_data.events_metadata))
		else:
			events_metadata[sd.EventMetadataRow.INITIAL_INSTRUCTOR_ID.value] = self.initial_player_id
		# with open(session_data.events_metadata, 'w', encoding=sd.ENCODING) as outf:
		#	writer = csv.writer(outf, dialect=sd.EVENTS_METADATA_CSV_DIALECT)
		# writer.writerows(sorted(events_metadata, key=lambda item: item[0]))

	def rename_player_files(self, session_dir: str, player_event_log_filenames: Mapping[str, str]):
		for player_id in player_event_log_filenames:
			anonymized_participant_id = self.__anonymize_player_id(player_id)
			for format_str in SESSION_DIR_FILENAME_FORMAT_STRINGS:
				old_filepath = os.path.join(session_dir, format_str.format(player_id))
				if os.path.exists(old_filepath):
					anonymized_filepath = os.path.join(session_dir, format_str.format(anonymized_participant_id))
					print("Renaming \"{}\" to \"{}\".".format(old_filepath, anonymized_filepath))
				# os.rename(old_filepath, anonymized_filepath)

	def rename_screenshot_files(self, screenshot_dir: str):
		for file in os.listdir(screenshot_dir):
			old_filepath = os.path.join(screenshot_dir, file)
			anonymized_filename = self.anonymize_screenshot_filename(file)
			anonymized_path = os.path.join(screenshot_dir, anonymized_filename)
			print("Renaming screenshot file \"{}\" to \"{}\".".format(old_filepath, anonymized_path))
		# os.rename(old_filepath, anonymized_filepath)

	def __anonymize_player_id(self, player_id: str) -> str:
		return "A" if player_id == self.initial_player_id else "B"

def parse_initial_player_id(event_log_file: str) -> str:
	result = None
	with open(event_log_file, 'r', encoding=iristk.LOGFILE_ENCODING) as inf:
		for line in inf:
			match = EVENT_LOG_PLAYER_INITIAL_ROLE_PATTERN.match(line)
			if match:
				result = match.group(1)
				break
	if result:
		return result
	else:
		raise ValueError("Could not find initial player ID in file \"{}\".".format(event_log_file))

def __create_argparser() -> argparse.ArgumentParser:
	result = argparse.ArgumentParser(
		description="Anonymize tangram sessions.")
	result.add_argument("inpaths", metavar="INPATH", nargs='+',
						help="The directories to process.")
	return result

def __main(args):
	inpaths = args.inpaths
	print("Looking for session data underneath {}.".format(inpaths))
	for session_dir, session_data in sd.walk_session_data(inpaths):
		session_desc_file = os.path.join(session_dir, "desc.properties")
		print("Reading session properties from \"{}\".".format(session_desc_file))
		with open(session_desc_file, "r", encoding=PROPERTIES_FILE_ENCODING) as inf:
			props = java_properties_files.parse_properties(inf)
			canonical_event_log_filename = props["canonicalEvents"]
			player_data = props["player"]
			player_event_log_filenames = {}
			for player_datum in player_data.values():
				player_id = sys.intern(player_datum["id"])
				player_event_log_filename = player_datum["eventLog"]
				player_event_log_filenames[player_id] = player_event_log_filename

		if len(player_event_log_filenames) != 2:
			raise ValueError("Not exactly two players described in file \"{}\".".format(session_desc_file))

		event_log_file = os.path.join(session_dir, canonical_event_log_filename)
		initial_player_id = sys.intern(parse_initial_player_id(event_log_file))
		anonymizer = SessionAnonymizer(initial_player_id)
		anonymizer(session_dir, session_data, player_event_log_filenames)

if __name__ == "__main__":
	__main(__create_argparser().parse_args())
<commit_msg>now anonymizing "game_start*" screenshots<commit_after>#!/usr/bin/env python3

import argparse
import os.path
import re
import sys
from collections import namedtuple
from enum import Enum, unique
from typing import Mapping, Match

import iristk
import java_properties_files
import session_data as sd

PROPERTIES_FILE_ENCODING = "utf-8"

EVENT_LOG_FILENAME_PATTERN = re.compile("events-([^-].).*")
EVENT_LOG_PLAYER_A_INITIAL_ROLE_FORMAT_STRING = '"playerRoles":[["MOVE_SUBMISSION","{}"],'
EVENT_LOG_PLAYER_B_INITIAL_ROLE_FORMAT_STRING = '],["WAITING_FOR_NEXT_MOVE","{}"]]'
EVENT_LOG_PLAYER_INITIAL_ROLE_PATTERN = re.compile(r'.*?\"playerRoles\":\[\["MOVE_SUBMISSION\",\"([^\"]+)"\].*')
EVENT_LOG_PLAYER_ID_FORMAT_STRING = "\"PLAYER_ID\":\"{}\""

SESSION_DIR_FILENAME_FORMAT_STRINGS = ("events-{}.txt", "img-info-{}.txt", "system-{}.log")

RegexReplacementFormatter = namedtuple("RegexReplacementFormatter", "format_str pattern")

@unique
class ScreenshotFilenameFormatter(Enum):
	GAME_START = RegexReplacementFormatter("game-start-{}-{}.png",
										   re.compile("game-start-([^-]+)-([^\\.]+?).png"))
	SELECTION = RegexReplacementFormatter("selection-piece-id{}-{}-{}.png",
										  re.compile("selection-piece-id([^-]+)-([^-]+)-([^\\.]+?).png"))
	TURN = RegexReplacementFormatter("turn-{}-{}-{}.png",
									 re.compile("turn-([^-]+)-([^-]+)-([^\\.]+?).png"))

class SessionAnonymizer(object):
	def __init__(self, initial_player_id: str):
		self.initial_player_id = initial_player_id
		self.screenshot_format_funcs = {
			ScreenshotFilenameFormatter.GAME_START: self.__anonymize_game_start_screenshot_filename,
			ScreenshotFilenameFormatter.SELECTION: self.__anonymize_selection_screenshot_filename,
			ScreenshotFilenameFormatter.TURN: self.__anonymize_turn_screenshot_filename}
		assert len(self.screenshot_format_funcs) == len(ScreenshotFilenameFormatter)

	def __call__(self, session_dir: str, session_data: sd.SessionData, player_event_log_filenames: Mapping[str, str]):
		self.anonymize_events(session_data)
		self.anonymize_events_metadata(session_data)
		self.anonymize_event_log_files(player_event_log_filenames, session_dir)

		self.rename_player_files(session_dir, player_event_log_filenames)
		self.rename_screenshot_files(os.path.join(session_dir, "screenshots"))

	def __anonymize_game_start_screenshot_filename(self, match: Match, format_str: str) -> str:
		timestamp = match.group(1)
		player_id = match.group(2)
		anonymized_participant_id = self.__anonymize_player_id(player_id)
		return format_str.format(timestamp, anonymized_participant_id)

	def __anonymize_selection_screenshot_filename(self, match: Match, format_str: str) -> str:
		img_id = match.group(1)
		timestamp = match.group(2)
		player_id = match.group(3)
		anonymized_participant_id = self.__anonymize_player_id(player_id)
		return format_str.format(img_id, timestamp, anonymized_participant_id)

	def __anonymize_turn_screenshot_filename(self, match: Match, format_str: str) -> str:
		round_id = match.group(1)
		timestamp = match.group(2)
		player_id = match.group(3)
		anonymized_participant_id = self.__anonymize_player_id(player_id)
		return format_str.format(round_id, timestamp, anonymized_participant_id)

	def anonymize_screenshot_filename(self, filename: str) -> str:
		result = filename
		for screenshot_formatter, format_func in self.screenshot_format_funcs.items():
			result, number_of_subs_made = screenshot_formatter.value.pattern.subn(
				lambda match: format_func(match, screenshot_formatter.value.format_str), result, count=1)
			if number_of_subs_made > 0:
				break
		return result

	def anonymize_event_log_files(self, player_event_log_filenames: Mapping[str, str], session_dir: str):
		for player_event_log_filename in player_event_log_filenames.values():
			player_event_log_filepath = os.path.join(session_dir, player_event_log_filename)
			print("Anonymizing event log at \"{}\".".format(player_event_log_filepath))
			anonymized_lines = []
			with open(player_event_log_filepath, 'r', encoding=iristk.LOGFILE_ENCODING) as inf:
				for line in inf:
					anonymized_line = line
					for player_id in player_event_log_filenames:
						anonymized_participant_id = self.__anonymize_player_id(player_id)
						initial_role_log_message_format_str = EVENT_LOG_PLAYER_A_INITIAL_ROLE_FORMAT_STRING if anonymized_participant_id == "A" else EVENT_LOG_PLAYER_B_INITIAL_ROLE_FORMAT_STRING
						initial_role_replacee_pattern = initial_role_log_message_format_str.format(player_id)
						initial_role_replacement_pattern = initial_role_log_message_format_str.format(
							anonymized_participant_id)
						anonymized_line = anonymized_line.replace(initial_role_replacee_pattern,
																  initial_role_replacement_pattern)

						player_id_replacee_pattern = EVENT_LOG_PLAYER_ID_FORMAT_STRING.format(player_id)
						player_id_replacement_pattern = EVENT_LOG_PLAYER_ID_FORMAT_STRING.format(
							anonymized_participant_id)
						anonymized_line = anonymized_line.replace(player_id_replacee_pattern,
																  player_id_replacement_pattern)
					anonymized_lines.append(anonymized_line)

				# with open(player_event_log_filepath, 'w', encoding=iristk.LOGFILE_ENCODING) as outf:
				#	outf.writelines(anonymized_lines)

	def anonymize_events(self, session_data: sd.SessionData):
		print("Anonymizing event tabular data at \"{}\".".format(session_data.events))
		events = session_data.read_events()
		unique_submitter_ids = events["SUBMITTER"].unique()
		if len(unique_submitter_ids) != 2:
			raise ValueError(
				"There were not exactly 2 submitter IDs found in events file at \"{}\": {}".format(session_data.events,
																								   unique_submitter_ids))
		else:
			events["SUBMITTER"] = events["SUBMITTER"].transform(self.__anonymize_player_id)
		# events.to_csv(session_data.events, index=False, sep=csv.excel_tab.delimiter, encoding=sd.ENCODING)

	def anonymize_events_metadata(self, session_data: sd.SessionData):
		print("Anonymizing eventmetadata at \"{}\".".format(session_data.events_metadata))
		events_metadata = session_data.read_events_metadata()
		if sd.EventMetadataRow.INITIAL_INSTRUCTOR_ID.value not in events_metadata:
			raise ValueError(
				"Could not find attribute \"{}\" in \"{}\".".format(sd.EventMetadataRow.INITIAL_INSTRUCTOR_ID.value,
																	session_data.events_metadata))
		else:
			events_metadata[sd.EventMetadataRow.INITIAL_INSTRUCTOR_ID.value] = self.initial_player_id
		# with open(session_data.events_metadata, 'w', encoding=sd.ENCODING) as outf:
		#	writer = csv.writer(outf, dialect=sd.EVENTS_METADATA_CSV_DIALECT)
		# writer.writerows(sorted(events_metadata, key=lambda item: item[0]))

	def rename_player_files(self, session_dir: str, player_event_log_filenames: Mapping[str, str]):
		for player_id in player_event_log_filenames:
			anonymized_participant_id = self.__anonymize_player_id(player_id)
			for format_str in SESSION_DIR_FILENAME_FORMAT_STRINGS:
				old_filepath = os.path.join(session_dir, format_str.format(player_id))
				if os.path.exists(old_filepath):
					anonymized_filepath = os.path.join(session_dir, format_str.format(anonymized_participant_id))
					print("Renaming \"{}\" to \"{}\".".format(old_filepath, anonymized_filepath))
				# os.rename(old_filepath, anonymized_filepath)

	def rename_screenshot_files(self, screenshot_dir: str):
		for file in os.listdir(screenshot_dir):
			old_filepath = os.path.join(screenshot_dir, file)
			anonymized_filename = self.anonymize_screenshot_filename(file)
			anonymized_path = os.path.join(screenshot_dir, anonymized_filename)
			print("Renaming screenshot file \"{}\" to \"{}\".".format(old_filepath, anonymized_path))
		# os.rename(old_filepath, anonymized_filepath)

	def __anonymize_player_id(self, player_id: str) -> str:
		return "A" if player_id == self.initial_player_id else "B"

def parse_initial_player_id(event_log_file: str) -> str:
	result = None
	with open(event_log_file, 'r', encoding=iristk.LOGFILE_ENCODING) as inf:
		for line in inf:
			match = EVENT_LOG_PLAYER_INITIAL_ROLE_PATTERN.match(line)
			if match:
				result = match.group(1)
				break
	if result:
		return result
	else:
		raise ValueError("Could not find initial player ID in file \"{}\".".format(event_log_file))

def __create_argparser() -> argparse.ArgumentParser:
	result = argparse.ArgumentParser(
		description="Anonymize tangram sessions.")
	result.add_argument("inpaths", metavar="INPATH", nargs='+',
						help="The directories to process.")
	return result

def __main(args):
	inpaths = args.inpaths
	print("Looking for session data underneath {}.".format(inpaths))
	for session_dir, session_data in sd.walk_session_data(inpaths):
		session_desc_file = os.path.join(session_dir, "desc.properties")
		print("Reading session properties from \"{}\".".format(session_desc_file))
		with open(session_desc_file, "r", encoding=PROPERTIES_FILE_ENCODING) as inf:
			props = java_properties_files.parse_properties(inf)
			canonical_event_log_filename = props["canonicalEvents"]
			player_data = props["player"]
			player_event_log_filenames = {}
			for player_datum in player_data.values():
				player_id = sys.intern(player_datum["id"])
				player_event_log_filename = player_datum["eventLog"]
				player_event_log_filenames[player_id] = player_event_log_filename

		if len(player_event_log_filenames) != 2:
			raise ValueError("Not exactly two players described in file \"{}\".".format(session_desc_file))

		event_log_file = os.path.join(session_dir, canonical_event_log_filename)
		initial_player_id = sys.intern(parse_initial_player_id(event_log_file))
		anonymizer = SessionAnonymizer(initial_player_id)
		anonymizer(session_dir, session_data, player_event_log_filenames)

if __name__ == "__main__":
	__main(__create_argparser().parse_args())
<|endoftext|>


<commit_before># Copyright (c) 2013-2016 Molly White
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# NOTE/CAUTION/WARNING:
#
# This file is used to store offensive terms so that triggering or offensive
# headlines are not generated by CyberPrefixer. Accordingly, it contains a lot
# of terms that are triggering, be they sexual, violent or otherwise a problem.

import re

offensive = re.compile(
    r"\b(deaths?|dead(ly)?|die(s|d)?|hurts?|(sex(ual)?|child)[ -]?(abuse|trafficking|assault)|"
    r"injur(e|i?es|ed|y)|kill(ing|ed|er|s)?s?|wound(ing|ed|s)?|fatal(ly|ity)?|shoo?t(s|ing|er)?s?|"
    r"crash(es|ed|ing)?|attack(s|ers?|ing|ed)?|murder(s|er|ed|ing)?s?|hostages?|rap(e|es|ed|ist|ists|ing)|"
    r"assault(s|ed)?|pile-?ups?|massacre(s|d)?|assassinate(d|s)?|sla(y|in|yed|ys|ying|yings)|"
    r"victims?|tortur(e|ed|ing|es)|execut(e|ion|ed|ioner)s?|gun(man|men|ned)|suicid(e|al|es)|"
    r"bomb(s|ed|ing|ings|er|ers)?|mass[- ]?graves?|bloodshed|state[- ]?of[- ]?emergency|"
    r"al[- ]?Qaeda|blasts?|violen(t|ce)|lethal|cancer(ous)?|stab(bed|bing|ber)?s?|casualt(y|ies)|"
    r"sla(y|ying|yer|in)|drown(s|ing|ed|ings)?|bod(y|ies)|kidnap(s|ped|per|pers|ping|pings)?|"
    r"rampage|beat(ings?|en)|terminal(ly)?|abduct(s|ed|ion)?s?|missing|behead(s|ed|ings?)?|"
    r"homicid(e|es|al)|burn(s|ed|ing)? alive|decapitated?s?|jihadi?s?t?|hang(ed|ing|s)?|"
    r"funerals?|traged(y|ies)|autops(y|ies)|child sex|sob(s|bing|bed)?)\W?\b",
    flags=re.IGNORECASE)

def tact(headline):
    # Avoid producing particularly tactless tweets
    if re.search(offensive, headline) is None:
        return True
    else:
        return False
<commit_msg>Highlight TW, reword a bit<commit_after># Copyright (c) 2013-2016 Molly White
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

#==============================================================================
# NOTE/CAUTION/WARNING:
#
# This file is used to store offensive terms so that triggering or offensive
# headlines are not satirized by CyberPrefixer and other bots. Accordingly, it
# contains a lot of terms that are triggering, be they sexual, violent or
# otherwise.
#==============================================================================

import re

offensive = re.compile(
    r"\b(deaths?|dead(ly)?|die(s|d)?|hurts?|(sex(ual)?|child)[ -]?(abuse|trafficking|assault)|"
    r"injur(e|i?es|ed|y)|kill(ing|ed|er|s)?s?|wound(ing|ed|s)?|fatal(ly|ity)?|shoo?t(s|ing|er)?s?|"
    r"crash(es|ed|ing)?|attack(s|ers?|ing|ed)?|murder(s|er|ed|ing)?s?|hostages?|rap(e|es|ed|ist|ists|ing)|"
    r"assault(s|ed)?|pile-?ups?|massacre(s|d)?|assassinate(d|s)?|sla(y|in|yed|ys|ying|yings)|"
    r"victims?|tortur(e|ed|ing|es)|execut(e|ion|ed|ioner)s?|gun(man|men|ned)|suicid(e|al|es)|"
    r"bomb(s|ed|ing|ings|er|ers)?|mass[- ]?graves?|bloodshed|state[- ]?of[- ]?emergency|"
    r"al[- ]?Qaeda|blasts?|violen(t|ce)|lethal|cancer(ous)?|stab(bed|bing|ber)?s?|casualt(y|ies)|"
    r"sla(y|ying|yer|in)|drown(s|ing|ed|ings)?|bod(y|ies)|kidnap(s|ped|per|pers|ping|pings)?|"
    r"rampage|beat(ings?|en)|terminal(ly)?|abduct(s|ed|ion)?s?|missing|behead(s|ed|ings?)?|"
    r"homicid(e|es|al)|burn(s|ed|ing)? alive|decapitated?s?|jihadi?s?t?|hang(ed|ing|s)?|"
    r"funerals?|traged(y|ies)|autops(y|ies)|child sex|sob(s|bing|bed)?)\W?\b",
    flags=re.IGNORECASE)

def tact(headline):
    # Avoid producing particularly tactless tweets
    if re.search(offensive, headline) is None:
        return True
    else:
        return False
<|endoftext|>


<commit_before>from __future__ import absolute_import

import logging
import os
import select
import socket
import sys
import termios
import time
import tty

from novaconsole.exc import *

try:
    import websocket
except ImportError:
    logging.fatal('This package requires the "websocket" module.')
    logging.fatal('See http://pypi.python.org/pypi/websocket-client for '
                  'more information.')
    sys.exit()

class Client (object):
    def __init__(self, url,
                 escape='~',
                 close_wait=0.5):
        self.url = url
        self.escape = escape
        self.close_wait = close_wait

        self.setup_logging()
        self.connect()

    def setup_logging(self):
        self.log = logging.getLogger('novaconsole.client')

    def connect(self):
        self.log.debug('connecting to: %s', self.url)
        try:
            self.ws = websocket.create_connection(
                self.url,
                subprotocols=['binary', 'base64'])
            self.log.warn('connected to: %s', self.url)
            self.log.warn('type "%s." to disconnect',
                          self.escape)
        except socket.error as e:
            raise ConnectionFailed(e)
        except websocket.WebSocketConnectionClosedException as e:
            raise ConnectionFailed(e)

    def start_loop(self):
        self.poll = select.poll()
        self.poll.register(sys.stdin,
                           select.POLLIN|select.POLLHUP|select.POLLPRI)
        self.poll.register(self.ws,
                           select.POLLIN|select.POLLHUP|select.POLLPRI)

        self.start_of_line = False
        self.read_escape = False

        try:
            self.setup_tty()
            self.run_forever()

        # here we attempt to translate the exceptions generated
        # by various modules into novaconsole exceptions, so that
        # client code does not need to import modules from all
        # over the place.
        except socket.error as e:
            raise ConnectionFailed(e)
        except websocket.WebSocketConnectionClosedException as e:
            raise Disconnected(e)
        finally:
            self.restore_tty()

    def run_forever(self):
        self.log.debug('starting main client loop')
        self.quit = False
        quitting = False
        when = None

        while True:
            for fd, event in self.poll.poll(0.5):
                if fd == self.ws.fileno():
                    self.handle_websocket(event)
                elif fd == sys.stdin.fileno():
                    self.handle_stdin(event)

            if self.quit and not quitting:
                self.log.debug('entering close_wait')
                quitting = True
                when = time.time() + self.close_wait

            if quitting and time.time() > when:
                self.log.debug('quitting')
                break

    def setup_tty(self):
        if os.isatty(sys.stdin.fileno()):
            self.log.debug('putting tty into raw mode')
            self.old_settings = termios.tcgetattr(sys.stdin)
            tty.setraw(sys.stdin)

    def restore_tty(self):
        if os.isatty(sys.stdin.fileno()):
            self.log.debug('restoring tty configuration')
            termios.tcsetattr(sys.stdin, termios.TCSADRAIN,
                              self.old_settings)

    def handle_stdin(self, event):
        if event in (select.POLLHUP, select.POLLNVAL):
            self.log.debug('event %d on stdin', event)

            self.log.debug('eof on stdin')
            self.poll.unregister(sys.stdin)
            self.quit = True

        data = os.read(sys.stdin.fileno(), 1024)
        self.log.debug('read %s (%d bytes) from stdin',
                       repr(data),
                       len(data))

        if not data:
            return

        if self.start_of_line and data == self.escape:
            self.read_escape = True
            return

        if self.read_escape and data == '.':
            self.log.debug('exit on escape code')
            raise UserExit()
        elif self.read_escape:
            self.read_escape = False
            self.ws.send(self.escape)

        self.ws.send(data)

        if data == '\r':
            self.start_of_line = True
        else:
            self.start_of_line = False

    def handle_websocket(self, event):
        if event in (select.POLLHUP, select.POLLNVAL):
            self.log.debug('event %d on websocket', event)

            self.log.debug('eof on websocket')
            self.poll.unregister(self.ws)
            self.quit = True

        data = self.ws.recv()
        self.log.debug('read %s (%d bytes) from websocket',
                       repr(data),
                       len(data))
        if not data:
            return

        sys.stdout.write(data)
        sys.stdout.flush()
<commit_msg>fix erroneous timeout on poll()<commit_after>from __future__ import absolute_import

import logging
import os
import select
import socket
import sys
import termios
import time
import tty

from novaconsole.exc import *

try:
    import websocket
except ImportError:
    logging.fatal('This package requires the "websocket" module.')
    logging.fatal('See http://pypi.python.org/pypi/websocket-client for '
                  'more information.')
    sys.exit()

class Client (object):
    def __init__(self, url,
                 escape='~',
                 close_wait=0.5):
        self.url = url
        self.escape = escape
        self.close_wait = close_wait

        self.setup_logging()
        self.connect()

    def setup_logging(self):
        self.log = logging.getLogger('novaconsole.client')

    def connect(self):
        self.log.debug('connecting to: %s', self.url)
        try:
            self.ws = websocket.create_connection(
                self.url,
                subprotocols=['binary', 'base64'])
            self.log.warn('connected to: %s', self.url)
            self.log.warn('type "%s." to disconnect',
                          self.escape)
        except socket.error as e:
            raise ConnectionFailed(e)
        except websocket.WebSocketConnectionClosedException as e:
            raise ConnectionFailed(e)

    def start_loop(self):
        self.poll = select.poll()
        self.poll.register(sys.stdin,
                           select.POLLIN|select.POLLHUP|select.POLLPRI)
        self.poll.register(self.ws,
                           select.POLLIN|select.POLLHUP|select.POLLPRI)

        self.start_of_line = False
        self.read_escape = False

        try:
            self.setup_tty()
            self.run_forever()

        # here we attempt to translate the exceptions generated
        # by various modules into novaconsole exceptions, so that
        # client code does not need to import modules from all
        # over the place.
        except socket.error as e:
            raise ConnectionFailed(e)
        except websocket.WebSocketConnectionClosedException as e:
            raise Disconnected(e)
        finally:
            self.restore_tty()

    def run_forever(self):
        self.log.debug('starting main client loop')
        self.quit = False
        quitting = False
        when = None

        while True:
            for fd, event in self.poll.poll(500):
                if fd == self.ws.fileno():
                    self.handle_websocket(event)
                elif fd == sys.stdin.fileno():
                    self.handle_stdin(event)

            if self.quit and not quitting:
                self.log.debug('entering close_wait')
                quitting = True
                when = time.time() + self.close_wait

            if quitting and time.time() > when:
                self.log.debug('quitting')
                break

    def setup_tty(self):
        if os.isatty(sys.stdin.fileno()):
            self.log.debug('putting tty into raw mode')
            self.old_settings = termios.tcgetattr(sys.stdin)
            tty.setraw(sys.stdin)

    def restore_tty(self):
        if os.isatty(sys.stdin.fileno()):
            self.log.debug('restoring tty configuration')
            termios.tcsetattr(sys.stdin, termios.TCSADRAIN,
                              self.old_settings)

    def handle_stdin(self, event):
        if event in (select.POLLHUP, select.POLLNVAL):
            self.log.debug('event %d on stdin', event)

            self.log.debug('eof on stdin')
            self.poll.unregister(sys.stdin)
            self.quit = True

        data = os.read(sys.stdin.fileno(), 1024)
        self.log.debug('read %s (%d bytes) from stdin',
                       repr(data),
                       len(data))

        if not data:
            return

        if self.start_of_line and data == self.escape:
            self.read_escape = True
            return

        if self.read_escape and data == '.':
            self.log.debug('exit on escape code')
            raise UserExit()
        elif self.read_escape:
            self.read_escape = False
            self.ws.send(self.escape)

        self.ws.send(data)

        if data == '\r':
            self.start_of_line = True
        else:
            self.start_of_line = False

    def handle_websocket(self, event):
        if event in (select.POLLHUP, select.POLLNVAL):
            self.log.debug('event %d on websocket', event)

            self.log.debug('eof on websocket')
            self.poll.unregister(self.ws)
            self.quit = True

        data = self.ws.recv()
        self.log.debug('read %s (%d bytes) from websocket',
                       repr(data),
                       len(data))
        if not data:
            return

        sys.stdout.write(data)
        sys.stdout.flush()
<|endoftext|>


<commit_before>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Compilertools Sphinx documentation"""

from os.path import abspath, dirname
import sys
sys.path.insert(0, abspath(dirname(dirname(__file__))))

# -- General configuration ------------------------------------------------
from setup import PACKAGE_INFO
SPHINX_INFO = PACKAGE_INFO['command_options']['build_sphinx']

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.napoleon',
    'sphinx.ext.coverage',
    'sphinx.ext.viewcode',
    'sphinx.ext.githubpages']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = SPHINX_INFO['project']
copyright = SPHINX_INFO['copyright']
author = PACKAGE_INFO['author']

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.

# The short X.Y version.
version = SPHINX_INFO['version']
# The full version, including alpha/beta/rc tags.
release = SPHINX_INFO['release']

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'default'

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'sphinx_rtd_theme'

# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, '%s.tex' % project, '%s Documentation' % project,
     author, 'manual'),
]

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, PACKAGE_INFO['name'], '%s Documentation' % project,
     [author], 1)
]

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (master_doc, project, '%s Documentation' % project,
     author, project, PACKAGE_INFO['description'],
     'Miscellaneous'),
]
<commit_msg>Fix sphinx conf.py<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Compilertools Sphinx documentation"""

from os.path import abspath, dirname
import sys
sys.path.insert(0, abspath(dirname(dirname(__file__))))

# -- General configuration ------------------------------------------------
from setup import PACKAGE_INFO
SPHINX_INFO = PACKAGE_INFO['command_options']['build_sphinx']

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.napoleon',
    'sphinx.ext.coverage',
    'sphinx.ext.viewcode',
    'sphinx.ext.githubpages']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = SPHINX_INFO['project'][1]
copyright = SPHINX_INFO['copyright'][1]
author = PACKAGE_INFO['author']

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.

# The short X.Y version.
version = SPHINX_INFO['version'][1]
# The full version, including alpha/beta/rc tags.
release = SPHINX_INFO['release'][1]

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'default'

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'sphinx_rtd_theme'

# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, '%s.tex' % project, '%s Documentation' % project,
     author, 'manual'),
]

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, PACKAGE_INFO['name'], '%s Documentation' % project,
     [author], 1)
]

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (master_doc, project, '%s Documentation' % project,
     author, project, PACKAGE_INFO['description'],
     'Miscellaneous'),
]
<|endoftext|>