"""A module for parsing command line arguments and logging.
"""
import argparse
import logging
import logging.config
import os
import tempfile
import time
from kvalikirstu2 import arg_parser
from kvalikirstu2 import localization
from kvalikirstu2.localization import _
_LOG_DIR = tempfile.gettempdir()
_LOG_LEVELS = [
'ERROR',
'WARNING',
'INFO',
'DEBUG'
]
_DEFAULT_CONF_SAVE_DIR = os.path.join(os.path.expanduser('~'), '.kvalikirstu2')
_DATA = {'settings': None,
'parser': None,
'running_gui': False,
'langcodes': None,
'archive_interface': None}
logger = logging.getLogger(__name__)
[docs]def register_archive_interface(interface):
""" Called when the package is loaded to allow for data archive specific arguments to be loaded.
:param DataArchiveInterface interface: The interface.
"""
if not register_archive_interface.registered:
_DATA['archive_interface'] = interface
register_archive_interface.registered = True
else:
raise AssertionError('Tried to register archive interface twice.')
register_archive_interface.registered = False
[docs]class ArgumentsNotSavedException(Exception):
"""Exception occurred while saving args."""
def __init__(self, inner_exception, *args, **kwargs):
self.inner_exception = inner_exception
super().__init__(*args, **kwargs)
[docs]class ArgumentsNotParsedException(Exception):
"""get_args called before parsing arguments"""
[docs]class ConfigFileParsingException(Exception):
"""Raised when parsing args fails (during gui run)"""
[docs]class Settings():
"""Class for command line settings."""
def __init__(self):
self.parser = arg_parser.Kvalikirstu2ConfigArgParser(config_files=arg_parser.DEFAULT_CONF_FILES,
description=_("Kvalikirstu 2, a tool for analyzing"
" qualitative studies."))
self.settings = None
self.log_config = None
[docs] def setup_logging(self):
"""Setup :mod: `logging` module."""
logging.config.dictConfig(self.log_config)
logger.info("Started logging, level: %s", self.settings.loglevel)
logger.debug("Read settings: %s", self.settings)
[docs] def get_shared_args(self):
"""Gets arguments that are shared between the GUI and the cmd version"""
_DATA['archive_interface'].add_shared_arguments(self.parser)
self.parser.add_argument('-c', '--config-file', is_config_file=True,
help='config file path')
self.parser.add_argument('--header-regex', type=str,
help=argparse.SUPPRESS)
self.parser.add_argument('--begindata', type=str,
help=argparse.SUPPRESS)
self.parser.add_argument('--enddata', type=str,
help=argparse.SUPPRESS)
self.parser.add_argument('--header_line_format', type=str,
help=argparse.SUPPRESS)
self.parser.add_argument('--min-headers-paragraph', type=int,
help=argparse.SUPPRESS)
self.parser.add_argument('--logconfig', type=str,
help=argparse.SUPPRESS)
self.parser.add_argument('--loglevel', action='store', choices=_LOG_LEVELS, type=str,
help=argparse.SUPPRESS)
self.parser.add_argument('--overwrite-temp', action='store_true',
help=_('Should temporary files be overwritten?'))
self.parser.add_argument('--separate_daf_symbol', type=str,
help=_('The symbol used for declaring a daF path in a header.'))
self.parser.add_argument('--index-data-folder', type=str, default="html",
help=_('The name of the folder where the index dependencies(subject files, etc.)'
' should be stored in.'))
self.parser.add_argument('--study-data-folder', type=str, default='data',
help=_('The name of the folder where the data in the study is.'))
self.parser.add_argument('--index-lang', type=str, default="fi_FI", help=_('Index language.'))
self.parser.add_argument('--encoding', type=str, default='utf-8',
help=_('The default encoding to be used in the application.'))
self.parser.add_argument('--csv-encoding', type=str, default='utf-8',
help=_('The encoding the .csv file is saved into.'))
self.parser.add_argument('--max-header-length', type=int, default=256,
help=_('The maximum length of a header in characters.'))
self.parser.add_argument('--subjects-per-page', type=int, default=50,
help=_('How many subjects should be shown per page by default in the index.'))
self.parser.add_argument('--max-backups', type=int, default=15,
help=_('The maximum number of folder backups.'))
self.parser.add_argument('--max-backup-folder-size', type=int, default=20e6,
help=_('The maximum size of a backed up folder.'))
self.parser.add_argument('--max-chars-in-table', type=int, default=64,
help=_('Maximum number of characters to display in the cell of the table'))
self.parser.add_argument('--max-chars-in-table-header', type=int, default=48,
help=_('Maximum number of characters to display in the cell of the table'))
self.parser.add_argument('--html-file-name', type=str, default='daF{study_number}_{name}.html',
help=_('The format string used for the html filename.'))
self.parser.add_argument('--lang', type=str, default=None, help=_('Language of the program. If not specified'
' selects it based on user locale.'))
self.parser.add_argument('--langcode_file', default=os.path.join(os.path.dirname(__file__), 'languages.txt'),
type=str, nargs='*', help=_('File that contains language codes.'))
self.parser.add_argument('--default-table-width', default=10, type=int,
help=_('Default width for table.'))
self.parser.add_argument('--table-widths', default=[6, 7, 8, 9, 10, 11, 12], type=int, nargs='*',
help=_('Table width options.'))
self.parser.add_argument('--timeout', default=30, type=float, help=_('Timeout for running subprocesses.'))
self.parser.add_argument('--detection-encodings', default=['utf-8', 'utf-8-sig'], nargs='+',
help=_('Set of encodings accepted by encoding detector. Set to empty'
' for no encodings.'))
self.parser.add_argument('--default-detection-encoding', default='windows-1252', help=_('Default encoding for'
' encoding detection '))
[docs] def parse_command_line(self, args):
"""Parse command line arguments and assign parser options to settings.
:param argv: arguments passed to ``configargparse.ArgumentParser``
:returns: parsed arguments.
:rtype: :obj:`argparse.Namespace`
"""
self.get_shared_args()
_DATA['archive_interface'].add_cli_arguments(self.parser)
self.parser.add_argument('--path', type=str,
help=_("The folder from which the data files in the study are searched from."))
self.parser.add_argument('--generate', action='store_true',
help=argparse.SUPPRESS)
self.parser.add_argument('--parse', action='store_true',
help=_('Parses files from the data folder'))
self.parser.add_argument('--citreq', action='store_true',
help=_('Generate a citation requirement for all the data files in the folder.'))
self.parser.add_argument('--add-text', nargs='?', const=None, type=str,
help=_('Text to be added to every data file.'))
self.parser.add_argument('--save-settings', nargs='?', const=None, type=str,
help=_('Save current settings.'))
self.parser.add_argument('--headers', nargs='*', default=[], type=str,
help=_('A list of headers used in the chosen task, such as header selection for'
' parsing or setting up external file studies.'))
self.parser.add_argument('--convert', default='', type=str,
help=_('Convert files in data folder based on the parameter given.'
' Options: encoding, txt, odt. Specify txt encoding with --encoding'))
self.parser.add_argument('--rename-files', action='store_true',
help=_('Rename data files in data folder.'))
self.parser.add_argument('--rename-headers', action='store_true',
help=_('Rename headers in data files'))
self.parser.add_argument('--setup_external_file_study', action='store_true',
help=_('Sets up a study containing external files and a single file that contains'
' the metadata'))
self.parser.add_argument('--print-dafs', action='store_true',
help=_('Prints the data files in the data folder.'))
self.parser.add_argument('--files', nargs='*', default=[], type=str,
help=_('A list of files to be marked with a certain language code.'))
self.parser.add_argument('--langcode', default=None, type=str,
help=_('A language code to mark files with. Specify files via the --files parameter.'))
self.parser.add_argument('--replace', nargs='*', type=str, default=[],
help=_('Replaces text in .txt and .odt files.'
' Usage: --replace old_value new_value\n'))
self.parser.add_argument('--restore', type=int, default=None,
help=_('Restores a backup with the given index.'))
self.settings = self.parser.parse_known_args(args)[0]
store_settings(self.settings)
[docs] def parse_gui(self, args):
"""Parse GUI arguments and return parser's namespace.
:param argv: arguments passed to ``configargparse.ArgumentParser``
:returns: parsed arguments.
:rtype: :obj:`argparse.Namespace`
"""
self.get_shared_args()
_DATA['archive_interface'].add_gui_arguments(self.parser)
self.parser.add_argument("--padding", type=int, help=_('The amount of padding in the controls.'))
self.settings = self.parser.parse_known_args(args)[0]
store_settings(self.settings)
[docs] def set_up_cli_settings(self, argv):
"""Sets up settings with command line arguments and starts logging"""
self.parse_command_line(argv)
_set_language()
self._set_log_config(True)
self.setup_logging()
store_parser(self.parser)
[docs] def set_up_gui_settings(self, argv):
"""Sets up settings and starts logging"""
self.parse_gui(argv)
_set_language()
self._set_log_config(False)
self.setup_logging()
store_parser(self.parser)
_DATA['running_gui'] = True
def _set_log_config(self, is_cli):
"""Sets up the log config.
:param is_cli: Is the program being run from the command line?
"""
self.log_config = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
'gui': {
'format': '%(message)s'
}
},
'handlers': {
'default': {
'level': self.settings.loglevel,
'formatter': 'standard',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(_LOG_DIR, 'kvalilog.log'),
'mode': 'a',
'maxBytes': 200000,
'backupCount': 5
}
},
'loggers': {
'kvalikirstu2': {
'level': 'DEBUG',
'handlers': ['default'],
'propagate': False
}
}
}
if is_cli:
self.log_config['handlers']['console'] = {
'level': 'INFO',
'formatter': 'standard',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout'
}
self.log_config['loggers']['kvalikirstu2']['handlers'].append('console')
else:
self.log_config['handlers']['gui'] = {
'level': 'WARN',
'formatter': 'gui',
'class': 'kvalikirstu2.warning_handler.WarningHandler'
}
self.log_config['loggers']['kvalikirstu2']['handlers'].append('gui')
[docs]def save_config(filepath=None):
"""Save current config to file
:param str filepath: The destination filepath of the config.
"""
timestamp = time.strftime("%Y%m%d_%H%M%S")
conf_dir = _DEFAULT_CONF_SAVE_DIR
if not filepath:
filepath = "kvali%s.conf" % timestamp
else:
filepath += '.conf'
if not os.path.exists(conf_dir):
logger.debug("making dir %s", conf_dir)
os.mkdir(conf_dir)
file_path = os.path.join(conf_dir, filepath)
logger.info("filepath=%s", file_path)
try:
_DATA['parser'].write_config_file(_DATA['settings'], [file_path]) # configargparse method
except Exception as exception:
logger.error(exception)
raise ArgumentsNotSavedException(exception)
[docs]def store_parser(parser):
"""Stores parser to global list _DATA"""
_DATA['parser'] = parser
[docs]def store_settings(settings):
"""Stores settings (parsed values) to global list _DATA"""
_DATA['settings'] = settings
[docs]def change_setting(setting_title, new_value):
"""Change a single setting
:param setting_title: The name of the setting.
:param new_value: The new value for the setting.
"""
vars(_DATA['settings']).update({setting_title: new_value})
[docs]def get_args():
"""Gets command line/config file arguments"""
if _DATA['settings'] is None:
logger.warning('get_args called before parsing args! (Args not parsed)')
return _DATA['settings']
[docs]def print_help():
"""Print parser help message"""
return _DATA['parser'].print_help()
[docs]def get_languages():
"""Get language codes from the configuration."""
if _DATA['langcodes'] is None:
args = get_args()
codes = []
with open(args.langcode_file, encoding=args.encoding, mode='r') as file:
for line in file:
codes.append(line.strip())
_DATA['langcodes'] = codes
return _DATA['langcodes']
def _set_language():
args = get_args()
if args.lang:
localization.set_language(args.lang)