Browse Source

turses

master
Shaggy 6 years ago
parent
commit
32d8bdb387
54 changed files with 6018 additions and 0 deletions
  1. BIN
      turses/pkg/turses/.MTREE
  2. 27
    0
      turses/pkg/turses/.PKGINFO
  3. 10
    0
      turses/pkg/turses/usr/bin/turses
  4. 50
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/PKG-INFO
  5. 33
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/SOURCES.txt
  6. 1
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/dependency_links.txt
  7. 3
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/entry_points.txt
  8. 3
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/requires.txt
  9. 1
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/top_level.txt
  10. 16
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/__init__.py
  11. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/__init__.pyc
  12. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/__init__.pyo
  13. 5
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/__init__.py
  14. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/__init__.pyc
  15. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/__init__.pyo
  16. 359
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/backends.py
  17. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/backends.pyc
  18. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/backends.pyo
  19. 469
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/base.py
  20. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/base.pyc
  21. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/base.pyo
  22. 199
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/debug.py
  23. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/debug.pyc
  24. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/debug.pyo
  25. 149
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/helpers.py
  26. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/helpers.pyc
  27. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/helpers.pyo
  28. 179
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/cli.py
  29. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/cli.pyc
  30. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/cli.pyo
  31. 778
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/config.py
  32. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/config.pyc
  33. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/config.pyo
  34. 1326
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/core.py
  35. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/core.pyc
  36. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/core.pyo
  37. 266
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/meta.py
  38. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/meta.pyc
  39. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/meta.pyo
  40. 615
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/models.py
  41. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/models.pyc
  42. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/models.pyo
  43. 200
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/session.py
  44. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/session.pyc
  45. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/session.pyo
  46. 1249
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/ui.py
  47. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/ui.pyc
  48. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/ui.pyo
  49. 80
    0
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/utils.py
  50. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/utils.pyc
  51. BIN
      turses/pkg/turses/usr/lib/python2.7/site-packages/turses/utils.pyo
  52. BIN
      turses/turses-0.2.23-2-any.pkg.tar.xz
  53. BIN
      turses/turses-0.2.23-2.src.tar.gz
  54. BIN
      turses/turses-0.2.23.tar.gz

BIN
turses/pkg/turses/.MTREE View File


+ 27
- 0
turses/pkg/turses/.PKGINFO View File

@@ -0,0 +1,27 @@
# Generated by makepkg 4.1.2
# using fakeroot version 1.20.2
# Sun Dec 14 15:03:54 UTC 2014
pkgname = turses
pkgver = 0.2.23-2
pkgdesc = A Twitter client for the console
url = http://pypi.python.org/pypi/turses/
builddate = 1418569434
packager = Unknown Packager
size = 618496
arch = any
license = GPLv3
depend = ncurses
depend = python2
depend = python2-oauth2
depend = python2-tweepy
depend = python2-urwid
depend = python2-setuptools
makepkgopt = strip
makepkgopt = docs
makepkgopt = !libtool
makepkgopt = !staticlibs
makepkgopt = emptydirs
makepkgopt = zipman
makepkgopt = purge
makepkgopt = !upx
makepkgopt = !debug

+ 10
- 0
turses/pkg/turses/usr/bin/turses View File

@@ -0,0 +1,10 @@
#!/bin/python2
# EASY-INSTALL-ENTRY-SCRIPT: 'turses==0.2.23','console_scripts','turses'
__requires__ = 'turses==0.2.23'
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
sys.exit(
load_entry_point('turses==0.2.23', 'console_scripts', 'turses')()
)

+ 50
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/PKG-INFO View File

@@ -0,0 +1,50 @@
Metadata-Version: 1.1
Name: turses
Version: 0.2.23
Summary: A Twitter client for the console.
Home-page: http://github.com/alejandrogomez/turses
Author: Alejandro Gómez
Author-email: alejandro@dialelo.com
License: UNKNOWN
Description:
turses
======
A Twitter client for the console.
The goal of the project is to build a full-featured, lightweight, and extremely
configurable Twitter client.
Documentation
-------------
The documentation for ``turses`` is `available on ReadTheDocs
<http://turses.readthedocs.org>`_.
License
-------
``turses`` is licensed under a GPLv3 license, see ``LICENSE`` for details.
Authors
-------
``turses`` is based on `Tyrs`_ by `Nicolas Paris`_.
.. _`Tyrs`: http://tyrs.nicosphere.net
.. _`Nicolas Paris`: http://github.com/Nic0
See ``AUTHORS`` for a full list of contributors.
Keywords: twitter client,curses,console,twitter
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console :: Curses
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Natural Language :: English
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Topic :: Communications

+ 33
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/SOURCES.txt View File

@@ -0,0 +1,33 @@
MANIFEST.in
README.rst
setup.cfg
setup.py
tests/__init__.py
tests/test_api.py
tests/test_config.py
tests/test_core.py
tests/test_meta.py
tests/test_models.py
tests/test_session.py
tests/test_ui.py
tests/test_utils.py
turses/__init__.py
turses/cli.py
turses/config.py
turses/core.py
turses/meta.py
turses/models.py
turses/session.py
turses/ui.py
turses/utils.py
turses.egg-info/PKG-INFO
turses.egg-info/SOURCES.txt
turses.egg-info/dependency_links.txt
turses.egg-info/entry_points.txt
turses.egg-info/requires.txt
turses.egg-info/top_level.txt
turses/api/__init__.py
turses/api/backends.py
turses/api/base.py
turses/api/debug.py
turses/api/helpers.py

+ 1
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/dependency_links.txt View File

@@ -0,0 +1 @@


+ 3
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/entry_points.txt View File

@@ -0,0 +1,3 @@
[console_scripts]
turses = turses.cli:main


+ 3
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/requires.txt View File

@@ -0,0 +1,3 @@
oauth2
urwid
tweepy >2.2,<3

+ 1
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses-0.2.23-py2.7.egg-info/top_level.txt View File

@@ -0,0 +1 @@
turses

+ 16
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/__init__.py View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-

"""
turses
~~~~~~

A Twitter client for the console.
"""

__title__ = "turses"
__author__ = "Alejandro Gómez"
__copyright__ = "Copyright 2012-2013 turses contributors"
__license__ = "GPL3"
__version__ = (0, 2, 23)

version = "%s.%s.%s" % __version__

BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/__init__.pyc View File


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/__init__.pyo View File


+ 5
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/__init__.py View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-

"""
This module contains the Twitter API implementations.
"""

BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/__init__.pyc View File


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/__init__.pyo View File


+ 359
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/backends.py View File

@@ -0,0 +1,359 @@
# -*- coding: utf-8 -*-

"""
This module contains implementations of :class:`turses.api.base.ApiAdapter`
using libraries for accessing the Twitter API.
"""

from functools import wraps, partial

from tweepy import API as BaseTweepyApi
from tweepy import OAuthHandler as TweepyOAuthHandler

from turses.config import configuration
from turses.meta import filter_result
from turses.models import User, Status, DirectMessage, List
from turses.api.base import ApiAdapter


def include_entities(func):
"""
Injects the `include_entities=True` keyword argument into `func`.
"""
@wraps(func)
def wrapper(*args, **kwargs):
kwargs['include_entities'] = True
return func(*args, **kwargs)
return wrapper

# Decorators for converting data to `turses.models`


def _to_status(status, **kwargs):
"""
Convert a `tweepy.Status` to a `turses.models.Status`.
"""
defaults = {
'id': status.id,
'created_at': status.created_at,
'user': None,
'text': status.text,
'is_reply': False,
'is_retweet': False,
'is_favorite': False,
'in_reply_to_user': '',
'in_reply_to_status_id': None,
'retweeted_status': None,
'retweet_count': 0,
'author': '',
'entities': getattr(status, 'entities', None),
}

# When fetching an individual user her last status is included and
# does not include a `user` attribute
if getattr(status, 'user', None):
defaults['user'] = status.user.screen_name

if hasattr(status, 'retweeted_status'):
defaults['is_retweet'] = True
defaults['retweeted_status'] = _to_status(status.retweeted_status)
defaults['retweet_count'] = status.retweet_count

# the `retweeted_status` could not have a `user` attribute
# (e.g. when fetching a user and her last status is a retweet)
if hasattr(status.retweeted_status, 'user'):
defaults['author'] = status.retweeted_status.user.screen_name

if getattr(status, 'in_reply_to_screen_name', False):
defaults['is_reply'] = True
defaults['in_reply_to_user'] = status.in_reply_to_screen_name

if getattr(status, 'in_reply_to_status_id', False):
defaults['in_reply_to_status_id'] = status.in_reply_to_status_id

if hasattr(status, 'favorited'):
defaults['is_favorite'] = status.favorited

defaults.update(**kwargs)
return Status(**defaults)


def _to_direct_message(dm, **kwargs):
"""
Convert a `tweepy.DirectMessage` to a `turses.models.DirectMessage`.
"""
defaults = {
'id': dm.id,
'created_at': dm.created_at,
'sender_screen_name': dm.sender_screen_name,
'recipient_screen_name': dm.recipient_screen_name,
'text': dm.text,
'entities': getattr(dm, 'entities', None),
}

defaults.update(**kwargs)
return DirectMessage(**defaults)


def _to_user(user, **kwargs):
"""
Convert a `tweepy.User` to a `turses.models.User`.
"""

defaults = {
'id': user.id,
'name': user.name,
'screen_name': user.screen_name,
'description': user.description,
'url': user.url,
'created_at': user.created_at,
'friends_count': user.friends_count,
'followers_count': user.followers_count,
'favorites_count': user.favourites_count,
}

if hasattr(user, 'status'):
status = _to_status(user.status, user=user.screen_name)
defaults['status'] = status

defaults.update(**kwargs)
return User(**defaults)


def _to_list(a_list, **kwargs):
"""
Convert a `tweepy.List` to a `turses.models.List`.
"""
defaults = {
'id': a_list.id,
'owner': _to_user(a_list.user),
# TODO: `created_at` should be a datetime object
'created_at': a_list.created_at,
'name': a_list.name,
'slug': a_list.slug,
'description': a_list.description,
'member_count': a_list.member_count,
'subscriber_count': a_list.subscriber_count,
'private': a_list.mode == u'private',
}

defaults.update(**kwargs)
return List(**defaults)

to_status = partial(filter_result,
filter_func=_to_status)
to_direct_message = partial(filter_result,
filter_func=_to_direct_message)
to_user = partial(filter_result,
filter_func=_to_user)
to_list = partial(filter_result,
filter_func=_to_list)


class TweepyApi(BaseTweepyApi, ApiAdapter):
"""
A :class:`turses.api.ApiAdapter` implementation using `tweepy` library.

http://github.com/tweepy/tweepy/
"""

def __init__(self, *args, **kwargs):
ApiAdapter.__init__(self, *args, **kwargs)

# from `turses.api.base.ApiAdapter`

def init_api(self):
oauth_handler = TweepyOAuthHandler(self._consumer_key,
self._consumer_secret,
secure=configuration.twitter['use_https'])
oauth_handler.set_access_token(self._access_token_key,
self._access_token_secret)
self._api = BaseTweepyApi(oauth_handler, secure=configuration.twitter['use_https'])

@to_user
def verify_credentials(self):
return self._api.me()

@to_user
@include_entities
def get_user(self, screen_name, **kwargs):
return self._api.get_user(screen_name=screen_name, **kwargs)

# timelines

@to_status
@include_entities
def get_status(self, status_id, **kwargs):
return self._api.get_status(status_id, **kwargs)

@to_status
@include_entities
def get_home_timeline(self, **kwargs):
tweets = self._api.home_timeline(**kwargs)
return tweets

@to_status
@include_entities
def get_user_timeline(self, screen_name, **kwargs):
return self._api.user_timeline(screen_name, **kwargs)

@to_status
@include_entities
def get_own_timeline(self, **kwargs):
me = self.verify_credentials()
return self._api.user_timeline(screen_name=me.screen_name, **kwargs)

@to_status
@include_entities
def get_mentions(self, **kwargs):
return self._api.mentions_timeline(**kwargs)

@to_status
@include_entities
def get_favorites(self, **kwargs):
return self._api.favorites(**kwargs)

@to_direct_message
@include_entities
def get_direct_messages(self, **kwargs):
dms = self._api.direct_messages(**kwargs)
sent = self._api.sent_direct_messages(**kwargs)
dms.extend(sent)
return dms

@include_entities
def get_thread(self, status, **kwargs):
"""
Get the conversation to which `status` belongs.
"""
users_in_conversation = [status.authors_username]

# Save the users that are mentioned
for user in status.mentioned_usernames:
if user not in users_in_conversation:
users_in_conversation.append(user)

# Fetch the tweets from participants before and after `status`
# was published
tweets_from_participants = []
for user in users_in_conversation:
user_tweets = self._get_older_and_newer_tweets(user, status.id)
tweets_from_participants.extend(user_tweets)

def belongs_to_conversation(tweet):
for user in users_in_conversation:
if user in tweet.text:
return True

return filter(belongs_to_conversation, tweets_from_participants)

def _get_older_and_newer_tweets(self, screen_name, tweet_id, count=20):
"""
Get tweets from the user with `screen_name` username that are older
and newer than `tweet_id`.

By default, 20 tweets are fetched. If provided, `count` controls how
many tweets are requested.
"""
older = self.get_user_timeline(screen_name,
max_id=tweet_id,
count=count/2)
newer = self.get_user_timeline(screen_name,
since_id=tweet_id,
count=count/2)
return older + newer

def get_message_thread(self, dm, **kwargs):
messages = self.get_direct_messages(**kwargs)

me = self.verify_credentials()
if dm.sender_screen_name == me.screen_name:
with_user = dm.recipient_screen_name
else:
with_user = dm.sender_screen_name

def belongs_to_conversation(message):
return (message.sender_screen_name == with_user or
message.recipient_screen_name == with_user)

return filter(belongs_to_conversation, messages)

@to_status
@include_entities
def search(self, text, **kwargs):
return self._api.search(text, **kwargs)

@to_status
@include_entities
def get_retweets_of_me(self, **kwargs):
return self._api.retweets_of_me(**kwargs)

def update(self, text):
self._api.update_status(text)

def reply(self, status, text):
self._api.update_status(text, in_reply_to_status_id=status.id)

def destroy_status(self, status):
self._api.destroy_status(status.id)

def retweet(self, status):
self._api.retweet(status.id)

def direct_message(self, username, text):
self._api.send_direct_message(user=username, text=text)

def destroy_direct_message(self, dm):
self._api.destroy_direct_message(dm.id)

def create_friendship(self, screen_name):
self._api.create_friendship(screen_name=screen_name)

def destroy_friendship(self, screen_name):
self._api.destroy_friendship(screen_name=screen_name)

def create_favorite(self, status):
self._api.create_favorite(status.id)

def destroy_favorite(self, status):
self._api.destroy_favorite(status.id)

# list methods

@to_list
def get_lists(self, screen_name):
return self._api.lists_all(screen_name)

@to_list
def get_own_lists(self):
return self._api.lists_all()

@to_list
def get_list_memberships(self):
return self._api.lists_memberships()

@to_list
def get_list_subscriptions(self):
return self._api.lists_subscriptions()

@to_status
def get_list_timeline(self, a_list):
owner = a_list.owner.screen_name
return self._api.list_timeline(owner=owner, slug=a_list.slug)

@to_user
def get_list_members(self, a_list):
owner = a_list.owner.screen_name
return self._api.list_members(owner=owner, slug=a_list.slug)

@to_list
def subscribe_to_list(self, a_list):
owner = a_list.owner
return self._api.subscribe_list(owner=owner.screen_name,
slug=a_list.slug)

@to_user
def get_list_subscribers(self, a_list):
owner = a_list.owner
return self._api.list_subscribers(owner=owner.screen_name,
slug=a_list.slug,)

BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/backends.pyc View File


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/backends.pyo View File


+ 469
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/base.py View File

@@ -0,0 +1,469 @@
# -*- coding: utf-8 -*-

"""
This module contains an `ApiAdapter` abstract class that acts as an adapter
for different Twitter API implementations.

It also contains `AsyncApi`, an asynchronous wrapper to `ApiAdapter` and a
function to authorize `turses` to use a Twitter account obtaining the OAuth
tokens.
"""

from ssl import SSLError
from abc import ABCMeta, abstractmethod
import oauth2 as oauth
from urlparse import parse_qsl, urljoin
from gettext import gettext as _

from turses.models import is_DM
from turses.utils import encode
from turses.meta import async, wrap_exceptions


TWITTER_CONSUMER_KEY = 'OEn4hrNGknVz9ozQytoR0A'
TWITTER_CONSUMER_SECRET = 'viud49uVgdVO9dnOGxSQJRo7jphTioIlEn3OdpkZI'

BASE_URL = 'https://api.twitter.com'

HTTP_OK = 200


def get_authorization_tokens():
"""
Authorize `turses` to use a Twitter account.

Return a dictionary with `oauth_token` and `oauth_token_secret` keys
if succesfull, `None` otherwise.
"""
# This function was borrowed from python-twitter developers and experienced
# an important refactoring
#
# Copyright 2007 The Python-Twitter Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
oauth_consumer = oauth.Consumer(key=TWITTER_CONSUMER_KEY,
secret=TWITTER_CONSUMER_SECRET)
oauth_client = oauth.Client(oauth_consumer)

print _('Requesting temporary token from Twitter')

try:
oauth_token, oauth_token_secret = get_temporary_tokens(oauth_client)
except SSLError:
print _("""There was an SSL certificate error, your user may not have
permission to access SSL. Try executing `turses` as a
privileged user.""")
return None
except Exception as e:
print e
return None


authorization_url = urljoin(BASE_URL, '/oauth/authorize')
authorization_url_with_token = urljoin(authorization_url,
'?oauth_token=%s' % oauth_token)
print
print _('Please visit the following page to retrieve the pin code needed '
'to obtain an Authorization Token:')
print
print authorization_url_with_token
print

pin_code = raw_input(_('Pin code? '))

print
print encode(_('Generating and signing request for an access token'))
print

# Generate an OAuth token that verifies the identity of the user
token = oauth.Token(oauth_token, oauth_token_secret)
token.set_verifier(pin_code)

# Re-create the OAuth client with the corresponding token
oauth_client = oauth.Client(oauth_consumer, token)

try:
access_tokens = get_access_tokens(oauth_client, pin_code)
return access_tokens
except Exception as e:
print e
return None

def get_temporary_tokens(oauth_client):
"""
Request temporary OAuth tokens using the provided `oauth_client`; these
tokens require the user to confirm its identity on Twitter's website for
obtaining an access token.

This function will return a tuple with a public and a private OAuth tokens
that can be used to retrieve an access token from Twitter if the request
was successfull.

If there is an error with the HTTP request, it will raise an
:class:`Exception` with a meaningful error message.
"""
request_token_url = urljoin(BASE_URL, '/oauth/request_token')

response, content = oauth_client.request(request_token_url, 'GET')


status_code = int(response['status'])
if status_code == HTTP_OK:
response_content = dict(parse_qsl(content))

oauth_token = response_content['oauth_token']
oauth_token_secret = response_content['oauth_token_secret']

return (oauth_token, oauth_token_secret)
else:
error_message = _('Twitter responded with an HTTP %s code.' % str(status_code))
raise Exception(error_message)

def get_access_tokens(oauth_client, pin_code):
"""
Request access tokens using the provided `oauth_client` and the
`pin_code`that verifies the user's identity.

This function will return a dictionary with `oauth_token` and
`oauth_token_secret` keys if the request was successful.

If there is an error with the HTTP request, it will raise an
:class:`Exception` with a meaningful error message.
"""
access_token_url = urljoin(BASE_URL, '/oauth/access_token')

response, content = oauth_client.request(access_token_url,
method='POST',
body='oauth_verifier=%s' % pin_code)

status_code = int(response['status'])

if status_code == HTTP_OK:
access_token = dict(parse_qsl(content))
return access_token
else:
error_message = _('Twitter responded with an HTTP %s code.' % str(status_code))
raise Exception(error_message)



class ApiAdapter(object):
"""
A simplified version of the API to use as an adapter for a real
implementation.
"""
__metaclass__ = ABCMeta

def __init__(self,
access_token_key,
access_token_secret,
consumer_key=TWITTER_CONSUMER_KEY,
consumer_secret=TWITTER_CONSUMER_SECRET,):
self._consumer_key = consumer_key
self._consumer_secret = consumer_secret
self._access_token_key = access_token_key
self._access_token_secret = access_token_secret
self.is_authenticated = False

@abstractmethod
def init_api(self):
pass

@abstractmethod
def verify_credentials(self):
"""
Return a `turses.models.User` with the authenticating user if the given
credentials are valid.
"""
pass

# users

@abstractmethod
def get_user(self, screen_name):
pass

# timelines

@abstractmethod
def get_status(self, status_id):
pass

@abstractmethod
def get_home_timeline(self):
pass

@abstractmethod
def get_user_timeline(self, screen_name):
pass

@abstractmethod
def get_own_timeline(self):
pass

@abstractmethod
def get_mentions(self):
pass

@abstractmethod
def get_favorites(self):
pass

@abstractmethod
def get_direct_messages(self):
pass

@abstractmethod
def get_thread(self, status):
pass

@abstractmethod
def get_message_thread(self, dm):
pass

@abstractmethod
def search(self, text):
pass

@abstractmethod
def get_retweets_of_me(self):
pass

# statuses

@abstractmethod
def update(self, text):
pass

@abstractmethod
def reply(self, status, text):
pass

@abstractmethod
def retweet(self, status):
pass

@abstractmethod
def destroy_status(self, status):
"""
Destroy the given `status` (must belong to authenticating user).
"""
pass

@abstractmethod
def direct_message(self, screen_name, text):
pass

@abstractmethod
def destroy_direct_message(self, dm):
"""
Destroy the given `dm` (must be written by the authenticating user).
"""
pass

# friendship

@abstractmethod
def create_friendship(self, screen_name):
pass

@abstractmethod
def destroy_friendship(self, screen_name):
pass

# favorite methods

@abstractmethod
def create_favorite(self, status):
pass

@abstractmethod
def destroy_favorite(self, status):
pass

# list methods

@abstractmethod
def get_lists(self, screen_name):
pass

@abstractmethod
def get_own_lists(self):
pass

@abstractmethod
def get_list_memberships(self):
pass

@abstractmethod
def get_list_subscriptions(self):
pass

@abstractmethod
def get_list_timeline(self, list):
pass

@abstractmethod
def get_list_members(self, list):
pass

@abstractmethod
def subscribe_to_list(self, list):
pass

@abstractmethod
def get_list_subscribers(self, list):
pass


class AsyncApi(ApiAdapter):
"""
Wrap an `ApiAdapter` subclass and execute the methods for creating,
updating and deleting Twitter entities in background. Those methods
are decorated with `turses.utils.wrap_exceptions`.
"""

def __init__(self, api_cls, *args, **kwargs):
"""
Args:
api_cls -- the class used to instantiate the Twitter API,
it must implement the methods in `ApiAdapter`.
"""
ApiAdapter.__init__(self, *args, **kwargs)
self._api = api_cls(access_token_key=self._access_token_key,
access_token_secret=self._access_token_secret,)

@wrap_exceptions
def init_api(self):
self._api.init_api()
self.is_authenticated = True
self.user = self.verify_credentials()

def verify_credentials(self):
return self._api.verify_credentials()

def get_status(self, **kwargs):
return self._api.get_status(**kwargs)

def get_home_timeline(self, **kwargs):
return self._api.get_home_timeline(**kwargs)

def get_user_timeline(self, screen_name, **kwargs):
return self._api.get_user_timeline(screen_name=screen_name, **kwargs)

def get_own_timeline(self, **kwargs):
return self._api.get_own_timeline(**kwargs)

def get_mentions(self, **kwargs):
return self._api.get_mentions()

def get_favorites(self, **kwargs):
return self._api.get_favorites()

def get_direct_messages(self, **kwargs):
return self._api.get_direct_messages(**kwargs)

def get_thread(self, status, **kwargs):
return self._api.get_thread(status, **kwargs)

def get_message_thread(self, dm, **kwargs):
return self._api.get_message_thread(dm, **kwargs)

def search(self, text, **kwargs):
return self._api.search(text, **kwargs)

def get_retweets_of_me(self, **kwargs):
return self._api.get_retweets_of_me(**kwargs)

def get_user(self, screen_name):
return self._api.get_user(screen_name)

@async
@wrap_exceptions
def update(self, text):
self._api.update(text)

@async
@wrap_exceptions
def reply(self, status, text):
self._api.reply(status, text)


@async
@wrap_exceptions
def retweet(self, status):
self._api.retweet(status)

@async
@wrap_exceptions
def destroy_status(self, status):
self._api.destroy_status(status)

@async
@wrap_exceptions
def destroy_direct_message(self, status):
self._api.destroy_direct_message(status)

@async
@wrap_exceptions
def direct_message(self, screen_name, text):
self._api.direct_message(screen_name, text)

@async
@wrap_exceptions
def create_friendship(self, screen_name):
self._api.create_friendship(screen_name)

@async
@wrap_exceptions
def destroy_friendship(self, screen_name):
self._api.destroy_friendship(screen_name)

@async
@wrap_exceptions
def create_favorite(self, status):
if is_DM(status) or status.is_favorite:
raise Exception
self._api.create_favorite(status)

@async
@wrap_exceptions
def destroy_favorite(self, status):
self._api.destroy_favorite(status)

def get_list(self, screen_name, slug):
pass

def get_lists(self, screen_name):
pass

def get_own_lists(self):
pass

def get_list_memberships(self):
pass

def get_list_subscriptions(self):
pass

def get_list_timeline(self, list):
pass

def get_list_members(self, list):
pass

def subscribe_to_list(self, list):
pass

def get_list_subscribers(self, list):
pass

BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/base.pyc View File


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/base.pyo View File


+ 199
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/debug.py View File

@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-

"""
Contains `MockApi`, a fake `turses.api.ApiAdapter` implementation for debugging
purposes.
"""

import random
from time import sleep
from datetime import datetime

from turses.models import User, Status
from turses.meta import wrap_exceptions
from turses.api.base import ApiAdapter


def random_status(quantity=1, **kwargs):
"""Return `quantity` random statuses.

By default it returns a single `turses.models.Status` instance, but
if `q` is greater than 1 it returns a list of random statuses."""
def create_status():
sleep(0.02)
now = datetime.now()
defaults = {
'id': random.randint(0, 999),
'created_at': now,
'user': 'testbot',
'text': 'Status created at %s' % now,
}
defaults.update(**kwargs)

return Status(**defaults)

if not quantity:
return

if quantity == 1:
return create_status()

return [create_status() for _ in range(0, quantity)]


def random_user(quantity=1, **kwargs):
"""Return `quantity` random users.

By default it returns a single `turses.models.user` instance, but
if `q` is greater than 1 it returns a list of random users."""
def create_user():
sleep(0.02)
now = datetime.now()
defaults = {
'id': random.randint(0, 999),
'name': 'Alejandro',
'screen_name': 'dialelo',
'description': None,
'url': 'http://dialelo.com',
'created_at': now,
'friends_count': 3,
'followers_count': 42,
'favorites_count': 0,
'status': random_status(),
}
defaults.update(**kwargs)

return User(**defaults)

if not quantity:
return

if quantity == 1:
return create_user()

return [create_user() for _ in range(0, quantity)]


class MockApi(ApiAdapter):
"""
"""
def __init__(self, *args, **kwargs):
ApiAdapter.__init__(self, *args, **kwargs)

@wrap_exceptions
def init_api(self):
self.is_authenticated = True

def verify_credentials(self):
return random_user()

# users

def get_user(self, screen_name):
return random_user(screen_name=screen_name)

# timelines

def get_status(self, status_id):
return random_status(id=status_id)

def get_home_timeline(self):
return random_status(quantity=3)

def get_user_timeline(self, screen_name):
return random_status(quantity=10)

def get_own_timeline(self):
return random_status(quantity=10)

def get_mentions(self):
return random_status(quantity=10)

def get_favorites(self):
return random_status(quantity=10)

def get_direct_messages(self):
# TODO: random DM
return random_status(quantity=10)

def get_thread(self, status):
return random_status(quantity=14)

def get_message_thread(self, status):
return random_status(quantity=4)

def search(self, text):
return random_status(quantity=14)

def get_retweets_of_me(self):
return random_status(quantity=14)

# statuses

def update(self, text):
pass

def reply(self, status, text):
pass

def retweet(self, status):
pass

def destroy_status(self, status):
"""
Destroy the given `status` (must belong to authenticating user).
"""
pass

def direct_message(self, screen_name, text):
pass

def destroy_direct_message(self, dm):
"""
Destroy the given `dm` (must be written by the authenticating user).
"""
pass

# friendship

def create_friendship(self, screen_name):
pass

def destroy_friendship(self, screen_name):
pass

# favorite methods

def create_favorite(self, status):
pass

def destroy_favorite(self, status):
pass

# list methods

def get_lists(self, screen_name):
pass

def get_own_lists(self):
pass

def get_list_memberships(self):
pass

def get_list_subscriptions(self):
pass

def get_list_timeline(self, list):
pass

def get_list_members(self, list):
pass


def subscribe_to_list(self, list):
pass

def get_list_subscribers(self, list):
pass


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/debug.pyc View File


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/debug.pyo View File


+ 149
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/helpers.py View File

@@ -0,0 +1,149 @@
"""
This module contains various methods for checking the type of timelines and a
class that creates all kinds of timelines.
"""

import re
from functools import partial
from gettext import gettext as _

from turses.models import Timeline, is_DM


HOME_TIMELINE = 'home'
MENTIONS_TIMELINE = 'mentions'
FAVORITES_TIMELINE = 'favorites'
MESSAGES_TIMELINE = 'messages'
OWN_TWEETS_TIMELINE = 'own_tweets'

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


def check_update_function_name(timeline, update_function_name=None):
if not isinstance(timeline, Timeline):
return False

update_function = timeline.update_function
if update_function is None:
return False

return update_function.__name__ == update_function_name

is_home_timeline = partial(check_update_function_name,
update_function_name='get_home_timeline')
is_mentions_timeline = partial(check_update_function_name,
update_function_name='get_mentions')
is_favorites_timeline = partial(check_update_function_name,
update_function_name='get_favorites')
is_own_timeline = partial(check_update_function_name,
update_function_name='get_own_timeline')
is_messages_timeline = partial(check_update_function_name,
update_function_name='get_direct_messages')
is_search_timeline = partial(check_update_function_name,
update_function_name='search')
is_user_timeline = partial(check_update_function_name,
update_function_name='get_user_timeline')
is_retweets_of_me_timeline = partial(check_update_function_name,
update_function_name='get_retweets_of_me')
is_thread_timeline = partial(check_update_function_name,
update_function_name='get_thread')


search_name_re = re.compile(r'^search:(?P<query>.+)$')
hashtag_name_re = re.compile(r'^hashtag:(?P<query>.+)$')
user_name_re = re.compile(r'^user:(?P<screen_name>[A-Za-z0-9_]+)$')


class TimelineFactory:
def __init__(self, api):
self.api = api

def __call__(self, timeline_string):
timeline = timeline_string.strip()

if timeline == HOME_TIMELINE:
return Timeline(name=_('tweets'),
update_function=self.api.get_home_timeline,)
elif timeline == MENTIONS_TIMELINE:
return Timeline(name=_('mentions'),
update_function=self.api.get_mentions,)
elif timeline == FAVORITES_TIMELINE:
return Timeline(name=_('favorites'),
update_function=self.api.get_favorites,)
elif timeline == MESSAGES_TIMELINE:
return Timeline(name=_('messages'),
update_function=self.api.get_direct_messages,)
elif timeline == OWN_TWEETS_TIMELINE:
return Timeline(name=_('me'),
update_function=self.api.get_own_timeline,)
elif timeline == 'retweets_of_me':
return Timeline(name=_('retweets of me'),
update_function=self.api.get_retweets_of_me,)

is_search = search_name_re.match(timeline)
if is_search:
query = is_search.groupdict()['query']
return Timeline(name=_('Search: %s' % query),
update_function=self.api.search,
update_function_args=query,)

is_hashtag = hashtag_name_re.match(timeline)
if is_hashtag:
query = "#{}".format(is_hashtag.groupdict()['query'])
return Timeline(name=_('hashtag: %s' % query),
update_function=self.api.search,
update_function_args=query,)

is_user = user_name_re.match(timeline)
if is_user:
screen_name = is_user.groupdict()['screen_name']
timeline_name = _('@{screen_name}'.format(screen_name=screen_name))
return Timeline(name=timeline_name,
update_function=self.api.get_user_timeline,
update_function_args=screen_name,)

def valid_timeline_name(self, name):
if name in DEFAULT_TIMELINES:
return True

if name == 'retweets_of_me':
return True

# search
if search_name_re.match(name):
return True

# user
if user_name_re.match(name):
return True

return False

def thread(self, status):
"""
Create a timeline with the conversation to which `status` belongs.
`status` can be a regular status or a direct message.
"""
if is_DM(status):
participants = [status.sender_screen_name,
status.recipient_screen_name]
name = _('DM thread: %s' % ', '.join(participants))
update_function = self.api.get_message_thread
else:
participants = status.mentioned_usernames
author = status.authors_username
if author not in participants:
participants.insert(0, author)

name = _('thread: %s' % ', '.join(participants))
update_function = self.api.get_thread

return Timeline(name=name,
update_function=update_function,
update_function_args=status,)

BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/helpers.pyc View File


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/api/helpers.pyo View File


+ 179
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/cli.py View File

@@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-

"""
Handle the invocation of ``turses`` from the command line.
"""

import logging
from sys import stdout
from argparse import ArgumentParser
from os import getenv
from gettext import gettext as _

from urwid import set_encoding

from turses import __name__
from turses import version as turses_version
from turses.config import configuration, LOG_FILE
from turses.models import TimelineList
from turses.ui import CursesInterface
from turses.api.base import AsyncApi
from turses.api.debug import MockApi
from turses.api.backends import TweepyApi
from turses.core import Controller as Turses


def save_stdout():
"""Save shell screen."""
stdout.write("\033[?1049h\033[H")


def restore_stdout():
"""Restore saved shell screen."""
stdout.write("\033[?1049l")


def set_title(string):
"""Set window title."""
try:
if getenv('TERM').startswith("screen"):
# terminal multiplexors
if getenv('TMUX'):
stdout.write("\033k%s\033\\" % string) # for tmux
else:
stdout.write("\033_%s\033\\" % string) # for GNU screen
else:
# terminal
stdout.write("\x1b]2;%s\x07" % string)
except:
pass


def restore_title():
"""Restore original window title."""
if getenv('TMUX'):
set_title(getenv('SHELL').split('/')[-1])


def create_async_api(api_backend_cls):
"""
Create an asynchronous API given a concrete API class ``api_backend_cls``.
"""
oauth_token = configuration.oauth_token
oauth_token_secret = configuration.oauth_token_secret

return AsyncApi(api_backend_cls,
access_token_key=oauth_token,
access_token_secret=oauth_token_secret,)


def read_arguments():
"""Read arguments from the command line."""

parser_title = "turses: Twitter client featuring a sexy curses interface."
parser = ArgumentParser(parser_title)

# load account
parser.add_argument("-a",
"--account",
help=_("Use account with the specified username."))

# load non-default configuration
parser.add_argument("-c",
"--config",
help=_("Use the specified configuration file."))

# generate configuration
generate_config_help = _("Generate a default configuration file is "
"the specified path.")
parser.add_argument("-g",
"--generate-config",
help=generate_config_help)

# load session
parser.add_argument("-s",
"--session",
help=_("Load the specified session"))

# version
version = "turses %s" % turses_version
parser.add_argument("-v",
"--version",
action="version",
version=version,
help=_("Show the current version of turses"))

# debug mode
parser.add_argument("-d",
"--debug",
action="store_true",
help=_("Start turses in debug mode."))

# offline debug mode
parser.add_argument("-o",
"--offline",
action="store_true",
help=_("Start turses in offline debug mode."))

args = parser.parse_args()
return args


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:
# 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)

BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/cli.pyc View File


BIN
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/cli.pyo View File


+ 778
- 0
turses/pkg/turses/usr/lib/python2.7/site-packages/turses/config.py View File

@@ -0,0 +1,778 @@
# -*- coding: utf-8 -*-

"""
The configuration files are located on ``$HOME/.turses`` directory.

There is one mayor configuration file in turses:

``config``
contains user preferences: colors, bindings, etc.

An one default token file:

``token``
contains authentication token for the default user account

Each user account that is no the default one needs to be aliased and has its
own token file ``alias.token``.

To create an aliased account:

.. code-block:: sh

$ turses -a work

And, after authorizing ``turses`` to use that account, a token file named
``work.token`` will be created. Optionally you can create a ``work.config``
file for a configuration specific to that account.

Now, when you execute again:

.. code-block:: sh

$ turses -a work

you will be logged in with the previously stored credentials.

Here is an example with two accounts apart from the default one, aliased
to ``alice`` and ``bob``.

.. code-block:: sh

~
|+.turses/
| |-config
| |-alice.config
| |-token
| |-alice.token
| `-bob.token
|+...
|-...
`


If you want to generate a configuration file, you can do so executing:

.. code-block:: sh

$ turses -g /path/to/file
"""

from sys import exit
from ConfigParser import RawConfigParser
from os import getenv, path, mkdir, remove
from functools import partial
from gettext import gettext as _

from turses.utils import encode
from turses.meta import wrap_exceptions
from turses.api.base import get_authorization_tokens

# -- Defaults -----------------------------------------------------------------

# Key bindings

KEY_BINDINGS = {
# motion
'up':
('k', _('scroll up')),
'down':
('j', _('scroll down')),
'left':
('h', _('activate the timeline on the left')),
'right':
('l', _('activate the timeline on the right')),
'scroll_to_top':
('g', _('scroll to top')),
'scroll_to_bottom':
('G', _('scroll to bottom')),

# buffers
'activate_first_buffer':
('a', _('activate first buffer')),
'activate_last_buffer':
('e', _('activate last buffer')),
'shift_buffer_beggining':
('ctrl a', _('shift active buffer to the beginning')),
'shift_buffer_end':
('ctrl e', _('shift active buffer to the end')),
'shift_buffer_left':
('<', _('shift active buffer one position to the left')),
'shift_buffer_right':
('>', _('shift active buffer one position to the right')),
'expand_visible_left':
('p', _('expand visible timelines one column to the left')),
'expand_visible_right':
('n', _('expand visible timelines one column to the right')),
'shrink_visible_left':
('P', _('shrink visible timelines one column from the left')),
'shrink_visible_right':
('N', _('shrink visible timelines one column from the left')),
'delete_buffer':
('d', _('delete buffer')),
'mark_all_as_read':
('A', _('mark all tweets in the current timeline as read')),

# tweets
'tweet':
('t', _('compose a tweet')),
'delete_tweet':
('X', _('delete focused status')),
'reply':
('r', _('reply to focused status')),
'retweet':
('R', _('retweet focused status')),
'retweet_and_edit':
('E', _('open a editor for manually retweeting the focused status')),
'retweet_and_fav':
('Y', _('mark focused tweet as favorite and retweet it')),
'send_dm':
('D', _('compose a direct message')),
'update':
('u', _('refresh the active timeline')),
'update_all':
('S', _('refresh all the timelines')),
'tweet_hashtag':
('H', _('compose a tweet with the same hashtags as the focused status')),
'fav':
('b', _('mark focused tweet as favorite')),
'delete_fav':
('ctrl b', _('remove tweet from favorites')),
'follow_selected':
('f', _('follow selected status\' author')),
'follow_user':
('F', _('follow user given in an editor')),
'unfollow_selected':
('U', _('unfollow selected status\' author')),
'unfollow_user':
('ctrl u', _('unfollow user given in an editor')),

# timelines
'home':
('.', _('open a home timeline')),
'own_tweets':
('_', _('open a timeline with your tweets')),
'favorites':
('B', _('open a timeline with your favorites')),
'mentions':
('m', _('open a mentions timeline')),
'DMs':
('M', _('open a direct message timeline')),
'search':
('/', _('search for term and show resulting timeline')),
'search_user':
('@', _('open a timeline with the tweets of the specified user')),
'user_timeline':
('+', _('open a timeline with the tweets of the focused status\' author')),
'thread':
('T', _('open the thread of the focused status')),
'hashtags':
('L', _('open a search timeline with the hashtags of the focused status')),
'retweets_of_me':
('I', _('open a timeline with your tweets that have been retweeted')),

# info
'user_info':
('i', _('show user\'s info')),

# meta
'help':
('?', _('show program help')),
'reload_config':
('C', _('reload configuration')),

# turses
'quit':
('q', _('exit program')),
'clear':
('c', _('clear status bar')),
'openurl':
('o', _('open URLs of the focused status in a browser')),
'open_status_url':
('O', _('open the focused status in a browser')),
'redraw':
('ctrl l', _('redraw the screen')),
}

# NOTE:
# The key binding categories are declared to order them in the configuration
# and in the help buffer. If you add a key binding, don't forget to include
# it in one of these categories.

MOTION_KEY_BINDINGS = [
'up',
'down',
'left',
'right',
'scroll_to_top',
'scroll_to_bottom',
]

BUFFERS_KEY_BINDINGS = [
'activate_first_buffer',
'activate_last_buffer',
'shift_buffer_beggining',
'shift_buffer_end',
'shift_buffer_left',
'shift_buffer_right',
'expand_visible_left',
'expand_visible_right',
'shrink_visible_left',
'shrink_visible_right',
'delete_buffer',
'mark_all_as_read',
]

TWEETS_KEY_BINDINGS = [
'tweet',
'delete_tweet',
'reply',
'retweet',
'retweet_and_edit',
'retweet_and_fav',
'send_dm',
'update',
'update_all',
'tweet_hashtag',
'fav',
'delete_fav',
'follow_selected',
'follow_user',
'unfollow_selected',
'unfollow_user',
'user_info',
]

TIMELINES_KEY_BINDINGS = [
'home',
'own_tweets',
'favorites',
'mentions',
'DMs',
'search',
'search_user',
'user_timeline',
'thread',
'hashtags',
'retweets_of_me',
]

META_KEY_BINDINGS = [
'help',
'reload_config',
]

TURSES_KEY_BINDINGS = [
'clear',
'quit',
'openurl',
'open_status_url',
'redraw',
]

# Palette

# TODO: not hard coded
# valid colors for `urwid`s palette
VALID_COLORS = [
'default',
'black',
'dark red',
'dark green',
'brown',
'dark blue',
'dark magenta',
'dark cyan',
'light gray',
'dark gray',
'light red',
'light green',
'yellow',
'light blue',
'light magenta',
'light cyan',
'white',
]


def validate_color(colorstring):
return colorstring if colorstring in VALID_COLORS else ''

PALETTE = [
#Tabs
['active_tab', 'white', 'dark blue'],
['visible_tab', 'yellow', 'dark blue'],
['inactive_tab', 'dark blue', ''],

# Statuses
['header', 'light blue', ''],
['body', 'white', ''],
['focus', 'light red', ''],
['line', 'black', ''],
['unread', 'dark red', ''],
['read', 'dark blue', ''],
['favorited', 'yellow', ''],

# Text
['highlight', 'dark red', ''],
['highlight_nick', 'light red', ''],
['attag', 'yellow', ''],
['hashtag', 'light red', ''],
['url', 'white', 'dark red'],

# Messages
['error', 'white', 'dark red'],
['info', 'white', 'dark blue'],

# Editor
['editor', 'white', 'dark blue'],
]

# Styles

STYLES = {
# TODO: make time string configurable
'reply_indicator': '@',
'retweet_indicator': 'RT',
'header_template': ' {username}{retweeted}{retweeter} - {time}{reply}{retweet_count} ',
'dm_template': ' {sender_screen_name} => {recipient_screen_name} - {time} ',
'tab_template': '{timeline_name} [{unread}]',
'box_around_status': True,
'status_divider': False,
'status_bar': True,
'status_divider_char': '─',
'editor_horizontal_align': 'center',
'editor_vertical_align': 'bottom',
'url_format': 'display',
'statuses_in_user_info': 3,
}

# Debug

LOGGING_LEVEL = 3

# Twitter
UPDATE_FREQUENCY = 300
USE_HTTPS = True

TWITTER = {
'update_frequency': UPDATE_FREQUENCY,
'use_https': USE_HTTPS,
}

# Environment

HOME = getenv('HOME')

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

DEFAULT_SESSION = 'defaults'

# Default config path
CONFIG_DIR = '.turses'
CONFIG_PATH = path.join(HOME, CONFIG_DIR)
DEFAULT_CONFIG_FILE = path.join(CONFIG_PATH, 'config')
DEFAULT_TOKEN_FILE = path.join(CONFIG_PATH, 'token')
LOG_FILE = path.join(CONFIG_PATH, 'log')

LEGACY_CONFIG_DIR = '.config/turses'
LEGACY_CONFIG_PATH = path.join(HOME, LEGACY_CONFIG_DIR)
LEGACY_CONFIG_FILE = path.join(LEGACY_CONFIG_PATH, 'turses.cfg')
LEGACY_TOKEN_FILE = path.join(LEGACY_CONFIG_PATH, 'turses.tok')

# Names of the sections in the configuration
SECTION_DEFAULT_TIMELINES = 'timelines'
SECTION_KEY_BINDINGS = 'bindings'
SECTION_PALETTE = 'colors'
SECTION_STYLES = 'styles'
SECTION_DEBUG = 'debug'
SECTION_TWITTER = 'twitter'

# Names of the sections in the token file
SECTION_TOKEN = 'token'


def print_deprecation_notice():
print "NOTE:"
print
print "The configuration file in %s has been deprecated." % LEGACY_CONFIG_FILE
print "A new configuration directory is being generated in %s." % CONFIG_PATH
print


def invert_command_map(bindings):
"""
Invert configuration keybindings to make reverse lookups faster
"""
command_map = {}
for command, (key, _) in bindings.iteritems():
command_map[key] = command
return command_map


class Configuration(object):
"""
Generate and parse configuration files. When instantiated, it loads the
defaults.

Calling :func:`Configuration.parse_args` with an
:class:`argparse.ArgumentParser` instance will modify the instance to match
the options provided by the command line arguments.

Calling :func:`turses.config.Configuration.load` on this class' instances
reads the preferences from the user configuration files. If no
configuration or token files are found, this class will take care of
creating them.

Offers backwards compatibility with the Tyrs configuration.
"""

def __init__(self):
"""
Create a `Configuration` taking into account the arguments
from the command line interface (if any).
"""
# load defaults
self.twitter = TWITTER
self.key_bindings = KEY_BINDINGS
self.key_mappings = invert_command_map(self.key_bindings)
self.palette = PALETTE
self.styles = STYLES
self.logging_level = LOGGING_LEVEL
self.session = DEFAULT_SESSION

# config and token files
self.config_file = DEFAULT_CONFIG_FILE
self.token_file = DEFAULT_TOKEN_FILE

# debug mode
self.debug = False

# create the config directory if it does not exist
if not path.isdir(CONFIG_PATH):
try:
mkdir(CONFIG_PATH)
except:
print encode(_('Error creating config directory in %s' % CONFIG_DIR))
self.exit_with_code(3)

def parse_args(self, cli_args):
"""Interprets the arguments provided by `cli_args`."""
if cli_args is None:
return

if cli_args.generate_config:
self.generate_config_file(config_file=cli_args.generate_config,)
self.exit_with_code(0)

# path to configuration file
if cli_args.config:
self.config_file = cli_args.config
elif cli_args.account:
self.config_file = path.join(CONFIG_PATH, '%s.config' % cli_args.account)

# path to token file
if cli_args.account:
self.token_file = path.join(CONFIG_PATH, '%s.token' % cli_args.account)

# session
if cli_args.session:
self.session = cli_args.session

# debug mode
self.debug = getattr(cli_args, 'debug', False)

def load(self):
"""
Loads configuration from files.
"""
self._init_config()
self._init_token()

def _init_config(self):
if path.isfile(LEGACY_CONFIG_FILE):
self._parse_legacy_config_file()
print_deprecation_notice()
remove(LEGACY_CONFIG_FILE)
elif path.isfile(self.config_file):
self.parse_config_file(self.config_file)
else:
self.generate_config_file(self.config_file)
self.key_mappings = invert_command_map(self.key_bindings)

def _add_section_twitter(self, conf):
# Twitter
if not conf.has_section(SECTION_TWITTER):
conf.add_section(SECTION_TWITTER)
if not conf.has_option(SECTION_TWITTER, 'update_frequency'):
conf.set(SECTION_TWITTER, 'update_frequency', UPDATE_FREQUENCY)
if not conf.has_option(SECTION_TWITTER, 'use_https'):
conf.set(SECTION_TWITTER, 'use_https', USE_HTTPS)

def _add_section_key_bindings(self, conf):
# Key bindings
if not conf.has_section(SECTION_KEY_BINDINGS):
conf.add_section(SECTION_KEY_BINDINGS)
binding_lists = [MOTION_KEY_BINDINGS,
BUFFERS_KEY_BINDINGS,
TWEETS_KEY_BINDINGS,
TIMELINES_KEY_BINDINGS,
META_KEY_BINDINGS,
TURSES_KEY_BINDINGS, ]
for binding_list in binding_lists:
for binding in binding_list:
key = self.key_bindings[binding][0]
if conf.has_option(SECTION_KEY_BINDINGS, binding):
continue
conf.set(SECTION_KEY_BINDINGS, binding, key)

def _add_section_palette(self, conf):
# Color
if not conf.has_section(SECTION_PALETTE):
conf.add_section(SECTION_PALETTE)
for label in PALETTE:
label_name, fg, bg = label[0], label[1], label[2]

# fg
if conf.has_option(SECTION_PALETTE, label_name) and \
validate_color(conf.get(SECTION_PALETTE, label_name)):
pass
else:
conf.set(SECTION_PALETTE, label_name, fg)

#bg
label_name_bg = label_name + '_bg'
if conf.has_option(SECTION_PALETTE, label_name_bg) and \
validate_color(conf.get(SECTION_PALETTE, label_name_bg)):
pass
else:
conf.set(SECTION_PALETTE, label_name_bg, bg)

def _add_section_styles(self, conf):
# Styles
if not conf.has_section(SECTION_STYLES):
conf.add_section(SECTION_STYLES)
for style in STYLES:
if conf.has_option(SECTION_STYLES, style):
continue
conf.set(SECTION_STYLES, style, self.styles[style])

def _add_section_debug(self, conf):
# Debug
if not conf.has_section(SECTION_DEBUG):
conf.add_section(SECTION_DEBUG)
if conf.has_option(SECTION_DEBUG, 'logging_level'):
return
conf.set(SECTION_DEBUG, 'logging_level', LOGGING_LEVEL)

def _init_token(self):
if path.isfile(LEGACY_TOKEN_FILE):
self.parse_token_file(LEGACY_TOKEN_FILE)
remove(LEGACY_TOKEN_FILE)
if (hasattr(self, 'oauth_token') and
hasattr(self, 'oauth_token_secret')):
self.generate_token_file(self.token_file,
self.oauth_token,
self.oauth_token_secret)
elif not path.isfile(self.token_file):
self.authorize_new_account()
else:
self.parse_token_file(self.token_file)

def _parse_legacy_config_file(self):
"""