Skip to content

Commit

Permalink
Add type hints
Browse files Browse the repository at this point in the history
  • Loading branch information
Josef-Friedrich committed Dec 31, 2019
1 parent f07cf01 commit df1750b
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 40 deletions.
17 changes: 10 additions & 7 deletions mscxyz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
from mscxyz.rename import rename_filename
from mscxyz.score_file_classes import MscoreXmlTree, list_scores
from mscxyz.utils import set_args, color
from mscxyz.settings import DefaultArguments
import lxml
import sys
import os
import configparser
import typing

from ._version import get_versions
__version__ = get_versions()['version']
del get_versions


def parse_config_ini(relpath: str = None):
def parse_config_ini(relpath: str = None) -> configparser.ConfigParser:
"""Parse the configuration file. The file format is INI. The default
location is ``/etc/mscxyz.ini``."""
if not relpath:
Expand All @@ -31,7 +33,8 @@ def parse_config_ini(relpath: str = None):
return config


def merge_config_into_args(config, args):
def merge_config_into_args(config: configparser.ConfigParser,
args: DefaultArguments) -> DefaultArguments:
for section in config.sections():
for key, value in config[section].items():
arg = '{}_{}'.format(section, key)
Expand All @@ -53,7 +56,7 @@ def merge_config_into_args(config, args):
return args


def heading(args, text, level=1):
def heading(args: DefaultArguments, text: str, level: int = 1):
length = len(text)
if args.help_markdown:
print('\n' + ('#' * level) + ' ' + text + '\n')
Expand All @@ -73,7 +76,7 @@ def heading(args, text, level=1):
print(text)


def code_block(args, text):
def code_block(args: DefaultArguments, text: str):
if args.help_markdown:
print('```\n' + text + '\n```')
elif args.help_rst:
Expand All @@ -82,7 +85,7 @@ def code_block(args, text):
print(text)


def show_all_help(args):
def show_all_help(args: DefaultArguments):
subcommands = ('clean', 'meta', 'lyrics', 'rename', 'export', 'help')

if args.path == 'all':
Expand All @@ -100,7 +103,7 @@ def show_all_help(args):
code_block(args, getattr(cli, args.path).format_help())


def report_errors(errors):
def report_errors(errors: typing.Sequence):
for error in errors:
print('{}: {}; message: {}'.format(
color('Error', 'white', 'on_red'),
Expand All @@ -116,7 +119,7 @@ def no_error(error, errors):
return True


def execute(args=None):
def execute(args: typing.Sequence = None):
args = cli.parser.parse_args(args)
config = parse_config_ini(args.general_config_file)
if config:
Expand Down
4 changes: 3 additions & 1 deletion mscxyz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import argparse
import textwrap
import tmep
import typing


def list_fields(fields, prefix='', suffix=''):
def list_fields(fields: typing.Sequence, prefix: str = '',
suffix: str = '') -> str:
out = []
for field in fields:
out.append(prefix + '- ' + field + suffix)
Expand Down
16 changes: 9 additions & 7 deletions mscxyz/lyrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

from mscxyz.score_file_classes import MscoreXmlTree
import lxml.etree as etree
import typing


class MscoreLyricsInterface(MscoreXmlTree):

def __init__(self, relpath):
def __init__(self, relpath: str):
super(MscoreLyricsInterface, self).__init__(relpath)
self.lyrics = self.normalize_lyrics()
self.max = self.get_max()
Expand Down Expand Up @@ -78,7 +79,7 @@ def get_max(self):

return max_lyric

def remap(self, remap_string, mscore=False):
def remap(self, remap_string: str, mscore: bool = False):
for pair in remap_string.split(','):
old = pair.split(':')[0]
new = pair.split(':')[1]
Expand All @@ -88,10 +89,10 @@ def remap(self, remap_string, mscore=False):

self.save(mscore)

def extract_one_lyrics_verse(self, number, mscore=False):
def extract_one_lyrics_verse(self, number: int, mscore: bool = False):
"""Extract a lyric verse by verse number.
:param int number: The number of the lyrics verse starting by 1
:param number: The number of the lyrics verse starting by 1
"""
score = MscoreLyricsInterface(self.relpath)

Expand All @@ -107,7 +108,8 @@ def extract_one_lyrics_verse(self, number, mscore=False):
new_name = score.relpath.replace(ext, '_' + str(number) + ext)
score.save(new_name, mscore)

def extract_lyrics(self, number=None, mscore=False):
def extract_lyrics(self, number: typing.Union[int, str] = None,
mscore: bool = False):
"""Extract one lyric verse or all lyric verses.
:param mixed number: The lyric verse number or 'all'
Expand All @@ -119,7 +121,7 @@ def extract_lyrics(self, number=None, mscore=False):
else:
self.extract_one_lyrics_verse(int(number))

def fix_lyrics_verse(self, verse_number):
def fix_lyrics_verse(self, verse_number: int):
"""
from:
Expand Down Expand Up @@ -178,7 +180,7 @@ def fix_lyrics_verse(self, verse_number):
if not isinstance(tag_syl, bool):
tag.append(tag_syl)

def fix_lyrics(self, mscore=False):
def fix_lyrics(self, mscore: bool = False):
for verse_number in range(1, self.max + 1):
self.fix_lyrics_verse(verse_number)

Expand Down
15 changes: 8 additions & 7 deletions mscxyz/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,41 @@
import lxml
import re
import tmep
import typing


class ReadOnlyFieldError(Exception):

def __init__(self, field):
def __init__(self, field: str):
self.msg = 'The field “{}” is read only!'.format(field)
Exception.__init__(self, self.msg)


class UnkownFieldError(Exception):

def __init__(self, field, valid_fields):
def __init__(self, field: str, valid_fields: typing.Sequence):
self.msg = 'Unkown field of name “{}”! Valid field names are: {}' \
.format(field, ', '.join(valid_fields))
Exception.__init__(self, self.msg)


class UnmatchedFormatStringError(Exception):

def __init__(self, format_string, input_string):
def __init__(self, format_string: str, input_string: str):
self.msg = 'Your format string “{}” doesn’t match on this ' \
'input string: “{}”'.format(format_string, input_string)
Exception.__init__(self, self.msg)


class FormatStringNoFieldError(Exception):

def __init__(self, format_string):
def __init__(self, format_string: str):
self.msg = 'No fields found in your format string “{}”!' \
.format(format_string)
Exception.__init__(self, self.msg)


def distribute_field(source, format_string):
def distribute_field(source, format_string: str):
fields = re.findall(r'\$([a-z_]*)', format_string)
if not fields:
raise FormatStringNoFieldError(format_string)
Expand Down Expand Up @@ -109,11 +110,11 @@ def _to_camel_case(field):
def __init__(self, xml_root):
self.xml_root = xml_root

def _get_element(self, field):
def _get_element(self, field: str):
for element in self.xml_root.xpath('//metaTag[@name="' + field + '"]'):
return element

def _get_text(self, field):
def _get_text(self, field: str) -> str:
element = self._get_element(field)
if hasattr(element, 'text'):
return element.text
Expand Down
12 changes: 6 additions & 6 deletions mscxyz/rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import tmep


def create_dir(path):
def create_dir(path: str):
try:
os.makedirs(os.path.dirname(path))
except OSError as exception:
if exception.errno != errno.EEXIST:
raise


def prepare_fields(fields):
def prepare_fields(fields: dict) -> dict:
args = get_args()
out = {}
for field, value in fields.items():
Expand All @@ -35,18 +35,18 @@ def prepare_fields(fields):
return out


def apply_format_string(fields):
def apply_format_string(fields: dict) -> str:
args = get_args()
fields = prepare_fields(fields)
name = tmep.parse(args.rename_format, fields)
return name


def show(old, new):
def show(old: str, new: str):
print('{} -> {}'.format(color(old, 'yellow'), color(new, 'green')))


def get_checksum(filename):
def get_checksum(filename: str) -> str:
BLOCKSIZE = 65536
hasher = hashlib.sha1()
with open(filename, 'rb') as afile:
Expand All @@ -57,7 +57,7 @@ def get_checksum(filename):
return hasher.hexdigest()


def rename_filename(source):
def rename_filename(source: str) -> Meta:
args = get_args()

meta = Meta(source)
Expand Down
52 changes: 40 additions & 12 deletions mscxyz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import platform
import subprocess
import termcolor
from typing import Sequence
from mscxyz.settings import DefaultArguments


def get_args():
def get_args() -> DefaultArguments:
"""Get the ``args`` object (the ``argparse`` object) which is stored in
the .settings.py submodule for all other submodules.
Expand All @@ -16,18 +18,19 @@ def get_args():
return getattr(settings, 'args')


def set_args(args):
def set_args(args) -> DefaultArguments:
"""Set the ``args`` object (the ``argparse`` object) which is stored in
the .settings.py submodule for all other submodules to import.
"""
from mscxyz import settings
return setattr(settings, 'args', args)
setattr(settings, 'args', args)
return args


def get_mscore_bin():
def get_mscore_bin() -> str:
"""Check the existance of the executable mscore
:return: Path of the executable if true, else false.
:return: Path of the executable.
"""
args = get_args()
system = platform.system()
Expand All @@ -47,30 +50,55 @@ def get_mscore_bin():
raise ValueError('mscore binary could not be found.')


def mscore(commands):
def mscore(cli_args: Sequence[str]) -> subprocess.Popen:
"""
:param cli_args: Command line arguments to call the mscore binary with.
"""
executable = get_mscore_bin()
commands.insert(0, executable)
p = subprocess.Popen(commands, stdout=subprocess.PIPE,
cli_args.insert(0, executable)
p = subprocess.Popen(cli_args, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
p.wait()
if p.returncode != 0:
for line in p.stderr:
print(line.decode('utf-8'))
raise ValueError('mscore exit with returncode != 0')
raise ValueError('mscore exits with returncode != 0')
return p


def re_open(input_file):
def re_open(input_file: str):
"""Open and save a MuseScore file with the ``mscore`` binary under the same
file path.
:param input_file: The path (relative or absolute) of a MuseScore file.
"""
mscore(['-o', input_file, input_file])


def convert_mxl(input_file):
def convert_mxl(input_file: str):
"""
Convert a MusicXML file into a MuseScore file.
:param input_file: The path (relative or absolute) of a MusicXML file.
"""
output_file = input_file.replace('.mxl', '.mscx')
mscore(['-o', output_file, input_file])
os.remove(input_file)


def color(*args):
def color(*args) -> str:
"""Wrapper function around ``termcolor.colored()`` to easily turn off and
on colorized terminal output on the command line.
:param args: First arg must be an string to print out, second arg a color
name.
Example usage:
.. code:: Python
color('“{}”'.format(post[field]), 'yellow')
"""
settings = get_args()
if settings.general_colorize:
return termcolor.colored(*args)
Expand Down

0 comments on commit df1750b

Please sign in to comment.