<commit_before>"""Run all benchmarks."""

# TODO: ensure compile() compiles included templates too, or at least that it's the same for everyone (render too)

from __future__ import with_statement

import collections
import os
import sys
import timeit
import warnings

BlogEntry = collections.namedtuple('BlogEntry', 'title url html_body')

TITLE = 'My Blog'
ENTRIES = [
    BlogEntry(u'<Sorry>', u'/sorry/?a=b&c=d', u'
Sorry for the lack of updates.
'),
    BlogEntry(u'My life & story', None, u'
Once upon a time...
'),
    BlogEntry(u'First \u201cpost\u201d', u'/first-post/', u'
This is the first post.
'),
]
ENTRIES *= 10  # To give the render test a bit more to chew on

def rel_dir(dirname):
    """Return full directory name of dirname from this file's directory."""
    return os.path.abspath(os.path.join(os.path.dirname(__file__), dirname))

class TemplateLanguage(object):
    num_compiles = 10
    num_renders = 100

    def setup_compile(self):
        pass

    def compile(self):
        raise NotImplementedError

    def setup_render(self):
        pass

    def render(self):
        raise NotImplementedError

    def benchmark(self):
        timings = {}
        self.setup_compile()
        timings['compile'] = min(timeit.repeat(self.compile, number=self.num_compiles)) / float(self.num_compiles)
        self.setup_render()
        timings['render'] = min(timeit.repeat(self.render, number=self.num_renders)) / float(self.num_renders)
        return timings

try:
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
    import symplate
except ImportError:
    warnings.warn("Can't import symplate, is it in your PYTHONPATH?")
    symplate = None
if symplate:
    class Symplate(TemplateLanguage):
        def __init__(self):
            self.renderer = symplate.Renderer(
                    rel_dir('symplate'),
                    output_dir=rel_dir('symplate_output'),
                    modify_path=False)

        def compile(self):
            return self.renderer.compile('main')

        def render(self):
            return self.renderer.render('main', title=TITLE, entries=ENTRIES)

try:
    import Cheetah.Template as cheetah
    from Cheetah.Filters import WebSafe as cheetah_websafe
except ImportError:
    warnings.warn("Can't import Cheetah, is it in your PYTHONPATH?")
    cheetah = None
if cheetah:
    cheetah.checkFileMtime(False)

    class Cheetah(TemplateLanguage):
        def __init__(self):
            self.template_dir = rel_dir('cheetah')

        def compile(self):
            file_name = os.path.join(self.template_dir, 'main.tmpl')
            return cheetah.Template.compile(
                    file=file_name, cacheCompilationResults=False, useCache=False)

        def setup_render(self):
            self.template = self.compile()

        def render(self):
            params = dict(title=TITLE, entries=ENTRIES, template_dir=self.template_dir)
            return self.template(searchList=[params], filter=cheetah_websafe).respond()

try:
    import jinja2
except ImportError:
    warnings.warn("Can't import jinja2, is it in your PYTHONPATH?")
    jinja2 = None
if jinja2:
    class Jinja2(TemplateLanguage):
        def __init__(self):
            loader = jinja2.FileSystemLoader(rel_dir('jinja2'))
            self.compile_env = jinja2.Environment(loader=loader, autoescape=True, cache_size=0, trim_blocks=True)
            self.render_env = jinja2.Environment(loader=loader, autoescape=True, trim_blocks=True)

        def compile(self):
            return self.compile_env.get_template('main.tmpl')

        def setup_render(self):
            self.template = self.render_env.get_template('main.tmpl')

        def render(self):
            return self.template.render(title=TITLE, entries=ENTRIES)

try:
    import mako.lookup
except ImportError:
    warnings.warn("Can't import mako, is it in your PYTHONPATH?")
    mako = None
if mako:
    class Mako(TemplateLanguage):
        def __init__(self):
            self.compile_lookup = mako.lookup.TemplateLookup(
                    directories=[rel_dir('mako')],
                    default_filters=['h'],
                    collection_size=0)
            self.render_lookup = mako.lookup.TemplateLookup(
                    directories=[rel_dir('mako')],
                    default_filters=['h'])

        def compile(self):
            return self.compile_lookup.get_template('main.tmpl')

        def setup_render(self):
            self.template = self.render_lookup.get_template('main.tmpl')

        def render(self):
            return self.template.render(title=TITLE, entries=ENTRIES)

try:
    import wheezy.template
except OSError:
    warnings.warn("Can't import wheezy.template, is it in your PYTHONPATH?")
    wheezy = None
if wheezy:
    # include this here so there's not a dependency on wheezy.html
    def wheezy_escape_html(s):
        return s.replace('&', '&').replace('<', '<').replace(
                         '>', '>').replace('"', '"')

    class Wheezy(TemplateLanguage):
        def __init__(self):
            self.engine = wheezy.template.Engine(
                    loader=wheezy.template.FileLoader([rel_dir('wheezy')]),
                    extensions=[wheezy.template.CoreExtension()])
            self.engine.global_vars.update({'h': wheezy_escape_html})

        def compile(self):
            self.engine.remove('main.tmpl')
            self.engine.remove('header.tmpl')
            self.engine.remove('footer.tmpl')
            return self.engine.get_template('main.tmpl')

        def setup_render(self):
            self.template = self.engine.get_template('main.tmpl')

        def render(self):
            return self.template.render({'title': TITLE, 'entries': ENTRIES})

def main():
    language_classes = [(name, cls) for name, cls in globals().items()
                        if isinstance(cls, type) and
                           issubclass(cls, TemplateLanguage) and
                           cls is not TemplateLanguage]

    results = []
    output = None
    for name, cls in language_classes:
        language = cls()
        timings = language.benchmark()
        results.append((name, timings['compile'], timings['render']))

        output_dir = rel_dir('output')
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        with open(os.path.join(output_dir, name.lower() + '.html'), 'wb') as f:
            language.setup_render()
            rendering = language.render().replace('\r\n', '\n')
            f.write(rendering.encode('utf-8'))
            if output is None:
                output = (name, rendering.strip())
            elif output[1] != rendering.strip():
                print 'ERROR: output from %s and %s differ' % (name, output[0])

    print 'Engine    compile (ms)  render (ms)'
    print '-----------------------------------'
    for name, compile_time, render_time in sorted(results, key=lambda r: r[2]):
        print '%-10s %11.3f %12.3f' % (name, compile_time * 1000, render_time * 1000)

if __name__ == '__main__':
    main()<commit_msg>Made run_benchmarks compile included templates too so it's the same for everyone<commit_after>"""Run all benchmarks."""

from __future__ import with_statement

import collections
import os
import sys
import timeit
import warnings

BlogEntry = collections.namedtuple('BlogEntry', 'title url html_body')

TITLE = 'My Blog'
ENTRIES = [
    BlogEntry(u'<Sorry>', u'/sorry/?a=b&c=d', u'
Sorry for the lack of updates.
'),
    BlogEntry(u'My life & story', None, u'
Once upon a time...
'),
    BlogEntry(u'First \u201cpost\u201d', u'/first-post/', u'
This is the first post.
'),
]
ENTRIES *= 10  # To give the render test a bit more to chew on

def rel_dir(dirname):
    """Return full directory name of dirname from this file's directory."""
    return os.path.abspath(os.path.join(os.path.dirname(__file__), dirname))

class TemplateLanguage(object):
    num_compiles = 10
    num_renders = 100

    def setup_compile(self):
        pass

    def compile(self):
        raise NotImplementedError

    def setup_render(self):
        pass

    def render(self):
        raise NotImplementedError

    def benchmark(self):
        timings = {}
        self.setup_compile()
        timings['compile'] = min(timeit.repeat(self.compile, number=self.num_compiles)) / float(self.num_compiles)
        self.setup_render()
        timings['render'] = min(timeit.repeat(self.render, number=self.num_renders)) / float(self.num_renders)
        return timings

try:
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
    import symplate
except ImportError:
    warnings.warn("Can't import symplate, is it in your PYTHONPATH?")
    symplate = None
if symplate:
    class Symplate(TemplateLanguage):
        def __init__(self):
            self.renderer = symplate.Renderer(
                    rel_dir('symplate'),
                    output_dir=rel_dir('symplate_output'),
                    modify_path=False)

        def compile(self):
            for name in ('header', 'main', 'footer'):
                self.renderer.compile(name)

        def render(self):
            return self.renderer.render('main', title=TITLE, entries=ENTRIES)

try:
    import Cheetah.Template as cheetah
    from Cheetah.Filters import WebSafe as cheetah_websafe
except ImportError:
    warnings.warn("Can't import Cheetah, is it in your PYTHONPATH?")
    cheetah = None
if cheetah:
    cheetah.checkFileMtime(False)

    class Cheetah(TemplateLanguage):
        def __init__(self):
            self.template_dir = rel_dir('cheetah')

        def compile(self):
            for name in ('header', 'main', 'footer'):
                file_name = os.path.join(self.template_dir, name + '.tmpl')
                cheetah.Template.compile(file=file_name, cacheCompilationResults=False, useCache=False)

        def setup_render(self):
            file_name = os.path.join(self.template_dir, 'main.tmpl')
            self.template = cheetah.Template.compile(file=file_name, cacheCompilationResults=True, useCache=True)

        def render(self):
            params = dict(title=TITLE, entries=ENTRIES, template_dir=self.template_dir)
            return self.template(searchList=[params], filter=cheetah_websafe).respond()

try:
    import jinja2
except ImportError:
    warnings.warn("Can't import jinja2, is it in your PYTHONPATH?")
    jinja2 = None
if jinja2:
    class Jinja2(TemplateLanguage):
        def __init__(self):
            loader = jinja2.FileSystemLoader(rel_dir('jinja2'))
            self.compile_env = jinja2.Environment(loader=loader, autoescape=True, cache_size=0, trim_blocks=True)
            self.render_env = jinja2.Environment(loader=loader, autoescape=True, trim_blocks=True)

        def compile(self):
            for name in ('header', 'main', 'footer'):
                self.compile_env.get_template(name + '.tmpl')

        def setup_render(self):
            for name in ('header', 'main', 'footer'):
                self.render_env.get_template(name + '.tmpl')
            self.template = self.render_env.get_template('main.tmpl')

        def render(self):
            return self.template.render(title=TITLE, entries=ENTRIES)

try:
    import mako.lookup
except ImportError:
    warnings.warn("Can't import mako, is it in your PYTHONPATH?")
    mako = None
if mako:
    class Mako(TemplateLanguage):
        def __init__(self):
            self.compile_lookup = mako.lookup.TemplateLookup(
                    directories=[rel_dir('mako')],
                    default_filters=['h'],
                    collection_size=0)
            self.render_lookup = mako.lookup.TemplateLookup(
                    directories=[rel_dir('mako')],
                    default_filters=['h'])

        def compile(self):
            for name in ('header', 'main', 'footer'):
                self.compile_lookup.get_template(name + '.tmpl')

        def setup_render(self):
            for name in ('header', 'main', 'footer'):
                self.render_lookup.get_template(name + '.tmpl')
            self.template = self.render_lookup.get_template('main.tmpl')

        def render(self):
            return self.template.render(title=TITLE, entries=ENTRIES)

try:
    import wheezy.template
except OSError:
    warnings.warn("Can't import wheezy.template, is it in your PYTHONPATH?")
    wheezy = None
if wheezy:
    # include this here so there's not a dependency on wheezy.html
    def wheezy_escape_html(s):
        return s.replace('&', '&').replace('<', '<').replace(
                         '>', '>').replace('"', '"')

    class Wheezy(TemplateLanguage):
        def __init__(self):
            self.engine = wheezy.template.Engine(
                    loader=wheezy.template.FileLoader([rel_dir('wheezy')]),
                    extensions=[wheezy.template.CoreExtension()])
            self.engine.global_vars.update({'h': wheezy_escape_html})

        def compile(self):
            for name in ('header', 'main', 'footer'):
                self.engine.remove(name + '.tmpl')
            for name in ('header', 'main', 'footer'):
                self.engine.get_template(name + '.tmpl')

        def setup_render(self):
            for name in ('header', 'main', 'footer'):
                self.engine.get_template(name + '.tmpl')
            self.template = self.engine.get_template('main.tmpl')

        def render(self):
            return self.template.render({'title': TITLE, 'entries': ENTRIES})

def main():
    language_classes = [(name, cls) for name, cls in globals().items()
                        if isinstance(cls, type) and
                           issubclass(cls, TemplateLanguage) and
                           cls is not TemplateLanguage]

    results = []
    output = None
    for name, cls in language_classes:
        language = cls()
        timings = language.benchmark()
        results.append((name, timings['compile'], timings['render']))

        output_dir = rel_dir('output')
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        with open(os.path.join(output_dir, name.lower() + '.html'), 'wb') as f:
            language.setup_render()
            rendering = language.render().replace('\r\n', '\n')
            f.write(rendering.encode('utf-8'))
            if output is None:
                output = (name, rendering.strip())
            elif output[1] != rendering.strip():
                print 'ERROR: output from %s and %s differ' % (name, output[0])

    print 'Engine    compile (ms)  render (ms)'
    print '-----------------------------------'
    for name, compile_time, render_time in sorted(results, key=lambda r: r[2]):
        print '%-10s %11.3f %12.3f' % (name, compile_time * 1000, render_time * 1000)

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


<commit_before>from flask import request, session, current_app
from flask import g as flaskg

# in the code, please always use this constants for permission values:
ADMIN = 'admin'
LIST = 'list'
CREATE = 'create'
READ = 'read'
DELETE = 'delete'

# key in the session:
PERMISSIONS = 'permissions'
LOGGEDIN = 'loggedin'

def lookup_permissions(token):
    """
    look up the permissions string for the secret <token> in the configuration.
    if no such secret is configured, return None
    """
    return current_app.config['PERMISSIONS'].get(token)

def get_permissions():
    """
    get the permissions for the current user (if logged in)
    or the default permissions (if not logged in).
    """
    auth = request.authorization
    if auth:
        # http basic auth header present
        permissions = lookup_permissions(auth.password)
    elif 'token' in request.values:
        # token present in query args or post form (can be used by cli clients)
        permissions = lookup_permissions(request.values['token'])
    else:
        # look into session, login might have put something there
        permissions = session.get(PERMISSIONS)
    if permissions is None:
        permissions = current_app.config['DEFAULT_PERMISSIONS']
    permissions = set(permissions.split(','))
    return permissions

def may(permission):
    """
    check whether the current user has the permission <permission>
    """
    return permission in flaskg.permissions

def logged_in():
    """
    is the user logged-in?
    """
    return session.get(LOGGEDIN, False)
<commit_msg>add get_permission_icons function<commit_after>from flask import request, session, current_app
from flask import g as flaskg

# in the code, please always use this constants for permission values:
ADMIN = 'admin'
LIST = 'list'
CREATE = 'create'
READ = 'read'
DELETE = 'delete'

# key in the session:
PERMISSIONS = 'permissions'
LOGGEDIN = 'loggedin'

permission_icons = {
    'admin': 'user',
    'list': 'list',
    'create': 'plus',
    'read': 'book',
    'delete': 'trash'
}

def lookup_permissions(token):
    """
    look up the permissions string for the secret <token> in the configuration.
    if no such secret is configured, return None
    """
    return current_app.config['PERMISSIONS'].get(token)

def get_permissions():
    """
    get the permissions for the current user (if logged in)
    or the default permissions (if not logged in).
    """
    auth = request.authorization
    if auth:
        # http basic auth header present
        permissions = lookup_permissions(auth.password)
    elif 'token' in request.values:
        # token present in query args or post form (can be used by cli clients)
        permissions = lookup_permissions(request.values['token'])
    else:
        # look into session, login might have put something there
        permissions = session.get(PERMISSIONS)
    if permissions is None:
        permissions = current_app.config['DEFAULT_PERMISSIONS']
    permissions = set(permissions.split(','))
    return permissions

def get_permission_icons():
    return [permission_icons[permission] for permission in get_permissions()]

def may(permission):
    """
    check whether the current user has the permission <permission>
    """
    return permission in flaskg.permissions

def logged_in():
    """
    is the user logged-in?
    """
    return session.get(LOGGEDIN, False)
<|endoftext|>


<commit_before>import csv
import datetime
from io import StringIO
from calendar import monthrange
from collections import defaultdict, OrderedDict

import requests
from slackbot import settings
from slackbot.bot import respond_to, listen_to
from sqlalchemy import func, Date, cast

from db import Session
from utils.slack import get_user_name

from beproudbot.plugins.kintai_models import KintaiHistory

HELP = """
- `$勤怠`: 自分の勤怠一覧を直近40日分表示する
- `$勤怠 csv <year>/<month>`: monthに指定した月の勤怠記録をCSV形式で返す(defaultは当年月)
- `おはよう` ・ `お早う` ・ `出社しました`: 出社時刻を記録します
- `帰ります` ・ `かえります` ・ `退社します`: 退社時刻を記録します
- `$勤怠 help`: 勤怠コマンドの使い方を返す
"""

DAY_OF_WEEK = '月火水木金土日'

@listen_to('おはよう|お早う|出社しました')
def register_workon_time(message):
    """出社時刻を記録して挨拶を返すコマンド

    :param message: slackbotの各種パラメータを保持したclass
    """
    user_id = message.body['user']
    register_worktime(user_id)
    message.reply('おはようございます')

@listen_to('帰ります|かえります|退社します')
def register_workoff_time(message):
    """退社時刻を記録して挨拶を返すコマンド

    :param message: slackbotの各種パラメータを保持したclass
    """
    user_id = message.body['user']
    register_worktime(user_id, is_workon=False)
    message.reply('お疲れ様でした')

def register_worktime(user_id, is_workon=True):
    """出社、退社時間をDBに登録する

    :param str user_id: Slackのuser_id
    :param bool is_workon: 出社か退社かのフラグ、defaltは出社
    """
    today = datetime.date.today()
    s = Session()
    # SQLiteを使用している場合、castできないのでMySQLでdebugする事
    record = (s.query(KintaiHistory)
              .filter(cast(KintaiHistory.registered_at, Date) == today)
              .filter(KintaiHistory.user_id == user_id)
              .filter(KintaiHistory.is_workon.is_(is_workon))
              .one_or_none())
    if record:
        record.registered_at = datetime.datetime.now()
    else:
        s.add(KintaiHistory(user_id=user_id, is_workon=is_workon))
    s.commit()

@respond_to('^勤怠$')
def show_kintai_history(message):
    """直近40日分の勤怠記録を表示します

    :param message: slackbotの各種パラメータを保持したclass
    """
    user_id = message.body['user']
    today = datetime.date.today()
    target_day = today - datetime.timedelta(days=40)

    s = Session()
    qs = (s.query(KintaiHistory)
          .filter(KintaiHistory.user_id == user_id)
          .filter(KintaiHistory.registered_at >= target_day)
          .order_by(KintaiHistory.registered_at.asc()))

    kintai = OrderedDict()
    for q in qs:
        day_of_week = DAY_OF_WEEK[q.registered_at.date().weekday()]
        prefix_day = '{:%Y年%m月%d日}({})'.format(q.registered_at, day_of_week)
        registered_at = '{:%I:%M:%S}'.format(q.registered_at)
        kind = '出社' if q.is_workon else '退社'
        kintai.setdefault(prefix_day, []).append('{}  {}'.format(kind,
                                                                 registered_at))

    rows = []
    for prefix, registered_ats in kintai.items():
        sorted_times = ' '.join(sorted(registered_ats))
        rows.append('{} {}'.format(prefix, sorted_times))

    if not rows:
        rows = ['勤怠記録はありません']

    user_name = get_user_name(user_id)
    message.send('{}の勤怠:\n{}'.format(user_name, '\n'.join(rows)))

@respond_to('^勤怠\s+csv$')
@respond_to('^勤怠\s+csv\s+(\d{4}/\d{1,2})$')
def show_kintai_history_csv(message, time=None):
    """指定した月の勤怠記録をCSV形式で返す

    :param message: slackbotの各種パラメータを保持したclass
    :param str time: `/` 区切りの年月(例: 2016/1)
    """
    user_id = message.body['user']
    now = datetime.datetime.now()
    year_str, month_str = now.strftime('%Y'), now.strftime('%m')
    if time:
        year_str, month_str = time.split('/')
        if int(month_str) not in range(1, 13):
            message.send('指定した対象月は存在しません')
            return
    year, month = int(year_str), int(month_str)

    s = Session()
    qs = (s.query(KintaiHistory)
          .filter(KintaiHistory.user_id == user_id)
          .filter(func.extract('year', KintaiHistory.registered_at) == year)
          .filter(func.extract('month', KintaiHistory.registered_at) == month))

    kintai = defaultdict(list)
    for q in qs:
        registered_at = q.registered_at.strftime('%Y-%m-%d')
        kintai[registered_at].append((q.is_workon,
                                      '{:%I:%M:%S}'.format(q.registered_at)))

    rows = []
    for day in range(1, monthrange(year, month)[1] + 1):
        aligin_date = '{}-{:02d}-{:02d}'.format(year, month, day)
        workon, workoff = '', ''
        for d in sorted(kintai[aligin_date]):
            if d[0]:
                workon = d[1]
            else:
                workoff = d[1]
        rows.append([aligin_date, workon, workoff])

    output = StringIO()
    w = csv.writer(output)
    w.writerows(rows)

    param = {
        'token': settings.API_TOKEN,
        'channels': message.body['channel'],
        'title': '勤怠記録'
    }
    requests.post(settings.FILE_UPLOAD_URL,
                  params=param,
                  files={'file': output.getvalue()})

@respond_to('^勤怠\s+help$')
def show_help_kintai_commands(message):
    """勤怠コマンドのhelpを表示

    :param message: slackbotの各種パラメータを保持したclass
    """
    message.send(HELP)
<commit_msg>refs #8 レビュー指摘部修正<commit_after>import csv
import datetime
from io import StringIO
from calendar import monthrange
from collections import defaultdict, OrderedDict

import requests
from slackbot import settings
from slackbot.bot import respond_to, listen_to
from sqlalchemy import func, Date, cast

from db import Session
from utils.slack import get_user_name

from beproudbot.plugins.kintai_models import KintaiHistory

HELP = """
- `$勤怠`: 自分の勤怠一覧を直近40日分表示する
- `$勤怠 csv <year>/<month>`: monthに指定した月の勤怠記録をCSV形式で返す(defaultは当年月)
- `おはよう` ・ `お早う` ・ `出社しました`: 出社時刻を記録します
- `帰ります` ・ `かえります` ・ `退社します`: 退社時刻を記録します
- `$勤怠 help`: 勤怠コマンドの使い方を返す
"""

DAY_OF_WEEK = '月火水木金土日'

@listen_to('おはよう|お早う|出社しました')
def register_workon_time(message):
    """出社時刻を記録して挨拶を返すコマンド

    :param message: slackbotの各種パラメータを保持したclass
    """
    user_id = message.body['user']
    register_worktime(user_id)
    message.reply('おはようございます')

@listen_to('帰ります|かえります|退社します')
def register_workoff_time(message):
    """退社時刻を記録して挨拶を返すコマンド

    :param message: slackbotの各種パラメータを保持したclass
    """
    user_id = message.body['user']
    register_worktime(user_id, is_workon=False)
    message.reply('お疲れ様でした')

def register_worktime(user_id, is_workon=True):
    """出社、退社時間をDBに登録する

    :param str user_id: Slackのuser_id
    :param bool is_workon: 出社か退社かのフラグ、defaltは出社
    """
    today = datetime.date.today()
    s = Session()
    # SQLiteを使用している場合、castできないのでMySQLでdebugする事
    record = (s.query(KintaiHistory)
              .filter(cast(KintaiHistory.registered_at, Date) == today)
              .filter(KintaiHistory.user_id == user_id)
              .filter(KintaiHistory.is_workon.is_(is_workon))
              .one_or_none())
    if record:
        record.registered_at = datetime.datetime.now()
    else:
        s.add(KintaiHistory(user_id=user_id, is_workon=is_workon))
    s.commit()

@respond_to('^勤怠$')
def show_kintai_history(message):
    """直近40日分の勤怠記録を表示します

    :param message: slackbotの各種パラメータを保持したclass
    """
    user_id = message.body['user']
    today = datetime.date.today()
    target_day = today - datetime.timedelta(days=40)

    s = Session()
    qs = (s.query(KintaiHistory)
          .filter(KintaiHistory.user_id == user_id)
          .filter(KintaiHistory.registered_at >= target_day)
          .order_by(KintaiHistory.registered_at.asc()))

    kintai = OrderedDict()
    for q in qs:
        day_of_week = DAY_OF_WEEK[q.registered_at.date().weekday()]
        prefix_day = '{:%Y年%m月%d日}({})'.format(q.registered_at, day_of_week)
        registered_at = '{:%I:%M:%S}'.format(q.registered_at)
        kind = '出社' if q.is_workon else '退社'
        kintai.setdefault(prefix_day, []).append('{}  {}'.format(kind,
                                                                 registered_at))

    rows = []
    for prefix, registered_ats in kintai.items():
        sorted_times = ' '.join(sorted(registered_ats))
        rows.append('{} {}'.format(prefix, sorted_times))

    if not rows:
        rows = ['勤怠記録はありません']

    user_name = get_user_name(user_id)
    message.send('{}の勤怠:\n{}'.format(user_name, '\n'.join(rows)))

@respond_to('^勤怠\s+csv$')
@respond_to('^勤怠\s+csv\s+(\d{4}/\d{1,2})$')
def show_kintai_history_csv(message, time=None):
    """指定した月の勤怠記録をCSV形式で返す

    :param message: slackbotの各種パラメータを保持したclass
    :param str time: `/` 区切りの年月(例: 2016/1)
    """
    user_id = message.body['user']
    if time:
        year_str, month_str = time.split('/')
    else:
        now = datetime.datetime.now()
        year_str, month_str = now.strftime('%Y'), now.strftime('%m')
    year, month = int(year_str), int(month_str)

    if not 1 <= month <= 12:
        message.send('指定した対象月は存在しません')
        return

    s = Session()
    qs = (s.query(KintaiHistory)
          .filter(KintaiHistory.user_id == user_id)
          .filter(func.extract('year', KintaiHistory.registered_at) == year)
          .filter(func.extract('month', KintaiHistory.registered_at) == month))

    kintai = defaultdict(list)
    for q in qs:
        registered_at = q.registered_at.strftime('%Y-%m-%d')
        kintai[registered_at].append((q.is_workon,
                                      '{:%I:%M:%S}'.format(q.registered_at)))

    rows = []
    for day in range(1, monthrange(year, month)[1] + 1):
        aligin_date = '{}-{:02d}-{:02d}'.format(year, month, day)
        workon, workoff = '', ''
        for d in sorted(kintai[aligin_date]):
            if d[0]:
                workon = d[1]
            else:
                workoff = d[1]
        rows.append([aligin_date, workon, workoff])

    output = StringIO()
    w = csv.writer(output)
    w.writerows(rows)

    param = {
        'token': settings.API_TOKEN,
        'channels': message.body['channel'],
        'title': '勤怠記録'
    }
    requests.post(settings.FILE_UPLOAD_URL,
                  params=param,
                  files={'file': output.getvalue()})

@respond_to('^勤怠\s+help$')
def show_help_kintai_commands(message):
    """勤怠コマンドのhelpを表示

    :param message: slackbotの各種パラメータを保持したclass
    """
    message.send(HELP)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
import os
import shutil
import socket
import subprocess
import sys
import time
import urllib
import urllib2

SqueakImage = "Squeak4.5-12568"

# You need to enter the real URL and have the server running
CODESPEED_URL = 'http://speed.bithug.org/'

class Project(object):
    def __init__(self, name, executables={}, arguments="", commitid=None):
        self.commitid = commitid if commitid else self.get_commitid()
        self.name = name
        self.executables = executables
        self.arguments = arguments

    def run(self):
        for executable in self.executables:
            yield executable.name, executable.run(self.arguments)

    def post_results(self):
        for executable, output in self.run():
            benchmarks = output.split('\n')
            for s in benchmarks:
                if ';' in s:
                    results = s.split(';')
                    if len(results) == 2:
                        self.add(executable, *results)
                    elif len(results) == 4:
                        self.add(executble, *results)

    def add(self, executable, benchmark, result, min=None, max=None):
        print "Saving result %s for executable %s, benchmark %s" % (
            result, executable, benchmark)
        if min is max is None:
            data = self.build_data(executable, benchmark, result)
        else:
            data = self.build_extended_data(executable, benchmark, result, min, max)
        params = urllib.urlencode(data)
        response = "None"
        print "Saving result for executable %s, revision %s, benchmark %s" % (
            data['executable'], data['commitid'], data['benchmark'])
        try:
            f = urllib2.urlopen(CODESPEED_URL + 'result/add/', params)
        except urllib2.HTTPError as e:
            print str(e)
            print e.read()
            return
        response = f.read()
        f.close()
        print "Server (%s) response: %s\n" % (CODESPEED_URL, response)

    def get_commitid(self):
        try:
            pipe = subprocess.Popen(
                ["hg", "log", "-l", "1", "--template", "{rev}:{node|short}"],
                stdout=subprocess.PIPE
            )
            if pipe.wait() == 0:
                return pipe.stdout.read()
        except:
            pass
        try:
            pipe = subprocess.Popen(
                ["git", "log", "-1", "--pretty=%H"],
                stdout=subprocess.PIPE
            )
            if pipe.wait() == 0:
                return pipe.stdout.read()
        except:
            pass
        raise Exception("commitid not found. not a git or hg repo")

    def build_data(self, executable, benchmark, result):
        # Mandatory fields
        return {
            'commitid': self.commitid,
            'branch': 'default',
            'project': self.name,
            'executable': executable,
            'benchmark': benchmark,
            'environment': socket.gethostname(),
            'result_value': str(result),
        }
        # Optional fields
        # {
        #     'std_dev': 1.11111, # Optional. Default is blank
        #     'max': 4001.6, # Optional. Default is blank
        #     'min': 3995.1, # Optional. Default is blank
        # }
    def build_data_extended(self, executable, benchmark, result, min, max):
        return dict(self.build_data(executable, benchmark, result),
            **{
                'min': str(min),
                'max': str(max)
            }
        )

class Archive(object):
    def __init__(self, filename, target, func):
        self.filename = filename
        self.func = func
        self.target = target

    def extract(self):
        self.func(self.filename, self.target)

    def __enter__(self):
        self.extract()

    def __exit__(self, *_):
        if os.path.exists(self.target) and os.path.isfile(self.target):
            os.remove(self.target)

class Executable(object):
    def __init__(self, name, path, url=None, callback=None):
        self.name = name
        self.path = path
        if url:
            self.download(url, callback=callback)

    def ungzip(self, source, target):
        import gzip
        contents = gzip.open(source).read()
        with open(target, "w") as t:
            t.write(contents)

    def untar(self, source, target):
        import tarfile
        try:
            f = tarfile.open(source)
            f.extractall(target)
        finally:
            f.close()

    def download(self, url, callback=None):
        if os.path.exists(self.path):
            shutil.rmtree(os.path.dirname(self.path))
        filename = url.rsplit("/", 1)[1]
        if os.path.exists(filename):
            os.remove(filename)
        print "Downloading from", url
        with open(filename, "w") as f:
            f.write(urllib2.urlopen(url).read())
        try:
            print "Extracting", filename
            if filename.endswith(".tar.gz") or filename.endswith(".tgz"):
                tarfile = os.path.basename(filename) + ".tar"
                with Archive(filename, tarfile, self.ungzip):
                    Archive(tarfile, ".", self.untar).extract()
            elif filename.endswith(".tar"):
                Archive(filename, ".", self.untar).extract()
            else:
                raise NotImplementedError
        finally:
            os.remove(filename)
        if callback:
            callback(self)

    def run(self, args):
        print 'Calling %s ...' % self.name
        pipe = subprocess.Popen(
            ["%s" % self.path] + args,
            stdout=subprocess.PIPE
        )
        out, err = pipe.communicate()
        errcode = pipe.wait()
        print out
        return out

# XXX: Find a better place to put this
def update_image(executable):
    print "Updating image ..."
    with open('update.st', 'w') as f:
        f.write('''Smalltalk snapshot: true andQuit: true.''')
    print executable.run(["-vm-display-X11", "-headless", "images/%s" % SqueakImage, "../update.st"])
    os.remove('update.st')

def find_cog_url():
    baseurl = "http://www.mirandabanda.org/files/Cog/VM/"
    r = urllib2.urlopen(baseurl)
    ver = r.read().rsplit("VM.r", 1)[1].split("/", 1)[0]
    vmfolder = "%s/VM.r%s/" % (baseurl, ver)
    r = urllib2.urlopen(vmfolder).read()
    off = r.find("coglinux")
    filename = r[off:r.find(".tgz", off)] + ".tgz"
    return ver, vmfolder + filename
cogid, cogurl = find_cog_url()

Cog = Project(
    "squeak",
    executables=[
        Executable(
            "cogvm",
            "coglinux/squeak",
            cogurl,
            callback=update_image
        ),
        Executable(
            "stackvm",
            "stackvm/bin/squeak",
            "http://squeakvm.org/unix/release/Squeak-4.10.2.2614-linux_i386.tar.gz",
            callback=(lambda x: subprocess.Popen(["mv", "Squeak-4.10.2.2614-linux_i386", "stackvm"]).wait())
        )
    ],
    arguments=['-vm-display-null', "images/%s.image" % SqueakImage, '../benchmarks.st'],
    commitid=cogid
)
RSqueakVM = Project(
    "lang-smalltalk",
    executables=[
        Executable("rsqueakvm", "./targetimageloadingsmalltalk-c"),
        Executable("rsqueakvm-nojit", "./targetimageloadingsmalltalk-nojit-c")
    ],
    arguments=["images/%s.image" % SqueakImage, '-m', 'runSPyBenchmarks']
)

if __name__ == "__main__":
    for project in [Cog, RSqueakVM]:
        project.post_results()
<commit_msg>reremoved nojit benchmarks<commit_after># -*- coding: utf-8 -*-
import os
import shutil
import socket
import subprocess
import sys
import time
import urllib
import urllib2

SqueakImage = "Squeak4.5-12568"

# You need to enter the real URL and have the server running
CODESPEED_URL = 'http://speed.bithug.org/'

class Project(object):
    def __init__(self, name, executables={}, arguments="", commitid=None):
        self.commitid = commitid if commitid else self.get_commitid()
        self.name = name
        self.executables = executables
        self.arguments = arguments

    def run(self):
        for executable in self.executables:
            yield executable.name, executable.run(self.arguments)

    def post_results(self):
        for executable, output in self.run():
            benchmarks = output.split('\n')
            for s in benchmarks:
                if ';' in s:
                    results = s.split(';')
                    if len(results) == 2:
                        self.add(executable, *results)
                    elif len(results) == 4:
                        self.add(executble, *results)

    def add(self, executable, benchmark, result, min=None, max=None):
        print "Saving result %s for executable %s, benchmark %s" % (
            result, executable, benchmark)
        if min is max is None:
            data = self.build_data(executable, benchmark, result)
        else:
            data = self.build_extended_data(executable, benchmark, result, min, max)
        params = urllib.urlencode(data)
        response = "None"
        print "Saving result for executable %s, revision %s, benchmark %s" % (
            data['executable'], data['commitid'], data['benchmark'])
        try:
            f = urllib2.urlopen(CODESPEED_URL + 'result/add/', params)
        except urllib2.HTTPError as e:
            print str(e)
            print e.read()
            return
        response = f.read()
        f.close()
        print "Server (%s) response: %s\n" % (CODESPEED_URL, response)

    def get_commitid(self):
        try:
            pipe = subprocess.Popen(
                ["hg", "log", "-l", "1", "--template", "{rev}:{node|short}"],
                stdout=subprocess.PIPE
            )
            if pipe.wait() == 0:
                return pipe.stdout.read()
        except:
            pass
        try:
            pipe = subprocess.Popen(
                ["git", "log", "-1", "--pretty=%H"],
                stdout=subprocess.PIPE
            )
            if pipe.wait() == 0:
                return pipe.stdout.read()
        except:
            pass
        raise Exception("commitid not found. not a git or hg repo")

    def build_data(self, executable, benchmark, result):
        # Mandatory fields
        return {
            'commitid': self.commitid,
            'branch': 'default',
            'project': self.name,
            'executable': executable,
            'benchmark': benchmark,
            'environment': socket.gethostname(),
            'result_value': str(result),
        }
        # Optional fields
        # {
        #     'std_dev': 1.11111, # Optional. Default is blank
        #     'max': 4001.6, # Optional. Default is blank
        #     'min': 3995.1, # Optional. Default is blank
        # }
    def build_data_extended(self, executable, benchmark, result, min, max):
        return dict(self.build_data(executable, benchmark, result),
            **{
                'min': str(min),
                'max': str(max)
            }
        )

class Archive(object):
    def __init__(self, filename, target, func):
        self.filename = filename
        self.func = func
        self.target = target

    def extract(self):
        self.func(self.filename, self.target)

    def __enter__(self):
        self.extract()

    def __exit__(self, *_):
        if os.path.exists(self.target) and os.path.isfile(self.target):
            os.remove(self.target)

class Executable(object):
    def __init__(self, name, path, url=None, callback=None):
        self.name = name
        self.path = path
        if url:
            self.download(url, callback=callback)

    def ungzip(self, source, target):
        import gzip
        contents = gzip.open(source).read()
        with open(target, "w") as t:
            t.write(contents)

    def untar(self, source, target):
        import tarfile
        try:
            f = tarfile.open(source)
            f.extractall(target)
        finally:
            f.close()

    def download(self, url, callback=None):
        if os.path.exists(self.path):
            shutil.rmtree(os.path.dirname(self.path))
        filename = url.rsplit("/", 1)[1]
        if os.path.exists(filename):
            os.remove(filename)
        print "Downloading from", url
        with open(filename, "w") as f:
            f.write(urllib2.urlopen(url).read())
        try:
            print "Extracting", filename
            if filename.endswith(".tar.gz") or filename.endswith(".tgz"):
                tarfile = os.path.basename(filename) + ".tar"
                with Archive(filename, tarfile, self.ungzip):
                    Archive(tarfile, ".", self.untar).extract()
            elif filename.endswith(".tar"):
                Archive(filename, ".", self.untar).extract()
            else:
                raise NotImplementedError
        finally:
            os.remove(filename)
        if callback:
            callback(self)

    def run(self, args):
        print 'Calling %s ...' % self.name
        pipe = subprocess.Popen(
            ["%s" % self.path] + args,
            stdout=subprocess.PIPE
        )
        out, err = pipe.communicate()
        errcode = pipe.wait()
        print out
        return out

# XXX: Find a better place to put this
def update_image(executable):
    print "Updating image ..."
    with open('update.st', 'w') as f:
        f.write('''Smalltalk snapshot: true andQuit: true.''')
    print executable.run(["-vm-display-X11", "-headless", "images/%s" % SqueakImage, "../update.st"])
    os.remove('update.st')

def find_cog_url():
    baseurl = "http://www.mirandabanda.org/files/Cog/VM/"
    r = urllib2.urlopen(baseurl)
    ver = r.read().rsplit("VM.r", 1)[1].split("/", 1)[0]
    vmfolder = "%s/VM.r%s/" % (baseurl, ver)
    r = urllib2.urlopen(vmfolder).read()
    off = r.find("coglinux")
    filename = r[off:r.find(".tgz", off)] + ".tgz"
    return ver, vmfolder + filename
cogid, cogurl = find_cog_url()

Cog = Project(
    "squeak",
    executables=[
        Executable(
            "cogvm",
            "coglinux/squeak",
            cogurl,
            callback=update_image
        ),
        Executable(
            "stackvm",
            "stackvm/bin/squeak",
            "http://squeakvm.org/unix/release/Squeak-4.10.2.2614-linux_i386.tar.gz",
            callback=(lambda x: subprocess.Popen(["mv", "Squeak-4.10.2.2614-linux_i386", "stackvm"]).wait())
        )
    ],
    arguments=['-vm-display-null', "images/%s.image" % SqueakImage, '../benchmarks.st'],
    commitid=cogid
)
RSqueakVM = Project(
    "lang-smalltalk",
    executables=[
        Executable("rsqueakvm", "./targetimageloadingsmalltalk-c"),
        # Executable("rsqueakvm-nojit", "./targetimageloadingsmalltalk-nojit-c")
    ],
    arguments=["images/%s.image" % SqueakImage, '-m', 'runSPyBenchmarks']
)

if __name__ == "__main__":
    for project in [Cog, RSqueakVM]:
        project.post_results()
<|endoftext|>


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

# Documentation: http://docs.astropy.org/en/stable/io/fits/index.html

import matplotlib
matplotlib.use('TkAgg')

import matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# implement the default mpl key bindings
from matplotlib.backend_bases import key_press_handler
from matplotlib import cm

import tkinter as tk

import argparse

from astropy.io import fits

def get_image_array_from_fits_file(file_path):

    hdu_list = fits.open(file_path)   # open the FITS file

    if len(hdu_list) != 1:
        raise Exception("The FITS file should contain only one HDU.")

    image_array = hdu_list[0].data    # "hdu.data" is a Numpy Array

    hdu_list.close()

    return image_array

def main():

    # PARSE OPTIONS ###############################################################

    parser = argparse.ArgumentParser(description="Display a FITS file.")

    parser.add_argument("--cmap", "-c", default="gray", metavar="STRING",
            help="the colormap to use. The list of available color maps is available here: "
                 "http://matplotlib.org/examples/color/colormaps_reference.html")

    parser.add_argument("filearg", nargs=1, metavar="FILE",
            help="the FITS file to process")

    args = parser.parse_args()

    cmap = args.cmap
    input_file_path = args.filearg[0]

    # READ THE INPUT FILE #########################################################

    input_img = get_image_array_from_fits_file(input_file_path)

    if input_img.ndim != 2:
        raise Exception("Unexpected error: the input FITS file should contain a 2D array.")

    # MATPLOTLIB ##################################################################

    fig = plt.figure(figsize=(8.0, 8.0))
    ax = fig.add_subplot(111)
    ax.set_title(input_file_path)
    ax.imshow(input_img, interpolation='nearest', cmap=cmap)

    # TKINTER #####################################################################

    root = tk.Tk()

    canvas = FigureCanvasTkAgg(fig, master=root)
    canvas.get_tk_widget().pack(fill="both", expand=True)

    def _quit():
        root.quit()     # stops mainloop
        root.destroy()  # this is necessary on Windows to prevent
                        # Fatal Python Error: PyEval_RestoreThread: NULL tstate

    button = tk.Button(master=root, text='Quit', command=_quit)
    button.pack(fill="x", expand=True)

    # Add a callback on WM_DELETE_WINDOW event
    root.protocol("WM_DELETE_WINDOW", _quit)

    root.mainloop()

if __name__ == "__main__":
    main()

<commit_msg>Put the GUI in a dedicated class.<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# FITS Viewer

# The MIT License
#
# Copyright (c) 2016 Jeremie DECOCK (http://www.jdhp.org)
#
# 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.

# Documentation: http://docs.astropy.org/en/stable/io/fits/index.html

__all__ = ['TkGUI']

import matplotlib
matplotlib.use('TkAgg')

import matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# implement the default mpl key bindings
from matplotlib.backend_bases import key_press_handler
from matplotlib import cm

import tkinter as tk

import argparse

from astropy.io import fits

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

def get_image_array_from_fits_file(file_path):

    hdu_list = fits.open(file_path)   # open the FITS file

    if len(hdu_list) != 1:
        raise Exception("The FITS file should contain only one HDU.")

    image_array = hdu_list[0].data    # "hdu.data" is a Numpy Array

    hdu_list.close()

    return image_array

def get_colour_map_list():
    """Return the list of the available colormaps."""
    return sorted(plt.cm.datad)

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

class TkGUI:
    """
    TODO...
    """

    def __init__(self, fig):
        """
        TODO...
        """

        self.fig = fig

        # GUI PARAMETERS ##############

        self.hide_color_bar = False

        # MAKE WIDGETS ################

        self.root = tk.Tk()   # TODO

        # ADD A CALLBACK ON WM_DELETE_WINDOW EVENT
        self.root.protocol("WM_DELETE_WINDOW", self.quit)

        # CANVAS
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
        self.canvas.get_tk_widget().pack(fill="both", expand=True)

        # BUTTONS
        button = tk.Button(master=self.root, text='Quit', command=self.quit)
        button.pack(fill="x", expand=True)

    def run(self):
        """Launch the main loop (Tk event loop)."""
        # TODO ???
        self.root.mainloop()

    def quit(self):
        self.root.quit()     # stops mainloop
        self.root.destroy()  # this is necessary on Windows to prevent
                             # Fatal Python Error: PyEval_RestoreThread: NULL tstate

def main():

    # PARSE OPTIONS ###############################################################

    parser = argparse.ArgumentParser(description="Display a FITS file.")

    parser.add_argument("--cmap", "-c", default="gray", metavar="STRING",
            help="the colormap to use. The list of available color maps is available here: "
                 "http://matplotlib.org/examples/color/colormaps_reference.html")

    parser.add_argument("--hidecbar", "-H", action="store_true",
            help="hide the color bar")

    parser.add_argument("filearg", nargs=1, metavar="FILE",
            help="the FITS file to process")

    args = parser.parse_args()

    color_map = args.cmap
    hide_color_bar = args.hidecbar
    input_file_path = args.filearg[0]

    # READ THE INPUT FILE #########################################################

    input_img = get_image_array_from_fits_file(input_file_path)

    if input_img.ndim != 2:
        raise Exception("Unexpected error: the input FITS file should contain a 2D array.")

    # MATPLOTLIB ##################################################################

    fig = plt.figure(figsize=(8.0, 8.0))
    ax = fig.add_subplot(111)
    ax.set_title(input_file_path)

    im = ax.imshow(input_img, interpolation='nearest', cmap=color_map)

    if not hide_color_bar:
        plt.colorbar(im) # draw the colorbar

    # TKINTER #####################################################################

    gui = TkGUI(fig)
    gui.run()

if __name__ == "__main__":
    main()

<|endoftext|>


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

# FITS Viewer

# The MIT License
#
# Copyright (c) 2016 Jeremie DECOCK (http://www.jdhp.org)
#
# 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.

# Documentation: http://docs.astropy.org/en/stable/io/fits/index.html

__all__ = ['TkGUI']

import matplotlib
matplotlib.use('TkAgg')

import matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# implement the default mpl key bindings
from matplotlib.backend_bases import key_press_handler
from matplotlib import cm

import tkinter as tk

import argparse
import os

from astropy.io import fits

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

def get_image_array_from_fits_file(file_path):

    hdu_list = fits.open(file_path)   # open the FITS file

    if len(hdu_list) != 1:
        raise Exception("The FITS file should contain only one HDU.")

    image_array = hdu_list[0].data    # "hdu.data" is a Numpy Array

    hdu_list.close()

    return image_array

def get_colour_map_list():
    """Return the list of the available colormaps."""
    return sorted(plt.cm.datad)

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

class TkGUI:
    """
    TODO...
    """

    def __init__(self, root):
        """
        TODO...
        """

        self.file_path = None
        self.image_array = None

        # Matplotlib ##################

        self.fig = plt.figure(figsize=(8.0, 8.0))
        self.ax = None

        # Gui parameters ##############

        self._color_map = tk.StringVar()
        self._show_color_bar = tk.BooleanVar()

        # Make widgets ################

        self.root = root

        # Add a callback on WM_DELETE_WINDOW events
        self.root.protocol("WM_DELETE_WINDOW", self.quit)

        # Canvas
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
        self.canvas.get_tk_widget().pack(fill="both", expand=True)

        ## Buttons
        #quit_button = tk.Button(master=self.root, text='Quit', command=self.quit)
        #quit_button.pack(fill="x", expand=True)

        # Make a menubar ##############

        # Create a toplevel menu
        menubar = tk.Menu(self.root)

        # Create a pulldown menu: /File
        file_menu = tk.Menu(menubar, tearoff=0)
        file_menu.add_command(label="Open...", command=self.select_fits_file)
        file_menu.add_command(label="Close", command=self.close_fits_file)
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=self.quit)

        menubar.add_cascade(label="File", menu=file_menu)

        # Create a pulldown menu: /View
        view_menu = tk.Menu(menubar, tearoff=0)
        view_menu.add_checkbutton(label="Show color bar",
                                  variable=self._show_color_bar,
                                  command=self.draw_figure)

        menubar.add_cascade(label="View", menu=view_menu)

        # Create a pulldown menu: /View/Color Map
        colormap_menu = tk.Menu(view_menu, tearoff=0)

        colormap_menu.add_radiobutton(label="plasma",
                                      variable=self._color_map,
                                      value="plasma",
                                      command=self.draw_figure)
        colormap_menu.add_radiobutton(label="inferno",
                                      variable=self._color_map,
                                      value="inferno",
                                      command=self.draw_figure)
        colormap_menu.add_radiobutton(label="viridis",
                                      variable=self._color_map,
                                      value="viridis",
                                      command=self.draw_figure)
        colormap_menu.add_radiobutton(label="gray",
                                      variable=self._color_map,
                                      value="gray",
                                      command=self.draw_figure)

        view_menu.add_cascade(label="Color Map", menu=colormap_menu)

        # Display the menu
        # The config method is used to attach the menu to the root window. The
        # contents of that menu is used to create a menubar at the top of the root
        # window. There is no need to pack the menu, since it is automatically
        # displayed by Tkinter.
        self.root.config(menu=menubar)

    def run(self):
        """
        Launch the main loop (Tk event loop).
        """

        # TODO ???
        self.root.mainloop()

    def quit(self):
        self.root.quit()     # stops mainloop
        self.root.destroy()  # this is necessary on Windows to prevent
                             # Fatal Python Error: PyEval_RestoreThread: NULL tstate

    def select_fits_file(self):
        """
        Display a file dialog to select the FITS file to open.

        Return the path of the selected file.
        """

        # Check and parse the file

        # FILE_TYPES = [(label1, pattern1), (label2, pattern2), ...]
        FILE_TYPES = [
                    ('FITS Files', '.fits')
                ]

        HOME = os.path.expanduser("~")

        path = tk.filedialog.askopenfilename(parent=self.root,
                                             filetypes=FILE_TYPES,     # optional
                                             defaultextension='.fits', # optional
                                             initialdir=HOME,          # optional
                                             #initialfile='demo.fits',  # optional
                                             title='Select your file') # optional

        self.open_fits_file(path)

    def open_fits_file(self, file_path):
        """
        Open and display the given FITS file.
        """
        # READ THE INPUT FILE #################################################

        self.file_path = file_path
        self.image_array = get_image_array_from_fits_file(self.file_path)

        if self.image_array.ndim != 2:
            raise Exception("Unexpected error: the input FITS file should contain a 2D array.")

        self.root.title(self.file_path)
        self.draw_figure()

    def close_fits_file(self):
        self.file_path = None
        self.image_array = None
        self.clear_figure()

    def clear_figure(self):
        self.fig.clf()
        self.fig.canvas.draw()

    def draw_figure(self):
        if self.image_array is not None:
            self.fig.clf() # TODO
            self.ax = self.fig.add_subplot(111)

            #self.ax.set_title(self.file_path)

            im = self.ax.imshow(self.image_array,
                                origin='lower',
                                interpolation='nearest',
                                cmap=self.color_map)

            if self.show_color_bar:
                plt.colorbar(im) # draw the colorbar

            self.fig.canvas.draw()

    # PROPERTIES ##############################################################

    @property
    def show_color_bar(self):
        return self._show_color_bar.get()

    @show_color_bar.setter
    def show_color_bar(self, value):
        self._show_color_bar.set(value)

    @property
    def color_map(self):
        return self._color_map.get()

    @color_map.setter
    def color_map(self, value):
        self._color_map.set(value)

def main():

    root = tk.Tk()   # TODO ?
    gui = TkGUI(root)

    # PARSE OPTIONS ###########################################################

    parser = argparse.ArgumentParser(description="Display a FITS file.")

    parser.add_argument("--cmap", "-c", default="gray", metavar="STRING",
            help="the colormap to use. The list of available color maps is available here: "
                 "http://matplotlib.org/examples/color/colormaps_reference.html")

    parser.add_argument("--hidecbar", "-H", action="store_true",
            help="hide the color bar")

    parser.add_argument("filearg", nargs="?", metavar="FILE", const=None,
            help="the FITS file to process")

    args = parser.parse_args()

    input_file_path = args.filearg

    # SET OPTIONS #############################################################

    gui.color_map = args.cmap
    gui.show_color_bar = not args.hidecbar

    if input_file_path is not None:
        gui.open_fits_file(input_file_path)

    # LAUNCH THE MAIN LOOP ####################################################

    gui.run()

if __name__ == "__main__":
    main()

<commit_msg>Add all available color maps.<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# FITS Viewer

# The MIT License
#
# Copyright (c) 2016 Jeremie DECOCK (http://www.jdhp.org)
#
# 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.

# Documentation: http://docs.astropy.org/en/stable/io/fits/index.html

__all__ = ['TkGUI']

import matplotlib
matplotlib.use('TkAgg')

import matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# implement the default mpl key bindings
from matplotlib.backend_bases import key_press_handler
from matplotlib import cm

import tkinter as tk

import argparse
import os

from astropy.io import fits

DEFAULT_COLOR_MAP="gray"

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

def get_image_array_from_fits_file(file_path):

    hdu_list = fits.open(file_path)   # open the FITS file

    if len(hdu_list) != 1:
        raise Exception("The FITS file should contain only one HDU.")

    image_array = hdu_list[0].data    # "hdu.data" is a Numpy Array

    hdu_list.close()

    return image_array

def get_colour_map_list():
    """Return the list of the available colormaps."""
    return sorted(plt.cm.datad)

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

class TkGUI:
    """
    TODO...
    """

    def __init__(self, root):
        """
        TODO...
        """

        self.file_path = None
        self.image_array = None

        # Matplotlib ##################

        self.fig = plt.figure(figsize=(8.0, 8.0))
        self.ax = None

        # Gui parameters ##############

        self._color_map = tk.StringVar()
        self._show_color_bar = tk.BooleanVar()

        # Make widgets ################

        self.root = root

        # Add a callback on WM_DELETE_WINDOW events
        self.root.protocol("WM_DELETE_WINDOW", self.quit)

        # Canvas
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
        self.canvas.get_tk_widget().pack(fill="both", expand=True)

        ## Buttons
        #quit_button = tk.Button(master=self.root, text='Quit', command=self.quit)
        #quit_button.pack(fill="x", expand=True)

        # Make a menubar ##############

        # Create a toplevel menu
        menubar = tk.Menu(self.root)

        # Create a pulldown menu: /File
        file_menu = tk.Menu(menubar, tearoff=0)
        file_menu.add_command(label="Open...", command=self.select_fits_file)
        file_menu.add_command(label="Close", command=self.close_fits_file)
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=self.quit)

        menubar.add_cascade(label="File", menu=file_menu)

        # Create a pulldown menu: /View
        view_menu = tk.Menu(menubar, tearoff=0)
        view_menu.add_checkbutton(label="Show color bar",
                                  variable=self._show_color_bar,
                                  command=self.draw_figure)

        menubar.add_cascade(label="View", menu=view_menu)

        # Create a pulldown menu: /View/Color Map
        colormap_menu = tk.Menu(view_menu, tearoff=0)

        for cmap_str in get_colour_map_list():
            colormap_menu.add_radiobutton(label=cmap_str,
                                          variable=self._color_map,
                                          value=cmap_str,
                                          command=self.draw_figure)

        view_menu.add_cascade(label="Color Map", menu=colormap_menu)

        # Display the menu
        # The config method is used to attach the menu to the root window. The
        # contents of that menu is used to create a menubar at the top of the root
        # window. There is no need to pack the menu, since it is automatically
        # displayed by Tkinter.
        self.root.config(menu=menubar)

    def run(self):
        """
        Launch the main loop (Tk event loop).
        """

        # TODO ???
        self.root.mainloop()

    def quit(self):
        self.root.quit()     # stops mainloop
        self.root.destroy()  # this is necessary on Windows to prevent
                             # Fatal Python Error: PyEval_RestoreThread: NULL tstate

    def select_fits_file(self):
        """
        Display a file dialog to select the FITS file to open.

        Return the path of the selected file.
        """

        # Check and parse the file

        # FILE_TYPES = [(label1, pattern1), (label2, pattern2), ...]
        FILE_TYPES = [
                    ('FITS Files', '.fits')
                ]

        HOME = os.path.expanduser("~")

        path = tk.filedialog.askopenfilename(parent=self.root,
                                             filetypes=FILE_TYPES,     # optional
                                             defaultextension='.fits', # optional
                                             initialdir=HOME,          # optional
                                             #initialfile='demo.fits',  # optional
                                             title='Select your file') # optional

        self.open_fits_file(path)

    def open_fits_file(self, file_path):
        """
        Open and display the given FITS file.
        """
        # READ THE INPUT FILE #################################################

        self.file_path = file_path
        self.image_array = get_image_array_from_fits_file(self.file_path)

        if self.image_array.ndim != 2:
            raise Exception("Unexpected error: the input FITS file should contain a 2D array.")

        self.root.title(self.file_path)
        self.draw_figure()

    def close_fits_file(self):
        self.file_path = None
        self.image_array = None
        self.clear_figure()

    def clear_figure(self):
        self.fig.clf()
        self.fig.canvas.draw()

    def draw_figure(self):
        if self.image_array is not None:
            self.fig.clf() # TODO
            self.ax = self.fig.add_subplot(111)

            #self.ax.set_title(self.file_path)

            im = self.ax.imshow(self.image_array,
                                origin='lower',
                                interpolation='nearest',
                                cmap=self.color_map)

            if self.show_color_bar:
                plt.colorbar(im) # draw the colorbar

            self.fig.canvas.draw()

    # PROPERTIES ##############################################################

    @property
    def show_color_bar(self):
        return self._show_color_bar.get()

    @show_color_bar.setter
    def show_color_bar(self, value):
        self._show_color_bar.set(value)

    @property
    def color_map(self):
        return self._color_map.get()

    @color_map.setter
    def color_map(self, value):
        self._color_map.set(value)

def main():

    root = tk.Tk()   # TODO ?
    gui = TkGUI(root)

    # PARSE OPTIONS ###########################################################

    parser = argparse.ArgumentParser(description="Display a FITS file.")

    parser.add_argument("--cmap", "-c", default=DEFAULT_COLOR_MAP, metavar="STRING",
            help="the colormap to use. The list of available color maps is available here: "
                 "http://matplotlib.org/examples/color/colormaps_reference.html")

    parser.add_argument("--hidecbar", "-H", action="store_true",
            help="hide the color bar")

    parser.add_argument("filearg", nargs="?", metavar="FILE", const=None,
            help="the FITS file to process")

    args = parser.parse_args()

    input_file_path = args.filearg

    # SET OPTIONS #############################################################

    gui.color_map = args.cmap
    gui.show_color_bar = not args.hidecbar

    if input_file_path is not None:
        gui.open_fits_file(input_file_path)

    # LAUNCH THE MAIN LOOP ####################################################

    gui.run()

if __name__ == "__main__":
    main()

<|endoftext|>