X-Git-Url: https://jfr.im/git/z_archive/twitter.git/blobdiff_plain/b15b92f0ab9b80bab945439062e4df2894a7eba6..a8b5ad3eb3bcfeeda2669d5ed4e9f99231724ea1:/twitter/cmdline.py diff --git a/twitter/cmdline.py b/twitter/cmdline.py index 6ffc5ef..ce25923 100644 --- a/twitter/cmdline.py +++ b/twitter/cmdline.py @@ -10,6 +10,7 @@ ACTIONS: leave remove the specified user from your following list public get latest public tweets replies get latest replies + search search twitter (Beware: octothorpe, escape it) set set your twitter status shell login the twitter shell @@ -54,6 +55,8 @@ from getpass import getpass import re import os.path from ConfigParser import SafeConfigParser +import datetime +from urllib import quote from api import Twitter, TwitterError import ansi @@ -108,11 +111,17 @@ def parse_args(args, options): if extra_args and not ('action' in options and options['action'] == 'help'): options['action'] = extra_args[0] options['extra_args'] = extra_args[1:] - -def get_time_string(status, options): + +def get_time_string(status, options, format="%a %b %d %H:%M:%S +0000 %Y"): timestamp = options["timestamp"] datestamp = options["datestamp"] - t = time.strptime(status['created_at'], "%a %b %d %H:%M:%S +0000 %Y") + t = time.strptime(status['created_at'], format) + i_hate_timezones = time.timezone + if (time.daylight): + i_hate_timezones = time.altzone + dt = datetime.datetime(*t[:-3]) - datetime.timedelta( + seconds=i_hate_timezones) + t = dt.timetuple() if timestamp and datestamp: return time.strftime("%Y-%m-%d %H:%M:%S ", t) elif timestamp: @@ -122,15 +131,15 @@ def get_time_string(status, options): return "" class StatusFormatter(object): - def __call__(self, status): - return (u"%S%s %s" %( + def __call__(self, status, options): + return (u"%s%s %s" %( get_time_string(status, options), status['user']['screen_name'], status['text'])) class AnsiStatusFormatter(object): def __init__(self): self._colourMap = ansi.ColourMap() - + def __call__(self, status, options): colour = self._colourMap.colourFor(status['user']['screen_name']) return (u"%s%s%s%s %s" %( @@ -168,12 +177,40 @@ class VerboseAdminFormatter(object): user['name'], user['url'])) +class SearchFormatter(object): + def __call__(self, result, options): + return(u"%s%s %s" %( + get_time_string(result, options, "%a, %d %b %Y %H:%M:%S +0000"), + result['from_user'], result['text'])) + +class VerboseSearchFormatter(SearchFormatter): + pass #Default to the regular one + +class URLSearchFormatter(object): + urlmatch = re.compile(r'https?://\S+') + def __call__(self, result, options): + urls = self.urlmatch.findall(result['text']) + return u'\n'.join(urls) if urls else "" + +class AnsiSearchFormatter(object): + def __init__(self): + self._colourMap = ansi.ColourMap() + + def __call__(self, result, options): + colour = self._colourMap.colourFor(result['from_user']) + return (u"%s%s%s%s %s" %( + get_time_string(result, options, "%a, %d %b %Y %H:%M:%S +0000"), + ansi.cmdColour(colour), result['from_user'], + ansi.cmdReset(), result['text'])) + +formatters = {} status_formatters = { 'default': StatusFormatter, 'verbose': VerboseStatusFormatter, 'urls': URLStatusFormatter, 'ansi': AnsiStatusFormatter } +formatters['status'] = status_formatters admin_formatters = { 'default': AdminFormatter, @@ -181,51 +218,58 @@ admin_formatters = { 'urls': AdminFormatter, 'ansi': AdminFormatter } +formatters['admin'] = admin_formatters -def get_status_formatter(options): - sf = status_formatters.get(options['format']) - if (not sf): - raise TwitterError( - "Unknown formatter '%s'" %(options['format'])) - return sf() +search_formatters = { + 'default': SearchFormatter, + 'verbose': VerboseSearchFormatter, + 'urls': URLSearchFormatter, + 'ansi': AnsiSearchFormatter +} +formatters['search'] = search_formatters -def get_admin_formatter(options): - sf = admin_formatters.get(options['format']) - if (not sf): +def get_formatter(action_type, options): + formatters_dict = formatters.get(action_type) + if (not formatters_dict): + raise TwitterError( + "There was an error finding a class of formatters for your type (%s)" + %(action_type)) + f = formatters_dict.get(options['format']) + if (not f): raise TwitterError( - "Unknown formatter '%s'" %(options['format'])) - return sf() + "Unknown formatter '%s' for status actions" %(options['format'])) + return f() class Action(object): - @staticmethod - def ask(subject='perform this action', careful=False): + + def ask(self, subject='perform this action', careful=False): ''' Requests fromt he user using `raw_input` if `subject` should be performed. When `careful`, the default answer is NO, otherwise YES. Returns the user answer in the form `True` or `False`. ''' - sample = '(y/N)' if careful else '(Y/n)' + sample = '(y/N)' + if not careful: + sample = '(Y/n)' + prompt = 'You really want to %s %s? ' %(subject, sample) try: answer = raw_input(prompt).lower() if careful: - if answer not in ('yes', 'y'): - return False - else: - return True + return answer in ('yes', 'y') else: - if answer in ('no', 'n'): - return False - else: - return True + return answer not in ('no', 'n') except EOFError: print >>sys.stderr # Put Newline since Enter was never pressed # TODO: # Figure out why on OS X the raw_input keeps raising # EOFError and is never able to reset and get more input # Hint: Look at how IPython implements their console - default = False if careful else True + default = True + if careful: + default = False return default + def __call__(self, twitter, options): action = actions.get(options['action'], NoSuchAction)() try: @@ -252,29 +296,46 @@ def printNicely(string): print string.encode(sys.stdout.encoding, 'replace') else: print string.encode('utf-8') - + class StatusAction(Action): def __call__(self, twitter, options): statuses = self.getStatuses(twitter, options) - sf = get_status_formatter(options) + sf = get_formatter('status', options) for status in statuses: statusStr = sf(status, options) if statusStr.strip(): printNicely(statusStr) +class SearchAction(Action): + def __call__(self, twitter, options): + # We need to be pointing at search.twitter.com to work, and it is less + # tangly to do it here than in the main() + twitter.domain="search.twitter.com" + # We need to bypass the TwitterCall parameter encoding, so we + # don't encode the plus sign, so we have to encode it ourselves + query_string = "+".join([quote(term) for term in options['extra_args']]) + twitter.encoded_args = "q=%s" %(query_string) + + results = twitter.search()['results'] + f = get_formatter('search', options) + for result in results: + resultStr = f(result, options) + if resultStr.strip(): + printNicely(resultStr) + class AdminAction(Action): def __call__(self, twitter, options): - if not options['extra_args'][0]: + if not (options['extra_args'] and options['extra_args'][0]): raise TwitterError("You need to specify a user (screen name)") - af = get_admin_formatter(options) + af = get_formatter('admin', options) try: user = self.getUser(twitter, options['extra_args'][0]) except TwitterError, e: print "There was a problem following or leaving the specified user." - print " You may be trying to follow a user you are already following;" - print " Leaving a user you are not currently following;" - print " Or the user may not exist." - print " Sorry." + print "You may be trying to follow a user you are already following;" + print "Leaving a user you are not currently following;" + print "Or the user may not exist." + print "Sorry." print print e else: @@ -309,19 +370,21 @@ class SetStatusAction(Action): twitter.statuses.update(status=status) class TwitterShell(Action): - @staticmethod - def render_prompt(prompt): + + def render_prompt(self, prompt): '''Parses the `prompt` string and returns the rendered version''' prompt = prompt.strip("'").replace("\\'","'") for colour in ansi.COLOURS_NAMED: if '[%s]' %(colour) in prompt: prompt = prompt.replace( - '[%s]' %(colour), ansi.cmdColourNamed(colour)) + '[%s]' %(colour), ansi.cmdColourNamed(colour)) prompt = prompt.replace('[R]', ansi.cmdReset()) return prompt + def __call__(self, twitter, options): prompt = self.render_prompt(options.get('prompt', 'twitter> ')) while True: + options['action'] = "" try: args = raw_input(prompt).split() parse_args(args, options) @@ -334,11 +397,11 @@ class TwitterShell(Action): continue elif options['action'] == 'help': print >>sys.stderr, '''\ntwitter> `action`\n - The Shell Accepts all the command line actions along with: + The Shell Accepts all the command line actions along with: - exit Leave the twitter shell (^D may also be used) + exit Leave the twitter shell (^D may also be used) - Full CMD Line help is appended below for your convinience.''' + Full CMD Line help is appended below for your convinience.''' Action()(twitter, options) options['action'] = '' except NoSuchActionError, e: @@ -364,6 +427,7 @@ actions = { 'leave' : LeaveAction, 'public' : PublicAction, 'replies' : RepliesAction, + 'search' : SearchAction, 'set' : SetStatusAction, 'shell' : TwitterShell, }