]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/cmdline.py
Apply patch by Jordan Gottlieb to handle URLs with ids in the middle.
[z_archive/twitter.git] / twitter / cmdline.py
index c70e7054cf69b88bfedac4cb19d138ffe65c5a4c..ec07cd44b092bbad0c05119eacd6a0e8f5449d18 100644 (file)
@@ -11,6 +11,7 @@ ACTIONS:
  public         get latest public tweets
  replies        get latest replies
  set            set your twitter status
  public         get latest public tweets
  replies        get latest replies
  set            set your twitter status
+ shell          login the twitter shell
 
 OPTIONS:
 
 
 OPTIONS:
 
@@ -33,7 +34,7 @@ FORMATS for the --format option
  verbose         multiple lines per status, more verbose status info
  urls            nothing but URLs
  ansi            ansi colour (rainbow mode)
  verbose         multiple lines per status, more verbose status info
  urls            nothing but URLs
  ansi            ansi colour (rainbow mode)
+
 CONFIG FILES
 
  The config file should contain a [twitter] header, and all the desired options
 CONFIG FILES
 
  The config file should contain a [twitter] header, and all the desired options
@@ -43,15 +44,17 @@ CONFIG FILES
 email: <username>
 password: <password>
 format: <desired_default_format_for_output>
 email: <username>
 password: <password>
 format: <desired_default_format_for_output>
+prompt: <twitter_shell_prompt e.g. '[cyan]twitter[R]> '>
 """
 
 import sys
 import time
 """
 
 import sys
 import time
-from getopt import getopt, GetoptError
+from getopt import gnu_getopt as getopt, GetoptError
 from getpass import getpass
 import re
 import os.path
 from ConfigParser import SafeConfigParser
 from getpass import getpass
 import re
 import os.path
 from ConfigParser import SafeConfigParser
+import datetime
 
 from api import Twitter, TwitterError
 import ansi
 
 from api import Twitter, TwitterError
 import ansi
@@ -67,6 +70,7 @@ OPTIONS = {
     'refresh': False,
     'refresh_rate': 600,
     'format': 'default',
     'refresh': False,
     'refresh_rate': 600,
     'format': 'default',
+    'prompt': '[cyan]twitter[R]> ',
     'config_filename': os.environ.get('HOME', '') + os.sep + '.twitter',
     'length': 20,
     'timestamp': False,
     'config_filename': os.environ.get('HOME', '') + os.sep + '.twitter',
     'length': 20,
     'timestamp': False,
@@ -98,12 +102,11 @@ def parse_args(args, options):
         elif opt in ('-d', '--datestamp'):
             options["datestamp"] = True
         elif opt in ('-?', '-h', '--help'):
         elif opt in ('-d', '--datestamp'):
             options["datestamp"] = True
         elif opt in ('-?', '-h', '--help'):
-            print __doc__
-            sys.exit(0)
+            options['action'] = 'help'
         elif opt in ('-c', '--config'):
             options['config_filename'] = arg
 
         elif opt in ('-c', '--config'):
             options['config_filename'] = arg
 
-    if extra_args:
+    if extra_args and not ('action' in options and options['action'] == 'help'):
         options['action'] = extra_args[0]
     options['extra_args'] = extra_args[1:]
     
         options['action'] = extra_args[0]
     options['extra_args'] = extra_args[1:]
     
@@ -111,6 +114,12 @@ def get_time_string(status, options):
     timestamp = options["timestamp"]
     datestamp = options["datestamp"]
     t = time.strptime(status['created_at'], "%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")
+    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:
     if timestamp and datestamp:
         return time.strftime("%Y-%m-%d %H:%M:%S ", t)
     elif timestamp:
@@ -120,8 +129,8 @@ def get_time_string(status, options):
     return ""                             
 
 class StatusFormatter(object):
     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']))
 
             get_time_string(status, options),
             status['user']['screen_name'], status['text']))
 
@@ -134,8 +143,8 @@ class AnsiStatusFormatter(object):
         return (u"%s%s%s%s %s" %(
             get_time_string(status, options),
             ansi.cmdColour(colour), status['user']['screen_name'],
         return (u"%s%s%s%s %s" %(
             get_time_string(status, options),
             ansi.cmdColour(colour), status['user']['screen_name'],
-            ansi.cmdReset(), status['text']))    
-    
+            ansi.cmdReset(), status['text']))
+
 class VerboseStatusFormatter(object):
     def __call__(self, status, options):
         return (u"-- %s (%s) on %s\n%s\n" %(
 class VerboseStatusFormatter(object):
     def __call__(self, status, options):
         return (u"-- %s (%s) on %s\n%s\n" %(
@@ -161,8 +170,8 @@ class AdminFormatter(object):
 class VerboseAdminFormatter(object):
     def __call__(self, action, user):
         return(u"-- %s: %s (%s): %s" % (
 class VerboseAdminFormatter(object):
     def __call__(self, action, user):
         return(u"-- %s: %s (%s): %s" % (
-            "Following" if action == "follow" else "Leaving", 
-            user['screen_name'], 
+            "Following" if action == "follow" else "Leaving",
+            user['screen_name'],
             user['name'],
             user['url']))
 
             user['name'],
             user['url']))
 
@@ -171,7 +180,7 @@ status_formatters = {
     'verbose': VerboseStatusFormatter,
     'urls': URLStatusFormatter,
     'ansi': AnsiStatusFormatter
     'verbose': VerboseStatusFormatter,
     'urls': URLStatusFormatter,
     'ansi': AnsiStatusFormatter
-}    
+}
 
 admin_formatters = {
     'default': AdminFormatter,
 
 admin_formatters = {
     'default': AdminFormatter,
@@ -195,12 +204,55 @@ def get_admin_formatter(options):
     return sf()
 
 class Action(object):
     return sf()
 
 class Action(object):
+
+    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 not careful:
+            sample = '(Y/n)'
+        
+        prompt = 'You really want to %s %s? ' %(subject, sample)
+        try:
+            answer = raw_input(prompt).lower()
+            if careful:
+                return answer in ('yes', 'y')
+            else:
+                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 = True
+            if careful:
+                default = False
+            return default
+        
+    def __call__(self, twitter, options):
+        action = actions.get(options['action'], NoSuchAction)()
+        try:
+            doAction = lambda : action(twitter, options)
+            if (options['refresh'] and isinstance(action, StatusAction)):
+                while True:
+                    doAction()
+                    time.sleep(options['refresh_rate'])
+            else:
+                doAction()
+        except KeyboardInterrupt:
+            print >>sys.stderr, '\n[Keyboard Interrupt]'
+            pass
+
+class NoSuchActionError(Exception):
     pass
 
 class NoSuchAction(Action):
     def __call__(self, twitter, options):
     pass
 
 class NoSuchAction(Action):
     def __call__(self, twitter, options):
-        print >> sys.stderr, "No such action: ", options['action']
-        sys.exit(1)
+        raise NoSuchActionError("No such action: %s" %(options['action']))
 
 def printNicely(string):        
     if sys.stdout.encoding:
 
 def printNicely(string):        
     if sys.stdout.encoding:
@@ -219,17 +271,17 @@ class StatusAction(Action):
 
 class AdminAction(Action):
     def __call__(self, twitter, options):
 
 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)
         try:
             user = self.getUser(twitter, options['extra_args'][0])
         except TwitterError, e:
             print "There was a problem following or leaving the specified user."
             raise TwitterError("You need to specify a user (screen name)")
         af = get_admin_formatter(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:
             print
             print e
         else:
@@ -257,24 +309,72 @@ class LeaveAction(AdminAction):
 
 class SetStatusAction(Action):
     def __call__(self, twitter, options):
 
 class SetStatusAction(Action):
     def __call__(self, twitter, options):
-        statusTxt = (u" ".join(options['extra_args']) 
-                     if options['extra_args'] 
+        statusTxt = (u" ".join(options['extra_args'])
+                     if options['extra_args']
                      else unicode(raw_input("message: ")))
         status = (statusTxt.encode('utf8', 'replace'))
         twitter.statuses.update(status=status)
 
                      else unicode(raw_input("message: ")))
         status = (statusTxt.encode('utf8', 'replace'))
         twitter.statuses.update(status=status)
 
+class TwitterShell(Action):
+
+    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))
+        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)
+                if not options['action']:
+                    continue
+                elif options['action'] == 'exit':
+                    raise SystemExit(0)
+                elif options['action'] == 'shell':
+                    print >>sys.stderr, 'Sorry Xzibit does not work here!'
+                    continue
+                elif options['action'] == 'help':
+                    print >>sys.stderr, '''\ntwitter> `action`\n
+        The Shell Accepts all the command line actions along with:
+
+            exit    Leave the twitter shell (^D may also be used)
+
+        Full CMD Line help is appended below for your convinience.'''
+                Action()(twitter, options)
+                options['action'] = ''
+            except NoSuchActionError, e:
+                print >>sys.stderr, e
+            except KeyboardInterrupt:
+                print >>sys.stderr, '\n[Keyboard Interrupt]'
+            except EOFError:
+                print >>sys.stderr
+                leaving = self.ask(subject='Leave')
+                if not leaving:
+                    print >>sys.stderr, 'Excellent!'
+                else:
+                    raise SystemExit(0)
+
 class HelpAction(Action):
     def __call__(self, twitter, options):
         print __doc__
 
 actions = {
 class HelpAction(Action):
     def __call__(self, twitter, options):
         print __doc__
 
 actions = {
-    'follow': FollowAction,
-    'friends': FriendsAction,
-    'help': HelpAction,
-    'leave': LeaveAction,
-    'public': PublicAction,
-    'replies': RepliesAction,
-    'set': SetStatusAction,
+    'follow'    : FollowAction,
+    'friends'   : FriendsAction,
+    'help'      : HelpAction,
+    'leave'     : LeaveAction,
+    'public'    : PublicAction,
+    'replies'   : RepliesAction,
+    'set'       : SetStatusAction,
+    'shell'     : TwitterShell,
 }
 
 def loadConfig(filename):
 }
 
 def loadConfig(filename):
@@ -282,7 +382,7 @@ def loadConfig(filename):
     if os.path.exists(filename):
         cp = SafeConfigParser()
         cp.read([filename])
     if os.path.exists(filename):
         cp = SafeConfigParser()
         cp.read([filename])
-        for option in ('email', 'password', 'format'):
+        for option in ('email', 'password', 'format', 'prompt'):
             if cp.has_option('twitter', option):
                 options[option] = cp.get('twitter', option)
     return options
             if cp.has_option('twitter', option):
                 options[option] = cp.get('twitter', option)
     return options
@@ -294,7 +394,7 @@ def main(args=sys.argv[1:]):
     except GetoptError, e:
         print >> sys.stderr, "I can't do that, %s." %(e)
         print >> sys.stderr
     except GetoptError, e:
         print >> sys.stderr, "I can't do that, %s." %(e)
         print >> sys.stderr
-        sys.exit(1)
+        raise SystemExit(1)
 
     config_options = loadConfig(
         arg_options.get('config_filename') or OPTIONS.get('config_filename'))
 
     config_options = loadConfig(
         arg_options.get('config_filename') or OPTIONS.get('config_filename'))
@@ -306,32 +406,23 @@ def main(args=sys.argv[1:]):
     for d in config_options, arg_options:
         for k,v in d.items():
             if v: options[k] = v
     for d in config_options, arg_options:
         for k,v in d.items():
             if v: options[k] = v
-    
+
     if options['refresh'] and options['action'] not in (
         'friends', 'public', 'replies'):
         print >> sys.stderr, "You can only refresh the friends, public, or replies actions."
         print >> sys.stderr, "Use 'twitter -h' for help."
     if options['refresh'] and options['action'] not in (
         'friends', 'public', 'replies'):
         print >> sys.stderr, "You can only refresh the friends, public, or replies actions."
         print >> sys.stderr, "Use 'twitter -h' for help."
-        sys.exit(1)
-        
+        raise SystemExit(1)
+
     if options['email'] and not options['password']:
         options['password'] = getpass("Twitter password: ")
     if options['email'] and not options['password']:
         options['password'] = getpass("Twitter password: ")
-        
+
     twitter = Twitter(options['email'], options['password'], agent=AGENT_STR)
     twitter = Twitter(options['email'], options['password'], agent=AGENT_STR)
-    action = actions.get(options['action'], NoSuchAction)()
-    
     try:
     try:
-        doAction = lambda : action(twitter, options)
-
-        if (options['refresh'] and isinstance(action, StatusAction)):
-            while True:
-                doAction()
-                time.sleep(options['refresh_rate'])
-        else:
-            doAction()
-
+        Action()(twitter, options)
+    except NoSuchActionError, e:
+        print >>sys.stderr, e
+        raise SystemExit(1)
     except TwitterError, e:
         print >> sys.stderr, e.args[0]
         print >> sys.stderr, "Use 'twitter -h' for help."
     except TwitterError, e:
         print >> sys.stderr, e.args[0]
         print >> sys.stderr, "Use 'twitter -h' for help."
-        sys.exit(1)
-    except KeyboardInterrupt:
-        pass
+        raise SystemExit(1)