mirror of https://github.com/sgoudham/Enso-Bot.git
Installing Menus
parent
7fe93bacda
commit
a5e6f7e847
@ -0,0 +1,3 @@
|
||||
UNKNOWN
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
pip
|
@ -0,0 +1,15 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: Menus
|
||||
Version: 0.2.0
|
||||
Summary: Create cli menus with ease
|
||||
Home-page: https://github.com/JMSwag/Menus
|
||||
Author: Digital Sapphire
|
||||
Author-email: menus@digitalsapphire.io
|
||||
License: MIT
|
||||
Platform: UNKNOWN
|
||||
Requires-Dist: dsdev-utils
|
||||
Requires-Dist: six
|
||||
|
||||
UNKNOWN
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
Menus-0.2.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10
|
||||
Menus-0.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
Menus-0.2.0.dist-info/METADATA,sha256=5E660I04WM6yX5M3KfFsqJZNz5l4kckz3MFAou_1Iu0,280
|
||||
Menus-0.2.0.dist-info/RECORD,,
|
||||
Menus-0.2.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
|
||||
Menus-0.2.0.dist-info/metadata.json,sha256=k5fJgBoAIoSIFjuR1yUm6YALlY9n5FTybcF8RqhDLjQ,480
|
||||
Menus-0.2.0.dist-info/top_level.txt,sha256=mr-Mm53IP5nmAdXmK0MURdgtX5aoNBmIrgHgyPdKUac,11
|
||||
Menus-0.2.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
||||
menus/__init__.py,sha256=Racmika2iOJjIGPzbSZs8hQARcJaif_N2rLyz0uUGJY,1419
|
||||
menus/__pycache__/__init__.cpython-36.pyc,,
|
||||
menus/__pycache__/_version.cpython-36.pyc,,
|
||||
menus/__pycache__/engine.cpython-36.pyc,,
|
||||
menus/__pycache__/example.cpython-36.pyc,,
|
||||
menus/__pycache__/exceptions.cpython-36.pyc,,
|
||||
menus/__pycache__/menu.cpython-36.pyc,,
|
||||
menus/__pycache__/utils.cpython-36.pyc,,
|
||||
menus/_version.py,sha256=Z0DxOc2U4ydSV42gG9XZ6DRszh-5f53SRi3SAzpPxKU,471
|
||||
menus/engine.py,sha256=tG6395XNJ4LcKzE76hdcDGGrOFPRYrOXdV6pjIN9Rb0,3315
|
||||
menus/example.py,sha256=tLYJJTtloPBfFc3kui6s-d_nPAXyEdGs8gqjssHOIpA,3292
|
||||
menus/exceptions.py,sha256=Widb24ezSJNpMy8Dt8Wi_xQmXA561x3cI4hj27Br2_4,2400
|
||||
menus/menu.py,sha256=kMzIcnwPsFHQLEN2NKQ0m4EUqTlzBKNlVnje7tJ6Cbs,6983
|
||||
menus/utils.py,sha256=ReoV8xbA9SjX6psiSPevG-A7NVJWQLmz0rVw7MLuPIQ,3670
|
||||
site/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
site/__pycache__/__init__.cpython-36.pyc,,
|
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -0,0 +1 @@
|
||||
{"extensions": {"python.details": {"contacts": [{"email": "menus@digitalsapphire.io", "name": "Digital Sapphire", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/JMSwag/Menus"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "license": "MIT", "metadata_version": "2.0", "name": "Menus", "run_requires": [{"requires": ["dsdev-utils", "six"]}], "summary": "Create cli menus with ease", "version": "0.2.0"}
|
@ -0,0 +1,2 @@
|
||||
menus
|
||||
site
|
@ -0,0 +1,17 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: dsdev-utils
|
||||
Version: 1.0.4
|
||||
Summary: Various utility functions
|
||||
Home-page: https://github.com/JMSwag/dsdev-utils
|
||||
Author: Digital Sapphire
|
||||
Author-email: digitalsapphire@gmail.com
|
||||
License: MIT
|
||||
Download-URL: https://github.com/JMSwag/dsdev-utils/archive/master.zip
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Environment :: Console
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
@ -0,0 +1,22 @@
|
||||
MANIFEST.in
|
||||
README.md
|
||||
setup.cfg
|
||||
setup.py
|
||||
versioneer.py
|
||||
dsdev_utils/__init__.py
|
||||
dsdev_utils/_version.py
|
||||
dsdev_utils/app.py
|
||||
dsdev_utils/compat.py
|
||||
dsdev_utils/config.py
|
||||
dsdev_utils/crypto.py
|
||||
dsdev_utils/exceptions.py
|
||||
dsdev_utils/helpers.py
|
||||
dsdev_utils/logger.py
|
||||
dsdev_utils/paths.py
|
||||
dsdev_utils/system.py
|
||||
dsdev_utils/terminal.py
|
||||
dsdev_utils.egg-info/PKG-INFO
|
||||
dsdev_utils.egg-info/SOURCES.txt
|
||||
dsdev_utils.egg-info/dependency_links.txt
|
||||
dsdev_utils.egg-info/requires.txt
|
||||
dsdev_utils.egg-info/top_level.txt
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1,29 @@
|
||||
..\dsdev_utils\__init__.py
|
||||
..\dsdev_utils\__pycache__\__init__.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\_version.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\app.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\compat.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\config.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\crypto.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\exceptions.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\helpers.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\logger.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\paths.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\system.cpython-36.pyc
|
||||
..\dsdev_utils\__pycache__\terminal.cpython-36.pyc
|
||||
..\dsdev_utils\_version.py
|
||||
..\dsdev_utils\app.py
|
||||
..\dsdev_utils\compat.py
|
||||
..\dsdev_utils\config.py
|
||||
..\dsdev_utils\crypto.py
|
||||
..\dsdev_utils\exceptions.py
|
||||
..\dsdev_utils\helpers.py
|
||||
..\dsdev_utils\logger.py
|
||||
..\dsdev_utils\paths.py
|
||||
..\dsdev_utils\system.py
|
||||
..\dsdev_utils\terminal.py
|
||||
PKG-INFO
|
||||
SOURCES.txt
|
||||
dependency_links.txt
|
||||
requires.txt
|
||||
top_level.txt
|
@ -0,0 +1,2 @@
|
||||
chardet
|
||||
six
|
@ -0,0 +1 @@
|
||||
dsdev_utils
|
@ -0,0 +1,26 @@
|
||||
# --------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2016 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# --------------------------------------------------------------------------
|
||||
from ._version import get_versions
|
||||
__version__ = get_versions()['version']
|
||||
del get_versions
|
@ -0,0 +1,21 @@
|
||||
|
||||
# This file was generated by 'versioneer.py' (0.16) from
|
||||
# revision-control system data, or from the parent directory name of an
|
||||
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
||||
# of this file.
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
version_json = '''
|
||||
{
|
||||
"dirty": false,
|
||||
"error": null,
|
||||
"full-revisionid": "fddc3e711787229b373a323759589a71f58722dd",
|
||||
"version": "1.0.4"
|
||||
}
|
||||
''' # END VERSION_JSON
|
||||
|
||||
|
||||
def get_versions():
|
||||
return json.loads(version_json)
|
@ -0,0 +1,47 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
FROZEN = getattr(sys, u'frozen', False)
|
||||
|
||||
|
||||
def _app_cwd():
|
||||
if FROZEN: # pragma: no cover
|
||||
# we are running in a |PyInstaller| bundle
|
||||
cwd = os.path.dirname(sys.argv[0])
|
||||
if cwd.endswith('MacOS') is True:
|
||||
log.debug('Looks like we\'re dealing with a Mac Gui')
|
||||
cwd = os.path.dirname(os.path.dirname(os.path.dirname(cwd)))
|
||||
|
||||
else:
|
||||
# we are running in a normal Python environment
|
||||
cwd = os.getcwd()
|
||||
return cwd
|
||||
|
||||
|
||||
app_cwd = _app_cwd()
|
@ -0,0 +1,66 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import chardet
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
if not six.PY2:
|
||||
# Helper for Python 2 and 3 compatibility
|
||||
unicode = str
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_compat_str(in_str):
|
||||
"""
|
||||
Tries to guess encoding of [str/bytes] and decode it into
|
||||
an unicode object.
|
||||
"""
|
||||
assert isinstance(in_str, (bytes, str, unicode))
|
||||
if not in_str:
|
||||
return unicode()
|
||||
|
||||
# Chardet in Py2 works on str + bytes objects
|
||||
if six.PY2 and isinstance(in_str, unicode):
|
||||
return in_str
|
||||
|
||||
# Chardet in Py3 works on bytes objects
|
||||
if not six.PY2 and not isinstance(in_str, bytes):
|
||||
return in_str
|
||||
|
||||
# Detect the encoding now
|
||||
enc = chardet.detect(in_str)
|
||||
|
||||
# Decode the object into a unicode object
|
||||
out_str = in_str.decode(enc['encoding'])
|
||||
|
||||
# Cleanup: Sometimes UTF-16 strings include the BOM
|
||||
if enc['encoding'] == "UTF-16BE":
|
||||
# Remove byte order marks (BOM)
|
||||
if out_str.startswith('\ufeff'):
|
||||
out_str = out_str[1:]
|
||||
|
||||
# Return the decoded string
|
||||
return out_str
|
@ -0,0 +1,61 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigDict(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ConfigDict, self).__init__(*args, **kwargs)
|
||||
self.__dict__ = self
|
||||
default = kwargs.get('default', {})
|
||||
assert isinstance(default, dict)
|
||||
self.update(default)
|
||||
|
||||
def update(self, data):
|
||||
_data = {}
|
||||
for k, v in data.items():
|
||||
if k.isupper():
|
||||
_data[k] = v
|
||||
super(ConfigDict, self).update(_data)
|
||||
|
||||
def from_object(self, obj):
|
||||
"""Updates the values from the given object
|
||||
|
||||
Args:
|
||||
|
||||
obj (instance): Object with config attributes
|
||||
|
||||
Objects are classes.
|
||||
|
||||
Just the uppercase variables in that object are stored in the config.
|
||||
Example usage::
|
||||
|
||||
from yourapplication import default_config
|
||||
app.config.from_object(default_config())
|
||||
"""
|
||||
for key in dir(obj):
|
||||
if key.isupper():
|
||||
self[key] = getattr(obj, key)
|
@ -0,0 +1,49 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_package_hashes(filename):
|
||||
"""Provides hash of given filename.
|
||||
|
||||
Args:
|
||||
|
||||
filename (str): Name of file to hash
|
||||
|
||||
Returns:
|
||||
|
||||
(str): sha256 hash
|
||||
"""
|
||||
log.debug('Getting package hashes')
|
||||
filename = os.path.abspath(filename)
|
||||
with open(filename, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
_hash = hashlib.sha256(data).hexdigest()
|
||||
log.debug('Hash for file %s: %s', filename, _hash)
|
||||
return _hash
|
@ -0,0 +1,68 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class STDError(Exception):
|
||||
"""Extends exceptions to show added message if error isn't expected.
|
||||
|
||||
Args:
|
||||
|
||||
msg (str): error message
|
||||
|
||||
Kwargs:
|
||||
|
||||
tb (obj): is the original traceback so that it can be printed.
|
||||
|
||||
expected (bool):
|
||||
|
||||
Meaning:
|
||||
|
||||
True - Report issue msg not shown
|
||||
|
||||
False - Report issue msg shown
|
||||
"""
|
||||
def __init__(self, msg, tb=None, expected=False):
|
||||
if expected is False:
|
||||
msg = msg + ('; please report this issue on https://github.com'
|
||||
'/JMSwag/dsdev-utils/issues')
|
||||
super(STDError, self).__init__(msg)
|
||||
|
||||
self.traceback = tb
|
||||
self.exc_info = sys.exc_info() # preserve original exception
|
||||
|
||||
def format_traceback(self):
|
||||
if self.traceback is None:
|
||||
return None
|
||||
return ''.join(traceback.format_tb(self.traceback))
|
||||
|
||||
|
||||
class VersionError(STDError):
|
||||
"""Raised for Utils exceptions"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VersionError, self).__init__(*args, **kwargs)
|
@ -0,0 +1,305 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import io
|
||||
import gzip
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from dsdev_utils.exceptions import VersionError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Decompress gzip data
|
||||
#
|
||||
# Args:
|
||||
#
|
||||
# data (str): Gzip data
|
||||
#
|
||||
#
|
||||
# Returns:
|
||||
#
|
||||
# (data): Decompressed data
|
||||
def gzip_decompress(data):
|
||||
# if isinstance(data, six.binary_type):
|
||||
# data = data.decode()
|
||||
compressed_file = io.BytesIO()
|
||||
compressed_file.write(data)
|
||||
#
|
||||
# Set the file's current position to the beginning
|
||||
# of the file so that gzip.GzipFile can read
|
||||
# its contents from the top.
|
||||
#
|
||||
compressed_file.seek(0)
|
||||
decompressed_file = gzip.GzipFile(fileobj=compressed_file, mode='rb')
|
||||
data = decompressed_file.read()
|
||||
compressed_file.close()
|
||||
decompressed_file.close()
|
||||
return data
|
||||
|
||||
|
||||
def lazy_import(func):
|
||||
"""Decorator for declaring a lazy import.
|
||||
|
||||
This decorator turns a function into an object that will act as a lazy
|
||||
importer. Whenever the object's attributes are accessed, the function
|
||||
is called and its return value used in place of the object. So you
|
||||
can declare lazy imports like this:
|
||||
|
||||
@lazy_import
|
||||
def socket():
|
||||
import socket
|
||||
return socket
|
||||
|
||||
The name "socket" will then be bound to a transparent object proxy which
|
||||
will import the socket module upon first use.
|
||||
|
||||
The syntax here is slightly more verbose than other lazy import recipes,
|
||||
but it's designed not to hide the actual "import" statements from tools
|
||||
like pyinstaller or grep.
|
||||
"""
|
||||
try:
|
||||
f = sys._getframe(1)
|
||||
except Exception: # pragma: no cover
|
||||
namespace = None
|
||||
else:
|
||||
namespace = f.f_locals
|
||||
return _LazyImport(func.__name__, func, namespace)
|
||||
|
||||
|
||||
class _LazyImport(object):
|
||||
"""Class representing a lazy import."""
|
||||
|
||||
def __init__(self, name, loader, namespace=None):
|
||||
self._dsdev_lazy_target = _LazyImport
|
||||
self._dsdev_lazy_name = name
|
||||
self._dsdev_lazy_loader = loader
|
||||
self._dsdev_lazy_namespace = namespace
|
||||
|
||||
def _dsdev_lazy_load(self):
|
||||
if self._dsdev_lazy_target is _LazyImport:
|
||||
self._dsdev_lazy_target = self._dsdev_lazy_loader()
|
||||
ns = self._dsdev_lazy_namespace
|
||||
if ns is not None:
|
||||
try:
|
||||
if ns[self._dsdev_lazy_name] is self:
|
||||
ns[self._dsdev_lazy_name] = self._dsdev_lazy_target
|
||||
except KeyError: # pragma: no cover
|
||||
pass
|
||||
|
||||
def __getattribute__(self, attr): # pragma: no cover
|
||||
try:
|
||||
return object.__getattribute__(self, attr)
|
||||
except AttributeError:
|
||||
if self._dsdev_lazy_target is _LazyImport:
|
||||
self._dsdev_lazy_load()
|
||||
return getattr(self._dsdev_lazy_target, attr)
|
||||
|
||||
def __nonzero__(self): # pragma: no cover
|
||||
if self._dsdev_lazy_target is _LazyImport:
|
||||
self._dsdev_lazy_load()
|
||||
return bool(self._dsdev_lazy_target)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return '_LazyImport: {}'.format(self._dsdev_lazy_name)
|
||||
|
||||
|
||||
# Normalizes version strings of different types. Examples
|
||||
# include 1.2, 1.2.1, 1.2b and 1.1.1b
|
||||
#
|
||||
# Args:
|
||||
#
|
||||
# version (str): Version number to normalizes
|
||||
class Version(object):
|
||||
|
||||
v_re = re.compile(r'(?P<major>\d+)\.(?P<minor>\d+)\.?(?P'
|
||||
r'<patch>\d+)?-?(?P<release>[abehl'
|
||||
r'pt]+)?-?(?P<releaseversion>\d+)?')
|
||||
|
||||
v_re_big = re.compile(r'(?P<major>\d+)\.(?P<minor>\d+)\.'
|
||||
r'(?P<patch>\d+)\.(?P<release>\d+)'
|
||||
r'\.(?P<releaseversion>\d+)')
|
||||
|
||||
def __init__(self, version):
|
||||
self.original_version = version
|
||||
self._parse_version_str(version)
|
||||
self.version_str = None
|
||||
|
||||
def _parse_version_str(self, version):
|
||||
count = self._quick_sanitize(version)
|
||||
try:
|
||||
# version in the form of 1.1, 1.1.1, 1.1.1-b1, 1.1.1a2
|
||||
if count == 4:
|
||||
version_data = self._parse_parsed_version(version)
|
||||
else:
|
||||
version_data = self._parse_version(version)
|
||||
except AssertionError:
|
||||
raise VersionError('Cannot parse version')
|
||||
|
||||
self.major = int(version_data.get('major', 0))
|
||||
self.minor = int(version_data.get('minor', 0))
|
||||
patch = version_data.get('patch')
|
||||
if patch is None:
|
||||
self.patch = 0
|
||||
else:
|
||||
self.patch = int(patch)
|
||||
release = version_data.get('release')
|
||||
self.channel = 'stable'
|
||||
if release is None:
|
||||
self.release = 2
|
||||
# Convert to number for easy comparison and sorting
|
||||
elif release in ['b', 'beta', '1']:
|
||||
self.release = 1
|
||||
self.channel = 'beta'
|
||||
elif release in ['a', 'alpha', '0']:
|
||||
self.release = 0
|
||||
self.channel = 'alpha'
|
||||
else:
|
||||
log.debug('Setting release as stable. '
|
||||
'Disregard if not prerelease')
|
||||
# Marking release as stable
|
||||
self.release = 2
|
||||
|
||||
release_version = version_data.get('releaseversion')
|
||||
if release_version is None:
|
||||
self.release_version = 0
|
||||
else:
|
||||
self.release_version = int(release_version)
|
||||
self.version_tuple = (self.major, self.minor, self.patch,
|
||||
self.release, self.release_version)
|
||||
self.version_str = str(self.version_tuple)
|
||||
|
||||
def _parse_version(self, version):
|
||||
r = self.v_re.search(version)
|
||||
assert r is not None
|
||||
return r.groupdict()
|
||||
|
||||
def _parse_parsed_version(self, version):
|
||||
r = self.v_re_big.search(version)
|
||||
assert r is not None
|
||||
return r.groupdict()
|
||||
|
||||
@staticmethod
|
||||
def _quick_sanitize(version):
|
||||
log.debug('Version str: %s', version)
|
||||
ext = os.path.splitext(version)[1]
|
||||
# Removing file extensions, to ensure count isn't
|
||||
# contaminated
|
||||
if ext == '.zip':
|
||||
log.debug('Removed ".zip"')
|
||||
version = version[:-4]
|
||||
elif ext == '.gz':
|
||||
log.debug('Removed ".tar.gz"')
|
||||
version = version[:-7]
|
||||
elif ext == '.bz2':
|
||||
log.debug('Removed ".tar.bz2"')
|
||||
version = version[:-8]
|
||||
count = version.count('.')
|
||||
# There will be 4 dots when version is passed
|
||||
# That was created with Version object.
|
||||
# 1.1 once parsed will be 1.1.0.0.0
|
||||
if count not in [1, 2, 4]:
|
||||
msg = ('Incorrect version format. 1 or 2 dots '
|
||||
'You have {} dots'.format(count))
|
||||
log.debug(msg)
|
||||
raise VersionError(msg)
|
||||
return count
|
||||
|
||||
def __str__(self):
|
||||
return '.'.join(map(str, self.version_tuple))
|
||||
|
||||
def __repr__(self):
|
||||
return '{}: {}'.format(self.__class__.__name__,
|
||||
self.version_str)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.version_tuple)
|
||||
|
||||
def __eq__(self, obj):
|
||||
return self.version_tuple == obj.version_tuple
|
||||
|
||||
def __ne__(self, obj):
|
||||
return self.version_tuple != obj.version_tuple
|
||||
|
||||
def __lt__(self, obj):
|
||||
return self.version_tuple < obj.version_tuple
|
||||
|
||||
def __gt__(self, obj):
|
||||
return self.version_tuple > obj.version_tuple
|
||||
|
||||
def __le__(self, obj):
|
||||
return self.version_tuple <= obj.version_tuple
|
||||
|
||||
def __ge__(self, obj):
|
||||
return self.version_tuple >= obj.version_tuple
|
||||
|
||||
|
||||
# Provides access to dict by pass a specially made key to
|
||||
# the get method. Default key sep is "*". Example key would be
|
||||
# updates*mac*1.7.0 would access {"updates":{"mac":{"1.7.0": "hi there"}}}
|
||||
# and return "hi there"
|
||||
#
|
||||
# Kwargs:
|
||||
#
|
||||
# dict_ (dict): Dict you would like easy asses to.
|
||||
#
|
||||
# sep (str): Used as a delimiter between keys
|
||||
class EasyAccessDict(object):
|
||||
|
||||
def __init__(self, dict_=None, sep='*'):
|
||||
self.sep = sep
|
||||
if not isinstance(dict_, dict):
|
||||
self.dict = {}
|
||||
else:
|
||||
self.dict = dict_
|
||||
|
||||
# Retrive value from internal dict.
|
||||
#
|
||||
# args:
|
||||
#
|
||||
# key (str): Key to access value
|
||||
#
|
||||
# Returns:
|
||||
#
|
||||
# (object): Value of key if found or None
|
||||
def get(self, key):
|
||||
try:
|
||||
layers = key.split(self.sep)
|
||||
value = self.dict
|
||||
for key in layers:
|
||||
value = value[key]
|
||||
return value
|
||||
except KeyError:
|
||||
return None
|
||||
except Exception: # pragma: no cover
|
||||
return None
|
||||
|
||||
# Because I always forget call the get method
|
||||
def __call__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.dict)
|
@ -0,0 +1,28 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import logging
|
||||
|
||||
|
||||
logging_formatter = logging.Formatter(u'[%(levelname)s] %(name)s '
|
||||
u'%(lineno)d: %(message)s')
|
@ -0,0 +1,96 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
|
||||
import six
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_mac_dot_app_dir(directory):
|
||||
"""Returns parent directory of mac .app
|
||||
|
||||
Args:
|
||||
|
||||
directory (str): Current directory
|
||||
|
||||
Returns:
|
||||
|
||||
(str): Parent directory of mac .app
|
||||
"""
|
||||
return os.path.dirname(os.path.dirname(os.path.dirname(directory)))
|
||||
|
||||
|
||||
def remove_any(path):
|
||||
if six.PY2 or sys.version_info[1] == 5:
|
||||
path = str(path)
|
||||
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
|
||||
def _remove_any(x):
|
||||
if os.path.isdir(x):
|
||||
shutil.rmtree(x, ignore_errors=True)
|
||||
else:
|
||||
os.remove(path)
|
||||
|
||||
if sys.platform != 'win32':
|
||||
_remove_any(path)
|
||||
else:
|
||||
for _ in range(100):
|
||||
try:
|
||||
_remove_any(path)
|
||||
except Exception as err:
|
||||
log.debug(err, exc_info=True)
|
||||
time.sleep(0.01)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
try:
|
||||
_remove_any(path)
|
||||
except Exception as err:
|
||||
log.debug(err, exc_info=True)
|
||||
|
||||
|
||||
class ChDir(object):
|
||||
|
||||
def __init__(self, path):
|
||||
if six.PY2 or sys.version_info[1] in [4, 5]:
|
||||
path = str(path)
|
||||
|
||||
self.old_dir = os.getcwd()
|
||||
self.new_dir = path
|
||||
|
||||
def __enter__(self):
|
||||
log.debug('Changing to Directory --> {}'.format(self.new_dir))
|
||||
os.chdir(self.new_dir)
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
log.debug('Moving back to Directory --> {}'.format(self.old_dir))
|
||||
os.chdir(self.old_dir)
|
@ -0,0 +1,64 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import unicode_literals
|
||||
import logging
|
||||
import platform
|
||||
import sys
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_PLATFORM = None
|
||||
_ARCHITECTURE = None
|
||||
|
||||
|
||||
def get_architecure():
|
||||
global _ARCHITECTURE
|
||||
if _ARCHITECTURE is not None:
|
||||
return _ARCHITECTURE
|
||||
if '64bit' in platform.architecture()[0]:
|
||||
_ARCHITECTURE = '64'
|
||||
else:
|
||||
_ARCHITECTURE = '32'
|
||||
return _ARCHITECTURE
|
||||
|
||||
|
||||
def get_system():
|
||||
global _PLATFORM
|
||||
if _PLATFORM is not None:
|
||||
return _PLATFORM
|
||||
if sys.platform == 'win32':
|
||||
_PLATFORM = 'win'
|
||||
elif sys.platform == 'darwin':
|
||||
_PLATFORM = 'mac'
|
||||
else:
|
||||
arch = get_architecure()
|
||||
if 'arm' in platform.uname()[4]:
|
||||
_PLATFORM = 'arm'
|
||||
if arch == '64':
|
||||
_PLATFORM += arch
|
||||
else:
|
||||
_PLATFORM = 'nix'
|
||||
if arch == '64':
|
||||
_PLATFORM += arch
|
||||
return _PLATFORM
|
@ -0,0 +1,284 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2019 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import print_function
|
||||
import logging
|
||||
try:
|
||||
import msvcrt
|
||||
except ImportError:
|
||||
msvcrt = None
|
||||
import locale
|
||||
import optparse
|
||||
import os
|
||||
import platform
|
||||
import shlex
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
try:
|
||||
import termios
|
||||
except ImportError:
|
||||
termios = None
|
||||
try:
|
||||
import tty
|
||||
except ImportError:
|
||||
tty = None
|
||||
|
||||
import six
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def print_to_console(text):
|
||||
enc = locale.getdefaultlocale()[1] or "utf-8"
|
||||
try:
|
||||
print(text.encode(enc, errors="backslashreplace"))
|
||||
except (LookupError, UnicodeEncodeError):
|
||||
# Unknown encoding or encoding problem. Fallback to ascii
|
||||
print(text.encode("ascii", errors="backslashreplace"))
|
||||
|
||||
|
||||
def terminal_formatter():
|
||||
max_width = 80
|
||||
max_help_position = 80
|
||||
|
||||
# No need to wrap help messages if we're on a wide console
|
||||
columns = get_terminal_size()[0]
|
||||
if columns:
|
||||
max_width = columns
|
||||
|
||||
fmt = optparse.IndentedHelpFormatter(width=max_width,
|
||||
max_help_position=max_help_position)
|
||||
return fmt
|
||||
|
||||
|
||||
# get width and height of console
|
||||
# works on linux, os x, windows, cygwin(windows)
|
||||
# originally retrieved from:
|
||||
# http://stackoverflow.com/questions/
|
||||
# 566746/how-to-get-console-window-width-in-python
|
||||
def get_terminal_size():
|
||||
current_os = platform.system()
|
||||
tuple_xy = None
|
||||
if current_os == u'Windows':
|
||||
tuple_xy = _get_terminal_size_windows()
|
||||
if tuple_xy is None:
|
||||
tuple_xy = _get_terminal_size_tput()
|
||||
# needed for window's python in cygwin's xterm!
|
||||
if current_os in [u'Linux', u'Darwin'] or current_os.startswith('CYGWIN'):
|
||||
tuple_xy = _get_terminal_size_linux()
|
||||
if tuple_xy is None:
|
||||
log.debug(u"default")
|
||||
tuple_xy = (80, 25) # default value
|
||||
return tuple_xy
|
||||
|
||||
|
||||
def _get_terminal_size_windows():
|
||||
try:
|
||||
from ctypes import windll, create_string_buffer
|
||||
# stdin handle is -10
|
||||
# stdout handle is -11
|
||||
# stderr handle is -12
|
||||
h = windll.kernel32.GetStdHandle(-12)
|
||||
csbi = create_string_buffer(22)
|
||||
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
|
||||
if res:
|
||||
(bufx, bufy, curx, cury, wattr,
|
||||
left, top, right, bottom,
|
||||
maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
|
||||
sizex = right - left + 1
|
||||
sizey = bottom - top + 1
|
||||
return sizex, sizey
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _get_terminal_size_tput():
|
||||
# get terminal width
|
||||
# http://stackoverflow.com/questions/263890/
|
||||
# how-do-i-find-the-width-height-of-a-terminal-window
|
||||
try:
|
||||
cols = int(subprocess.check_call(shlex.split('tput cols')))
|
||||
rows = int(subprocess.check_call(shlex.split('tput lines')))
|
||||
return (cols, rows)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _get_terminal_size_linux():
|
||||
def ioctl_GWINSZ(fd):
|
||||
try:
|
||||
import fcntl
|
||||
# Is this required
|
||||
# import termios
|
||||
cr = struct.unpack('hh',
|
||||
fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
||||
return cr
|
||||
except:
|
||||
pass
|
||||
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
||||
if not cr:
|
||||
try:
|
||||
fd = os.open(os.ctermid(), os.O_RDONLY)
|
||||
cr = ioctl_GWINSZ(fd)
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
if not cr:
|
||||
try:
|
||||
cr = (os.environ['LINES'], os.environ['COLUMNS'])
|
||||
except:
|
||||
return None
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
|
||||
# Gets a single character form standard input. Does not echo to the screen
|
||||
class GetCh:
|
||||
|
||||
def __init__(self):
|
||||
if sys.platform == u'win32':
|
||||
self.impl = _GetchWindows()
|
||||
else:
|
||||
self.impl = _GetchUnix()
|
||||
|
||||
def __call__(self):
|
||||
return self.impl()
|
||||
|
||||
|
||||
class _GetchUnix:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self):
|
||||
pass
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
ch = sys.stdin.read(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
return ch
|
||||
|
||||
|
||||
class _GetchWindows:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self):
|
||||
return msvcrt.getch()
|
||||
|
||||
|
||||
def ask_yes_no(question, default='no', answer=None):
|
||||
u"""Will ask a question and keeps prompting until
|
||||
answered.
|
||||
|
||||
Args:
|
||||
|
||||
question (str): Question to ask end user
|
||||
|
||||
default (str): Default answer if user just press enter at prompt
|
||||
|
||||
answer (str): Used for testing
|
||||
|
||||
Returns:
|
||||
|
||||
(bool) Meaning:
|
||||
|
||||
True - Answer is yes
|
||||
|
||||
False - Answer is no
|
||||
"""
|
||||
default = default.lower()
|
||||
yes = [u'yes', u'ye', u'y']
|
||||
no = [u'no', u'n']
|
||||
if default in no:
|
||||
help_ = u'[N/y]?'
|
||||
default = False
|
||||
else:
|
||||
default = True
|
||||
help_ = u'[Y/n]?'
|
||||
while 1:
|
||||
display = question + '\n' + help_
|
||||
if answer is None:
|
||||
log.debug(u'Under None')
|
||||
answer = six.moves.input(display)
|
||||
answer = answer.lower()
|
||||
if answer == u'':
|
||||
log.debug(u'Under blank')
|
||||
return default
|
||||
if answer in yes:
|
||||
log.debug(u'Must be true')
|
||||
return True
|
||||
elif answer in no:
|
||||
log.debug(u'Must be false')
|
||||
return False
|
||||
else:
|
||||
sys.stdout.write(u'Please answer yes or no only!\n\n')
|
||||
sys.stdout.flush()
|
||||
answer = None
|
||||
six.moves.input(u'Press enter to continue')
|
||||
sys.stdout.write('\n\n\n\n\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def get_correct_answer(question, default=None, required=False,
|
||||
answer=None, is_answer_correct=None):
|
||||
u"""Ask user a question and confirm answer
|
||||
|
||||
Args:
|
||||
|
||||
question (str): Question to ask user
|
||||
|
||||
default (str): Default answer if no input from user
|
||||
|
||||
required (str): Require user to input answer
|
||||
|
||||
answer (str): Used for testing
|
||||
|
||||
is_answer_correct (str): Used for testing
|
||||
"""
|
||||
while 1:
|
||||
if default is None:
|
||||
msg = u' - No Default Available'
|
||||
else:
|
||||
msg = (u'\n[DEFAULT] -> {}\nPress Enter To '
|
||||
u'Use Default'.format(default))
|
||||
prompt = question + msg + u'\n--> '
|
||||
if answer is None:
|
||||
answer = six.moves.input(prompt)
|
||||
if answer == '' and required and default is not None:
|
||||
print(u'You have to enter a value\n\n')
|
||||
six.moves.input(u'Press enter to continue')
|
||||
print(u'\n\n')
|
||||
answer = None
|
||||
continue
|
||||
if answer == u'' and default is not None:
|
||||
answer = default
|
||||
_ans = ask_yes_no(u'You entered {}, is this '
|
||||
u'correct?'.format(answer),
|
||||
answer=is_answer_correct)
|
||||
if _ans:
|
||||
return answer
|
||||
else:
|
||||
answer = None
|
@ -0,0 +1,37 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2016 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
import logging
|
||||
|
||||
from menus.engine import Engine
|
||||
from menus.exceptions import MenusError
|
||||
from menus.menu import BaseMenu
|
||||
|
||||
|
||||
__all__ = ['BaseMenu', 'Engine', 'MenusError']
|
||||
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
from ._version import get_versions # noqa
|
||||
__version__ = get_versions()['version']
|
||||
del get_versions
|
@ -0,0 +1,21 @@
|
||||
|
||||
# This file was generated by 'versioneer.py' (0.15) from
|
||||
# revision-control system data, or from the parent directory name of an
|
||||
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
||||
# of this file.
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
version_json = '''
|
||||
{
|
||||
"dirty": false,
|
||||
"error": null,
|
||||
"full-revisionid": "4dd50cb49a508b57d56a4d512ef40da67b01ab77",
|
||||
"version": "0.2.0"
|
||||
}
|
||||
''' # END VERSION_JSON
|
||||
|
||||
|
||||
def get_versions():
|
||||
return json.loads(version_json)
|
@ -0,0 +1,96 @@
|
||||
# --------------------------------------------------------------------------
|
||||
# Copyright (c) 2016 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the
|
||||
# following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
# ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
# TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
# OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
# --------------------------------------------------------------------------
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from menus.example import load_example_menus
|
||||
from menus.exceptions import MenusError
|
||||
from menus.menu import BaseMenu, MainMenu
|
||||
from menus.utils import check_commands_else_raise
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Ensure that a custom menus subclasses BaseMenu
|
||||
def check_mro(c):
|
||||
if issubclass(c.__class__, BaseMenu) is False:
|
||||
raise MenusError('Not a sublcass of BaseMenu \n\nclass '
|
||||
'{}'.format(c.__class__.__name__),
|
||||
expected=True)
|
||||
return True
|
||||
|
||||
|
||||
class Engine(object):
|
||||
|
||||
def __init__(self, app_name=None, menus=None, example=False):
|
||||
# Name used in every menu header
|
||||
if app_name is None:
|
||||
app_name = 'ACME'
|
||||
|
||||
# Create initial commands for main menu
|
||||
sub_menus = []
|
||||
|
||||
# Adding submenus
|
||||
if example is True:
|
||||
sub_menus += load_example_menus()
|
||||
else:
|
||||
if menus is not None:
|
||||
for m in menus:
|
||||
check_mro(m)
|
||||
sub_menus += menus
|
||||
|
||||
# Commands with app_name added to it
|
||||
new_sub_menus = []
|
||||
for sub in sub_menus:
|
||||
# Adding the app name to the menu
|
||||
sub.app_name = app_name
|
||||
sub.commands.append(('Main Menu', getattr(sub, 'done')))
|
||||
|
||||
# Quick hack to add users class name as menu option
|
||||
# only for main menu
|
||||
new_sub_menu = (sub.menu_name, sub)
|
||||
new_sub_menus.append(new_sub_menu)
|
||||
|
||||
new_sub_menus.append(('Quit', self.quit))
|
||||
|
||||
# Sanatisation checks on passed commands
|
||||
check_commands_else_raise(new_sub_menus)
|
||||
|
||||
# Initilazie main menu with submenus
|
||||
self.main = MainMenu(new_sub_menus)
|
||||
|
||||
# Adding the app name to the main menu
|
||||
self.main.app_name = app_name
|
||||
|
||||
# Starts event loop
|
||||
def start(self): # pragma: no cover
|
||||
while 1:
|
||||
start = self.main.display()
|
||||
start()
|
||||
|
||||
def quit(self): # pragma: no cover
|
||||
log.debug('Quitting')
|
||||
sys.exit(0)
|
@ -0,0 +1,98 @@
|
||||
# --------------------------------------------------------------------------
|
||||
# Copyright (c) 2016 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the
|
||||
# following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
# ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
# TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
# OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
# --------------------------------------------------------------------------
|
||||
import logging
|
||||
|
||||
from menus.menu import BaseMenu
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cool(BaseMenu):
|
||||
|
||||
def __init__(self):
|
||||
# An option is a tuple which consists of ('Display Name', function)
|
||||
commands = [('Speak', self.speak)]
|
||||
super(Cool, self).__init__(commands=commands)
|
||||
|
||||
def speak(self):
|
||||
# Used to nicely display a message towards
|
||||
# the middle of the screen
|
||||
self.display_msg('Cool is speaking')
|
||||
|
||||
# Will pause for 3 seconds
|
||||
self.pause(seconds=3)
|
||||
|
||||
# Used to return to Cool Menu. If omitted
|
||||
# the user will be returned to the Main Menu
|
||||
self()
|
||||
|
||||
|
||||
class Hot(BaseMenu):
|
||||
|
||||
def __init__(self):
|
||||
# An option is a tuple which consists of ('Display Name', function)
|
||||
commands = [('Speak', self.speak)]
|
||||
super(Hot, self).__init__(commands=commands, menu_name='Really Hot')
|
||||
|
||||
def speak(self):
|
||||
# Used to nicely display a message towards
|
||||
# the middle of the screen
|
||||
self.display_msg("It's getting hot in here!")
|
||||
|
||||
# Will pause for 3 seconds
|
||||
self.pause(seconds=3)
|
||||
|
||||
# Used to return to Cool Menu. If omitted
|
||||
# the user will be returned to the Main Menu
|
||||
self()
|
||||
|
||||
|
||||
class Keys(BaseMenu):
|
||||
|
||||
def __init__(self):
|
||||
# An option is a tuple which consists of ('Display Name', function)
|
||||
commands = [('Show Public Key', self.show_public_key)]
|
||||
|
||||
super(Keys, self).__init__(commands=commands)
|
||||
|
||||
def show_public_key(self):
|
||||
log.debug('Show public key')
|
||||
|
||||
# Used to nicely display a message towards
|
||||
# the middle of the screen
|
||||
self.display_msg('thdkalfjl;da;ksfkda;fdkj')
|
||||
|
||||
# Will prompt user to press enter to continue
|
||||
self.pause(enter_to_continue=True)
|
||||
|
||||
# Used to return to Cool Menu. If omitted
|
||||
# the user will be returned to the Main Menu
|
||||
self()
|
||||
|
||||
|
||||
# List of menus to be used when user initializes Engine(example=True)
|
||||
def load_example_menus():
|
||||
return [Cool(), Hot(), Keys()]
|
@ -0,0 +1,69 @@
|
||||
# --------------------------------------------------------------------------
|
||||
# Copyright (c) 2016 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the
|
||||
# following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
# ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
# TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
# OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
# --------------------------------------------------------------------------
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
class STDError(Exception):
|
||||
"""Extends exceptions to show added message if error isn't expected.
|
||||
|
||||
Args:
|
||||
|
||||
msg (str): error message
|
||||
|
||||
Kwargs:
|
||||
|
||||
tb (obj): is the original traceback so that it can be printed.
|
||||
|
||||
expected (bool):
|
||||
|
||||
Meaning:
|
||||
|
||||
True - Report issue msg not shown
|
||||
|
||||
False - Report issue msg shown
|
||||
"""
|
||||
def __init__(self, msg, tb=None, expected=False):
|
||||
if not expected:
|
||||
msg = msg + ('; please report this issue on https://git'
|
||||
'hub.com/JMSwag/Menus')
|
||||
msg = '\n\n' + msg
|
||||
super(STDError, self).__init__(msg)
|
||||
|
||||
self.traceback = tb
|
||||
self.exc_info = sys.exc_info() # preserve original exception
|
||||
|
||||
def format_traceback(self):
|
||||
if self.traceback is None:
|
||||
return None
|
||||
return ''.join(traceback.format_tb(self.traceback))
|
||||
|
||||
|
||||
class MenusError(STDError):
|
||||
"""Raised for Archiver exceptions"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MenusError, self).__init__(*args, **kwargs)
|
@ -0,0 +1,206 @@
|
||||
# --------------------------------------------------------------------------
|
||||
# Copyright (c) 2016 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the
|
||||
# following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
# ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
# TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
# OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
# --------------------------------------------------------------------------
|
||||
from __future__ import print_function
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from dsdev_utils.terminal import (ask_yes_no,
|
||||
get_correct_answer,
|
||||
get_terminal_size)
|
||||
import six
|
||||
|
||||
from menus.exceptions import MenusError
|
||||
from menus.utils import clear_screen_cmd, Getch
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseMenu(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# Used to display the apps name on all menu headers
|
||||
self.app_name = None
|
||||
|
||||
# The custom menu name
|
||||
self.menu_name = kwargs.get('menu_name')
|
||||
|
||||
# If we do not have a custom menu
|
||||
# name then use the class name
|
||||
if self.menu_name is None:
|
||||
self.menu_name = self.__class__.__name__
|
||||
|
||||
# A message to display when this menus loads
|
||||
self.message = kwargs.get('message')
|
||||
|
||||
# User commands for this menu
|
||||
self._commands = kwargs.get('commands', [])
|
||||
|
||||
# ToDo: Remove in v1.0
|
||||
# User commands for this menu
|
||||
options = kwargs.get('options', [])
|
||||
if len(options) > 0:
|
||||
if len(self._commands) > 0:
|
||||
raise MenusError('Cannot mix old & new API. Use '
|
||||
'commands kwarg only.')
|
||||
|
||||
warn_msg = ('BaseMenu(options=[]) is deprecated, '
|
||||
'user BaseMenu(commands=[]')
|
||||
warnings.warn(warn_msg)
|
||||
self._commands += options
|
||||
# End ToDo
|
||||
|
||||
@property
|
||||
def commands(self):
|
||||
return self._commands
|
||||
|
||||
def __call__(self):
|
||||
x = self.display()
|
||||
x()
|
||||
|
||||
def display(self):
|
||||
self._display_menu_header()
|
||||
self.display_msg(self.message)
|
||||
return self._menu_options(self._commands)
|
||||
|
||||
def pause(self, seconds=5, enter_to_continue=False):
|
||||
if not isinstance(enter_to_continue, bool):
|
||||
raise MenusError('enter_to_continue must be boolean',
|
||||
expected=True)
|
||||
if not isinstance(seconds, six.integer_types):
|
||||
raise MenusError('seconds must be integer', expected=True)
|
||||
|
||||
if enter_to_continue is True:
|
||||
self.display_msg('Press enter to quit')
|
||||
six.moves.input()
|
||||
else:
|
||||
time.sleep(seconds)
|
||||
return True
|
||||
|
||||
# Prompt the user with a question & only accept
|
||||
# yes or no answers
|
||||
def ask_yes_no(self, question, default):
|
||||
return ask_yes_no(question, default)
|
||||
|
||||
# Promot the user with a question & confirm it's correct.
|
||||
# If required is True, user won't be able to enter a blank answer
|
||||
def get_correct_answer(self, question, default, required=False):
|
||||
return get_correct_answer(question, default, required)
|
||||
|
||||
# Display a message centered on the screen.
|
||||
def display_msg(self, message=None):
|
||||
self._display_msg(message)
|
||||
|
||||
def done(self):
|
||||
pass
|
||||
|
||||
# ToDo: Remove in v1.0
|
||||
def get_correct_action(self, question, default, required):
|
||||
return get_correct_answer(question, default, required)
|
||||
# End ToDo
|
||||
|
||||
# Takes a string and adds it to the menu header along side
|
||||
# the app name.
|
||||
def _display_menu_header(self):
|
||||
window_size = get_terminal_size()[0]
|
||||
|
||||
# Adding some styling to the header
|
||||
def add_style():
|
||||
top = '*' * window_size + '\n'
|
||||
bottom = '\n' + '*' * window_size + '\n'
|
||||
|
||||
header = self.app_name + ' - ' + self.menu_name
|
||||
header = header.center(window_size)
|
||||
msg = top + header + bottom
|
||||
return msg
|
||||
|
||||
os.system(clear_screen_cmd)
|
||||
print(add_style())
|
||||
|
||||
def _display_msg(self, message):
|
||||
window_size = get_terminal_size()[0]
|
||||
if message is None:
|
||||
return ''
|
||||
|
||||
if not isinstance(message, six.string_types):
|
||||
log.warning('Did not pass str')
|
||||
return ''
|
||||
|
||||
# Home grown word wrap
|
||||
def format_msg():
|
||||
formatted = []
|
||||
finished = ['\n']
|
||||
count = 0
|
||||
words = message.split(' ')
|
||||
for w in words:
|
||||
w = w + ' '
|
||||
if count + len(w) > window_size / 2:
|
||||
finished.append(''.join(formatted).center(window_size))
|
||||
finished.append('\n')
|
||||
count = len(w)
|
||||
# Starting a new line.
|
||||
formatted = []
|
||||
formatted.append(w)
|
||||
else:
|
||||
formatted.append(w)
|
||||
count += len(w)
|
||||
finished.append(''.join(formatted).center(window_size))
|
||||
finished.append('\n')
|
||||
return ''.join(finished)
|
||||
print(format_msg())
|
||||
|
||||
# Takes a list of tuples(menu_name, call_back) adds menu numbers
|
||||
# then prints menu to screen.
|
||||
# Gets input from user, then returns the callback
|
||||
def _menu_options(self, commands=None):
|
||||
|
||||
def add_options():
|
||||
getch = Getch()
|
||||
menu = []
|
||||
count = 1
|
||||
for s in commands:
|
||||
item = '{}. {}\n'.format(count, s[0])
|
||||
menu.append(item)
|
||||
count += 1
|
||||
print(''.join(menu))
|
||||
answers = []
|
||||
for a in six.moves.range(1, len(menu) + 1):
|
||||
answers.append(str(a))
|
||||
while 1:
|
||||
ans = getch()
|
||||
if ans in answers:
|
||||
break
|
||||
else:
|
||||
log.debug('Not an acceptable answer!')
|
||||
return commands[int(ans) - 1][1]
|
||||
return add_options()
|
||||
|
||||
|
||||
# This is used as the initial Menu
|
||||
class MainMenu(BaseMenu):
|
||||
|
||||
def __init__(self, commands):
|
||||
super(MainMenu, self).__init__(commands=commands)
|
@ -0,0 +1,118 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2016 Digital Sapphire
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from menus.exceptions import MenusError
|
||||
|
||||
# Preventing import errors on non windows
|
||||
# platforms
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
else:
|
||||
import termios
|
||||
import tty
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
clear_screen_cmd = 'cls'
|
||||
else:
|
||||
clear_screen_cmd = 'clear'
|
||||
|
||||
|
||||
def check_commands_else_raise(options):
|
||||
# We need a least one thing to display
|
||||
# Using this as a teaching aid. Also making the use of
|
||||
# Engine(example=Ture) very explicit
|
||||
if len(options) == 0:
|
||||
msg = ('You must pass a menus object or initilize '
|
||||
'like -> Engine(example=True)')
|
||||
raise MenusError(msg, expected=True)
|
||||
|
||||
# We need a list or tuple to loop through
|
||||
if not isinstance(options, list):
|
||||
if not isinstance(options, tuple):
|
||||
msg = ('You must pass a list or tuple to menus.')
|
||||
raise MenusError(msg, expected=True)
|
||||
|
||||
if len(options) > 9:
|
||||
msg = ('Cannot have more then 8 options per menu')
|
||||
raise MenusError(msg, expected=True)
|
||||
|
||||
for o in options:
|
||||
# Ensuring each item in list/tuple is a tuple
|
||||
if not isinstance(o, tuple):
|
||||
raise MenusError('Item must be tuple: {}'.format(o), expected=True)
|
||||
|
||||
if len(o) != 2:
|
||||
raise MenusError('Invalid number of tuple '
|
||||
'items:\n\n{}'.format(o))
|
||||
# Ensure index 0 is a str
|
||||
if not isinstance(o[0], six.string_types):
|
||||
msg = 'Menus are passed as [("Menu Name", MenuObject())]'
|
||||
raise MenusError(msg)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# Gets a single character form standard input. Does not echo to the screen
|
||||
class Getch(object):
|
||||
|
||||
def __init__(self):
|
||||
if sys.platform == 'win32':
|
||||
self.impl = GetchWindows()
|
||||
else:
|
||||
self.impl = GetchUnix()
|
||||
|
||||
def __call__(self):
|
||||
return self.impl()
|
||||
|
||||
|
||||
class GetchUnix(object):
|
||||
def __init__(self):
|
||||
# Not sure if these imports are required here
|
||||
# import tty, sys
|
||||
pass
|
||||
|
||||
def __call__(self):
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
ch = sys.stdin.read(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
return ch
|
||||
|
||||
|
||||
class GetchWindows(object):
|
||||
def __init__(self):
|
||||
# Not sure if this import is required
|
||||
# import msvcrt
|
||||
pass
|
||||
|
||||
def __call__(self):
|
||||
return msvcrt.getch()
|
Loading…
Reference in New Issue