import os
import gc
import sys
import glob
import logging
import warnings
import argparse
import contextlib
lang_dir = os.path.dirname(__file__)
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
[docs]def is_path(fpath):
r"""Determine if a path is valid in a case-sensitive manner.
Args:
fpath (str): Full path to be tested.
Returns:
bool: True if the path exists, False otherwise.
"""
pdir, base = os.path.split(fpath)
return (base in os.listdir(pdir))
[docs]def is_file(fname):
r"""Determine if a file is valid in a case-sensitive manner.
Args:
fname (str): Full file path to be tested.
Returns:
bool: True if the file exists, False otherwise.
"""
if not os.path.isfile(fname):
return False
return is_path(fname)
[docs]def is_dir(fdir):
r"""Determine if a directory is valid in a case-sensitive manner.
Args:
fdir (str): Full directory path to be tested.
Returns:
bool: True if the directory exists, False otherwise.
"""
if not os.path.isdir(fdir):
return False
return is_path(fdir)
[docs]def get_language_directories():
r"""Get language directories.
Returns:
list: Language directories.
"""
out = []
lang_dirs = sorted(glob.glob(os.path.join(lang_dir, '*')))
for x in lang_dirs:
if not os.path.isdir(x):
continue
base = os.path.basename(x)
if base not in ['__pycache__', 'tests']:
out.append(base)
return out
[docs]@contextlib.contextmanager
def import_language_install(language, no_import=False):
r"""Temporarily import a language installation script.
Args:
language (str): Name of language that installation script should be
imported for.
no_import (bool, optional): If True, yggdrasil will not be imported.
Defaults to False.
Yields:
module: Language installation module.
"""
if not is_file(os.path.join(lang_dir, language, 'install.py')):
if not (no_import or is_dir(os.path.join(lang_dir, language))):
from yggdrasil.languages import get_language_dir
try:
fname = os.path.basename(get_language_dir(language))
except ValueError: # pragma: debug
yield None
return
with import_language_install(fname, no_import=True) as install:
yield install
else:
yield None
else:
try:
sys.path.insert(0, os.path.join(lang_dir, language))
import install
yield install
except BaseException: # pragma: debug
install = None
raise
finally:
sys.path.pop(0)
if install is not None:
del install
if 'install' in sys.modules:
del sys.modules['install']
gc.collect()
[docs]def update_argparser(parser=None, language=None, no_import=None,
from_setup=False, arglist=None):
r"""Update argument parser with language specific arguments.
Args:
parser (argparse.ArgumentParser, optional): Existing argument parser
that should be updated. Default to None and a new argument parser
will be created.
language (str, optional): Language that argument parser should be
updated with options for. Defaults to None and all language options
will be added.
no_import (bool, optional): If True, yggdrasil will not be imported.
Defaults to None and will be set based on sys.argv.
from_setup (bool, optional): If True, the function is being called from
setup.py and the positional arguments should not be parsed and
unrecognized arguments will be ignored. Defaults to False.
arglist (list, optional): List of arguments to parse. Defaults to
None and sys.argv is used.
Returns:
argparse.ArgumentParser: Argument parser with language specific arguments.
"""
if arglist is None:
arglist = sys.argv[1:]
if (no_import is None) and from_setup:
no_import = True
all_languages = [x.lower() for x in get_language_directories()]
if parser is None:
parser = argparse.ArgumentParser(
"Run the installation scripts for one or more languages.")
parser.add_argument('--no-import', action='store_true',
help=('Don\'t import the yggdrasil package in '
'calling the installation script.'))
if not from_setup:
parser.add_argument('language', nargs='*',
choices=(['all'] + all_languages),
type=str.lower,
default='all',
help='One or more languages to install.')
if ('-h' in arglist) or ('--help' in arglist) or from_setup:
if no_import is None:
no_import = False
if language is None:
language = get_language_directories()
else:
args = parser.parse_known_args(args=arglist)[0]
args.language = getattr(
args, 'languages', getattr(args, 'language', None))
if no_import is None:
no_import = args.no_import
if language is None:
if args.language and ('all' not in args.language):
language = args.language
else:
language = all_languages
if not isinstance(language, (list, tuple)):
language = [language]
for ilang in language:
with import_language_install(ilang, no_import=no_import) as install:
if hasattr(install, 'update_argparser'):
parser = install.update_argparser(parser)
return parser
[docs]def install_language(language, results=None, no_import=None, args=None,
arglist=None):
r"""Call install for a specific language.
Args:
language (str): Name of language that should be checked.
results (dict, optional): Dictionary where result (whether or not the
language is installed) should be logged. Defaults to None and is
initialized to an empty dict.
no_import (bool, optional): If True, yggdrasil will not be imported.
Defaults to None and is set based on the value parsed from
arglist.
args (argparse.Namespace, optional): Arguments parsed from the
command line. Default to None and is created.
arglist (list, optional): List of arguments to parse. Defaults to
None and sys.argv is used.
"""
if args is None:
args = update_argparser(no_import=no_import,
arglist=arglist).parse_args(args=arglist)
if no_import is None:
no_import = args.no_import
if results is None:
results = {}
with import_language_install(language, no_import=no_import) as install:
if install is None:
logger.info("Nothing to be done for %s" % language)
name_in_pragmas = language.lower()
results[name_in_pragmas] = True
return
else:
name_in_pragmas = getattr(install, 'name_in_pragmas', language.lower())
out = install.install(args=args)
results[name_in_pragmas] = out
if not out:
warnings.warn(("Could not complete installation for {lang}. "
"{lang} support will be disabled.").format(lang=language))
else:
logger.info("Language %s installed." % language)
[docs]def install_all_languages(from_setup=False, arglist=None, **kwargs):
r"""Call install.py for all languages that have one and return a dictionary
mapping from language name to the installation state (True if install was
successful, False otherwise).
Args:
from_setup (bool, optional): If True, the function is being called from
setup.py and the positional arguments should not be parsed and
unrecognized arguments will be ignored. Defaults to False.
arglist (list, optional): List of arguments to parse. Defaults to
None and sys.argv is used.
**kwargs: Additional keyword arguments are passed to each call to
install_language.
Returns:
dict: Mapping from language name to boolean describing installation
success.
"""
if not kwargs.get('args', None):
kwargs['args'], _ = update_argparser(
from_setup=True,
no_import=kwargs.get('no_import', None),
arglist=arglist).parse_known_args(args=arglist)
installed_languages = {}
for ilang in sorted(get_language_directories()):
install_language(ilang, installed_languages, **kwargs)
return installed_languages
if __name__ == "__main__":
install_all_languages()