#!/usr/bin/env python
# -*- coding: utf-8 -*-
# It script it publish under GNU GENERAL PUBLIC LICENSE
# http://www.gnu.org/licenses/gpl-3.0.en.html
# Author: the Galaxie Curses Team, all rights reserved
import random
import re
import os
import io
import math
import time
import curses
import logging
from array import array
import GLXCurses
from GLXCurses.Constants import GLXC
from datetime import date
[docs]def check_mnemonic_in_text(text=None, mnemonic_char=None):
if text is None:
text = ""
if mnemonic_char is None:
mnemonic_char = "_"
if type(text) != str:
raise TypeError("'text' argv must be a str type or None")
if type(mnemonic_char) != str:
raise TypeError("'mnemonic_char' must be a str type or None")
if len(mnemonic_char) > 1:
raise ValueError("'mnemonic_char' must be a single char")
mnemonic_string_position = None
text_without_mnemonic_string = ""
found = 0
for x_inc in range(0, len(text)):
if text[x_inc] == mnemonic_char and found <= 0:
mnemonic_string_position = x_inc
found += 1
else:
text_without_mnemonic_string += text[x_inc]
return {"text": text_without_mnemonic_string, "position": mnemonic_string_position}
[docs]def glxc_type(thing_to_test=None):
"""
Internal method for check if object pass as argument is GLXCurses Type Object
:param thing_to_test = A object to test
:type thing_to_test: object
:return: True or False
:rtype: bool
"""
if hasattr(thing_to_test, "glxc_type") and (
thing_to_test.glxc_type == str("GLXCurses." + thing_to_test.__class__.__name__)
):
return True
else:
return False
[docs]def resize_text_wrap_char(text="", max_width=0):
"""
Resize the text , and return a new text
example: return '123' for '123456789' where max_width = 3
:param text: the original text to resize
:type text: str
:param max_width: the size of the text
:type max_width: int
:return: a resize text
:rtype: str
"""
# Try to quit as soon of possible
if type(text) != str:
raise TypeError('"text" must be a str type')
if type(max_width) != int:
raise TypeError('"max_width" must be a int type')
# just if it have something to resize
if max_width < len(text):
return text[:max_width]
else:
return text
[docs]def sizeof(value=None):
"""
Convert a num to a human readable thing it use metric prefix.
:param value: a ``value`` to translate for a future display
:type value: int or float
:return: str
:raise TypeError: when ``value`` argument is not a int or float
"""
# Metric prefixes in everyday use:
#
# yotta Y 1000000000000000000000000 10 power 24
# zetta Z 1000000000000000000000 10 power 21
# exa E 1000000000000000000 10 power 18
# peta P 1000000000000000 10 power 15
# tera T 1000000000000 10 power 12
# giga G 1000000000 10 power 9
# mega M 1000000 10 power 6
# kilo k 1000 10 power 3
# (none) (none) 1 10 power 0
# Exit a soon of possible
if type(value) != int and type(value) != float and type(value) != int:
raise TypeError("'value' must be a int or float type")
suffix = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"]
i = 0 if value < 1 else int(math.log(value, 1024)) + 1
v = value / math.pow(1024, i)
v, i = (v, i) if v > 0.5 else (v * 1024, (i - 1 if i else 0))
return str(str(int(round(v, 0))) + suffix[i])
# """
# For split a area homogeneously a area, it use by HBox and VBox for create children area
#
# Console:
# get_split_area_positions(start=0, stop=99, num=3)
# {'0': (0.0, 32.0), '1': (33.0, 65.0), '2': (66.0, 99)}
#
# :param start: The starting value of the sequence.
# :type start: int
# :param stop: The end value of the sequence
# :type stop: int
# :param num: Number of samples to generate.
# :type num: int
# :param roundtype: GLXC.RoundType
# :type roundtype: str
# :return: a dictionary with position
# :rtype : dict
# :raise TypeError: when ``start`` argument is not a :py:data:`int`
# :raise TypeError: when ``stop`` argument is not a :py:data:`int`
# :raise TypeError: when ``num`` argument is not a :py:data:`int`
# :raise TypeError: when ``roundtype`` argument is not in GLXC.RoundType :py:data:`list`
# """
# def get_split_area_positions(start=0, stop=0, num=0, roundtype=GLXC.ROUND_UP):
# # Exit a soon of possible
# if type(start) != int:
# raise TypeError("'start' must be a int type")
#
# if type(stop) != int:
# raise TypeError("'stop' must be a int type")
#
# if type(num) != int:
# raise TypeError("'num' must be a int type")
#
# if roundtype not in GLXC.RoundType:
# raise TypeError("'roundtype' must be in GLXC.RoundType list")
#
# # We make the job
# position_list = numpy.linspace(start, stop, num + 1)
# thing_to_return = dict()
# count = 0
# for i in range(num):
# if count + 1 >= num:
# item_start = position_list[int(count)]
# item_stop = stop
# else:
# item_start = position_list[int(count)]
# item_stop = position_list[int(count + 1)] - 1
#
# # We have to round the Value because that is that it balance the fact our first
# # and last element have min and max position.
# # That is clearly require for human smooth thing
# if roundtype == GLXC.ROUND_UP:
# item_start = int(round_up(item_start, decimals=0))
# item_stop = int(round_up(item_stop, decimals=0))
#
# elif roundtype == GLXC.ROUND_DOWN:
# item_start = int(round_down(item_start, decimals=0))
# item_stop = int(round_down(item_stop, decimals=0))
#
# elif roundtype == GLXC.ROUND_HALF_UP:
# item_start = int(round_half_up(item_start, decimals=0))
# item_stop = int(round_half_up(item_stop, decimals=0))
#
# elif roundtype == GLXC.ROUND_HALF_DOWN:
# item_start = int(round_half_down(item_start, decimals=0))
# item_stop = int(round_half_down(item_stop, decimals=0))
#
# thing_to_return[str(count)] = (item_start, item_stop)
# count += 1
#
# # Return something
# return thing_to_return
#
[docs]def disk_usage(path):
"""
Return something like: 94G/458G (20%).
It use teh File system it self and just request it about the drive space where is store teh file pass in argument.
:param path:
:type:
:return: something like 94G/458G (20%) with '.' as ``path`` argument
"""
st = os.statvfs(path)
total = st.f_blocks * st.f_frsize
used = st.f_bsize * st.f_bavail
try:
percent = (float(used) / total) * 100
except ZeroDivisionError: # pragma: no cover
percent = 0
line = str(sizeof(used) + "/" + sizeof(total) + " (" + str(int(percent)) + "%)")
line = " " + line + " "
return line
[docs]def round_up(n, decimals=0):
"""
https://realpython.com/python-rounding/
:param n:
:param decimals:
:return:
"""
multiplier = 10 ** decimals
if decimals == 0:
return int(math.ceil(n * multiplier) / multiplier)
return math.ceil(n * multiplier) / multiplier
[docs]def round_down(n, decimals=0):
"""
https://realpython.com/python-rounding/
:param n: the number
:type n: int or float
:param decimals: number of decimal
:type decimals: int
:return: the rounded value
:rtype: int or float
"""
multiplier = 10 ** decimals
if decimals == 0:
return int(math.floor(n * multiplier) / multiplier)
return math.floor(n * multiplier) / multiplier
[docs]def round_half_up(n, decimals=0):
"""
https://realpython.com/python-rounding/
:param n:
:param decimals:
:return:
"""
multiplier = 10 ** decimals
if decimals == 0:
return int(math.floor(n * multiplier + 0.5) / multiplier)
return math.floor(n * multiplier + 0.5) / multiplier
[docs]def round_half_down(n, decimals=0):
"""
https://realpython.com/python-rounding/
:param n:
:param decimals:
:return:
"""
multiplier = 10 ** decimals
if decimals == 0:
return int(math.ceil(n * multiplier - 0.5) / multiplier)
return math.ceil(n * multiplier - 0.5) / multiplier
[docs]def resize_text(text="", max_width=0, separator="~"):
"""
Resize the text , and return a new text
example: return '123~789' for '123456789' where max_width = 7 or 8
:param text: the original text to resize
:type text: str
:param max_width: the size of the text
:type max_width: int
:param separator: a separator a in middle of the resize text
:type separator: str
:return: a resize text
:rtype: str
"""
# Try to quit as soon of possible
if type(text) != str:
raise TypeError('"text" must be a str type')
if type(max_width) != int:
raise TypeError('"max_width" must be a int type')
if type(separator) != str:
raise TypeError('"separator" must be a str type')
# If we are here we haven't quit
if max_width < len(text):
if max_width <= 0:
return str("")
elif max_width == 1:
return str(text[:1])
elif max_width == 2:
return str(text[:1] + text[-1:])
elif max_width == 3:
return str(text[:1] + separator[:1] + text[-1:])
elif max_width == 4:
return str(text[:2] + separator[:1] + text[-1:])
elif max_width == 5:
return str(text[:2] + separator[:1] + text[-2:])
elif max_width == 6:
return str(text[:3] + separator[:1] + text[-2:])
elif max_width == 7:
return str(text[:3] + separator[:1] + text[-3:])
elif max_width == 8:
return str(text[:4] + separator[:1] + text[-3:])
elif max_width == 9:
return str(text[:4] + separator[:1] + text[-4:])
elif max_width == 10:
return str(text[:5] + separator[:1] + text[-4:])
elif max_width == 11:
return str(text[:5] + separator[:1] + text[-5:])
elif max_width == 12:
return str(text[:6] + separator[:1] + text[-5:])
elif max_width == 13:
return str(text[:6] + separator[:1] + text[-6:])
elif max_width == 14:
return str(text[:7] + separator[:1] + text[-6:])
elif max_width == 15:
return str(text[:7] + separator[:1] + text[-7:])
else:
max_width -= len(separator[:1])
max_div = int(max_width / 2)
return str(text[:max_div] + separator[:1] + text[-max_div:])
return str(text)
[docs]def clamp_to_zero(value=None):
"""
Convert any int value to positive int
:param value: a integer
:type value: int or None
:return: a integer
:rtype: int
"""
if type(value) != int and value is not None:
raise TypeError('"value" must be a int type or None')
if value is None or value <= 0:
return 0
return value
[docs]def clamp(value=None, smallest=None, largest=None):
"""
Back ``value`` inside ``smallest`` and ``largest`` value range.
:param value: The value it have to be clamped
:param smallest: The lower value
:param largest: The upper value
:type value: int or float
:type value: int or float
:return: The clamped value it depend of parameters value type, int or float will be preserve.
:rtype: int or float
"""
# Try to exit as soon of possible
if type(value) != int and type(value) != float:
raise TypeError(">value< must be a int or float type")
if type(smallest) != int and type(smallest) != float:
raise TypeError(">smallest< must be a int or float type")
if type(largest) != int and type(largest) != float:
raise TypeError(">largest< must be a int or float type")
# make the job
if type(value) == int:
if value < smallest:
value = smallest
elif value > largest:
value = largest
return int(value)
elif type(value) == float:
if value < smallest:
value = smallest
elif value > largest:
value = largest
return float(value)
[docs]def new_id():
"""
Generate a GLXCurses ID like 'E59E8457', two chars by two chars it's a random HEX
**Default size:** 8
**Default chars:** 'ABCDEF0123456789'
**Benchmark**
+----------------+---------------+----------------------------------------------+
| **Iteration** | **Duration** | **CPU Information** |
+----------------+---------------+----------------------------------------------+
| 10000000 | 99.114s | Intel(R) Core(TM) i7-2860QM CPU @ 2.50GHz |
+----------------+---------------+----------------------------------------------+
| 1000000 | 9.920s | Intel(R) Core(TM) i7-2860QM CPU @ 2.50GHz |
+----------------+---------------+----------------------------------------------+
| 100000 | 0.998s | Intel(R) Core(TM) i7-2860QM CPU @ 2.50GHz |
+----------------+---------------+----------------------------------------------+
| 10000 | 0.108s | Intel(R) Core(TM) i7-2860QM CPU @ 2.50GHz |
+----------------+---------------+----------------------------------------------+
:return: a string it represent a unique ID
:rtype: str
"""
return "%02x%02x%02x%02x".upper() % (
random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255),
)
[docs]def is_valid_id(value):
"""
Check if it's a valid id
:param value: a id to verify
:return: bool
"""
allowed = re.compile(
r"""
(
^([0-9A-F]{8})$
)
""",
re.VERBOSE | re.IGNORECASE,
)
try:
if allowed.match(value) is None:
return False
else:
return True
except TypeError:
return False
[docs]def merge_dicts(*dict_args):
"""
A merge dict fully compatible Python 2 and 3
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
[docs]def get_os_temporary_dir():
"""
Get the OS default dir , the better as it can.
It suppose to be cross platform
:return: A tmp dir path
:rtype: str
"""
text_flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
text_flags |= os.O_NOFOLLOW
bin_flags = text_flags
if hasattr(os, "O_BINARY"): # pragma: no cover
bin_flags |= os.O_BINARY
directory_list = list()
# First, try the environment.
for envname in "TMPDIR", "TEMP", "TMP":
dirname = os.getenv(envname)
if dirname:
directory_list.append(os.path.abspath(dirname))
directory_list.extend(["/tmp", "/var/tmp", "/usr/tmp"])
for directory in directory_list:
if directory != os.path.curdir:
directory = os.path.abspath(directory)
name = str("GLXCurses-")
name += str(new_id())
name += str(".cp")
filename = os.path.join(directory, name)
try:
fd = os.open(filename, bin_flags, 0o600)
try:
try:
with io.open(fd, "wb", closefd=False) as fp:
fp.write(b"Test")
finally:
os.close(fd)
finally:
os.unlink(filename)
return directory
except PermissionError: # pragma: no cover
break # no point trying more names in this directory
except OSError: # pragma: no cover
break # no point trying more names in this directory
raise FileNotFoundError(
"No usable temporary directory found in %s" % directory_list
)