<commit_before>from plex.core.idict import idict
from plex.interfaces.core.base import Interface

class LibraryMetadataInterface(Interface):
    path = 'library/metadata'

    def all_leaves(self, key):
        response = self.http.get(key, 'allLeaves')

        return self.parse(response, idict({
            'MediaContainer': {
                '_': 'viewGroup',

                'episode': ('ShowLeavesContainer', idict({
                    'Video': {
                        'episode': 'Episode'
                    }
                })),

                'track': ('ArtistLeavesContainer', idict({
                    'Track': 'Track'
                }))
            }
        }))

    def children(self, key):
        response = self.http.get(key, 'children')

        return self.parse(response, idict({
            'MediaContainer': {
                '_': 'viewGroup',

                # ---------------------------------------
                # Music
                # ---------------------------------------
                'album': ('ArtistChildrenContainer', idict({
                    'Directory': {
                        'album': 'Album'
                    }
                })),

                'track': ('AlbumChildrenContainer', idict({
                    'Track': 'Track'
                })),

                # ---------------------------------------
                # TV
                # ---------------------------------------
                'season': ('ShowChildrenContainer', idict({
                    'Directory': {
                        'season': 'Season'
                    }
                })),

                'episode': ('SeasonChildrenContainer', idict({
                    'Video': {
                        'episode': 'Episode'
                    }
                }))
            }
        }))
<commit_msg>plex.py: add refresh endpoint to library/metadata<commit_after>from plex.core.idict import idict
from plex.interfaces.core.base import Interface

class LibraryMetadataInterface(Interface):
    path = 'library/metadata'

    def refresh(self, key):
	response = self.http.put(str(key) + "/refresh")

    def all_leaves(self, key):
        response = self.http.get(key, 'allLeaves')

        return self.parse(response, idict({
            'MediaContainer': {
                '_': 'viewGroup',

                'episode': ('ShowLeavesContainer', idict({
                    'Video': {
                        'episode': 'Episode'
                    }
                })),

                'track': ('ArtistLeavesContainer', idict({
                    'Track': 'Track'
                }))
            }
        }))

    def children(self, key):
        response = self.http.get(key, 'children')

        return self.parse(response, idict({
            'MediaContainer': {
                '_': 'viewGroup',

                # ---------------------------------------
                # Music
                # ---------------------------------------
                'album': ('ArtistChildrenContainer', idict({
                    'Directory': {
                        'album': 'Album'
                    }
                })),

                'track': ('AlbumChildrenContainer', idict({
                    'Track': 'Track'
                })),

                # ---------------------------------------
                # TV
                # ---------------------------------------
                'season': ('ShowChildrenContainer', idict({
                    'Directory': {
                        'season': 'Season'
                    }
                })),

                'episode': ('SeasonChildrenContainer', idict({
                    'Video': {
                        'episode': 'Episode'
                    }
                }))
            }
        }))
<|endoftext|>


<commit_before># coding utf-8
import logging
import collections
import threading
import re
import traceback
import datetime
import time

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
from telegram.contrib.botan import Botan
from meduzach.credentials import BOT_TOKEN

BOTAN_TOKEN = None
settings = {"track": True}

try:
    from meduzach.credentials import BOTAN_TOKEN as bt
    BOTAN_TOKEN = bt
except:
    pass

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.DEBUG)

MSG_LIMIT = 2048

SHORT_TITLE_LENGTH = 12
CHATS_CACHE_EXPIRE_TIME = datetime.timedelta(seconds=10)

TOGGLE_SUB_RE = re.compile('^/(\d+)$')

class UserState():
    def __init__(self):
        self.latest = None
        self.unsub_time = collections.defaultdict(int)
        self.chats = []

class context:
    meduzach = None
    chats_to_readers = collections.defaultdict(list)
    readers = collections.defaultdict(lambda: UserState())
    bot = None
    lock = threading.RLock()
    track_lock = threading.Lock()
    botan = Botan(BOTAN_TOKEN) if BOTAN_TOKEN is not None else None

def _track(user, action, payload=None):
    if not settings.get("track", False):
        return
    try:
        if BOTAN_TOKEN is not None:
            context.botan.track(user, payload, action)
        with context.track_lock:
            with open("track.txt", "a") as outf:
                print(
                    datetime.datetime.now().strftime("%d/%m/%y_%H:%M"),
                    user, action, payload, file=outf)
    except:
        traceback.print_exc()

def _restore_tracked():
    earliest = datetime.datetime.now() - datetime.timedelta(days=1)
    try:
        with open("track.txt") as infile:
            for line in infile:
                try:
                    date, user, action, chat_id = line.split()
                    if chat_id == "None":
                        continue
                    if chat_id not in context.meduzach.chats:
                        print("chat {} ended".format(chat_id))
                        continue
                    user = int(user)
                    print("so we proceed", action)
                    is_sub = None
                    if action == "sub":
                        is_sub = True
                    elif action == "unsub":
                        is_sub = False
                    if is_sub is None:
                        continue
                    print("so we proceed - 2", action)
                    date = datetime.datetime.strptime(date, "%d/%m/%y_%H:%M")
                    if date > earliest:
                        print("date ok")
                        try:
                            if is_sub:
                                print("sub!")
                                _sub(user, chat_id, False)
                            else:
                                _unsub(user, chat_id)
                        except IndexError:
                            print("No messages in chat")
                except:
                    traceback.print_exc()
    except:
        traceback.print_exc()

def _format_messages(messages):
    """
    Return a list of telegram messages
    representing given list of chat messages.

    Takes care of maximum telegram message length.
    """
    cur_msg = []
    cur_size = 0
    for message in messages:
        formatted = "[{author}] {text}".format(**message)
        fmt_size = len(formatted)
        if cur_size + fmt_size < MSG_LIMIT:
            cur_msg.append(formatted)
            cur_size += fmt_size
        else:
            yield "\n".join(cur_msg)
            cur_msg = [formatted]
            cur_size = fmt_size
    if cur_msg:
        yield "\n".join(cur_msg)

def process_chat_update(chat_id, messages):
    """
    Publish new message to all subscriptors.

    Called from meduzach thread.
    """
    if not context.meduzach.is_initialized:
        return
    with context.lock:
        short_title = context.meduzach.chats[chat_id]['title']
        if len(short_title) > SHORT_TITLE_LENGTH + 3:
            short_title = short_title[:SHORT_TITLE_LENGTH] + "..."
        header = ("Обновление чата /{} ({}):\n".format(
            chat_id, short_title))
        formatted_messages = list(_format_messages(messages))
        if len(formatted_messages) > 1 or len(
                formatted_messages[0]) + len(header) >= MSG_LIMIT:
            formatted_messages_h = [header] + formatted_messages
        else:
            formatted_messages_h = [header + formatted_messages[0]]
        for reader_chat_id in context.chats_to_readers[chat_id]:
            if context.readers[reader_chat_id].latest == chat_id:
                for msg in formatted_messages:
                    context.bot.sendMessage(reader_chat_id, msg)
            else:
                context.readers[reader_chat_id].latest = chat_id
                for msg in formatted_messages_h:
                    context.bot.sendMessage(reader_chat_id, msg)

def _sub(reader_id, chat_id, send_messages=True):
    """
    Add reader subscription to chat
    """
    if send_messages:
        messages = [
            m for m in context.meduzach.messages[chat_id]
            if m['inserted_at'] > context.readers[reader_id].unsub_time[chat_id]]
        if messages:
            for msg in _format_messages(messages):
                context.bot.sendMessage(
                    reader_id,
                    text=msg)
        context.readers[reader_id].latest = chat_id
    context.chats_to_readers[chat_id].append(
        reader_id)
    context.readers[reader_id].chats.append(
        chat_id)

def _unsub(reader_id, chat_id):
    """
    Remove subscription to chat from reader
    """
    context.chats_to_readers[chat_id].remove(reader_id)
    context.readers[reader_id].chats.remove(chat_id)
    context.readers[reader_id].unsub_time[chat_id] = (
        context.meduzach.messages[chat_id][-1]['inserted_at'])

###
# section Bot methods.
###

def chats(bot, update):
    """
    List available chats.

    /chats command
    """
    with context.lock:
        try:
            reader_id = update.message.chat_id
            print(context.readers[reader_id])
            print(context.readers)
            sorted_chats = sorted(
                list(context.meduzach.chats.items()),
                key=lambda c: -c[1]['last_message_at'])
            chat_text = "\n".join(
                "/{} [{}] {} ({})".format(
                    k,
                    '+' if k in context.readers[reader_id].chats else '-',
                    v['title'],
                    v['messages_count'])
                for k, v in sorted_chats)
            if not chat_text:
                chat_text = "Список пуст."
            bot.sendMessage(reader_id, text=chat_text)
        except:
            traceback.print_exc()
    _track(update.message.chat.id, 'chats')

def show_help(bot, update):
    """
    Show help.

    /help command
    """
    try:
        help_text = ("Список активных чатов: /chats\n"
                     "Чтобы подписаться или отписаться от обновлений чата, "
                     "нажмите на его номер в списке.\n\n"
                     "Если что, пишите @upppi\n"
                     "https://github.com/uppi/meduzach")
        bot.sendMessage(
            update.message.chat_id, text=help_text)
    except:
        traceback.print_exc()

    _track(update.message.chat.id, 'help')

def toggle_subscription(bot, update):
    """
    Add or remove subscription to chat.

    /123 command
    """
    action = "?"
    chat_id = None
    reader_id = update.message.chat_id
    with context.lock:
        try:
            match = TOGGLE_SUB_RE.match(update.message.text)
            if match is None:
                print('"{}"'.format(update.message.text))
                return
            chat_id = match.group(1)
            if chat_id in context.chats_to_readers:
                if reader_id in context.chats_to_readers[chat_id]:
                    bot.sendMessage(
                        reader_id,
                        "Вы отписались от /{}".format(chat_id))
                    _unsub(reader_id, chat_id)
                    action = "unsub"
                else:
                    bot.sendMessage(
                        reader_id,
                        "Вы подписаны на /{}\nhttps://meduza.io/{}".format(
                            chat_id,
                            context.meduzach.chats[chat_id]['key']))
                    _sub(reader_id, chat_id)
                    action = "sub"
        except:
            traceback.print_exc()
    _track(reader_id, action, chat_id)

###
# end section Bot methods.
###

def error(bot, update, error):
    logging.warning('Update "%s" caused error "%s"' % (update, error))

def run(meduzach, token):
    """
    Start telegram bot.
    """
    context.meduzach = meduzach
    context.token = token

    while not meduzach.is_initialized:
        time.sleep(3)
    _restore_tracked()

    updater = Updater(context.token)

    context.bot = updater.bot

    updater.dispatcher.add_handler(CommandHandler('help', show_help), group=0)
    updater.dispatcher.add_handler(CommandHandler('start', show_help), group=0)
    updater.dispatcher.add_handler(CommandHandler('chats', chats), group=0)

    updater.dispatcher.add_handler(
        MessageHandler([Filters.command], toggle_subscription), group=1)

    updater.dispatcher.add_error_handler(error)

    updater.start_polling()
    updater.idle()

def main():
    import meduzach.meduzach as m
    listener = m.Meduzach()
    listener.add_chat_update_action(process_chat_update)

    listener_thread = threading.Thread(
        target=lambda m: m.run(), args=(listener, ))
    listener_thread.start()
    run(listener, BOT_TOKEN)

if __name__ == '__main__':
    main()
<commit_msg>Fix chat_id search at toggle<commit_after># coding utf-8
import logging
import collections
import threading
import re
import traceback
import datetime
import time

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
from telegram.contrib.botan import Botan
from meduzach.credentials import BOT_TOKEN

BOTAN_TOKEN = None
settings = {"track": True}

try:
    from meduzach.credentials import BOTAN_TOKEN as bt
    BOTAN_TOKEN = bt
except:
    pass

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.DEBUG)

MSG_LIMIT = 2048

SHORT_TITLE_LENGTH = 12
CHATS_CACHE_EXPIRE_TIME = datetime.timedelta(seconds=10)

TOGGLE_SUB_RE = re.compile('^/(\d+)$')

class UserState():
    def __init__(self):
        self.latest = None
        self.unsub_time = collections.defaultdict(int)
        self.chats = []

class context:
    meduzach = None
    chats_to_readers = collections.defaultdict(list)
    readers = collections.defaultdict(lambda: UserState())
    bot = None
    lock = threading.RLock()
    track_lock = threading.Lock()
    botan = Botan(BOTAN_TOKEN) if BOTAN_TOKEN is not None else None

def _track(user, action, payload=None):
    if not settings.get("track", False):
        return
    try:
        if BOTAN_TOKEN is not None:
            context.botan.track(user, payload, action)
        with context.track_lock:
            with open("track.txt", "a") as outf:
                print(
                    datetime.datetime.now().strftime("%d/%m/%y_%H:%M"),
                    user, action, payload, file=outf)
    except:
        traceback.print_exc()

def _restore_tracked():
    earliest = datetime.datetime.now() - datetime.timedelta(days=1)
    try:
        with open("track.txt") as infile:
            for line in infile:
                try:
                    date, user, action, chat_id = line.split()
                    if chat_id == "None":
                        continue
                    if chat_id not in context.meduzach.chats:
                        print("chat {} ended".format(chat_id))
                        continue
                    user = int(user)
                    print("so we proceed", action)
                    is_sub = None
                    if action == "sub":
                        is_sub = True
                    elif action == "unsub":
                        is_sub = False
                    if is_sub is None:
                        continue
                    print("so we proceed - 2", action)
                    date = datetime.datetime.strptime(date, "%d/%m/%y_%H:%M")
                    if date > earliest:
                        print("date ok")
                        try:
                            if is_sub:
                                print("sub!")
                                _sub(user, chat_id, False)
                            else:
                                _unsub(user, chat_id)
                        except IndexError:
                            print("No messages in chat")
                except:
                    traceback.print_exc()
    except:
        traceback.print_exc()

def _format_messages(messages):
    """
    Return a list of telegram messages
    representing given list of chat messages.

    Takes care of maximum telegram message length.
    """
    cur_msg = []
    cur_size = 0
    for message in messages:
        formatted = "[{author}] {text}".format(**message)
        fmt_size = len(formatted)
        if cur_size + fmt_size < MSG_LIMIT:
            cur_msg.append(formatted)
            cur_size += fmt_size
        else:
            yield "\n".join(cur_msg)
            cur_msg = [formatted]
            cur_size = fmt_size
    if cur_msg:
        yield "\n".join(cur_msg)

def process_chat_update(chat_id, messages):
    """
    Publish new message to all subscriptors.

    Called from meduzach thread.
    """
    if not context.meduzach.is_initialized:
        return
    with context.lock:
        short_title = context.meduzach.chats[chat_id]['title']
        if len(short_title) > SHORT_TITLE_LENGTH + 3:
            short_title = short_title[:SHORT_TITLE_LENGTH] + "..."
        header = ("Обновление чата /{} ({}):\n".format(
            chat_id, short_title))
        formatted_messages = list(_format_messages(messages))
        if len(formatted_messages) > 1 or len(
                formatted_messages[0]) + len(header) >= MSG_LIMIT:
            formatted_messages_h = [header] + formatted_messages
        else:
            formatted_messages_h = [header + formatted_messages[0]]
        for reader_chat_id in context.chats_to_readers[chat_id]:
            if context.readers[reader_chat_id].latest == chat_id:
                for msg in formatted_messages:
                    context.bot.sendMessage(reader_chat_id, msg)
            else:
                context.readers[reader_chat_id].latest = chat_id
                for msg in formatted_messages_h:
                    context.bot.sendMessage(reader_chat_id, msg)

def _sub(reader_id, chat_id, send_messages=True):
    """
    Add reader subscription to chat
    """
    if send_messages:
        messages = [
            m for m in context.meduzach.messages[chat_id]
            if m['inserted_at'] > context.readers[reader_id].unsub_time[chat_id]]
        if messages:
            for msg in _format_messages(messages):
                context.bot.sendMessage(
                    reader_id,
                    text=msg)
        context.readers[reader_id].latest = chat_id
    context.chats_to_readers[chat_id].append(
        reader_id)
    context.readers[reader_id].chats.append(
        chat_id)

def _unsub(reader_id, chat_id):
    """
    Remove subscription to chat from reader
    """
    context.chats_to_readers[chat_id].remove(reader_id)
    context.readers[reader_id].chats.remove(chat_id)
    context.readers[reader_id].unsub_time[chat_id] = (
        context.meduzach.messages[chat_id][-1]['inserted_at'])

###
# section Bot methods.
###

def chats(bot, update):
    """
    List available chats.

    /chats command
    """
    with context.lock:
        try:
            reader_id = update.message.chat_id
            print(context.readers[reader_id])
            print(context.readers)
            sorted_chats = sorted(
                list(context.meduzach.chats.items()),
                key=lambda c: -c[1]['last_message_at'])
            chat_text = "\n".join(
                "/{} [{}] {} ({})".format(
                    k,
                    '+' if k in context.readers[reader_id].chats else '-',
                    v['title'],
                    v['messages_count'])
                for k, v in sorted_chats)
            if not chat_text:
                chat_text = "Список пуст."
            bot.sendMessage(reader_id, text=chat_text)
        except:
            traceback.print_exc()
    _track(update.message.chat.id, 'chats')

def show_help(bot, update):
    """
    Show help.

    /help command
    """
    try:
        help_text = ("Список активных чатов: /chats\n"
                     "Чтобы подписаться или отписаться от обновлений чата, "
                     "нажмите на его номер в списке.\n\n"
                     "Если что, пишите @upppi\n"
                     "https://github.com/uppi/meduzach")
        bot.sendMessage(
            update.message.chat_id, text=help_text)
    except:
        traceback.print_exc()

    _track(update.message.chat.id, 'help')

def toggle_subscription(bot, update):
    """
    Add or remove subscription to chat.

    /123 command
    """
    action = "?"
    chat_id = None
    reader_id = update.message.chat_id
    with context.lock:
        try:
            match = TOGGLE_SUB_RE.match(update.message.text)
            if match is None:
                print('"{}"'.format(update.message.text))
                return
            chat_id = match.group(1)
            if chat_id in context.meduzach.chats:
                if reader_id in context.chats_to_readers[chat_id]:
                    bot.sendMessage(
                        reader_id,
                        "Вы отписались от /{}".format(chat_id))
                    _unsub(reader_id, chat_id)
                    action = "unsub"
                else:
                    bot.sendMessage(
                        reader_id,
                        "Вы подписаны на /{}\nhttps://meduza.io/{}".format(
                            chat_id,
                            context.meduzach.chats[chat_id]['key']))
                    _sub(reader_id, chat_id)
                    action = "sub"
            else:
                bot.sendMessage(
                    reader_id,
                    "Чата /{} уже не существует.".format(chat_id))
        except:
            traceback.print_exc()
    _track(reader_id, action, chat_id)

###
# end section Bot methods.
###

def error(bot, update, error):
    logging.warning('Update "%s" caused error "%s"' % (update, error))

def run(meduzach, token):
    """
    Start telegram bot.
    """
    context.meduzach = meduzach
    context.token = token

    while not meduzach.is_initialized:
        time.sleep(3)
    _restore_tracked()

    updater = Updater(context.token)

    context.bot = updater.bot

    updater.dispatcher.add_handler(CommandHandler('help', show_help), group=0)
    updater.dispatcher.add_handler(CommandHandler('start', show_help), group=0)
    updater.dispatcher.add_handler(CommandHandler('chats', chats), group=0)

    updater.dispatcher.add_handler(
        MessageHandler([Filters.command], toggle_subscription), group=1)

    updater.dispatcher.add_error_handler(error)

    updater.start_polling()
    updater.idle()

def main():
    import meduzach.meduzach as m
    listener = m.Meduzach()
    listener.add_chat_update_action(process_chat_update)

    listener_thread = threading.Thread(
        target=lambda m: m.run(), args=(listener, ))
    listener_thread.start()
    run(listener, BOT_TOKEN)

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


<commit_before>"""Advanced tools for dense recursive polynomials in ``K[x]`` or ``K[X]``."""

from .densearith import (dmp_add, dmp_add_term, dmp_div, dmp_mul,
                         dmp_mul_ground, dmp_neg, dmp_sub, dup_add, dup_mul)
from .densebasic import (dmp_degree_in, dmp_from_dict, dmp_ground, dmp_LC,
                         dmp_to_dict, dmp_zero, dmp_zero_p)
from .polyerrors import DomainError

def dmp_diff_in(f, m, j, u, K):
    """``m``-th order derivative in ``x_j`` of a polynomial in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    return f.diff(x=j, m=m).to_dense()

def dmp_eval_in(f, a, j, u, K):
    """Evaluate a polynomial at ``x_j = a`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    r = f.eval(x=j, a=a)
    if ring.is_multivariate:
        r = r.to_dense()
    return r

def dmp_eval_tail(f, A, u, K):
    """Evaluate a polynomial at ``x_j = a_j, ...`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    x = [(x, a) for x, a in zip(ring.gens[-len(A):], A)]
    if not x:
        return f.to_dense()
    r = f.eval(x)
    if hasattr(r, 'ring'):
        r = r.to_dense()
    return r

def dmp_diff_eval_in(f, m, a, j, u, K):
    """Differentiate and evaluate a polynomial in ``x_j`` at ``a`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    r = f.diff(x=j, m=m).eval(x=j, a=a)
    return r.to_dense()

def dmp_ground_trunc(f, p, u, K):
    """Reduce a ``K[X]`` polynomial modulo a constant ``p`` in ``K``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    f = f.trunc_ground(p)
    return f.to_dense()

def dmp_ground_monic(f, u, K):
    """Divide all coefficients by ``LC(f)`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    f = f.monic()
    return f.to_dense()

def dmp_ground_content(f, u, K):
    """Compute the GCD of coefficients of ``f`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    return f.content()

def dmp_ground_primitive(f, u, K):
    """Compute content and the primitive form of ``f`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    cont, p = f.primitive()
    return cont, p.to_dense()

def dup_real_imag(f, K):
    """
    Return bivariate polynomials ``f1`` and ``f2``, such that ``f = f1 + f2*I``.

    Examples
    ========

    >>> R, x, y = ring('x y', ZZ)

    >>> R.dup_real_imag(x**3 + x**2 + x + 1)
    (x**3 + x**2 - 3*x*y**2 + x - y**2 + 1, 3*x**2*y + 2*x*y - y**3 + y)

    >>> R, x, y = ring('x y', QQ.algebraic_field(I))

    >>> R.dup_real_imag(x**2 + I*x - 1)
    (x**2 - y**2 - y - 1, 2*x*y + x)

    """
    if K.is_ComplexAlgebraicField:
        K0 = K.domain
        r1, i1 = dup_real_imag([_.real for _ in f], K0)
        r2, i2 = dup_real_imag([_.imag for _ in f], K0)
        return dmp_add(r1, dmp_neg(i2, 1, K0), 1, K0), dmp_add(r2, i1, 1, K0)
    elif not K.is_IntegerRing and not K.is_RationalField and not K.is_RealAlgebraicField:
        raise DomainError(f'computing real and imaginary parts is not supported over {K}')

    f1 = dmp_zero(1)
    f2 = dmp_zero(1)

    if not f:
        return f1, f2

    g = [[[K.one, K.zero]], [[K.one], []]]
    h = dmp_ground(f[0], 2)

    for c in f[1:]:
        h = dmp_mul(h, g, 2, K)
        h = dmp_add_term(h, dmp_ground(c, 1), 0, 2, K)

    H = dmp_to_dict(h, 0)

    for (k,), h in H.items():
        m = k % 4

        if not m:
            f1 = dmp_add(f1, h, 1, K)
        elif m == 1:
            f2 = dmp_add(f2, h, 1, K)
        elif m == 2:
            f1 = dmp_sub(f1, h, 1, K)
        else:
            f2 = dmp_sub(f2, h, 1, K)

    return f1, f2

def dup_transform(f, p, q, K):
    """
    Evaluate functional transformation ``q**n * f(p/q)`` in ``K[x]``.

    Examples
    ========

    >>> R, x = ring('x', ZZ)

    >>> R.dup_transform(x**2 - 2*x + 1, x**2 + 1, x - 1)
    x**4 - 2*x**3 + 5*x**2 - 4*x + 4

    """
    if not f:
        return []

    n = len(f) - 1
    h, Q = [f[0]], [[K.one]]

    for i in range(n):
        Q.append(dup_mul(Q[-1], q, K))

    for c, q in zip(f[1:], Q[1:]):
        h = dup_mul(h, p, K)
        q = dmp_mul_ground(q, c, 0, K)
        h = dup_add(h, q, K)

    return h

def dmp_compose(f, g, u, K):
    """
    Evaluate functional composition ``f(g)`` in ``K[X]``.

    Examples
    ========

    >>> R, x, y = ring('x y', ZZ)

    >>> R.dmp_compose(x*y + 2*x + y, y)
    y**2 + 3*y

    """
    if dmp_zero_p(f, u):
        return f

    h = [f[0]]

    for c in f[1:]:
        h = dmp_mul(h, g, u, K)
        h = dmp_add_term(h, c, 0, u, K)

    return h

def _dup_right_decompose(f, s, K):
    n = len(f) - 1
    lc = dmp_LC(f, K)

    f = dmp_to_dict(f, 0)
    g = {(s,): K.one}

    r = n // s

    for i in range(1, s):
        coeff = K.zero

        for j in range(i):
            if not (n + j - i,) in f:
                continue

            assert (s - j,) in g

            fc, gc = f[(n + j - i,)], g[(s - j,)]
            coeff += (i - r*j)*fc*gc

        g[(s - i,)] = K.quo(coeff, i*r*lc)

    return dmp_from_dict(g, 0, K)

def _dup_left_decompose(f, h, K):
    g, i = {}, 0

    while f:
        q, r = dmp_div(f, h, 0, K)

        if dmp_degree_in(r, 0, 0) > 0:
            return
        else:
            g[(i,)] = dmp_LC(r, K)
            f, i = q, i + 1

    return dmp_from_dict(g, 0, K)

def _dup_decompose(f, K):
    df = len(f) - 1

    for s in range(2, df):
        if df % s != 0:
            continue

        h = _dup_right_decompose(f, s, K)
        g = _dup_left_decompose(f, h, K)

        if g is not None:
            return g, h

def dup_decompose(f, K):
    """
    Compute functional decomposition of ``f`` in ``K[x]``.

    Given a univariate polynomial ``f`` with coefficients in a field of
    characteristic zero, returns list ``[f_1, f_2, ..., f_n]``, where::

              f = f_1 o f_2 o ... f_n = f_1(f_2(... f_n))

    and ``f_2, ..., f_n`` are monic and homogeneous polynomials of at
    least second degree.

    Unlike factorization, complete functional decompositions of
    polynomials are not unique, consider examples:

    1. ``f o g = f(x + b) o (g - b)``
    2. ``x**n o x**m = x**m o x**n``
    3. ``T_n o T_m = T_m o T_n``

    where ``T_n`` and ``T_m`` are Chebyshev polynomials.

    Examples
    ========

    >>> R, x = ring('x', ZZ)

    >>> (x**4 - 2*x**3 + x**2).decompose()
    [x**2, x**2 - x]

    References
    ==========

    * :cite:`Kozen1989decomposition`

    """
    F = []

    while True:
        result = _dup_decompose(f, K)

        if result is not None:
            f, h = result
            F = [h] + F
        else:
            break

    return [f] + F

def dmp_clear_denoms(f, u, K, convert=False):
    """Clear denominators."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    common, f = f.clear_denoms(convert=convert)
    return common, f.to_dense()
<commit_msg>polys: simplify dmp_eval_tail()<commit_after>"""Advanced tools for dense recursive polynomials in ``K[x]`` or ``K[X]``."""

from .densearith import (dmp_add, dmp_add_term, dmp_div, dmp_mul,
                         dmp_mul_ground, dmp_neg, dmp_sub, dup_add, dup_mul)
from .densebasic import (dmp_degree_in, dmp_from_dict, dmp_ground, dmp_LC,
                         dmp_to_dict, dmp_zero, dmp_zero_p)
from .polyerrors import DomainError

def dmp_diff_in(f, m, j, u, K):
    """``m``-th order derivative in ``x_j`` of a polynomial in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    return f.diff(x=j, m=m).to_dense()

def dmp_eval_in(f, a, j, u, K):
    """Evaluate a polynomial at ``x_j = a`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    r = f.eval(x=j, a=a)
    if ring.is_multivariate:
        r = r.to_dense()
    return r

def dmp_eval_tail(f, A, u, K):
    """Evaluate a polynomial at ``x_j = a_j, ...`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    if not A:
        return f.to_dense()
    r = f.eval(list(zip(reversed(ring.gens), reversed(A))))
    if hasattr(r, 'ring'):
        r = r.to_dense()
    return r

def dmp_diff_eval_in(f, m, a, j, u, K):
    """Differentiate and evaluate a polynomial in ``x_j`` at ``a`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    r = f.diff(x=j, m=m).eval(x=j, a=a)
    return r.to_dense()

def dmp_ground_trunc(f, p, u, K):
    """Reduce a ``K[X]`` polynomial modulo a constant ``p`` in ``K``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    f = f.trunc_ground(p)
    return f.to_dense()

def dmp_ground_monic(f, u, K):
    """Divide all coefficients by ``LC(f)`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    f = f.monic()
    return f.to_dense()

def dmp_ground_content(f, u, K):
    """Compute the GCD of coefficients of ``f`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    return f.content()

def dmp_ground_primitive(f, u, K):
    """Compute content and the primitive form of ``f`` in ``K[X]``."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    cont, p = f.primitive()
    return cont, p.to_dense()

def dup_real_imag(f, K):
    """
    Return bivariate polynomials ``f1`` and ``f2``, such that ``f = f1 + f2*I``.

    Examples
    ========

    >>> R, x, y = ring('x y', ZZ)

    >>> R.dup_real_imag(x**3 + x**2 + x + 1)
    (x**3 + x**2 - 3*x*y**2 + x - y**2 + 1, 3*x**2*y + 2*x*y - y**3 + y)

    >>> R, x, y = ring('x y', QQ.algebraic_field(I))

    >>> R.dup_real_imag(x**2 + I*x - 1)
    (x**2 - y**2 - y - 1, 2*x*y + x)

    """
    if K.is_ComplexAlgebraicField:
        K0 = K.domain
        r1, i1 = dup_real_imag([_.real for _ in f], K0)
        r2, i2 = dup_real_imag([_.imag for _ in f], K0)
        return dmp_add(r1, dmp_neg(i2, 1, K0), 1, K0), dmp_add(r2, i1, 1, K0)
    elif not K.is_IntegerRing and not K.is_RationalField and not K.is_RealAlgebraicField:
        raise DomainError(f'computing real and imaginary parts is not supported over {K}')

    f1 = dmp_zero(1)
    f2 = dmp_zero(1)

    if not f:
        return f1, f2

    g = [[[K.one, K.zero]], [[K.one], []]]
    h = dmp_ground(f[0], 2)

    for c in f[1:]:
        h = dmp_mul(h, g, 2, K)
        h = dmp_add_term(h, dmp_ground(c, 1), 0, 2, K)

    H = dmp_to_dict(h, 0)

    for (k,), h in H.items():
        m = k % 4

        if not m:
            f1 = dmp_add(f1, h, 1, K)
        elif m == 1:
            f2 = dmp_add(f2, h, 1, K)
        elif m == 2:
            f1 = dmp_sub(f1, h, 1, K)
        else:
            f2 = dmp_sub(f2, h, 1, K)

    return f1, f2

def dup_transform(f, p, q, K):
    """
    Evaluate functional transformation ``q**n * f(p/q)`` in ``K[x]``.

    Examples
    ========

    >>> R, x = ring('x', ZZ)

    >>> R.dup_transform(x**2 - 2*x + 1, x**2 + 1, x - 1)
    x**4 - 2*x**3 + 5*x**2 - 4*x + 4

    """
    if not f:
        return []

    n = len(f) - 1
    h, Q = [f[0]], [[K.one]]

    for i in range(n):
        Q.append(dup_mul(Q[-1], q, K))

    for c, q in zip(f[1:], Q[1:]):
        h = dup_mul(h, p, K)
        q = dmp_mul_ground(q, c, 0, K)
        h = dup_add(h, q, K)

    return h

def dmp_compose(f, g, u, K):
    """
    Evaluate functional composition ``f(g)`` in ``K[X]``.

    Examples
    ========

    >>> R, x, y = ring('x y', ZZ)

    >>> R.dmp_compose(x*y + 2*x + y, y)
    y**2 + 3*y

    """
    if dmp_zero_p(f, u):
        return f

    h = [f[0]]

    for c in f[1:]:
        h = dmp_mul(h, g, u, K)
        h = dmp_add_term(h, c, 0, u, K)

    return h

def _dup_right_decompose(f, s, K):
    n = len(f) - 1
    lc = dmp_LC(f, K)

    f = dmp_to_dict(f, 0)
    g = {(s,): K.one}

    r = n // s

    for i in range(1, s):
        coeff = K.zero

        for j in range(i):
            if not (n + j - i,) in f:
                continue

            assert (s - j,) in g

            fc, gc = f[(n + j - i,)], g[(s - j,)]
            coeff += (i - r*j)*fc*gc

        g[(s - i,)] = K.quo(coeff, i*r*lc)

    return dmp_from_dict(g, 0, K)

def _dup_left_decompose(f, h, K):
    g, i = {}, 0

    while f:
        q, r = dmp_div(f, h, 0, K)

        if dmp_degree_in(r, 0, 0) > 0:
            return
        else:
            g[(i,)] = dmp_LC(r, K)
            f, i = q, i + 1

    return dmp_from_dict(g, 0, K)

def _dup_decompose(f, K):
    df = len(f) - 1

    for s in range(2, df):
        if df % s != 0:
            continue

        h = _dup_right_decompose(f, s, K)
        g = _dup_left_decompose(f, h, K)

        if g is not None:
            return g, h

def dup_decompose(f, K):
    """
    Compute functional decomposition of ``f`` in ``K[x]``.

    Given a univariate polynomial ``f`` with coefficients in a field of
    characteristic zero, returns list ``[f_1, f_2, ..., f_n]``, where::

              f = f_1 o f_2 o ... f_n = f_1(f_2(... f_n))

    and ``f_2, ..., f_n`` are monic and homogeneous polynomials of at
    least second degree.

    Unlike factorization, complete functional decompositions of
    polynomials are not unique, consider examples:

    1. ``f o g = f(x + b) o (g - b)``
    2. ``x**n o x**m = x**m o x**n``
    3. ``T_n o T_m = T_m o T_n``

    where ``T_n`` and ``T_m`` are Chebyshev polynomials.

    Examples
    ========

    >>> R, x = ring('x', ZZ)

    >>> (x**4 - 2*x**3 + x**2).decompose()
    [x**2, x**2 - x]

    References
    ==========

    * :cite:`Kozen1989decomposition`

    """
    F = []

    while True:
        result = _dup_decompose(f, K)

        if result is not None:
            f, h = result
            F = [h] + F
        else:
            break

    return [f] + F

def dmp_clear_denoms(f, u, K, convert=False):
    """Clear denominators."""
    ring = K.poly_ring(*[f'_{i}' for i in range(u + 1)])
    f = ring.from_list(f)
    common, f = f.clear_denoms(convert=convert)
    return common, f.to_dense()
<|endoftext|>


<commit_before>#!/usr/bin/env python
#########################################################################################
#
# msct_types
# This file contains many useful (and tiny) classes corresponding to data types.
# Large data types with many options have their own file (e.g., msct_image)
#
# ---------------------------------------------------------------------------------------
# Copyright (c) 2013 Polytechnique Montreal <www.neuro.polymtl.ca>
# Author: Benjamin De Leener
# Created: 2015-02-10
# Last modified: 2015-02-10
#
# About the license: see the file LICENSE.TXT
#########################################################################################

from math import sqrt

class Point(object):
    def __init__(self):
        self.x = 0
        self.y = 0
        self.z = 0

    # Euclidean distance
    def euclideanDistance(self, other_point):
        return sqrt(pow((self.x - self.x), 2) + pow((self.y - self.y), 2) + pow((self.z - self.z), 2))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.x == other.x and self.y == other.y and self.z == other.z
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

class Coordinate(Point):
    def __init__(self, coord=None):
        super(Coordinate, self).__init__()
        if coord is None:
            self.value = 0
            return

        if not isinstance(coord, list) and not isinstance(coord, str):
            raise TypeError("Coordinates parameter must be a list with coordinates [x, y, z] or [x, y, z, value] or a string with coordinates delimited by commas.")

        if isinstance(coord, str):
            # coordinate as a string. Values delimited by a comma.
            coord = coord.split(',')

        if len(coord) not in [3,4]:
            raise TypeError("Parameter must be a list with coordinates [x, y, z] or [x, y, z, value].")

        self.x = coord[0]
        self.y = coord[1]
        self.z = coord[2]
        if len(coord) == 4:
            self.value = coord[3]
        else:
            self.value = 0
        # coordinates and value must be digits:
        try:
            int(self.x), int(self.y), int(self.z), int(self.value)
        except ValueError:
            raise TypeError("All coordinates and value must be digits. x="+str(self.x)+", y="+str(self.y)+", z="+str(self.z)+", value="+str(self.value))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.x == other.x and self.y == other.y and self.z == other.z
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

    def hasEqualValue(self, other):
        return self.value == other.value
<commit_msg>OPT: now allow float input value and not just int<commit_after>#!/usr/bin/env python
#########################################################################################
#
# msct_types
# This file contains many useful (and tiny) classes corresponding to data types.
# Large data types with many options have their own file (e.g., msct_image)
#
# ---------------------------------------------------------------------------------------
# Copyright (c) 2013 Polytechnique Montreal <www.neuro.polymtl.ca>
# Author: Benjamin De Leener
# Created: 2015-02-10
# Last modified: 2015-02-10
#
# About the license: see the file LICENSE.TXT
#########################################################################################

from math import sqrt

class Point(object):
    def __init__(self):
        self.x = 0
        self.y = 0
        self.z = 0

    # Euclidean distance
    def euclideanDistance(self, other_point):
        return sqrt(pow((self.x - self.x), 2) + pow((self.y - self.y), 2) + pow((self.z - self.z), 2))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.x == other.x and self.y == other.y and self.z == other.z
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

class Coordinate(Point):
    def __init__(self, coord=None):
        super(Coordinate, self).__init__()
        if coord is None:
            self.value = 0
            return

        if not isinstance(coord, list) and not isinstance(coord, str):
            raise TypeError("Coordinates parameter must be a list with coordinates [x, y, z] or [x, y, z, value] or a string with coordinates delimited by commas.")

        if isinstance(coord, str):
            # coordinate as a string. Values delimited by a comma.
            coord = coord.split(',')

        if len(coord) not in [3,4]:
            raise TypeError("Parameter must be a list with coordinates [x, y, z] or [x, y, z, value].")

        self.x = coord[0]
        self.y = coord[1]
        self.z = coord[2]
        if len(coord) == 4:
            self.value = coord[3]
        else:
            self.value = 0
        # coordinates and value must be digits:
        try:
            int(self.x), int(self.y), int(self.z), float(self.value)
        except ValueError:
            raise TypeError("All coordinates must be int and the value can be a float or a int. x="+str(self.x)+", y="+str(self.y)+", z="+str(self.z)+", value="+str(self.value))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.x == other.x and self.y == other.y and self.z == other.z
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

    def hasEqualValue(self, other):
        return self.value == other.value
<|endoftext|>


<commit_before>""" Testing vizualization with fvtk
"""
import numpy as np

from dipy.viz import fvtk

from numpy.testing import assert_array_almost_equal, assert_
import numpy.testing as npt

@npt.dec.skipif(not fvtk.have_vtk)
def test_fvtk_functions():

    # Create a renderer
    r = fvtk.ren()

    # Create 2 lines with 2 different colors
    lines = [np.random.rand(10, 3), np.random.rand(20, 3)]
    colors = np.random.rand(2, 3)
    c = fvtk.line(lines, colors)
    fvtk.add(r, c)

    # Create a volume and return a volumetric actor using volumetric rendering
    vol = 100 * np.random.rand(100, 100, 100)
    vol = vol.astype('uint8')
    r = fvtk.ren()
    v = fvtk.volume(vol)
    fvtk.add(r, v)

    # Remove all objects
    fvtk.rm_all(r)

    # Put some text
    l = fvtk.label(r, text='Yes Men')
    fvtk.add(r, l)

    # Show everything
    # fvtk.show(r)

@npt.dec.skipif(not fvtk.have_vtk)
def test_fvtk_ellipsoid():

    evals = np.array([1.4, .35, .35]) * 10 ** (-3)
    evecs = np.eye(3)

    mevals = np.zeros((3, 2, 4, 3))
    mevecs = np.zeros((3, 2, 4, 3, 3))

    mevals[..., :] = evals
    mevecs[..., :, :] = evecs

    from dipy.data import get_sphere

    sphere = get_sphere('symmetric724')
    ren = fvtk.ren()
    fvtk.add(ren, fvtk.tensor(mevals, mevecs, sphere))

    fvtk.show(ren)

<commit_msg>Removed show from test<commit_after>""" Testing vizualization with fvtk
"""
import numpy as np

from dipy.viz import fvtk

from numpy.testing import assert_array_almost_equal, assert_
import numpy.testing as npt

@npt.dec.skipif(not fvtk.have_vtk)
def test_fvtk_functions():

    # Create a renderer
    r = fvtk.ren()

    # Create 2 lines with 2 different colors
    lines = [np.random.rand(10, 3), np.random.rand(20, 3)]
    colors = np.random.rand(2, 3)
    c = fvtk.line(lines, colors)
    fvtk.add(r, c)

    # Create a volume and return a volumetric actor using volumetric rendering
    vol = 100 * np.random.rand(100, 100, 100)
    vol = vol.astype('uint8')
    r = fvtk.ren()
    v = fvtk.volume(vol)
    fvtk.add(r, v)

    # Remove all objects
    fvtk.rm_all(r)

    # Put some text
    l = fvtk.label(r, text='Yes Men')
    fvtk.add(r, l)

    # Show everything
    # fvtk.show(r)

@npt.dec.skipif(not fvtk.have_vtk)
def test_fvtk_ellipsoid():

    evals = np.array([1.4, .35, .35]) * 10 ** (-3)
    evecs = np.eye(3)

    mevals = np.zeros((3, 2, 4, 3))
    mevecs = np.zeros((3, 2, 4, 3, 3))

    mevals[..., :] = evals
    mevecs[..., :, :] = evecs

    from dipy.data import get_sphere

    sphere = get_sphere('symmetric724')
    ren = fvtk.ren()
    fvtk.add(ren, fvtk.tensor(mevals, mevecs, sphere))

    #fvtk.show(ren)

<|endoftext|>


<commit_before>import itertools

import django_filters
from django.apps import apps
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.db.models import Max
from django.db.models import Min
from django.shortcuts import get_object_or_404
from django.shortcuts import redirect
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from django.views import generic
from rules.contrib.views import LoginRequiredMixin
from rules.contrib.views import PermissionRequiredMixin

from adhocracy4.administrative_districts.models import AdministrativeDistrict
from adhocracy4.dashboard import mixins as a4dashboard_mixins
from adhocracy4.dashboard import signals as a4dashboard_signals
from adhocracy4.filters import widgets as filters_widgets
from adhocracy4.filters.filters import DefaultsFilterSet
from adhocracy4.filters.filters import DistinctOrderingFilter
from adhocracy4.filters.filters import FreeTextFilter
from adhocracy4.filters.widgets import DropdownLinkWidget
from adhocracy4.projects import models as project_models
from adhocracy4.projects.mixins import ProjectMixin

from . import forms
from . import models

User = get_user_model()

class OrderingWidget(DropdownLinkWidget):
    label = _('Ordering')
    right = True

class OrganisationWidget(DropdownLinkWidget):
    label = _('Organisation')

class DistrictWidget(DropdownLinkWidget):
    label = _('District')

class FreeTextFilterWidget(filters_widgets.FreeTextFilterWidget):
    label = _('Search')

class ArchivedWidget(DropdownLinkWidget):
    label = _('Archived')

    def __init__(self, attrs=None):
        choices = (
            ('', _('All')),
            ('false', _('No')),
            ('true', _('Yes')),
        )
        super().__init__(attrs, choices)

class YearWidget(DropdownLinkWidget):
    label = _('Year')

    def __init__(self, attrs=None):
        choices = (('', _('Any')),)
        now = timezone.now().year
        try:
            first_year = project_models.Project.objects.earliest('created').\
                created.year
        except project_models.Project.DoesNotExist:
            first_year = now
        for year in range(now, first_year - 1, -1):
            choices += (year, year),
        super().__init__(attrs, choices)

class ProjectFilterSet(DefaultsFilterSet):

    defaults = {
        'is_archived': 'false'
    }

    ordering = DistinctOrderingFilter(
        choices=(
            ('-created', _('Most recent')),
        ),
        empty_label=None,
        widget=OrderingWidget,
    )

    search = FreeTextFilter(
        widget=FreeTextFilterWidget,
        fields=['name', 'description',
                'projectcontainer__projects__name']
    )

    organisation = django_filters.ModelChoiceFilter(
        queryset=apps.get_model(settings.A4_ORGANISATIONS_MODEL).objects
                     .order_by('name'),
        widget=OrganisationWidget,
    )

    is_archived = django_filters.BooleanFilter(
        widget=ArchivedWidget
    )

    created = django_filters.NumberFilter(
        field_name='created',
        lookup_expr='year',
        widget=YearWidget,
    )

    administrative_district = django_filters.ModelChoiceFilter(
        queryset=AdministrativeDistrict.objects.all(),
        widget=DistrictWidget
    )

    class Meta:
        model = project_models.Project
        fields = ['search', 'organisation', 'is_archived',
                  'created', 'administrative_district']

class ParticipantInviteDetailView(generic.DetailView):
    model = models.ParticipantInvite
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def dispatch(self, request, invite_token, *args, **kwargs):
        if request.user.is_authenticated():
            return redirect(
                'project-participant-invite-update',
                invite_token=invite_token
            )
        else:
            return super().dispatch(request, *args, **kwargs)

class ParticipantInviteUpdateView(LoginRequiredMixin, generic.UpdateView):
    model = models.ParticipantInvite
    form_class = forms.ParticipantInviteForm
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def form_valid(self, form):
        if form.is_accepted():
            form.instance.accept(self.request.user)
            return redirect(form.instance.project.get_absolute_url())
        else:
            form.instance.reject()
            return redirect('/')

class ModeratorInviteDetailView(generic.DetailView):
    model = models.ModeratorInvite
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def dispatch(self, request, invite_token, *args, **kwargs):
        if request.user.is_authenticated():
            return redirect(
                'project-moderator-invite-update',
                invite_token=invite_token
            )
        else:
            return super().dispatch(request, *args, **kwargs)

class ModeratorInviteUpdateView(LoginRequiredMixin, generic.UpdateView):
    model = models.ModeratorInvite
    form_class = forms.ModeratorInviteForm
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def form_valid(self, form):
        if form.is_accepted():
            form.instance.accept(self.request.user)
            return redirect(form.instance.project.get_absolute_url())
        else:
            form.instance.reject()
            return redirect('/')

class AbstractProjectUserInviteListView(
        ProjectMixin,
        a4dashboard_mixins.DashboardBaseMixin,
        a4dashboard_mixins.DashboardComponentMixin,
        generic.base.TemplateResponseMixin,
        generic.edit.FormMixin,
        generic.detail.SingleObjectMixin,
        generic.edit.ProcessFormView):

    form_class = forms.InviteUsersFromEmailForm
    invite_model = None

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        if 'submit_action' in request.POST:
            if request.POST['submit_action'] == 'remove_user':
                pk = int(request.POST['user_pk'])
                user = get_object_or_404(User, pk=pk)
                related_users = getattr(self.object, self.related_users_field)
                related_users.remove(user)
                messages.success(request, self.success_message_removal)
            elif request.POST['submit_action'] == 'remove_invite':
                pk = int(request.POST['invite_pk'])
                invite = self.invite_model.objects.get(pk=pk)
                invite.delete()
                messages.success(request, _('Invitation succesfully removed.'))

            response = redirect(self.get_success_url())
        else:
            response = super().post(request, *args, **kwargs)

        self._send_component_updated_signal()
        return response

    def filter_existing(self, emails):
        related_users = getattr(self.object, self.related_users_field)
        related_emails = [u.email for u in related_users.all()]
        existing = []
        filtered_emails = []
        for email in emails:
            if email in related_emails:
                existing.append(email)
            else:
                filtered_emails.append(email)
        return filtered_emails, existing

    def filter_pending(self, emails):
        pending = []
        filtered_emails = []
        for email in emails:
            if self.invite_model.objects.filter(email=email,
                                                project=self.project).exists():
                pending.append(email)
            else:
                filtered_emails.append(email)
        return filtered_emails, pending

    def form_valid(self, form):
        emails = list(set(
            itertools.chain(form.cleaned_data['add_users'],
                            form.cleaned_data['add_users_upload'])))

        emails, existing = self.filter_existing(emails)
        if existing:
            messages.error(
                self.request,
                _('Following users already accepted an invitation: ') +
                ', '.join(existing)
            )

        emails, pending = self.filter_pending(emails)
        if pending:
            messages.error(
                self.request,
                _('Following users are already invited: ') +
                ', '.join(pending)
            )

        for email in emails:
            self.invite_model.objects.invite(
                self.request.user,
                self.project,
                email
            )

        messages.success(
            self.request,
            ungettext(self.success_message[0], self.success_message[1],
                      len(emails)).format(len(emails))
        )

        return redirect(self.get_success_url())

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['labels'] = (self.add_user_field_label,
                            self.add_user_upload_field_label)
        return kwargs

    def _send_component_updated_signal(self):
        a4dashboard_signals.project_component_updated.send(
            sender=self.component.__class__,
            project=self.project,
            component=self.component,
            user=self.request.user
        )

class DashboardProjectModeratorsView(AbstractProjectUserInviteListView):

    model = project_models.Project
    slug_url_kwarg = 'project_slug'
    template_name = 'meinberlin_projects/project_moderators.html'
    permission_required = 'a4projects.change_project'
    menu_item = 'project'

    related_users_field = 'moderators'
    add_user_field_label = _('Invite moderators via email')
    add_user_upload_field_label = _('Invite moderators via file upload')
    success_message = (_('{} moderator invited.'), _('{} moderators invited.'))
    success_message_removal = _('Moderator successfully removed.')

    invite_model = models.ModeratorInvite

    def get_permission_object(self):
        return self.project

class DashboardProjectParticipantsView(AbstractProjectUserInviteListView):

    model = project_models.Project
    slug_url_kwarg = 'project_slug'
    template_name = 'meinberlin_projects/project_participants.html'
    permission_required = 'a4projects.change_project'
    menu_item = 'project'

    related_users_field = 'participants'
    add_user_field_label = _('Invite users via email')
    add_user_upload_field_label = _('Invite users via file upload')
    success_message = (
        _('{} participant invited.'),
        _('{} participants invited.'))
    success_message_removal = _('Participant successfully removed.')

    invite_model = models.ParticipantInvite

    def get_permission_object(self):
        return self.project

class ProjectDetailView(PermissionRequiredMixin,
                        generic.DetailView):

    model = models.Project
    permission_required = 'a4projects.view_project'
    template_name = 'meinberlin_projects/project_detail.html'

    @cached_property
    def project(self):
        return self.get_object()

    @cached_property
    def module(self):
        return self.project.last_active_module

    @cached_property
    def modules(self):
        return self.project.modules\
            .annotate(start_date=Min('phase__start_date'))\
            .annotate(end_date=Max('phase__end_date'))\
            .order_by('start_date')

    def get_module_dict(self, count, start_date):
        return {
            'title': 'Onlinebeteiligung {}'.format(str(count)),
            'type': 'module',
            'date': start_date
        }

    def get_current_modules(self):
        now = timezone.now()
        return self.modules.filter(
            start_date__lte=now,
            end_date__gte=now)

    def get_module_cluster(self):
        modules = self.modules
        start_date = modules.first().start_date
        end_date = modules.first().end_date
        count = 1
        clusters = []
        first_cluster = self.get_module_dict(count, start_date)
        clusters.append(first_cluster)

        for module in modules[1:]:
            if module.start_date > end_date:
                start_date = module.start_date
                end_date = module.end_date
                count += 1
                next_cluster = self.get_module_dict(count, start_date)
                clusters.append(next_cluster)
        return clusters

    def get_events_list(self):
        return self.project.offlineevent_set.values('date')

    def get_full_list(self):
        module_cluster = self.get_module_cluster()
        event_list = self.get_events_list()
        full_list = module_cluster + list(event_list)
        return sorted(full_list, key=lambda k: k['date'])

    def dispatch(self, request, *args, **kwargs):
        # Choose the appropriate view for the current active phase.
        kwargs['project'] = self.project
        kwargs['module'] = self.module

        if self.modules.count() == 1:
            return self._view_by_phase()(request, *args, **kwargs)
        else:
            return super().dispatch(request)

    def _view_by_phase(self):
        if self.module and self.module.last_active_phase:
            return self.module.last_active_phase.view.as_view()
        else:
            return super().dispatch

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['modules'] = self.get_current_modules()
        context['participation_dates'] = self.get_full_list()
        return context

    @property
    def raise_exception(self):
        return self.request.user.is_authenticated

class ModuleDetailview(PermissionRequiredMixin,
                       PhaseDispatchMixin):

    model = module_models.Module
    permission_required = 'a4projects.view_project'
    slug_url_kwarg = 'module_slug'

    @cached_property
    def project(self):
        return self.module.project

    @cached_property
    def module(self):
        return self.get_object()

    def get_permission_object(self):
        return self.project

    def get_context_data(self, **kwargs):
        """Append project and module to the template context."""
        if 'project' not in kwargs:
            kwargs['project'] = self.project
        if 'module' not in kwargs:
            kwargs['module'] = self.module
        return super().get_context_data(**kwargs)
<commit_msg>apps/project: also display timeline if there is one module and one event<commit_after>import itertools

import django_filters
from django.apps import apps
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.db.models import Max
from django.db.models import Min
from django.shortcuts import get_object_or_404
from django.shortcuts import redirect
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from django.views import generic
from rules.contrib.views import LoginRequiredMixin
from rules.contrib.views import PermissionRequiredMixin

from adhocracy4.administrative_districts.models import AdministrativeDistrict
from adhocracy4.dashboard import mixins as a4dashboard_mixins
from adhocracy4.dashboard import signals as a4dashboard_signals
from adhocracy4.filters import widgets as filters_widgets
from adhocracy4.filters.filters import DefaultsFilterSet
from adhocracy4.filters.filters import DistinctOrderingFilter
from adhocracy4.filters.filters import FreeTextFilter
from adhocracy4.filters.widgets import DropdownLinkWidget
from adhocracy4.modules import models as module_models
from adhocracy4.projects import models as project_models
from adhocracy4.projects.mixins import PhaseDispatchMixin
from adhocracy4.projects.mixins import ProjectMixin

from . import forms
from . import models

User = get_user_model()

class OrderingWidget(DropdownLinkWidget):
    label = _('Ordering')
    right = True

class OrganisationWidget(DropdownLinkWidget):
    label = _('Organisation')

class DistrictWidget(DropdownLinkWidget):
    label = _('District')

class FreeTextFilterWidget(filters_widgets.FreeTextFilterWidget):
    label = _('Search')

class ArchivedWidget(DropdownLinkWidget):
    label = _('Archived')

    def __init__(self, attrs=None):
        choices = (
            ('', _('All')),
            ('false', _('No')),
            ('true', _('Yes')),
        )
        super().__init__(attrs, choices)

class YearWidget(DropdownLinkWidget):
    label = _('Year')

    def __init__(self, attrs=None):
        choices = (('', _('Any')),)
        now = timezone.now().year
        try:
            first_year = project_models.Project.objects.earliest('created').\
                created.year
        except project_models.Project.DoesNotExist:
            first_year = now
        for year in range(now, first_year - 1, -1):
            choices += (year, year),
        super().__init__(attrs, choices)

class ProjectFilterSet(DefaultsFilterSet):

    defaults = {
        'is_archived': 'false'
    }

    ordering = DistinctOrderingFilter(
        choices=(
            ('-created', _('Most recent')),
        ),
        empty_label=None,
        widget=OrderingWidget,
    )

    search = FreeTextFilter(
        widget=FreeTextFilterWidget,
        fields=['name', 'description',
                'projectcontainer__projects__name']
    )

    organisation = django_filters.ModelChoiceFilter(
        queryset=apps.get_model(settings.A4_ORGANISATIONS_MODEL).objects
                     .order_by('name'),
        widget=OrganisationWidget,
    )

    is_archived = django_filters.BooleanFilter(
        widget=ArchivedWidget
    )

    created = django_filters.NumberFilter(
        field_name='created',
        lookup_expr='year',
        widget=YearWidget,
    )

    administrative_district = django_filters.ModelChoiceFilter(
        queryset=AdministrativeDistrict.objects.all(),
        widget=DistrictWidget
    )

    class Meta:
        model = project_models.Project
        fields = ['search', 'organisation', 'is_archived',
                  'created', 'administrative_district']

class ParticipantInviteDetailView(generic.DetailView):
    model = models.ParticipantInvite
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def dispatch(self, request, invite_token, *args, **kwargs):
        if request.user.is_authenticated():
            return redirect(
                'project-participant-invite-update',
                invite_token=invite_token
            )
        else:
            return super().dispatch(request, *args, **kwargs)

class ParticipantInviteUpdateView(LoginRequiredMixin, generic.UpdateView):
    model = models.ParticipantInvite
    form_class = forms.ParticipantInviteForm
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def form_valid(self, form):
        if form.is_accepted():
            form.instance.accept(self.request.user)
            return redirect(form.instance.project.get_absolute_url())
        else:
            form.instance.reject()
            return redirect('/')

class ModeratorInviteDetailView(generic.DetailView):
    model = models.ModeratorInvite
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def dispatch(self, request, invite_token, *args, **kwargs):
        if request.user.is_authenticated():
            return redirect(
                'project-moderator-invite-update',
                invite_token=invite_token
            )
        else:
            return super().dispatch(request, *args, **kwargs)

class ModeratorInviteUpdateView(LoginRequiredMixin, generic.UpdateView):
    model = models.ModeratorInvite
    form_class = forms.ModeratorInviteForm
    slug_field = 'token'
    slug_url_kwarg = 'invite_token'

    def form_valid(self, form):
        if form.is_accepted():
            form.instance.accept(self.request.user)
            return redirect(form.instance.project.get_absolute_url())
        else:
            form.instance.reject()
            return redirect('/')

class AbstractProjectUserInviteListView(
        ProjectMixin,
        a4dashboard_mixins.DashboardBaseMixin,
        a4dashboard_mixins.DashboardComponentMixin,
        generic.base.TemplateResponseMixin,
        generic.edit.FormMixin,
        generic.detail.SingleObjectMixin,
        generic.edit.ProcessFormView):

    form_class = forms.InviteUsersFromEmailForm
    invite_model = None

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        if 'submit_action' in request.POST:
            if request.POST['submit_action'] == 'remove_user':
                pk = int(request.POST['user_pk'])
                user = get_object_or_404(User, pk=pk)
                related_users = getattr(self.object, self.related_users_field)
                related_users.remove(user)
                messages.success(request, self.success_message_removal)
            elif request.POST['submit_action'] == 'remove_invite':
                pk = int(request.POST['invite_pk'])
                invite = self.invite_model.objects.get(pk=pk)
                invite.delete()
                messages.success(request, _('Invitation succesfully removed.'))

            response = redirect(self.get_success_url())
        else:
            response = super().post(request, *args, **kwargs)

        self._send_component_updated_signal()
        return response

    def filter_existing(self, emails):
        related_users = getattr(self.object, self.related_users_field)
        related_emails = [u.email for u in related_users.all()]
        existing = []
        filtered_emails = []
        for email in emails:
            if email in related_emails:
                existing.append(email)
            else:
                filtered_emails.append(email)
        return filtered_emails, existing

    def filter_pending(self, emails):
        pending = []
        filtered_emails = []
        for email in emails:
            if self.invite_model.objects.filter(email=email,
                                                project=self.project).exists():
                pending.append(email)
            else:
                filtered_emails.append(email)
        return filtered_emails, pending

    def form_valid(self, form):
        emails = list(set(
            itertools.chain(form.cleaned_data['add_users'],
                            form.cleaned_data['add_users_upload'])))

        emails, existing = self.filter_existing(emails)
        if existing:
            messages.error(
                self.request,
                _('Following users already accepted an invitation: ') +
                ', '.join(existing)
            )

        emails, pending = self.filter_pending(emails)
        if pending:
            messages.error(
                self.request,
                _('Following users are already invited: ') +
                ', '.join(pending)
            )

        for email in emails:
            self.invite_model.objects.invite(
                self.request.user,
                self.project,
                email
            )

        messages.success(
            self.request,
            ungettext(self.success_message[0], self.success_message[1],
                      len(emails)).format(len(emails))
        )

        return redirect(self.get_success_url())

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['labels'] = (self.add_user_field_label,
                            self.add_user_upload_field_label)
        return kwargs

    def _send_component_updated_signal(self):
        a4dashboard_signals.project_component_updated.send(
            sender=self.component.__class__,
            project=self.project,
            component=self.component,
            user=self.request.user
        )

class DashboardProjectModeratorsView(AbstractProjectUserInviteListView):

    model = project_models.Project
    slug_url_kwarg = 'project_slug'
    template_name = 'meinberlin_projects/project_moderators.html'
    permission_required = 'a4projects.change_project'
    menu_item = 'project'

    related_users_field = 'moderators'
    add_user_field_label = _('Invite moderators via email')
    add_user_upload_field_label = _('Invite moderators via file upload')
    success_message = (_('{} moderator invited.'), _('{} moderators invited.'))
    success_message_removal = _('Moderator successfully removed.')

    invite_model = models.ModeratorInvite

    def get_permission_object(self):
        return self.project

class DashboardProjectParticipantsView(AbstractProjectUserInviteListView):

    model = project_models.Project
    slug_url_kwarg = 'project_slug'
    template_name = 'meinberlin_projects/project_participants.html'
    permission_required = 'a4projects.change_project'
    menu_item = 'project'

    related_users_field = 'participants'
    add_user_field_label = _('Invite users via email')
    add_user_upload_field_label = _('Invite users via file upload')
    success_message = (
        _('{} participant invited.'),
        _('{} participants invited.'))
    success_message_removal = _('Participant successfully removed.')

    invite_model = models.ParticipantInvite

    def get_permission_object(self):
        return self.project

class ProjectDetailView(PermissionRequiredMixin,
                        generic.DetailView):

    model = models.Project
    permission_required = 'a4projects.view_project'
    template_name = 'meinberlin_projects/project_detail.html'

    @cached_property
    def project(self):
        return self.get_object()

    @cached_property
    def module(self):
        return self.project.last_active_module

    @cached_property
    def modules(self):
        return self.project.modules\
            .annotate(start_date=Min('phase__start_date'))\
            .annotate(end_date=Max('phase__end_date'))\
            .order_by('start_date')

    @cached_property
    def events(self):
        return self.project.offlineevent_set.all()

    def get_module_dict(self, count, start_date):
        return {
            'title': 'Onlinebeteiligung {}'.format(str(count)),
            'type': 'module',
            'date': start_date
        }

    def get_current_modules(self):
        now = timezone.now()
        return self.modules.filter(
            start_date__lte=now,
            end_date__gte=now)

    def get_module_cluster(self):
        modules = self.modules
        start_date = modules.first().start_date
        end_date = modules.first().end_date
        count = 1
        clusters = []
        first_cluster = self.get_module_dict(count, start_date)
        clusters.append(first_cluster)

        for module in modules[1:]:
            if module.start_date > end_date:
                start_date = module.start_date
                end_date = module.end_date
                count += 1
                next_cluster = self.get_module_dict(count, start_date)
                clusters.append(next_cluster)
        return clusters

    def get_events_list(self):
        return self.events.values('date')

    def get_full_list(self):
        module_cluster = self.get_module_cluster()
        event_list = self.get_events_list()
        full_list = module_cluster + list(event_list)
        return sorted(full_list, key=lambda k: k['date'])

    def dispatch(self, request, *args, **kwargs):
        kwargs['project'] = self.project
        kwargs['module'] = self.module

        if self.modules.count() == 1 and not self.events:
            return self._view_by_phase()(request, *args, **kwargs)
        else:
            return super().dispatch(request)

    def _view_by_phase(self):
        if self.module and self.module.last_active_phase:
            return self.module.last_active_phase.view.as_view()
        else:
            return super().dispatch

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['modules'] = self.get_current_modules()
        context['participation_dates'] = self.get_full_list()
        return context

    @property
    def raise_exception(self):
        return self.request.user.is_authenticated

class ModuleDetailview(PermissionRequiredMixin,
                       PhaseDispatchMixin):

    model = module_models.Module
    permission_required = 'a4projects.view_project'
    slug_url_kwarg = 'module_slug'

    @cached_property
    def project(self):
        return self.module.project

    @cached_property
    def module(self):
        return self.get_object()

    def get_permission_object(self):
        return self.project

    def get_context_data(self, **kwargs):
        """Append project and module to the template context."""
        if 'project' not in kwargs:
            kwargs['project'] = self.project
        if 'module' not in kwargs:
            kwargs['module'] = self.module
        return super().get_context_data(**kwargs)
<|endoftext|>


<commit_before># Copyright (c) 2014 by California Institute of Technology
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the California Institute of Technology nor
#    the names of its contributors may be used to endorse or promote
#    products derived from this software without specific prior
#    written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL CALTECH
# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

"""
interface to ltl2ba
"""
import logging
logger = logging.getLogger(__name__)

import subprocess
import ply.lex
import ply.yacc

from tulip import transys as trs
from tulip.spec import ast

TABMODULE = 'ltl2ba_parsetab'

class Lexer(object):
    """Token rules to build lexer for ltl2ba output."""

    reserved = {
        'goto':'GOTO',
        'if':'IF',
        'fi':'FI',
        'never':'NEVER',
        'skip':'SKIP'
    }

    tokens = (
        'TRUE', 'FALSE',
        'NUMBER',
        'NOT', 'AND','OR', 'XOR', 'IMP', 'BIMP',
        'EQUALS', 'NEQUALS', 'LT', 'LE', 'GT', 'GE',
        'PLUS', 'MINUS', 'TIMES', 'DIV',
        'LPAREN','RPAREN', 'LBRACE', 'RBRACE',
        'COLON', 'COLON2', 'SEMI',
        'COMMENT', 'NAME'
    ) + tuple(reserved.values() )

    # Tokens
    t_TRUE = 'TRUE|True|true'
    t_FALSE = 'FALSE|False|false'

    t_COMMENT = '/\*.*\*/'
    t_NOT = r'\!'
    t_AND = r'\&\&|\&'
    t_OR = r'\|\||\|'
    t_XOR = r'\^'

    t_EQUALS = r'\=|\=\='
    t_NEQUALS = r'\!\='
    t_LT = r'\<'
    t_LE = r'\<\='
    t_GT = r'>\='
    t_GE = r'>'

    t_LPAREN = r'\('
    t_RPAREN = r'\)'
    t_LBRACE = r'\{'
    t_RBRACE = r'\}'
    t_SEMI = r';'
    t_COLON2 = r'::'
    t_COLON = r':'

    t_NUMBER = r'\d+'

    t_IMP = r'->'

    # Ignored characters
    t_ignore = ' \t'

    def __init__(self):
        self.lexer = ply.lex.lex(module=self, debug=False)

    def t_newline(self, t):
        r'\n+'
        t.lexer.lineno += t.value.count("\n")

    def t_error(self, t):
        logger.warn("Illegal character '%s'" % t.value[0])
        t.lexer.skip(1)

    def t_name(self, t):
        r'[A-Za-z_][a-zA-Z0-9_]*'
        t.type = self.reserved.get(t.value, 'NAME')
        return t

class Parser(object):
    """Production rules to build parser for ltl2ba output."""

    precedence = (
        #('right', 'UNTIL', 'RELEASE'),
        ('right', 'BIMP'),
        ('right', 'IMP'),
        ('left', 'XOR'),
        ('left', 'OR'),
        ('left', 'AND'),
        #('right', 'ALWAYS', 'EVENTUALLY'),
        #('right', 'NEXT'),
        ('right', 'NOT'),
        #('left', 'PRIME'),
        ('nonassoc', 'EQUALS', 'NEQUALS', 'LT', 'LE', 'GT', 'GE'),
        ('nonassoc', 'TIMES', 'DIV'),
        ('nonassoc', 'PLUS', 'MINUS'),
        ('nonassoc', 'TRUE', 'FALSE')
    )

    def __init__(self):
        """Build lexer and parser."""
        self.lexer = Lexer()
        self.tokens = self.lexer.tokens

        self.parser = ply.yacc.yacc(module=self, tabmodule=TABMODULE,
                                    write_tables=True, debug=False)

        self.ba = None

    def parse(self, ltl2ba_output):
        """Return a Buchi automaton from parsing C{ltl2ba_output}."""
        self.ba = trs.BA(symbolic=True)
        self.parser.parse(ltl2ba_output, lexer=self.lexer.lexer)
        return self.ba

    def p_claim(self, p):
        """claim : NEVER LBRACE COMMENT clauses RBRACE"""
        p[0] = p[4]

    def p_clauses(self, p):
        """clauses : clauses clause"""
        p[0] = p[1] + [p[2]]

    def p_clauses_end(self, p):
        """clauses : clause"""
        p[0] = [p[1]]

    def p_clause(self, p):
        """clause : if_clause"""
        p[0] = p[1]

    def p_clause_labeled(self, p):
        """clause : state COLON if_clause"""
        u = p[1]
        if_clause = p[3]

        for g, v in if_clause:
            self.ba.transitions.add(u, v, letter=g)

        p[0] = (u, if_clause)

    def p_if_clause(self, p):
        """if_clause : IF cases FI SEMI"""
        p[0] = p[2]

    def p_cases(self, p):
        """cases : cases case"""
        p[0] = p[1] + [p[2]]

    def p_cases_end(self, p):
        """cases : case"""
        p[0] = [p[1]]

    def p_case(self, p):
        """case : COLON2 expr IMP goto"""
        p[0] = (p[2], p[4])

    def p_expr_paren(self, p):
        """expr : LPAREN expr RPAREN"""
        p[0] = p[2]

    def p_and(self, p):
        """expr : expr AND expr"""
        p[0] = ast.And(p[2], p[1], p[3])

    def p_or(self, p):
        """expr : expr OR expr"""
        p[0] = ast.Or(p[2], p[1], p[3])

    def p_not(self, p):
        """expr : NOT expr"""
        p[0] = ast.Not(p[1], p[2])

    def p_number(self, p):
        """expr : NUMBER"""
        p[0] = p[1]

    def p_expr_name(self, p):
        """expr : NAME"""
        p[0] = ast.Var(p[1])

    def p_goto(self, p):
        """goto : GOTO state"""
        p[0] = p[2]

    def p_state(self, p):
        """state : NAME"""
        state = p[1]

        self.ba.states.add(state)

        if 'init' in state:
            self.ba.states.initial.add(state)
        elif 'accept' in state:
            self.ba.states.accepting.add(state)

        p[0] = p[1]

    def p_empty(self, p):
        """empty :"""

    def p_error(self, p):
        logger.error('Syntax error at ' + p.value)

def _call_ltl2ba(formula, prefix=''):
    """Load a Buchi Automaton from a Never Claim.

    depends
    =======
    ltl2ba: http://www.lsv.ens-cachan.fr/~gastin/ltl2ba/

    @param formula: LTL formula for input to ltl2ba
    @type formula: str

    @return: Buchi Automaton
    @rtype: tulip.transys.BA

    todo: make sure guard semantics properly accounted for:
        'prop' | '!prop' | '1' and skip
    """
    try:
        subprocess.call(['ltl2ba', '-h'], stdout=subprocess.PIPE)
    except OSError:
        raise Exception('cannot find ltl2ba on path')

    p = subprocess.Popen(
        [prefix + 'ltl2ba', '-f', '"' + formula + '"'],
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT
    )
    p.wait()

    ltl2ba_output = p.stdout.read()

    logger.info('ltl2ba output:\n\n' + ltl2ba_output)

    if p.returncode != 0:
        raise Exception('Error when converting LTL to Buchi.')

    return ltl2ba_output

# build parser once only
parser = Parser()

def convert(formula):
    """Convert LTL formula to Buchi Automaton using ltl2ba.

    @type formula: str(formula) must be admissible ltl2ba input

    @return: Buchi automaton whose edges are annotated
        with Boolean formulas (in parsed form, see L{spec.ast.Node})
    @rtype: L{BuchiAutomaton}
    """
    ltl2ba_out = _call_ltl2ba(str(formula))
    ba = parser.parse(ltl2ba_out)
    logger.info('Resulting automaton:\n' + str(ba))
    return ba

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    logger.setLevel(level=logging.DEBUG)

    f = '[] !s && <> (a || c) && <> (b U c)'
    fba = convert(f)
    #fba.save('ba.pdf')
<commit_msg>use strings for formulas guarding BA edges, instead of ASTs<commit_after># Copyright (c) 2014 by California Institute of Technology
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the California Institute of Technology nor
#    the names of its contributors may be used to endorse or promote
#    products derived from this software without specific prior
#    written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL CALTECH
# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

"""
interface to ltl2ba
"""
import logging
logger = logging.getLogger(__name__)

import subprocess
import ply.lex
import ply.yacc

from tulip import transys as trs

TABMODULE = 'ltl2ba_parsetab'

class Lexer(object):
    """Token rules to build lexer for ltl2ba output."""

    reserved = {
        'goto':'GOTO',
        'if':'IF',
        'fi':'FI',
        'never':'NEVER',
        'skip':'SKIP'
    }

    tokens = (
        'TRUE', 'FALSE',
        'NUMBER',
        'NOT', 'AND','OR', 'XOR', 'IMP', 'BIMP',
        'EQUALS', 'NEQUALS', 'LT', 'LE', 'GT', 'GE',
        'PLUS', 'MINUS', 'TIMES', 'DIV',
        'LPAREN','RPAREN', 'LBRACE', 'RBRACE',
        'COLON', 'COLON2', 'SEMI',
        'COMMENT', 'NAME'
    ) + tuple(reserved.values() )

    # Tokens
    t_TRUE = 'TRUE|True|true'
    t_FALSE = 'FALSE|False|false'

    t_COMMENT = '/\*.*\*/'
    t_NOT = r'\!'
    t_AND = r'\&\&|\&'
    t_OR = r'\|\||\|'
    t_XOR = r'\^'

    t_EQUALS = r'\=|\=\='
    t_NEQUALS = r'\!\='
    t_LT = r'\<'
    t_LE = r'\<\='
    t_GT = r'>\='
    t_GE = r'>'

    t_LPAREN = r'\('
    t_RPAREN = r'\)'
    t_LBRACE = r'\{'
    t_RBRACE = r'\}'
    t_SEMI = r';'
    t_COLON2 = r'::'
    t_COLON = r':'

    t_NUMBER = r'\d+'

    t_IMP = r'->'

    # Ignored characters
    t_ignore = ' \t'

    def __init__(self):
        self.lexer = ply.lex.lex(module=self, debug=False)

    def t_newline(self, t):
        r'\n+'
        t.lexer.lineno += t.value.count("\n")

    def t_error(self, t):
        logger.warn("Illegal character '%s'" % t.value[0])
        t.lexer.skip(1)

    def t_name(self, t):
        r'[A-Za-z_][a-zA-Z0-9_]*'
        t.type = self.reserved.get(t.value, 'NAME')
        return t

class Parser(object):
    """Production rules to build parser for ltl2ba output."""

    precedence = (
        #('right', 'UNTIL', 'RELEASE'),
        ('right', 'BIMP'),
        ('right', 'IMP'),
        ('left', 'XOR'),
        ('left', 'OR'),
        ('left', 'AND'),
        #('right', 'ALWAYS', 'EVENTUALLY'),
        #('right', 'NEXT'),
        ('right', 'NOT'),
        #('left', 'PRIME'),
        ('nonassoc', 'EQUALS', 'NEQUALS', 'LT', 'LE', 'GT', 'GE'),
        ('nonassoc', 'TIMES', 'DIV'),
        ('nonassoc', 'PLUS', 'MINUS'),
        ('nonassoc', 'TRUE', 'FALSE')
    )

    def __init__(self):
        """Build lexer and parser."""
        self.lexer = Lexer()
        self.tokens = self.lexer.tokens

        self.parser = ply.yacc.yacc(module=self, tabmodule=TABMODULE,
                                    write_tables=True, debug=False)

        self.ba = None

    def parse(self, ltl2ba_output):
        """Return a Buchi automaton from parsing C{ltl2ba_output}."""
        self.ba = trs.BA(symbolic=True)
        self.parser.parse(ltl2ba_output, lexer=self.lexer.lexer)
        return self.ba

    def p_claim(self, p):
        """claim : NEVER LBRACE COMMENT clauses RBRACE"""
        p[0] = p[4]

    def p_clauses(self, p):
        """clauses : clauses clause"""
        p[0] = p[1] + [p[2]]

    def p_clauses_end(self, p):
        """clauses : clause"""
        p[0] = [p[1]]

    def p_clause(self, p):
        """clause : if_clause"""
        p[0] = p[1]

    def p_clause_labeled(self, p):
        """clause : state COLON if_clause"""
        u = p[1]
        if_clause = p[3]

        for g, v in if_clause:
            self.ba.transitions.add(u, v, letter=g)

        p[0] = (u, if_clause)

    def p_if_clause(self, p):
        """if_clause : IF cases FI SEMI"""
        p[0] = p[2]

    def p_cases(self, p):
        """cases : cases case"""
        p[0] = p[1] + [p[2]]

    def p_cases_end(self, p):
        """cases : case"""
        p[0] = [p[1]]

    def p_case(self, p):
        """case : COLON2 expr IMP goto"""
        p[0] = (p[2], p[4])

    def p_expr_paren(self, p):
        """expr : LPAREN expr RPAREN"""
        p[0] = '({expr})'.format(expr=p[2])

    def p_and(self, p):
        """expr : expr AND expr"""
        p[0] = '({x} and {y})'.format(x=p[1], y=p[3])

    def p_or(self, p):
        """expr : expr OR expr"""
        p[0] = '({x} or {y})'.format(x=p[1], y=p[3])

    def p_not(self, p):
        """expr : NOT expr"""
        p[0] = '(not {expr})'.format(expr=p[2])

    def p_number(self, p):
        """expr : NUMBER"""
        p[0] = int(p[1])

    def p_expr_name(self, p):
        """expr : NAME"""
        p[0] = p[1]

    def p_goto(self, p):
        """goto : GOTO state"""
        p[0] = p[2]

    def p_state(self, p):
        """state : NAME"""
        state = p[1]

        self.ba.states.add(state)

        if 'init' in state:
            self.ba.states.initial.add(state)
        elif 'accept' in state:
            self.ba.states.accepting.add(state)

        p[0] = p[1]

    def p_empty(self, p):
        """empty :"""

    def p_error(self, p):
        logger.error('Syntax error at ' + p.value)

def _call_ltl2ba(formula, prefix=''):
    """Load a Buchi Automaton from a Never Claim.

    depends
    =======
    ltl2ba: http://www.lsv.ens-cachan.fr/~gastin/ltl2ba/

    @param formula: LTL formula for input to ltl2ba
    @type formula: str

    @return: Buchi Automaton
    @rtype: tulip.transys.BA

    todo: make sure guard semantics properly accounted for:
        'prop' | '!prop' | '1' and skip
    """
    try:
        subprocess.call(['ltl2ba', '-h'], stdout=subprocess.PIPE)
    except OSError:
        raise Exception('cannot find ltl2ba on path')

    p = subprocess.Popen(
        [prefix + 'ltl2ba', '-f', '"' + formula + '"'],
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT
    )
    p.wait()

    ltl2ba_output = p.stdout.read()

    logger.info('ltl2ba output:\n\n' + ltl2ba_output)

    if p.returncode != 0:
        raise Exception('Error when converting LTL to Buchi.')

    return ltl2ba_output

# build parser once only
parser = Parser()

def convert(formula):
    """Convert LTL formula to Buchi Automaton using ltl2ba.

    @type formula: str(formula) must be admissible ltl2ba input

    @return: Buchi automaton whose edges are annotated
        with Boolean formulas (in parsed form, see L{spec.ast.Node})
    @rtype: L{BuchiAutomaton}
    """
    ltl2ba_out = _call_ltl2ba(str(formula))
    ba = parser.parse(ltl2ba_out)
    logger.info('Resulting automaton:\n' + str(ba))
    return ba

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    logger.setLevel(level=logging.DEBUG)

    f = '[] !s && <> (a || c) && <> (b U c)'
    fba = convert(f)
    #fba.save('ba.pdf')
<|endoftext|>