Commits

Jerónimo Albi committed cb89aa4 Draft Merge

Merging python3 branch into main branch

Comments (0)

Files changed (9)

 Changelog
 =========
 
-1.0.2 - unreleased
+1.1.0 - 2012-07-31
 ==================
 
- * Added nose and unittests
+ * Added python 3 support
  * Updated documentation
+ * Added initial files for unit testing
 
 1.0.1 - 2012-07-19
 ==================
 Shrinking (or minifying) these files reduces the number of request that are
 made after a page load and also the size of these requests.
 
-This command depends on `YUI Compressor`_ to do the work, and can be run
-with Python 2.5 and above (Python 3 not supported yet).
+This command depends on `YUI Compressor`_ for compression, and can be run
+with Python 2.5 and above, including Python 3.
 
 Install
 =======
 * Add log file support
 * Add suppport for google closure javascript compressor
 * Check that java interpreter exists to avoid Popen IOError
-* Add support for Python 3
 * Add tox for a proper interpreter version testing
 CURRENT_DIR = os.path.dirname(__file__)
 README = read_file(CURRENT_DIR, "README.rst")
 CHANGELOG = read_file(CURRENT_DIR, "CHANGELOG.rst")
-LONG_DESCRIPTION = u"{0}\n\n{1}".format(README, CHANGELOG)
+LONG_DESCRIPTION = "%s\n\n%s" % (README, CHANGELOG)
 AUTHORS = (
     ("Jer\xc3\xb3nimo Jos\xc3\xa9 Albi", "albi@wienfluss.net"),
 )
     keywords="minify javascript css yuicompressor",
     license="BSD License",
     platforms=["OS Independent"],
+    use_2to3=True,
     zip_safe=False,
     packages=find_packages(exclude=["tests"]),
     include_package_data=True,
         "Programming Language :: Python :: 2.5",
         "Programming Language :: Python :: 2.6",
         "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
         "Topic :: Utilities",
     ],
     entry_points={

shrink/__init__.py

 
 VERSION_INFO = {
     'major': 1,
-    'minor': 0,
-    'micro': 2,
+    'minor': 1,
+    'micro': 0,
 }
 
 

shrink/command.py

 
 from shrink import get_version
 from shrink import utils
+from shrink.compatibility import get_exception_obj
+from shrink.compatibility import print_text
 from shrink.parser import Parser
 from shrink.parser import ParserError
 from shrink.utils import JAVA_BIN
 
     """
     for line in parser.list_all_sections():
-        print line
+        print_text(line)
 
 
 def run():
     cfg_file = os.path.abspath(cfg_file)
 
     if args.version:
-        print 'Shrink %s' % get_version()
+        print_text('Shrink %s', get_version())
         sys.exit()
     elif args.example:
         # when --example-cfg is present copy example to
         # current folder and exit
         file_name = 'example_shrink.cfg'
         if os.path.isfile(file_name):
-            print ('File %s not copied because it already exists in '
-                   'current folder' % file_name)
+            text = ('File %s not copied because it already exists in '
+                    'current folder')
+            print_text(text, file_name)
             sys.exit(1)
 
         file_path = utils.get_data_file(file_name)
         shutil.copy(file_path, file_name)
-        print 'Copied %s to current folder' % file_name
+        print_text('Copied %s to current folder', file_name)
         sys.exit()
     elif not (args.sections or args.list_sections):
         #when no section name(s) is given or no section
     if args.cfg_file and not cfg_file_exists:
         # when config file argument exist but
         # file does not inform user and exit
-        print (u'File %s does not exist' % cfg_file)
+        print_text('File %s does not exist' % cfg_file)
         sys.exit(1)
 
     try:
         utils.init_logging(options=parser.args)
         # print message when using default config file
         if not args.cfg_file:
-            LOG.info(u'Using config file %s', cfg_file)
+            LOG.info('Using config file %s', cfg_file)
 
         if args.list_sections:
             # list config file sections (--list-sections)
         else:
             # by default parse config file and minify
             parser.parse()
-    except ParserError, err:
+    except ParserError:
+        err = get_exception_obj()
         LOG.error(err)
         sys.exit(1)
 

shrink/compatibility.py

+import sys
+
+
+# define a function to handle str types
+if sys.version < '3':
+    import codecs
+
+    def u(text):
+        # return a unicode text
+        return codecs.unicode_escape_decode(text)[0]
+else:
+    def u(text):
+        # leave text as it is
+        return text
+
+
+def print_text(text, *args, **kwargs):
+    """Print a text to standar output
+
+    """
+    if text:
+        end = kwargs.pop('end', '\n')
+        if args:
+            text = (text % args) + end
+        else:
+            text = text + end
+
+    sys.stdout.write(text)
+
+
+def get_exception_obj():
+    """Get Exception instance in the context of an exception
+
+    """
+    return sys.exc_info()[1]
+
+
+def get_exception_info():
+    """Get a "3-uple" with exception information
+
+    Tuple contains (Exception class, Exception instance, traceback).
+
+    """
+    return sys.exc_info()
 import os
 import shutil
 
-from ConfigParser import ConfigParser
-from ConfigParser import NoSectionError
+try:
+    import configparser
+except ImportError:
+    configparser = __import__("ConfigParser")
 
 from shrink import utils
 
-OPEN_FILE_ERROR = u'Unable to open file %s'
+OPEN_FILE_ERROR = 'Unable to open file %s'
 
 LOG = logging.getLogger('shrink')
 
         # init some default config parser variable values
         cfg_defaults = {'here': self.cfg_file_dir}
         # read config file options
-        cfg_parser = ConfigParser(defaults=cfg_defaults)
+        cfg_parser = configparser.ConfigParser(defaults=cfg_defaults)
         try:
             cfg_parser.read(cfg_file)
         except Exception:
-            # print traceback onlu when debug flag is present
-            if self.args.debug:
-                LOG.exception(u'Config file is not valid')
+            # print traceback only when debug flag is present
+            LOG.exception('Config file is not valid')
 
-            raise ParserError(u'Unable to parse config file %s' % cfg_file)
+            raise ParserError('Unable to parse config file %s' % cfg_file)
 
         return cfg_parser
 
             arg_name = name.replace('arg.', '')
             # when argument is missing use ini file value
             if getattr(args, arg_name, None) is None:
-                LOG.debug(u'Setting arg %s=%s from ini', arg_name, value)
+                LOG.debug('Setting arg %s=%s from ini', arg_name, value)
                 setattr(args, arg_name, value)
 
         return args
             if self.args.debug:
                 LOG.exception(OPEN_FILE_ERROR, file_name)
 
-            LOG.error(u'File %s not included in hash', file_name)
+            LOG.error('File %s not included in hash', file_name)
             return
-
         # get file contents in small chunks and update global hash
         while True:
             chunk = file_obj.read(chunk_size)
 
             # update global hash object with current chunk digest
             chunk_digest = hashlib.sha1(chunk).hexdigest()
-            self.sha1_hash.update(chunk_digest)
+            self.sha1_hash.update(chunk_digest.encode('utf8'))
             self.sha1_updated = True
 
         file_obj.close()
             file_obj = open(file_name, 'w')
             file_obj.write(digest)
             file_obj.close()
-            LOG.info(u'Saved hash file %s', file_name)
+            LOG.info('Saved hash file %s', file_name)
         except Exception:
             if self.args.debug:
                 LOG.exception(OPEN_FILE_ERROR, file_name)
 
-            raise ParserError(u'Unable to save hash to %s' % file_name)
+            raise ParserError('Unable to save hash to %s' % file_name)
 
     def parse_section(self, section_name):
         """Parse a config section and minify files for that section
 
         """
-        LOG.debug(u'Parsing section [%s]', section_name)
+        LOG.debug('Parsing section [%s]', section_name)
         # create a dictionary with config file section values
         values = dict(self.cfg_parser.items(section_name))
 
         # check if current section is a section for a group
         # NOTE: including groups inside groups can lead to recursion
         if 'group' in values:
-            LOG.debug(u'Parsing section group [%s]', section_name)
+            LOG.debug('Parsing section group [%s]', section_name)
             section_name_list = values['group'].strip('\n').split('\n')
             for name in section_name_list:
                 try:
                     self.parse_section(name)
-                except NoSectionError:
-                    msg = (u'Invalid section name "%s" in section group [%s]'
+                except configparser.NoSectionError:
+                    msg = ('Invalid section name "%s" in section group [%s]'
                            % (name, section_name))
 
                     raise ParserError(msg)
         # check mandatory section option values
         for name in ('source_directory', 'destination_file', 'source_files'):
             if not values.get(name):
-                msg = u'No %s value in section [%s]' % (name, section_name)
+                msg = 'No %s value in section [%s]' % (name, section_name)
 
                 raise ParserError(msg)
 
             # with contents of all files in group
             source_file = utils.join_files(source_file_list)
             if not source_file:
-                msg = (u'Unable to generate file %s for section [%s]'
+                msg = ('Unable to generate file %s for section [%s]'
                        % (destination_file, section_name))
                 LOG.error(msg)
 
             source_file = utils.get_absolute_path(source_directory,
                                                   source_file)
         else:
-            msg = u'Section [%s] dont have source file(s)' % section_name
+            msg = 'Section [%s] dont have source file(s)' % section_name
             LOG.error(msg)
 
             return
                                                     destination_file,
                                                     **kw)
         else:
-            LOG.info(u'Generating file %s', destination_file)
+            LOG.info('Generating file %s', destination_file)
             shutil.copy(source_file, destination_file)
             return_code = 0
 
         if return_code:
-            LOG.error(u'Unable to generate %s', destination_file)
+            LOG.error('Unable to generate %s', destination_file)
             # include yuicompressor error output
             if self.args.verbose:
                 LOG.error(out_str)
         #source file because is a temporary file
         if source_file_count > 1:
             os.remove(source_file)
-            LOG.debug(u'Deleted temporary file %s', source_file)
+            LOG.debug('Deleted temporary file %s', source_file)
 
     def get_section_names(self):
         """Get a list with all config file sections
             if is_section_group:
                 sub_sections = cfg_parser.get(section_name, 'group')
                 sub_sections = sub_sections.strip('\n')
-                sub_sections = sub_sections.replace('\n', u', ')
+                sub_sections = sub_sections.replace('\n', ', ')
 
-                line = u'%s [%s]' % (line, sub_sections)
+                line = '%s [%s]' % (line, sub_sections)
 
             yield line
 
     if not yui_jar:
         yui_jar = YUI_JAR
 
-    LOG.info(u'Generating file %s', out_file_name)
+    LOG.info('Generating file %s', out_file_name)
     #get compression type from file extension (css, js)
     file_type = os.path.splitext(out_file_name)[1]
     file_type = file_type.lstrip('.')
 
     cmd = [java_bin, '-jar', yui_jar, '-o', out_file_name,
            '--type', file_type, in_file_name]
-    LOG.debug(u'Executing: %s', u' '.join(cmd))
+    LOG.debug('Executing: %s', ' '.join(cmd))
 
     #create a process to run compressor and
     #dont display command output to console
     failed = False
     #create a temporary file to concatenate file contents
     (tmp_file_fd, tmp_file_name) = tempfile.mkstemp(text=True)
-    LOG.debug(u'Created temporary file %s for join', tmp_file_name)
+    LOG.debug('Created temporary file %s for join', tmp_file_name)
 
     #use given file descriptor to open file
     tmp_file = os.fdopen(tmp_file_fd, 'w')
     #concatenate each file in list into the temporary file
     for file_name in file_name_list:
         if not os.path.isfile(file_name):
-            LOG.error(u'Invalid file %s for concatenation', file_name)
+            LOG.error('Invalid file %s for concatenation', file_name)
             failed = True
             break
 
         src_file = None
         try:
-            LOG.debug(u'Adding %s to temporary file', file_name)
+            LOG.debug('Adding %s to temporary file', file_name)
             src_file = open(file_name, 'r')
             #copy file contents into temp file
             shutil.copyfileobj(src_file, tmp_file)
         except Exception:
-            LOG.exception(u'Unable to concatenate file %s', file_name)
+            LOG.exception('Unable to concatenate file %s', file_name)
             failed = True
             #close current opened file
             if src_file:
     if failed:
         #remove temp file before exit
         os.remove(tmp_file_name)
-        LOG.debug(u'Deleted temporary file %s', tmp_file_name)
+        LOG.debug('Deleted temporary file %s', tmp_file_name)
 
         return False