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