#!/usr/bin/env python # -*- coding: utf-8 -*- """ Options """ import json import os import pkgutil import shlex from argparse import ArgumentParser import six def build_argument_parser(): """ Builds the argument parser :return: the argument parser :rtype: ArgumentParser """ opts = ArgumentParser() opts.add_argument(dest='filename', help='Filename or release name to guess', nargs='*') naming_opts = opts.add_argument_group("Naming") naming_opts.add_argument('-t', '--type', dest='type', default=None, help='The suggested file type: movie, episode. If undefined, type will be guessed.') naming_opts.add_argument('-n', '--name-only', dest='name_only', action='store_true', default=None, help='Parse files as name only, considering "/" and "\\" like other separators.') naming_opts.add_argument('-Y', '--date-year-first', action='store_true', dest='date_year_first', default=None, help='If short date is found, consider the first digits as the year.') naming_opts.add_argument('-D', '--date-day-first', action='store_true', dest='date_day_first', default=None, help='If short date is found, consider the second digits as the day.') naming_opts.add_argument('-L', '--allowed-languages', action='append', dest='allowed_languages', default=None, help='Allowed language (can be used multiple times)') naming_opts.add_argument('-C', '--allowed-countries', action='append', dest='allowed_countries', default=None, help='Allowed country (can be used multiple times)') naming_opts.add_argument('-E', '--episode-prefer-number', action='store_true', dest='episode_prefer_number', default=None, help='Guess "serie.213.avi" as the episode 213. Without this option, ' 'it will be guessed as season 2, episode 13') naming_opts.add_argument('-T', '--expected-title', action='append', dest='expected_title', default=None, help='Expected title to parse (can be used multiple times)') naming_opts.add_argument('-G', '--expected-group', action='append', dest='expected_group', default=None, help='Expected release group (can be used multiple times)') input_opts = opts.add_argument_group("Input") input_opts.add_argument('-f', '--input-file', dest='input_file', default=None, help='Read filenames from an input text file. File should use UTF-8 charset.') output_opts = opts.add_argument_group("Output") output_opts.add_argument('-v', '--verbose', action='store_true', dest='verbose', default=None, help='Display debug output') output_opts.add_argument('-P', '--show-property', dest='show_property', default=None, help='Display the value of a single property (title, series, video_codec, year, ...)') output_opts.add_argument('-a', '--advanced', dest='advanced', action='store_true', default=None, help='Display advanced information for filename guesses, as json output') output_opts.add_argument('-s', '--single-value', dest='single_value', action='store_true', default=None, help='Keep only first value found for each property') output_opts.add_argument('-l', '--enforce-list', dest='enforce_list', action='store_true', default=None, help='Wrap each found value in a list even when property has a single value') output_opts.add_argument('-j', '--json', dest='json', action='store_true', default=None, help='Display information for filename guesses as json output') output_opts.add_argument('-y', '--yaml', dest='yaml', action='store_true', default=None, help='Display information for filename guesses as yaml output') conf_opts = opts.add_argument_group("Configuration") conf_opts.add_argument('-c', '--config', dest='config', action='append', default=None, help='Filepath to the configuration file. Configuration contains the same options as ' 'those command line options, but option names have "-" characters replaced with "_". ' 'If not defined, guessit tries to read a configuration default configuration file at ' '~/.guessit/options.(json|yml|yaml) and ~/.config/guessit/options.(json|yml|yaml). ' 'Set to "false" to disable default configuration file loading.') conf_opts.add_argument('--no-embedded-config', dest='no_embedded_config', action='store_true', default=None, help='Disable default configuration.') information_opts = opts.add_argument_group("Information") information_opts.add_argument('-p', '--properties', dest='properties', action='store_true', default=None, help='Display properties that can be guessed.') information_opts.add_argument('-V', '--values', dest='values', action='store_true', default=None, help='Display property values that can be guessed.') information_opts.add_argument('--version', dest='version', action='store_true', default=None, help='Display the guessit version.') return opts def parse_options(options=None, api=False): """ Parse given option string :param options: :type options: :param api :type boolean :return: :rtype: """ if isinstance(options, six.string_types): args = shlex.split(options) options = vars(argument_parser.parse_args(args)) elif options is None: if api: options = {} else: options = vars(argument_parser.parse_args()) elif not isinstance(options, dict): options = vars(argument_parser.parse_args(options)) return options argument_parser = build_argument_parser() class ConfigurationException(Exception): """ Exception related to configuration file. """ pass def load_config(options): """ Load configuration from configuration file, if defined. :param options: :type options: :return: :rtype: """ config_files_enabled = True custom_config_files = None if options.get('config') is not None: custom_config_files = options.get('config') if not custom_config_files \ or not custom_config_files[0] \ or custom_config_files[0].lower() in ['0', 'no', 'false', 'disabled']: config_files_enabled = False configurations = [] if config_files_enabled: home_directory = os.path.expanduser("~") cwd = os.getcwd() yaml_supported = False try: import yaml # pylint: disable=unused-variable yaml_supported = True except ImportError: pass config_file_locations = get_config_file_locations(home_directory, cwd, yaml_supported) config_files = [f for f in config_file_locations if os.path.exists(f)] if custom_config_files: config_files = config_files + custom_config_files for config_file in config_files: config_file_options = load_config_file(config_file) if config_file_options: configurations.append(config_file_options) if not options.get('no_embedded_config'): embedded_options_data = pkgutil.get_data('guessit', 'config/options.json').decode("utf-8") embedded_options = json.loads(embedded_options_data) configurations.append(embedded_options) if configurations: configurations.append(options) return merge_configurations(*configurations) return options def merge_configurations(*configurations): """ Merge configurations into a single options dict. :param configurations: :type configurations: :return: :rtype: """ merged = {} for options in configurations: pristine = options.get('pristine') if pristine: if pristine is True: merged = {} else: for to_reset in pristine: if to_reset in merged: del merged[to_reset] for (option, value) in options.items(): if value is not None and option != 'pristine': if option in merged.keys() and isinstance(merged[option], list): merged[option].extend(value) elif isinstance(value, list): merged[option] = list(value) else: merged[option] = value return merged def load_config_file(filepath): """ Load a configuration as an options dict. Format of the file is given with filepath extension. :param filepath: :type filepath: :return: :rtype: """ if filepath.endswith('.json'): with open(filepath) as config_file_data: return json.load(config_file_data) if filepath.endswith('.yaml') or filepath.endswith('.yml'): try: import yaml with open(filepath) as config_file_data: return yaml.load(config_file_data) except ImportError: # pragma: no cover raise ConfigurationException('Configuration file extension is not supported. ' 'PyYAML should be installed to support "%s" file' % ( filepath,)) raise ConfigurationException('Configuration file extension is not supported for "%s" file.' % (filepath,)) def get_config_file_locations(homedir, cwd, yaml_supported=False): """ Get all possible locations for configuration file. :param homedir: user home directory :type homedir: basestring :param cwd: current working directory :type homedir: basestring :return: :rtype: list """ locations = [] configdirs = [(os.path.join(homedir, '.guessit'), 'options'), (os.path.join(homedir, '.config', 'guessit'), 'options'), (cwd, 'guessit.options')] configexts = ['json'] if yaml_supported: configexts.append('yaml') configexts.append('yml') for configdir in configdirs: for configext in configexts: locations.append(os.path.join(configdir[0], configdir[1] + '.' + configext)) return locations