diff -Nru awscli-1.7.3/awscli/customizations/s3/subcommands.py awscli-1.7.4/awscli/customizations/s3/subcommands.py --- awscli-1.7.3/awscli/customizations/s3/subcommands.py 2015-01-20 22:56:25.000000000 +0000 +++ awscli-1.7.4/awscli/customizations/s3/subcommands.py 2015-01-27 03:45:27.000000000 +0000 @@ -27,7 +27,7 @@ from awscli.customizations.s3.filters import create_filter from awscli.customizations.s3.s3handler import S3Handler, S3StreamHandler from awscli.customizations.s3.utils import find_bucket_key, uni_print, \ - AppendFilter, find_dest_path_comp_key + AppendFilter, find_dest_path_comp_key, human_readable_size from awscli.customizations.s3.syncstrategy.base import MissingFileSync, \ SizeAndLastModifiedSync, NeverSync @@ -38,6 +38,12 @@ "Command is performed on all files or objects " "under the specified directory or prefix.")} +HUMAN_READABLE = {'name': 'human-readable', 'action': 'store_true', + 'help_text': "Displays file sizes in human readable format."} + +SUMMARIZE = {'name': 'summarize', 'action': 'store_true', 'help_text': ( + "Displays summary information (number of objects, total size).")} + DRYRUN = {'name': 'dryrun', 'action': 'store_true', 'help_text': ( "Displays the operations that would be performed using the " @@ -242,13 +248,16 @@ USAGE = " or NONE" ARG_TABLE = [{'name': 'paths', 'nargs': '?', 'default': 's3://', 'positional_arg': True, 'synopsis': USAGE}, RECURSIVE, - PAGE_SIZE] + PAGE_SIZE, HUMAN_READABLE, SUMMARIZE] EXAMPLES = BasicCommand.FROM_FILE('s3/ls.rst') def _run_main(self, parsed_args, parsed_globals): super(ListCommand, self)._run_main(parsed_args, parsed_globals) self._empty_result = False self._at_first_page = True + self._size_accumulator = 0 + self._total_objects = 0 + self._human_readable = parsed_args.human_readable path = parsed_args.paths if path.startswith('s3://'): path = path[5:] @@ -261,8 +270,10 @@ parsed_args.page_size) else: self._list_all_objects(bucket, key, parsed_args.page_size) + if parsed_args.summarize: + self._print_summary() if key: - # User specified a key to look for. We should return an rc of one + # User specified a key to look for. We should return an rc of one # if there are no matching keys and/or prefixes or return an rc # of zero if there are matching keys or prefixes. return self._check_no_objects() @@ -276,7 +287,6 @@ return 0 def _list_all_objects(self, bucket, key, page_size=None): - operation = self.service.get_operation('ListObjects') iterator = operation.paginate(self.endpoint, bucket=bucket, prefix=key, delimiter='/', @@ -298,6 +308,8 @@ uni_print(print_str) for content in contents: last_mod_str = self._make_last_mod_str(content['LastModified']) + self._size_accumulator += int(content['Size']) + self._total_objects += 1 size_str = self._make_size_str(content['Size']) if use_basename: filename_components = content['Key'].split('/') @@ -343,7 +355,7 @@ str(last_mod.day).zfill(2), str(last_mod.hour).zfill(2), str(last_mod.minute).zfill(2), - str(last_mod.second).zfill(2)) + str(last_mod.second).zfill(2)) last_mod_str = "%s-%s-%s %s:%s:%s" % last_mod_tup return last_mod_str.ljust(19, ' ') @@ -351,9 +363,18 @@ """ This function creates the size string when objects are being listed. """ - size_str = str(size) + size_str = human_readable_size(size) if self._human_readable else str(size) return size_str.rjust(10, ' ') + def _print_summary(self): + """ + This function prints a summary of total objects and total bytes + """ + print_str = str(self._total_objects) + uni_print("\nTotal Objects: ".rjust(15, ' ') + print_str + "\n") + print_str = human_readable_size(self._size_accumulator) if self._human_readable else str(self._size_accumulator) + uni_print("Total Size: ".rjust(15, ' ') + print_str + "\n") + class WebsiteCommand(S3Command): NAME = 'website' @@ -553,7 +574,7 @@ return False else: return True - + def choose_sync_strategies(self): """Determines the sync strategy for the command. @@ -648,7 +669,7 @@ endpoint=self._endpoint, is_stream=True)] file_info_builder = FileInfoBuilder(self._service, self._endpoint, - self._source_endpoint, self.parameters) + self._source_endpoint, self.parameters) s3handler = S3Handler(self.session, self.parameters, result_queue=result_queue) s3_stream_handler = S3StreamHandler(self.session, self.parameters, @@ -712,7 +733,7 @@ # tasks failed and the number of tasks warned. # This means that files[0] now contains a namedtuple with # the number of failed tasks and the number of warned tasks. - # In terms of the RC, we're keeping it simple and saying + # In terms of the RC, we're keeping it simple and saying # that > 0 failed tasks will give a 1 RC and > 0 warned # tasks will give a 2 RC. Otherwise a RC of zero is returned. rc = 0 diff -Nru awscli-1.7.3/awscli/customizations/s3/utils.py awscli-1.7.4/awscli/customizations/s3/utils.py --- awscli-1.7.3/awscli/customizations/s3/utils.py 2015-01-20 22:56:25.000000000 +0000 +++ awscli-1.7.4/awscli/customizations/s3/utils.py 2015-01-27 03:45:27.000000000 +0000 @@ -31,6 +31,42 @@ from awscli.compat import queue +humanize_suffixes = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB') + + +def human_readable_size(value): + """Convert an size in bytes into a human readable format. + + For example:: + + >>> human_readable_size(1) + '1 Byte' + >>> human_readable_size(10) + '10 Bytes' + >>> human_readable_size(1024) + '1.0 KiB' + >>> human_readable_size(1024 * 1024) + '1.0 MiB' + + :param value: The size in bytes + :return: The size in a human readable format based on base-2 units. + + """ + one_decimal_point = '%.1f' + base = 1024 + bytes_int = float(value) + + if bytes_int == 1: + return '1 Byte' + elif bytes_int < base: + return '%d Bytes' % bytes_int + + for i, suffix in enumerate(humanize_suffixes): + unit = base ** (i+2) + if round((bytes_int / unit) * base) < base: + return '%.1f %s' % ((base * bytes_int / unit), suffix) + + class AppendFilter(argparse.Action): """ This class is used as an action when parsing the parameters. diff -Nru awscli-1.7.3/awscli/examples/s3/ls.rst awscli-1.7.4/awscli/examples/s3/ls.rst --- awscli-1.7.3/awscli/examples/s3/ls.rst 2015-01-20 22:56:25.000000000 +0000 +++ awscli-1.7.4/awscli/examples/s3/ls.rst 2015-01-27 03:45:27.000000000 +0000 @@ -48,3 +48,25 @@ 2013-09-02 21:32:57 189 foo/bar/.baz/hooks/foo 2013-09-02 21:32:57 398 z.txt +The following ``ls`` command demonstrates the same command using the --humanize +and --summarize options. --humanize displays file size in +Bytes/MiB/KiB/GiB/TiB/PiB/EiB. --summarize displays the total number of objects +and total size at the end of the result listing:: + + aws s3 ls s3://mybucket --recursive --humanize --summarize + +Output:: + + 2013-09-02 21:37:53 10 Bytes a.txt + 2013-09-02 21:37:53 2.9 MiB foo.zip + 2013-09-02 21:32:57 23 Bytes foo/bar/.baz/a + 2013-09-02 21:32:58 41 Bytes foo/bar/.baz/b + 2013-09-02 21:32:57 281 Bytes foo/bar/.baz/c + 2013-09-02 21:32:57 73 Bytes foo/bar/.baz/d + 2013-09-02 21:32:57 452 Bytes foo/bar/.baz/e + 2013-09-02 21:32:57 896 Bytes foo/bar/.baz/hooks/bar + 2013-09-02 21:32:57 189 Bytes foo/bar/.baz/hooks/foo + 2013-09-02 21:32:57 398 Bytes z.txt + + Total Objects: 10 + Total Size: 2.9 MiB diff -Nru awscli-1.7.3/awscli/__init__.py awscli-1.7.4/awscli/__init__.py --- awscli-1.7.3/awscli/__init__.py 2015-01-20 22:56:26.000000000 +0000 +++ awscli-1.7.4/awscli/__init__.py 2015-01-27 03:45:27.000000000 +0000 @@ -17,7 +17,7 @@ """ import os -__version__ = '1.7.3' +__version__ = '1.7.4' # # Get our data path to be added to botocore's search path diff -Nru awscli-1.7.3/awscli.egg-info/PKG-INFO awscli-1.7.4/awscli.egg-info/PKG-INFO --- awscli-1.7.3/awscli.egg-info/PKG-INFO 2015-01-20 22:56:26.000000000 +0000 +++ awscli-1.7.4/awscli.egg-info/PKG-INFO 2015-01-27 03:45:27.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: awscli -Version: 1.7.3 +Version: 1.7.4 Summary: Universal Command Line Environment for AWS. Home-page: http://aws.amazon.com/cli/ Author: Mitch Garnaat diff -Nru awscli-1.7.3/awscli.egg-info/requires.txt awscli-1.7.4/awscli.egg-info/requires.txt --- awscli-1.7.3/awscli.egg-info/requires.txt 2015-01-20 22:56:26.000000000 +0000 +++ awscli-1.7.4/awscli.egg-info/requires.txt 2015-01-27 03:45:27.000000000 +0000 @@ -1,4 +1,4 @@ -botocore>=0.84.0,<0.85.0 +botocore>=0.85.0,<0.86.0 bcdoc>=0.12.0,<0.13.0 colorama==0.2.5 docutils>=0.10 diff -Nru awscli-1.7.3/debian/changelog awscli-1.7.4/debian/changelog --- awscli-1.7.3/debian/changelog 2015-01-23 23:01:27.000000000 +0000 +++ awscli-1.7.4/debian/changelog 2015-01-28 21:27:58.000000000 +0000 @@ -1,3 +1,9 @@ +awscli (1.7.4-1chl1~trusty1) trusty; urgency=low + + * Update to 1.7.4. + + -- Chris Lea Wed, 28 Jan 2015 13:27:37 -0800 + awscli (1.7.3-1chl1~trusty1) trusty; urgency=low * Update to 1.7.3. diff -Nru awscli-1.7.3/debian/control awscli-1.7.4/debian/control --- awscli-1.7.3/debian/control 2015-01-23 23:01:40.000000000 +0000 +++ awscli-1.7.4/debian/control 2015-01-28 21:28:07.000000000 +0000 @@ -6,8 +6,8 @@ Build-Depends: debhelper (>= 9), python3, python3-setuptools, - python3-botocore (>= 0.84.0), - python3-botocore (<< 0.85.0), + python3-botocore (>= 0.85.0), + python3-botocore (<< 0.86.0), python3-bcdoc (>= 0.12.0), python3-bcdoc (<< 0.13.0), python3-six (>= 1.1.0), diff -Nru awscli-1.7.3/PKG-INFO awscli-1.7.4/PKG-INFO --- awscli-1.7.3/PKG-INFO 2015-01-20 22:56:26.000000000 +0000 +++ awscli-1.7.4/PKG-INFO 2015-01-27 03:45:27.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: awscli -Version: 1.7.3 +Version: 1.7.4 Summary: Universal Command Line Environment for AWS. Home-page: http://aws.amazon.com/cli/ Author: Mitch Garnaat diff -Nru awscli-1.7.3/setup.py awscli-1.7.4/setup.py --- awscli-1.7.3/setup.py 2015-01-20 22:56:26.000000000 +0000 +++ awscli-1.7.4/setup.py 2015-01-27 03:45:27.000000000 +0000 @@ -6,7 +6,7 @@ import awscli -requires = ['botocore>=0.84.0,<0.85.0', +requires = ['botocore>=0.85.0,<0.86.0', 'bcdoc>=0.12.0,<0.13.0', 'colorama==0.2.5', 'docutils>=0.10',