Fixes for Python 2.6
[animesorter.git] / animesorter2.py
index 9585394..e85a2b3 100755 (executable)
@@ -36,83 +36,95 @@ import sys
 import errno
 import shutil
 import logging
-from optparse import OptionParser
+import optparse
+
+PROGRAM_NAME = 'animesorter2'
+PROGRAM_VERSION = '2.1.0'
 
 ### Default Configuration Variables ###
 DICT_FILE = os.path.join ('~','.config','animesorter2','animesorter.dict')
 WORK_DIR =  os.path.join ('~','downloads','usenet')
 SORT_DIR =  os.path.join ('/','data','Anime')
-TYPES_REGEX = '.*(avi|ogm|mkv|mp4|\d\d\d)$'
-
 
 class AnimeSorter2:
 
     def __init__(self, options):
         self.options = options
+        self.dict = None
+
+    def __valid_dict_line (self, line):
+        if len(line) <= 0:
+            return False
+
+        if '=' not in line:
+            return False
+
+        # Comment lines are not really valid
+        if re.match ('^(\s*#.*|\s*)$', line):
+            return False
+
+        # Make sure that there is a definition and it is valid
+        try:
+            (regex, directory) = line.split('=')
+            regex = regex.strip()
+            directory = directory.strip()
+        except:
+            return False
+
+        # Make sure they have length
+        if len(regex) <= 0 or len(directory) <= 0:
+            return False
+
+        # I guess that it's valid now
+        return True
 
     def parse_dict(self):
         """Parses a dictionary file containing the sort definitions in the form:
-        DIRECTORY = PATTERN
+        REGEX_PATTERN = DIRECTORY
 
         Returns a list of tuples of the form (compiled_regex, to_directory)"""
 
         try:
             f = open(self.options.dict_file, 'r', 0)
             try:
-                data = f.read()
+                raw_lines = f.readlines()
             finally:
                 f.close()
         except IOError:
             logging.critical ('Opening dictionary: %s FAILED' % self.options.dict_file)
-            sys.exit()
-
-        ### Get a LIST containing each line in the file
-        lines = [l for l in data.split('\n') if len(l) > 0]
-
-        ### Remove comments / blank lines (zero length lines already removed above)
-        regex = re.compile ('^\s*#.*$')
-        lines = [l for l in lines if not re.match (regex, l)]
-        regex = re.compile ('^\s*$')
-        lines = [l for l in lines if not re.match (regex, l)]
-
-        ### Split each line into a tuple, and strip each element of spaces
-        result = self.split_lines(lines)
-        result = [(re.compile(r), d) for r, d in result]
-
-        ### Give some information about the dictionary we are using
-        logging.info ('Successfully loaded %d records from %s\n' % \
-                (len(result), self.options.dict_file))
+            sys.exit(1)
 
-        return tuple(result)
-
-    def split_lines(self, lines):
+        ### Find all of the valid lines in the file
+        valid_lines = [l for l in raw_lines if self.__valid_dict_line (l)]
 
+        # Set up variable for result
         result = []
 
-        for l in lines:
-
-            try:
-                r, d = l.split('=')
-                r = r.strip()
-                d = d.strip()
-            except ValueError:
-                logging.warning ('Bad line in dictionary: "%s"' % l)
-                continue
+        ### Split each line into a tuple, and strip each element of spaces
+        for l in valid_lines:
+            (regex, directory) = l.split('=')
+            regex = regex.strip()
+            directory = directory.strip()
 
-            result.append((r, d))
+            # Fix up the directory if necessary
+            if directory[0] != '/':
+                directory = os.path.join (self.options.output_dir, directory)
 
-        return result
+            # Fix up the regex
+            if regex[0] != '^':
+                regex = '^' + regex
 
-    def get_matches(self, files, pattern):
-        """get_matches(files, pattern):
+            if regex[-1] != '$':
+                regex += '$'
 
-        files is type LIST
-        pattern is type sre.SRE_Pattern
+            # Store the result
+            result.append ( (re.compile (regex), directory) )
 
-        Returns a list of the files matching the pattern as type sre.SRE_Match."""
+        ### Give some information about the dictionary we are using
+        logging.info ('Successfully loaded %d records from %s\n' % \
+                (len(result), self.options.dict_file))
 
-        matches = [m for m in files if pattern.search(m)]
-        return matches
+        return tuple (result)
 
     def as_makedirs (self, dirname):
         """Call os.makedirs(dirname), but check first whether we are in pretend
@@ -162,9 +174,9 @@ class AnimeSorter2:
 
         ret = 0
 
-        ## Check for a non-default directory
-        if todir[0] != '/':
-            todir = os.path.join(self.options.output_dir, todir)
+        # Leave immediately if we have nothing to do
+        if len(files) <= 0:
+            return ret
 
         ## Create the directory if it doesn't exist
         ret = self.as_makedirs (todir)
@@ -183,19 +195,11 @@ class AnimeSorter2:
 
         return ret
 
-    def __dir_walker(self, dict, root, dirs, files):
+    def __dir_walker(self, rootdir, files):
 
-        ## Get all of the files in the directory that are of the correct types
-        types_re = re.compile(TYPES_REGEX, re.IGNORECASE)
-        raw_matches = [f for f in files if types_re.match(f)]
-
-        ### Loop through the dictionary and try to move everything that matches
-        for regex, todir in dict:
-            matches = self.get_matches(raw_matches, regex)
-
-            ## Move the files if we've found some
-            if len(matches) > 0:
-                self.move_files(matches, root, todir)
+        for (r,d) in self.dict:
+            matches = [f for f in files if r.match(f)]
+            self.move_files (matches, rootdir, d)
 
     def get_user_choice(self, prompt):
 
@@ -226,16 +230,15 @@ class AnimeSorter2:
         logging.info ('')
 
         ## Parse the dictionary
-        dict = self.parse_dict()
+        self.dict = self.parse_dict()
 
         if self.options.recursive:
             ## Start walking through directories
             for root, dirs, files in os.walk(self.options.start_dir):
-                self.__dir_walker(dict, root, dirs, files)
+                self.__dir_walker(root, files)
         else:
-            self.__dir_walker(dict, self.options.start_dir,
-                    [d for d in os.listdir(self.options.start_dir) if os.path.isdir(d)],
-                    [f for f in os.listdir(self.options.start_dir) if os.path.isfile(f)])
+            self.__dir_walker(self.options.start_dir, [f for f in
+                    os.listdir(self.options.start_dir) if os.path.isfile(f)])
 
 
 ### MAIN IS HERE ###
@@ -245,7 +248,7 @@ def main():
     logging.basicConfig (level=logging.INFO, format='%(message)s')
 
     ### Get the program options
-    parser = OptionParser()
+    parser = optparse.OptionParser()
     parser.add_option('-q', '--quiet', action='store_true', dest='quiet',
             default=False, help="Don't print status messages to stdout")
     parser.add_option('-d', '--dict', dest='dict_file', default=DICT_FILE,
@@ -260,6 +263,10 @@ def main():
             help='Confirm each move', action='store_true')
     parser.add_option('-p', '--pretend', dest='pretend', default=False,
             help='Enable pretend mode', action='store_true')
+    parser.add_option('-e', '--editor', dest='run_editor', default=False,
+            help='Run editor on dictionary', action='store_true')
+    parser.add_option('-V', '--version', dest='version', default=False,
+            help='Show version and exit', action='store_true')
 
     ## Parse the options
     (options, args) = parser.parse_args()
@@ -269,12 +276,35 @@ def main():
     options.start_dir = os.path.abspath(os.path.expanduser(options.start_dir))
     options.output_dir = os.path.abspath(os.path.expanduser(options.output_dir))
 
+    ## Show version if necessary
+    if options.version:
+        print '%s - %s' % (PROGRAM_NAME, PROGRAM_VERSION)
+        print
+        print 'Copyright (c) 2005-2007 Ira W. Snyder (devel@irasnyder.com)'
+        print 'This program comes with ABSOLUTELY NO WARRANTY.'
+        print 'This is free software, and you are welcome to redistribute it'
+        print 'under certain conditions. See the file COPYING for details.'
+
+        sys.exit (0)
+
+    ## Run editor if necessary
+    if options.run_editor:
+        editor = os.getenv ('EDITOR')
+
+        if editor != None:
+            os.system ('%s %s' % (editor, options.dict_file))
+        else:
+            logging.critical ('Default editor could not be found!')
+            sys.exit (1)
+
+        sys.exit (0) # successful, but exit anyway
+
     # Change the loglevel if we're running in quiet mode
     if options.quiet:
         logging.getLogger().setLevel (logging.CRITICAL)
 
-    as = AnimeSorter2(options)
-    as.main()
+    sorter = AnimeSorter2(options)
+    sorter.main()
 
 if __name__ == '__main__':
     main ()