spradio-server-django/savepointradio/core/utils.py

150 lines
4.6 KiB
Python

'''
Various utlity functions that are independant of any Django app or
model.
'''
from nturl2path import pathname2url as ntpathname2url
from nturl2path import url2pathname as url2ntpathname
import re
import string
from unicodedata import normalize
from urllib.parse import urljoin, urlparse
from urllib.request import pathname2url, url2pathname
from django.core.exceptions import ObjectDoesNotExist
from django.utils.encoding import iri_to_uri, uri_to_iri
GROUP_NT_UNC = r'file://[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+/'
GROUP_NT_DRIVE_LETTER = r'file:///[A-Za-z](?:\:|\|)/'
GROUP_NON_AUTH = r'file:///[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+'
FILE_IRI_PATTERN = (
r'^(?P<unc>' +
GROUP_NT_UNC +
r')|(?P<driveletter>' +
GROUP_NT_DRIVE_LETTER +
r')|(?P<nonauth>' +
GROUP_NON_AUTH +
r')'
)
def naturalize(text):
'''
Return a normalized unicode string, with removed starting articles, for use
in natural sorting.
Code was inspired by 'django-naturalsortfield' from Nathan Reynolds:
https://github.com/nathforge/django-naturalsortfield
'''
def naturalize_int_match(match):
return '{:08d}'.format(int(match.group(0)))
text = normalize('NFKD', text).encode('ascii', 'ignore').decode('ascii')
text = text.lower()
punc = re.compile('[{}]'.format(re.escape(string.punctuation)))
text = re.sub(punc, ' ', text)
text = text.strip()
text = re.sub(r'^(a|an|the)\s+', '', text)
text = re.sub(r'\d+', naturalize_int_match, text)
return text
def quantify(quantity, model):
'''
A message based on the quantity and singular/plural name of the model.
'''
if quantity == 1:
message = '1 {}'.format(model._meta.verbose_name)
else:
message = '{} {}'.format(str(quantity),
model._meta.verbose_name_plural)
return message
def create_success_message(parent_model, parent_quantity, child_model,
child_quantity, remove=False):
'''
Creates a message for displaying the success of model modification.
'''
p_message = quantify(parent_quantity, parent_model)
c_message = quantify(child_quantity, child_model)
if remove:
return '{} successfully removed from {}'.format(c_message, p_message)
return '{} successfully added to {}.'.format(c_message, p_message)
def get_pretty_time(seconds):
'''
Displays a human-readable representation of time.
'''
if seconds > 0:
periods = [
('year', 60*60*24*365.25),
('day', 60*60*24),
('hour', 60*60),
('minute', 60),
('second', 1)
]
strings = []
for period_name, period_seconds in periods:
if seconds >= period_seconds:
period_value, seconds = divmod(seconds, period_seconds)
strings.append('{} {}{}'.format(period_value,
period_name,
('s', '')[period_value == 1]))
return ', '.join(strings)
return 'Now'
def path_to_iri(path):
'''
OS-independant attempt at converting any OS absolute path to an
RFC3987-defined IRI along with the file scheme from RFC8089.
'''
# Looking to see if the path starts with a drive letter or UNC path
# (eg. 'D:\' or '\\')
windows = re.match(r'^(?:[A-Za-z]:|\\)\\', path)
if windows:
return uri_to_iri(urljoin('file:', ntpathname2url(path)))
return uri_to_iri(urljoin('file:', pathname2url(path)))
def iri_to_path(iri):
'''
OS-independant attempt at converting an RFC3987-defined IRI with a file
scheme from RFC8089 to an OS-specific absolute path.
'''
# Drive letter IRI will have three slashes followed by the drive letter
# UNC path IRI will have two slashes followed by the UNC path
uri = iri_to_uri(iri)
patt = r'^(?:' + GROUP_NT_DRIVE_LETTER + r'|' + GROUP_NT_UNC + r')'
windows = re.match(patt, uri)
if windows:
parse = urlparse(uri)
# UNC path URIs put the server name in the 'netloc' parameter.
if parse.netloc:
return '\\' + url2ntpathname('/' + parse.netloc + parse.path)
return url2ntpathname(parse.path)
return url2pathname(urlparse(uri).path)
def clean_quotes(unclean_string):
'''
Escapes quotes for use in the Liquidsoap parser.
'''
return unclean_string.replace('"', '\\"')
def beautify_artists(artists):
'''
Turns a list of one or more artists into a proper English listing.
'''
output = ', '
if len(artists) == 2:
output = ' & '
return clean_quotes(output.join(artists))