Installing Menus

pull/2/head
sgoudham 4 years ago
parent 7fe93bacda
commit a5e6f7e847

@ -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,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,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,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…
Cancel
Save