Ejemplo n.º 1
0
Archivo: cli.py Proyecto: louipc/turses
def main():
    """
    Launch ``turses``.
    """
    set_title(__name__)
    set_encoding('utf8')

    args = read_arguments()

    # check if stdout has to be restored after program exit
    if any([
            args.debug, args.offline,
            getattr(args, 'help', False),
            getattr(args, 'version', False)
    ]):
        # we are going to print information to stdout
        save_and_restore_stdout = False
    else:
        save_and_restore_stdout = True

    if save_and_restore_stdout:
        save_stdout()

    # parse arguments and load configuration
    configuration.parse_args(args)
    configuration.load()

    # start logger
    logging.basicConfig(filename=LOG_FILE, level=configuration.logging_level)

    # create view
    curses_interface = CursesInterface()

    # create model
    timeline_list = TimelineList()

    # create API
    api = create_async_api(MockApi if args.offline else TweepyApi)

    # create controller
    turses = Turses(
        ui=curses_interface,
        api=api,
        timelines=timeline_list,
    )

    try:
        turses.start()
    except Exception:
        # A unexpected exception occurred, open the debugger in debug mode
        if args.debug or args.offline:
            import pdb
            pdb.post_mortem()
    finally:
        if save_and_restore_stdout:
            restore_stdout()

        restore_title()

        exit(0)
Ejemplo n.º 2
0
    def test_custom_session(self):
        """
        Test that, when defining a custom session, the timelines are created
        correctly.
        """
        timeline_list = TimelineList()

        visible_string = 'home, mentions, search:turses'
        self.session.append_visible_timelines(visible_string, timeline_list)

        # check that the visible timelines are appended correctly
        self.assertTrue(len(timeline_list), 3)

        self.assertTrue(is_home_timeline(timeline_list[0]))
        self.assertTrue(is_mentions_timeline(timeline_list[1]))
        self.assertTrue(is_search_timeline(timeline_list[2]))

        self.assertEqual(
            timeline_list.visible_timelines,
            [timeline_list[0], timeline_list[1], timeline_list[2]])

        # now let's append the buffers in the background
        buffers_string = 'messages'
        self.session.append_background_timelines(buffers_string, timeline_list)

        self.assertTrue(len(timeline_list), 4)

        self.assertTrue(is_messages_timeline(timeline_list[3]))
Ejemplo n.º 3
0
    def __init__(self, ui, api_backend):
        self.ui = ui
        self.editor = None

        # Model
        self.timelines = TimelineList()
        self.timelines.subscribe(self)

        # Mode
        self.mode = self.INFO_MODE

        # API
        oauth_token = configuration.oauth_token
        oauth_token_secret = configuration.oauth_token_secret
        self.api = AsyncApi(
            api_backend,
            access_token_key=oauth_token,
            access_token_secret=oauth_token_secret,
        )
Ejemplo n.º 4
0
    def __init__(self, configuration, ui, api_backend):
        self.configuration = configuration
        self.ui = ui
        self.editor = None
        self.timelines = TimelineList()

        # Mode
        self.mode = self.INFO_MODE

        # API
        oauth_token = self.configuration.oauth_token
        oauth_token_secret = self.configuration.oauth_token_secret
        self.api = AsyncApi(api_backend,
                            access_token_key=oauth_token,
                            access_token_secret=oauth_token_secret,)
Ejemplo n.º 5
0
 def setUp(self):
     self.timeline_list = TimelineList()
Ejemplo n.º 6
0
class TimelineListTest(ActiveListTest):
    def active_index(self):
        return self.timeline_list.active_index

    # - Helpers ---------------------------------------------------------------

    def append_timeline(self):
        self.timeline_list.append_timeline(Timeline('Timeline'))

    def assert_visible(self, visible_list):
        self.assertEqual(self.timeline_list.visible, visible_list)

    # - Tests -----------------------------------------------------------------

    def setUp(self):
        self.timeline_list = TimelineList()

    def test_has_timelines_false_if_empty(self):
        self.failIf(self.timeline_list.has_timelines())

    def test_has_timelines_true_otherwise(self):
        self.append_timeline()
        self.failUnless(self.timeline_list.has_timelines())

    def test_null_index_with_no_timelines(self):
        self.assert_null_index()

    def test_active_index_0_when_appending_first_timeline(self):
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous(self):
        # null index when there are no timelines
        self.timeline_list.activate_previous()
        self.assert_null_index()
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_next(self):
        # null index when there are no timelines
        self.timeline_list.activate_next()
        self.assert_null_index()
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous_and_activate_next(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        # next
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        # previous
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_active_returns_first_appended(self):
        # append
        name = 'Timeline'
        timeline = Timeline(name)
        self.timeline_list.append_timeline(timeline)
        # assert
        active_timeline = self.timeline_list.active
        self.assertEqual(timeline, active_timeline)

    def test_active_returns_None_when_empty(self):
        self.assertEqual(self.timeline_list.active, None)

    def test_append_timeline_increases_timeline_size(self):
        self.assertEqual(len(self.timeline_list), 0)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 1)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 2)

    def test_activate_first(self):
        # null index when there are no timelines
        self.timeline_list.activate_first()
        self.assert_null_index()
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the first when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_next()
        self.timeline_list.activate_next()
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_last(self):
        # null index when there are no timelines
        self.timeline_list.activate_last()
        self.assert_null_index()
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the last when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 2)

    def test_shift_active_previous(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_previous()
        self.assert_null_index()
        # does not change if its the first
        self.append_timeline()
        self.timeline_list.shift_active_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_shift_active_next(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_next()
        self.assert_null_index()
        # does not change if its the last
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 0)
        # it increases until reaching the end
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)

    # visibility

    def test_no_visible_when_newly_created(self):
        self.assert_visible([])
        self.timeline_list.expand_visible_previous()

    def test_only_visible_is_index_0_when_appending_first_timeline(self):
        self.append_timeline()
        self.assert_visible([0])

    def test_expand_visible_previous(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assert_visible([0])
        self.timeline_list.activate_last()
        self.assert_visible([2])

        self.timeline_list.expand_visible_previous()
        self.assert_visible([1, 2])
        self.timeline_list.expand_visible_previous()
        self.assert_visible([0, 1, 2])

        # there are no more timelines
        self.timeline_list.expand_visible_previous()
        self.assert_visible([0, 1, 2])

    def test_expand_visible_next(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assert_visible([0])

        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1])
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1, 2])

        # there are no more timelines
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1, 2])

    def test_shrink_visible_beggining(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_last()
        self.timeline_list.expand_visible_previous()
        self.timeline_list.expand_visible_previous()
        self.assert_visible([0, 1, 2])

        self.timeline_list.shrink_visible_beggining()
        self.assert_visible([1, 2])
        self.timeline_list.shrink_visible_beggining()
        self.assert_visible([2])

        # at least the active timeline has to be visible
        self.timeline_list.shrink_visible_beggining()
        self.assert_visible([2])

    def test_shrink_visible_end(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1, 2])

        self.timeline_list.shrink_visible_end()
        self.assert_visible([0, 1])
        self.timeline_list.shrink_visible_end()
        self.assert_visible([0])

        # at least the active timeline has to be visible
        self.timeline_list.shrink_visible_end()
        self.assert_visible([0])

    def test_visible_active_only_when_activating_invisible_timeline(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1])

        self.timeline_list.activate_last()
        self.assert_visible([2])

        self.timeline_list.expand_visible_previous()
        self.assert_visible([1, 2])

        self.timeline_list.activate_first()
        self.assert_visible([0])

    def test_consistent_visible_timelines_when_deleting_leftmost_active(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0, 1, 2])

        self.timeline_list.delete_active_timeline()

        # active index does not change
        self.assertEqual(self.active_index(), 0)

        # visible
        self.assert_visible([0, 1])

        # relative index
        relative_index = self.timeline_list.active_index_relative_to_visible
        self.assertEqual(relative_index, 0)

    def test_consistent_visible_timelines_when_deleting_middle_active(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0, 1, 2, 3, 4])

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([0, 1, 2, 3, 4])

        self.timeline_list.delete_active_timeline()

        # active index does not change
        self.assertEqual(self.active_index(), 2)

        # visible
        self.assert_visible([0, 1, 2, 3])

        # relative index
        relative_index = self.timeline_list.active_index_relative_to_visible
        self.assertEqual(relative_index, 2)

    def test_consistent_visible_timelines_when_deleting_rightmost_timeline(
            self):

        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 3)

        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0, 1, 2])

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([0, 1, 2])

        self.timeline_list.delete_active_timeline()
        self.assertEqual(len(self.timeline_list), 2)

        # active index shifts left
        self.assertEqual(self.active_index(), 1)

        # visible
        self.assert_visible([0, 1])

        # relative index
        relative_index = self.timeline_list.active_index_relative_to_visible
        self.assertEqual(relative_index, 1)

    def test_delete_active_timeline_with_one_visible_timeline_in_the_left(
            self):
        self.append_timeline()
        self.append_timeline()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0])

        self.timeline_list.delete_active_timeline()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0])

    def test_delete_active_timeline_with_one_visible_timeline_in_the_middle(
            self):

        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0])
        self.assertEqual(len(self.timeline_list), 5)

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([2])

        self.timeline_list.delete_active_timeline()
        self.assertEqual(len(self.timeline_list), 4)

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([2])

    def test_delete_active_timeline_with_one_visible_timeline_in_the_right(
            self):

        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 3)

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([2])

        self.timeline_list.delete_active_timeline()
        self.assertEqual(len(self.timeline_list), 2)

        self.assertEqual(self.active_index(), 1)
        self.assert_visible([1])
Ejemplo n.º 7
0
 def setUp(self):
     self.timeline_list = TimelineList()
Ejemplo n.º 8
0
class TimelineListTest(ActiveListTest):

    def active_index(self):
        return self.timeline_list.active_index

    # - Helpers ---------------------------------------------------------------

    def append_timeline(self):
        self.timeline_list.append_timeline(Timeline('Timeline'))

    def assert_visible(self, visible_list):
        self.assertEqual(self.timeline_list.visible, visible_list)

    # - Tests -----------------------------------------------------------------

    def setUp(self):
        self.timeline_list = TimelineList()

    def test_has_timelines_false_if_empty(self):
        self.failIf(self.timeline_list.has_timelines())

    def test_has_timelines_true_otherwise(self):
        self.append_timeline()
        self.failUnless(self.timeline_list.has_timelines())

    def test_null_index_with_no_timelines(self):
        self.assert_null_index()

    def test_active_index_0_when_appending_first_timeline(self):
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous(self):
        # null index when there are no timelines
        self.timeline_list.activate_previous()
        self.assert_null_index()
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_next(self):
        # null index when there are no timelines
        self.timeline_list.activate_next()
        self.assert_null_index()
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous_and_activate_next(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        # next
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        # previous
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_active_returns_first_appended(self):
        # append
        name = 'Timeline'
        timeline = Timeline(name)
        self.timeline_list.append_timeline(timeline)
        # assert
        active_timeline = self.timeline_list.active
        self.assertEqual(timeline, active_timeline)

    def test_active_returns_None_when_empty(self):
        self.assertEqual(self.timeline_list.active, None)

    def test_append_timeline_increases_timeline_size(self):
        self.assertEqual(len(self.timeline_list), 0)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 1)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 2)

    def test_activate_first(self):
        # null index when there are no timelines
        self.timeline_list.activate_first()
        self.assert_null_index()
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the first when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_next()
        self.timeline_list.activate_next()
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_last(self):
        # null index when there are no timelines
        self.timeline_list.activate_last()
        self.assert_null_index()
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the last when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 2)

    def test_shift_active_previous(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_previous()
        self.assert_null_index()
        # does not change if its the first
        self.append_timeline()
        self.timeline_list.shift_active_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_shift_active_next(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_next()
        self.assert_null_index()
        # does not change if its the last
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 0)
        # it increases until reaching the end
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)

    # visibility

    def test_no_visible_when_newly_created(self):
        self.assert_visible([])
        self.timeline_list.expand_visible_previous()

    def test_only_visible_is_index_0_when_appending_first_timeline(self):
        self.append_timeline()
        self.assert_visible([0])

    def test_expand_visible_previous(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assert_visible([0])
        self.timeline_list.activate_last()
        self.assert_visible([2])

        self.timeline_list.expand_visible_previous()
        self.assert_visible([1, 2])
        self.timeline_list.expand_visible_previous()
        self.assert_visible([0, 1, 2])

        # there are no more timelines
        self.timeline_list.expand_visible_previous()
        self.assert_visible([0, 1, 2])

    def test_expand_visible_next(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assert_visible([0])

        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1])
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1, 2])

        # there are no more timelines
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1, 2])

    def test_shrink_visible_beggining(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_last()
        self.timeline_list.expand_visible_previous()
        self.timeline_list.expand_visible_previous()
        self.assert_visible([0, 1, 2])

        self.timeline_list.shrink_visible_beggining()
        self.assert_visible([1, 2])
        self.timeline_list.shrink_visible_beggining()
        self.assert_visible([2])

        # at least the active timeline has to be visible
        self.timeline_list.shrink_visible_beggining()
        self.assert_visible([2])

    def test_shrink_visible_end(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1, 2])

        self.timeline_list.shrink_visible_end()
        self.assert_visible([0, 1])
        self.timeline_list.shrink_visible_end()
        self.assert_visible([0])

        # at least the active timeline has to be visible
        self.timeline_list.shrink_visible_end()
        self.assert_visible([0])

    def test_visible_active_only_when_activating_invisible_timeline(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.assert_visible([0, 1])

        self.timeline_list.activate_last()
        self.assert_visible([2])

        self.timeline_list.expand_visible_previous()
        self.assert_visible([1, 2])

        self.timeline_list.activate_first()
        self.assert_visible([0])

    def test_consistent_visible_timelines_when_deleting_leftmost_active(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0, 1, 2])

        self.timeline_list.delete_active_timeline()

        # active index does not change
        self.assertEqual(self.active_index(), 0)

        # visible
        self.assert_visible([0, 1])

        # relative index
        relative_index = self.timeline_list.active_index_relative_to_visible
        self.assertEqual(relative_index, 0)

    def test_consistent_visible_timelines_when_deleting_middle_active(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0, 1, 2, 3, 4])

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([0, 1, 2, 3, 4])

        self.timeline_list.delete_active_timeline()

        # active index does not change
        self.assertEqual(self.active_index(), 2)

        # visible
        self.assert_visible([0, 1, 2, 3])

        # relative index
        relative_index = self.timeline_list.active_index_relative_to_visible
        self.assertEqual(relative_index, 2)

    def test_consistent_visible_timelines_when_deleting_rightmost_timeline(
            self):

        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 3)

        self.timeline_list.expand_visible_next()
        self.timeline_list.expand_visible_next()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0, 1, 2])

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([0, 1, 2])

        self.timeline_list.delete_active_timeline()
        self.assertEqual(len(self.timeline_list), 2)

        # active index shifts left
        self.assertEqual(self.active_index(), 1)

        # visible
        self.assert_visible([0, 1])

        # relative index
        relative_index = self.timeline_list.active_index_relative_to_visible
        self.assertEqual(relative_index, 1)

    def test_delete_active_timeline_with_one_visible_timeline_in_the_left(
            self):
        self.append_timeline()
        self.append_timeline()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0])

        self.timeline_list.delete_active_timeline()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0])

    def test_delete_active_timeline_with_one_visible_timeline_in_the_middle(
            self):

        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()

        self.assertEqual(self.active_index(), 0)
        self.assert_visible([0])
        self.assertEqual(len(self.timeline_list), 5)

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([2])

        self.timeline_list.delete_active_timeline()
        self.assertEqual(len(self.timeline_list), 4)

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([2])

    def test_delete_active_timeline_with_one_visible_timeline_in_the_right(
            self):

        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 3)

        self.timeline_list.activate_next()
        self.timeline_list.activate_next()

        self.assertEqual(self.active_index(), 2)
        self.assert_visible([2])

        self.timeline_list.delete_active_timeline()
        self.assertEqual(len(self.timeline_list), 2)

        self.assertEqual(self.active_index(), 1)
        self.assert_visible([1])
Ejemplo n.º 9
0
 def setUp(self):
     self.timelines = TimelineList()
     self.controller = Controller(ui=Mock(),
                                  api=MockApi('foo', 'bar'),
                                  timelines=self.timelines)
Ejemplo n.º 10
0
class Controller(Observer):
    """
    The :class:`Controller`.
    """

    # Modes

    INFO_MODE = 0
    TIMELINE_MODE = 1
    HELP_MODE = 2
    EDITOR_MODE = 3
    USER_INFO_MODE = 4

    # -- Initialization -------------------------------------------------------

    def __init__(self, ui, api_backend):
        self.ui = ui
        self.editor = None

        # Model
        self.timelines = TimelineList()
        self.timelines.subscribe(self)

        # Mode
        self.mode = self.INFO_MODE

        # API
        oauth_token = configuration.oauth_token
        oauth_token_secret = configuration.oauth_token_secret
        self.api = AsyncApi(
            api_backend,
            access_token_key=oauth_token,
            access_token_secret=oauth_token_secret,
        )

    def start(self):
        self.main_loop()

    def authenticate_api(self):
        self.info_message(_('Authenticating API'))

        self.api.init_api(
            on_error=self.api_init_error,
            on_success=self.init_timelines,
        )

    @async
    def init_timelines(self):
        # API has to be authenticated
        while (not self.api.is_authenticated):
            pass

        # fetch the authenticated user
        self.user = self.api.verify_credentials()

        # initialize the timelines
        self.info_message(_('Initializing timelines'))
        self.append_default_timelines()

        # Main loop has to be running
        while not getattr(self, 'loop'):
            pass

        # update alarm
        seconds = configuration.update_frequency
        self.loop.set_alarm_in(seconds, self.update_alarm)

    def main_loop(self):
        """
        Launch the main loop of the program.
        """
        if not hasattr(self, 'loop'):
            # Creating the main loop for the first time
            self.key_handler = KeyHandler(self)
            handler = self.key_handler.handle
            self.loop = urwid.MainLoop(self.ui,
                                       configuration.palette,
                                       handle_mouse=False,
                                       unhandled_input=handler)

            # Authenticate API just before starting main loop
            self.authenticate_api()

        try:
            self.loop.run()
        except TweepError, message:
            logging.exception(message)
            self.error_message(_('API error: %s' % message))
            # recover from API errors
            self.main_loop()
Ejemplo n.º 11
0
class TimelineListTest(unittest.TestCase):
    def setUp(self):
        self.timeline_list = TimelineList()

    def append_timeline(self):
        self.timeline_list.append_timeline(Timeline('Timeline'))

    def test_has_timelines_false_if_empty(self):
        self.failIf(self.timeline_list.has_timelines())

    def test_has_timelines_true_otherwise(self):
        self.append_timeline()
        self.failUnless(self.timeline_list.has_timelines())

    def test_null_index_with_no_timelines(self):
        self.assertEqual(self.timeline_list.active_index, ActiveList.NULL_INDEX)

    def test_active_index_0_when_appending_first_timeline(self):
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous(self):
        # null index when there are no timelines
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, ActiveList.NULL_INDEX)
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_next(self):
        # null index when there are no timelines
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, ActiveList.NULL_INDEX)
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous_and_activate_next(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        # next
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        # previous
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_get_active_timeline_name_returns_first_appended(self):
        # append
        name = 'Timeline'
        timeline = Timeline(name)
        self.timeline_list.append_timeline(timeline)
        # assert
        active_timeline_name = self.timeline_list.get_active_timeline_name()
        self.assertEqual(name, active_timeline_name)

    def test_get_active_timeline_name_raises_exception_when_empty(self):
        self.assertRaises(Exception, self.timeline_list.get_active_timeline_name)

    def test_get_active_timeline_returns_first_appended(self):
        # append
        name = 'Timeline'
        timeline = Timeline(name)
        self.timeline_list.append_timeline(timeline)
        # assert
        active_timeline = self.timeline_list.get_active_timeline()
        self.assertEqual(timeline, active_timeline)

    def test_get_active_timeline_raises_exception_when_empty(self):
        self.assertRaises(Exception, self.timeline_list.get_active_timeline)

    def test_append_timeline_increases_timeline_size(self):
        self.assertEqual(len(self.timeline_list), 0)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 1)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 2)

    def test_activate_first(self):
        # null index when there are no timelines
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, ActiveList.NULL_INDEX)
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the first when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_next()
        self.timeline_list.activate_next()
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_last(self):
        # null index when there are no timelines
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, ActiveList.NULL_INDEX)
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the last when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 2)

    def test_shift_active_previous(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_previous()
        self.assertEqual(self.timeline_list.active_index, ActiveList.NULL_INDEX)
        # does not change if its the first
        self.append_timeline()
        self.timeline_list.shift_active_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_shift_active_next(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, ActiveList.NULL_INDEX)
        # does not change if its the last
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 0)
        # it increases until reaching the end
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)
Ejemplo n.º 12
0
class Controller(object):
    """
    An abstract class that implements most of the controller logic of
    ``turses``.

    Subclasses must implement the methods decorated with
    ``abc.abstractmethod``.
    """

    INFO_MODE = 0
    TIMELINE_MODE = 1
    HELP_MODE = 2
    EDITOR_MODE = 3
    USER_INFO_MODE = 4

    __metaclass__ = ABCMeta

    # -- Initialization -------------------------------------------------------

    def __init__(self, configuration, ui, api_backend):
        self.configuration = configuration
        self.ui = ui
        self.editor = None
        self.timelines = TimelineList()

        # Mode
        self.mode = self.INFO_MODE

        # API
        oauth_token = self.configuration.oauth_token
        oauth_token_secret = self.configuration.oauth_token_secret
        self.api = AsyncApi(api_backend,
                            access_token_key=oauth_token,
                            access_token_secret=oauth_token_secret,)

    def start(self):
        self.authenticate_api()
        self.main_loop()

    def authenticate_api(self):
        self.info_message(_('Authenticating API'))

        self.api.init_api(on_error=self.api_init_error,
                          on_success=self.init_timelines,)

    @async
    def init_timelines(self):
        # API has to be authenticated
        while (not self.api.is_authenticated):
            pass
        self.user = self.api.verify_credentials()
        self.info_message(_('Initializing timelines'))
        self.append_default_timelines()
        seconds = self.configuration.update_frequency
        # The main loop must have started
        while (not hasattr(self, 'loop')):
            pass
        # TODO: don't call `loop` explicitly or pull down to `CursesController`
        self.loop.set_alarm_in(seconds, self.update_alarm)

    # abstract methods

    @abstractmethod
    def main_loop(self):
        """
        Main loop of the program, `Controller` subclasses must override this
        method.
        """
        pass

    @abstractmethod
    def exit(self):
        """Exit the program."""
        pass

    # -- Callbacks ------------------------------------------------------------

    def api_init_error(self):
        # TODO retry
        self.error_message(_('Couldn\'t initialize API'))

    def update_alarm(self, *args, **kwargs):
        seconds = self.configuration.update_frequency
        self.update_all_timelines()
        self.loop.set_alarm_in(seconds, self.update_alarm)

    # -- Modes ----------------------------------------------------------------

    def timeline_mode(self):
        """
        Activates the Timeline mode if there are Timelines.

        If not, shows program info.
        """
        if self.is_in_user_info_mode():
            self.ui.hide_user_info()

        if self.is_in_timeline_mode():
            return

        if self.is_in_help_mode():
            self.clear_status()

        if self.timelines.has_timelines():
            self.mode = self.TIMELINE_MODE
            self.draw_timelines()
        else:
            self.mode = self.INFO_MODE
            self.ui.show_info()

        self.redraw_screen()

    def is_in_timeline_mode(self):
        return self.mode == self.TIMELINE_MODE

    def info_mode(self):
        self.mode = self.INFO_MODE
        self.ui.show_info()
        self.redraw_screen()

    def is_in_info_mode(self):
        return self.mode == self.INFO_MODE

    def help_mode(self):
        """Activate help mode."""
        if self.is_in_help_mode():
            return
        self.mode = self.HELP_MODE
        self.ui.show_help(self.configuration)
        self.redraw_screen()

    def is_in_help_mode(self):
        return self.mode == self.HELP_MODE

    def editor_mode(self, editor):
        """Activate editor mode."""
        self.editor = editor
        self.mode = self.EDITOR_MODE

    def is_in_editor_mode(self):
        return self.mode == self.EDITOR_MODE

    def user_info_mode(self, user):
        """Activate user info mode."""
        self._user_info = user
        self.mode = self.USER_INFO_MODE

    def is_in_user_info_mode(self):
        return self.mode == self.USER_INFO_MODE

    # -- Timelines ------------------------------------------------------------

    @wrap_exceptions
    def append_timeline(self,
                        name,
                        update_function,
                        update_args=None,
                        update_kwargs=None):
        """
        Given a name, function to update a timeline and optionally
        arguments to the update function, it creates the timeline and
        appends it to `timelines`.
        """
        timeline = Timeline(name=name,
                            update_function=update_function,
                            update_function_args=update_args,
                            update_function_kwargs=update_kwargs)
        timeline.update()
        timeline.activate_first()
        self.timelines.append_timeline(timeline)
        if self.is_in_info_mode():
            self.timeline_mode()
        self.draw_timelines()

    @async
    def append_default_timelines(self):
        default_timelines = {
            HOME_TIMELINE:       self.append_home_timeline,
            MENTIONS_TIMELINE:   self.append_mentions_timeline,
            FAVORITES_TIMELINE:  self.append_favorites_timeline,
            MESSAGES_TIMELINE:   self.append_direct_messages_timeline,
            OWN_TWEETS_TIMELINE: self.append_own_tweets_timeline,
        }

        timelines = [
            HOME_TIMELINE,
            MENTIONS_TIMELINE,
            FAVORITES_TIMELINE,
            MESSAGES_TIMELINE,
            OWN_TWEETS_TIMELINE,
        ]

        is_any = any([self.configuration.default_timelines[timeline]
                      for timeline in timelines])

        if is_any:
            self.timeline_mode()
        else:
            message = _('You don\'t have any default timelines activated')
            self.info_message(message)
            return

        for timeline in timelines:
            append = default_timelines[timeline]
            if self.configuration.default_timelines[timeline]:
                append()
                self.draw_timelines()
        self.clear_status()

    def append_home_timeline(self):
        timeline_fetched = partial(self.info_message,
                                    _('Home timeline fetched'))
        timeline_not_fetched = partial(self.error_message,
                                        _('Failed to fetch home timeline'))

        self.append_timeline(name=_('tweets'),
                             update_function=self.api.get_home_timeline,
                             on_error=timeline_not_fetched,
                             on_success=timeline_fetched,)

    def append_user_timeline(self, username):
        success_message = _('@%s\'s tweets fetched' % username)
        timeline_fetched = partial(self.info_message,
                                   success_message)
        error_message = _('Failed to fetch @%s\'s tweets' % username)
        timeline_not_fetched = partial(self.error_message,
                                       error_message)

        self.append_timeline(name='@%s' % username,
                             update_function=self.api.get_user_timeline,
                             update_kwargs={'screen_name': username},
                             on_error=timeline_not_fetched,
                             on_success=timeline_fetched,)

    def append_own_tweets_timeline(self):
        timeline_fetched = partial(self.info_message,
                                    _('Your tweets fetched'))
        timeline_not_fetched = partial(self.error_message,
                                        _('Failed to fetch your tweets'))

        if not hasattr(self, 'user'):
            self.user = self.api.verify_credentials()
        self.append_timeline(name='@%s' % self.user.screen_name,
                             update_function=self.api.get_own_timeline,
                             on_error=timeline_not_fetched,
                             on_success=timeline_fetched,)

    def append_mentions_timeline(self):
        timeline_fetched = partial(self.info_message,
                                    _('Mentions fetched'))
        timeline_not_fetched = partial(self.error_message,
                                        _('Failed to fetch mentions'))

        self.append_timeline(name=_('mentions'),
                             update_function=self.api.get_mentions,
                             on_error=timeline_not_fetched,
                             on_success=timeline_fetched,)

    def append_favorites_timeline(self):
        timeline_fetched = partial(self.info_message,
                                    _('Favorites fetched'))
        timeline_not_fetched = partial(self.error_message,
                                        _('Failed to fetch favorites'))

        self.append_timeline(name=_('favorites'),
                             update_function=self.api.get_favorites,
                             on_error=timeline_not_fetched,
                             on_success=timeline_fetched,)

    def append_direct_messages_timeline(self):
        timeline_fetched = partial(self.info_message,
                                    _('Messages fetched'))
        timeline_not_fetched = partial(self.error_message,
                                        _('Failed to fetch messages'))

        self.append_timeline(name=_('messages'),
                             update_function=self.api.get_direct_messages,
                             on_error=timeline_not_fetched,
                             on_success=timeline_fetched,)

    def append_thread_timeline(self):
        status = self.timelines.active_status

        timeline_fetched = partial(self.info_message,
                                   _('Thread fetched'))
        timeline_not_fetched = partial(self.error_message,
                                       _('Failed to fetch thread'))

        if is_DM(status):
            self.error_message(_('Doesn\'t look like a public conversation'))
            return

        participants = status.mentioned_usernames
        author = status.authors_username
        if author not in participants:
            participants.insert(0, author)

        name = _('thread: %s' % ', '.join(participants))
        self.append_timeline(name=name,
                             update_function=self.api.get_thread,
                             update_args=status,
                             on_error=timeline_not_fetched,
                             on_success=timeline_fetched)

    @async
    def update_all_timelines(self):
        for timeline in self.timelines:
            timeline.update()
            self.draw_timelines()
            self.info_message(_('%s updated' % timeline.name))
        self.clear_status()

    # -- Timeline mode --------------------------------------------------------

    def draw_timelines(self):
        if self.is_in_timeline_mode():
            self.update_header()
            self.draw_timeline_buffer()

    def update_header(self):
        template = self.configuration.styles['tab_template']
        name_and_unread = [(tl.name, tl.unread_count) for tl in self.timelines]

        tabs = [template.format(timeline_name=name, unread=unread)
                for (name, unread) in name_and_unread]
        self.ui.set_tab_names(tabs)

        # highlight the active
        active_index = self.timelines.active_index
        self.ui.activate_tab(active_index)

        # colorize the visible tabs
        visible_indexes = self.timelines.visible
        self.ui.header.set_visible_tabs(visible_indexes)

    def draw_timeline_buffer(self):
        # draw visible timelines
        visible_timelines = self.timelines.visible_timelines
        self.ui.draw_timelines(visible_timelines)
        # focus active timeline
        active_timeline = self.timelines.active
        active_pos = self.timelines.active_index_relative_to_visible
        # focus active status
        self.ui.focus_timeline(active_pos)
        self.ui.focus_status(active_timeline.active_index)

    def mark_all_as_read(self):
        """Mark all statuses in active timeline as read."""
        active_timeline = self.timelines.active
        for tweet in active_timeline:
            tweet.read = True
        self.update_header()

    @async
    def update_active_timeline(self):
        """Updates the timeline and renders the active timeline."""
        if self.timelines.has_timelines():
            active_timeline = self.timelines.active
            try:
                newest = active_timeline[0]
            except IndexError:
                return
            active_timeline.update(since_id=newest.id)
            if self.is_in_timeline_mode():
                self.draw_timelines()
            self.info_message('%s updated' % active_timeline.name)

    @async
    def update_active_timeline_with_newer_statuses(self):
        """
        Updates the active timeline with newer tweets than the active.
        """
        active_timeline = self.timelines.active
        active_status = active_timeline.active
        if active_status:
            active_timeline.update(since_id=active_status.id)

    @async
    def update_active_timeline_with_older_statuses(self):
        """
        Updates the active timeline with older tweets than the active.
        """
        active_timeline = self.timelines.active
        active_status = active_timeline.active
        if active_status:
            active_timeline.update(max_id=active_status.id)

    def previous_timeline(self):
        if self.timelines.has_timelines():
            self.timelines.activate_previous()
            self.draw_timelines()

    def next_timeline(self):
        if self.timelines.has_timelines():
            self.timelines.activate_next()
            self.draw_timelines()

    def shift_buffer_left(self):
        if self.timelines.has_timelines():
            self.timelines.shift_active_previous()
            self.draw_timelines()

    def shift_buffer_right(self):
        if self.timelines.has_timelines():
            self.timelines.shift_active_next()
            self.draw_timelines()

    def shift_buffer_beggining(self):
        if self.timelines.has_timelines():
            self.timelines.shift_active_beggining()
            self.draw_timelines()

    def shift_buffer_end(self):
        if self.timelines.has_timelines():
            self.timelines.shift_active_end()
            self.draw_timelines()

    def expand_buffer_left(self):
        if self.timelines.has_timelines():
            self.timelines.expand_visible_previous()
            self.draw_timelines()

    def expand_buffer_right(self):
        if self.timelines.has_timelines():
            self.timelines.expand_visible_next()
            self.draw_timelines()

    def shrink_buffer_left(self):
        if self.timelines.has_timelines():
            self.timelines.shrink_visible_beggining()
            self.draw_timelines()

    def shrink_buffer_right(self):
        if self.timelines.has_timelines():
            self.timelines.shrink_visible_end()
            self.draw_timelines()

    def activate_first_buffer(self):
        if self.timelines.has_timelines():
            self.timelines.activate_first()
            self.draw_timelines()

    def activate_last_buffer(self):
        if self.timelines.has_timelines():
            self.timelines.activate_last()
            self.draw_timelines()

    def delete_buffer(self):
        self.timelines.delete_active_timeline()
        if self.timelines.has_timelines():
            self.draw_timelines()
        else:
            self.info_mode()

    # -- Motion ---------------------------------------------------------------

    def scroll_up(self):
        self.ui.focus_previous()
        if self.is_in_timeline_mode():
            active_timeline = self.timelines.active
            # update with newer tweets when scrolling down being at the bottom
            if active_timeline.active_index == 0:
                self.update_active_timeline_with_newer_statuses()
            active_timeline.activate_previous()
            self.draw_timelines()

    def scroll_down(self):
        self.ui.focus_next()
        if self.is_in_timeline_mode():
            active_timeline = self.timelines.active
            # update with older tweets when scrolling down being at the bottom
            if active_timeline.active_index == len(active_timeline) - 1:
                self.update_active_timeline_with_older_statuses()
            active_timeline.activate_next()
            self.draw_timelines()

    def scroll_top(self):
        self.ui.focus_first()
        if self.is_in_timeline_mode():
            active_timeline = self.timelines.active
            active_timeline.activate_first()
            self.draw_timelines()

    def scroll_bottom(self):
        self.ui.focus_last()
        if self.is_in_timeline_mode():
            active_timeline = self.timelines.active
            active_timeline.activate_last()
            self.draw_timelines()

    # -- Footer ---------------------------------------------------------------

    def error_message(self, message):
        self.ui.status_error_message(message)
        self.redraw_screen()

    def info_message(self, message):
        self.ui.status_info_message(message)
        self.redraw_screen()

    def clear_body(self):
        """Clear body."""
        self.ui.body.clear()

    def clear_status(self):
        """Clear the status bar."""
        self.ui.clear_status()
        self.redraw_screen()

    # -- UI -------------------------------------------------------------------

    @abstractmethod
    def redraw_screen(self):
        pass

    # -- Editor ---------------------------------------------------------------

    def forward_to_editor(self, key):
        if self.editor:
            size = 20,
            self.editor.keypress(size, key)

    @text_from_editor
    def tweet_handler(self, text):
        """Handle the post as a tweet of the given `text`."""
        self.info_message(_('Sending tweet'))

        if not is_valid_status_text(text):
            # <Esc> was pressed
            self.info_message(_('Tweet canceled'))
            return

        tweet_sent = partial(self.info_message, _('Tweet sent'))
        tweet_not_sent = partial(self.error_message, _('Tweet not sent'))

        # API call
        self.api.update(text=text,
                        on_success=tweet_sent,
                        on_error=tweet_not_sent,)

    @text_from_editor
    def direct_message_handler(self, username, text):
        """Handle the post as a DM of the given `text` to `username`."""
        self.info_message(_('Sending DM'))

        if not is_valid_status_text(text):
            # <Esc> was pressed
            self.info_message(_('DM canceled'))
            return

        dm_info = _('Direct Message to @%s sent' % username)
        dm_sent = partial(self.info_message, dm_info)
        dm_error = _('Failed to send message to @%s' % username)
        dm_not_sent = partial(self.error_message, dm_error)

        self.api.direct_message(screen_name=username,
                                text=text,
                                on_success=dm_sent,
                                on_error=dm_not_sent,)

    @text_from_editor
    def follow_user_handler(self, username):
        """
        Handles following the user given in `username`.
        """
        if username is None:
            self.info_message(_('Search cancelled'))
            return

        username = sanitize_username(username)
        if username == self.user.screen_name:
            self.error_message(_('You can\'t follow yourself'))
            return

        # TODO make sure that the user EXISTS and THEN follow
        if not is_username(username):
            self.info_message(_('Invalid username'))
            return
        else:
            self.info_message(_('Following @%s' % username))

        success_message = _('You are now following @%s' % username)
        follow_done = partial(self.info_message,
                              success_message)

        error_template = _('We can not ensure that you are following @%s')
        error_message = error_template % username
        follow_error = partial(self.error_message,
                               error_message)

        self.api.create_friendship(screen_name=username,
                                   on_error=follow_error,
                                   on_success=follow_done)

    @text_from_editor
    def unfollow_user_handler(self, username):
        """
        Handles unfollowing the user given in `username`.
        """
        if username is None:
            self.info_message(_('Search cancelled'))
            return

        username = sanitize_username(username)
        if username == self.user.screen_name:
            self.error_message(_('That doesn\'t make any sense'))
            return

        # TODO make sure that the user EXISTS and THEN follow
        if not is_username(username):
            self.info_message(_('Invalid username'))
            return
        else:
            self.info_message(_('Unfollowing @%s' % username))

        success_message = _('You are no longer following %s' % username)
        unfollow_done = partial(self.info_message,
                                success_message)

        error_template = _('We can not ensure that you are not following %s')
        error_message = error_template % username
        unfollow_error = partial(self.error_message,
                                 error_message)

        self.api.destroy_friendship(screen_name=username,
                                    on_error=unfollow_error,
                                    on_success=unfollow_done)

    @text_from_editor
    def search_handler(self, text):
        """
        Handles creating a timeline tracking the search term given in
        `text`.
        """
        if text is None:
            self.info_message(_('Search cancelled'))
            return

        text = text.strip()
        if not is_valid_search_text(text):
            self.error_message(_('Invalid search'))
            return
        else:
            self.info_message(_('Creating search timeline for "%s"' % text))

        success_message = _('Search timeline for "%s" created' % text)
        timeline_created = partial(self.info_message,
                                   success_message)
        error_message = _('Error creating search timeline for "%s"' % text)
        timeline_not_created = partial(self.info_message,
                                       error_message)

        self.append_timeline(name=_('Search: %s' % text),
                             update_function=self.api.search,
                             update_args=text,
                             on_error=timeline_not_created,
                             on_success=timeline_created)

    @text_from_editor
    def search_user_handler(self, username):
        """
        Handles creating a timeline tracking the searched user's tweets.
        """
        if username is None:
            self.info_message(_('Search cancelled'))
            return

        # TODO make sure that the user EXISTS and THEN fetch its tweets
        username = sanitize_username(username)
        if not is_username(username):
            self.info_message(_('Invalid username'))
            return
        else:
            self.info_message(_('Fetching latest tweets from @%s' % username))

        success_message = _('@%s\'s timeline created' % username)
        timeline_created = partial(self.info_message,
                                   success_message)
        error_message = _('Unable to create @%s\'s timeline' % username)
        timeline_not_created = partial(self.error_message,
                                       error_message)

        self.append_timeline(name='@%s' % username,
                             update_function=self.api.get_user_timeline,
                             update_args=username,
                             on_success=timeline_created,
                             on_error=timeline_not_created)

    # -- Twitter --------------------------------------------------------------

    def search(self, text=None):
        text = '' if text is None else text
        handler = self.search_handler
        editor = self.ui.show_text_editor(prompt='Search',
                                          content=text,
                                          done_signal_handler=handler)
        self.editor_mode(editor)

    def search_user(self):
        prompt = _('Search user (no need to prepend it with "@"')
        handler = self.search_user_handler
        editor = self.ui.show_text_editor(prompt=prompt,
                                 content='',
                                 done_signal_handler=handler)
        self.editor_mode(editor)

    @has_active_status
    def search_hashtags(self):
        status = self.timelines.active_status

        hashtags = ' '.join(status.hashtags)
        self.search_handler(text=hashtags)

    @has_active_status
    def focused_status_author_timeline(self):
        status = self.timelines.active_status

        author = status.authors_username
        self.append_user_timeline(author)

    def tweet(self,
              prompt=_('Tweet'),
              content='',
              cursor_position=None):
        handler = self.tweet_handler
        editor = self.ui.show_tweet_editor(prompt=prompt,
                                           content=content,
                                           done_signal_handler=handler,
                                           cursor_position=cursor_position)
        self.editor_mode(editor)

    @has_active_status
    def retweet(self):
        status = self.timelines.active_status

        if is_DM(status):
            self.error_message(_('You can\'t retweet direct messages'))
            return

        retweet_posted = partial(self.info_message,
                                 _('Retweet posted'))
        retweet_post_failed = partial(self.error_message,
                                      _('Failed to post retweet'))
        self.api.retweet(on_error=retweet_post_failed,
                         on_success=retweet_posted,
                         status=status,)

    @has_active_status
    def manual_retweet(self):
        status = self.timelines.active_status

        rt_text = ''.join([' RT @%s: ' % status.authors_username,
                           status.text])
        if is_valid_status_text(rt_text):
            self.tweet(content=rt_text,
                       cursor_position=0)
        else:
            self.error_message(_('Tweet too long for manual retweet'))

    @has_active_status
    def reply(self):
        status = self.timelines.active_status

        if is_DM(status):
            self.direct_message()
            return

        author = status.authors_username
        mentioned = status.mentioned_for_reply
        try:
            mentioned.remove('@%s' % self.user.screen_name)
        except ValueError:
            pass

        handler = self.tweet_handler
        editor = self.ui.show_tweet_editor(prompt=_('Reply to %s' % author),
                                           content=' '.join(mentioned),
                                           done_signal_handler=handler)
        self.editor_mode(editor)

    @has_active_status
    def direct_message(self):
        status = self.timelines.active_status

        recipient = status.dm_recipients_username(self.user.screen_name)
        if recipient:
            handler = self.direct_message_handler
            editor = self.ui.show_dm_editor(prompt=_('DM to %s' % recipient),
                                            content='',
                                            recipient=recipient,
                                            done_signal_handler=handler)
            self.editor_mode(editor)
        else:
            self.error_message(_('What do you mean?'))

    @has_active_status
    def tweet_with_hashtags(self):
        status = self.timelines.active_status

        hashtags = ' '.join(status.hashtags)
        if hashtags:
            handler = self.tweet_handler
            content = ''.join([' ', hashtags])
            editor = self.ui.show_tweet_editor(prompt=_('%s' % hashtags),
                                               content=content,
                                               done_signal_handler=handler,
                                               cursor_position=0)
            self.editor_mode(editor)

    @has_active_status
    def delete_tweet(self):
        status = self.timelines.active_status

        if is_DM(status):
            self.delete_dm()
            return

        author = status.authors_username
        if (author != self.user.screen_name and
            status.user != self.user.screen_name):
            self.error_message(_('You can only delete your own tweets'))
            return

        status_deleted = partial(self.info_message,
                                 _('Tweet deleted'))
        status_not_deleted = partial(self.error_message,
                                     _('Failed to delete tweet'))

        self.api.destroy_status(status=status,
                                on_error=status_not_deleted,
                                on_success=status_deleted)

    def delete_dm(self):
        dm = self.timelines.active_status
        if dm is None:
            return

        if dm.sender_screen_name != self.user.screen_name:
            self.error_message(_('You can only delete messages sent by you'))
            return

        dm_deleted = partial(self.info_message,
                             _('Message deleted'))
        dm_not_deleted = partial(self.error_message,
                                 _('Failed to delete message'))

        self.api.destroy_direct_message(status=dm,
                                        on_error=dm_not_deleted,
                                        on_success=dm_deleted)

    @has_active_status
    def follow_selected(self):
        status = self.timelines.active_status

        username = status.authors_username
        if username == self.user.screen_name:
            self.error_message(_('You can\'t follow yourself'))
            return

        success_message = _('You are now following @%s' % username)
        follow_done = partial(self.info_message,
                              success_message)

        error_template = _('We can not ensure that you are following @%s')
        error_message = error_template % username
        follow_error = partial(self.error_message,
                               error_message)

        self.api.create_friendship(screen_name=username,
                                   on_error=follow_error,
                                   on_success=follow_done)

    def follow_user(self,
                    prompt=_('Follow user (no need to prepend it with "@"'),
                    content='',
                    cursor_position=None):
        handler = self.follow_user_handler
        editor = self.ui.show_text_editor(prompt=prompt,
                                          content=content,
                                          done_signal_handler=handler,
                                          cursor_position=cursor_position)
        self.editor_mode(editor)

    def unfollow_user(self,
                      prompt=_('Unfollow user (no need to prepend it with'
                               ' "@"'),
                      content='',
                      cursor_position=None):
        handler = self.unfollow_user_handler
        editor = self.ui.show_text_editor(prompt=prompt,
                                          content=content,
                                          done_signal_handler=handler,
                                          cursor_position=cursor_position)
        self.editor_mode(editor)

    @has_active_status
    def unfollow_selected(self):
        status = self.timelines.active_status

        username = status.authors_username
        if username == self.user.screen_name:
            self.error_message(_('That doesn\'t make any sense'))
            return

        success_message = _('You are no longer following %s' % username)
        unfollow_done = partial(self.info_message,
                                success_message)

        error_template = _('We can not ensure that you are not following %s')
        error_message = error_template % username
        unfollow_error = partial(self.error_message,
                                 error_message)

        self.api.destroy_friendship(screen_name=username,
                                    on_error=unfollow_error,
                                    on_success=unfollow_done)

    @has_active_status
    def favorite(self):
        status = self.timelines.active_status

        favorite_error = partial(self.error_message,
                                 _('Failed to mark tweet as favorite'))
        favorite_done = partial(self.info_message,
                                _('Tweet marked as favorite'))
        self.api.create_favorite(on_error=favorite_error,
                                 on_success=favorite_done,
                                 status=status,)

    @has_active_status
    def unfavorite(self):
        status = self.timelines.active_status

        unfavorite_error = partial(self.error_message,
                                   _('Failed to remove tweet from favorites'))
        unfavorite_done = partial(self.info_message,
                                  _('Tweet removed from favorites'))
        self.api.destroy_favorite(on_error=unfavorite_error,
                                  on_success=unfavorite_done,
                                  status=status,)

    @has_active_status
    def user_info(self):
        status = self.timelines.active_status

        user = self.api.get_user(status.authors_username)
        self.ui.show_user_info(user)
        self.user_info_mode(user)

    # - Configuration ---------------------------------------------------------

    def reload_configuration(self):
        self.configuration.reload()
        self.redraw_screen()
        self.info_message(_('Configuration reloaded'))

    # - Browser ---------------------------------------------------------------

    @has_active_status
    def open_urls(self):
        """
        Open the URLs contained on the focused tweets in a browser.
        """
        status = self.timelines.active_status
        urls = get_urls(status.text)

        if not urls:
            self.info_message(_('No URLs found on this tweet'))
            return

        args = ' '.join(urls)
        self.open_urls_in_browser(args)

    @has_active_status
    def open_status_url(self):
        """
        Open the focused tweet in a browser.
        """
        status = self.timelines.active_status

        if is_DM(status):
            message = _('You only can open regular statuses in a browser')
            self.info_message(message)
            return

        self.open_urls_in_browser(status.url)

    def open_urls_in_browser(self, urls):
        """
        Open `urls` in $BROWSER if the environment variable is set.
        """
        command = self.configuration.browser
        if not command:
            message = _('You have to set the BROWSER environment variable'
                        ' to open URLs')
            self.error_message(message)
            return

        try:
            spawn_process(command, urls)
        except:
            self.error_message(_('Unable to launch the browser'))
Ejemplo n.º 13
0
class Controller(Observer):
    """
    The :class:`Controller`.
    """

    # Modes

    INFO_MODE = 0
    TIMELINE_MODE = 1
    HELP_MODE = 2
    EDITOR_MODE = 3
    USER_INFO_MODE = 4

    # -- Initialization -------------------------------------------------------

    def __init__(self, ui, api_backend):
        self.ui = ui
        self.editor = None

        # Model
        self.timelines = TimelineList()
        self.timelines.subscribe(self)

        # Mode
        self.mode = self.INFO_MODE

        # API
        oauth_token = configuration.oauth_token
        oauth_token_secret = configuration.oauth_token_secret
        self.api = AsyncApi(api_backend,
                            access_token_key=oauth_token,
                            access_token_secret=oauth_token_secret,)

    def start(self):
        self.main_loop()

    def authenticate_api(self):
        self.info_message(_('Authenticating API'))

        self.api.init_api(on_error=self.api_init_error,
                          on_success=self.init_timelines,)

    @async
    def init_timelines(self):
        # API has to be authenticated
        while (not self.api.is_authenticated):
            pass

        # fetch the authenticated user
        self.user = self.api.verify_credentials()

        # initialize the timelines
        self.info_message(_('Initializing timelines'))
        self.append_default_timelines()

        # Main loop has to be running
        while not getattr(self, 'loop'):
            pass

        # update alarm
        seconds = configuration.update_frequency
        self.loop.set_alarm_in(seconds, self.update_alarm)

    def main_loop(self):
        """
        Launch the main loop of the program.
        """
        if not hasattr(self, 'loop'):
            # Creating the main loop for the first time
            self.key_handler = KeyHandler(self)
            handler = self.key_handler.handle
            self.loop = urwid.MainLoop(self.ui,
                                       configuration.palette,
                                       handle_mouse=False,
                                       unhandled_input=handler)

            # Authenticate API just before starting main loop
            self.authenticate_api()

        try:
            self.loop.run()
        except TweepError, message:
            logging.exception(message)
            self.error_message(_('API error: %s' % message))
            # recover from API errors
            self.main_loop()
Ejemplo n.º 14
0
class TimelineListTest(unittest.TestCase):
    def setUp(self):
        self.timeline_list = TimelineList()

    def append_timeline(self):
        self.timeline_list.append_timeline(Timeline('Timeline'))

    def test_has_timelines_false_if_empty(self):
        self.failIf(self.timeline_list.has_timelines())

    def test_has_timelines_true_otherwise(self):
        self.append_timeline()
        self.failUnless(self.timeline_list.has_timelines())

    def test_null_index_with_no_timelines(self):
        self.assertEqual(self.timeline_list.active_index,
                         ActiveList.NULL_INDEX)

    def test_active_index_0_when_appending_first_timeline(self):
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous(self):
        # null index when there are no timelines
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index,
                         ActiveList.NULL_INDEX)
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_next(self):
        # null index when there are no timelines
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index,
                         ActiveList.NULL_INDEX)
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_previous_and_activate_next(self):
        self.append_timeline()
        self.append_timeline()
        self.append_timeline()
        # next
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        # previous
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.timeline_list.activate_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_get_active_timeline_name_returns_first_appended(self):
        # append
        name = 'Timeline'
        timeline = Timeline(name)
        self.timeline_list.append_timeline(timeline)
        # assert
        active_timeline_name = self.timeline_list.get_active_timeline_name()
        self.assertEqual(name, active_timeline_name)

    def test_get_active_timeline_name_raises_exception_when_empty(self):
        self.assertRaises(Exception,
                          self.timeline_list.get_active_timeline_name)

    def test_get_active_timeline_returns_first_appended(self):
        # append
        name = 'Timeline'
        timeline = Timeline(name)
        self.timeline_list.append_timeline(timeline)
        # assert
        active_timeline = self.timeline_list.get_active_timeline()
        self.assertEqual(timeline, active_timeline)

    def test_get_active_timeline_raises_exception_when_empty(self):
        self.assertRaises(Exception, self.timeline_list.get_active_timeline)

    def test_append_timeline_increases_timeline_size(self):
        self.assertEqual(len(self.timeline_list), 0)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 1)
        self.append_timeline()
        self.assertEqual(len(self.timeline_list), 2)

    def test_activate_first(self):
        # null index when there are no timelines
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index,
                         ActiveList.NULL_INDEX)
        # does not change if its the first
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the first when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_next()
        self.timeline_list.activate_next()
        self.timeline_list.activate_first()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_activate_last(self):
        # null index when there are no timelines
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index,
                         ActiveList.NULL_INDEX)
        # does not change if its the last
        self.append_timeline()
        self.assertEqual(self.timeline_list.active_index, 0)
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 0)
        # moves to the last when in another position
        self.append_timeline()
        self.append_timeline()
        self.timeline_list.activate_last()
        self.assertEqual(self.timeline_list.active_index, 2)

    def test_shift_active_previous(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_previous()
        self.assertEqual(self.timeline_list.active_index,
                         ActiveList.NULL_INDEX)
        # does not change if its the first
        self.append_timeline()
        self.timeline_list.shift_active_previous()
        self.assertEqual(self.timeline_list.active_index, 0)

    def test_shift_active_next(self):
        # null index when there are no timelines
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index,
                         ActiveList.NULL_INDEX)
        # does not change if its the last
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 0)
        # it increases until reaching the end
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 1)
        self.append_timeline()
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)
        self.timeline_list.shift_active_next()
        self.assertEqual(self.timeline_list.active_index, 2)