├── tests ├── __init__.py ├── base.py ├── test_false.py ├── test_true.py ├── test_tee.py ├── test_whoami.py ├── test_md5sum.py ├── test_sha224sum.py ├── test_sha256sum.py ├── test_sha384sum.py ├── test_basename.py ├── test_sha512sum.py ├── test_main.py ├── test_base64.py └── test_sha1sum.py ├── pycoreutils ├── vendor │ ├── __init__.py │ └── click │ │ ├── _textwrap.py │ │ ├── globals.py │ │ ├── _bashcomplete.py │ │ ├── __init__.py │ │ ├── _unicodefun.py │ │ ├── exceptions.py │ │ ├── _winconsole.py │ │ ├── formatting.py │ │ ├── decorators.py │ │ ├── testing.py │ │ ├── utils.py │ │ ├── parser.py │ │ ├── _termui_impl.py │ │ ├── types.py │ │ └── termui.py ├── version.py ├── commands │ ├── _false │ │ ├── __init__.py │ │ └── command.py │ ├── _tee │ │ ├── __init__.py │ │ └── command.py │ ├── _true │ │ ├── __init__.py │ │ └── command.py │ ├── _base64 │ │ ├── __init__.py │ │ └── command.py │ ├── _basename │ │ ├── __init__.py │ │ └── command.py │ ├── _md5sum │ │ ├── __init__.py │ │ └── command.py │ ├── _sha1sum │ │ ├── __init__.py │ │ └── command.py │ ├── _sha224sum │ │ ├── __init__.py │ │ └── command.py │ ├── _sha256sum │ │ ├── __init__.py │ │ └── command.py │ ├── _sha384sum │ │ ├── __init__.py │ │ └── command.py │ ├── _sha512sum │ │ ├── __init__.py │ │ └── command.py │ ├── _whoami │ │ ├── __init__.py │ │ └── command.py │ ├── __init__.py │ └── hasher.py ├── __init__.py ├── main.py └── utils.py ├── setup.cfg ├── docs ├── commands.rst ├── index.rst ├── contributing.rst ├── Makefile └── conf.py ├── AUTHORS.txt ├── scripts └── pycoreutils ├── tox.ini ├── .travis.yml ├── .editorconfig ├── README.rst ├── LICENSE.txt ├── .gitignore └── setup.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pycoreutils/vendor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pycoreutils/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0a1' 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_false/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_tee/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_true/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 160 3 | exclude = pycoreutils/vendor 4 | -------------------------------------------------------------------------------- /docs/commands.rst: -------------------------------------------------------------------------------- 1 | Commands 2 | ======== 3 | 4 | .. Include automatic documentation 5 | -------------------------------------------------------------------------------- /pycoreutils/commands/_base64/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_basename/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_md5sum/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha1sum/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha224sum/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha256sum/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha384sum/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha512sum/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /pycoreutils/commands/_whoami/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import subcommand # noqa 2 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Hans van Leeuwen 2 | David Fischer 3 | -------------------------------------------------------------------------------- /scripts/pycoreutils: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pycoreutils 3 | 4 | 5 | pycoreutils.cli() 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py{33,34,35},pypy 3 | skip_missing_interpreters=true 4 | 5 | [testenv] 6 | deps= 7 | nose 8 | flake8 9 | commands= 10 | flake8 pycoreutils tests 11 | nosetests 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | 9 | install: 10 | - pip install nose flake8 11 | 12 | script: 13 | - flake8 pycoreutils tests 14 | - nosetests 15 | -------------------------------------------------------------------------------- /pycoreutils/commands/_true/command.py: -------------------------------------------------------------------------------- 1 | from ...vendor import click 2 | 3 | 4 | @click.command( 5 | help='Exit with a successful status code', 6 | ) 7 | @click.help_option('-h', '--help') 8 | def subcommand(): 9 | pass 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.{py}] 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [Makefile] 13 | indent_style = tab 14 | -------------------------------------------------------------------------------- /pycoreutils/commands/_false/command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ...vendor import click 4 | 5 | 6 | @click.command( 7 | help='Exit with a failure status code', 8 | ) 9 | @click.help_option('-h', '--help') 10 | def subcommand(): 11 | sys.exit(1) 12 | -------------------------------------------------------------------------------- /pycoreutils/commands/__init__.py: -------------------------------------------------------------------------------- 1 | commands = [ 2 | 'base64', 3 | 'basename', 4 | 'false', 5 | 'sha1sum', 6 | 'sha224sum', 7 | 'sha256sum', 8 | 'sha384sum', 9 | 'sha512sum', 10 | 'tee', 11 | 'true', 12 | 'whoami', 13 | ] 14 | -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pycoreutils.main import cli 4 | from pycoreutils.vendor.click.testing import CliRunner 5 | 6 | 7 | class PycoreutilsBaseTest(unittest.TestCase): 8 | def setUp(self): 9 | self.runner = CliRunner() 10 | self.cli = cli 11 | -------------------------------------------------------------------------------- /tests/test_false.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestFalse(PycoreutilsBaseTest): 7 | def test_false(self): 8 | result = self.runner.invoke(self.cli, ['false']) 9 | self.assertEqual(result.exit_code, 1) 10 | -------------------------------------------------------------------------------- /tests/test_true.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestTrue(PycoreutilsBaseTest): 7 | def test_true(self): 8 | result = self.runner.invoke(self.cli, ['true']) 9 | self.assertEqual(result.exit_code, 0) 10 | -------------------------------------------------------------------------------- /pycoreutils/commands/_whoami/command.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | 3 | from ...vendor import click 4 | 5 | 6 | @click.command( 7 | help='Print the username of the current user', 8 | short_help='Print the current user', 9 | ) 10 | @click.help_option('-h', '--help') 11 | def subcommand(): 12 | click.echo(getpass.getuser()) 13 | -------------------------------------------------------------------------------- /tests/test_tee.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestTee(PycoreutilsBaseTest): 7 | def test_tee(self): 8 | result = self.runner.invoke(self.cli, ['tee'], input=b'test') 9 | self.assertEqual(result.exit_code, 0) 10 | self.assertEqual(result.output, 'test') 11 | -------------------------------------------------------------------------------- /pycoreutils/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | PyCoreutils is a pure Python implementation of standard UNIX commands 3 | similar to what is provided by the GNU Coreutils. It has no extrenal 4 | dependencies and supports all major platforms and Python versions 2.7 and 3.3+. 5 | 6 | Copyright (c) 2009 - 2016 Hans van Leeuwen, David Fischer & contributors 7 | See LICENSE.txt for details. 8 | ''' 9 | 10 | from .main import cli # noqa 11 | from .version import __version__ # noqa 12 | -------------------------------------------------------------------------------- /tests/test_whoami.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import os 4 | 5 | from .base import PycoreutilsBaseTest 6 | 7 | 8 | class TestWhoami(PycoreutilsBaseTest): 9 | def test_whoami(self): 10 | test_username = 'pycoreutils-user' 11 | os.environ['LOGNAME'] = test_username 12 | result = self.runner.invoke(self.cli, ['whoami']) 13 | self.assertEqual(result.exit_code, 0) 14 | self.assertEqual(result.output.strip(), test_username) 15 | -------------------------------------------------------------------------------- /tests/test_md5sum.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestMd5Sum(PycoreutilsBaseTest): 7 | """ 8 | Very little code is unique to the md5sum command 9 | Instead most of the actual tests are in `test_sha1sum.py` 10 | """ 11 | 12 | def test_md5_stdin(self): 13 | result = self.runner.invoke(self.cli, ['md5sum', '-'], input=b'test') 14 | self.assertEqual(result.exit_code, 0) 15 | self.assertEqual(result.output, '098f6bcd4621d373cade4e832627b4f6 -\n') 16 | -------------------------------------------------------------------------------- /tests/test_sha224sum.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestSha224Sum(PycoreutilsBaseTest): 7 | """ 8 | Very little code is unique to the sha224sum command 9 | Instead most of the actual tests are in `test_sha1sum.py` 10 | """ 11 | 12 | def test_224_stdin(self): 13 | result = self.runner.invoke(self.cli, ['sha224sum', '-'], input=b'test') 14 | self.assertEqual(result.exit_code, 0) 15 | self.assertEqual(result.output, '90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809 -\n') 16 | -------------------------------------------------------------------------------- /tests/test_sha256sum.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestSha256Sum(PycoreutilsBaseTest): 7 | """ 8 | Very little code is unique to the sha256sum command 9 | Instead most of the actual tests are in `test_sha1sum.py` 10 | """ 11 | 12 | def test_sha256_stdin(self): 13 | result = self.runner.invoke(self.cli, ['sha256sum', '-'], input=b'test') 14 | self.assertEqual(result.exit_code, 0) 15 | self.assertEqual(result.output, '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 -\n') 16 | -------------------------------------------------------------------------------- /tests/test_sha384sum.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestSha384Sum(PycoreutilsBaseTest): 7 | """ 8 | Very little code is unique to the sha384sum command 9 | Instead most of the actual tests are in `test_sha1sum.py` 10 | """ 11 | 12 | def test_sha384_stdin(self): 13 | result = self.runner.invoke(self.cli, ['sha384sum', '-'], input=b'test') 14 | self.assertEqual(result.exit_code, 0) 15 | self.assertEqual(result.output, '768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9 -\n') 16 | -------------------------------------------------------------------------------- /tests/test_basename.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | from pycoreutils.commands._basename.command import get_base_name 6 | 7 | 8 | class TestBaseName(PycoreutilsBaseTest): 9 | def test_get_base_name(self): 10 | pairs = ( 11 | ('/path/to/file.c', 'file.c', '', '/'), 12 | ('/path/to/file.c', 'file', '.c', '/'), 13 | ('', '', '.c', '/'), 14 | ('c:\\system32\\etc\\hosts.txt', 'hosts.txt', '', '\\'), 15 | ) 16 | for name, expected, suffix, sep in pairs: 17 | self.assertEqual(get_base_name(name, suffix, sep), expected) 18 | -------------------------------------------------------------------------------- /tests/test_sha512sum.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestSha512Sum(PycoreutilsBaseTest): 7 | """ 8 | Very little code is unique to the sha512sum command 9 | Instead most of the actual tests are in `test_sha1sum.py` 10 | """ 11 | 12 | def test_sha512_stdin(self): 13 | result = self.runner.invoke(self.cli, ['sha512sum', '-'], input=b'test') 14 | expected = 'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff -\n' 15 | self.assertEqual(result.exit_code, 0) 16 | self.assertEqual(result.output, expected) 17 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python Coreutils (PyCoreutils) 2 | ============================== 3 | 4 | PyCoreutils is a pure Python implementation of standard UNIX commands 5 | similar to what is provided by the `GNU Coreutils`_. 6 | 7 | .. _GNU Coreutils: https://www.gnu.org/software/coreutils/coreutils.html 8 | 9 | 10 | **NOTE:** this is under active development and is not ready for general 11 | consumption yet. Not all of the coreutils commands are implemented. 12 | 13 | 14 | Usage 15 | ----- 16 | 17 | .. code-block:: bash 18 | 19 | $ pycoreutils # Show a list of all commands 20 | $ pycoreutils whoami 21 | $ pycoreutils uniq --help 22 | 23 | 24 | 25 | Installation 26 | ------------ 27 | 28 | Coming soon to a PyPI near you... 29 | 30 | 31 | Documentation 32 | ------------- 33 | 34 | Coming soon to Read the Docs 35 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestMain(PycoreutilsBaseTest): 7 | def test_noargs(self): 8 | result = self.runner.invoke(self.cli, []) 9 | self.assertEqual(result.exit_code, 0) 10 | 11 | def test_version(self): 12 | result = self.runner.invoke(self.cli, ['--version']) 13 | self.assertEqual(result.exit_code, 0) 14 | # The executable name will be pycoreutils after deploying 15 | self.assertTrue(result.output.startswith('cli v'), result.output) 16 | 17 | def test_invalid_command(self): 18 | result = self.runner.invoke(self.cli, ['invalidsubcommand']) 19 | self.assertEqual(result.exit_code, 2) 20 | self.assertTrue('Error: No such command' in result.output, result.output) 21 | -------------------------------------------------------------------------------- /pycoreutils/commands/_tee/command.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | from ...vendor import click 4 | 5 | 6 | @click.command( 7 | help='Copy standard input to each FILE and to standard output.', 8 | ) 9 | @click.help_option('-h', '--help') 10 | @click.option('-a', '--append', is_flag=True, default=False, help='append to the given FILEs, do not overwrite') 11 | @click.argument('files', metavar='FILE', required=False, nargs=-1, type=click.Path()) 12 | def subcommand(append, files): 13 | stdin = click.get_binary_stream('stdin') 14 | 15 | if append: 16 | mode = 'ab' 17 | else: 18 | mode = 'wb' 19 | 20 | fds = [open(click.format_filename(f), mode) for f in files] 21 | 22 | for data in iter(functools.partial(stdin.read, 4096), b''): 23 | for fd in fds: 24 | fd.write(data) 25 | click.echo(data, nl=False) 26 | -------------------------------------------------------------------------------- /pycoreutils/commands/_md5sum/command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ..hasher import HasherCommand 4 | from ...vendor import click 5 | 6 | 7 | @click.command( 8 | help='Print or check MD5 checksums', 9 | ) 10 | @click.help_option('-h', '--help') 11 | @click.option('-c', '--check', is_flag=True, default=False, help='read MD5 sums from the FILEs and check them') 12 | @click.option('--tag', is_flag=True, default=False, help='Output a BSD-style checksum file') 13 | @click.option('--quiet', is_flag=True, default=False, help="don't print OK for each successfully verified file") 14 | @click.option('--status', is_flag=True, default=False, help="don't output anything, status code shows success") 15 | @click.argument('files', metavar='FILE', required=False, nargs=-1, type=click.File('rb')) 16 | def subcommand(check, tag, quiet, status, files): 17 | if len(files) == 0: 18 | files = (click.get_binary_stream('stdin'),) 19 | 20 | hasher = HasherCommand('md5', tag, quiet, status) 21 | success = hasher.process_files(files, check) 22 | 23 | if not success: 24 | sys.exit(1) 25 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha1sum/command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ..hasher import HasherCommand 4 | from ...vendor import click 5 | 6 | 7 | @click.command( 8 | help='Print or check SHA1 checksums', 9 | ) 10 | @click.help_option('-h', '--help') 11 | @click.option('-c', '--check', is_flag=True, default=False, help='read SHA1 sums from the FILEs and check them') 12 | @click.option('--tag', is_flag=True, default=False, help='Output a BSD-style checksum file') 13 | @click.option('--quiet', is_flag=True, default=False, help="don't print OK for each successfully verified file") 14 | @click.option('--status', is_flag=True, default=False, help="don't output anything, status code shows success") 15 | @click.argument('files', metavar='FILE', required=False, nargs=-1, type=click.File('rb')) 16 | def subcommand(check, tag, quiet, status, files): 17 | if len(files) == 0: 18 | files = (click.get_binary_stream('stdin'),) 19 | 20 | hasher = HasherCommand('sha1', tag, quiet, status) 21 | success = hasher.process_files(files, check) 22 | 23 | if not success: 24 | sys.exit(1) 25 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha224sum/command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ..hasher import HasherCommand 4 | from ...vendor import click 5 | 6 | 7 | @click.command( 8 | help='Print or check SHA224 checksums', 9 | ) 10 | @click.help_option('-h', '--help') 11 | @click.option('-c', '--check', is_flag=True, default=False, help='read SHA224 sums from the FILEs and check them') 12 | @click.option('--tag', is_flag=True, default=False, help='Output a BSD-style checksum file') 13 | @click.option('--quiet', is_flag=True, default=False, help="don't print OK for each successfully verified file") 14 | @click.option('--status', is_flag=True, default=False, help="don't output anything, status code shows success") 15 | @click.argument('files', metavar='FILE', required=False, nargs=-1, type=click.File('rb')) 16 | def subcommand(check, tag, quiet, status, files): 17 | if len(files) == 0: 18 | files = (click.get_binary_stream('stdin'),) 19 | 20 | hasher = HasherCommand('sha224', tag, quiet, status) 21 | success = hasher.process_files(files, check) 22 | 23 | if not success: 24 | sys.exit(1) 25 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha256sum/command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ..hasher import HasherCommand 4 | from ...vendor import click 5 | 6 | 7 | @click.command( 8 | help='Print or check SHA256 checksums', 9 | ) 10 | @click.help_option('-h', '--help') 11 | @click.option('-c', '--check', is_flag=True, default=False, help='read SHA256 sums from the FILEs and check them') 12 | @click.option('--tag', is_flag=True, default=False, help='Output a BSD-style checksum file') 13 | @click.option('--quiet', is_flag=True, default=False, help="don't print OK for each successfully verified file") 14 | @click.option('--status', is_flag=True, default=False, help="don't output anything, status code shows success") 15 | @click.argument('files', metavar='FILE', required=False, nargs=-1, type=click.File('rb')) 16 | def subcommand(check, tag, quiet, status, files): 17 | if len(files) == 0: 18 | files = (click.get_binary_stream('stdin'),) 19 | 20 | hasher = HasherCommand('sha256', tag, quiet, status) 21 | success = hasher.process_files(files, check) 22 | 23 | if not success: 24 | sys.exit(1) 25 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha384sum/command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ..hasher import HasherCommand 4 | from ...vendor import click 5 | 6 | 7 | @click.command( 8 | help='Print or check SHA384 checksums', 9 | ) 10 | @click.help_option('-h', '--help') 11 | @click.option('-c', '--check', is_flag=True, default=False, help='read SHA384 sums from the FILEs and check them') 12 | @click.option('--tag', is_flag=True, default=False, help='Output a BSD-style checksum file') 13 | @click.option('--quiet', is_flag=True, default=False, help="don't print OK for each successfully verified file") 14 | @click.option('--status', is_flag=True, default=False, help="don't output anything, status code shows success") 15 | @click.argument('files', metavar='FILE', required=False, nargs=-1, type=click.File('rb')) 16 | def subcommand(check, tag, quiet, status, files): 17 | if len(files) == 0: 18 | files = (click.get_binary_stream('stdin'),) 19 | 20 | hasher = HasherCommand('sha384', tag, quiet, status) 21 | success = hasher.process_files(files, check) 22 | 23 | if not success: 24 | sys.exit(1) 25 | -------------------------------------------------------------------------------- /pycoreutils/commands/_sha512sum/command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ..hasher import HasherCommand 4 | from ...vendor import click 5 | 6 | 7 | @click.command( 8 | help='Print or check SHA512 checksums', 9 | ) 10 | @click.help_option('-h', '--help') 11 | @click.option('-c', '--check', is_flag=True, default=False, help='read SHA512 sums from the FILEs and check them') 12 | @click.option('--tag', is_flag=True, default=False, help='Output a BSD-style checksum file') 13 | @click.option('--quiet', is_flag=True, default=False, help="don't print OK for each successfully verified file") 14 | @click.option('--status', is_flag=True, default=False, help="don't output anything, status code shows success") 15 | @click.argument('files', metavar='FILE', required=False, nargs=-1, type=click.File('rb')) 16 | def subcommand(check, tag, quiet, status, files): 17 | if len(files) == 0: 18 | files = (click.get_binary_stream('stdin'),) 19 | 20 | hasher = HasherCommand('sha512', tag, quiet, status) 21 | success = hasher.process_files(files, check) 22 | 23 | if not success: 24 | sys.exit(1) 25 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | PyCoreutils: Python Coreutils 2 | ============================= 3 | 4 | Release v\ |version|. 5 | 6 | PyCoreutils is a pure Python implementation of standard UNIX commands 7 | similar to what is provided by the `GNU Coreutils`_. It has no extrenal 8 | dependencies and supports all major platforms and Python versions 2.7 and 3.3+. 9 | 10 | .. _GNU Coreutils: https://www.gnu.org/software/coreutils/coreutils.html 11 | 12 | 13 | Installation 14 | ------------ 15 | 16 | Pycoreutils has no external dependencies outside of the standard library: 17 | 18 | :: 19 | 20 | $ pip install pycoreutils 21 | 22 | 23 | Using PyCoreutils 24 | ----------------- 25 | 26 | The package installs a script called `pycoreutils`:: 27 | 28 | $ pycoreutils # List all commands 29 | $ pycoreutils ls 30 | 31 | 32 | The module can also be run using directly from python:: 33 | 34 | $ python -m pycoreutils ls 35 | 36 | 37 | Further reading 38 | --------------- 39 | 40 | .. toctree:: 41 | :maxdepth: 1 42 | 43 | commands 44 | contributing 45 | 46 | Links 47 | ----- 48 | 49 | * `PyCoreutils on GitHub `_ 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 - 2016 Hans van Leeuwen, David Fischer & contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included 11 | in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 17 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 19 | OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Developing & contributing 2 | ========================= 3 | 4 | If you are interested in getting involved in PyCoreutils, read on! 5 | 6 | 7 | Testing 8 | ------- 9 | 10 | The codeline is well tested. To run the tests, use:: 11 | 12 | $ python setup.py test 13 | 14 | Ensure that any code is compatible with Python 2.7 and 3.3+ 15 | 16 | 17 | Code style 18 | ---------- 19 | 20 | The code follows PEP8 with exemptions specified in ``setup.cfg``:: 21 | 22 | $ flake8 pycoreutils tests 23 | 24 | 25 | Generating the documentation 26 | ---------------------------- 27 | 28 | To generate the html documentation in docs/, install Sphinx and use: 29 | 30 | :: 31 | 32 | $ make html 33 | 34 | 35 | Contributing 36 | ------------ 37 | 38 | When contributing code, you'll want to follow this checklist: 39 | 40 | #. Fork the repository on GitHub. 41 | #. Run the tests to confirm they all pass on your system 42 | #. Write tests that demonstrate your bug or feature. Ensure that they fail. 43 | #. Make your change. 44 | #. Run the entire test suite again, confirming that all tests pass including the ones you just added. 45 | #. Send a GitHub Pull Request 46 | 47 | Upon merging your pull request, your name will be added to the AUTHORS.txt file! 48 | -------------------------------------------------------------------------------- /pycoreutils/main.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | from .commands import commands 4 | from .vendor import click 5 | from .version import __version__ 6 | 7 | 8 | class PycoreutilsMulticommand(click.MultiCommand): 9 | def list_commands(self, ctx): 10 | return commands 11 | 12 | def get_command(self, ctx, name): 13 | try: 14 | mod = importlib.import_module(u'pycoreutils.commands._{}'.format(name)) 15 | if hasattr(mod, 'subcommand'): 16 | return getattr(mod, 'subcommand') 17 | except ImportError: 18 | pass 19 | 20 | return None 21 | 22 | 23 | @click.command( 24 | cls=PycoreutilsMulticommand, 25 | epilog='See "COMMAND -h" to read about a specific subcommand', 26 | short_help='%(prog)s [-h] COMMAND [args]', 27 | ) 28 | @click.help_option('-h', '--help') 29 | @click.version_option(__version__, '-v', '--version', message='%(prog)s v%(version)s') 30 | def cli(): 31 | ''' 32 | Coreutils in Pure Python 33 | 34 | \b 35 | ____ _ _ ___ _____ ____ ____ __ __ ____ ____ __ ___ 36 | ( _ \( \/ )/ __)( _ )( _ \( ___)( )( )(_ _)(_ _)( ) / __) 37 | )___/ \ /( (__ )(_)( ) / )__) )(__)( )( _)(_ )(__ \__ \\ 38 | (__) (__) \___)(_____)(_)\_)(____)(______) (__) (____)(____)(___/ 39 | ''' 40 | pass 41 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/_textwrap.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | from contextlib import contextmanager 3 | 4 | 5 | class TextWrapper(textwrap.TextWrapper): 6 | 7 | def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): 8 | space_left = max(width - cur_len, 1) 9 | 10 | if self.break_long_words: 11 | last = reversed_chunks[-1] 12 | cut = last[:space_left] 13 | res = last[space_left:] 14 | cur_line.append(cut) 15 | reversed_chunks[-1] = res 16 | elif not cur_line: 17 | cur_line.append(reversed_chunks.pop()) 18 | 19 | @contextmanager 20 | def extra_indent(self, indent): 21 | old_initial_indent = self.initial_indent 22 | old_subsequent_indent = self.subsequent_indent 23 | self.initial_indent += indent 24 | self.subsequent_indent += indent 25 | try: 26 | yield 27 | finally: 28 | self.initial_indent = old_initial_indent 29 | self.subsequent_indent = old_subsequent_indent 30 | 31 | def indent_only(self, text): 32 | rv = [] 33 | for idx, line in enumerate(text.splitlines()): 34 | indent = self.initial_indent 35 | if idx > 0: 36 | indent = self.subsequent_indent 37 | rv.append(indent + line) 38 | return '\n'.join(rv) 39 | -------------------------------------------------------------------------------- /pycoreutils/commands/_basename/command.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ...vendor import click 4 | 5 | 6 | @click.command( 7 | help='Print NAME with any leading directory components removed.', 8 | short_help='Remove leading directory from names', 9 | ) 10 | @click.help_option('-h', '--help') 11 | @click.option('-a', '--multiple', is_flag=True, help='For compatibility only') 12 | @click.option('-z', '--zero', is_flag=True, default=False, help='end each output line with NUL, not newline') 13 | @click.option('-s', '--suffix', metavar='SUFFIX', help='remove a trailing SUFFIX as well') 14 | @click.option('--separator', metavar='SEPARATOR', help='the directory separator [default: "{}"]'.format(os.sep), default=os.sep) 15 | @click.argument('names', metavar='NAME', nargs=-1) 16 | def subcommand(multiple, zero, suffix, separator, names): 17 | # This command differs from its GNU alternative in that -a is always assumed 18 | # --separator is non-standard as well but handy on Windows especially 19 | line_ending = '\n' 20 | if zero: 21 | line_ending = '\0' 22 | 23 | for n in names: 24 | click.echo(u'{}{}'.format(get_base_name(n, suffix, separator), line_ending), nl=False) 25 | 26 | 27 | def get_base_name(name, suffix, separator): 28 | base_name = name.split(separator)[-1] 29 | if suffix and base_name.endswith(suffix): 30 | base_name = base_name[:-len(suffix)] 31 | 32 | return base_name 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # Jupyter Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | .venv/ 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | -------------------------------------------------------------------------------- /tests/test_base64.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestBase64(PycoreutilsBaseTest): 7 | def test_base64_decode(self): 8 | encoded = b'SGF2ZSBhIGxvdCBvZiBmdW4uLi4K' 9 | result = self.runner.invoke(self.cli, ['base64', '-d'], input=encoded) 10 | self.assertEqual(result.exit_code, 0) 11 | self.assertEqual(result.output, 'Have a lot of fun...\n') 12 | 13 | def test_b64_long_decode(self): 14 | encoded = b'TG9M' * 4096 15 | result = self.runner.invoke(self.cli, ['base64', '-d'], input=encoded) 16 | self.assertEqual(result.exit_code, 0) 17 | self.assertEqual(result.output, 'LoL' * 4096) 18 | 19 | def test_base64_encode(self): 20 | decoded = b'Have a lot of fun...\n' 21 | result = self.runner.invoke(self.cli, ['base64', '-w5'], input=decoded) 22 | self.assertEqual(result.exit_code, 0) 23 | self.assertEqual(result.output, 'SGF2Z\nSBhIG\nxvdCB\nvZiBm\ndW4uL\ni4K\n') 24 | 25 | def test_base64_long_encode(self): 26 | decoded = b'LoL' * 4096 27 | result = self.runner.invoke(self.cli, ['base64', '-w0'], input=decoded) 28 | self.assertEqual(result.exit_code, 0) 29 | self.assertEqual(result.output, 'TG9M' * 4096) 30 | 31 | def test_invalid_b64(self): 32 | encoded = b'*234' 33 | result = self.runner.invoke(self.cli, ['base64', '-d'], input=encoded) 34 | self.assertEqual(result.exit_code, 1) 35 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/globals.py: -------------------------------------------------------------------------------- 1 | from threading import local 2 | 3 | 4 | _local = local() 5 | 6 | 7 | def get_current_context(silent=False): 8 | """Returns the current click context. This can be used as a way to 9 | access the current context object from anywhere. This is a more implicit 10 | alternative to the :func:`pass_context` decorator. This function is 11 | primarily useful for helpers such as :func:`echo` which might be 12 | interested in changing it's behavior based on the current context. 13 | 14 | To push the current context, :meth:`Context.scope` can be used. 15 | 16 | .. versionadded:: 5.0 17 | 18 | :param silent: is set to `True` the return value is `None` if no context 19 | is available. The default behavior is to raise a 20 | :exc:`RuntimeError`. 21 | """ 22 | try: 23 | return getattr(_local, 'stack')[-1] 24 | except (AttributeError, IndexError): 25 | if not silent: 26 | raise RuntimeError('There is no active click context.') 27 | 28 | 29 | def push_context(ctx): 30 | """Pushes a new context to the current stack.""" 31 | _local.__dict__.setdefault('stack', []).append(ctx) 32 | 33 | 34 | def pop_context(): 35 | """Removes the top level from the stack.""" 36 | _local.stack.pop() 37 | 38 | 39 | def resolve_color_default(color=None): 40 | """"Internal helper to get the default value of the color flag. If a 41 | value is passed it's returned unchanged, otherwise it's looked up from 42 | the current context. 43 | """ 44 | if color is not None: 45 | return color 46 | ctx = get_current_context(silent=True) 47 | if ctx is not None: 48 | return ctx.color 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) 2009, 2010, 2011 Hans van Leeuwen. 4 | # See LICENSE.txt for details. 5 | import re 6 | 7 | from codecs import open 8 | from setuptools import setup 9 | 10 | 11 | with open('pycoreutils/version.py', 'r', 'utf-8') as fd: 12 | version = re.search( 13 | r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 14 | fd.read(), 15 | re.MULTILINE, 16 | ).group(1) 17 | if not version: 18 | raise RuntimeError('Cannot find version information') 19 | 20 | with open('README.rst', 'r', 'utf-8') as f: 21 | readme = f.read() 22 | 23 | setup( 24 | name='pycoreutils', 25 | version=version, 26 | description='Coreutils in Pure Python', 27 | long_description=readme, 28 | classifiers=[ 29 | 'Development Status :: 3 - Alpha', 30 | 'Environment :: Console', 31 | 'Intended Audience :: Developers', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Programming Language :: Python :: 2', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.3', 37 | 'Programming Language :: Python :: 3.4', 38 | 'Programming Language :: Python :: 3.5', 39 | 'Topic :: System :: Shells', 40 | 'Topic :: Utilities', 41 | ], 42 | license='MIT', 43 | url='http://pypi.python.org/pypi/pycoreutils', 44 | author='Hans van Leeuwen', 45 | author_email='hansvl@gmail.com', 46 | entry_points=''' 47 | [console_scripts] 48 | pycoreutils=main:cli 49 | ''', 50 | packages=[ 51 | 'pycoreutils', 52 | 'pycoreutils.commands', 53 | ], 54 | tests_require=['nose'], 55 | test_suite='nose.collector', 56 | ) 57 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/_bashcomplete.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from .utils import echo 4 | from .parser import split_arg_string 5 | from .core import MultiCommand, Option 6 | 7 | 8 | COMPLETION_SCRIPT = ''' 9 | %(complete_func)s() { 10 | COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\ 11 | COMP_CWORD=$COMP_CWORD \\ 12 | %(autocomplete_var)s=complete $1 ) ) 13 | return 0 14 | } 15 | 16 | complete -F %(complete_func)s -o default %(script_names)s 17 | ''' 18 | 19 | _invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]') 20 | 21 | 22 | def get_completion_script(prog_name, complete_var): 23 | cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_')) 24 | return (COMPLETION_SCRIPT % { 25 | 'complete_func': '_%s_completion' % cf_name, 26 | 'script_names': prog_name, 27 | 'autocomplete_var': complete_var, 28 | }).strip() + ';' 29 | 30 | 31 | def resolve_ctx(cli, prog_name, args): 32 | ctx = cli.make_context(prog_name, args, resilient_parsing=True) 33 | while ctx.args + ctx.protected_args and isinstance(ctx.command, MultiCommand): 34 | a = ctx.args + ctx.protected_args 35 | cmd = ctx.command.get_command(ctx, a[0]) 36 | if cmd is None: 37 | return None 38 | ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True) 39 | return ctx 40 | 41 | 42 | def get_choices(cli, prog_name, args, incomplete): 43 | ctx = resolve_ctx(cli, prog_name, args) 44 | if ctx is None: 45 | return 46 | 47 | choices = [] 48 | if incomplete and not incomplete[:1].isalnum(): 49 | for param in ctx.command.params: 50 | if not isinstance(param, Option): 51 | continue 52 | choices.extend(param.opts) 53 | choices.extend(param.secondary_opts) 54 | elif isinstance(ctx.command, MultiCommand): 55 | choices.extend(ctx.command.list_commands(ctx)) 56 | 57 | for item in choices: 58 | if item.startswith(incomplete): 59 | yield item 60 | 61 | 62 | def do_complete(cli, prog_name): 63 | cwords = split_arg_string(os.environ['COMP_WORDS']) 64 | cword = int(os.environ['COMP_CWORD']) 65 | args = cwords[1:cword] 66 | try: 67 | incomplete = cwords[cword] 68 | except IndexError: 69 | incomplete = '' 70 | 71 | for item in get_choices(cli, prog_name, args, incomplete): 72 | echo(item) 73 | 74 | return True 75 | 76 | 77 | def bashcomplete(cli, prog_name, complete_var, complete_instr): 78 | if complete_instr == 'source': 79 | echo(get_completion_script(prog_name, complete_var)) 80 | return True 81 | elif complete_instr == 'complete': 82 | return do_complete(cli, prog_name) 83 | return False 84 | -------------------------------------------------------------------------------- /pycoreutils/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | import stat 4 | 5 | 6 | def getsignals(): 7 | ''' 8 | Return a dict of all available signals 9 | ''' 10 | signallist = [ 11 | 'ABRT', 'CONT', 'IO', 'PROF', 'SEGV', 'TSTP', 'USR2', '_DFL', 'ALRM', 12 | 'FPE', 'IOT', 'PWR', 'STOP', 'TTIN', 'VTALRM', '_IGN', 'BUS', 'HUP', 13 | 'KILL', 'QUIT', 'SYS', 'TTOU', 'WINCH', 'CHLD', 'ILL', 'PIPE', 'RTMAX', 14 | 'TERM', 'URG', 'XCPU', 'CLD', 'INT', 'POLL', 'RTMIN', 'TRAP', 'USR1', 15 | 'XFSZ', 16 | ] 17 | signals = {} 18 | for signame in signallist: 19 | if hasattr(signal, 'SIG' + signame): 20 | signals[signame] = getattr(signal, 'SIG' + signame) 21 | return signals 22 | 23 | 24 | def getuserhome(): 25 | ''' 26 | Returns the home-directory of the current user 27 | ''' 28 | if 'HOME' in os.environ: 29 | return os.environ['HOME'] # Unix 30 | if 'HOMEPATH' in os.environ: 31 | return os.environ['HOMEPATH'] # Windows 32 | 33 | 34 | def mode2string(mode): 35 | ''' 36 | Convert mode-integer to string 37 | 38 | >>> from pycoreutils.lib import mode2string 39 | >>> mode2string(33261) 40 | '-rwxr-xr-x' 41 | >>> mode2string(33024) 42 | '-r--------' 43 | ''' 44 | if stat.S_ISREG(mode): 45 | s = '-' 46 | elif stat.S_ISDIR(mode): 47 | s = 'd' 48 | elif stat.S_ISCHR(mode): 49 | s = 'c' 50 | elif stat.S_ISBLK(mode): 51 | s = 'b' 52 | elif stat.S_ISLNK(mode): 53 | s = 'l' 54 | elif stat.S_ISFIFO(mode): 55 | s = 'p' 56 | elif stat.S_ISSOCK(mode): 57 | s = 's' 58 | else: 59 | s = '-' 60 | 61 | # User Read 62 | if bool(mode & stat.S_IRUSR): 63 | s += 'r' 64 | else: 65 | s += '-' 66 | 67 | # User Write 68 | if bool(mode & stat.S_IWUSR): 69 | s += 'w' 70 | else: 71 | s += '-' 72 | 73 | # User Execute 74 | if bool(mode & stat.S_IXUSR): 75 | s += 'x' 76 | else: 77 | s += '-' 78 | 79 | # Group Read 80 | if bool(mode & stat.S_IRGRP): 81 | s += 'r' 82 | else: 83 | s += '-' 84 | 85 | # Group Write 86 | if bool(mode & stat.S_IWGRP): 87 | s += 'w' 88 | else: 89 | s += '-' 90 | 91 | # Group Execute 92 | if bool(mode & stat.S_IXGRP): 93 | s += 'x' 94 | else: 95 | s += '-' 96 | 97 | # Other Read 98 | if bool(mode & stat.S_IROTH): 99 | s += 'r' 100 | else: 101 | s += '-' 102 | 103 | # Other Write 104 | if bool(mode & stat.S_IWOTH): 105 | s += 'w' 106 | else: 107 | s += '-' 108 | 109 | # Other Execute 110 | if bool(mode & stat.S_IXOTH): 111 | s += 'x' 112 | else: 113 | s += '-' 114 | 115 | return s 116 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | click 4 | ~~~~~ 5 | 6 | Click is a simple Python module that wraps the stdlib's optparse to make 7 | writing command line scripts fun. Unlike other modules, it's based around 8 | a simple API that does not come with too much magic and is composable. 9 | 10 | In case optparse ever gets removed from the stdlib, it will be shipped by 11 | this module. 12 | 13 | :copyright: (c) 2014 by Armin Ronacher. 14 | :license: BSD, see LICENSE for more details. 15 | """ 16 | 17 | # Core classes 18 | from .core import Context, BaseCommand, Command, MultiCommand, Group, \ 19 | CommandCollection, Parameter, Option, Argument 20 | 21 | # Globals 22 | from .globals import get_current_context 23 | 24 | # Decorators 25 | from .decorators import pass_context, pass_obj, make_pass_decorator, \ 26 | command, group, argument, option, confirmation_option, \ 27 | password_option, version_option, help_option 28 | 29 | # Types 30 | from .types import ParamType, File, Path, Choice, IntRange, Tuple, \ 31 | STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED 32 | 33 | # Utilities 34 | from .utils import echo, get_binary_stream, get_text_stream, open_file, \ 35 | format_filename, get_app_dir, get_os_args 36 | 37 | # Terminal functions 38 | from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \ 39 | progressbar, clear, style, unstyle, secho, edit, launch, getchar, \ 40 | pause 41 | 42 | # Exceptions 43 | from .exceptions import ClickException, UsageError, BadParameter, \ 44 | FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \ 45 | MissingParameter 46 | 47 | # Formatting 48 | from .formatting import HelpFormatter, wrap_text 49 | 50 | # Parsing 51 | from .parser import OptionParser 52 | 53 | 54 | __all__ = [ 55 | # Core classes 56 | 'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group', 57 | 'CommandCollection', 'Parameter', 'Option', 'Argument', 58 | 59 | # Globals 60 | 'get_current_context', 61 | 62 | # Decorators 63 | 'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group', 64 | 'argument', 'option', 'confirmation_option', 'password_option', 65 | 'version_option', 'help_option', 66 | 67 | # Types 68 | 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING', 69 | 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', 70 | 71 | # Utilities 72 | 'echo', 'get_binary_stream', 'get_text_stream', 'open_file', 73 | 'format_filename', 'get_app_dir', 'get_os_args', 74 | 75 | # Terminal functions 76 | 'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager', 77 | 'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch', 78 | 'getchar', 'pause', 79 | 80 | # Exceptions 81 | 'ClickException', 'UsageError', 'BadParameter', 'FileError', 82 | 'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage', 83 | 'MissingParameter', 84 | 85 | # Formatting 86 | 'HelpFormatter', 'wrap_text', 87 | 88 | # Parsing 89 | 'OptionParser', 90 | ] 91 | 92 | 93 | # Controls if click should emit the warning about the use of unicode 94 | # literals. 95 | disable_unicode_literals_warning = False 96 | 97 | 98 | __version__ = '6.6' 99 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # Required to import pycoreutils for automatic documentation 5 | export PYTHONPATH=.. 6 | 7 | # You can set these variables from the command line. 8 | SPHINXOPTS = 9 | SPHINXBUILD = sphinx-build 10 | PAPER = 11 | BUILDDIR = _build 12 | 13 | # Internal variables. 14 | PAPEROPT_a4 = -D latex_paper_size=a4 15 | PAPEROPT_letter = -D latex_paper_size=letter 16 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 17 | 18 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 19 | 20 | help: 21 | @echo "Please use \`make ' where is one of" 22 | @echo " html to make standalone HTML files" 23 | @echo " dirhtml to make HTML files named index.html in directories" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " changes to make an overview of all changed/added/deprecated items" 30 | @echo " linkcheck to check all external links for integrity" 31 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 32 | 33 | clean: 34 | -rm -rf $(BUILDDIR)/* 35 | 36 | html: 37 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 38 | @echo 39 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 40 | 41 | dirhtml: 42 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 43 | @echo 44 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 45 | 46 | pickle: 47 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 48 | @echo 49 | @echo "Build finished; now you can process the pickle files." 50 | 51 | json: 52 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 53 | @echo 54 | @echo "Build finished; now you can process the JSON files." 55 | 56 | htmlhelp: 57 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 58 | @echo 59 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 60 | ".hhp project file in $(BUILDDIR)/htmlhelp." 61 | 62 | qthelp: 63 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 64 | @echo 65 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 66 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 67 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/RPC4Django.qhcp" 68 | @echo "To view the help file:" 69 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/RPC4Django.qhc" 70 | 71 | latex: 72 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 73 | @echo 74 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 75 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 76 | "run these through (pdf)latex." 77 | 78 | changes: 79 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 80 | @echo 81 | @echo "The overview file is in $(BUILDDIR)/changes." 82 | 83 | linkcheck: 84 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 85 | @echo 86 | @echo "Link check complete; look for any errors in the above output " \ 87 | "or in $(BUILDDIR)/linkcheck/output.txt." 88 | 89 | doctest: 90 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 91 | @echo "Testing of doctests in the sources finished, look at the " \ 92 | "results in $(BUILDDIR)/doctest/output.txt." 93 | -------------------------------------------------------------------------------- /pycoreutils/commands/_base64/command.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import binascii 3 | import functools 4 | import re 5 | import sys 6 | import textwrap 7 | 8 | from ...vendor import click 9 | 10 | 11 | COMMAND_NAME = 'base64' 12 | 13 | # Because every 3 bytes map to 4 base64 bytes, the buffer should be 14 | # a multiple of 3. Conveniently, this is also a multiple of 4 for decoding 15 | BUFSIZE = 3 * 1024 16 | 17 | 18 | @click.command( 19 | help='Base64 encode or decode FILE or standard input, to standard output', 20 | short_help='Base64 encode or decode input', 21 | ) 22 | @click.help_option('-h', '--help') 23 | @click.option('-d', '--decode', is_flag=True, default=False, help='decode data') 24 | @click.option('-w', '--wrap', metavar='COLS', default=76, help='wrap encoded lines after COLS character (default %(default)s). Use 0 to disable line wrapping') 25 | @click.argument('file', metavar='FILE', required=False, nargs=1, type=click.File('rb')) 26 | def subcommand(decode, wrap, file): 27 | if not file: 28 | file = click.get_binary_stream('stdin') 29 | 30 | if decode: 31 | decode_base64(file) 32 | else: 33 | encode_base64(file, wrap) 34 | 35 | 36 | def _validated_b64decode(b64_buffer): 37 | # Ensures the base64_buffer is valid b64 38 | if sys.version_info >= (3, 0): 39 | return base64.b64decode(b64_buffer, validate=True) 40 | else: 41 | if not re.match(b'^[A-Za-z0-9+/]*={0,2}$', b64_buffer): 42 | raise binascii.Error('Non-base64 digit found') 43 | return base64.b64decode(b64_buffer) 44 | 45 | 46 | def decode_base64(fd): 47 | """ 48 | Base64 decode the contents of a file 49 | """ 50 | inputbuffer = b'' 51 | 52 | for chunk in iter(functools.partial(fd.read, BUFSIZE), b''): 53 | inputbuffer += chunk.replace(b'\n', b'') 54 | 55 | while len(inputbuffer) > BUFSIZE: 56 | # Ensure we output a multiple of BUFSIZE b64 bytes 57 | b64_to_output = inputbuffer[:BUFSIZE] 58 | inputbuffer = inputbuffer[BUFSIZE:] 59 | 60 | # Validate the base64 bytes, convert them to binary 61 | # and output them 62 | try: 63 | click.echo(_validated_b64decode(b64_to_output), nl=False) 64 | except (binascii.Error, TypeError): 65 | click.echo('{}: invalid input'.format(COMMAND_NAME), err=True) 66 | sys.exit(1) 67 | 68 | if len(inputbuffer) > 0: 69 | # output any remaining bytes 70 | try: 71 | click.echo(_validated_b64decode(inputbuffer), nl=False) 72 | except (binascii.Error, TypeError): 73 | click.echo('{}: invalid input'.format(COMMAND_NAME), err=True) 74 | sys.exit(1) 75 | 76 | 77 | def encode_base64(fd, wrap): 78 | """ 79 | Base64 encode the contents of a file 80 | 81 | Wrap the results based on `wrap` number of columns 82 | """ 83 | outputbuffer = b'' 84 | 85 | for chunk in iter(functools.partial(fd.read, BUFSIZE), b''): 86 | outputbuffer += base64.b64encode(chunk) 87 | 88 | if wrap <= 0: 89 | click.echo(outputbuffer, nl=False) 90 | outputbuffer = b'' 91 | else: 92 | # Use as many bytes from the outputbuffer as possible while maintaining 93 | # that it is a multiple of `wrap`. 94 | num_bytes = wrap * int(len(outputbuffer) / wrap) 95 | text = outputbuffer[:num_bytes].decode('ascii') 96 | outputbuffer = outputbuffer[num_bytes:] 97 | 98 | # Wrap the results based on `wrap` 99 | for line in textwrap.wrap(text, width=wrap): 100 | click.echo(line.encode('ascii')) 101 | 102 | if len(outputbuffer) > 0: 103 | # print any remaining bytes 104 | click.echo(outputbuffer) 105 | -------------------------------------------------------------------------------- /tests/test_sha1sum.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .base import PycoreutilsBaseTest 4 | 5 | 6 | class TestSha1Sum(PycoreutilsBaseTest): 7 | def test_sha1_stdin(self): 8 | result = self.runner.invoke(self.cli, ['sha1sum', '-'], input=b'test') 9 | self.assertEqual(result.exit_code, 0) 10 | self.assertEqual(result.output, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 -\n') 11 | 12 | def test_sha1_file(self): 13 | with self.runner.isolated_filesystem(): 14 | with open('hello.txt', 'w') as f: 15 | f.write('test') 16 | 17 | result = self.runner.invoke(self.cli, ['sha1sum', 'hello.txt']) 18 | self.assertEqual(result.exit_code, 0) 19 | self.assertEqual(result.output, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 hello.txt\n') 20 | 21 | def test_sha1_bsd(self): 22 | with self.runner.isolated_filesystem(): 23 | with open('hello.txt', 'w') as f: 24 | f.write('test') 25 | 26 | result = self.runner.invoke(self.cli, ['sha1sum', '--tag', 'hello.txt']) 27 | self.assertEqual(result.exit_code, 0) 28 | self.assertEqual(result.output, 'SHA1 (hello.txt) = a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\n') 29 | 30 | def test_sha1_invalid_options(self): 31 | result = self.runner.invoke(self.cli, ['sha1sum', '--tag', '--check', '-'], input=b'') 32 | self.assertTrue(result.exit_code != 0) 33 | 34 | result = self.runner.invoke(self.cli, ['sha1sum', '--status', '-'], input=b'') 35 | self.assertTrue(result.exit_code != 0) 36 | 37 | result = self.runner.invoke(self.cli, ['sha1sum', '--quiet', '-'], input=b'') 38 | self.assertTrue(result.exit_code != 0) 39 | 40 | def test_sha1_check(self): 41 | expected_checksums = ( 42 | 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 hello.txt\n' 43 | 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 hello2.txt\n' 44 | ) 45 | 46 | with self.runner.isolated_filesystem(): 47 | with open('hello.txt', 'w') as f: 48 | f.write('test') 49 | 50 | with open('hello2.txt', 'w') as f: 51 | f.write('test') 52 | 53 | result = self.runner.invoke(self.cli, ['sha1sum', 'hello.txt', 'hello2.txt']) 54 | self.assertEqual(result.exit_code, 0) 55 | self.assertEqual(result.output, expected_checksums) 56 | 57 | with open('checksum.txt', 'w') as f: 58 | f.write(result.output) 59 | 60 | result = self.runner.invoke(self.cli, ['sha1sum', '--check', 'checksum.txt']) 61 | self.assertEqual(result.exit_code, 0) 62 | 63 | result = self.runner.invoke(self.cli, ['sha1sum', '--check', '--quiet', 'checksum.txt']) 64 | self.assertEqual(result.exit_code, 0) 65 | self.assertEqual(result.output, '') 66 | 67 | result = self.runner.invoke(self.cli, ['sha1sum', '--check', '--status', 'checksum.txt']) 68 | self.assertEqual(result.exit_code, 0) 69 | self.assertEqual(result.output, '') 70 | 71 | def test_sha1_check_fail(self): 72 | checksums = ( 73 | 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 hello.txt\n' 74 | 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 hello2.txt\n' 75 | ) 76 | 77 | with self.runner.isolated_filesystem(): 78 | with open('hello.txt', 'w') as f: 79 | f.write('test') 80 | 81 | with open('hello2.txt', 'w') as f: 82 | f.write('testtest') 83 | 84 | with open('checksum.txt', 'w') as f: 85 | f.write(checksums) 86 | 87 | result = self.runner.invoke(self.cli, ['sha1sum', '--check', 'checksum.txt']) 88 | self.assertTrue(result.exit_code != 0) 89 | self.assertTrue('hello2.txt: FAILED' in result.output) 90 | self.assertTrue('WARNING: 1 computed checksum did NOT match' in result.output) 91 | -------------------------------------------------------------------------------- /pycoreutils/commands/hasher.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import hashlib 3 | import re 4 | 5 | from ..vendor import click 6 | 7 | 8 | class HasherCommand(object): 9 | def __init__(self, algorithm, tag=False, quiet=False, status=False): 10 | """ 11 | :param algorithm: the hashing algorithm to use (eg. 'sha1', 'md5') 12 | """ 13 | self.algorithm = algorithm 14 | self.tag = tag 15 | self.quiet = quiet 16 | self.status = status 17 | 18 | hashlen = len(hashlib.new(algorithm).hexdigest()) 19 | self.checksum_line_regex = re.compile('^(?P[a-f0-9]{{{hashlen}}}) (?P.+)$'.format(hashlen=hashlen).encode('ascii')) 20 | 21 | def process_files(self, files, check=False): 22 | success = True 23 | 24 | if check and self.tag: 25 | raise click.BadOptionUsage('the --tag option is meaningless when verifying checksums') 26 | 27 | if not check and (self.status or self.quiet): 28 | raise click.BadOptionUsage('--status is only meaningful when verifying checksums') 29 | 30 | for file in files: 31 | if not check: 32 | # in testing, BytesIO has not attribute "name" 33 | filepath = getattr(file, 'name', '-') 34 | checksum = self.checksum_calculator(file) 35 | if self.tag: 36 | click.echo('{} ({}) = {}'.format(self.algorithm.upper(), filepath, checksum)) 37 | else: 38 | click.echo('{} {}'.format(checksum, filepath)) 39 | else: 40 | success = self.checksum_verifier(file) and success 41 | 42 | return success 43 | 44 | def checksum_calculator(self, file): 45 | """ 46 | Computes a checksum using the class' algorithm 47 | Returns a unicode of the hexdigest 48 | 49 | Assumes the file is opened in binary mode 50 | """ 51 | h = hashlib.new(self.algorithm) 52 | for data in iter(functools.partial(file.read, 4096), b''): 53 | h.update(data) 54 | return h.hexdigest() 55 | 56 | def checksum_verifier(self, file): 57 | noformat_count = 0 58 | nomatch_count = 0 59 | noread_count = 0 60 | 61 | for line in file: 62 | match = self.checksum_line_regex.match(line) 63 | if not match: 64 | noformat_count += 1 65 | else: 66 | group = match.groupdict() 67 | checksum = group['checksum'] 68 | filepath = click.format_filename(group['filepath']) 69 | try: 70 | with click.open_file(filepath, 'rb') as fd: 71 | calculated_checksum = self.checksum_calculator(fd).encode('ascii') 72 | except IOError: 73 | noread_count += 1 74 | if not self.status: 75 | click.echo('{}: FAILED open or read'.format(filepath)) 76 | continue 77 | 78 | if checksum == calculated_checksum: 79 | output = 'OK' 80 | else: 81 | output = 'FAILED' 82 | nomatch_count += 1 83 | 84 | if not self.status: 85 | if not self.quiet or output != 'OK': 86 | click.echo('{}: {}'.format(filepath, output)) 87 | 88 | if not self.status: 89 | if noformat_count == 1: 90 | click.echo('WARNING: 1 line is improperly formatted', err=True) 91 | if noformat_count > 1: 92 | click.echo('WARNING: {} lines are improperly formatted'.format(noformat_count), err=True) 93 | if noread_count == 1: 94 | click.echo('WARNING: 1 listed file could not be read', err=True) 95 | if noread_count > 1: 96 | click.echo('WARNING: {} listed files could not be read'.format(noread_count), err=True) 97 | if nomatch_count == 1: 98 | click.echo('WARNING: 1 computed checksum did NOT match', err=True) 99 | if nomatch_count > 1: 100 | click.echo('WARNING: {} computed checksum did NOT match'.format(nomatch_count), err=True) 101 | 102 | return noformat_count + nomatch_count + noread_count == 0 103 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/_unicodefun.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import codecs 4 | 5 | from ._compat import PY2 6 | 7 | 8 | # If someone wants to vendor click, we want to ensure the 9 | # correct package is discovered. Ideally we could use a 10 | # relative import here but unfortunately Python does not 11 | # support that. 12 | click = sys.modules[__name__.rsplit('.', 1)[0]] 13 | 14 | 15 | def _find_unicode_literals_frame(): 16 | import __future__ 17 | frm = sys._getframe(1) 18 | idx = 1 19 | while frm is not None: 20 | if frm.f_globals.get('__name__', '').startswith('click.'): 21 | frm = frm.f_back 22 | idx += 1 23 | elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: 24 | return idx 25 | else: 26 | break 27 | return 0 28 | 29 | 30 | def _check_for_unicode_literals(): 31 | if not __debug__: 32 | return 33 | if not PY2 or click.disable_unicode_literals_warning: 34 | return 35 | bad_frame = _find_unicode_literals_frame() 36 | if bad_frame <= 0: 37 | return 38 | from warnings import warn 39 | warn(Warning('Click detected the use of the unicode_literals ' 40 | '__future__ import. This is heavily discouraged ' 41 | 'because it can introduce subtle bugs in your ' 42 | 'code. You should instead use explicit u"" literals ' 43 | 'for your unicode strings. For more information see ' 44 | 'http://click.pocoo.org/python3/'), 45 | stacklevel=bad_frame) 46 | 47 | 48 | def _verify_python3_env(): 49 | """Ensures that the environment is good for unicode on Python 3.""" 50 | if PY2: 51 | return 52 | try: 53 | import locale 54 | fs_enc = codecs.lookup(locale.getpreferredencoding()).name 55 | except Exception: 56 | fs_enc = 'ascii' 57 | if fs_enc != 'ascii': 58 | return 59 | 60 | extra = '' 61 | if os.name == 'posix': 62 | import subprocess 63 | rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, 64 | stderr=subprocess.PIPE).communicate()[0] 65 | good_locales = set() 66 | has_c_utf8 = False 67 | 68 | # Make sure we're operating on text here. 69 | if isinstance(rv, bytes): 70 | rv = rv.decode('ascii', 'replace') 71 | 72 | for line in rv.splitlines(): 73 | locale = line.strip() 74 | if locale.lower().endswith(('.utf-8', '.utf8')): 75 | good_locales.add(locale) 76 | if locale.lower() in ('c.utf8', 'c.utf-8'): 77 | has_c_utf8 = True 78 | 79 | extra += '\n\n' 80 | if not good_locales: 81 | extra += ( 82 | 'Additional information: on this system no suitable UTF-8\n' 83 | 'locales were discovered. This most likely requires resolving\n' 84 | 'by reconfiguring the locale system.' 85 | ) 86 | elif has_c_utf8: 87 | extra += ( 88 | 'This system supports the C.UTF-8 locale which is recommended.\n' 89 | 'You might be able to resolve your issue by exporting the\n' 90 | 'following environment variables:\n\n' 91 | ' export LC_ALL=C.UTF-8\n' 92 | ' export LANG=C.UTF-8' 93 | ) 94 | else: 95 | extra += ( 96 | 'This system lists a couple of UTF-8 supporting locales that\n' 97 | 'you can pick from. The following suitable locales where\n' 98 | 'discovered: %s' 99 | ) % ', '.join(sorted(good_locales)) 100 | 101 | bad_locale = None 102 | for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'): 103 | if locale and locale.lower().endswith(('.utf-8', '.utf8')): 104 | bad_locale = locale 105 | if locale is not None: 106 | break 107 | if bad_locale is not None: 108 | extra += ( 109 | '\n\nClick discovered that you exported a UTF-8 locale\n' 110 | 'but the locale system could not pick up from it because\n' 111 | 'it does not exist. The exported locale is "%s" but it\n' 112 | 'is not supported' 113 | ) % bad_locale 114 | 115 | raise RuntimeError('Click will abort further execution because Python 3 ' 116 | 'was configured to use ASCII as encoding for the ' 117 | 'environment. Either run this under Python 2 or ' 118 | 'consult http://click.pocoo.org/python3/ for ' 119 | 'mitigation steps.' + extra) 120 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/exceptions.py: -------------------------------------------------------------------------------- 1 | from ._compat import PY2, filename_to_ui, get_text_stderr 2 | from .utils import echo 3 | 4 | 5 | class ClickException(Exception): 6 | """An exception that Click can handle and show to the user.""" 7 | 8 | #: The exit code for this exception 9 | exit_code = 1 10 | 11 | def __init__(self, message): 12 | if PY2: 13 | if message is not None: 14 | message = message.encode('utf-8') 15 | Exception.__init__(self, message) 16 | self.message = message 17 | 18 | def format_message(self): 19 | return self.message 20 | 21 | def show(self, file=None): 22 | if file is None: 23 | file = get_text_stderr() 24 | echo('Error: %s' % self.format_message(), file=file) 25 | 26 | 27 | class UsageError(ClickException): 28 | """An internal exception that signals a usage error. This typically 29 | aborts any further handling. 30 | 31 | :param message: the error message to display. 32 | :param ctx: optionally the context that caused this error. Click will 33 | fill in the context automatically in some situations. 34 | """ 35 | exit_code = 2 36 | 37 | def __init__(self, message, ctx=None): 38 | ClickException.__init__(self, message) 39 | self.ctx = ctx 40 | 41 | def show(self, file=None): 42 | if file is None: 43 | file = get_text_stderr() 44 | color = None 45 | if self.ctx is not None: 46 | color = self.ctx.color 47 | echo(self.ctx.get_usage() + '\n', file=file, color=color) 48 | echo('Error: %s' % self.format_message(), file=file, color=color) 49 | 50 | 51 | class BadParameter(UsageError): 52 | """An exception that formats out a standardized error message for a 53 | bad parameter. This is useful when thrown from a callback or type as 54 | Click will attach contextual information to it (for instance, which 55 | parameter it is). 56 | 57 | .. versionadded:: 2.0 58 | 59 | :param param: the parameter object that caused this error. This can 60 | be left out, and Click will attach this info itself 61 | if possible. 62 | :param param_hint: a string that shows up as parameter name. This 63 | can be used as alternative to `param` in cases 64 | where custom validation should happen. If it is 65 | a string it's used as such, if it's a list then 66 | each item is quoted and separated. 67 | """ 68 | 69 | def __init__(self, message, ctx=None, param=None, 70 | param_hint=None): 71 | UsageError.__init__(self, message, ctx) 72 | self.param = param 73 | self.param_hint = param_hint 74 | 75 | def format_message(self): 76 | if self.param_hint is not None: 77 | param_hint = self.param_hint 78 | elif self.param is not None: 79 | param_hint = self.param.opts or [self.param.human_readable_name] 80 | else: 81 | return 'Invalid value: %s' % self.message 82 | if isinstance(param_hint, (tuple, list)): 83 | param_hint = ' / '.join('"%s"' % x for x in param_hint) 84 | return 'Invalid value for %s: %s' % (param_hint, self.message) 85 | 86 | 87 | class MissingParameter(BadParameter): 88 | """Raised if click required an option or argument but it was not 89 | provided when invoking the script. 90 | 91 | .. versionadded:: 4.0 92 | 93 | :param param_type: a string that indicates the type of the parameter. 94 | The default is to inherit the parameter type from 95 | the given `param`. Valid values are ``'parameter'``, 96 | ``'option'`` or ``'argument'``. 97 | """ 98 | 99 | def __init__(self, message=None, ctx=None, param=None, 100 | param_hint=None, param_type=None): 101 | BadParameter.__init__(self, message, ctx, param, param_hint) 102 | self.param_type = param_type 103 | 104 | def format_message(self): 105 | if self.param_hint is not None: 106 | param_hint = self.param_hint 107 | elif self.param is not None: 108 | param_hint = self.param.opts or [self.param.human_readable_name] 109 | else: 110 | param_hint = None 111 | if isinstance(param_hint, (tuple, list)): 112 | param_hint = ' / '.join('"%s"' % x for x in param_hint) 113 | 114 | param_type = self.param_type 115 | if param_type is None and self.param is not None: 116 | param_type = self.param.param_type_name 117 | 118 | msg = self.message 119 | if self.param is not None: 120 | msg_extra = self.param.type.get_missing_message(self.param) 121 | if msg_extra: 122 | if msg: 123 | msg += '. ' + msg_extra 124 | else: 125 | msg = msg_extra 126 | 127 | return 'Missing %s%s%s%s' % ( 128 | param_type, 129 | param_hint and ' %s' % param_hint or '', 130 | msg and '. ' or '.', 131 | msg or '', 132 | ) 133 | 134 | 135 | class NoSuchOption(UsageError): 136 | """Raised if click attempted to handle an option that does not 137 | exist. 138 | 139 | .. versionadded:: 4.0 140 | """ 141 | 142 | def __init__(self, option_name, message=None, possibilities=None, 143 | ctx=None): 144 | if message is None: 145 | message = 'no such option: %s' % option_name 146 | UsageError.__init__(self, message, ctx) 147 | self.option_name = option_name 148 | self.possibilities = possibilities 149 | 150 | def format_message(self): 151 | bits = [self.message] 152 | if self.possibilities: 153 | if len(self.possibilities) == 1: 154 | bits.append('Did you mean %s?' % self.possibilities[0]) 155 | else: 156 | possibilities = sorted(self.possibilities) 157 | bits.append('(Possible options: %s)' % ', '.join(possibilities)) 158 | return ' '.join(bits) 159 | 160 | 161 | class BadOptionUsage(UsageError): 162 | """Raised if an option is generally supplied but the use of the option 163 | was incorrect. This is for instance raised if the number of arguments 164 | for an option is not correct. 165 | 166 | .. versionadded:: 4.0 167 | """ 168 | 169 | def __init__(self, message, ctx=None): 170 | UsageError.__init__(self, message, ctx) 171 | 172 | 173 | class BadArgumentUsage(UsageError): 174 | """Raised if an argument is generally supplied but the use of the argument 175 | was incorrect. This is for instance raised if the number of values 176 | for an argument is not correct. 177 | 178 | .. versionadded:: 6.0 179 | """ 180 | 181 | def __init__(self, message, ctx=None): 182 | UsageError.__init__(self, message, ctx) 183 | 184 | 185 | class FileError(ClickException): 186 | """Raised if a file cannot be opened.""" 187 | 188 | def __init__(self, filename, hint=None): 189 | ui_filename = filename_to_ui(filename) 190 | if hint is None: 191 | hint = 'unknown error' 192 | ClickException.__init__(self, hint) 193 | self.ui_filename = ui_filename 194 | self.filename = filename 195 | 196 | def format_message(self): 197 | return 'Could not open file %s: %s' % (self.ui_filename, self.message) 198 | 199 | 200 | class Abort(RuntimeError): 201 | """An internal signalling exception that signals Click to abort.""" 202 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PyCoreutils documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Nov 7 19:13:38 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | import pycoreutils 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | #sys.path.insert(0, os.path.abspath('.')) 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | #needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ['_templates'] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | #source_encoding = 'utf-8-sig' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = u'PyCoreutils' 45 | copyright = u'2009-2016 {} contributors'.format(project) 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | # The short X.Y version. 52 | version = '.'.join(pycoreutils.__version__.split('.')[:2]) 53 | # The full version, including alpha/beta/rc tags. 54 | release = pycoreutils.__version__ 55 | 56 | # The language for content autogenerated by Sphinx. Refer to documentation 57 | # for a list of supported languages. 58 | #language = None 59 | 60 | # There are two options for replacing |today|: either, you set today to some 61 | # non-false value, then it is used: 62 | #today = '' 63 | # Else, today_fmt is used as the format for a strftime call. 64 | #today_fmt = '%B %d, %Y' 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | exclude_patterns = ['_build'] 69 | 70 | # The reST default role (used for this markup: `text`) to use for all documents. 71 | #default_role = None 72 | 73 | # If true, '()' will be appended to :func: etc. cross-reference text. 74 | #add_function_parentheses = True 75 | 76 | # If true, the current module name will be prepended to all description 77 | # unit titles (such as .. function::). 78 | #add_module_names = True 79 | 80 | # If true, sectionauthor and moduleauthor directives will be shown in the 81 | # output. They are ignored by default. 82 | #show_authors = False 83 | 84 | # The name of the Pygments (syntax highlighting) style to use. 85 | pygments_style = 'sphinx' 86 | 87 | # A list of ignored prefixes for module index sorting. 88 | #modindex_common_prefix = [] 89 | 90 | 91 | # -- Options for HTML output --------------------------------------------------- 92 | 93 | # The theme to use for HTML and HTML Help pages. See the documentation for 94 | # a list of builtin themes. 95 | html_theme = 'haiku' 96 | 97 | # Theme options are theme-specific and customize the look and feel of a theme 98 | # further. For a list of options available for each theme, see the 99 | # documentation. 100 | #html_theme_options = {} 101 | 102 | # Add any paths that contain custom themes here, relative to this directory. 103 | #html_theme_path = [] 104 | 105 | # The name for this set of Sphinx documents. If None, it defaults to 106 | # " v documentation". 107 | #html_title = None 108 | 109 | # A shorter title for the navigation bar. Default is the same as html_title. 110 | #html_short_title = None 111 | 112 | # The name of an image file (relative to this directory) to place at the top 113 | # of the sidebar. 114 | #html_logo = None 115 | 116 | # The name of an image file (within the static path) to use as favicon of the 117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 118 | # pixels large. 119 | #html_favicon = 'favicon.ico' 120 | 121 | # Add any paths that contain custom static files (such as style sheets) here, 122 | # relative to this directory. They are copied after the builtin static files, 123 | # so a file named "default.css" will overwrite the builtin "default.css". 124 | html_static_path = ['_static'] 125 | 126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 127 | # using the given strftime format. 128 | #html_last_updated_fmt = '%b %d, %Y' 129 | 130 | # If true, SmartyPants will be used to convert quotes and dashes to 131 | # typographically correct entities. 132 | #html_use_smartypants = True 133 | 134 | # Custom sidebar templates, maps document names to template names. 135 | #html_sidebars = {} 136 | 137 | # Additional templates that should be rendered to pages, maps page names to 138 | # template names. 139 | #html_additional_pages = {} 140 | 141 | # If false, no module index is generated. 142 | #html_domain_indices = True 143 | 144 | # If false, no index is generated. 145 | #html_use_index = True 146 | 147 | # If true, the index is split into individual pages for each letter. 148 | #html_split_index = False 149 | 150 | # If true, links to the reST sources are added to the pages. 151 | html_show_sourcelink = True 152 | 153 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 154 | #html_show_sphinx = True 155 | 156 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 157 | #html_show_copyright = True 158 | 159 | # If true, an OpenSearch description file will be output, and all pages will 160 | # contain a tag referring to it. The value of this option must be the 161 | # base URL from which the finished HTML is served. 162 | #html_use_opensearch = '' 163 | 164 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 165 | #html_file_suffix = None 166 | 167 | # Output file base name for HTML help builder. 168 | htmlhelp_basename = 'PyCoreutilsdoc' 169 | 170 | 171 | # -- Options for LaTeX output -------------------------------------------------- 172 | 173 | # The paper size ('letter' or 'a4'). 174 | #latex_paper_size = 'letter' 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #latex_font_size = '10pt' 178 | 179 | # Grouping the document tree into LaTeX files. List of tuples 180 | # (source start file, target name, title, author, documentclass [howto/manual]). 181 | latex_documents = [ 182 | ('index', 'PyCoreutils.tex', u'PyCoreutils Documentation', 183 | u'Hans van Leeuwen', 'manual'), 184 | ] 185 | 186 | # The name of an image file (relative to this directory) to place at the top of 187 | # the title page. 188 | #latex_logo = None 189 | 190 | # For "manual" documents, if this is true, then toplevel headings are parts, 191 | # not chapters. 192 | #latex_use_parts = False 193 | 194 | # If true, show page references after internal links. 195 | #latex_show_pagerefs = False 196 | 197 | # If true, show URL addresses after external links. 198 | #latex_show_urls = False 199 | 200 | # Additional stuff for the LaTeX preamble. 201 | #latex_preamble = '' 202 | 203 | # Documents to append as an appendix to all manuals. 204 | #latex_appendices = [] 205 | 206 | # If false, no module index is generated. 207 | #latex_domain_indices = True 208 | 209 | 210 | # -- Options for manual page output -------------------------------------------- 211 | 212 | # One entry per manual page. List of tuples 213 | # (source start file, name, description, authors, manual section). 214 | man_pages = [ 215 | ('index', 'pycoreutils', u'PyCoreutils Documentation', 216 | [u'Hans van Leeuwen'], 1) 217 | ] 218 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/_winconsole.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This module is based on the excellent work by Adam Bartoš who 3 | # provided a lot of what went into the implementation here in 4 | # the discussion to issue1602 in the Python bug tracker. 5 | # 6 | # There are some general differences in regards to how this works 7 | # compared to the original patches as we do not need to patch 8 | # the entire interpreter but just work in our little world of 9 | # echo and prmopt. 10 | 11 | import io 12 | import os 13 | import sys 14 | import zlib 15 | import time 16 | import ctypes 17 | import msvcrt 18 | from click._compat import _NonClosingTextIOWrapper, text_type, PY2 19 | from ctypes import byref, POINTER, c_int, c_char, c_char_p, \ 20 | c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE 21 | try: 22 | from ctypes import pythonapi 23 | PyObject_GetBuffer = pythonapi.PyObject_GetBuffer 24 | PyBuffer_Release = pythonapi.PyBuffer_Release 25 | except ImportError: 26 | pythonapi = None 27 | from ctypes.wintypes import LPWSTR, LPCWSTR 28 | 29 | 30 | c_ssize_p = POINTER(c_ssize_t) 31 | 32 | kernel32 = windll.kernel32 33 | GetStdHandle = kernel32.GetStdHandle 34 | ReadConsoleW = kernel32.ReadConsoleW 35 | WriteConsoleW = kernel32.WriteConsoleW 36 | GetLastError = kernel32.GetLastError 37 | GetCommandLineW = WINFUNCTYPE(LPWSTR)( 38 | ('GetCommandLineW', windll.kernel32)) 39 | CommandLineToArgvW = WINFUNCTYPE( 40 | POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( 41 | ('CommandLineToArgvW', windll.shell32)) 42 | 43 | 44 | STDIN_HANDLE = GetStdHandle(-10) 45 | STDOUT_HANDLE = GetStdHandle(-11) 46 | STDERR_HANDLE = GetStdHandle(-12) 47 | 48 | 49 | PyBUF_SIMPLE = 0 50 | PyBUF_WRITABLE = 1 51 | 52 | ERROR_SUCCESS = 0 53 | ERROR_NOT_ENOUGH_MEMORY = 8 54 | ERROR_OPERATION_ABORTED = 995 55 | 56 | STDIN_FILENO = 0 57 | STDOUT_FILENO = 1 58 | STDERR_FILENO = 2 59 | 60 | EOF = b'\x1a' 61 | MAX_BYTES_WRITTEN = 32767 62 | 63 | 64 | class Py_buffer(ctypes.Structure): 65 | _fields_ = [ 66 | ('buf', c_void_p), 67 | ('obj', py_object), 68 | ('len', c_ssize_t), 69 | ('itemsize', c_ssize_t), 70 | ('readonly', c_int), 71 | ('ndim', c_int), 72 | ('format', c_char_p), 73 | ('shape', c_ssize_p), 74 | ('strides', c_ssize_p), 75 | ('suboffsets', c_ssize_p), 76 | ('internal', c_void_p) 77 | ] 78 | 79 | if PY2: 80 | _fields_.insert(-1, ('smalltable', c_ssize_t * 2)) 81 | 82 | 83 | # On PyPy we cannot get buffers so our ability to operate here is 84 | # serverly limited. 85 | if pythonapi is None: 86 | get_buffer = None 87 | else: 88 | def get_buffer(obj, writable=False): 89 | buf = Py_buffer() 90 | flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE 91 | PyObject_GetBuffer(py_object(obj), byref(buf), flags) 92 | try: 93 | buffer_type = c_char * buf.len 94 | return buffer_type.from_address(buf.buf) 95 | finally: 96 | PyBuffer_Release(byref(buf)) 97 | 98 | 99 | class _WindowsConsoleRawIOBase(io.RawIOBase): 100 | 101 | def __init__(self, handle): 102 | self.handle = handle 103 | 104 | def isatty(self): 105 | io.RawIOBase.isatty(self) 106 | return True 107 | 108 | 109 | class _WindowsConsoleReader(_WindowsConsoleRawIOBase): 110 | 111 | def readable(self): 112 | return True 113 | 114 | def readinto(self, b): 115 | bytes_to_be_read = len(b) 116 | if not bytes_to_be_read: 117 | return 0 118 | elif bytes_to_be_read % 2: 119 | raise ValueError('cannot read odd number of bytes from ' 120 | 'UTF-16-LE encoded console') 121 | 122 | buffer = get_buffer(b, writable=True) 123 | code_units_to_be_read = bytes_to_be_read // 2 124 | code_units_read = c_ulong() 125 | 126 | rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read, 127 | byref(code_units_read), None) 128 | if GetLastError() == ERROR_OPERATION_ABORTED: 129 | # wait for KeyboardInterrupt 130 | time.sleep(0.1) 131 | if not rv: 132 | raise OSError('Windows error: %s' % GetLastError()) 133 | 134 | if buffer[0] == EOF: 135 | return 0 136 | return 2 * code_units_read.value 137 | 138 | 139 | class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): 140 | 141 | def writable(self): 142 | return True 143 | 144 | @staticmethod 145 | def _get_error_message(errno): 146 | if errno == ERROR_SUCCESS: 147 | return 'ERROR_SUCCESS' 148 | elif errno == ERROR_NOT_ENOUGH_MEMORY: 149 | return 'ERROR_NOT_ENOUGH_MEMORY' 150 | return 'Windows error %s' % errno 151 | 152 | def write(self, b): 153 | bytes_to_be_written = len(b) 154 | buf = get_buffer(b) 155 | code_units_to_be_written = min(bytes_to_be_written, 156 | MAX_BYTES_WRITTEN) // 2 157 | code_units_written = c_ulong() 158 | 159 | WriteConsoleW(self.handle, buf, code_units_to_be_written, 160 | byref(code_units_written), None) 161 | bytes_written = 2 * code_units_written.value 162 | 163 | if bytes_written == 0 and bytes_to_be_written > 0: 164 | raise OSError(self._get_error_message(GetLastError())) 165 | return bytes_written 166 | 167 | 168 | class ConsoleStream(object): 169 | 170 | def __init__(self, text_stream, byte_stream): 171 | self._text_stream = text_stream 172 | self.buffer = byte_stream 173 | 174 | @property 175 | def name(self): 176 | return self.buffer.name 177 | 178 | def write(self, x): 179 | if isinstance(x, text_type): 180 | return self._text_stream.write(x) 181 | try: 182 | self.flush() 183 | except Exception: 184 | pass 185 | return self.buffer.write(x) 186 | 187 | def writelines(self, lines): 188 | for line in lines: 189 | self.write(line) 190 | 191 | def __getattr__(self, name): 192 | return getattr(self._text_stream, name) 193 | 194 | def isatty(self): 195 | return self.buffer.isatty() 196 | 197 | def __repr__(self): 198 | return '' % ( 199 | self.name, 200 | self.encoding, 201 | ) 202 | 203 | 204 | def _get_text_stdin(buffer_stream): 205 | text_stream = _NonClosingTextIOWrapper( 206 | io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), 207 | 'utf-16-le', 'strict', line_buffering=True) 208 | return ConsoleStream(text_stream, buffer_stream) 209 | 210 | 211 | def _get_text_stdout(buffer_stream): 212 | text_stream = _NonClosingTextIOWrapper( 213 | _WindowsConsoleWriter(STDOUT_HANDLE), 214 | 'utf-16-le', 'strict', line_buffering=True) 215 | return ConsoleStream(text_stream, buffer_stream) 216 | 217 | 218 | def _get_text_stderr(buffer_stream): 219 | text_stream = _NonClosingTextIOWrapper( 220 | _WindowsConsoleWriter(STDERR_HANDLE), 221 | 'utf-16-le', 'strict', line_buffering=True) 222 | return ConsoleStream(text_stream, buffer_stream) 223 | 224 | 225 | if PY2: 226 | def _hash_py_argv(): 227 | return zlib.crc32('\x00'.join(sys.argv[1:])) 228 | 229 | _initial_argv_hash = _hash_py_argv() 230 | 231 | def _get_windows_argv(): 232 | argc = c_int(0) 233 | argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) 234 | argv = [argv_unicode[i] for i in range(0, argc.value)] 235 | 236 | if not hasattr(sys, 'frozen'): 237 | argv = argv[1:] 238 | while len(argv) > 0: 239 | arg = argv[0] 240 | if not arg.startswith('-') or arg == '-': 241 | break 242 | argv = argv[1:] 243 | if arg.startswith(('-c', '-m')): 244 | break 245 | 246 | return argv[1:] 247 | 248 | 249 | _stream_factories = { 250 | 0: _get_text_stdin, 251 | 1: _get_text_stdout, 252 | 2: _get_text_stderr, 253 | } 254 | 255 | 256 | def _get_windows_console_stream(f, encoding, errors): 257 | if get_buffer is not None and \ 258 | encoding in ('utf-16-le', None) \ 259 | and errors in ('strict', None) and \ 260 | hasattr(f, 'isatty') and f.isatty(): 261 | func = _stream_factories.get(f.fileno()) 262 | if func is not None: 263 | if not PY2: 264 | f = getattr(f, 'buffer') 265 | if f is None: 266 | return None 267 | else: 268 | # If we are on Python 2 we need to set the stream that we 269 | # deal with to binary mode as otherwise the exercise if a 270 | # bit moot. The same problems apply as for 271 | # get_binary_stdin and friends from _compat. 272 | msvcrt.setmode(f.fileno(), os.O_BINARY) 273 | return func(f) 274 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/formatting.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from .termui import get_terminal_size 3 | from .parser import split_opt 4 | from ._compat import term_len 5 | 6 | 7 | # Can force a width. This is used by the test system 8 | FORCED_WIDTH = None 9 | 10 | 11 | def measure_table(rows): 12 | widths = {} 13 | for row in rows: 14 | for idx, col in enumerate(row): 15 | widths[idx] = max(widths.get(idx, 0), term_len(col)) 16 | return tuple(y for x, y in sorted(widths.items())) 17 | 18 | 19 | def iter_rows(rows, col_count): 20 | for row in rows: 21 | row = tuple(row) 22 | yield row + ('',) * (col_count - len(row)) 23 | 24 | 25 | def wrap_text(text, width=78, initial_indent='', subsequent_indent='', 26 | preserve_paragraphs=False): 27 | """A helper function that intelligently wraps text. By default, it 28 | assumes that it operates on a single paragraph of text but if the 29 | `preserve_paragraphs` parameter is provided it will intelligently 30 | handle paragraphs (defined by two empty lines). 31 | 32 | If paragraphs are handled, a paragraph can be prefixed with an empty 33 | line containing the ``\\b`` character (``\\x08``) to indicate that 34 | no rewrapping should happen in that block. 35 | 36 | :param text: the text that should be rewrapped. 37 | :param width: the maximum width for the text. 38 | :param initial_indent: the initial indent that should be placed on the 39 | first line as a string. 40 | :param subsequent_indent: the indent string that should be placed on 41 | each consecutive line. 42 | :param preserve_paragraphs: if this flag is set then the wrapping will 43 | intelligently handle paragraphs. 44 | """ 45 | from ._textwrap import TextWrapper 46 | text = text.expandtabs() 47 | wrapper = TextWrapper(width, initial_indent=initial_indent, 48 | subsequent_indent=subsequent_indent, 49 | replace_whitespace=False) 50 | if not preserve_paragraphs: 51 | return wrapper.fill(text) 52 | 53 | p = [] 54 | buf = [] 55 | indent = None 56 | 57 | def _flush_par(): 58 | if not buf: 59 | return 60 | if buf[0].strip() == '\b': 61 | p.append((indent or 0, True, '\n'.join(buf[1:]))) 62 | else: 63 | p.append((indent or 0, False, ' '.join(buf))) 64 | del buf[:] 65 | 66 | for line in text.splitlines(): 67 | if not line: 68 | _flush_par() 69 | indent = None 70 | else: 71 | if indent is None: 72 | orig_len = term_len(line) 73 | line = line.lstrip() 74 | indent = orig_len - term_len(line) 75 | buf.append(line) 76 | _flush_par() 77 | 78 | rv = [] 79 | for indent, raw, text in p: 80 | with wrapper.extra_indent(' ' * indent): 81 | if raw: 82 | rv.append(wrapper.indent_only(text)) 83 | else: 84 | rv.append(wrapper.fill(text)) 85 | 86 | return '\n\n'.join(rv) 87 | 88 | 89 | class HelpFormatter(object): 90 | """This class helps with formatting text-based help pages. It's 91 | usually just needed for very special internal cases, but it's also 92 | exposed so that developers can write their own fancy outputs. 93 | 94 | At present, it always writes into memory. 95 | 96 | :param indent_increment: the additional increment for each level. 97 | :param width: the width for the text. This defaults to the terminal 98 | width clamped to a maximum of 78. 99 | """ 100 | 101 | def __init__(self, indent_increment=2, width=None, max_width=None): 102 | self.indent_increment = indent_increment 103 | if max_width is None: 104 | max_width = 80 105 | if width is None: 106 | width = FORCED_WIDTH 107 | if width is None: 108 | width = max(min(get_terminal_size()[0], max_width) - 2, 50) 109 | self.width = width 110 | self.current_indent = 0 111 | self.buffer = [] 112 | 113 | def write(self, string): 114 | """Writes a unicode string into the internal buffer.""" 115 | self.buffer.append(string) 116 | 117 | def indent(self): 118 | """Increases the indentation.""" 119 | self.current_indent += self.indent_increment 120 | 121 | def dedent(self): 122 | """Decreases the indentation.""" 123 | self.current_indent -= self.indent_increment 124 | 125 | def write_usage(self, prog, args='', prefix='Usage: '): 126 | """Writes a usage line into the buffer. 127 | 128 | :param prog: the program name. 129 | :param args: whitespace separated list of arguments. 130 | :param prefix: the prefix for the first line. 131 | """ 132 | usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog) 133 | text_width = self.width - self.current_indent 134 | 135 | if text_width >= (term_len(usage_prefix) + 20): 136 | # The arguments will fit to the right of the prefix. 137 | indent = ' ' * term_len(usage_prefix) 138 | self.write(wrap_text(args, text_width, 139 | initial_indent=usage_prefix, 140 | subsequent_indent=indent)) 141 | else: 142 | # The prefix is too long, put the arguments on the next line. 143 | self.write(usage_prefix) 144 | self.write('\n') 145 | indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4) 146 | self.write(wrap_text(args, text_width, 147 | initial_indent=indent, 148 | subsequent_indent=indent)) 149 | 150 | self.write('\n') 151 | 152 | def write_heading(self, heading): 153 | """Writes a heading into the buffer.""" 154 | self.write('%*s%s:\n' % (self.current_indent, '', heading)) 155 | 156 | def write_paragraph(self): 157 | """Writes a paragraph into the buffer.""" 158 | if self.buffer: 159 | self.write('\n') 160 | 161 | def write_text(self, text): 162 | """Writes re-indented text into the buffer. This rewraps and 163 | preserves paragraphs. 164 | """ 165 | text_width = max(self.width - self.current_indent, 11) 166 | indent = ' ' * self.current_indent 167 | self.write(wrap_text(text, text_width, 168 | initial_indent=indent, 169 | subsequent_indent=indent, 170 | preserve_paragraphs=True)) 171 | self.write('\n') 172 | 173 | def write_dl(self, rows, col_max=30, col_spacing=2): 174 | """Writes a definition list into the buffer. This is how options 175 | and commands are usually formatted. 176 | 177 | :param rows: a list of two item tuples for the terms and values. 178 | :param col_max: the maximum width of the first column. 179 | :param col_spacing: the number of spaces between the first and 180 | second column. 181 | """ 182 | rows = list(rows) 183 | widths = measure_table(rows) 184 | if len(widths) != 2: 185 | raise TypeError('Expected two columns for definition list') 186 | 187 | first_col = min(widths[0], col_max) + col_spacing 188 | 189 | for first, second in iter_rows(rows, len(widths)): 190 | self.write('%*s%s' % (self.current_indent, '', first)) 191 | if not second: 192 | self.write('\n') 193 | continue 194 | if term_len(first) <= first_col - col_spacing: 195 | self.write(' ' * (first_col - term_len(first))) 196 | else: 197 | self.write('\n') 198 | self.write(' ' * (first_col + self.current_indent)) 199 | 200 | text_width = max(self.width - first_col - 2, 10) 201 | lines = iter(wrap_text(second, text_width).splitlines()) 202 | if lines: 203 | self.write(next(lines) + '\n') 204 | for line in lines: 205 | self.write('%*s%s\n' % ( 206 | first_col + self.current_indent, '', line)) 207 | else: 208 | self.write('\n') 209 | 210 | @contextmanager 211 | def section(self, name): 212 | """Helpful context manager that writes a paragraph, a heading, 213 | and the indents. 214 | 215 | :param name: the section name that is written as heading. 216 | """ 217 | self.write_paragraph() 218 | self.write_heading(name) 219 | self.indent() 220 | try: 221 | yield 222 | finally: 223 | self.dedent() 224 | 225 | @contextmanager 226 | def indentation(self): 227 | """A context manager that increases the indentation.""" 228 | self.indent() 229 | try: 230 | yield 231 | finally: 232 | self.dedent() 233 | 234 | def getvalue(self): 235 | """Returns the buffer contents.""" 236 | return ''.join(self.buffer) 237 | 238 | 239 | def join_options(options): 240 | """Given a list of option strings this joins them in the most appropriate 241 | way and returns them in the form ``(formatted_string, 242 | any_prefix_is_slash)`` where the second item in the tuple is a flag that 243 | indicates if any of the option prefixes was a slash. 244 | """ 245 | rv = [] 246 | any_prefix_is_slash = False 247 | for opt in options: 248 | prefix = split_opt(opt)[0] 249 | if prefix == '/': 250 | any_prefix_is_slash = True 251 | rv.append((len(prefix), opt)) 252 | 253 | rv.sort(key=lambda x: x[0]) 254 | 255 | rv = ', '.join(x[1] for x in rv) 256 | return rv, any_prefix_is_slash 257 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/decorators.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import inspect 3 | 4 | from functools import update_wrapper 5 | 6 | from ._compat import iteritems 7 | from ._unicodefun import _check_for_unicode_literals 8 | from .utils import echo 9 | from .globals import get_current_context 10 | 11 | 12 | def pass_context(f): 13 | """Marks a callback as wanting to receive the current context 14 | object as first argument. 15 | """ 16 | def new_func(*args, **kwargs): 17 | return f(get_current_context(), *args, **kwargs) 18 | return update_wrapper(new_func, f) 19 | 20 | 21 | def pass_obj(f): 22 | """Similar to :func:`pass_context`, but only pass the object on the 23 | context onwards (:attr:`Context.obj`). This is useful if that object 24 | represents the state of a nested system. 25 | """ 26 | def new_func(*args, **kwargs): 27 | return f(get_current_context().obj, *args, **kwargs) 28 | return update_wrapper(new_func, f) 29 | 30 | 31 | def make_pass_decorator(object_type, ensure=False): 32 | """Given an object type this creates a decorator that will work 33 | similar to :func:`pass_obj` but instead of passing the object of the 34 | current context, it will find the innermost context of type 35 | :func:`object_type`. 36 | 37 | This generates a decorator that works roughly like this:: 38 | 39 | from functools import update_wrapper 40 | 41 | def decorator(f): 42 | @pass_context 43 | def new_func(ctx, *args, **kwargs): 44 | obj = ctx.find_object(object_type) 45 | return ctx.invoke(f, obj, *args, **kwargs) 46 | return update_wrapper(new_func, f) 47 | return decorator 48 | 49 | :param object_type: the type of the object to pass. 50 | :param ensure: if set to `True`, a new object will be created and 51 | remembered on the context if it's not there yet. 52 | """ 53 | def decorator(f): 54 | def new_func(*args, **kwargs): 55 | ctx = get_current_context() 56 | if ensure: 57 | obj = ctx.ensure_object(object_type) 58 | else: 59 | obj = ctx.find_object(object_type) 60 | if obj is None: 61 | raise RuntimeError('Managed to invoke callback without a ' 62 | 'context object of type %r existing' 63 | % object_type.__name__) 64 | return ctx.invoke(f, obj, *args[1:], **kwargs) 65 | return update_wrapper(new_func, f) 66 | return decorator 67 | 68 | 69 | def _make_command(f, name, attrs, cls): 70 | if isinstance(f, Command): 71 | raise TypeError('Attempted to convert a callback into a ' 72 | 'command twice.') 73 | try: 74 | params = f.__click_params__ 75 | params.reverse() 76 | del f.__click_params__ 77 | except AttributeError: 78 | params = [] 79 | help = attrs.get('help') 80 | if help is None: 81 | help = inspect.getdoc(f) 82 | if isinstance(help, bytes): 83 | help = help.decode('utf-8') 84 | else: 85 | help = inspect.cleandoc(help) 86 | attrs['help'] = help 87 | _check_for_unicode_literals() 88 | return cls(name=name or f.__name__.lower(), 89 | callback=f, params=params, **attrs) 90 | 91 | 92 | def command(name=None, cls=None, **attrs): 93 | """Creates a new :class:`Command` and uses the decorated function as 94 | callback. This will also automatically attach all decorated 95 | :func:`option`\s and :func:`argument`\s as parameters to the command. 96 | 97 | The name of the command defaults to the name of the function. If you 98 | want to change that, you can pass the intended name as the first 99 | argument. 100 | 101 | All keyword arguments are forwarded to the underlying command class. 102 | 103 | Once decorated the function turns into a :class:`Command` instance 104 | that can be invoked as a command line utility or be attached to a 105 | command :class:`Group`. 106 | 107 | :param name: the name of the command. This defaults to the function 108 | name. 109 | :param cls: the command class to instantiate. This defaults to 110 | :class:`Command`. 111 | """ 112 | if cls is None: 113 | cls = Command 114 | def decorator(f): 115 | cmd = _make_command(f, name, attrs, cls) 116 | cmd.__doc__ = f.__doc__ 117 | return cmd 118 | return decorator 119 | 120 | 121 | def group(name=None, **attrs): 122 | """Creates a new :class:`Group` with a function as callback. This 123 | works otherwise the same as :func:`command` just that the `cls` 124 | parameter is set to :class:`Group`. 125 | """ 126 | attrs.setdefault('cls', Group) 127 | return command(name, **attrs) 128 | 129 | 130 | def _param_memo(f, param): 131 | if isinstance(f, Command): 132 | f.params.append(param) 133 | else: 134 | if not hasattr(f, '__click_params__'): 135 | f.__click_params__ = [] 136 | f.__click_params__.append(param) 137 | 138 | 139 | def argument(*param_decls, **attrs): 140 | """Attaches an argument to the command. All positional arguments are 141 | passed as parameter declarations to :class:`Argument`; all keyword 142 | arguments are forwarded unchanged (except ``cls``). 143 | This is equivalent to creating an :class:`Argument` instance manually 144 | and attaching it to the :attr:`Command.params` list. 145 | 146 | :param cls: the argument class to instantiate. This defaults to 147 | :class:`Argument`. 148 | """ 149 | def decorator(f): 150 | ArgumentClass = attrs.pop('cls', Argument) 151 | _param_memo(f, ArgumentClass(param_decls, **attrs)) 152 | return f 153 | return decorator 154 | 155 | 156 | def option(*param_decls, **attrs): 157 | """Attaches an option to the command. All positional arguments are 158 | passed as parameter declarations to :class:`Option`; all keyword 159 | arguments are forwarded unchanged (except ``cls``). 160 | This is equivalent to creating an :class:`Option` instance manually 161 | and attaching it to the :attr:`Command.params` list. 162 | 163 | :param cls: the option class to instantiate. This defaults to 164 | :class:`Option`. 165 | """ 166 | def decorator(f): 167 | if 'help' in attrs: 168 | attrs['help'] = inspect.cleandoc(attrs['help']) 169 | OptionClass = attrs.pop('cls', Option) 170 | _param_memo(f, OptionClass(param_decls, **attrs)) 171 | return f 172 | return decorator 173 | 174 | 175 | def confirmation_option(*param_decls, **attrs): 176 | """Shortcut for confirmation prompts that can be ignored by passing 177 | ``--yes`` as parameter. 178 | 179 | This is equivalent to decorating a function with :func:`option` with 180 | the following parameters:: 181 | 182 | def callback(ctx, param, value): 183 | if not value: 184 | ctx.abort() 185 | 186 | @click.command() 187 | @click.option('--yes', is_flag=True, callback=callback, 188 | expose_value=False, prompt='Do you want to continue?') 189 | def dropdb(): 190 | pass 191 | """ 192 | def decorator(f): 193 | def callback(ctx, param, value): 194 | if not value: 195 | ctx.abort() 196 | attrs.setdefault('is_flag', True) 197 | attrs.setdefault('callback', callback) 198 | attrs.setdefault('expose_value', False) 199 | attrs.setdefault('prompt', 'Do you want to continue?') 200 | attrs.setdefault('help', 'Confirm the action without prompting.') 201 | return option(*(param_decls or ('--yes',)), **attrs)(f) 202 | return decorator 203 | 204 | 205 | def password_option(*param_decls, **attrs): 206 | """Shortcut for password prompts. 207 | 208 | This is equivalent to decorating a function with :func:`option` with 209 | the following parameters:: 210 | 211 | @click.command() 212 | @click.option('--password', prompt=True, confirmation_prompt=True, 213 | hide_input=True) 214 | def changeadmin(password): 215 | pass 216 | """ 217 | def decorator(f): 218 | attrs.setdefault('prompt', True) 219 | attrs.setdefault('confirmation_prompt', True) 220 | attrs.setdefault('hide_input', True) 221 | return option(*(param_decls or ('--password',)), **attrs)(f) 222 | return decorator 223 | 224 | 225 | def version_option(version=None, *param_decls, **attrs): 226 | """Adds a ``--version`` option which immediately ends the program 227 | printing out the version number. This is implemented as an eager 228 | option that prints the version and exits the program in the callback. 229 | 230 | :param version: the version number to show. If not provided Click 231 | attempts an auto discovery via setuptools. 232 | :param prog_name: the name of the program (defaults to autodetection) 233 | :param message: custom message to show instead of the default 234 | (``'%(prog)s, version %(version)s'``) 235 | :param others: everything else is forwarded to :func:`option`. 236 | """ 237 | if version is None: 238 | module = sys._getframe(1).f_globals.get('__name__') 239 | def decorator(f): 240 | prog_name = attrs.pop('prog_name', None) 241 | message = attrs.pop('message', '%(prog)s, version %(version)s') 242 | 243 | def callback(ctx, param, value): 244 | if not value or ctx.resilient_parsing: 245 | return 246 | prog = prog_name 247 | if prog is None: 248 | prog = ctx.find_root().info_name 249 | ver = version 250 | if ver is None: 251 | try: 252 | import pkg_resources 253 | except ImportError: 254 | pass 255 | else: 256 | for dist in pkg_resources.working_set: 257 | scripts = dist.get_entry_map().get('console_scripts') or {} 258 | for script_name, entry_point in iteritems(scripts): 259 | if entry_point.module_name == module: 260 | ver = dist.version 261 | break 262 | if ver is None: 263 | raise RuntimeError('Could not determine version') 264 | echo(message % { 265 | 'prog': prog, 266 | 'version': ver, 267 | }, color=ctx.color) 268 | ctx.exit() 269 | 270 | attrs.setdefault('is_flag', True) 271 | attrs.setdefault('expose_value', False) 272 | attrs.setdefault('is_eager', True) 273 | attrs.setdefault('help', 'Show the version and exit.') 274 | attrs['callback'] = callback 275 | return option(*(param_decls or ('--version',)), **attrs)(f) 276 | return decorator 277 | 278 | 279 | def help_option(*param_decls, **attrs): 280 | """Adds a ``--help`` option which immediately ends the program 281 | printing out the help page. This is usually unnecessary to add as 282 | this is added by default to all commands unless suppressed. 283 | 284 | Like :func:`version_option`, this is implemented as eager option that 285 | prints in the callback and exits. 286 | 287 | All arguments are forwarded to :func:`option`. 288 | """ 289 | def decorator(f): 290 | def callback(ctx, param, value): 291 | if value and not ctx.resilient_parsing: 292 | echo(ctx.get_help(), color=ctx.color) 293 | ctx.exit() 294 | attrs.setdefault('is_flag', True) 295 | attrs.setdefault('expose_value', False) 296 | attrs.setdefault('help', 'Show this message and exit.') 297 | attrs.setdefault('is_eager', True) 298 | attrs['callback'] = callback 299 | return option(*(param_decls or ('--help',)), **attrs)(f) 300 | return decorator 301 | 302 | 303 | # Circular dependencies between core and decorators 304 | from .core import Command, Group, Argument, Option 305 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/testing.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | import tempfile 5 | import contextlib 6 | 7 | from ._compat import iteritems, PY2 8 | 9 | 10 | # If someone wants to vendor click, we want to ensure the 11 | # correct package is discovered. Ideally we could use a 12 | # relative import here but unfortunately Python does not 13 | # support that. 14 | clickpkg = sys.modules[__name__.rsplit('.', 1)[0]] 15 | 16 | 17 | if PY2: 18 | from cStringIO import StringIO 19 | else: 20 | import io 21 | from ._compat import _find_binary_reader 22 | 23 | 24 | class EchoingStdin(object): 25 | 26 | def __init__(self, input, output): 27 | self._input = input 28 | self._output = output 29 | 30 | def __getattr__(self, x): 31 | return getattr(self._input, x) 32 | 33 | def _echo(self, rv): 34 | self._output.write(rv) 35 | return rv 36 | 37 | def read(self, n=-1): 38 | return self._echo(self._input.read(n)) 39 | 40 | def readline(self, n=-1): 41 | return self._echo(self._input.readline(n)) 42 | 43 | def readlines(self): 44 | return [self._echo(x) for x in self._input.readlines()] 45 | 46 | def __iter__(self): 47 | return iter(self._echo(x) for x in self._input) 48 | 49 | def __repr__(self): 50 | return repr(self._input) 51 | 52 | 53 | def make_input_stream(input, charset): 54 | # Is already an input stream. 55 | if hasattr(input, 'read'): 56 | if PY2: 57 | return input 58 | rv = _find_binary_reader(input) 59 | if rv is not None: 60 | return rv 61 | raise TypeError('Could not find binary reader for input stream.') 62 | 63 | if input is None: 64 | input = b'' 65 | elif not isinstance(input, bytes): 66 | input = input.encode(charset) 67 | if PY2: 68 | return StringIO(input) 69 | return io.BytesIO(input) 70 | 71 | 72 | class Result(object): 73 | """Holds the captured result of an invoked CLI script.""" 74 | 75 | def __init__(self, runner, output_bytes, exit_code, exception, 76 | exc_info=None): 77 | #: The runner that created the result 78 | self.runner = runner 79 | #: The output as bytes. 80 | self.output_bytes = output_bytes 81 | #: The exit code as integer. 82 | self.exit_code = exit_code 83 | #: The exception that happend if one did. 84 | self.exception = exception 85 | #: The traceback 86 | self.exc_info = exc_info 87 | 88 | @property 89 | def output(self): 90 | """The output as unicode string.""" 91 | return self.output_bytes.decode(self.runner.charset, 'replace') \ 92 | .replace('\r\n', '\n') 93 | 94 | def __repr__(self): 95 | return '' % ( 96 | self.exception and repr(self.exception) or 'okay', 97 | ) 98 | 99 | 100 | class CliRunner(object): 101 | """The CLI runner provides functionality to invoke a Click command line 102 | script for unittesting purposes in a isolated environment. This only 103 | works in single-threaded systems without any concurrency as it changes the 104 | global interpreter state. 105 | 106 | :param charset: the character set for the input and output data. This is 107 | UTF-8 by default and should not be changed currently as 108 | the reporting to Click only works in Python 2 properly. 109 | :param env: a dictionary with environment variables for overriding. 110 | :param echo_stdin: if this is set to `True`, then reading from stdin writes 111 | to stdout. This is useful for showing examples in 112 | some circumstances. Note that regular prompts 113 | will automatically echo the input. 114 | """ 115 | 116 | def __init__(self, charset=None, env=None, echo_stdin=False): 117 | if charset is None: 118 | charset = 'utf-8' 119 | self.charset = charset 120 | self.env = env or {} 121 | self.echo_stdin = echo_stdin 122 | 123 | def get_default_prog_name(self, cli): 124 | """Given a command object it will return the default program name 125 | for it. The default is the `name` attribute or ``"root"`` if not 126 | set. 127 | """ 128 | return cli.name or 'root' 129 | 130 | def make_env(self, overrides=None): 131 | """Returns the environment overrides for invoking a script.""" 132 | rv = dict(self.env) 133 | if overrides: 134 | rv.update(overrides) 135 | return rv 136 | 137 | @contextlib.contextmanager 138 | def isolation(self, input=None, env=None, color=False): 139 | """A context manager that sets up the isolation for invoking of a 140 | command line tool. This sets up stdin with the given input data 141 | and `os.environ` with the overrides from the given dictionary. 142 | This also rebinds some internals in Click to be mocked (like the 143 | prompt functionality). 144 | 145 | This is automatically done in the :meth:`invoke` method. 146 | 147 | .. versionadded:: 4.0 148 | The ``color`` parameter was added. 149 | 150 | :param input: the input stream to put into sys.stdin. 151 | :param env: the environment overrides as dictionary. 152 | :param color: whether the output should contain color codes. The 153 | application can still override this explicitly. 154 | """ 155 | input = make_input_stream(input, self.charset) 156 | 157 | old_stdin = sys.stdin 158 | old_stdout = sys.stdout 159 | old_stderr = sys.stderr 160 | old_forced_width = clickpkg.formatting.FORCED_WIDTH 161 | clickpkg.formatting.FORCED_WIDTH = 80 162 | 163 | env = self.make_env(env) 164 | 165 | if PY2: 166 | sys.stdout = sys.stderr = bytes_output = StringIO() 167 | if self.echo_stdin: 168 | input = EchoingStdin(input, bytes_output) 169 | else: 170 | bytes_output = io.BytesIO() 171 | if self.echo_stdin: 172 | input = EchoingStdin(input, bytes_output) 173 | input = io.TextIOWrapper(input, encoding=self.charset) 174 | sys.stdout = sys.stderr = io.TextIOWrapper( 175 | bytes_output, encoding=self.charset) 176 | 177 | sys.stdin = input 178 | 179 | def visible_input(prompt=None): 180 | sys.stdout.write(prompt or '') 181 | val = input.readline().rstrip('\r\n') 182 | sys.stdout.write(val + '\n') 183 | sys.stdout.flush() 184 | return val 185 | 186 | def hidden_input(prompt=None): 187 | sys.stdout.write((prompt or '') + '\n') 188 | sys.stdout.flush() 189 | return input.readline().rstrip('\r\n') 190 | 191 | def _getchar(echo): 192 | char = sys.stdin.read(1) 193 | if echo: 194 | sys.stdout.write(char) 195 | sys.stdout.flush() 196 | return char 197 | 198 | default_color = color 199 | def should_strip_ansi(stream=None, color=None): 200 | if color is None: 201 | return not default_color 202 | return not color 203 | 204 | old_visible_prompt_func = clickpkg.termui.visible_prompt_func 205 | old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func 206 | old__getchar_func = clickpkg.termui._getchar 207 | old_should_strip_ansi = clickpkg.utils.should_strip_ansi 208 | clickpkg.termui.visible_prompt_func = visible_input 209 | clickpkg.termui.hidden_prompt_func = hidden_input 210 | clickpkg.termui._getchar = _getchar 211 | clickpkg.utils.should_strip_ansi = should_strip_ansi 212 | 213 | old_env = {} 214 | try: 215 | for key, value in iteritems(env): 216 | old_env[key] = os.environ.get(value) 217 | if value is None: 218 | try: 219 | del os.environ[key] 220 | except Exception: 221 | pass 222 | else: 223 | os.environ[key] = value 224 | yield bytes_output 225 | finally: 226 | for key, value in iteritems(old_env): 227 | if value is None: 228 | try: 229 | del os.environ[key] 230 | except Exception: 231 | pass 232 | else: 233 | os.environ[key] = value 234 | sys.stdout = old_stdout 235 | sys.stderr = old_stderr 236 | sys.stdin = old_stdin 237 | clickpkg.termui.visible_prompt_func = old_visible_prompt_func 238 | clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func 239 | clickpkg.termui._getchar = old__getchar_func 240 | clickpkg.utils.should_strip_ansi = old_should_strip_ansi 241 | clickpkg.formatting.FORCED_WIDTH = old_forced_width 242 | 243 | def invoke(self, cli, args=None, input=None, env=None, 244 | catch_exceptions=True, color=False, **extra): 245 | """Invokes a command in an isolated environment. The arguments are 246 | forwarded directly to the command line script, the `extra` keyword 247 | arguments are passed to the :meth:`~clickpkg.Command.main` function of 248 | the command. 249 | 250 | This returns a :class:`Result` object. 251 | 252 | .. versionadded:: 3.0 253 | The ``catch_exceptions`` parameter was added. 254 | 255 | .. versionchanged:: 3.0 256 | The result object now has an `exc_info` attribute with the 257 | traceback if available. 258 | 259 | .. versionadded:: 4.0 260 | The ``color`` parameter was added. 261 | 262 | :param cli: the command to invoke 263 | :param args: the arguments to invoke 264 | :param input: the input data for `sys.stdin`. 265 | :param env: the environment overrides. 266 | :param catch_exceptions: Whether to catch any other exceptions than 267 | ``SystemExit``. 268 | :param extra: the keyword arguments to pass to :meth:`main`. 269 | :param color: whether the output should contain color codes. The 270 | application can still override this explicitly. 271 | """ 272 | exc_info = None 273 | with self.isolation(input=input, env=env, color=color) as out: 274 | exception = None 275 | exit_code = 0 276 | 277 | try: 278 | cli.main(args=args or (), 279 | prog_name=self.get_default_prog_name(cli), **extra) 280 | except SystemExit as e: 281 | if e.code != 0: 282 | exception = e 283 | 284 | exc_info = sys.exc_info() 285 | 286 | exit_code = e.code 287 | if not isinstance(exit_code, int): 288 | sys.stdout.write(str(exit_code)) 289 | sys.stdout.write('\n') 290 | exit_code = 1 291 | except Exception as e: 292 | if not catch_exceptions: 293 | raise 294 | exception = e 295 | exit_code = -1 296 | exc_info = sys.exc_info() 297 | finally: 298 | sys.stdout.flush() 299 | output = out.getvalue() 300 | 301 | return Result(runner=self, 302 | output_bytes=output, 303 | exit_code=exit_code, 304 | exception=exception, 305 | exc_info=exc_info) 306 | 307 | @contextlib.contextmanager 308 | def isolated_filesystem(self): 309 | """A context manager that creates a temporary folder and changes 310 | the current working directory to it for isolated filesystem tests. 311 | """ 312 | cwd = os.getcwd() 313 | t = tempfile.mkdtemp() 314 | os.chdir(t) 315 | try: 316 | yield t 317 | finally: 318 | os.chdir(cwd) 319 | try: 320 | shutil.rmtree(t) 321 | except (OSError, IOError): 322 | pass 323 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from .globals import resolve_color_default 5 | 6 | from ._compat import text_type, open_stream, get_filesystem_encoding, \ 7 | get_streerror, string_types, PY2, binary_streams, text_streams, \ 8 | filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \ 9 | _default_text_stdout, _default_text_stderr, is_bytes, WIN 10 | 11 | if not PY2: 12 | from ._compat import _find_binary_writer 13 | elif WIN: 14 | from ._winconsole import _get_windows_argv, \ 15 | _hash_py_argv, _initial_argv_hash 16 | 17 | 18 | echo_native_types = string_types + (bytes, bytearray) 19 | 20 | 21 | def _posixify(name): 22 | return '-'.join(name.split()).lower() 23 | 24 | 25 | def safecall(func): 26 | """Wraps a function so that it swallows exceptions.""" 27 | def wrapper(*args, **kwargs): 28 | try: 29 | return func(*args, **kwargs) 30 | except Exception: 31 | pass 32 | return wrapper 33 | 34 | 35 | def make_str(value): 36 | """Converts a value into a valid string.""" 37 | if isinstance(value, bytes): 38 | try: 39 | return value.decode(get_filesystem_encoding()) 40 | except UnicodeError: 41 | return value.decode('utf-8', 'replace') 42 | return text_type(value) 43 | 44 | 45 | def make_default_short_help(help, max_length=45): 46 | words = help.split() 47 | total_length = 0 48 | result = [] 49 | done = False 50 | 51 | for word in words: 52 | if word[-1:] == '.': 53 | done = True 54 | new_length = result and 1 + len(word) or len(word) 55 | if total_length + new_length > max_length: 56 | result.append('...') 57 | done = True 58 | else: 59 | if result: 60 | result.append(' ') 61 | result.append(word) 62 | if done: 63 | break 64 | total_length += new_length 65 | 66 | return ''.join(result) 67 | 68 | 69 | class LazyFile(object): 70 | """A lazy file works like a regular file but it does not fully open 71 | the file but it does perform some basic checks early to see if the 72 | filename parameter does make sense. This is useful for safely opening 73 | files for writing. 74 | """ 75 | 76 | def __init__(self, filename, mode='r', encoding=None, errors='strict', 77 | atomic=False): 78 | self.name = filename 79 | self.mode = mode 80 | self.encoding = encoding 81 | self.errors = errors 82 | self.atomic = atomic 83 | 84 | if filename == '-': 85 | self._f, self.should_close = open_stream(filename, mode, 86 | encoding, errors) 87 | else: 88 | if 'r' in mode: 89 | # Open and close the file in case we're opening it for 90 | # reading so that we can catch at least some errors in 91 | # some cases early. 92 | open(filename, mode).close() 93 | self._f = None 94 | self.should_close = True 95 | 96 | def __getattr__(self, name): 97 | return getattr(self.open(), name) 98 | 99 | def __repr__(self): 100 | if self._f is not None: 101 | return repr(self._f) 102 | return '' % (self.name, self.mode) 103 | 104 | def open(self): 105 | """Opens the file if it's not yet open. This call might fail with 106 | a :exc:`FileError`. Not handling this error will produce an error 107 | that Click shows. 108 | """ 109 | if self._f is not None: 110 | return self._f 111 | try: 112 | rv, self.should_close = open_stream(self.name, self.mode, 113 | self.encoding, 114 | self.errors, 115 | atomic=self.atomic) 116 | except (IOError, OSError) as e: 117 | from .exceptions import FileError 118 | raise FileError(self.name, hint=get_streerror(e)) 119 | self._f = rv 120 | return rv 121 | 122 | def close(self): 123 | """Closes the underlying file, no matter what.""" 124 | if self._f is not None: 125 | self._f.close() 126 | 127 | def close_intelligently(self): 128 | """This function only closes the file if it was opened by the lazy 129 | file wrapper. For instance this will never close stdin. 130 | """ 131 | if self.should_close: 132 | self.close() 133 | 134 | def __enter__(self): 135 | return self 136 | 137 | def __exit__(self, exc_type, exc_value, tb): 138 | self.close_intelligently() 139 | 140 | def __iter__(self): 141 | self.open() 142 | return iter(self._f) 143 | 144 | 145 | class KeepOpenFile(object): 146 | 147 | def __init__(self, file): 148 | self._file = file 149 | 150 | def __getattr__(self, name): 151 | return getattr(self._file, name) 152 | 153 | def __enter__(self): 154 | return self 155 | 156 | def __exit__(self, exc_type, exc_value, tb): 157 | pass 158 | 159 | def __repr__(self): 160 | return repr(self._file) 161 | 162 | def __iter__(self): 163 | return iter(self._file) 164 | 165 | 166 | def echo(message=None, file=None, nl=True, err=False, color=None): 167 | """Prints a message plus a newline to the given file or stdout. On 168 | first sight, this looks like the print function, but it has improved 169 | support for handling Unicode and binary data that does not fail no 170 | matter how badly configured the system is. 171 | 172 | Primarily it means that you can print binary data as well as Unicode 173 | data on both 2.x and 3.x to the given file in the most appropriate way 174 | possible. This is a very carefree function as in that it will try its 175 | best to not fail. As of Click 6.0 this includes support for unicode 176 | output on the Windows console. 177 | 178 | In addition to that, if `colorama`_ is installed, the echo function will 179 | also support clever handling of ANSI codes. Essentially it will then 180 | do the following: 181 | 182 | - add transparent handling of ANSI color codes on Windows. 183 | - hide ANSI codes automatically if the destination file is not a 184 | terminal. 185 | 186 | .. _colorama: http://pypi.python.org/pypi/colorama 187 | 188 | .. versionchanged:: 6.0 189 | As of Click 6.0 the echo function will properly support unicode 190 | output on the windows console. Not that click does not modify 191 | the interpreter in any way which means that `sys.stdout` or the 192 | print statement or function will still not provide unicode support. 193 | 194 | .. versionchanged:: 2.0 195 | Starting with version 2.0 of Click, the echo function will work 196 | with colorama if it's installed. 197 | 198 | .. versionadded:: 3.0 199 | The `err` parameter was added. 200 | 201 | .. versionchanged:: 4.0 202 | Added the `color` flag. 203 | 204 | :param message: the message to print 205 | :param file: the file to write to (defaults to ``stdout``) 206 | :param err: if set to true the file defaults to ``stderr`` instead of 207 | ``stdout``. This is faster and easier than calling 208 | :func:`get_text_stderr` yourself. 209 | :param nl: if set to `True` (the default) a newline is printed afterwards. 210 | :param color: controls if the terminal supports ANSI colors or not. The 211 | default is autodetection. 212 | """ 213 | if file is None: 214 | if err: 215 | file = _default_text_stderr() 216 | else: 217 | file = _default_text_stdout() 218 | 219 | # Convert non bytes/text into the native string type. 220 | if message is not None and not isinstance(message, echo_native_types): 221 | message = text_type(message) 222 | 223 | if nl: 224 | message = message or u'' 225 | if isinstance(message, text_type): 226 | message += u'\n' 227 | else: 228 | message += b'\n' 229 | 230 | # If there is a message, and we're in Python 3, and the value looks 231 | # like bytes, we manually need to find the binary stream and write the 232 | # message in there. This is done separately so that most stream 233 | # types will work as you would expect. Eg: you can write to StringIO 234 | # for other cases. 235 | if message and not PY2 and is_bytes(message): 236 | binary_file = _find_binary_writer(file) 237 | if binary_file is not None: 238 | file.flush() 239 | binary_file.write(message) 240 | binary_file.flush() 241 | return 242 | 243 | # ANSI-style support. If there is no message or we are dealing with 244 | # bytes nothing is happening. If we are connected to a file we want 245 | # to strip colors. If we are on windows we either wrap the stream 246 | # to strip the color or we use the colorama support to translate the 247 | # ansi codes to API calls. 248 | if message and not is_bytes(message): 249 | color = resolve_color_default(color) 250 | if should_strip_ansi(file, color): 251 | message = strip_ansi(message) 252 | elif WIN: 253 | if auto_wrap_for_ansi is not None: 254 | file = auto_wrap_for_ansi(file) 255 | elif not color: 256 | message = strip_ansi(message) 257 | 258 | if message: 259 | file.write(message) 260 | file.flush() 261 | 262 | 263 | def get_binary_stream(name): 264 | """Returns a system stream for byte processing. This essentially 265 | returns the stream from the sys module with the given name but it 266 | solves some compatibility issues between different Python versions. 267 | Primarily this function is necessary for getting binary streams on 268 | Python 3. 269 | 270 | :param name: the name of the stream to open. Valid names are ``'stdin'``, 271 | ``'stdout'`` and ``'stderr'`` 272 | """ 273 | opener = binary_streams.get(name) 274 | if opener is None: 275 | raise TypeError('Unknown standard stream %r' % name) 276 | return opener() 277 | 278 | 279 | def get_text_stream(name, encoding=None, errors='strict'): 280 | """Returns a system stream for text processing. This usually returns 281 | a wrapped stream around a binary stream returned from 282 | :func:`get_binary_stream` but it also can take shortcuts on Python 3 283 | for already correctly configured streams. 284 | 285 | :param name: the name of the stream to open. Valid names are ``'stdin'``, 286 | ``'stdout'`` and ``'stderr'`` 287 | :param encoding: overrides the detected default encoding. 288 | :param errors: overrides the default error mode. 289 | """ 290 | opener = text_streams.get(name) 291 | if opener is None: 292 | raise TypeError('Unknown standard stream %r' % name) 293 | return opener(encoding, errors) 294 | 295 | 296 | def open_file(filename, mode='r', encoding=None, errors='strict', 297 | lazy=False, atomic=False): 298 | """This is similar to how the :class:`File` works but for manual 299 | usage. Files are opened non lazy by default. This can open regular 300 | files as well as stdin/stdout if ``'-'`` is passed. 301 | 302 | If stdin/stdout is returned the stream is wrapped so that the context 303 | manager will not close the stream accidentally. This makes it possible 304 | to always use the function like this without having to worry to 305 | accidentally close a standard stream:: 306 | 307 | with open_file(filename) as f: 308 | ... 309 | 310 | .. versionadded:: 3.0 311 | 312 | :param filename: the name of the file to open (or ``'-'`` for stdin/stdout). 313 | :param mode: the mode in which to open the file. 314 | :param encoding: the encoding to use. 315 | :param errors: the error handling for this file. 316 | :param lazy: can be flipped to true to open the file lazily. 317 | :param atomic: in atomic mode writes go into a temporary file and it's 318 | moved on close. 319 | """ 320 | if lazy: 321 | return LazyFile(filename, mode, encoding, errors, atomic=atomic) 322 | f, should_close = open_stream(filename, mode, encoding, errors, 323 | atomic=atomic) 324 | if not should_close: 325 | f = KeepOpenFile(f) 326 | return f 327 | 328 | 329 | def get_os_args(): 330 | """This returns the argument part of sys.argv in the most appropriate 331 | form for processing. What this means is that this return value is in 332 | a format that works for Click to process but does not necessarily 333 | correspond well to what's actually standard for the interpreter. 334 | 335 | On most environments the return value is ``sys.argv[:1]`` unchanged. 336 | However if you are on Windows and running Python 2 the return value 337 | will actually be a list of unicode strings instead because the 338 | default behavior on that platform otherwise will not be able to 339 | carry all possible values that sys.argv can have. 340 | 341 | .. versionadded:: 6.0 342 | """ 343 | # We can only extract the unicode argv if sys.argv has not been 344 | # changed since the startup of the application. 345 | if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): 346 | return _get_windows_argv() 347 | return sys.argv[1:] 348 | 349 | 350 | def format_filename(filename, shorten=False): 351 | """Formats a filename for user display. The main purpose of this 352 | function is to ensure that the filename can be displayed at all. This 353 | will decode the filename to unicode if necessary in a way that it will 354 | not fail. Optionally, it can shorten the filename to not include the 355 | full path to the filename. 356 | 357 | :param filename: formats a filename for UI display. This will also convert 358 | the filename into unicode without failing. 359 | :param shorten: this optionally shortens the filename to strip of the 360 | path that leads up to it. 361 | """ 362 | if shorten: 363 | filename = os.path.basename(filename) 364 | return filename_to_ui(filename) 365 | 366 | 367 | def get_app_dir(app_name, roaming=True, force_posix=False): 368 | r"""Returns the config folder for the application. The default behavior 369 | is to return whatever is most appropriate for the operating system. 370 | 371 | To give you an idea, for an app called ``"Foo Bar"``, something like 372 | the following folders could be returned: 373 | 374 | Mac OS X: 375 | ``~/Library/Application Support/Foo Bar`` 376 | Mac OS X (POSIX): 377 | ``~/.foo-bar`` 378 | Unix: 379 | ``~/.config/foo-bar`` 380 | Unix (POSIX): 381 | ``~/.foo-bar`` 382 | Win XP (roaming): 383 | ``C:\Documents and Settings\\Local Settings\Application Data\Foo Bar`` 384 | Win XP (not roaming): 385 | ``C:\Documents and Settings\\Application Data\Foo Bar`` 386 | Win 7 (roaming): 387 | ``C:\Users\\AppData\Roaming\Foo Bar`` 388 | Win 7 (not roaming): 389 | ``C:\Users\\AppData\Local\Foo Bar`` 390 | 391 | .. versionadded:: 2.0 392 | 393 | :param app_name: the application name. This should be properly capitalized 394 | and can contain whitespace. 395 | :param roaming: controls if the folder should be roaming or not on Windows. 396 | Has no affect otherwise. 397 | :param force_posix: if this is set to `True` then on any POSIX system the 398 | folder will be stored in the home folder with a leading 399 | dot instead of the XDG config home or darwin's 400 | application support folder. 401 | """ 402 | if WIN: 403 | key = roaming and 'APPDATA' or 'LOCALAPPDATA' 404 | folder = os.environ.get(key) 405 | if folder is None: 406 | folder = os.path.expanduser('~') 407 | return os.path.join(folder, app_name) 408 | if force_posix: 409 | return os.path.join(os.path.expanduser('~/.' + _posixify(app_name))) 410 | if sys.platform == 'darwin': 411 | return os.path.join(os.path.expanduser( 412 | '~/Library/Application Support'), app_name) 413 | return os.path.join( 414 | os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), 415 | _posixify(app_name)) 416 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/parser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | click.parser 4 | ~~~~~~~~~~~~ 5 | 6 | This module started out as largely a copy paste from the stdlib's 7 | optparse module with the features removed that we do not need from 8 | optparse because we implement them in Click on a higher level (for 9 | instance type handling, help formatting and a lot more). 10 | 11 | The plan is to remove more and more from here over time. 12 | 13 | The reason this is a different module and not optparse from the stdlib 14 | is that there are differences in 2.x and 3.x about the error messages 15 | generated and optparse in the stdlib uses gettext for no good reason 16 | and might cause us issues. 17 | """ 18 | import re 19 | from collections import deque 20 | from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \ 21 | BadArgumentUsage 22 | 23 | 24 | def _unpack_args(args, nargs_spec): 25 | """Given an iterable of arguments and an iterable of nargs specifications, 26 | it returns a tuple with all the unpacked arguments at the first index 27 | and all remaining arguments as the second. 28 | 29 | The nargs specification is the number of arguments that should be consumed 30 | or `-1` to indicate that this position should eat up all the remainders. 31 | 32 | Missing items are filled with `None`. 33 | """ 34 | args = deque(args) 35 | nargs_spec = deque(nargs_spec) 36 | rv = [] 37 | spos = None 38 | 39 | def _fetch(c): 40 | try: 41 | if spos is None: 42 | return c.popleft() 43 | else: 44 | return c.pop() 45 | except IndexError: 46 | return None 47 | 48 | while nargs_spec: 49 | nargs = _fetch(nargs_spec) 50 | if nargs == 1: 51 | rv.append(_fetch(args)) 52 | elif nargs > 1: 53 | x = [_fetch(args) for _ in range(nargs)] 54 | # If we're reversed, we're pulling in the arguments in reverse, 55 | # so we need to turn them around. 56 | if spos is not None: 57 | x.reverse() 58 | rv.append(tuple(x)) 59 | elif nargs < 0: 60 | if spos is not None: 61 | raise TypeError('Cannot have two nargs < 0') 62 | spos = len(rv) 63 | rv.append(None) 64 | 65 | # spos is the position of the wildcard (star). If it's not `None`, 66 | # we fill it with the remainder. 67 | if spos is not None: 68 | rv[spos] = tuple(args) 69 | args = [] 70 | rv[spos + 1:] = reversed(rv[spos + 1:]) 71 | 72 | return tuple(rv), list(args) 73 | 74 | 75 | def _error_opt_args(nargs, opt): 76 | if nargs == 1: 77 | raise BadOptionUsage('%s option requires an argument' % opt) 78 | raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs)) 79 | 80 | 81 | def split_opt(opt): 82 | first = opt[:1] 83 | if first.isalnum(): 84 | return '', opt 85 | if opt[1:2] == first: 86 | return opt[:2], opt[2:] 87 | return first, opt[1:] 88 | 89 | 90 | def normalize_opt(opt, ctx): 91 | if ctx is None or ctx.token_normalize_func is None: 92 | return opt 93 | prefix, opt = split_opt(opt) 94 | return prefix + ctx.token_normalize_func(opt) 95 | 96 | 97 | def split_arg_string(string): 98 | """Given an argument string this attempts to split it into small parts.""" 99 | rv = [] 100 | for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'" 101 | r'|"([^"\\]*(?:\\.[^"\\]*)*)"' 102 | r'|\S+)\s*', string, re.S): 103 | arg = match.group().strip() 104 | if arg[:1] == arg[-1:] and arg[:1] in '"\'': 105 | arg = arg[1:-1].encode('ascii', 'backslashreplace') \ 106 | .decode('unicode-escape') 107 | try: 108 | arg = type(string)(arg) 109 | except UnicodeError: 110 | pass 111 | rv.append(arg) 112 | return rv 113 | 114 | 115 | class Option(object): 116 | 117 | def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None): 118 | self._short_opts = [] 119 | self._long_opts = [] 120 | self.prefixes = set() 121 | 122 | for opt in opts: 123 | prefix, value = split_opt(opt) 124 | if not prefix: 125 | raise ValueError('Invalid start character for option (%s)' 126 | % opt) 127 | self.prefixes.add(prefix[0]) 128 | if len(prefix) == 1 and len(value) == 1: 129 | self._short_opts.append(opt) 130 | else: 131 | self._long_opts.append(opt) 132 | self.prefixes.add(prefix) 133 | 134 | if action is None: 135 | action = 'store' 136 | 137 | self.dest = dest 138 | self.action = action 139 | self.nargs = nargs 140 | self.const = const 141 | self.obj = obj 142 | 143 | @property 144 | def takes_value(self): 145 | return self.action in ('store', 'append') 146 | 147 | def process(self, value, state): 148 | if self.action == 'store': 149 | state.opts[self.dest] = value 150 | elif self.action == 'store_const': 151 | state.opts[self.dest] = self.const 152 | elif self.action == 'append': 153 | state.opts.setdefault(self.dest, []).append(value) 154 | elif self.action == 'append_const': 155 | state.opts.setdefault(self.dest, []).append(self.const) 156 | elif self.action == 'count': 157 | state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 158 | else: 159 | raise ValueError('unknown action %r' % self.action) 160 | state.order.append(self.obj) 161 | 162 | 163 | class Argument(object): 164 | 165 | def __init__(self, dest, nargs=1, obj=None): 166 | self.dest = dest 167 | self.nargs = nargs 168 | self.obj = obj 169 | 170 | def process(self, value, state): 171 | if self.nargs > 1: 172 | holes = sum(1 for x in value if x is None) 173 | if holes == len(value): 174 | value = None 175 | elif holes != 0: 176 | raise BadArgumentUsage('argument %s takes %d values' 177 | % (self.dest, self.nargs)) 178 | state.opts[self.dest] = value 179 | state.order.append(self.obj) 180 | 181 | 182 | class ParsingState(object): 183 | 184 | def __init__(self, rargs): 185 | self.opts = {} 186 | self.largs = [] 187 | self.rargs = rargs 188 | self.order = [] 189 | 190 | 191 | class OptionParser(object): 192 | """The option parser is an internal class that is ultimately used to 193 | parse options and arguments. It's modelled after optparse and brings 194 | a similar but vastly simplified API. It should generally not be used 195 | directly as the high level Click classes wrap it for you. 196 | 197 | It's not nearly as extensible as optparse or argparse as it does not 198 | implement features that are implemented on a higher level (such as 199 | types or defaults). 200 | 201 | :param ctx: optionally the :class:`~click.Context` where this parser 202 | should go with. 203 | """ 204 | 205 | def __init__(self, ctx=None): 206 | #: The :class:`~click.Context` for this parser. This might be 207 | #: `None` for some advanced use cases. 208 | self.ctx = ctx 209 | #: This controls how the parser deals with interspersed arguments. 210 | #: If this is set to `False`, the parser will stop on the first 211 | #: non-option. Click uses this to implement nested subcommands 212 | #: safely. 213 | self.allow_interspersed_args = True 214 | #: This tells the parser how to deal with unknown options. By 215 | #: default it will error out (which is sensible), but there is a 216 | #: second mode where it will ignore it and continue processing 217 | #: after shifting all the unknown options into the resulting args. 218 | self.ignore_unknown_options = False 219 | if ctx is not None: 220 | self.allow_interspersed_args = ctx.allow_interspersed_args 221 | self.ignore_unknown_options = ctx.ignore_unknown_options 222 | self._short_opt = {} 223 | self._long_opt = {} 224 | self._opt_prefixes = set(['-', '--']) 225 | self._args = [] 226 | 227 | def add_option(self, opts, dest, action=None, nargs=1, const=None, 228 | obj=None): 229 | """Adds a new option named `dest` to the parser. The destination 230 | is not inferred (unlike with optparse) and needs to be explicitly 231 | provided. Action can be any of ``store``, ``store_const``, 232 | ``append``, ``appnd_const`` or ``count``. 233 | 234 | The `obj` can be used to identify the option in the order list 235 | that is returned from the parser. 236 | """ 237 | if obj is None: 238 | obj = dest 239 | opts = [normalize_opt(opt, self.ctx) for opt in opts] 240 | option = Option(opts, dest, action=action, nargs=nargs, 241 | const=const, obj=obj) 242 | self._opt_prefixes.update(option.prefixes) 243 | for opt in option._short_opts: 244 | self._short_opt[opt] = option 245 | for opt in option._long_opts: 246 | self._long_opt[opt] = option 247 | 248 | def add_argument(self, dest, nargs=1, obj=None): 249 | """Adds a positional argument named `dest` to the parser. 250 | 251 | The `obj` can be used to identify the option in the order list 252 | that is returned from the parser. 253 | """ 254 | if obj is None: 255 | obj = dest 256 | self._args.append(Argument(dest=dest, nargs=nargs, obj=obj)) 257 | 258 | def parse_args(self, args): 259 | """Parses positional arguments and returns ``(values, args, order)`` 260 | for the parsed options and arguments as well as the leftover 261 | arguments if there are any. The order is a list of objects as they 262 | appear on the command line. If arguments appear multiple times they 263 | will be memorized multiple times as well. 264 | """ 265 | state = ParsingState(args) 266 | try: 267 | self._process_args_for_options(state) 268 | self._process_args_for_args(state) 269 | except UsageError: 270 | if self.ctx is None or not self.ctx.resilient_parsing: 271 | raise 272 | return state.opts, state.largs, state.order 273 | 274 | def _process_args_for_args(self, state): 275 | pargs, args = _unpack_args(state.largs + state.rargs, 276 | [x.nargs for x in self._args]) 277 | 278 | for idx, arg in enumerate(self._args): 279 | arg.process(pargs[idx], state) 280 | 281 | state.largs = args 282 | state.rargs = [] 283 | 284 | def _process_args_for_options(self, state): 285 | while state.rargs: 286 | arg = state.rargs.pop(0) 287 | arglen = len(arg) 288 | # Double dashes always handled explicitly regardless of what 289 | # prefixes are valid. 290 | if arg == '--': 291 | return 292 | elif arg[:1] in self._opt_prefixes and arglen > 1: 293 | self._process_opts(arg, state) 294 | elif self.allow_interspersed_args: 295 | state.largs.append(arg) 296 | else: 297 | state.rargs.insert(0, arg) 298 | return 299 | 300 | # Say this is the original argument list: 301 | # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] 302 | # ^ 303 | # (we are about to process arg(i)). 304 | # 305 | # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of 306 | # [arg0, ..., arg(i-1)] (any options and their arguments will have 307 | # been removed from largs). 308 | # 309 | # The while loop will usually consume 1 or more arguments per pass. 310 | # If it consumes 1 (eg. arg is an option that takes no arguments), 311 | # then after _process_arg() is done the situation is: 312 | # 313 | # largs = subset of [arg0, ..., arg(i)] 314 | # rargs = [arg(i+1), ..., arg(N-1)] 315 | # 316 | # If allow_interspersed_args is false, largs will always be 317 | # *empty* -- still a subset of [arg0, ..., arg(i-1)], but 318 | # not a very interesting subset! 319 | 320 | def _match_long_opt(self, opt, explicit_value, state): 321 | if opt not in self._long_opt: 322 | possibilities = [word for word in self._long_opt 323 | if word.startswith(opt)] 324 | raise NoSuchOption(opt, possibilities=possibilities) 325 | 326 | option = self._long_opt[opt] 327 | if option.takes_value: 328 | # At this point it's safe to modify rargs by injecting the 329 | # explicit value, because no exception is raised in this 330 | # branch. This means that the inserted value will be fully 331 | # consumed. 332 | if explicit_value is not None: 333 | state.rargs.insert(0, explicit_value) 334 | 335 | nargs = option.nargs 336 | if len(state.rargs) < nargs: 337 | _error_opt_args(nargs, opt) 338 | elif nargs == 1: 339 | value = state.rargs.pop(0) 340 | else: 341 | value = tuple(state.rargs[:nargs]) 342 | del state.rargs[:nargs] 343 | 344 | elif explicit_value is not None: 345 | raise BadOptionUsage('%s option does not take a value' % opt) 346 | 347 | else: 348 | value = None 349 | 350 | option.process(value, state) 351 | 352 | def _match_short_opt(self, arg, state): 353 | stop = False 354 | i = 1 355 | prefix = arg[0] 356 | unknown_options = [] 357 | 358 | for ch in arg[1:]: 359 | opt = normalize_opt(prefix + ch, self.ctx) 360 | option = self._short_opt.get(opt) 361 | i += 1 362 | 363 | if not option: 364 | if self.ignore_unknown_options: 365 | unknown_options.append(ch) 366 | continue 367 | raise NoSuchOption(opt) 368 | if option.takes_value: 369 | # Any characters left in arg? Pretend they're the 370 | # next arg, and stop consuming characters of arg. 371 | if i < len(arg): 372 | state.rargs.insert(0, arg[i:]) 373 | stop = True 374 | 375 | nargs = option.nargs 376 | if len(state.rargs) < nargs: 377 | _error_opt_args(nargs, opt) 378 | elif nargs == 1: 379 | value = state.rargs.pop(0) 380 | else: 381 | value = tuple(state.rargs[:nargs]) 382 | del state.rargs[:nargs] 383 | 384 | else: 385 | value = None 386 | 387 | option.process(value, state) 388 | 389 | if stop: 390 | break 391 | 392 | # If we got any unknown options we re-combinate the string of the 393 | # remaining options and re-attach the prefix, then report that 394 | # to the state as new larg. This way there is basic combinatorics 395 | # that can be achieved while still ignoring unknown arguments. 396 | if self.ignore_unknown_options and unknown_options: 397 | state.largs.append(prefix + ''.join(unknown_options)) 398 | 399 | def _process_opts(self, arg, state): 400 | explicit_value = None 401 | # Long option handling happens in two parts. The first part is 402 | # supporting explicitly attached values. In any case, we will try 403 | # to long match the option first. 404 | if '=' in arg: 405 | long_opt, explicit_value = arg.split('=', 1) 406 | else: 407 | long_opt = arg 408 | norm_long_opt = normalize_opt(long_opt, self.ctx) 409 | 410 | # At this point we will match the (assumed) long option through 411 | # the long option matching code. Note that this allows options 412 | # like "-foo" to be matched as long options. 413 | try: 414 | self._match_long_opt(norm_long_opt, explicit_value, state) 415 | except NoSuchOption: 416 | # At this point the long option matching failed, and we need 417 | # to try with short options. However there is a special rule 418 | # which says, that if we have a two character options prefix 419 | # (applies to "--foo" for instance), we do not dispatch to the 420 | # short option code and will instead raise the no option 421 | # error. 422 | if arg[:2] not in self._opt_prefixes: 423 | return self._match_short_opt(arg, state) 424 | if not self.ignore_unknown_options: 425 | raise 426 | state.largs.append(arg) 427 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/_termui_impl.py: -------------------------------------------------------------------------------- 1 | """ 2 | click._termui_impl 3 | ~~~~~~~~~~~~~~~~~~ 4 | 5 | This module contains implementations for the termui module. To keep the 6 | import time of Click down, some infrequently used functionality is placed 7 | in this module and only imported as needed. 8 | 9 | :copyright: (c) 2014 by Armin Ronacher. 10 | :license: BSD, see LICENSE for more details. 11 | """ 12 | import os 13 | import sys 14 | import time 15 | import math 16 | from ._compat import _default_text_stdout, range_type, PY2, isatty, \ 17 | open_stream, strip_ansi, term_len, get_best_encoding, WIN 18 | from .utils import echo 19 | from .exceptions import ClickException 20 | 21 | 22 | if os.name == 'nt': 23 | BEFORE_BAR = '\r' 24 | AFTER_BAR = '\n' 25 | else: 26 | BEFORE_BAR = '\r\033[?25l' 27 | AFTER_BAR = '\033[?25h\n' 28 | 29 | 30 | def _length_hint(obj): 31 | """Returns the length hint of an object.""" 32 | try: 33 | return len(obj) 34 | except TypeError: 35 | try: 36 | get_hint = type(obj).__length_hint__ 37 | except AttributeError: 38 | return None 39 | try: 40 | hint = get_hint(obj) 41 | except TypeError: 42 | return None 43 | if hint is NotImplemented or \ 44 | not isinstance(hint, (int, long)) or \ 45 | hint < 0: 46 | return None 47 | return hint 48 | 49 | 50 | class ProgressBar(object): 51 | 52 | def __init__(self, iterable, length=None, fill_char='#', empty_char=' ', 53 | bar_template='%(bar)s', info_sep=' ', show_eta=True, 54 | show_percent=None, show_pos=False, item_show_func=None, 55 | label=None, file=None, color=None, width=30): 56 | self.fill_char = fill_char 57 | self.empty_char = empty_char 58 | self.bar_template = bar_template 59 | self.info_sep = info_sep 60 | self.show_eta = show_eta 61 | self.show_percent = show_percent 62 | self.show_pos = show_pos 63 | self.item_show_func = item_show_func 64 | self.label = label or '' 65 | if file is None: 66 | file = _default_text_stdout() 67 | self.file = file 68 | self.color = color 69 | self.width = width 70 | self.autowidth = width == 0 71 | 72 | if length is None: 73 | length = _length_hint(iterable) 74 | if iterable is None: 75 | if length is None: 76 | raise TypeError('iterable or length is required') 77 | iterable = range_type(length) 78 | self.iter = iter(iterable) 79 | self.length = length 80 | self.length_known = length is not None 81 | self.pos = 0 82 | self.avg = [] 83 | self.start = self.last_eta = time.time() 84 | self.eta_known = False 85 | self.finished = False 86 | self.max_width = None 87 | self.entered = False 88 | self.current_item = None 89 | self.is_hidden = not isatty(self.file) 90 | self._last_line = None 91 | 92 | def __enter__(self): 93 | self.entered = True 94 | self.render_progress() 95 | return self 96 | 97 | def __exit__(self, exc_type, exc_value, tb): 98 | self.render_finish() 99 | 100 | def __iter__(self): 101 | if not self.entered: 102 | raise RuntimeError('You need to use progress bars in a with block.') 103 | self.render_progress() 104 | return self 105 | 106 | def render_finish(self): 107 | if self.is_hidden: 108 | return 109 | self.file.write(AFTER_BAR) 110 | self.file.flush() 111 | 112 | @property 113 | def pct(self): 114 | if self.finished: 115 | return 1.0 116 | return min(self.pos / (float(self.length) or 1), 1.0) 117 | 118 | @property 119 | def time_per_iteration(self): 120 | if not self.avg: 121 | return 0.0 122 | return sum(self.avg) / float(len(self.avg)) 123 | 124 | @property 125 | def eta(self): 126 | if self.length_known and not self.finished: 127 | return self.time_per_iteration * (self.length - self.pos) 128 | return 0.0 129 | 130 | def format_eta(self): 131 | if self.eta_known: 132 | t = self.eta + 1 133 | seconds = t % 60 134 | t /= 60 135 | minutes = t % 60 136 | t /= 60 137 | hours = t % 24 138 | t /= 24 139 | if t > 0: 140 | days = t 141 | return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds) 142 | else: 143 | return '%02d:%02d:%02d' % (hours, minutes, seconds) 144 | return '' 145 | 146 | def format_pos(self): 147 | pos = str(self.pos) 148 | if self.length_known: 149 | pos += '/%s' % self.length 150 | return pos 151 | 152 | def format_pct(self): 153 | return ('% 4d%%' % int(self.pct * 100))[1:] 154 | 155 | def format_progress_line(self): 156 | show_percent = self.show_percent 157 | 158 | info_bits = [] 159 | if self.length_known: 160 | bar_length = int(self.pct * self.width) 161 | bar = self.fill_char * bar_length 162 | bar += self.empty_char * (self.width - bar_length) 163 | if show_percent is None: 164 | show_percent = not self.show_pos 165 | else: 166 | if self.finished: 167 | bar = self.fill_char * self.width 168 | else: 169 | bar = list(self.empty_char * (self.width or 1)) 170 | if self.time_per_iteration != 0: 171 | bar[int((math.cos(self.pos * self.time_per_iteration) 172 | / 2.0 + 0.5) * self.width)] = self.fill_char 173 | bar = ''.join(bar) 174 | 175 | if self.show_pos: 176 | info_bits.append(self.format_pos()) 177 | if show_percent: 178 | info_bits.append(self.format_pct()) 179 | if self.show_eta and self.eta_known and not self.finished: 180 | info_bits.append(self.format_eta()) 181 | if self.item_show_func is not None: 182 | item_info = self.item_show_func(self.current_item) 183 | if item_info is not None: 184 | info_bits.append(item_info) 185 | 186 | return (self.bar_template % { 187 | 'label': self.label, 188 | 'bar': bar, 189 | 'info': self.info_sep.join(info_bits) 190 | }).rstrip() 191 | 192 | def render_progress(self): 193 | from .termui import get_terminal_size 194 | nl = False 195 | 196 | if self.is_hidden: 197 | buf = [self.label] 198 | nl = True 199 | else: 200 | buf = [] 201 | # Update width in case the terminal has been resized 202 | if self.autowidth: 203 | old_width = self.width 204 | self.width = 0 205 | clutter_length = term_len(self.format_progress_line()) 206 | new_width = max(0, get_terminal_size()[0] - clutter_length) 207 | if new_width < old_width: 208 | buf.append(BEFORE_BAR) 209 | buf.append(' ' * self.max_width) 210 | self.max_width = new_width 211 | self.width = new_width 212 | 213 | clear_width = self.width 214 | if self.max_width is not None: 215 | clear_width = self.max_width 216 | 217 | buf.append(BEFORE_BAR) 218 | line = self.format_progress_line() 219 | line_len = term_len(line) 220 | if self.max_width is None or self.max_width < line_len: 221 | self.max_width = line_len 222 | buf.append(line) 223 | 224 | buf.append(' ' * (clear_width - line_len)) 225 | line = ''.join(buf) 226 | 227 | # Render the line only if it changed. 228 | if line != self._last_line: 229 | self._last_line = line 230 | echo(line, file=self.file, color=self.color, nl=nl) 231 | self.file.flush() 232 | 233 | def make_step(self, n_steps): 234 | self.pos += n_steps 235 | if self.length_known and self.pos >= self.length: 236 | self.finished = True 237 | 238 | if (time.time() - self.last_eta) < 1.0: 239 | return 240 | 241 | self.last_eta = time.time() 242 | self.avg = self.avg[-6:] + [-(self.start - time.time()) / (self.pos)] 243 | 244 | self.eta_known = self.length_known 245 | 246 | def update(self, n_steps): 247 | self.make_step(n_steps) 248 | self.render_progress() 249 | 250 | def finish(self): 251 | self.eta_known = 0 252 | self.current_item = None 253 | self.finished = True 254 | 255 | def next(self): 256 | if self.is_hidden: 257 | return next(self.iter) 258 | try: 259 | rv = next(self.iter) 260 | self.current_item = rv 261 | except StopIteration: 262 | self.finish() 263 | self.render_progress() 264 | raise StopIteration() 265 | else: 266 | self.update(1) 267 | return rv 268 | 269 | if not PY2: 270 | __next__ = next 271 | del next 272 | 273 | 274 | def pager(text, color=None): 275 | """Decide what method to use for paging through text.""" 276 | stdout = _default_text_stdout() 277 | if not isatty(sys.stdin) or not isatty(stdout): 278 | return _nullpager(stdout, text, color) 279 | pager_cmd = (os.environ.get('PAGER', None) or '').strip() 280 | if pager_cmd: 281 | if WIN: 282 | return _tempfilepager(text, pager_cmd, color) 283 | return _pipepager(text, pager_cmd, color) 284 | if os.environ.get('TERM') in ('dumb', 'emacs'): 285 | return _nullpager(stdout, text, color) 286 | if WIN or sys.platform.startswith('os2'): 287 | return _tempfilepager(text, 'more <', color) 288 | if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0: 289 | return _pipepager(text, 'less', color) 290 | 291 | import tempfile 292 | fd, filename = tempfile.mkstemp() 293 | os.close(fd) 294 | try: 295 | if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0: 296 | return _pipepager(text, 'more', color) 297 | return _nullpager(stdout, text, color) 298 | finally: 299 | os.unlink(filename) 300 | 301 | 302 | def _pipepager(text, cmd, color): 303 | """Page through text by feeding it to another program. Invoking a 304 | pager through this might support colors. 305 | """ 306 | import subprocess 307 | env = dict(os.environ) 308 | 309 | # If we're piping to less we might support colors under the 310 | # condition that 311 | cmd_detail = cmd.rsplit('/', 1)[-1].split() 312 | if color is None and cmd_detail[0] == 'less': 313 | less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:]) 314 | if not less_flags: 315 | env['LESS'] = '-R' 316 | color = True 317 | elif 'r' in less_flags or 'R' in less_flags: 318 | color = True 319 | 320 | if not color: 321 | text = strip_ansi(text) 322 | 323 | c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, 324 | env=env) 325 | encoding = get_best_encoding(c.stdin) 326 | try: 327 | c.stdin.write(text.encode(encoding, 'replace')) 328 | c.stdin.close() 329 | except (IOError, KeyboardInterrupt): 330 | pass 331 | 332 | # Less doesn't respect ^C, but catches it for its own UI purposes (aborting 333 | # search or other commands inside less). 334 | # 335 | # That means when the user hits ^C, the parent process (click) terminates, 336 | # but less is still alive, paging the output and messing up the terminal. 337 | # 338 | # If the user wants to make the pager exit on ^C, they should set 339 | # `LESS='-K'`. It's not our decision to make. 340 | while True: 341 | try: 342 | c.wait() 343 | except KeyboardInterrupt: 344 | pass 345 | else: 346 | break 347 | 348 | 349 | def _tempfilepager(text, cmd, color): 350 | """Page through text by invoking a program on a temporary file.""" 351 | import tempfile 352 | filename = tempfile.mktemp() 353 | if not color: 354 | text = strip_ansi(text) 355 | encoding = get_best_encoding(sys.stdout) 356 | with open_stream(filename, 'wb')[0] as f: 357 | f.write(text.encode(encoding)) 358 | try: 359 | os.system(cmd + ' "' + filename + '"') 360 | finally: 361 | os.unlink(filename) 362 | 363 | 364 | def _nullpager(stream, text, color): 365 | """Simply print unformatted text. This is the ultimate fallback.""" 366 | if not color: 367 | text = strip_ansi(text) 368 | stream.write(text) 369 | 370 | 371 | class Editor(object): 372 | 373 | def __init__(self, editor=None, env=None, require_save=True, 374 | extension='.txt'): 375 | self.editor = editor 376 | self.env = env 377 | self.require_save = require_save 378 | self.extension = extension 379 | 380 | def get_editor(self): 381 | if self.editor is not None: 382 | return self.editor 383 | for key in 'VISUAL', 'EDITOR': 384 | rv = os.environ.get(key) 385 | if rv: 386 | return rv 387 | if WIN: 388 | return 'notepad' 389 | for editor in 'vim', 'nano': 390 | if os.system('which %s >/dev/null 2>&1' % editor) == 0: 391 | return editor 392 | return 'vi' 393 | 394 | def edit_file(self, filename): 395 | import subprocess 396 | editor = self.get_editor() 397 | if self.env: 398 | environ = os.environ.copy() 399 | environ.update(self.env) 400 | else: 401 | environ = None 402 | try: 403 | c = subprocess.Popen('%s "%s"' % (editor, filename), 404 | env=environ, shell=True) 405 | exit_code = c.wait() 406 | if exit_code != 0: 407 | raise ClickException('%s: Editing failed!' % editor) 408 | except OSError as e: 409 | raise ClickException('%s: Editing failed: %s' % (editor, e)) 410 | 411 | def edit(self, text): 412 | import tempfile 413 | 414 | text = text or '' 415 | if text and not text.endswith('\n'): 416 | text += '\n' 417 | 418 | fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension) 419 | try: 420 | if WIN: 421 | encoding = 'utf-8-sig' 422 | text = text.replace('\n', '\r\n') 423 | else: 424 | encoding = 'utf-8' 425 | text = text.encode(encoding) 426 | 427 | f = os.fdopen(fd, 'wb') 428 | f.write(text) 429 | f.close() 430 | timestamp = os.path.getmtime(name) 431 | 432 | self.edit_file(name) 433 | 434 | if self.require_save \ 435 | and os.path.getmtime(name) == timestamp: 436 | return None 437 | 438 | f = open(name, 'rb') 439 | try: 440 | rv = f.read() 441 | finally: 442 | f.close() 443 | return rv.decode('utf-8-sig').replace('\r\n', '\n') 444 | finally: 445 | os.unlink(name) 446 | 447 | 448 | def open_url(url, wait=False, locate=False): 449 | import subprocess 450 | 451 | def _unquote_file(url): 452 | try: 453 | import urllib 454 | except ImportError: 455 | import urllib 456 | if url.startswith('file://'): 457 | url = urllib.unquote(url[7:]) 458 | return url 459 | 460 | if sys.platform == 'darwin': 461 | args = ['open'] 462 | if wait: 463 | args.append('-W') 464 | if locate: 465 | args.append('-R') 466 | args.append(_unquote_file(url)) 467 | null = open('/dev/null', 'w') 468 | try: 469 | return subprocess.Popen(args, stderr=null).wait() 470 | finally: 471 | null.close() 472 | elif WIN: 473 | if locate: 474 | url = _unquote_file(url) 475 | args = 'explorer /select,"%s"' % _unquote_file( 476 | url.replace('"', '')) 477 | else: 478 | args = 'start %s "" "%s"' % ( 479 | wait and '/WAIT' or '', url.replace('"', '')) 480 | return os.system(args) 481 | 482 | try: 483 | if locate: 484 | url = os.path.dirname(_unquote_file(url)) or '.' 485 | else: 486 | url = _unquote_file(url) 487 | c = subprocess.Popen(['xdg-open', url]) 488 | if wait: 489 | return c.wait() 490 | return 0 491 | except OSError: 492 | if url.startswith(('http://', 'https://')) and not locate and not wait: 493 | import webbrowser 494 | webbrowser.open(url) 495 | return 0 496 | return 1 497 | 498 | 499 | def _translate_ch_to_exc(ch): 500 | if ch == '\x03': 501 | raise KeyboardInterrupt() 502 | if ch == '\x04': 503 | raise EOFError() 504 | 505 | 506 | if WIN: 507 | import msvcrt 508 | 509 | def getchar(echo): 510 | rv = msvcrt.getch() 511 | if echo: 512 | msvcrt.putchar(rv) 513 | _translate_ch_to_exc(rv) 514 | if PY2: 515 | enc = getattr(sys.stdin, 'encoding', None) 516 | if enc is not None: 517 | rv = rv.decode(enc, 'replace') 518 | else: 519 | rv = rv.decode('cp1252', 'replace') 520 | return rv 521 | else: 522 | import tty 523 | import termios 524 | 525 | def getchar(echo): 526 | if not isatty(sys.stdin): 527 | f = open('/dev/tty') 528 | fd = f.fileno() 529 | else: 530 | fd = sys.stdin.fileno() 531 | f = None 532 | try: 533 | old_settings = termios.tcgetattr(fd) 534 | try: 535 | tty.setraw(fd) 536 | ch = os.read(fd, 32) 537 | if echo and isatty(sys.stdout): 538 | sys.stdout.write(ch) 539 | finally: 540 | termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) 541 | sys.stdout.flush() 542 | if f is not None: 543 | f.close() 544 | except termios.error: 545 | pass 546 | _translate_ch_to_exc(ch) 547 | return ch.decode(get_best_encoding(sys.stdin), 'replace') 548 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/types.py: -------------------------------------------------------------------------------- 1 | import os 2 | import stat 3 | 4 | from ._compat import open_stream, text_type, filename_to_ui, \ 5 | get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2 6 | from .exceptions import BadParameter 7 | from .utils import safecall, LazyFile 8 | 9 | 10 | class ParamType(object): 11 | """Helper for converting values through types. The following is 12 | necessary for a valid type: 13 | 14 | * it needs a name 15 | * it needs to pass through None unchanged 16 | * it needs to convert from a string 17 | * it needs to convert its result type through unchanged 18 | (eg: needs to be idempotent) 19 | * it needs to be able to deal with param and context being `None`. 20 | This can be the case when the object is used with prompt 21 | inputs. 22 | """ 23 | is_composite = False 24 | 25 | #: the descriptive name of this type 26 | name = None 27 | 28 | #: if a list of this type is expected and the value is pulled from a 29 | #: string environment variable, this is what splits it up. `None` 30 | #: means any whitespace. For all parameters the general rule is that 31 | #: whitespace splits them up. The exception are paths and files which 32 | #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on 33 | #: Windows). 34 | envvar_list_splitter = None 35 | 36 | def __call__(self, value, param=None, ctx=None): 37 | if value is not None: 38 | return self.convert(value, param, ctx) 39 | 40 | def get_metavar(self, param): 41 | """Returns the metavar default for this param if it provides one.""" 42 | 43 | def get_missing_message(self, param): 44 | """Optionally might return extra information about a missing 45 | parameter. 46 | 47 | .. versionadded:: 2.0 48 | """ 49 | 50 | def convert(self, value, param, ctx): 51 | """Converts the value. This is not invoked for values that are 52 | `None` (the missing value). 53 | """ 54 | return value 55 | 56 | def split_envvar_value(self, rv): 57 | """Given a value from an environment variable this splits it up 58 | into small chunks depending on the defined envvar list splitter. 59 | 60 | If the splitter is set to `None`, which means that whitespace splits, 61 | then leading and trailing whitespace is ignored. Otherwise, leading 62 | and trailing splitters usually lead to empty items being included. 63 | """ 64 | return (rv or '').split(self.envvar_list_splitter) 65 | 66 | def fail(self, message, param=None, ctx=None): 67 | """Helper method to fail with an invalid value message.""" 68 | raise BadParameter(message, ctx=ctx, param=param) 69 | 70 | 71 | class CompositeParamType(ParamType): 72 | is_composite = True 73 | 74 | @property 75 | def arity(self): 76 | raise NotImplementedError() 77 | 78 | 79 | class FuncParamType(ParamType): 80 | 81 | def __init__(self, func): 82 | self.name = func.__name__ 83 | self.func = func 84 | 85 | def convert(self, value, param, ctx): 86 | try: 87 | return self.func(value) 88 | except ValueError: 89 | try: 90 | value = text_type(value) 91 | except UnicodeError: 92 | value = str(value).decode('utf-8', 'replace') 93 | self.fail(value, param, ctx) 94 | 95 | 96 | class UnprocessedParamType(ParamType): 97 | name = 'text' 98 | 99 | def convert(self, value, param, ctx): 100 | return value 101 | 102 | def __repr__(self): 103 | return 'UNPROCESSED' 104 | 105 | 106 | class StringParamType(ParamType): 107 | name = 'text' 108 | 109 | def convert(self, value, param, ctx): 110 | if isinstance(value, bytes): 111 | enc = _get_argv_encoding() 112 | try: 113 | value = value.decode(enc) 114 | except UnicodeError: 115 | fs_enc = get_filesystem_encoding() 116 | if fs_enc != enc: 117 | try: 118 | value = value.decode(fs_enc) 119 | except UnicodeError: 120 | value = value.decode('utf-8', 'replace') 121 | return value 122 | return value 123 | 124 | def __repr__(self): 125 | return 'STRING' 126 | 127 | 128 | class Choice(ParamType): 129 | """The choice type allows a value to be checked against a fixed set of 130 | supported values. All of these values have to be strings. 131 | 132 | See :ref:`choice-opts` for an example. 133 | """ 134 | name = 'choice' 135 | 136 | def __init__(self, choices): 137 | self.choices = choices 138 | 139 | def get_metavar(self, param): 140 | return '[%s]' % '|'.join(self.choices) 141 | 142 | def get_missing_message(self, param): 143 | return 'Choose from %s.' % ', '.join(self.choices) 144 | 145 | def convert(self, value, param, ctx): 146 | # Exact match 147 | if value in self.choices: 148 | return value 149 | 150 | # Match through normalization 151 | if ctx is not None and \ 152 | ctx.token_normalize_func is not None: 153 | value = ctx.token_normalize_func(value) 154 | for choice in self.choices: 155 | if ctx.token_normalize_func(choice) == value: 156 | return choice 157 | 158 | self.fail('invalid choice: %s. (choose from %s)' % 159 | (value, ', '.join(self.choices)), param, ctx) 160 | 161 | def __repr__(self): 162 | return 'Choice(%r)' % list(self.choices) 163 | 164 | 165 | class IntParamType(ParamType): 166 | name = 'integer' 167 | 168 | def convert(self, value, param, ctx): 169 | try: 170 | return int(value) 171 | except (ValueError, UnicodeError): 172 | self.fail('%s is not a valid integer' % value, param, ctx) 173 | 174 | def __repr__(self): 175 | return 'INT' 176 | 177 | 178 | class IntRange(IntParamType): 179 | """A parameter that works similar to :data:`click.INT` but restricts 180 | the value to fit into a range. The default behavior is to fail if the 181 | value falls outside the range, but it can also be silently clamped 182 | between the two edges. 183 | 184 | See :ref:`ranges` for an example. 185 | """ 186 | name = 'integer range' 187 | 188 | def __init__(self, min=None, max=None, clamp=False): 189 | self.min = min 190 | self.max = max 191 | self.clamp = clamp 192 | 193 | def convert(self, value, param, ctx): 194 | rv = IntParamType.convert(self, value, param, ctx) 195 | if self.clamp: 196 | if self.min is not None and rv < self.min: 197 | return self.min 198 | if self.max is not None and rv > self.max: 199 | return self.max 200 | if self.min is not None and rv < self.min or \ 201 | self.max is not None and rv > self.max: 202 | if self.min is None: 203 | self.fail('%s is bigger than the maximum valid value ' 204 | '%s.' % (rv, self.max), param, ctx) 205 | elif self.max is None: 206 | self.fail('%s is smaller than the minimum valid value ' 207 | '%s.' % (rv, self.min), param, ctx) 208 | else: 209 | self.fail('%s is not in the valid range of %s to %s.' 210 | % (rv, self.min, self.max), param, ctx) 211 | return rv 212 | 213 | def __repr__(self): 214 | return 'IntRange(%r, %r)' % (self.min, self.max) 215 | 216 | 217 | class BoolParamType(ParamType): 218 | name = 'boolean' 219 | 220 | def convert(self, value, param, ctx): 221 | if isinstance(value, bool): 222 | return bool(value) 223 | value = value.lower() 224 | if value in ('true', '1', 'yes', 'y'): 225 | return True 226 | elif value in ('false', '0', 'no', 'n'): 227 | return False 228 | self.fail('%s is not a valid boolean' % value, param, ctx) 229 | 230 | def __repr__(self): 231 | return 'BOOL' 232 | 233 | 234 | class FloatParamType(ParamType): 235 | name = 'float' 236 | 237 | def convert(self, value, param, ctx): 238 | try: 239 | return float(value) 240 | except (UnicodeError, ValueError): 241 | self.fail('%s is not a valid floating point value' % 242 | value, param, ctx) 243 | 244 | def __repr__(self): 245 | return 'FLOAT' 246 | 247 | 248 | class UUIDParameterType(ParamType): 249 | name = 'uuid' 250 | 251 | def convert(self, value, param, ctx): 252 | import uuid 253 | try: 254 | if PY2 and isinstance(value, text_type): 255 | value = value.encode('ascii') 256 | return uuid.UUID(value) 257 | except (UnicodeError, ValueError): 258 | self.fail('%s is not a valid UUID value' % value, param, ctx) 259 | 260 | def __repr__(self): 261 | return 'UUID' 262 | 263 | 264 | class File(ParamType): 265 | """Declares a parameter to be a file for reading or writing. The file 266 | is automatically closed once the context tears down (after the command 267 | finished working). 268 | 269 | Files can be opened for reading or writing. The special value ``-`` 270 | indicates stdin or stdout depending on the mode. 271 | 272 | By default, the file is opened for reading text data, but it can also be 273 | opened in binary mode or for writing. The encoding parameter can be used 274 | to force a specific encoding. 275 | 276 | The `lazy` flag controls if the file should be opened immediately or 277 | upon first IO. The default is to be non lazy for standard input and 278 | output streams as well as files opened for reading, lazy otherwise. 279 | 280 | Starting with Click 2.0, files can also be opened atomically in which 281 | case all writes go into a separate file in the same folder and upon 282 | completion the file will be moved over to the original location. This 283 | is useful if a file regularly read by other users is modified. 284 | 285 | See :ref:`file-args` for more information. 286 | """ 287 | name = 'filename' 288 | envvar_list_splitter = os.path.pathsep 289 | 290 | def __init__(self, mode='r', encoding=None, errors='strict', lazy=None, 291 | atomic=False): 292 | self.mode = mode 293 | self.encoding = encoding 294 | self.errors = errors 295 | self.lazy = lazy 296 | self.atomic = atomic 297 | 298 | def resolve_lazy_flag(self, value): 299 | if self.lazy is not None: 300 | return self.lazy 301 | if value == '-': 302 | return False 303 | elif 'w' in self.mode: 304 | return True 305 | return False 306 | 307 | def convert(self, value, param, ctx): 308 | try: 309 | if hasattr(value, 'read') or hasattr(value, 'write'): 310 | return value 311 | 312 | lazy = self.resolve_lazy_flag(value) 313 | 314 | if lazy: 315 | f = LazyFile(value, self.mode, self.encoding, self.errors, 316 | atomic=self.atomic) 317 | if ctx is not None: 318 | ctx.call_on_close(f.close_intelligently) 319 | return f 320 | 321 | f, should_close = open_stream(value, self.mode, 322 | self.encoding, self.errors, 323 | atomic=self.atomic) 324 | # If a context is provided, we automatically close the file 325 | # at the end of the context execution (or flush out). If a 326 | # context does not exist, it's the caller's responsibility to 327 | # properly close the file. This for instance happens when the 328 | # type is used with prompts. 329 | if ctx is not None: 330 | if should_close: 331 | ctx.call_on_close(safecall(f.close)) 332 | else: 333 | ctx.call_on_close(safecall(f.flush)) 334 | return f 335 | except (IOError, OSError) as e: 336 | self.fail('Could not open file: %s: %s' % ( 337 | filename_to_ui(value), 338 | get_streerror(e), 339 | ), param, ctx) 340 | 341 | 342 | class Path(ParamType): 343 | """The path type is similar to the :class:`File` type but it performs 344 | different checks. First of all, instead of returning an open file 345 | handle it returns just the filename. Secondly, it can perform various 346 | basic checks about what the file or directory should be. 347 | 348 | .. versionchanged:: 6.0 349 | `allow_dash` was added. 350 | 351 | :param exists: if set to true, the file or directory needs to exist for 352 | this value to be valid. If this is not required and a 353 | file does indeed not exist, then all further checks are 354 | silently skipped. 355 | :param file_okay: controls if a file is a possible value. 356 | :param dir_okay: controls if a directory is a possible value. 357 | :param writable: if true, a writable check is performed. 358 | :param readable: if true, a readable check is performed. 359 | :param resolve_path: if this is true, then the path is fully resolved 360 | before the value is passed onwards. This means 361 | that it's absolute and symlinks are resolved. 362 | :param allow_dash: If this is set to `True`, a single dash to indicate 363 | standard streams is permitted. 364 | :param type: optionally a string type that should be used to 365 | represent the path. The default is `None` which 366 | means the return value will be either bytes or 367 | unicode depending on what makes most sense given the 368 | input data Click deals with. 369 | """ 370 | envvar_list_splitter = os.path.pathsep 371 | 372 | def __init__(self, exists=False, file_okay=True, dir_okay=True, 373 | writable=False, readable=True, resolve_path=False, 374 | allow_dash=False, path_type=None): 375 | self.exists = exists 376 | self.file_okay = file_okay 377 | self.dir_okay = dir_okay 378 | self.writable = writable 379 | self.readable = readable 380 | self.resolve_path = resolve_path 381 | self.allow_dash = allow_dash 382 | self.type = path_type 383 | 384 | if self.file_okay and not self.dir_okay: 385 | self.name = 'file' 386 | self.path_type = 'File' 387 | if self.dir_okay and not self.file_okay: 388 | self.name = 'directory' 389 | self.path_type = 'Directory' 390 | else: 391 | self.name = 'path' 392 | self.path_type = 'Path' 393 | 394 | def coerce_path_result(self, rv): 395 | if self.type is not None and not isinstance(rv, self.type): 396 | if self.type is text_type: 397 | rv = rv.decode(get_filesystem_encoding()) 398 | else: 399 | rv = rv.encode(get_filesystem_encoding()) 400 | return rv 401 | 402 | def convert(self, value, param, ctx): 403 | rv = value 404 | 405 | is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-') 406 | 407 | if not is_dash: 408 | if self.resolve_path: 409 | rv = os.path.realpath(rv) 410 | 411 | try: 412 | st = os.stat(rv) 413 | except OSError: 414 | if not self.exists: 415 | return self.coerce_path_result(rv) 416 | self.fail('%s "%s" does not exist.' % ( 417 | self.path_type, 418 | filename_to_ui(value) 419 | ), param, ctx) 420 | 421 | if not self.file_okay and stat.S_ISREG(st.st_mode): 422 | self.fail('%s "%s" is a file.' % ( 423 | self.path_type, 424 | filename_to_ui(value) 425 | ), param, ctx) 426 | if not self.dir_okay and stat.S_ISDIR(st.st_mode): 427 | self.fail('%s "%s" is a directory.' % ( 428 | self.path_type, 429 | filename_to_ui(value) 430 | ), param, ctx) 431 | if self.writable and not os.access(value, os.W_OK): 432 | self.fail('%s "%s" is not writable.' % ( 433 | self.path_type, 434 | filename_to_ui(value) 435 | ), param, ctx) 436 | if self.readable and not os.access(value, os.R_OK): 437 | self.fail('%s "%s" is not readable.' % ( 438 | self.path_type, 439 | filename_to_ui(value) 440 | ), param, ctx) 441 | 442 | return self.coerce_path_result(rv) 443 | 444 | 445 | class Tuple(CompositeParamType): 446 | """The default behavior of Click is to apply a type on a value directly. 447 | This works well in most cases, except for when `nargs` is set to a fixed 448 | count and different types should be used for different items. In this 449 | case the :class:`Tuple` type can be used. This type can only be used 450 | if `nargs` is set to a fixed number. 451 | 452 | For more information see :ref:`tuple-type`. 453 | 454 | This can be selected by using a Python tuple literal as a type. 455 | 456 | :param types: a list of types that should be used for the tuple items. 457 | """ 458 | 459 | def __init__(self, types): 460 | self.types = [convert_type(ty) for ty in types] 461 | 462 | @property 463 | def name(self): 464 | return "<" + " ".join(ty.name for ty in self.types) + ">" 465 | 466 | @property 467 | def arity(self): 468 | return len(self.types) 469 | 470 | def convert(self, value, param, ctx): 471 | if len(value) != len(self.types): 472 | raise TypeError('It would appear that nargs is set to conflict ' 473 | 'with the composite type arity.') 474 | return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) 475 | 476 | 477 | def convert_type(ty, default=None): 478 | """Converts a callable or python ty into the most appropriate param 479 | ty. 480 | """ 481 | guessed_type = False 482 | if ty is None and default is not None: 483 | if isinstance(default, tuple): 484 | ty = tuple(map(type, default)) 485 | else: 486 | ty = type(default) 487 | guessed_type = True 488 | 489 | if isinstance(ty, tuple): 490 | return Tuple(ty) 491 | if isinstance(ty, ParamType): 492 | return ty 493 | if ty is text_type or ty is str or ty is None: 494 | return STRING 495 | if ty is int: 496 | return INT 497 | # Booleans are only okay if not guessed. This is done because for 498 | # flags the default value is actually a bit of a lie in that it 499 | # indicates which of the flags is the one we want. See get_default() 500 | # for more information. 501 | if ty is bool and not guessed_type: 502 | return BOOL 503 | if ty is float: 504 | return FLOAT 505 | if guessed_type: 506 | return STRING 507 | 508 | # Catch a common mistake 509 | if __debug__: 510 | try: 511 | if issubclass(ty, ParamType): 512 | raise AssertionError('Attempted to use an uninstantiated ' 513 | 'parameter type (%s).' % ty) 514 | except TypeError: 515 | pass 516 | return FuncParamType(ty) 517 | 518 | 519 | #: A dummy parameter type that just does nothing. From a user's 520 | #: perspective this appears to just be the same as `STRING` but internally 521 | #: no string conversion takes place. This is necessary to achieve the 522 | #: same bytes/unicode behavior on Python 2/3 in situations where you want 523 | #: to not convert argument types. This is usually useful when working 524 | #: with file paths as they can appear in bytes and unicode. 525 | #: 526 | #: For path related uses the :class:`Path` type is a better choice but 527 | #: there are situations where an unprocessed type is useful which is why 528 | #: it is is provided. 529 | #: 530 | #: .. versionadded:: 4.0 531 | UNPROCESSED = UnprocessedParamType() 532 | 533 | #: A unicode string parameter type which is the implicit default. This 534 | #: can also be selected by using ``str`` as type. 535 | STRING = StringParamType() 536 | 537 | #: An integer parameter. This can also be selected by using ``int`` as 538 | #: type. 539 | INT = IntParamType() 540 | 541 | #: A floating point value parameter. This can also be selected by using 542 | #: ``float`` as type. 543 | FLOAT = FloatParamType() 544 | 545 | #: A boolean parameter. This is the default for boolean flags. This can 546 | #: also be selected by using ``bool`` as a type. 547 | BOOL = BoolParamType() 548 | 549 | #: A UUID parameter. 550 | UUID = UUIDParameterType() 551 | -------------------------------------------------------------------------------- /pycoreutils/vendor/click/termui.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import struct 4 | 5 | from ._compat import raw_input, text_type, string_types, \ 6 | isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN 7 | from .utils import echo 8 | from .exceptions import Abort, UsageError 9 | from .types import convert_type 10 | from .globals import resolve_color_default 11 | 12 | 13 | # The prompt functions to use. The doc tools currently override these 14 | # functions to customize how they work. 15 | visible_prompt_func = raw_input 16 | 17 | _ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 18 | 'cyan', 'white', 'reset') 19 | _ansi_reset_all = '\033[0m' 20 | 21 | 22 | def hidden_prompt_func(prompt): 23 | import getpass 24 | return getpass.getpass(prompt) 25 | 26 | 27 | def _build_prompt(text, suffix, show_default=False, default=None): 28 | prompt = text 29 | if default is not None and show_default: 30 | prompt = '%s [%s]' % (prompt, default) 31 | return prompt + suffix 32 | 33 | 34 | def prompt(text, default=None, hide_input=False, 35 | confirmation_prompt=False, type=None, 36 | value_proc=None, prompt_suffix=': ', 37 | show_default=True, err=False): 38 | """Prompts a user for input. This is a convenience function that can 39 | be used to prompt a user for input later. 40 | 41 | If the user aborts the input by sending a interrupt signal, this 42 | function will catch it and raise a :exc:`Abort` exception. 43 | 44 | .. versionadded:: 6.0 45 | Added unicode support for cmd.exe on Windows. 46 | 47 | .. versionadded:: 4.0 48 | Added the `err` parameter. 49 | 50 | :param text: the text to show for the prompt. 51 | :param default: the default value to use if no input happens. If this 52 | is not given it will prompt until it's aborted. 53 | :param hide_input: if this is set to true then the input value will 54 | be hidden. 55 | :param confirmation_prompt: asks for confirmation for the value. 56 | :param type: the type to use to check the value against. 57 | :param value_proc: if this parameter is provided it's a function that 58 | is invoked instead of the type conversion to 59 | convert a value. 60 | :param prompt_suffix: a suffix that should be added to the prompt. 61 | :param show_default: shows or hides the default value in the prompt. 62 | :param err: if set to true the file defaults to ``stderr`` instead of 63 | ``stdout``, the same as with echo. 64 | """ 65 | result = None 66 | 67 | def prompt_func(text): 68 | f = hide_input and hidden_prompt_func or visible_prompt_func 69 | try: 70 | # Write the prompt separately so that we get nice 71 | # coloring through colorama on Windows 72 | echo(text, nl=False, err=err) 73 | return f('') 74 | except (KeyboardInterrupt, EOFError): 75 | # getpass doesn't print a newline if the user aborts input with ^C. 76 | # Allegedly this behavior is inherited from getpass(3). 77 | # A doc bug has been filed at https://bugs.python.org/issue24711 78 | if hide_input: 79 | echo(None, err=err) 80 | raise Abort() 81 | 82 | if value_proc is None: 83 | value_proc = convert_type(type, default) 84 | 85 | prompt = _build_prompt(text, prompt_suffix, show_default, default) 86 | 87 | while 1: 88 | while 1: 89 | value = prompt_func(prompt) 90 | if value: 91 | break 92 | # If a default is set and used, then the confirmation 93 | # prompt is always skipped because that's the only thing 94 | # that really makes sense. 95 | elif default is not None: 96 | return default 97 | try: 98 | result = value_proc(value) 99 | except UsageError as e: 100 | echo('Error: %s' % e.message, err=err) 101 | continue 102 | if not confirmation_prompt: 103 | return result 104 | while 1: 105 | value2 = prompt_func('Repeat for confirmation: ') 106 | if value2: 107 | break 108 | if value == value2: 109 | return result 110 | echo('Error: the two entered values do not match', err=err) 111 | 112 | 113 | def confirm(text, default=False, abort=False, prompt_suffix=': ', 114 | show_default=True, err=False): 115 | """Prompts for confirmation (yes/no question). 116 | 117 | If the user aborts the input by sending a interrupt signal this 118 | function will catch it and raise a :exc:`Abort` exception. 119 | 120 | .. versionadded:: 4.0 121 | Added the `err` parameter. 122 | 123 | :param text: the question to ask. 124 | :param default: the default for the prompt. 125 | :param abort: if this is set to `True` a negative answer aborts the 126 | exception by raising :exc:`Abort`. 127 | :param prompt_suffix: a suffix that should be added to the prompt. 128 | :param show_default: shows or hides the default value in the prompt. 129 | :param err: if set to true the file defaults to ``stderr`` instead of 130 | ``stdout``, the same as with echo. 131 | """ 132 | prompt = _build_prompt(text, prompt_suffix, show_default, 133 | default and 'Y/n' or 'y/N') 134 | while 1: 135 | try: 136 | # Write the prompt separately so that we get nice 137 | # coloring through colorama on Windows 138 | echo(prompt, nl=False, err=err) 139 | value = visible_prompt_func('').lower().strip() 140 | except (KeyboardInterrupt, EOFError): 141 | raise Abort() 142 | if value in ('y', 'yes'): 143 | rv = True 144 | elif value in ('n', 'no'): 145 | rv = False 146 | elif value == '': 147 | rv = default 148 | else: 149 | echo('Error: invalid input', err=err) 150 | continue 151 | break 152 | if abort and not rv: 153 | raise Abort() 154 | return rv 155 | 156 | 157 | def get_terminal_size(): 158 | """Returns the current size of the terminal as tuple in the form 159 | ``(width, height)`` in columns and rows. 160 | """ 161 | # If shutil has get_terminal_size() (Python 3.3 and later) use that 162 | if sys.version_info >= (3, 3): 163 | import shutil 164 | shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None) 165 | if shutil_get_terminal_size: 166 | sz = shutil_get_terminal_size() 167 | return sz.columns, sz.lines 168 | 169 | if get_winterm_size is not None: 170 | return get_winterm_size() 171 | 172 | def ioctl_gwinsz(fd): 173 | try: 174 | import fcntl 175 | import termios 176 | cr = struct.unpack( 177 | 'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) 178 | except Exception: 179 | return 180 | return cr 181 | 182 | cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2) 183 | if not cr: 184 | try: 185 | fd = os.open(os.ctermid(), os.O_RDONLY) 186 | try: 187 | cr = ioctl_gwinsz(fd) 188 | finally: 189 | os.close(fd) 190 | except Exception: 191 | pass 192 | if not cr or not cr[0] or not cr[1]: 193 | cr = (os.environ.get('LINES', 25), 194 | os.environ.get('COLUMNS', DEFAULT_COLUMNS)) 195 | return int(cr[1]), int(cr[0]) 196 | 197 | 198 | def echo_via_pager(text, color=None): 199 | """This function takes a text and shows it via an environment specific 200 | pager on stdout. 201 | 202 | .. versionchanged:: 3.0 203 | Added the `color` flag. 204 | 205 | :param text: the text to page. 206 | :param color: controls if the pager supports ANSI colors or not. The 207 | default is autodetection. 208 | """ 209 | color = resolve_color_default(color) 210 | if not isinstance(text, string_types): 211 | text = text_type(text) 212 | from ._termui_impl import pager 213 | return pager(text + '\n', color) 214 | 215 | 216 | def progressbar(iterable=None, length=None, label=None, show_eta=True, 217 | show_percent=None, show_pos=False, 218 | item_show_func=None, fill_char='#', empty_char='-', 219 | bar_template='%(label)s [%(bar)s] %(info)s', 220 | info_sep=' ', width=36, file=None, color=None): 221 | """This function creates an iterable context manager that can be used 222 | to iterate over something while showing a progress bar. It will 223 | either iterate over the `iterable` or `length` items (that are counted 224 | up). While iteration happens, this function will print a rendered 225 | progress bar to the given `file` (defaults to stdout) and will attempt 226 | to calculate remaining time and more. By default, this progress bar 227 | will not be rendered if the file is not a terminal. 228 | 229 | The context manager creates the progress bar. When the context 230 | manager is entered the progress bar is already displayed. With every 231 | iteration over the progress bar, the iterable passed to the bar is 232 | advanced and the bar is updated. When the context manager exits, 233 | a newline is printed and the progress bar is finalized on screen. 234 | 235 | No printing must happen or the progress bar will be unintentionally 236 | destroyed. 237 | 238 | Example usage:: 239 | 240 | with progressbar(items) as bar: 241 | for item in bar: 242 | do_something_with(item) 243 | 244 | Alternatively, if no iterable is specified, one can manually update the 245 | progress bar through the `update()` method instead of directly 246 | iterating over the progress bar. The update method accepts the number 247 | of steps to increment the bar with:: 248 | 249 | with progressbar(length=chunks.total_bytes) as bar: 250 | for chunk in chunks: 251 | process_chunk(chunk) 252 | bar.update(chunks.bytes) 253 | 254 | .. versionadded:: 2.0 255 | 256 | .. versionadded:: 4.0 257 | Added the `color` parameter. Added a `update` method to the 258 | progressbar object. 259 | 260 | :param iterable: an iterable to iterate over. If not provided the length 261 | is required. 262 | :param length: the number of items to iterate over. By default the 263 | progressbar will attempt to ask the iterator about its 264 | length, which might or might not work. If an iterable is 265 | also provided this parameter can be used to override the 266 | length. If an iterable is not provided the progress bar 267 | will iterate over a range of that length. 268 | :param label: the label to show next to the progress bar. 269 | :param show_eta: enables or disables the estimated time display. This is 270 | automatically disabled if the length cannot be 271 | determined. 272 | :param show_percent: enables or disables the percentage display. The 273 | default is `True` if the iterable has a length or 274 | `False` if not. 275 | :param show_pos: enables or disables the absolute position display. The 276 | default is `False`. 277 | :param item_show_func: a function called with the current item which 278 | can return a string to show the current item 279 | next to the progress bar. Note that the current 280 | item can be `None`! 281 | :param fill_char: the character to use to show the filled part of the 282 | progress bar. 283 | :param empty_char: the character to use to show the non-filled part of 284 | the progress bar. 285 | :param bar_template: the format string to use as template for the bar. 286 | The parameters in it are ``label`` for the label, 287 | ``bar`` for the progress bar and ``info`` for the 288 | info section. 289 | :param info_sep: the separator between multiple info items (eta etc.) 290 | :param width: the width of the progress bar in characters, 0 means full 291 | terminal width 292 | :param file: the file to write to. If this is not a terminal then 293 | only the label is printed. 294 | :param color: controls if the terminal supports ANSI colors or not. The 295 | default is autodetection. This is only needed if ANSI 296 | codes are included anywhere in the progress bar output 297 | which is not the case by default. 298 | """ 299 | from ._termui_impl import ProgressBar 300 | color = resolve_color_default(color) 301 | return ProgressBar(iterable=iterable, length=length, show_eta=show_eta, 302 | show_percent=show_percent, show_pos=show_pos, 303 | item_show_func=item_show_func, fill_char=fill_char, 304 | empty_char=empty_char, bar_template=bar_template, 305 | info_sep=info_sep, file=file, label=label, 306 | width=width, color=color) 307 | 308 | 309 | def clear(): 310 | """Clears the terminal screen. This will have the effect of clearing 311 | the whole visible space of the terminal and moving the cursor to the 312 | top left. This does not do anything if not connected to a terminal. 313 | 314 | .. versionadded:: 2.0 315 | """ 316 | if not isatty(sys.stdout): 317 | return 318 | # If we're on Windows and we don't have colorama available, then we 319 | # clear the screen by shelling out. Otherwise we can use an escape 320 | # sequence. 321 | if WIN: 322 | os.system('cls') 323 | else: 324 | sys.stdout.write('\033[2J\033[1;1H') 325 | 326 | 327 | def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, 328 | blink=None, reverse=None, reset=True): 329 | """Styles a text with ANSI styles and returns the new string. By 330 | default the styling is self contained which means that at the end 331 | of the string a reset code is issued. This can be prevented by 332 | passing ``reset=False``. 333 | 334 | Examples:: 335 | 336 | click.echo(click.style('Hello World!', fg='green')) 337 | click.echo(click.style('ATTENTION!', blink=True)) 338 | click.echo(click.style('Some things', reverse=True, fg='cyan')) 339 | 340 | Supported color names: 341 | 342 | * ``black`` (might be a gray) 343 | * ``red`` 344 | * ``green`` 345 | * ``yellow`` (might be an orange) 346 | * ``blue`` 347 | * ``magenta`` 348 | * ``cyan`` 349 | * ``white`` (might be light gray) 350 | * ``reset`` (reset the color code only) 351 | 352 | .. versionadded:: 2.0 353 | 354 | :param text: the string to style with ansi codes. 355 | :param fg: if provided this will become the foreground color. 356 | :param bg: if provided this will become the background color. 357 | :param bold: if provided this will enable or disable bold mode. 358 | :param dim: if provided this will enable or disable dim mode. This is 359 | badly supported. 360 | :param underline: if provided this will enable or disable underline. 361 | :param blink: if provided this will enable or disable blinking. 362 | :param reverse: if provided this will enable or disable inverse 363 | rendering (foreground becomes background and the 364 | other way round). 365 | :param reset: by default a reset-all code is added at the end of the 366 | string which means that styles do not carry over. This 367 | can be disabled to compose styles. 368 | """ 369 | bits = [] 370 | if fg: 371 | try: 372 | bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30)) 373 | except ValueError: 374 | raise TypeError('Unknown color %r' % fg) 375 | if bg: 376 | try: 377 | bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40)) 378 | except ValueError: 379 | raise TypeError('Unknown color %r' % bg) 380 | if bold is not None: 381 | bits.append('\033[%dm' % (1 if bold else 22)) 382 | if dim is not None: 383 | bits.append('\033[%dm' % (2 if dim else 22)) 384 | if underline is not None: 385 | bits.append('\033[%dm' % (4 if underline else 24)) 386 | if blink is not None: 387 | bits.append('\033[%dm' % (5 if blink else 25)) 388 | if reverse is not None: 389 | bits.append('\033[%dm' % (7 if reverse else 27)) 390 | bits.append(text) 391 | if reset: 392 | bits.append(_ansi_reset_all) 393 | return ''.join(bits) 394 | 395 | 396 | def unstyle(text): 397 | """Removes ANSI styling information from a string. Usually it's not 398 | necessary to use this function as Click's echo function will 399 | automatically remove styling if necessary. 400 | 401 | .. versionadded:: 2.0 402 | 403 | :param text: the text to remove style information from. 404 | """ 405 | return strip_ansi(text) 406 | 407 | 408 | def secho(text, file=None, nl=True, err=False, color=None, **styles): 409 | """This function combines :func:`echo` and :func:`style` into one 410 | call. As such the following two calls are the same:: 411 | 412 | click.secho('Hello World!', fg='green') 413 | click.echo(click.style('Hello World!', fg='green')) 414 | 415 | All keyword arguments are forwarded to the underlying functions 416 | depending on which one they go with. 417 | 418 | .. versionadded:: 2.0 419 | """ 420 | return echo(style(text, **styles), file=file, nl=nl, err=err, color=color) 421 | 422 | 423 | def edit(text=None, editor=None, env=None, require_save=True, 424 | extension='.txt', filename=None): 425 | r"""Edits the given text in the defined editor. If an editor is given 426 | (should be the full path to the executable but the regular operating 427 | system search path is used for finding the executable) it overrides 428 | the detected editor. Optionally, some environment variables can be 429 | used. If the editor is closed without changes, `None` is returned. In 430 | case a file is edited directly the return value is always `None` and 431 | `require_save` and `extension` are ignored. 432 | 433 | If the editor cannot be opened a :exc:`UsageError` is raised. 434 | 435 | Note for Windows: to simplify cross-platform usage, the newlines are 436 | automatically converted from POSIX to Windows and vice versa. As such, 437 | the message here will have ``\n`` as newline markers. 438 | 439 | :param text: the text to edit. 440 | :param editor: optionally the editor to use. Defaults to automatic 441 | detection. 442 | :param env: environment variables to forward to the editor. 443 | :param require_save: if this is true, then not saving in the editor 444 | will make the return value become `None`. 445 | :param extension: the extension to tell the editor about. This defaults 446 | to `.txt` but changing this might change syntax 447 | highlighting. 448 | :param filename: if provided it will edit this file instead of the 449 | provided text contents. It will not use a temporary 450 | file as an indirection in that case. 451 | """ 452 | from ._termui_impl import Editor 453 | editor = Editor(editor=editor, env=env, require_save=require_save, 454 | extension=extension) 455 | if filename is None: 456 | return editor.edit(text) 457 | editor.edit_file(filename) 458 | 459 | 460 | def launch(url, wait=False, locate=False): 461 | """This function launches the given URL (or filename) in the default 462 | viewer application for this file type. If this is an executable, it 463 | might launch the executable in a new session. The return value is 464 | the exit code of the launched application. Usually, ``0`` indicates 465 | success. 466 | 467 | Examples:: 468 | 469 | click.launch('http://click.pocoo.org/') 470 | click.launch('/my/downloaded/file', locate=True) 471 | 472 | .. versionadded:: 2.0 473 | 474 | :param url: URL or filename of the thing to launch. 475 | :param wait: waits for the program to stop. 476 | :param locate: if this is set to `True` then instead of launching the 477 | application associated with the URL it will attempt to 478 | launch a file manager with the file located. This 479 | might have weird effects if the URL does not point to 480 | the filesystem. 481 | """ 482 | from ._termui_impl import open_url 483 | return open_url(url, wait=wait, locate=locate) 484 | 485 | 486 | # If this is provided, getchar() calls into this instead. This is used 487 | # for unittesting purposes. 488 | _getchar = None 489 | 490 | 491 | def getchar(echo=False): 492 | """Fetches a single character from the terminal and returns it. This 493 | will always return a unicode character and under certain rare 494 | circumstances this might return more than one character. The 495 | situations which more than one character is returned is when for 496 | whatever reason multiple characters end up in the terminal buffer or 497 | standard input was not actually a terminal. 498 | 499 | Note that this will always read from the terminal, even if something 500 | is piped into the standard input. 501 | 502 | .. versionadded:: 2.0 503 | 504 | :param echo: if set to `True`, the character read will also show up on 505 | the terminal. The default is to not show it. 506 | """ 507 | f = _getchar 508 | if f is None: 509 | from ._termui_impl import getchar as f 510 | return f(echo) 511 | 512 | 513 | def pause(info='Press any key to continue ...', err=False): 514 | """This command stops execution and waits for the user to press any 515 | key to continue. This is similar to the Windows batch "pause" 516 | command. If the program is not run through a terminal, this command 517 | will instead do nothing. 518 | 519 | .. versionadded:: 2.0 520 | 521 | .. versionadded:: 4.0 522 | Added the `err` parameter. 523 | 524 | :param info: the info string to print before pausing. 525 | :param err: if set to message goes to ``stderr`` instead of 526 | ``stdout``, the same as with echo. 527 | """ 528 | if not isatty(sys.stdin) or not isatty(sys.stdout): 529 | return 530 | try: 531 | if info: 532 | echo(info, nl=False, err=err) 533 | try: 534 | getchar() 535 | except (KeyboardInterrupt, EOFError): 536 | pass 537 | finally: 538 | if info: 539 | echo(err=err) 540 | --------------------------------------------------------------------------------