Update upstream source from tag 'upstream/9.1'
Update to upstream version '9.1'
with Debian dir 04914890f75b54b6317c2c21720bd98fd0b2289c
Sandro Tosi
3 years ago
0 | [run] | |
1 | branch = true | |
2 | parallel = True | |
3 | concurrency = multiprocessing | |
4 | source = humanfriendly | |
5 | omit = humanfriendly/tests.py |
0 | *.egg-info/ | |
1 | *.pyc | |
2 | .coverage | |
3 | .coverage.* | |
4 | __pycache__/ | |
5 | dist/*.tar.gz | |
6 | dist/*.zip | |
7 | docs/_build/ | |
8 | docs/_static/ | |
9 | docs/_templates/ | |
10 | docs/build/ | |
11 | htmlcov/ |
0 | language: python | |
1 | cache: pip | |
2 | matrix: | |
3 | include: | |
4 | - os: osx | |
5 | language: generic | |
6 | - python: pypy | |
7 | - python: 2.7 | |
8 | - python: 3.5 | |
9 | - python: 3.6 | |
10 | - python: 3.7 | |
11 | - python: 3.8 | |
12 | - python: 3.9-dev | |
13 | install: | |
14 | - scripts/travis.sh pip install --upgrade setuptools | |
15 | - scripts/travis.sh pip install --upgrade --requirement=requirements-travis.txt | |
16 | - scripts/travis.sh LC_ALL=C pip install . | |
17 | script: | |
18 | - scripts/travis.sh make check | |
19 | - scripts/travis.sh make test | |
20 | after_success: | |
21 | - scripts/travis.sh coveralls | |
22 | branches: | |
23 | except: | |
24 | - /^[0-9]/ |
9 | 9 | |
10 | 10 | .. _Keep a Changelog: http://keepachangelog.com/ |
11 | 11 | .. _semantic versioning: http://semver.org/ |
12 | ||
13 | `Release 9.1`_ (2020-12-10) | |
14 | --------------------------- | |
15 | ||
16 | Added :func:`~humanfriendly.compat.on_macos()` function to detect Apple MacOS | |
17 | (I need this in an upcoming :pypi:`coloredlogs` release and don't want to have | |
18 | to think about how to detect MacOS again in the future 😇). | |
19 | ||
20 | .. _Release 9.1: https://github.com/xolox/python-humanfriendly/compare/9.0...9.1 | |
21 | ||
22 | `Release 9.0`_ (2020-12-01) | |
23 | --------------------------- | |
24 | ||
25 | The major version number was bumped because the bug fix for | |
26 | :func:`~humanfriendly.text.pluralize()` is backwards incompatible | |
27 | and (even though this seems like very "cosmetic" functionality) | |
28 | version numbers are cheap, so who cares 😉. | |
29 | ||
30 | **Bug fixes:** | |
31 | ||
32 | - Changed :func:`~humanfriendly.format_number()` to properly support negative | |
33 | numbers (as suggested in `issue #40`_). | |
34 | ||
35 | - Changed :func:`~humanfriendly.text.pluralize()` to generate "1.5 seconds" | |
36 | instead of "1.5 second" (as suggested in `issue #43`_). | |
37 | ||
38 | **Enhancements:** | |
39 | ||
40 | - Enhanced :func:`~humanfriendly.text.concatenate()` to support ``conjunction`` | |
41 | and ``serial_comma`` keyword arguments (as suggested in `issue #30`_). | |
42 | ||
43 | - Added :func:`~humanfriendly.text.pluralize_raw()` to select singular or | |
44 | plural form without prefixing the count to the text that is returned. | |
45 | ||
46 | .. _Release 9.0: https://github.com/xolox/python-humanfriendly/compare/8.2...9.0 | |
47 | .. _issue #30: https://github.com/xolox/python-humanfriendly/issues/30 | |
48 | .. _issue #40: https://github.com/xolox/python-humanfriendly/issues/40 | |
49 | .. _issue #43: https://github.com/xolox/python-humanfriendly/issues/43 | |
12 | 50 | |
13 | 51 | `Release 8.2`_ (2020-04-19) |
14 | 52 | --------------------------- |
0 | # Makefile for the 'humanfriendly' package. | |
1 | # | |
2 | # Author: Peter Odding <peter@peterodding.com> | |
3 | # Last Change: March 1, 2020 | |
4 | # URL: https://humanfriendly.readthedocs.io | |
5 | ||
6 | PACKAGE_NAME = humanfriendly | |
7 | WORKON_HOME ?= $(HOME)/.virtualenvs | |
8 | VIRTUAL_ENV ?= $(WORKON_HOME)/$(PACKAGE_NAME) | |
9 | PYTHON ?= python3 | |
10 | PATH := $(VIRTUAL_ENV)/bin:$(PATH) | |
11 | MAKE := $(MAKE) --no-print-directory | |
12 | SHELL = bash | |
13 | ||
14 | default: | |
15 | @echo "Makefile for $(PACKAGE_NAME)" | |
16 | @echo | |
17 | @echo 'Usage:' | |
18 | @echo | |
19 | @echo ' make install install the package in a virtual environment' | |
20 | @echo ' make reset recreate the virtual environment' | |
21 | @echo ' make check check coding style (PEP-8, PEP-257)' | |
22 | @echo ' make test run the test suite, report coverage' | |
23 | @echo ' make tox run the tests on all Python versions' | |
24 | @echo ' make readme update usage in readme' | |
25 | @echo ' make docs update documentation using Sphinx' | |
26 | @echo ' make publish publish changes to GitHub/PyPI' | |
27 | @echo ' make clean cleanup all temporary files' | |
28 | @echo | |
29 | ||
30 | install: | |
31 | @test -d "$(VIRTUAL_ENV)" || mkdir -p "$(VIRTUAL_ENV)" | |
32 | @test -x "$(VIRTUAL_ENV)/bin/python" || virtualenv --python=$(PYTHON) --quiet "$(VIRTUAL_ENV)" | |
33 | @pip uninstall --yes $(PACKAGE_NAME) &>/dev/null || true | |
34 | @pip install --quiet --no-deps --ignore-installed . | |
35 | ||
36 | reset: | |
37 | @$(MAKE) clean | |
38 | @rm -Rf "$(VIRTUAL_ENV)" | |
39 | @$(MAKE) install | |
40 | ||
41 | check: install | |
42 | @pip install --upgrade --quiet --requirement=requirements-checks.txt | |
43 | @flake8 | |
44 | ||
45 | test: install | |
46 | @pip install --quiet --requirement=requirements-tests.txt | |
47 | @py.test --cov | |
48 | @coverage combine || true | |
49 | @coverage html | |
50 | ||
51 | tox: install | |
52 | @pip install --quiet tox | |
53 | @tox | |
54 | ||
55 | readme: install | |
56 | @pip install --quiet cogapp | |
57 | @cog.py -r README.rst | |
58 | ||
59 | docs: readme | |
60 | @pip install --quiet sphinx | |
61 | @cd docs && sphinx-build -nWb html -d build/doctrees . build/html | |
62 | ||
63 | publish: install | |
64 | @git push origin && git push --tags origin | |
65 | @$(MAKE) clean | |
66 | @pip install --quiet twine wheel | |
67 | @python setup.py sdist bdist_wheel | |
68 | @twine upload dist/* | |
69 | @$(MAKE) clean | |
70 | ||
71 | clean: | |
72 | @rm -Rf *.egg .cache .coverage .tox build dist docs/build htmlcov | |
73 | @find -depth -type d -name __pycache__ -exec rm -Rf {} \; | |
74 | @find -type f -name '*.pyc' -delete | |
75 | ||
76 | .PHONY: default install reset check test tox readme docs publish clean |
0 | Metadata-Version: 1.2 | |
1 | Name: humanfriendly | |
2 | Version: 8.2 | |
3 | Summary: Human friendly output for text interfaces using Python | |
4 | Home-page: https://humanfriendly.readthedocs.io | |
5 | Author: Peter Odding | |
6 | Author-email: peter@peterodding.com | |
7 | License: MIT | |
8 | Description: humanfriendly: Human friendly input/output in Python | |
9 | ==================================================== | |
10 | ||
11 | .. image:: https://travis-ci.org/xolox/python-humanfriendly.svg?branch=master | |
12 | :target: https://travis-ci.org/xolox/python-humanfriendly | |
13 | ||
14 | .. image:: https://coveralls.io/repos/github/xolox/python-humanfriendly/badge.svg?branch=master | |
15 | :target: https://coveralls.io/github/xolox/python-humanfriendly?branch=master | |
16 | ||
17 | The functions and classes in the `humanfriendly` package can be used to make | |
18 | text interfaces more user friendly. Some example features: | |
19 | ||
20 | - Parsing and formatting numbers, file sizes, pathnames and timespans in | |
21 | simple, human friendly formats. | |
22 | ||
23 | - Easy to use timers for long running operations, with human friendly | |
24 | formatting of the resulting timespans. | |
25 | ||
26 | - Prompting the user to select a choice from a list of options by typing the | |
27 | option's number or a unique substring of the option. | |
28 | ||
29 | - Terminal interaction including text styling (ANSI escape sequences), user | |
30 | friendly rendering of usage messages and querying the terminal for its | |
31 | size. | |
32 | ||
33 | The `humanfriendly` package is currently tested on Python 2.7, 3.5+ and PyPy | |
34 | (2.7) on Linux and macOS. While the intention is to support Windows as well, | |
35 | you may encounter some rough edges. | |
36 | ||
37 | .. contents:: | |
38 | :local: | |
39 | ||
40 | Getting started | |
41 | --------------- | |
42 | ||
43 | It's very simple to start using the `humanfriendly` package:: | |
44 | ||
45 | >>> import humanfriendly | |
46 | >>> user_input = raw_input("Enter a readable file size: ") | |
47 | Enter a readable file size: 16G | |
48 | >>> num_bytes = humanfriendly.parse_size(user_input) | |
49 | >>> print num_bytes | |
50 | 16000000000 | |
51 | >>> print "You entered:", humanfriendly.format_size(num_bytes) | |
52 | You entered: 16 GB | |
53 | >>> print "You entered:", humanfriendly.format_size(num_bytes, binary=True) | |
54 | You entered: 14.9 GiB | |
55 | ||
56 | Command line | |
57 | ------------ | |
58 | ||
59 | .. A DRY solution to avoid duplication of the `humanfriendly --help' text: | |
60 | .. | |
61 | .. [[[cog | |
62 | .. from humanfriendly.usage import inject_usage | |
63 | .. inject_usage('humanfriendly.cli') | |
64 | .. ]]] | |
65 | ||
66 | **Usage:** `humanfriendly [OPTIONS]` | |
67 | ||
68 | Human friendly input/output (text formatting) on the command | |
69 | line based on the Python package with the same name. | |
70 | ||
71 | **Supported options:** | |
72 | ||
73 | .. csv-table:: | |
74 | :header: Option, Description | |
75 | :widths: 30, 70 | |
76 | ||
77 | ||
78 | "``-c``, ``--run-command``","Execute an external command (given as the positional arguments) and render | |
79 | a spinner and timer while the command is running. The exit status of the | |
80 | command is propagated." | |
81 | ``--format-table``,"Read tabular data from standard input (each line is a row and each | |
82 | whitespace separated field is a column), format the data as a table and | |
83 | print the resulting table to standard output. See also the ``--delimiter`` | |
84 | option." | |
85 | "``-d``, ``--delimiter=VALUE``","Change the delimiter used by ``--format-table`` to ``VALUE`` (a string). By default | |
86 | all whitespace is treated as a delimiter." | |
87 | "``-l``, ``--format-length=LENGTH``","Convert a length count (given as the integer or float ``LENGTH``) into a human | |
88 | readable string and print that string to standard output." | |
89 | "``-n``, ``--format-number=VALUE``","Format a number (given as the integer or floating point number ``VALUE``) with | |
90 | thousands separators and two decimal places (if needed) and print the | |
91 | formatted number to standard output." | |
92 | "``-s``, ``--format-size=BYTES``","Convert a byte count (given as the integer ``BYTES``) into a human readable | |
93 | string and print that string to standard output." | |
94 | "``-b``, ``--binary``","Change the output of ``-s``, ``--format-size`` to use binary multiples of bytes | |
95 | (base-2) instead of the default decimal multiples of bytes (base-10)." | |
96 | "``-t``, ``--format-timespan=SECONDS``","Convert a number of seconds (given as the floating point number ``SECONDS``) | |
97 | into a human readable timespan and print that string to standard output." | |
98 | ``--parse-length=VALUE``,"Parse a human readable length (given as the string ``VALUE``) and print the | |
99 | number of metres to standard output." | |
100 | ``--parse-size=VALUE``,"Parse a human readable data size (given as the string ``VALUE``) and print the | |
101 | number of bytes to standard output." | |
102 | ``--demo``,"Demonstrate changing the style and color of the terminal font using ANSI | |
103 | escape sequences." | |
104 | "``-h``, ``--help``",Show this message and exit. | |
105 | ||
106 | .. [[[end]]] | |
107 | ||
108 | A note about size units | |
109 | ----------------------- | |
110 | ||
111 | When I originally published the `humanfriendly` package I went with binary | |
112 | multiples of bytes (powers of two). It was pointed out several times that this | |
113 | was a poor choice (see issue `#4`_ and pull requests `#8`_ and `#9`_) and thus | |
114 | the new default became decimal multiples of bytes (powers of ten): | |
115 | ||
116 | +------+---------------+---------------+ | |
117 | | Unit | Binary value | Decimal value | | |
118 | +------+---------------+---------------+ | |
119 | | KB | 1024 | 1000 + | |
120 | +------+---------------+---------------+ | |
121 | | MB | 1048576 | 1000000 | | |
122 | +------+---------------+---------------+ | |
123 | | GB | 1073741824 | 1000000000 | | |
124 | +------+---------------+---------------+ | |
125 | | TB | 1099511627776 | 1000000000000 | | |
126 | +------+---------------+---------------+ | |
127 | | etc | | | | |
128 | +------+---------------+---------------+ | |
129 | ||
130 | The option to use binary multiples of bytes remains by passing the keyword | |
131 | argument `binary=True` to the `format_size()`_ and `parse_size()`_ functions. | |
132 | ||
133 | Windows support | |
134 | --------------- | |
135 | ||
136 | Windows 10 gained native support for ANSI escape sequences which means commands | |
137 | like ``humanfriendly --demo`` should work out of the box (if your system is | |
138 | up-to-date enough). If this doesn't work then you can install the colorama_ | |
139 | package, it will be used automatically once installed. | |
140 | ||
141 | Contact | |
142 | ------- | |
143 | ||
144 | The latest version of `humanfriendly` is available on PyPI_ and GitHub_. The | |
145 | documentation is hosted on `Read the Docs`_ and includes a changelog_. For bug | |
146 | reports please create an issue on GitHub_. If you have questions, suggestions, | |
147 | etc. feel free to send me an e-mail at `peter@peterodding.com`_. | |
148 | ||
149 | License | |
150 | ------- | |
151 | ||
152 | This software is licensed under the `MIT license`_. | |
153 | ||
154 | © 2020 Peter Odding. | |
155 | ||
156 | .. External references: | |
157 | .. _#4: https://github.com/xolox/python-humanfriendly/issues/4 | |
158 | .. _#8: https://github.com/xolox/python-humanfriendly/pull/8 | |
159 | .. _#9: https://github.com/xolox/python-humanfriendly/pull/9 | |
160 | .. _changelog: https://humanfriendly.readthedocs.io/en/latest/changelog.html | |
161 | .. _colorama: https://pypi.org/project/colorama | |
162 | .. _format_size(): https://humanfriendly.readthedocs.io/en/latest/#humanfriendly.format_size | |
163 | .. _GitHub: https://github.com/xolox/python-humanfriendly | |
164 | .. _MIT license: https://en.wikipedia.org/wiki/MIT_License | |
165 | .. _parse_size(): https://humanfriendly.readthedocs.io/en/latest/#humanfriendly.parse_size | |
166 | .. _peter@peterodding.com: peter@peterodding.com | |
167 | .. _PyPI: https://pypi.org/project/humanfriendly | |
168 | .. _Read the Docs: https://humanfriendly.readthedocs.io | |
169 | ||
170 | Platform: UNKNOWN | |
171 | Classifier: Development Status :: 6 - Mature | |
172 | Classifier: Environment :: Console | |
173 | Classifier: Framework :: Sphinx :: Extension | |
174 | Classifier: Intended Audience :: Developers | |
175 | Classifier: Intended Audience :: System Administrators | |
176 | Classifier: License :: OSI Approved :: MIT License | |
177 | Classifier: Natural Language :: English | |
178 | Classifier: Programming Language :: Python | |
179 | Classifier: Programming Language :: Python :: 2 | |
180 | Classifier: Programming Language :: Python :: 2.7 | |
181 | Classifier: Programming Language :: Python :: 3 | |
182 | Classifier: Programming Language :: Python :: 3.5 | |
183 | Classifier: Programming Language :: Python :: 3.6 | |
184 | Classifier: Programming Language :: Python :: 3.7 | |
185 | Classifier: Programming Language :: Python :: 3.8 | |
186 | Classifier: Programming Language :: Python :: Implementation :: CPython | |
187 | Classifier: Programming Language :: Python :: Implementation :: PyPy | |
188 | Classifier: Topic :: Communications | |
189 | Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces | |
190 | Classifier: Topic :: Software Development | |
191 | Classifier: Topic :: Software Development :: Libraries :: Python Modules | |
192 | Classifier: Topic :: Software Development :: User Interfaces | |
193 | Classifier: Topic :: System :: Shells | |
194 | Classifier: Topic :: System :: System Shells | |
195 | Classifier: Topic :: System :: Systems Administration | |
196 | Classifier: Topic :: Terminals | |
197 | Classifier: Topic :: Text Processing :: General | |
198 | Classifier: Topic :: Text Processing :: Linguistic | |
199 | Classifier: Topic :: Utilities | |
200 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* |
0 | 0 | # Human friendly input/output in Python. |
1 | 1 | # |
2 | 2 | # Author: Peter Odding <peter@peterodding.com> |
3 | # Last Change: April 19, 2020 | |
3 | # Last Change: December 10, 2020 | |
4 | 4 | # URL: https://humanfriendly.readthedocs.io |
5 | 5 | |
6 | 6 | """The main module of the `humanfriendly` package.""" |
50 | 50 | ) |
51 | 51 | |
52 | 52 | # Semi-standard module versioning. |
53 | __version__ = '8.2' | |
53 | __version__ = '9.1' | |
54 | 54 | |
55 | 55 | # Named tuples to define units of size. |
56 | 56 | SizeUnit = collections.namedtuple('SizeUnit', 'divider, symbol, name') |
352 | 352 | 6,000,000,000 |
353 | 353 | """ |
354 | 354 | integer_part, _, decimal_part = str(float(number)).partition('.') |
355 | reversed_digits = ''.join(reversed(integer_part)) | |
355 | negative_sign = integer_part.startswith('-') | |
356 | reversed_digits = ''.join(reversed(integer_part.lstrip('-'))) | |
356 | 357 | parts = [] |
357 | 358 | while reversed_digits: |
358 | 359 | parts.append(reversed_digits[:3]) |
361 | 362 | decimals_to_add = decimal_part[:num_decimals].rstrip('0') |
362 | 363 | if decimals_to_add: |
363 | 364 | formatted_number += '.' + decimals_to_add |
365 | if negative_sign: | |
366 | formatted_number = '-' + formatted_number | |
364 | 367 | return formatted_number |
365 | 368 | |
366 | 369 |
0 | 0 | # Human friendly input/output in Python. |
1 | 1 | # |
2 | 2 | # Author: Peter Odding <peter@peterodding.com> |
3 | # Last Change: March 1, 2020 | |
3 | # Last Change: December 10, 2020 | |
4 | 4 | # URL: https://humanfriendly.readthedocs.io |
5 | 5 | |
6 | 6 | """ |
51 | 51 | 'is_unicode', |
52 | 52 | 'monotonic', |
53 | 53 | 'name2codepoint', |
54 | 'on_macos', | |
54 | 55 | 'on_windows', |
55 | 56 | 'unichr', |
56 | 57 | 'unicode', |
131 | 132 | return isinstance(value, unicode) |
132 | 133 | |
133 | 134 | |
135 | def on_macos(): | |
136 | """ | |
137 | Check if we're running on Apple MacOS. | |
138 | ||
139 | :returns: :data:`True` if running MacOS, :data:`False` otherwise. | |
140 | """ | |
141 | return sys.platform.startswith('darwin') | |
142 | ||
143 | ||
134 | 144 | def on_windows(): |
135 | 145 | """ |
136 | 146 | Check if we're running on the Microsoft Windows OS. |
3 | 3 | # Tests for the `humanfriendly' package. |
4 | 4 | # |
5 | 5 | # Author: Peter Odding <peter.odding@paylogic.eu> |
6 | # Last Change: April 19, 2020 | |
6 | # Last Change: December 1, 2020 | |
7 | 7 | # URL: https://humanfriendly.readthedocs.io |
8 | 8 | |
9 | 9 | """Test suite for the `humanfriendly` package.""" |
440 | 440 | # Make sure milliseconds are never shown separately when detailed=False. |
441 | 441 | # https://github.com/xolox/python-humanfriendly/issues/10 |
442 | 442 | assert '1 minute, 1 second and 100 milliseconds' == format_timespan(61.10, detailed=True) |
443 | assert '1 minute and 1.1 second' == format_timespan(61.10, detailed=False) | |
443 | assert '1 minute and 1.1 seconds' == format_timespan(61.10, detailed=False) | |
444 | 444 | # Test for loss of precision as reported in issue 11: |
445 | 445 | # https://github.com/xolox/python-humanfriendly/issues/11 |
446 | 446 | assert '1 minute and 0.3 seconds' == format_timespan(60.300) |
574 | 574 | self.assertEqual('1,000', format_number(1000.12, 0)) |
575 | 575 | self.assertEqual('1,000,000', format_number(1000000)) |
576 | 576 | self.assertEqual('1,000,000.42', format_number(1000000.42)) |
577 | # Regression test for https://github.com/xolox/python-humanfriendly/issues/40. | |
578 | self.assertEqual('-285.67', format_number(-285.67)) | |
577 | 579 | |
578 | 580 | def test_round_number(self): |
579 | 581 | """Test :func:`humanfriendly.round_number()`.""" |
727 | 729 | assert concatenate(['one']) == 'one' |
728 | 730 | assert concatenate(['one', 'two']) == 'one and two' |
729 | 731 | assert concatenate(['one', 'two', 'three']) == 'one, two and three' |
732 | # Test the 'conjunction' option. | |
733 | assert concatenate(['one', 'two', 'three'], conjunction='or') == 'one, two or three' | |
734 | # Test the 'serial_comma' option. | |
735 | assert concatenate(['one', 'two', 'three'], serial_comma=True) == 'one, two, and three' | |
730 | 736 | |
731 | 737 | def test_split(self): |
732 | 738 | """Test :func:`humanfriendly.text.split()`.""" |
786 | 792 | .replace(ANSI_HIDE_CURSOR, '')) |
787 | 793 | lines = [line for line in output.split(ANSI_ERASE_LINE) if line] |
788 | 794 | self.assertTrue(len(lines) > 0) |
789 | self.assertTrue(all('test spinner' in l for l in lines)) | |
790 | self.assertTrue(all('%' in l for l in lines)) | |
795 | self.assertTrue(all('test spinner' in ln for ln in lines)) | |
796 | self.assertTrue(all('%' in ln for ln in lines)) | |
791 | 797 | self.assertEqual(sorted(set(lines)), sorted(lines)) |
792 | 798 | |
793 | 799 | def test_automatic_spinner(self): |
951 | 957 | # https://github.com/xolox/python-humanfriendly/issues/28 |
952 | 958 | returncode, output = run_cli(main, '--demo') |
953 | 959 | assert returncode == 0 |
954 | lines = [ansi_strip(l) for l in output.splitlines()] | |
960 | lines = [ansi_strip(ln) for ln in output.splitlines()] | |
955 | 961 | assert "Text styles:" in lines |
956 | 962 | assert "Foreground colors:" in lines |
957 | 963 | assert "Background colors:" in lines |
0 | 0 | # Human friendly input/output in Python. |
1 | 1 | # |
2 | 2 | # Author: Peter Odding <peter@peterodding.com> |
3 | # Last Change: March 1, 2020 | |
3 | # Last Change: December 1, 2020 | |
4 | 4 | # URL: https://humanfriendly.readthedocs.io |
5 | 5 | |
6 | 6 | """ |
19 | 19 | """ |
20 | 20 | |
21 | 21 | # Standard library modules. |
22 | import math | |
23 | 22 | import numbers |
24 | 23 | import random |
25 | 24 | import re |
37 | 36 | 'is_empty_line', |
38 | 37 | 'join_lines', |
39 | 38 | 'pluralize', |
39 | 'pluralize_raw', | |
40 | 40 | 'random_string', |
41 | 41 | 'split', |
42 | 42 | 'split_paragraphs', |
93 | 93 | return ''.join(lines) |
94 | 94 | |
95 | 95 | |
96 | def concatenate(items): | |
96 | def concatenate(items, conjunction='and', serial_comma=False): | |
97 | 97 | """ |
98 | 98 | Concatenate a list of items in a human friendly way. |
99 | 99 | |
100 | :param items: A sequence of strings. | |
101 | :returns: A single string. | |
100 | :param items: | |
101 | ||
102 | A sequence of strings. | |
103 | ||
104 | :param conjunction: | |
105 | ||
106 | The word to use before the last item (a string, defaults to "and"). | |
107 | ||
108 | :param serial_comma: | |
109 | ||
110 | :data:`True` to use a `serial comma`_, :data:`False` otherwise | |
111 | (defaults to :data:`False`). | |
112 | ||
113 | :returns: | |
114 | ||
115 | A single string. | |
102 | 116 | |
103 | 117 | >>> from humanfriendly.text import concatenate |
104 | 118 | >>> concatenate(["eggs", "milk", "bread"]) |
105 | 119 | 'eggs, milk and bread' |
120 | ||
121 | .. _serial comma: https://en.wikipedia.org/wiki/Serial_comma | |
106 | 122 | """ |
107 | 123 | items = list(items) |
108 | 124 | if len(items) > 1: |
109 | return ', '.join(items[:-1]) + ' and ' + items[-1] | |
125 | final_item = items.pop() | |
126 | formatted = ', '.join(items) | |
127 | if serial_comma: | |
128 | formatted += ',' | |
129 | return ' '.join([formatted, conjunction, final_item]) | |
110 | 130 | elif items: |
111 | 131 | return items[0] |
112 | 132 | else: |
275 | 295 | """ |
276 | 296 | Combine a count with the singular or plural form of a word. |
277 | 297 | |
298 | :param count: The count (a number). | |
299 | :param singular: The singular form of the word (a string). | |
300 | :param plural: The plural form of the word (a string or :data:`None`). | |
301 | :returns: The count and singular or plural word concatenated (a string). | |
302 | ||
303 | See :func:`pluralize_raw()` for the logic underneath :func:`pluralize()`. | |
304 | """ | |
305 | return '%s %s' % (count, pluralize_raw(count, singular, plural)) | |
306 | ||
307 | ||
308 | def pluralize_raw(count, singular, plural=None): | |
309 | """ | |
310 | Select the singular or plural form of a word based on a count. | |
311 | ||
312 | :param count: The count (a number). | |
313 | :param singular: The singular form of the word (a string). | |
314 | :param plural: The plural form of the word (a string or :data:`None`). | |
315 | :returns: The singular or plural form of the word (a string). | |
316 | ||
317 | When the given count is exactly 1.0 the singular form of the word is | |
318 | selected, in all other cases the plural form of the word is selected. | |
319 | ||
278 | 320 | If the plural form of the word is not provided it is obtained by |
279 | 321 | concatenating the singular form of the word with the letter "s". Of course |
280 | 322 | this will not always be correct, which is why you have the option to |
281 | 323 | specify both forms. |
282 | ||
283 | :param count: The count (a number). | |
284 | :param singular: The singular form of the word (a string). | |
285 | :param plural: The plural form of the word (a string or :data:`None`). | |
286 | :returns: The count and singular/plural word concatenated (a string). | |
287 | 324 | """ |
288 | 325 | if not plural: |
289 | 326 | plural = singular + 's' |
290 | return '%s %s' % (count, singular if math.floor(float(count)) == 1 else plural) | |
327 | return singular if float(count) == 1.0 else plural | |
291 | 328 | |
292 | 329 | |
293 | 330 | def random_string(length=(25, 100), characters=string.ascii_letters): |
0 | 0 | # Human friendly input/output in Python. |
1 | 1 | # |
2 | 2 | # Author: Peter Odding <peter@peterodding.com> |
3 | # Last Change: June 24, 2017 | |
3 | # Last Change: December 1, 2020 | |
4 | 4 | # URL: https://humanfriendly.readthedocs.io |
5 | 5 | |
6 | 6 | """ |
257 | 257 | ('\n\n'.join(render_paragraph(p, meta_variables) for p in split_paragraphs(description))).rstrip(), |
258 | 258 | ]) |
259 | 259 | csv_lines = csv_buffer.getvalue().splitlines() |
260 | output.append('\n'.join(' %s' % l for l in csv_lines)) | |
260 | output.append('\n'.join(' %s' % ln for ln in csv_lines)) | |
261 | 261 | logger.debug("Rendered output: %s", output) |
262 | 262 | return '\n\n'.join(trim_empty_lines(o) for o in output) |
263 | 263 |
0 | Metadata-Version: 1.2 | |
1 | Name: humanfriendly | |
2 | Version: 8.2 | |
3 | Summary: Human friendly output for text interfaces using Python | |
4 | Home-page: https://humanfriendly.readthedocs.io | |
5 | Author: Peter Odding | |
6 | Author-email: peter@peterodding.com | |
7 | License: MIT | |
8 | Description: humanfriendly: Human friendly input/output in Python | |
9 | ==================================================== | |
10 | ||
11 | .. image:: https://travis-ci.org/xolox/python-humanfriendly.svg?branch=master | |
12 | :target: https://travis-ci.org/xolox/python-humanfriendly | |
13 | ||
14 | .. image:: https://coveralls.io/repos/github/xolox/python-humanfriendly/badge.svg?branch=master | |
15 | :target: https://coveralls.io/github/xolox/python-humanfriendly?branch=master | |
16 | ||
17 | The functions and classes in the `humanfriendly` package can be used to make | |
18 | text interfaces more user friendly. Some example features: | |
19 | ||
20 | - Parsing and formatting numbers, file sizes, pathnames and timespans in | |
21 | simple, human friendly formats. | |
22 | ||
23 | - Easy to use timers for long running operations, with human friendly | |
24 | formatting of the resulting timespans. | |
25 | ||
26 | - Prompting the user to select a choice from a list of options by typing the | |
27 | option's number or a unique substring of the option. | |
28 | ||
29 | - Terminal interaction including text styling (ANSI escape sequences), user | |
30 | friendly rendering of usage messages and querying the terminal for its | |
31 | size. | |
32 | ||
33 | The `humanfriendly` package is currently tested on Python 2.7, 3.5+ and PyPy | |
34 | (2.7) on Linux and macOS. While the intention is to support Windows as well, | |
35 | you may encounter some rough edges. | |
36 | ||
37 | .. contents:: | |
38 | :local: | |
39 | ||
40 | Getting started | |
41 | --------------- | |
42 | ||
43 | It's very simple to start using the `humanfriendly` package:: | |
44 | ||
45 | >>> import humanfriendly | |
46 | >>> user_input = raw_input("Enter a readable file size: ") | |
47 | Enter a readable file size: 16G | |
48 | >>> num_bytes = humanfriendly.parse_size(user_input) | |
49 | >>> print num_bytes | |
50 | 16000000000 | |
51 | >>> print "You entered:", humanfriendly.format_size(num_bytes) | |
52 | You entered: 16 GB | |
53 | >>> print "You entered:", humanfriendly.format_size(num_bytes, binary=True) | |
54 | You entered: 14.9 GiB | |
55 | ||
56 | Command line | |
57 | ------------ | |
58 | ||
59 | .. A DRY solution to avoid duplication of the `humanfriendly --help' text: | |
60 | .. | |
61 | .. [[[cog | |
62 | .. from humanfriendly.usage import inject_usage | |
63 | .. inject_usage('humanfriendly.cli') | |
64 | .. ]]] | |
65 | ||
66 | **Usage:** `humanfriendly [OPTIONS]` | |
67 | ||
68 | Human friendly input/output (text formatting) on the command | |
69 | line based on the Python package with the same name. | |
70 | ||
71 | **Supported options:** | |
72 | ||
73 | .. csv-table:: | |
74 | :header: Option, Description | |
75 | :widths: 30, 70 | |
76 | ||
77 | ||
78 | "``-c``, ``--run-command``","Execute an external command (given as the positional arguments) and render | |
79 | a spinner and timer while the command is running. The exit status of the | |
80 | command is propagated." | |
81 | ``--format-table``,"Read tabular data from standard input (each line is a row and each | |
82 | whitespace separated field is a column), format the data as a table and | |
83 | print the resulting table to standard output. See also the ``--delimiter`` | |
84 | option." | |
85 | "``-d``, ``--delimiter=VALUE``","Change the delimiter used by ``--format-table`` to ``VALUE`` (a string). By default | |
86 | all whitespace is treated as a delimiter." | |
87 | "``-l``, ``--format-length=LENGTH``","Convert a length count (given as the integer or float ``LENGTH``) into a human | |
88 | readable string and print that string to standard output." | |
89 | "``-n``, ``--format-number=VALUE``","Format a number (given as the integer or floating point number ``VALUE``) with | |
90 | thousands separators and two decimal places (if needed) and print the | |
91 | formatted number to standard output." | |
92 | "``-s``, ``--format-size=BYTES``","Convert a byte count (given as the integer ``BYTES``) into a human readable | |
93 | string and print that string to standard output." | |
94 | "``-b``, ``--binary``","Change the output of ``-s``, ``--format-size`` to use binary multiples of bytes | |
95 | (base-2) instead of the default decimal multiples of bytes (base-10)." | |
96 | "``-t``, ``--format-timespan=SECONDS``","Convert a number of seconds (given as the floating point number ``SECONDS``) | |
97 | into a human readable timespan and print that string to standard output." | |
98 | ``--parse-length=VALUE``,"Parse a human readable length (given as the string ``VALUE``) and print the | |
99 | number of metres to standard output." | |
100 | ``--parse-size=VALUE``,"Parse a human readable data size (given as the string ``VALUE``) and print the | |
101 | number of bytes to standard output." | |
102 | ``--demo``,"Demonstrate changing the style and color of the terminal font using ANSI | |
103 | escape sequences." | |
104 | "``-h``, ``--help``",Show this message and exit. | |
105 | ||
106 | .. [[[end]]] | |
107 | ||
108 | A note about size units | |
109 | ----------------------- | |
110 | ||
111 | When I originally published the `humanfriendly` package I went with binary | |
112 | multiples of bytes (powers of two). It was pointed out several times that this | |
113 | was a poor choice (see issue `#4`_ and pull requests `#8`_ and `#9`_) and thus | |
114 | the new default became decimal multiples of bytes (powers of ten): | |
115 | ||
116 | +------+---------------+---------------+ | |
117 | | Unit | Binary value | Decimal value | | |
118 | +------+---------------+---------------+ | |
119 | | KB | 1024 | 1000 + | |
120 | +------+---------------+---------------+ | |
121 | | MB | 1048576 | 1000000 | | |
122 | +------+---------------+---------------+ | |
123 | | GB | 1073741824 | 1000000000 | | |
124 | +------+---------------+---------------+ | |
125 | | TB | 1099511627776 | 1000000000000 | | |
126 | +------+---------------+---------------+ | |
127 | | etc | | | | |
128 | +------+---------------+---------------+ | |
129 | ||
130 | The option to use binary multiples of bytes remains by passing the keyword | |
131 | argument `binary=True` to the `format_size()`_ and `parse_size()`_ functions. | |
132 | ||
133 | Windows support | |
134 | --------------- | |
135 | ||
136 | Windows 10 gained native support for ANSI escape sequences which means commands | |
137 | like ``humanfriendly --demo`` should work out of the box (if your system is | |
138 | up-to-date enough). If this doesn't work then you can install the colorama_ | |
139 | package, it will be used automatically once installed. | |
140 | ||
141 | Contact | |
142 | ------- | |
143 | ||
144 | The latest version of `humanfriendly` is available on PyPI_ and GitHub_. The | |
145 | documentation is hosted on `Read the Docs`_ and includes a changelog_. For bug | |
146 | reports please create an issue on GitHub_. If you have questions, suggestions, | |
147 | etc. feel free to send me an e-mail at `peter@peterodding.com`_. | |
148 | ||
149 | License | |
150 | ------- | |
151 | ||
152 | This software is licensed under the `MIT license`_. | |
153 | ||
154 | © 2020 Peter Odding. | |
155 | ||
156 | .. External references: | |
157 | .. _#4: https://github.com/xolox/python-humanfriendly/issues/4 | |
158 | .. _#8: https://github.com/xolox/python-humanfriendly/pull/8 | |
159 | .. _#9: https://github.com/xolox/python-humanfriendly/pull/9 | |
160 | .. _changelog: https://humanfriendly.readthedocs.io/en/latest/changelog.html | |
161 | .. _colorama: https://pypi.org/project/colorama | |
162 | .. _format_size(): https://humanfriendly.readthedocs.io/en/latest/#humanfriendly.format_size | |
163 | .. _GitHub: https://github.com/xolox/python-humanfriendly | |
164 | .. _MIT license: https://en.wikipedia.org/wiki/MIT_License | |
165 | .. _parse_size(): https://humanfriendly.readthedocs.io/en/latest/#humanfriendly.parse_size | |
166 | .. _peter@peterodding.com: peter@peterodding.com | |
167 | .. _PyPI: https://pypi.org/project/humanfriendly | |
168 | .. _Read the Docs: https://humanfriendly.readthedocs.io | |
169 | ||
170 | Platform: UNKNOWN | |
171 | Classifier: Development Status :: 6 - Mature | |
172 | Classifier: Environment :: Console | |
173 | Classifier: Framework :: Sphinx :: Extension | |
174 | Classifier: Intended Audience :: Developers | |
175 | Classifier: Intended Audience :: System Administrators | |
176 | Classifier: License :: OSI Approved :: MIT License | |
177 | Classifier: Natural Language :: English | |
178 | Classifier: Programming Language :: Python | |
179 | Classifier: Programming Language :: Python :: 2 | |
180 | Classifier: Programming Language :: Python :: 2.7 | |
181 | Classifier: Programming Language :: Python :: 3 | |
182 | Classifier: Programming Language :: Python :: 3.5 | |
183 | Classifier: Programming Language :: Python :: 3.6 | |
184 | Classifier: Programming Language :: Python :: 3.7 | |
185 | Classifier: Programming Language :: Python :: 3.8 | |
186 | Classifier: Programming Language :: Python :: Implementation :: CPython | |
187 | Classifier: Programming Language :: Python :: Implementation :: PyPy | |
188 | Classifier: Topic :: Communications | |
189 | Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces | |
190 | Classifier: Topic :: Software Development | |
191 | Classifier: Topic :: Software Development :: Libraries :: Python Modules | |
192 | Classifier: Topic :: Software Development :: User Interfaces | |
193 | Classifier: Topic :: System :: Shells | |
194 | Classifier: Topic :: System :: System Shells | |
195 | Classifier: Topic :: System :: Systems Administration | |
196 | Classifier: Topic :: Terminals | |
197 | Classifier: Topic :: Text Processing :: General | |
198 | Classifier: Topic :: Text Processing :: Linguistic | |
199 | Classifier: Topic :: Utilities | |
200 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* |
0 | CHANGELOG.rst | |
1 | LICENSE.txt | |
2 | MANIFEST.in | |
3 | README.rst | |
4 | requirements-checks.txt | |
5 | requirements-tests.txt | |
6 | requirements-travis.txt | |
7 | setup.cfg | |
8 | setup.py | |
9 | docs/api.rst | |
10 | docs/changelog.rst | |
11 | docs/conf.py | |
12 | docs/index.rst | |
13 | docs/readme.rst | |
14 | docs/images/ansi-demo.png | |
15 | docs/images/html-to-ansi.png | |
16 | docs/images/pretty-table.png | |
17 | docs/images/spinner-basic.gif | |
18 | docs/images/spinner-with-progress.gif | |
19 | docs/images/spinner-with-timer.gif | |
20 | humanfriendly/__init__.py | |
21 | humanfriendly/case.py | |
22 | humanfriendly/cli.py | |
23 | humanfriendly/compat.py | |
24 | humanfriendly/decorators.py | |
25 | humanfriendly/deprecation.py | |
26 | humanfriendly/prompts.py | |
27 | humanfriendly/sphinx.py | |
28 | humanfriendly/tables.py | |
29 | humanfriendly/testing.py | |
30 | humanfriendly/tests.py | |
31 | humanfriendly/text.py | |
32 | humanfriendly/usage.py | |
33 | humanfriendly.egg-info/PKG-INFO | |
34 | humanfriendly.egg-info/SOURCES.txt | |
35 | humanfriendly.egg-info/dependency_links.txt | |
36 | humanfriendly.egg-info/entry_points.txt | |
37 | humanfriendly.egg-info/requires.txt | |
38 | humanfriendly.egg-info/top_level.txt | |
39 | humanfriendly/terminal/__init__.py | |
40 | humanfriendly/terminal/html.py | |
41 | humanfriendly/terminal/spinners.py⏎ |
0 | #!/bin/bash -e | |
1 | ||
2 | # Even though Travis CI supports Mac OS X [1] and several Python interpreters | |
3 | # are installed out of the box, the Python environment cannot be configured in | |
4 | # the Travis CI build configuration [2]. | |
5 | # | |
6 | # As a workaround the build configuration file specifies a single Mac OS X job | |
7 | # with `language: generic' that runs this script to create and activate a | |
8 | # Python virtual environment. | |
9 | # | |
10 | # Recently the `virtualenv' command seems to no longer come pre-installed on | |
11 | # the MacOS workers of Travis CI [3] so when this situation is detected we | |
12 | # install it ourselves. | |
13 | # | |
14 | # [1] https://github.com/travis-ci/travis-ci/issues/216 | |
15 | # [2] https://github.com/travis-ci/travis-ci/issues/2312 | |
16 | # [3] https://travis-ci.org/xolox/python-humanfriendly/jobs/411396506 | |
17 | ||
18 | main () { | |
19 | if [ "$TRAVIS_OS_NAME" = osx ]; then | |
20 | local environment="$HOME/virtualenv/python2.7" | |
21 | if [ -x "$environment/bin/python" ]; then | |
22 | msg "Activating virtual environment ($environment) .." | |
23 | source "$environment/bin/activate" | |
24 | else | |
25 | if ! which virtualenv &>/dev/null; then | |
26 | msg "Installing 'virtualenv' in per-user site-packages .." | |
27 | pip install --user virtualenv | |
28 | msg "Figuring out 'bin' directory of per-user site-packages .." | |
29 | LOCAL_BINARIES=$(python -c 'import os, site; print(os.path.join(site.USER_BASE, "bin"))') | |
30 | msg "Prefixing '$LOCAL_BINARIES' to PATH .." | |
31 | export PATH="$LOCAL_BINARIES:$PATH" | |
32 | fi | |
33 | msg "Creating virtual environment ($environment) .." | |
34 | virtualenv "$environment" | |
35 | msg "Activating virtual environment ($environment) .." | |
36 | source "$environment/bin/activate" | |
37 | msg "Checking if 'pip' executable works .." | |
38 | if ! pip --version; then | |
39 | msg "Bootstrapping working 'pip' installation using get-pip.py .." | |
40 | curl -s https://bootstrap.pypa.io/get-pip.py | python - | |
41 | fi | |
42 | fi | |
43 | fi | |
44 | msg "Running command: $*" | |
45 | eval "$@" | |
46 | } | |
47 | ||
48 | msg () { | |
49 | echo "[travis.sh] $*" >&2 | |
50 | } | |
51 | ||
52 | main "$@" |
0 | # Enable building of universal wheels so we can publish wheel | |
1 | # distribution archives to PyPI (the Python package index) | |
2 | # that are compatible with Python 2 as well as Python 3. | |
3 | ||
0 | 4 | [wheel] |
1 | universal = 1 | |
2 | ||
3 | [egg_info] | |
4 | tag_build = | |
5 | tag_date = 0 | |
6 | ||
5 | universal=1 |
0 | # Tox (http://tox.testrun.org/) is a tool for running tests in multiple | |
1 | # virtualenvs. This configuration file will run the test suite on all supported | |
2 | # python versions. To use it, "pip install tox" and then run "tox" from this | |
3 | # directory. | |
4 | ||
5 | [tox] | |
6 | envlist = py27, py35, py36, py37, py38, py39, pypy | |
7 | ||
8 | [testenv] | |
9 | deps = -rrequirements-tests.txt | |
10 | commands = py.test {posargs} | |
11 | passenv = HOME | |
12 | ||
13 | [pytest] | |
14 | addopts = --verbose | |
15 | norecursedirs = .tox | |
16 | python_files = humanfriendly/tests.py | |
17 | ||
18 | [flake8] | |
19 | exclude = .tox | |
20 | extend-ignore = D200,D205,D211,D400,D401,D402,D412,D413,W504 | |
21 | max-line-length = 120 |