#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Options
"""
import copy
import json
import os
import shlex
from argparse import ArgumentParser
try :
from importlib . resources import read_text
except ImportError :
from importlib_resources import read_text
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) ' )
naming_opts . add_argument ( ' --includes ' , action = ' append ' , dest = ' includes ' , default = None ,
help = ' List of properties to be detected ' )
naming_opts . add_argument ( ' --excludes ' , action = ' append ' , dest = ' excludes ' , default = None ,
help = ' List of properties to be ignored ' )
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 ' )
output_opts . add_argument ( ' -i ' , ' --output-input-string ' , dest = ' output_input_string ' , action = ' store_true ' ,
default = False , help = ' Add input_string property in the output ' )
conf_opts = opts . add_argument_group ( " Configuration " )
conf_opts . add_argument ( ' -c ' , ' --config ' , dest = ' config ' , action = ' append ' , default = None ,
help = ' Filepath to configuration file. Configuration file contains the same '
' options as those from command line options, but option names have " - " characters '
' replaced with " _ " . This configuration will be merged with default and user '
' configuration files. ' )
conf_opts . add_argument ( ' --no-user-config ' , dest = ' no_user_config ' , action = ' store_true ' ,
default = None ,
help = ' Disable user configuration. If not defined, guessit tries to read configuration files '
' at ~/.guessit/options.(json|yml|yaml) and ~/.config/guessit/options.(json|yml|yaml) ' )
conf_opts . add_argument ( ' --no-default-config ' , dest = ' no_default_config ' , action = ' store_true ' ,
default = None ,
help = ' Disable default configuration. This should be done only if you are providing a full '
' configuration through user configuration or --config option. If no " advanced_config " '
' is provided by another configuration file, it will still be loaded from 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 api : boolean
: return :
: rtype :
"""
if isinstance ( options , str ) :
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 # pylint:disable=unnecessary-pass
def load_config ( options ) :
"""
Load options from configuration files , if defined and present .
: param options :
: type options :
: return :
: rtype :
"""
configurations = [ ]
if not options . get ( ' no_default_config ' ) :
default_options_data = read_text ( ' guessit.config ' , ' options.json ' )
default_options = json . loads ( default_options_data )
configurations . append ( default_options )
config_files = [ ]
if not options . get ( ' no_user_config ' ) :
home_directory = os . path . expanduser ( " ~ " )
cwd = os . getcwd ( )
yaml_supported = False
try :
import yaml # pylint:disable=unused-variable,unused-import,import-outside-toplevel
yaml_supported = True
except ImportError :
pass
config_file_locations = get_options_file_locations ( home_directory , cwd , yaml_supported )
config_files = [ f for f in config_file_locations if os . path . exists ( f ) ]
custom_config_files = options . get ( ' config ' )
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 )
config = { }
if configurations :
config = merge_options ( * configurations )
if ' advanced_config ' not in config :
# Guessit doesn't work without advanced_config, so we use default if no configuration files provides it.
default_options_data = read_text ( ' guessit.config ' , ' options.json ' )
default_options = json . loads ( default_options_data )
config [ ' advanced_config ' ] = default_options [ ' advanced_config ' ]
return config
def merge_options ( * options ) :
"""
Merge options into a single options dict .
: param options :
: type options :
: return :
: rtype :
"""
merged = { }
if options :
if options [ 0 ] :
merged . update ( copy . deepcopy ( options [ 0 ] ) )
for options in options [ 1 : ] :
if options :
pristine = options . get ( ' pristine ' )
if pristine is True :
merged = { }
elif pristine :
for to_reset in pristine :
if to_reset in merged :
del merged [ to_reset ]
for ( option , value ) in options . items ( ) :
merge_option_value ( option , value , merged )
return merged
def merge_option_value ( option , value , merged ) :
"""
Merge option value
: param option :
: param value :
: param merged :
: return :
"""
if value is not None and option != ' pristine ' :
if option in merged . keys ( ) and isinstance ( merged [ option ] , list ) :
for val in value :
if val not in merged [ option ] and val is not None :
merged [ option ] . append ( val )
elif option in merged . keys ( ) and isinstance ( merged [ option ] , dict ) :
merged [ option ] = merge_options ( merged [ option ] , value )
elif isinstance ( value , list ) :
merged [ option ] = list ( value )
else :
merged [ option ] = value
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 , encoding = ' utf-8 ' ) as config_file_data :
return json . load ( config_file_data )
if filepath . endswith ( ' .yaml ' ) or filepath . endswith ( ' .yml ' ) :
try :
import yaml # pylint:disable=import-outside-toplevel
with open ( filepath , encoding = ' utf-8 ' ) as config_file_data :
return yaml . load ( config_file_data , yaml . SafeLoader )
except ImportError as err : # pragma: no cover
raise ConfigurationException ( ' Configuration file extension is not supported. '
f ' PyYAML should be installed to support " { filepath } " file ' ) from err
try :
# Try to load input as JSON
return json . loads ( filepath )
except : # pylint: disable=bare-except
pass
raise ConfigurationException ( f ' Configuration file extension is not supported for " { filepath } " file. ' )
def get_options_file_locations ( homedir , cwd , yaml_supported = False ) :
"""
Get all possible locations for options 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