def render_to_content(widget, size, focus=False):
    return [line for line in widget.render(size, focus).content()] def render_widgets_to_content(widgets, size, focus=False, inline=False):
    if inline:
        widget = urwid.Columns([("pack", w) for w in widgets])
    else:
        widget = urwid.Pile([("pack", w) for w in widgets])
    return render_to_content(widget, size, focus) class SummaryItemWidget(BasicWidget):
    """ Summary item widget

    Summary item widget that display summary and defined action to open other view

    Attributes:
        message: Original message genrate by BaseDataStore
        summary: Format message by displayer
        displayer_context: DisplayerContext instance
    """
    def __init__(self, message, title, displayer_context, **kwargs):
        super(SummaryItemWidget, self).__init__(
            widget=self._widget(title),
            **kwargs)

        self.displayer_context = displayer_context
        self.message = message

    def _widget(self, title):
        return FocusableText(title, attr_map="summary", focus_map="summary focus")

    def get_title_as_plain_text(self):
        """ Get title in plain text

        Returns:
            title in plain text
        """
        return self._w.get_plain_text()

    def set_title(self, title):
        self.display(self._widget(title))

    def keypress(self, size, key):
        if key == "enter":
            try:
                self.controller.open_view(DetailWidget(
                    self.message, self.displayer_context, controller=self.controller,
                    context=self.context))
            except:  # pragma: no cover
                self.controller.open_error()
            return None
        if key in self.displayer_context.actions:
            try:
                self.displayer_context.actions[key].__call__(
                    self.controller, self.message, self)
            except:  # pragma: no cover
                self.controller.open_error()
            return None

        return super(SummaryItemWidget, self).keypress(size, key)  # pragma: no cover class SummaryListWalker(urwid.SimpleFocusListWalker):
    """ Summary item widgets wrapper

    Contains the SummaryItemWidget,
    and used to receive message from data store

    Attributes:
        content: list of SummaryItemWidget
        displayer_context: DisplayerContext instance
    """
    def __init__(self, content=None, displayer_context=None,
                 base_walker=None, controller=None, context=None,
                 on_receive=None):
        super(SummaryListWalker, self).__init__(content or [])
        self.controller = controller or base_walker.controller
        self.context = context or base_walker.context
        self.displayer_context = displayer_context or base_walker.displayer_context
        self.on_receive = on_receive or base_walker.on_receive
        self.base_walker = base_walker
        self.displayer_context.store.register(self)

    def recv(self, message):
        """ Action when received message from data store

        Will transform message into summary,
        and generate a SummaryItemWidget into its content
        """
        try:
            self.append(self._create_widget(message))
        except:
            self.controller.open_error()
        else:
            self.on_receive()

    def _create_widget(self, message):
            summary = self.displayer_context.displayer.summary(message)
            return SummaryItemWidget(
                message, summary, self.displayer_context, controller=self.controller, context=self.context) class FilterSummaryListWalker(SummaryListWalker):
    """ Summary item widgets wrapper that filter by keyword

    Optional display SummaryItemWidget depend on summary is match by keyword or not

    Attributes:
        base_walker: Original SummaryListWalker
        keyword: Filter keyword
    """
    def __init__(self, base_walker, keyword):
        content = [m for m in base_walker if base_walker.displayer_context.displayer.match(keyword, m.message, m.get_title_as_plain_text())]
        super(FilterSummaryListWalker, self).__init__(
            content=content, base_walker=base_walker)
        self.keyword = keyword

    def recv(self, message):
        """ Action when received message from data store

        Will transform message into summary,
        and check message or summary is match by keyword or not,
        generate a SummaryItemWidget and display it if match
        """
        try:
            widget = self._create_widget(message)
            if self.displayer_context.displayer.match(self.keyword, message, widget.get_title_as_plain_text()):
                self.append(widget)
        except:
            self.controller.open_error()
        else:
            self.on_receive()

    def close(self):
        """ Unregister listener if quit search mode """
        self.displayer_context.store.unregister(self)

    def __delitem__(self, index):
        if isinstance(index, slice):
            start_index = index.start or 0
            end_index = min(index.stop or len(self), len(self))
            for i in range(start_index, end_index):
                base_walker_index = self.base_walker.index(self[i])
                del self.base_walker[base_walker_index]
        else:
            base_walker_index = self.base_walker.index(self[index])
            del self.base_walker[base_walker_index]
        super(FilterSummaryListWalker, self).__delitem__(index) class SummaryListWidget(BasicWidget):
    """ ListBox widget to contains the content of SummaryItemWidget

    Attributes:
        displayer_context: DisplayerContext
    """
    def __init__(self, displayer_context, **kwargs):
        super(SummaryListWidget, self).__init__(**kwargs)
        _verify_keys(displayer_context.actions)

        self.name = displayer_context.displayer.get_name()
        self.base_walker = SummaryListWalker(
            displayer_context=displayer_context,
            on_receive=self._on_receive, **kwargs)
        self.current_walker = self.base_walker

        self.search_widget = SearchWidget(self._filter, self._clear_search)
        self.help_widget = HelpWidget(
            HelpContent(
                [HelpCategory("Basic", self.context.config.keys),
                 HelpCategory("Advanced", _ADVANCED_KEYS),
                 make_category_with_actions("Custom", displayer_context.actions)]),
            **kwargs
        )

        self.display(urwid.ListBox(self.base_walker))

    def _filter(self, keyword):
        if keyword:
            new_walker = FilterSummaryListWalker(self.base_walker, keyword)
        else:
            new_walker = self.base_walker
        if new_walker is not self.current_walker:
            self._update_content(new_walker)
        self.controller._focus_body()

    def _update_content(self, walker):
        if self.current_walker is not self.base_walker:
            self.current_walker.close()

        self.current_walker = walker
        self.display(urwid.ListBox(walker))
        self.update_info()

    def _open_search(self):
        self.search_widget.clear()
        self.controller.open_edit(self.search_widget)

    def _clear_search(self):
        self._filter(None)
        self.controller.close_edit()

    def update_info(self):
        if len(self.current_walker):
            curr_index = self._w.focus_position + 1
            total_index = len(self.current_walker)
            self.controller._update_info(
                self, "{0}[{1}/{2}]".format(self.name, curr_index, total_index))
        else:
            self.controller._update_info(self, "{0}[0/0]".format(self.name))

    def _on_receive(self):
        if self.context.config.auto_scroll:
            curr_index = self._w.focus_position + 1
            total_index = len(self.current_walker)
            if curr_index == total_index - 1:
                self._w.set_focus(curr_index)
        self.update_info()

    def keypress(self, size, key):
        if key == "/":
            self._open_search()
            return None
        if key == "q" and isinstance(self.current_walker, FilterSummaryListWalker):
            self._clear_search()
            return None
        if key == "q":
            self.controller.back()
            return None
        if key == "g":
            self._w.set_focus(0)
            self.update_info()
            return super(SummaryListWidget, self).keypress(size, key)
        if key == "G":
            self._w.set_focus(len(self.current_walker) - 1)
            self.update_info()
            return super(SummaryListWidget, self).keypress(size, key)
        if key == "x":
            del self.current_walker[self._w.focus_position]
            self.update_info()
            return super(SummaryListWidget, self).keypress(size, key)
        if key == "X":
            del self.current_walker[:]
            self.update_info()
            return super(SummaryListWidget, self).keypress(size, key)
        if key == "?":
            self.controller.open_view(self.help_widget)
            return None

        keypress_result = super(SummaryListWidget, self).keypress(size, key)  # pragma: no cover
        self.update_info()
        return keypress_result def _verify_keys(actions):
    for key, _, _ in actions:
        if key in _ADVANCED_KEYS:
            raise ValueError("key '{0}' had defined by GViewer for {1}".format(key, _ADVANCED_KEYS[key])) class DetailWidget(BasicWidget):
    """ Display content for message

    Attributes:
        message: message generate by DataStore
        displayer_context: DisplayerContext instance
        index: view's index
    """
    def __init__(self, message, displayer_context, index=0, **kwargs):
        super(DetailWidget, self).__init__(**kwargs)
        self.index = index
        self.message = message
        self.displayer_context = displayer_context

        self.views = self.displayer_context.displayer.get_views()
        self.name, view_callable = self.views[index]
        self.view = view_callable.__call__(self.message)

        self.content_widget = self.view.widget(
            self.message, **kwargs)

        _verify_keys(self.view.actions)
        self.search_widget = SearchWidget(self._search, self._clear_search)
        self.help_widget = HelpWidget(
            HelpContent(
                [HelpCategory("Basic", self.context.config.keys),
                 HelpCategory("Advanced", _ADVANCED_KEYS),
                 make_category_with_actions("Custom", self.view.actions)]),
            **kwargs
        )

        if len(self.views) > 1:
            header = Tabs([k for k, _ in self.views], self.index)
        else:
            header = None

        widget = urwid.Frame(self.content_widget, header=header)
        self.display(widget)

    def _open(self, index):
        self._clear_search()
        try:
            self.controller.open_view(DetailWidget(
                self.message, self.displayer_context, index=index,
                controller=self.controller, context=self.context),
                push_prev=False)
        except:
            self.controller.open_error()

    def _next_view(self):
        if len(self.views) == 1:
            return

        if len(self.views) > self.index + 1:
            next_index = self.index + 1
        else:
            next_index = 0
        self._open(next_index)

    def _prev_view(self):
        if len(self.views) == 1:
            return

        next_index = len(self.views) - 1 if self.index == 0 else self.index - 1
        self._open(next_index)

    def _open_search(self):
        self.search_widget.clear()
        self.content_widget.clear_prev_search()
        self.controller.open_edit(self.search_widget)

    def _search(self, keyword):
        self.controller._focus_body()
        self.content_widget.search_next(keyword)
        self.controller.close_edit()

    def _clear_search(self):
        self.controller.close_edit()

    def _export(self):  # pragma: no cover
        file_name = "export-%13d" % (time.time() * 1000)
        with open(file_name, "w") as f:
            f.write(str(self.view))
        self.controller.notify("Export to file {0}".format(file_name))

    def update_info(self):
        return self.controller._update_info(self, self.name)

    def keypress(self, size, key):
        if key == "q":
            self._clear_search()
            self.controller.back()
            return None
        if key == "tab":
            self._next_view()
            return None
        if key == "shift tab":
            self._prev_view()
            return None
        if key == "/":
            self._open_search()
            return None
        if key == "n":
            if self.search_widget.get_keyword():
                self.content_widget.search_next(
                    self.search_widget.get_keyword()
                )
            return None
        if key == "N":
            if self.search_widget.get_keyword():
                self.content_widget.search_prev(
                    self.search_widget.get_keyword()
                )
            return None
        if key == "e":  # pragma: no cover
            self._export()
            return None
        if key == "?":  # pragma: no cover
            self.controller.open_view(self.help_widget)
            return None

        return super(DetailWidget, self).keypress(size, key)  # pragma: no cover class View(Base):
    """View Element

    Attributes:
        groups: iterable of Group
        actions: dict defined {key: callback}
    """
    def __init__(self, groups, actions=None):
        self.groups = groups
        self.actions = actions or Actions()

    def widget(self, message, controller=None, context=None):
        widgets = []
        for group in self.groups:
            widgets += group.widgets(message, controller=controller, context=context)
            widgets.append(EmptyLine())

        if not widgets:
            widgets.append(EmptyLine())

        return ContentWidget(
            widgets, message, self.actions, controller=controller,
            context=context)

    def __unicode__(self):
        return u"\n".join([str(g) + u"\n" for g in self.groups]) class Actions(object):
    """Actions contains action list

    Attributes:
        actions: dict contains k->Action
    """
    def __init__(self, actions=None):
        actions = actions or []
        if not isinstance(actions, list):  # pragma: no cover
            raise ValueError("Actions cannot accept {0}".format(type(actions)))
        self.actions = OrderedDict([
            (k, Action(d, f)) for (k, d, f) in actions
        ])

    def __contains__(self, key):
        return key in self.actions

    def __getitem__(self, key):
        return self.actions[key]

    def __iter__(self):
        return iter([(k, a.desc, a.function) for k, a in self.actions.items()]) class StaticDataStore(BaseDataStore):
    """
    Used for static unmodified data that load data at first time

    Attributes:
        messages: list of any type of message
    """
    def __init__(self, messages):
        super(StaticDataStore, self).__init__()
        self.messages = messages

    def setup(self):
        for message in self.messages:
            self.on_message(message) class DisplayerContext(object):
    """Context defined for how ui display

    Attributes:
        store: DataStore instance
        displayer: BaseDisplayer implementation instance
        actions: Actions instance
    """
    def __init__(self, store, displayer, actions=None):
        self.store = store
        self.displayer = displayer
        self.actions = actions or Actions() class Config(object):
    """Config for GViewer

    Attributes:
        header: header content
        keys: dictionary define key mapping
        template: list of tuple for default stylesheet
    """
    def __init__(self,
                 header="General Viewer",
                 keys=vim,
                 template=default,
                 auto_scroll=False):
        self.header = header
        self.keys = keys
        self.template = template
        self.auto_scroll = auto_scroll class BaseDisplayer(object):  # pragma: no cover
    """Absctract class for displayer"""
    def summary(self, message):
        """Define how message display in summary widget

        Args:
            message: message that defined at DataStore implementation

        Returns:
            str or unicode or text markup
        """
        return message

    def match(self, keyword, message, summary):
        """Define is that the message match the keyword

        Default implementation is to check that keyword is contain in summary or not

        Args:
            keyword: str or unicode
            message: message that defined at DataStore implementation
            summary: summary that was generated by to_summary
        Returns:
            bool represent the key is match or not
        """
        return keyword in summary

    def get_views(self):
        """Define how display message into different views

        Returns:
            list of tuple contans ("view_name", View)
        """
        return [("Undefined", lambda m: View([]))]

    def get_name(self):
        """The name of displayer"""
        return "GViewer"
import unittest
import urwid
import mock
from ..util import render_to_content, render_widgets_to_content
from gviewer.view.summary import (
    SummaryItemWidget, SummaryListWalker,
    FilterSummaryListWalker, SummaryListWidget)
from gviewer.view.summary import _verify_keys
from gviewer.view.detail import DetailWidget
from gviewer.view.element import View
from gviewer.action import Actions
from gviewer.store import StaticDataStore
from gviewer.context import DisplayerContext
from gviewer.config import Config
from gviewer.displayer import BaseDisplayer
        self.assertEqual(len(self.original_walker), 1)
        self.assertIs(walker[0], self.original_walker[0])

    def test_del_all_items(self):
        walker = FilterSummaryListWalker(
            self.original_walker, "summary 1")
        del walker[:]
        self.assertEqual(len(walker), 0)
        self.assertEqual(len(self.original_walker), 1)


class TestSummaryListWidget(unittest.TestCase):
    def setUp(self):
        self.controller = mock.Mock()

        self.displayer_context = DisplayerContext(
            StaticDataStore(["summary 1", "summary 2"]),
            BaseDisplayer())

        self.context = mock.Mock()
        self.context.config = Config()

        self.widget = SummaryListWidget(
            self.displayer_context,
            controller=self.controller,
            context=self.context)

        self.displayer_context.store.setup()

    def test_render(self):
        self.assertEqual(
            render_to_content(self.widget, (9, 2)),
            render_widgets_to_content([
                urwid.AttrMap(urwid.Text("summary 1"), "summary"),
                urwid.AttrMap(urwid.Text("summary 2"), "summary")
            ], (9, 2))
        )

    def test_open_search(self):
        self.widget._open_search()
        self.controller.open_edit.assert_called_with(self.widget.search_widget)

    def test_filter(self):
        self.widget._filter("summary 1")
        self.assertIsInstance(self.widget.current_walker, FilterSummaryListWalker)
        self.assertEqual(len(self.widget.current_walker), 1)

    def test_filter_with_empty(self):
        self.widget._filter("")
        self.assertIs(
            self.widget.current_walker,
            self.widget.base_walker)

    def test_keypress_open_search(self):
        self.widget.keypress(None, "/")
        self.controller.open_edit.assert_called_with(self.widget.search_widget)

    def test_keypress_clear_search(self):
        self.widget._filter("test")
        self.assertEqual(len(self.widget.current_walker), 0)

        self.widget.keypress((0,), "q")
        self.assertIs(
            self.widget.current_walker,
            self.widget.base_walker)
        self.assertEqual(len(self.widget.current_walker), 2)

    def test_keypress_back(self):
        self.widget.keypress((0,), "q")
        self.controller.back.assert_called_with()

    def test_keypress_open_help(self):
        self.assertIsNone(self.widget.keypress((0, 0), "?"))
        self.controller.open_view.assert_called_with(self.widget.help_widget)

    def test_keypress_bottom_and_top(self):
        self.widget.keypress((10, 10), "G")
        self.assertEqual(self.widget._w.focus_position, 1)
        self.controller._update_info.assert_called_with(
            self.widget, "GViewer[2/2]")

        self.widget.keypress((10, 10), "g")
        self.assertEqual(self.widget._w.focus_position, 0)
        self.controller._update_info.assert_called_with(
            self.widget, "GViewer[1/2]")

    def test_keypress_bottom_and_top_when_search(self):
        self.widget._filter("summary")
        self.widget.keypress((10, 10), "G")
        self.assertEqual(self.widget._w.focus_position, 1)
        self.widget.keypress((10, 10), "g")
        self.assertEqual(self.widget._w.focus_position, 0)

    def test_keypress_clear_item(self):
        self.widget.keypress((10, 10), "x")
        self.assertEqual(len(self.widget.current_walker), 1)
        self.controller._update_info.assert_called_with(
            self.widget, "GViewer[1/1]")

    def test_keypress_clear_items(self):
        self.widget.keypress((10, 10), "X")
        self.assertEqual(len(self.widget.current_walker), 0)
        self.controller._update_info.assert_called_with(
            self.widget, "GViewer[0/0]")

    def test_keypress_up_and_down(self):
        self.widget.keypress((10, 10), "down")
        self.assertEqual(self.widget._w.focus_position, 1)
        self.controller._update_info.assert_called_with(
            self.widget, "GViewer[2/2]")

        self.widget.keypress((10, 10), "up")
        self.assertEqual(self.widget._w.focus_position, 0)
        self.controller._update_info.assert_called_with(
            self.widget, "GViewer[1/2]")

    def test_auto_scroll(self):
        displayer_context = DisplayerContext(
            StaticDataStore(["summary 1", "summary 2"]),
            BaseDisplayer())

        self.context.config = Config(auto_scroll=True)