playlist_max maybe working? need to eval
This commit is contained in:
parent
229af2351a
commit
6bb9e96384
13 changed files with 322 additions and 230 deletions
132
src/ytdl_sub/entries/script/custom_functions.py
Normal file
132
src/ytdl_sub/entries/script/custom_functions.py
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import os
|
||||||
|
import posixpath
|
||||||
|
|
||||||
|
from yt_dlp.utils import sanitize_filename
|
||||||
|
|
||||||
|
from ytdl_sub.script.functions import Functions
|
||||||
|
from ytdl_sub.script.types.map import Map
|
||||||
|
from ytdl_sub.script.types.resolvable import AnyArgument
|
||||||
|
from ytdl_sub.script.types.resolvable import Integer
|
||||||
|
from ytdl_sub.script.types.resolvable import ReturnableArgument
|
||||||
|
from ytdl_sub.script.types.resolvable import String
|
||||||
|
from ytdl_sub.script.utils.exceptions import RuntimeException
|
||||||
|
from ytdl_sub.utils.file_path import FilePathTruncater
|
||||||
|
|
||||||
|
|
||||||
|
def _pad(num: int, width: int):
|
||||||
|
return str(num).zfill(width)
|
||||||
|
|
||||||
|
|
||||||
|
_days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFunctions:
|
||||||
|
@staticmethod
|
||||||
|
def legacy_bracket_safety(value: ReturnableArgument) -> ReturnableArgument:
|
||||||
|
if isinstance(value, String):
|
||||||
|
value = String(value.value.replace("{", "{").replace("}", "}"))
|
||||||
|
return value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_native_filepath(filepath: String) -> String:
|
||||||
|
return String(filepath.value.replace(posixpath.sep, os.sep))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def truncate_filepath_if_too_long(filepath: String) -> String:
|
||||||
|
return String(FilePathTruncater.maybe_truncate_file_path(filepath.value))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sanitize(value: AnyArgument) -> String:
|
||||||
|
return String(sanitize_filename(str(value)))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sanitize_plex_episode(string: String) -> String:
|
||||||
|
sanitized_string = CustomFunctions.sanitize(string).value
|
||||||
|
out = ""
|
||||||
|
for char in sanitized_string:
|
||||||
|
match char:
|
||||||
|
case "0":
|
||||||
|
out += "0"
|
||||||
|
case "1":
|
||||||
|
out += "1"
|
||||||
|
case "2":
|
||||||
|
out += "2"
|
||||||
|
case "3":
|
||||||
|
out += "3"
|
||||||
|
case "4":
|
||||||
|
out += "4"
|
||||||
|
case "5":
|
||||||
|
out += "5"
|
||||||
|
case "6":
|
||||||
|
out += "6"
|
||||||
|
case "7":
|
||||||
|
out += "7"
|
||||||
|
case "8":
|
||||||
|
out += "8"
|
||||||
|
case "9":
|
||||||
|
out += "9"
|
||||||
|
case _:
|
||||||
|
out += char
|
||||||
|
return String(out)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_date_metadata(yyyymmdd: String) -> Map:
|
||||||
|
date_str = yyyymmdd.value
|
||||||
|
if not (date_str.isnumeric() and len(date_str) == 8):
|
||||||
|
raise RuntimeException(
|
||||||
|
f"Expected input of date_metadata to be YYYYMMDD, but received {date_str}"
|
||||||
|
)
|
||||||
|
|
||||||
|
year: int = int(date_str[:4])
|
||||||
|
month_padded: str = date_str[4:6]
|
||||||
|
day_padded: str = date_str[6:8]
|
||||||
|
|
||||||
|
month: int = int(month_padded)
|
||||||
|
day: int = int(day_padded)
|
||||||
|
year_truncated: int = int(str(year)[-2:])
|
||||||
|
|
||||||
|
day_of_year: int = sum(_days_in_month[:month]) + day
|
||||||
|
total_days_in_month: int = _days_in_month[month]
|
||||||
|
total_days_in_year: int = 365
|
||||||
|
if year % 4 == 0:
|
||||||
|
total_days_in_year += 1
|
||||||
|
if month == 2:
|
||||||
|
total_days_in_month += 1
|
||||||
|
if month > 2:
|
||||||
|
day_of_year += 1
|
||||||
|
|
||||||
|
day_of_year_reversed: int = total_days_in_year + 1 - day_of_year
|
||||||
|
month_reversed: int = 13 - month
|
||||||
|
day_reversed: int = total_days_in_month + 1 - day
|
||||||
|
|
||||||
|
return Map(
|
||||||
|
{
|
||||||
|
String("date"): yyyymmdd,
|
||||||
|
String("date_standardized"): String(f"{year}-{month_padded}-{day_padded}"),
|
||||||
|
String("year"): Integer(year),
|
||||||
|
String("month"): Integer(month),
|
||||||
|
String("day"): Integer(day),
|
||||||
|
String("year_truncated"): String(year_truncated),
|
||||||
|
String("month_padded"): String(month_padded),
|
||||||
|
String("day_padded"): String(day_padded),
|
||||||
|
String("year_truncated_reversed"): Integer(100 - year_truncated),
|
||||||
|
String("month_reversed"): Integer(month_reversed),
|
||||||
|
String("month_reversed_padded"): String(_pad(month_reversed, width=2)),
|
||||||
|
String("day_reversed"): Integer(day_reversed),
|
||||||
|
String("day_reversed_padded"): String(_pad(day_reversed, width=2)),
|
||||||
|
String("day_of_year"): Integer(day_of_year),
|
||||||
|
String("day_of_year_padded"): String(_pad(day_of_year, width=3)),
|
||||||
|
String("day_of_year_reversed"): Integer(day_of_year_reversed),
|
||||||
|
String("day_of_year_reversed_padded"): String(_pad(day_of_year_reversed, width=3)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register():
|
||||||
|
if not Functions.is_built_in("sanitize"):
|
||||||
|
Functions.register_function(CustomFunctions.legacy_bracket_safety)
|
||||||
|
Functions.register_function(CustomFunctions.truncate_filepath_if_too_long)
|
||||||
|
Functions.register_function(CustomFunctions.to_native_filepath)
|
||||||
|
Functions.register_function(CustomFunctions.sanitize)
|
||||||
|
Functions.register_function(CustomFunctions.sanitize_plex_episode)
|
||||||
|
Functions.register_function(CustomFunctions.to_date_metadata)
|
||||||
|
|
@ -1,132 +1,32 @@
|
||||||
import os
|
from typing import Dict
|
||||||
import posixpath
|
|
||||||
|
|
||||||
from yt_dlp.utils import sanitize_filename
|
from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
|
||||||
|
|
||||||
from ytdl_sub.script.functions import Functions
|
CUSTOM_FUNCTION_SCRIPTS: Dict[str, str] = {
|
||||||
from ytdl_sub.script.types.map import Map
|
"%extract_field_from_metadata_array_getter": "{ %map_get( %map(%array_at($0, 0)), %array_at($0, 1) ) }",
|
||||||
from ytdl_sub.script.types.resolvable import AnyArgument
|
"%extract_field_from_metadata_array": f"""{{
|
||||||
from ytdl_sub.script.types.resolvable import Integer
|
%if(
|
||||||
from ytdl_sub.script.types.resolvable import ReturnableArgument
|
%bool($0),
|
||||||
from ytdl_sub.script.types.resolvable import String
|
%array_extend(
|
||||||
from ytdl_sub.script.utils.exceptions import RuntimeException
|
%array_apply(
|
||||||
from ytdl_sub.utils.file_path import FilePathTruncater
|
%array_product(
|
||||||
|
%array($0),
|
||||||
|
[ %string($1) ]
|
||||||
def _pad(num: int, width: int):
|
),
|
||||||
return str(num).zfill(width)
|
%extract_field_from_metadata_array_getter
|
||||||
|
|
||||||
|
|
||||||
_days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
||||||
|
|
||||||
|
|
||||||
class CustomFunctions:
|
|
||||||
@staticmethod
|
|
||||||
def legacy_bracket_safety(value: ReturnableArgument) -> ReturnableArgument:
|
|
||||||
if isinstance(value, String):
|
|
||||||
value = String(value.value.replace("{", "{").replace("}", "}"))
|
|
||||||
return value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def to_native_filepath(filepath: String) -> String:
|
|
||||||
return String(filepath.value.replace(posixpath.sep, os.sep))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def truncate_filepath_if_too_long(filepath: String) -> String:
|
|
||||||
return String(FilePathTruncater.maybe_truncate_file_path(filepath.value))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def sanitize(value: AnyArgument) -> String:
|
|
||||||
return String(sanitize_filename(str(value)))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def sanitize_plex_episode(string: String) -> String:
|
|
||||||
sanitized_string = CustomFunctions.sanitize(string).value
|
|
||||||
out = ""
|
|
||||||
for char in sanitized_string:
|
|
||||||
match char:
|
|
||||||
case "0":
|
|
||||||
out += "0"
|
|
||||||
case "1":
|
|
||||||
out += "1"
|
|
||||||
case "2":
|
|
||||||
out += "2"
|
|
||||||
case "3":
|
|
||||||
out += "3"
|
|
||||||
case "4":
|
|
||||||
out += "4"
|
|
||||||
case "5":
|
|
||||||
out += "5"
|
|
||||||
case "6":
|
|
||||||
out += "6"
|
|
||||||
case "7":
|
|
||||||
out += "7"
|
|
||||||
case "8":
|
|
||||||
out += "8"
|
|
||||||
case "9":
|
|
||||||
out += "9"
|
|
||||||
case _:
|
|
||||||
out += char
|
|
||||||
return String(out)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def to_date_metadata(yyyymmdd: String) -> Map:
|
|
||||||
date_str = yyyymmdd.value
|
|
||||||
if not (date_str.isnumeric() and len(date_str) == 8):
|
|
||||||
raise RuntimeException(
|
|
||||||
f"Expected input of date_metadata to be YYYYMMDD, but received {date_str}"
|
|
||||||
)
|
)
|
||||||
|
),
|
||||||
year: int = int(date_str[:4])
|
[]
|
||||||
month_padded: str = date_str[4:6]
|
)
|
||||||
day_padded: str = date_str[6:8]
|
}}""",
|
||||||
|
"%extract_field_from_siblings": f"""{{
|
||||||
month: int = int(month_padded)
|
%if(
|
||||||
day: int = int(day_padded)
|
%bool({v.sibling_metadata.variable_name}),
|
||||||
year_truncated: int = int(str(year)[-2:])
|
%extract_field_from_metadata_array(
|
||||||
|
{v.sibling_metadata.variable_name},
|
||||||
day_of_year: int = sum(_days_in_month[:month]) + day
|
$0
|
||||||
total_days_in_month: int = _days_in_month[month]
|
),
|
||||||
total_days_in_year: int = 365
|
[]
|
||||||
if year % 4 == 0:
|
)
|
||||||
total_days_in_year += 1
|
}}""",
|
||||||
if month == 2:
|
|
||||||
total_days_in_month += 1
|
|
||||||
if month > 2:
|
|
||||||
day_of_year += 1
|
|
||||||
|
|
||||||
day_of_year_reversed: int = total_days_in_year + 1 - day_of_year
|
|
||||||
month_reversed: int = 13 - month
|
|
||||||
day_reversed: int = total_days_in_month + 1 - day
|
|
||||||
|
|
||||||
return Map(
|
|
||||||
{
|
|
||||||
String("date"): yyyymmdd,
|
|
||||||
String("date_standardized"): String(f"{year}-{month_padded}-{day_padded}"),
|
|
||||||
String("year"): Integer(year),
|
|
||||||
String("month"): Integer(month),
|
|
||||||
String("day"): Integer(day),
|
|
||||||
String("year_truncated"): String(year_truncated),
|
|
||||||
String("month_padded"): String(month_padded),
|
|
||||||
String("day_padded"): String(day_padded),
|
|
||||||
String("year_truncated_reversed"): Integer(100 - year_truncated),
|
|
||||||
String("month_reversed"): Integer(month_reversed),
|
|
||||||
String("month_reversed_padded"): String(_pad(month_reversed, width=2)),
|
|
||||||
String("day_reversed"): Integer(day_reversed),
|
|
||||||
String("day_reversed_padded"): String(_pad(day_reversed, width=2)),
|
|
||||||
String("day_of_year"): Integer(day_of_year),
|
|
||||||
String("day_of_year_padded"): String(_pad(day_of_year, width=3)),
|
|
||||||
String("day_of_year_reversed"): Integer(day_of_year_reversed),
|
|
||||||
String("day_of_year_reversed_padded"): String(_pad(day_of_year_reversed, width=3)),
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def register():
|
|
||||||
if not Functions.is_built_in("sanitize"):
|
|
||||||
Functions.register_function(CustomFunctions.legacy_bracket_safety)
|
|
||||||
Functions.register_function(CustomFunctions.truncate_filepath_if_too_long)
|
|
||||||
Functions.register_function(CustomFunctions.to_native_filepath)
|
|
||||||
Functions.register_function(CustomFunctions.sanitize)
|
|
||||||
Functions.register_function(CustomFunctions.sanitize_plex_episode)
|
|
||||||
Functions.register_function(CustomFunctions.to_date_metadata)
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from typing import Set
|
||||||
|
|
||||||
import mergedeep
|
import mergedeep
|
||||||
|
|
||||||
from ytdl_sub.entries.script.function_scripts import CustomFunctions
|
from ytdl_sub.entries.script.custom_functions import CustomFunctions
|
||||||
from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
|
from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
|
||||||
from ytdl_sub.entries.script.variable_definitions import Metadata
|
from ytdl_sub.entries.script.variable_definitions import Metadata
|
||||||
from ytdl_sub.entries.script.variable_definitions import MetadataVariable
|
from ytdl_sub.entries.script.variable_definitions import MetadataVariable
|
||||||
|
|
@ -35,8 +35,11 @@ def date_metadata(date_key: Variable, metadata_key: str) -> str:
|
||||||
# Metadata Getters
|
# Metadata Getters
|
||||||
|
|
||||||
|
|
||||||
def _get_internal(
|
def _get(
|
||||||
metadata: Metadata, key: MetadataVariable, default: Optional[Variable | str | int | Dict | List]
|
cast: str,
|
||||||
|
metadata: Metadata,
|
||||||
|
key: MetadataVariable,
|
||||||
|
default: Optional[Variable | str | int | Dict | List],
|
||||||
) -> str:
|
) -> str:
|
||||||
if default is None:
|
if default is None:
|
||||||
# TODO: assert with good error message if key DNE
|
# TODO: assert with good error message if key DNE
|
||||||
|
|
@ -52,57 +55,51 @@ def _get_internal(
|
||||||
else:
|
else:
|
||||||
out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {default})"
|
out = f"%map_get_non_empty({metadata.variable_name}, '{key.metadata_key}', {default})"
|
||||||
|
|
||||||
return f"%legacy_bracket_safety({out})"
|
return f"{{ %legacy_bracket_safety(%{cast}({out})) }}"
|
||||||
|
|
||||||
|
|
||||||
def _get_int(
|
|
||||||
metadata: Metadata, key: MetadataVariable, default: Optional[Variable | int] = None
|
|
||||||
) -> str:
|
|
||||||
return f"{{%int({_get_internal(metadata=metadata, key=key, default=default)})}}"
|
|
||||||
|
|
||||||
|
|
||||||
def _get(
|
|
||||||
metadata: Metadata, key: MetadataVariable, default: Optional[Variable | str | int | Dict | List]
|
|
||||||
) -> str:
|
|
||||||
return f"{{{_get_internal(metadata=metadata, key=key, default=default)}}}"
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################################
|
###############################################################################################
|
||||||
# Entry Getters
|
# Entry Getters
|
||||||
|
|
||||||
|
|
||||||
def entry_get(
|
def entry_get_str(key: MetadataVariable, default: Optional[Variable | str] = None) -> str:
|
||||||
key: MetadataVariable, default: Optional[Variable | str | int | Dict | List] = None
|
return _get("string", metadata=v.entry_metadata, key=key, default=default)
|
||||||
) -> str:
|
|
||||||
return _get(metadata=v.entry_metadata, key=key, default=default)
|
|
||||||
|
|
||||||
|
|
||||||
def entry_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
|
def entry_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
|
||||||
return _get_int(metadata=v.entry_metadata, key=key, default=default)
|
return _get("int", metadata=v.entry_metadata, key=key, default=default)
|
||||||
|
|
||||||
|
|
||||||
|
def entry_get_map(key: MetadataVariable, default: Optional[Variable | Dict] = None):
|
||||||
|
return _get("map", metadata=v.entry_metadata, key=key, default=default)
|
||||||
|
|
||||||
|
|
||||||
|
def entry_get_array(key: MetadataVariable, default: Optional[Variable | List] = None):
|
||||||
|
return _get("array", metadata=v.entry_metadata, key=key, default=default)
|
||||||
|
|
||||||
|
|
||||||
###############################################################################################
|
###############################################################################################
|
||||||
# Playlist Getters
|
# Playlist Getters
|
||||||
|
|
||||||
|
|
||||||
def playlist_get(key: MetadataVariable, default: Optional[Variable | str | int] = None) -> str:
|
def playlist_get_str(key: MetadataVariable, default: Optional[Variable | str] = None) -> str:
|
||||||
return _get(metadata=v.playlist_metadata, key=key, default=default)
|
return _get("string", metadata=v.playlist_metadata, key=key, default=default)
|
||||||
|
|
||||||
|
|
||||||
def playlist_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
|
def playlist_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
|
||||||
return _get_int(metadata=v.playlist_metadata, key=key, default=default)
|
return _get("int", metadata=v.playlist_metadata, key=key, default=default)
|
||||||
|
|
||||||
|
|
||||||
###############################################################################################
|
###############################################################################################
|
||||||
# Source Getters
|
# Source Getters
|
||||||
|
|
||||||
|
|
||||||
def source_get(key: MetadataVariable, default: Optional[Variable | str | int] = None) -> str:
|
def source_get_str(key: MetadataVariable, default: Optional[Variable | str] = None) -> str:
|
||||||
return _get(metadata=v.source_metadata, key=key, default=default)
|
return _get("string", metadata=v.source_metadata, key=key, default=default)
|
||||||
|
|
||||||
|
|
||||||
def source_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
|
def source_get_int(key: MetadataVariable, default: Optional[Variable | int] = None) -> str:
|
||||||
return _get_int(metadata=v.source_metadata, key=key, default=default)
|
return _get("int", metadata=v.source_metadata, key=key, default=default)
|
||||||
|
|
||||||
|
|
||||||
###############################################################################################
|
###############################################################################################
|
||||||
|
|
@ -116,32 +113,32 @@ ENTRY_HARDCODED_VARIABLES: Dict[Variable, str] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTRY_RELATIVE_VARIABLES: Dict[MetadataVariable, str] = {
|
ENTRY_RELATIVE_VARIABLES: Dict[MetadataVariable, str] = {
|
||||||
v.playlist_metadata: entry_get(v.playlist_metadata, {}),
|
v.playlist_metadata: entry_get_map(v.playlist_metadata, {}),
|
||||||
v.source_metadata: entry_get(v.source_metadata, {}),
|
v.source_metadata: entry_get_map(v.source_metadata, {}),
|
||||||
v.sibling_metadata: entry_get(v.sibling_metadata, []),
|
v.sibling_metadata: entry_get_array(v.sibling_metadata, []),
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTRY_REQUIRED_VARIABLES: Dict[MetadataVariable, str] = {
|
ENTRY_REQUIRED_VARIABLES: Dict[MetadataVariable, str] = {
|
||||||
v.uid: entry_get(v.uid),
|
v.uid: entry_get_str(v.uid),
|
||||||
v.extractor_key: entry_get(v.extractor_key),
|
v.extractor_key: entry_get_str(v.extractor_key),
|
||||||
v.epoch: entry_get_int(v.epoch),
|
v.epoch: entry_get_int(v.epoch),
|
||||||
v.webpage_url: entry_get(v.webpage_url),
|
v.webpage_url: entry_get_str(v.webpage_url),
|
||||||
v.ext: entry_get(v.ext),
|
v.ext: entry_get_str(v.ext),
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTRY_DEFAULT_VARIABLES: Dict[MetadataVariable, str] = {
|
ENTRY_DEFAULT_VARIABLES: Dict[MetadataVariable, str] = {
|
||||||
v.playlist_index: playlist_get_int(v.playlist_index, 1),
|
v.playlist_index: playlist_get_int(v.playlist_index, 1),
|
||||||
v.title: entry_get(v.title, v.uid),
|
v.title: entry_get_str(v.title, v.uid),
|
||||||
v.extractor: entry_get(v.extractor, v.extractor_key),
|
v.extractor: entry_get_str(v.extractor, v.extractor_key),
|
||||||
v.description: entry_get(v.description, ""),
|
v.description: entry_get_str(v.description, ""),
|
||||||
v.uploader_id: entry_get(v.uploader_id, v.uid),
|
v.uploader_id: entry_get_str(v.uploader_id, v.uid),
|
||||||
v.uploader: entry_get(v.uploader, v.uploader_id),
|
v.uploader: entry_get_str(v.uploader, v.uploader_id),
|
||||||
v.uploader_url: entry_get(v.uploader_url, v.webpage_url),
|
v.uploader_url: entry_get_str(v.uploader_url, v.webpage_url),
|
||||||
v.upload_date: entry_get(v.upload_date, v.epoch_date),
|
v.upload_date: entry_get_str(v.upload_date, v.epoch_date),
|
||||||
v.release_date: entry_get(v.release_date, v.upload_date),
|
v.release_date: entry_get_str(v.release_date, v.upload_date),
|
||||||
v.channel: entry_get(v.channel, v.uploader),
|
v.channel: entry_get_str(v.channel, v.uploader),
|
||||||
v.creator: entry_get(v.creator, v.channel),
|
v.creator: entry_get_str(v.creator, v.channel),
|
||||||
v.channel_id: entry_get(v.channel_id, v.uploader_id),
|
v.channel_id: entry_get_str(v.channel_id, v.uploader_id),
|
||||||
}
|
}
|
||||||
|
|
||||||
# MARK AS UNRESOLVABLE UNTIL THEY ARE ADDED
|
# MARK AS UNRESOLVABLE UNTIL THEY ARE ADDED
|
||||||
|
|
@ -212,14 +209,14 @@ ENTRY_RELEASE_DATE_VARIABLES: Dict[Variable, str] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
PLAYLIST_VARIABLES: Dict[Variable, str] = {
|
PLAYLIST_VARIABLES: Dict[Variable, str] = {
|
||||||
v.playlist_uid: playlist_get(v.playlist_uid, v.uid),
|
v.playlist_uid: playlist_get_str(v.playlist_uid, v.uid),
|
||||||
v.playlist_title: playlist_get(v.playlist_title, v.title),
|
v.playlist_title: playlist_get_str(v.playlist_title, v.title),
|
||||||
v.playlist_webpage_url: playlist_get(v.playlist_webpage_url, v.webpage_url),
|
v.playlist_webpage_url: playlist_get_str(v.playlist_webpage_url, v.webpage_url),
|
||||||
v.playlist_count: playlist_get_int(v.playlist_count, 1),
|
v.playlist_count: playlist_get_int(v.playlist_count, 1),
|
||||||
v.playlist_description: playlist_get(v.playlist_description, v.description),
|
v.playlist_description: playlist_get_str(v.playlist_description, v.description),
|
||||||
v.playlist_uploader_id: playlist_get(v.playlist_uploader_id, v.uploader_id),
|
v.playlist_uploader_id: playlist_get_str(v.playlist_uploader_id, v.uploader_id),
|
||||||
v.playlist_uploader: playlist_get(v.playlist_uploader, v.uploader),
|
v.playlist_uploader: playlist_get_str(v.playlist_uploader, v.uploader),
|
||||||
v.playlist_uploader_url: playlist_get(v.playlist_uploader_url, v.playlist_webpage_url),
|
v.playlist_uploader_url: playlist_get_str(v.playlist_uploader_url, v.playlist_webpage_url),
|
||||||
}
|
}
|
||||||
|
|
||||||
PLAYLIST_DERIVED_VARIABLES: Dict[Variable, str] = {
|
PLAYLIST_DERIVED_VARIABLES: Dict[Variable, str] = {
|
||||||
|
|
@ -234,15 +231,15 @@ PLAYLIST_DERIVED_VARIABLES: Dict[Variable, str] = {
|
||||||
|
|
||||||
|
|
||||||
SOURCE_VARIABLES: Dict[Variable, str] = {
|
SOURCE_VARIABLES: Dict[Variable, str] = {
|
||||||
v.source_uid: source_get(v.source_uid, v.playlist_uid),
|
v.source_uid: source_get_str(v.source_uid, v.playlist_uid),
|
||||||
v.source_title: source_get(v.source_title, v.playlist_title),
|
v.source_title: source_get_str(v.source_title, v.playlist_title),
|
||||||
v.source_webpage_url: source_get(v.source_webpage_url, v.playlist_webpage_url),
|
v.source_webpage_url: source_get_str(v.source_webpage_url, v.playlist_webpage_url),
|
||||||
v.source_index: source_get_int(v.source_index, 1),
|
v.source_index: source_get_int(v.source_index, 1),
|
||||||
v.source_count: source_get_int(v.source_count, 1),
|
v.source_count: source_get_int(v.source_count, 1),
|
||||||
v.source_description: source_get(v.source_description, v.playlist_description),
|
v.source_description: source_get_str(v.source_description, v.playlist_description),
|
||||||
v.source_uploader_id: source_get(v.source_uploader_id, v.playlist_uploader_id),
|
v.source_uploader_id: source_get_str(v.source_uploader_id, v.playlist_uploader_id),
|
||||||
v.source_uploader: source_get(v.source_uploader, v.playlist_uploader),
|
v.source_uploader: source_get_str(v.source_uploader, v.playlist_uploader),
|
||||||
v.source_uploader_url: source_get(v.source_uploader_url, v.source_webpage_url),
|
v.source_uploader_url: source_get_str(v.source_uploader_url, v.source_webpage_url),
|
||||||
}
|
}
|
||||||
|
|
||||||
SOURCE_DERIVED_VARIABLES: Dict[Variable, str] = {
|
SOURCE_DERIVED_VARIABLES: Dict[Variable, str] = {
|
||||||
|
|
@ -251,7 +248,15 @@ SOURCE_DERIVED_VARIABLES: Dict[Variable, str] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
SIBLING_VARIABLES: Dict[Variable, str] = {
|
SIBLING_VARIABLES: Dict[Variable, str] = {
|
||||||
v.playlist_max_upload_year: "TODO!!!",
|
v.playlist_max_upload_year: f"""{{
|
||||||
|
%array_reduce(
|
||||||
|
%if_passthrough(
|
||||||
|
%extract_field_from_siblings('{v.upload_year.variable_name}'),
|
||||||
|
[{v.upload_year.variable_name}]
|
||||||
|
),
|
||||||
|
%max
|
||||||
|
)
|
||||||
|
}}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
SIBLING_DERIVED_VARIABLES: Dict[Variable, str] = {
|
SIBLING_DERIVED_VARIABLES: Dict[Variable, str] = {
|
||||||
|
|
@ -270,6 +275,8 @@ mergedeep.merge(
|
||||||
ENTRY_DERIVED_VARIABLES,
|
ENTRY_DERIVED_VARIABLES,
|
||||||
ENTRY_UPLOAD_DATE_VARIABLES,
|
ENTRY_UPLOAD_DATE_VARIABLES,
|
||||||
ENTRY_RELEASE_DATE_VARIABLES,
|
ENTRY_RELEASE_DATE_VARIABLES,
|
||||||
|
SIBLING_VARIABLES,
|
||||||
|
SIBLING_DERIVED_VARIABLES,
|
||||||
PLAYLIST_VARIABLES,
|
PLAYLIST_VARIABLES,
|
||||||
PLAYLIST_DERIVED_VARIABLES,
|
PLAYLIST_DERIVED_VARIABLES,
|
||||||
SOURCE_VARIABLES,
|
SOURCE_VARIABLES,
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,22 @@ from ytdl_sub.script.types.resolvable import LambdaTwo
|
||||||
from ytdl_sub.script.types.resolvable import Resolvable
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
from ytdl_sub.script.utils.exceptions import UNREACHABLE
|
from ytdl_sub.script.utils.exceptions import UNREACHABLE
|
||||||
from ytdl_sub.script.utils.exceptions import ArrayValueDoesNotExist
|
from ytdl_sub.script.utils.exceptions import ArrayValueDoesNotExist
|
||||||
|
from ytdl_sub.script.utils.exceptions import FunctionRuntimeException
|
||||||
|
|
||||||
|
|
||||||
class ArrayFunctions:
|
class ArrayFunctions:
|
||||||
|
@staticmethod
|
||||||
|
def array(maybe_array: AnyArgument) -> Array:
|
||||||
|
if not isinstance(maybe_array, Array):
|
||||||
|
raise FunctionRuntimeException(
|
||||||
|
f"Tried and failed to cast {maybe_array.type_name()} as an Array"
|
||||||
|
)
|
||||||
|
return maybe_array
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def array_size(array: Array) -> Integer:
|
||||||
|
return Integer(len(array.value))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def array_extend(*arrays: Array) -> Array:
|
def array_extend(*arrays: Array) -> Array:
|
||||||
"""
|
"""
|
||||||
|
|
@ -27,7 +40,7 @@ class ArrayFunctions:
|
||||||
return Array(output)
|
return Array(output)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def array_at(array: Array, idx: Integer) -> Resolvable:
|
def array_at(array: Array, idx: Integer) -> AnyArgument:
|
||||||
"""
|
"""
|
||||||
Return the element in the Array at index ``idx``.
|
Return the element in the Array at index ``idx``.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,15 @@ class ConditionalFunctions:
|
||||||
if condition.value:
|
if condition.value:
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def if_passthrough(
|
||||||
|
maybe_true_arg: ReturnableArgumentA, else_arg: ReturnableArgumentB
|
||||||
|
) -> Union[ReturnableArgumentA, ReturnableArgumentB]:
|
||||||
|
"""
|
||||||
|
Conditional ``if`` statement that returns the ``maybe_true_arg`` if it evaluates to True,
|
||||||
|
otherwise returns ``else_arg``.
|
||||||
|
"""
|
||||||
|
if bool(maybe_true_arg.value):
|
||||||
|
return maybe_true_arg
|
||||||
|
return else_arg
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,45 @@ from ytdl_sub.script.types.resolvable import Integer
|
||||||
from ytdl_sub.script.types.resolvable import LambdaThree
|
from ytdl_sub.script.types.resolvable import LambdaThree
|
||||||
from ytdl_sub.script.types.resolvable import LambdaTwo
|
from ytdl_sub.script.types.resolvable import LambdaTwo
|
||||||
from ytdl_sub.script.types.resolvable import String
|
from ytdl_sub.script.types.resolvable import String
|
||||||
|
from ytdl_sub.script.utils.exceptions import FunctionRuntimeException
|
||||||
from ytdl_sub.script.utils.exceptions import KeyDoesNotExistRuntimeException
|
from ytdl_sub.script.utils.exceptions import KeyDoesNotExistRuntimeException
|
||||||
|
from ytdl_sub.script.utils.exceptions import KeyNotHashableRuntimeException
|
||||||
|
|
||||||
|
|
||||||
class MapFunctions:
|
class MapFunctions:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def map_get(mapping: Map, key: Hashable, default: Optional[AnyArgument] = None) -> AnyArgument:
|
def map(maybe_mapping: AnyArgument) -> Map:
|
||||||
|
if not isinstance(maybe_mapping, Map):
|
||||||
|
raise FunctionRuntimeException(
|
||||||
|
f"Tried and failed to cast {maybe_mapping.type_name()} as a Map"
|
||||||
|
)
|
||||||
|
return maybe_mapping
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def map_size(mapping: Map) -> Integer:
|
||||||
|
return Integer(len(mapping.value))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def map_contains(mapping: Map, key: AnyArgument) -> Boolean:
|
||||||
|
"""
|
||||||
|
Returns True if the key is in the Map. False otherwise.
|
||||||
|
"""
|
||||||
|
if not isinstance(key, Hashable):
|
||||||
|
raise KeyNotHashableRuntimeException(
|
||||||
|
f"Tried to use {key.type_name()} as a Map key, but it is not hashable."
|
||||||
|
)
|
||||||
|
|
||||||
|
return Boolean(key in mapping.value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def map_get(
|
||||||
|
mapping: Map, key: AnyArgument, default: Optional[AnyArgument] = None
|
||||||
|
) -> AnyArgument:
|
||||||
"""
|
"""
|
||||||
Return ``key``'s value within the Map. If ``key`` does not exist, and ``default`` is
|
Return ``key``'s value within the Map. If ``key`` does not exist, and ``default`` is
|
||||||
provided, it will return ``default``. Otherwise, will error.
|
provided, it will return ``default``. Otherwise, will error.
|
||||||
"""
|
"""
|
||||||
if key not in mapping.value:
|
if not MapFunctions.map_contains(mapping=mapping, key=key).value:
|
||||||
if default is not None:
|
if default is not None:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
@ -29,7 +57,7 @@ class MapFunctions:
|
||||||
return mapping.value[key]
|
return mapping.value[key]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def map_get_non_empty(mapping: Map, key: Hashable, default: AnyArgument) -> AnyArgument:
|
def map_get_non_empty(mapping: Map, key: AnyArgument, default: AnyArgument) -> AnyArgument:
|
||||||
"""
|
"""
|
||||||
Return ``key``'s value within the Map. If ``key`` does not exist or is an empty string,
|
Return ``key``'s value within the Map. If ``key`` does not exist or is an empty string,
|
||||||
return ``default``. Otherwise, will error.
|
return ``default``. Otherwise, will error.
|
||||||
|
|
@ -39,13 +67,6 @@ class MapFunctions:
|
||||||
return default
|
return default
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def map_contains(mapping: Map, key: Hashable) -> Boolean:
|
|
||||||
"""
|
|
||||||
Returns True if the key is in the Map. False otherwise.
|
|
||||||
"""
|
|
||||||
return Boolean(key in mapping.value)
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from ytdl_sub.script.types.array import UnresolvedArray
|
||||||
from ytdl_sub.script.types.resolvable import Argument
|
from ytdl_sub.script.types.resolvable import Argument
|
||||||
from ytdl_sub.script.types.resolvable import BuiltInFunctionType
|
from ytdl_sub.script.types.resolvable import BuiltInFunctionType
|
||||||
from ytdl_sub.script.types.resolvable import FunctionType
|
from ytdl_sub.script.types.resolvable import FunctionType
|
||||||
|
from ytdl_sub.script.types.resolvable import FutureResolvable
|
||||||
from ytdl_sub.script.types.resolvable import Lambda
|
from ytdl_sub.script.types.resolvable import Lambda
|
||||||
from ytdl_sub.script.types.resolvable import NamedCustomFunction
|
from ytdl_sub.script.types.resolvable import NamedCustomFunction
|
||||||
from ytdl_sub.script.types.resolvable import Resolvable
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
|
|
@ -105,6 +106,8 @@ class BuiltInFunction(Function, BuiltInFunctionType):
|
||||||
def _arg_output_type(cls, arg: Argument) -> Type[Argument]:
|
def _arg_output_type(cls, arg: Argument) -> Type[Argument]:
|
||||||
if isinstance(arg, BuiltInFunction):
|
if isinstance(arg, BuiltInFunction):
|
||||||
return arg.output_type()
|
return arg.output_type()
|
||||||
|
if isinstance(arg, FutureResolvable):
|
||||||
|
return arg.future_resolvable_type()
|
||||||
return type(arg)
|
return type(arg)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ def is_type_compatible(
|
||||||
arg_type: Type[NamedType] = arg.__class__
|
arg_type: Type[NamedType] = arg.__class__
|
||||||
if isinstance(arg, BuiltInFunctionType):
|
if isinstance(arg, BuiltInFunctionType):
|
||||||
arg_type = arg.output_type() # built-in function
|
arg_type = arg.output_type() # built-in function
|
||||||
elif isinstance(arg, FutureResolvable):
|
|
||||||
|
if isinstance(arg, FutureResolvable):
|
||||||
arg_type = arg.future_resolvable_type()
|
arg_type = arg.future_resolvable_type()
|
||||||
elif isinstance(arg, FunctionType):
|
elif isinstance(arg, FunctionType):
|
||||||
return True # custom-function, can be anything, so pass for now
|
return True # custom-function, can be anything, so pass for now
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
|
from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS
|
||||||
from ytdl_sub.entries.script.variable_scripts import UNRESOLVED_VARIABLES
|
from ytdl_sub.entries.script.variable_scripts import UNRESOLVED_VARIABLES
|
||||||
from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS
|
from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS
|
||||||
from ytdl_sub.script.script import Script
|
from ytdl_sub.script.script import Script
|
||||||
|
|
@ -12,7 +13,9 @@ from ytdl_sub.utils.script import ScriptUtils
|
||||||
|
|
||||||
class Scriptable(ABC):
|
class Scriptable(ABC):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.script = Script(copy.deepcopy(VARIABLE_SCRIPTS))
|
self.script = Script(
|
||||||
|
dict(copy.deepcopy(VARIABLE_SCRIPTS), **copy.deepcopy(CUSTOM_FUNCTION_SCRIPTS))
|
||||||
|
)
|
||||||
self.unresolvable: Set[str] = copy.deepcopy(UNRESOLVED_VARIABLES)
|
self.unresolvable: Set[str] = copy.deepcopy(UNRESOLVED_VARIABLES)
|
||||||
|
|
||||||
def update_script(self) -> None:
|
def update_script(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from resources import file_fixture_path
|
||||||
from yt_dlp.utils import sanitize_filename
|
from yt_dlp.utils import sanitize_filename
|
||||||
|
|
||||||
from ytdl_sub.config.config_file import ConfigFile
|
from ytdl_sub.config.config_file import ConfigFile
|
||||||
from ytdl_sub.entries.script.function_scripts import CustomFunctions
|
from ytdl_sub.entries.script.custom_functions import CustomFunctions
|
||||||
from ytdl_sub.subscriptions.subscription_download import SubscriptionDownload
|
from ytdl_sub.subscriptions.subscription_download import SubscriptionDownload
|
||||||
from ytdl_sub.utils.file_handler import FileHandler
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
from ytdl_sub.utils.logger import Logger
|
from ytdl_sub.utils.logger import Logger
|
||||||
|
|
|
||||||
|
|
@ -15,21 +15,21 @@ from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
|
||||||
|
|
||||||
class TestArray:
|
class TestArray:
|
||||||
def test_return(self):
|
def test_return(self):
|
||||||
assert Script({"array": "{['a', 3.14]}"}).resolve() == ScriptOutput(
|
assert Script({"arr": "{['a', 3.14]}"}).resolve() == ScriptOutput(
|
||||||
{"array": Array([String("a"), Float(3.14)])}
|
{"arr": Array([String("a"), Float(3.14)])}
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_return_as_str(self):
|
def test_return_as_str(self):
|
||||||
assert Script({"array": "str: {['a', 3.14]}"}).resolve() == ScriptOutput(
|
assert Script({"arr": "str: {['a', 3.14]}"}).resolve() == ScriptOutput(
|
||||||
{"array": String('str: ["a", 3.14]')}
|
{"arr": String('str: ["a", 3.14]')}
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_nested_array(self):
|
def test_nested_array(self):
|
||||||
assert Script(
|
assert Script(
|
||||||
{"array": "{['level1', ['level2', ['level3', 'level3'], 'level2'], 'level1']}"}
|
{"arr": "{['level1', ['level2', ['level3', 'level3'], 'level2'], 'level1']}"}
|
||||||
).resolve() == ScriptOutput(
|
).resolve() == ScriptOutput(
|
||||||
{
|
{
|
||||||
"array": Array(
|
"arr": Array(
|
||||||
[
|
[
|
||||||
String("level1"),
|
String("level1"),
|
||||||
Array(
|
Array(
|
||||||
|
|
@ -55,7 +55,7 @@ class TestArray:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_empty(self, array: str):
|
def test_empty(self, array: str):
|
||||||
assert Script({"array": array}).resolve() == ScriptOutput({"array": Array([])})
|
assert Script({"arr": array}).resolve() == ScriptOutput({"arr": Array([])})
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"array",
|
"array",
|
||||||
|
|
@ -73,7 +73,7 @@ class TestArray:
|
||||||
InvalidSyntaxException,
|
InvalidSyntaxException,
|
||||||
match=re.escape(str(_UNEXPECTED_COMMA_ARGUMENT(ParsedArgType.ARRAY))),
|
match=re.escape(str(_UNEXPECTED_COMMA_ARGUMENT(ParsedArgType.ARRAY))),
|
||||||
):
|
):
|
||||||
Script({"array": array}).resolve()
|
Script({"arr": array}).resolve()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"array",
|
"array",
|
||||||
|
|
@ -90,7 +90,7 @@ class TestArray:
|
||||||
InvalidSyntaxException,
|
InvalidSyntaxException,
|
||||||
match=re.escape(str(_UNEXPECTED_CHAR_ARGUMENT(ParsedArgType.ARRAY))),
|
match=re.escape(str(_UNEXPECTED_CHAR_ARGUMENT(ParsedArgType.ARRAY))),
|
||||||
):
|
):
|
||||||
assert Script({"array": array}).resolve()
|
assert Script({"arr": array}).resolve()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"array",
|
"array",
|
||||||
|
|
@ -104,7 +104,7 @@ class TestArray:
|
||||||
InvalidSyntaxException,
|
InvalidSyntaxException,
|
||||||
match=re.escape(str(_UNEXPECTED_CHAR_ARGUMENT(ParsedArgType.SCRIPT))),
|
match=re.escape(str(_UNEXPECTED_CHAR_ARGUMENT(ParsedArgType.SCRIPT))),
|
||||||
):
|
):
|
||||||
assert Script({"array": array}).resolve()
|
assert Script({"arr": array}).resolve()
|
||||||
|
|
||||||
def test_custom_function(self):
|
def test_custom_function(self):
|
||||||
assert Script(
|
assert Script(
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class TestFunction:
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
IncompatibleFunctionArguments,
|
IncompatibleFunctionArguments,
|
||||||
match=_incompatible_arguments_match(
|
match=_incompatible_arguments_match(
|
||||||
expected="Map, Hashable, Optional[AnyArgument]",
|
expected="Map, AnyArgument, Optional[AnyArgument]",
|
||||||
recieved="%if(...)->Union[Array, Map], String",
|
recieved="%if(...)->Union[Array, Map], String",
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,13 @@ from ytdl_sub.script.utils.exceptions import KeyNotHashableRuntimeException
|
||||||
|
|
||||||
class TestMap:
|
class TestMap:
|
||||||
def test_return(self):
|
def test_return(self):
|
||||||
assert Script({"map": "{{'a': 3.14}}"}).resolve() == ScriptOutput(
|
assert Script({"dict": "{{'a': 3.14}}"}).resolve() == ScriptOutput(
|
||||||
{"map": Map({String("a"): Float(3.14)})}
|
{"dict": Map({String("a"): Float(3.14)})}
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_return_as_str(self):
|
def test_return_as_str(self):
|
||||||
assert Script({"map": "json: {{'a': 3.14}}"}).resolve() == ScriptOutput(
|
assert Script({"dict": "json: {{'a': 3.14}}"}).resolve() == ScriptOutput(
|
||||||
{"map": String('json: {"a": 3.14}')}
|
{"dict": String('json: {"a": 3.14}')}
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_nested_map(self):
|
def test_nested_map(self):
|
||||||
|
|
@ -45,9 +45,9 @@ class TestMap:
|
||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
assert Script({"map": map_str}).resolve() == ScriptOutput(
|
assert Script({"dict": map_str}).resolve() == ScriptOutput(
|
||||||
{
|
{
|
||||||
"map": Map(
|
"dict": Map(
|
||||||
{
|
{
|
||||||
String("level1"): Map(
|
String("level1"): Map(
|
||||||
{
|
{
|
||||||
|
|
@ -78,7 +78,7 @@ class TestMap:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_empty_map(self, empty_map: str):
|
def test_empty_map(self, empty_map: str):
|
||||||
assert Script({"map": empty_map}).resolve() == ScriptOutput({"map": Map({})})
|
assert Script({"dict": empty_map}).resolve() == ScriptOutput({"dict": Map({})})
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"map",
|
"map",
|
||||||
|
|
@ -93,7 +93,7 @@ class TestMap:
|
||||||
)
|
)
|
||||||
def test_map_not_closed(self, map: str):
|
def test_map_not_closed(self, map: str):
|
||||||
with pytest.raises(InvalidSyntaxException, match=re.escape(str(BRACKET_NOT_CLOSED))):
|
with pytest.raises(InvalidSyntaxException, match=re.escape(str(BRACKET_NOT_CLOSED))):
|
||||||
Script({"map": map}).resolve()
|
Script({"dict": map}).resolve()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"value",
|
"value",
|
||||||
|
|
@ -108,7 +108,7 @@ class TestMap:
|
||||||
)
|
)
|
||||||
def test_key_has_no_value(self, value: str):
|
def test_key_has_no_value(self, value: str):
|
||||||
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_WITH_NO_VALUE))):
|
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_WITH_NO_VALUE))):
|
||||||
Script({"map": value}).resolve()
|
Script({"dict": value}).resolve()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"value",
|
"value",
|
||||||
|
|
@ -124,7 +124,7 @@ class TestMap:
|
||||||
InvalidSyntaxException,
|
InvalidSyntaxException,
|
||||||
match=re.escape(str(_UNEXPECTED_COMMA_ARGUMENT(ParsedArgType.MAP_KEY))),
|
match=re.escape(str(_UNEXPECTED_COMMA_ARGUMENT(ParsedArgType.MAP_KEY))),
|
||||||
):
|
):
|
||||||
Script({"map": value}).resolve()
|
Script({"dict": value}).resolve()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"value",
|
"value",
|
||||||
|
|
@ -136,7 +136,7 @@ class TestMap:
|
||||||
)
|
)
|
||||||
def test_map_multiple_keys(self, value: str):
|
def test_map_multiple_keys(self, value: str):
|
||||||
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_MULTIPLE_VALUES))):
|
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_MULTIPLE_VALUES))):
|
||||||
Script({"map": value}).resolve()
|
Script({"dict": value}).resolve()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"value",
|
"value",
|
||||||
|
|
@ -148,7 +148,7 @@ class TestMap:
|
||||||
)
|
)
|
||||||
def test_map_missing_key(self, value: str):
|
def test_map_missing_key(self, value: str):
|
||||||
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_MISSING_KEY))):
|
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_MISSING_KEY))):
|
||||||
Script({"map": value}).resolve()
|
Script({"dict": value}).resolve()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"value",
|
"value",
|
||||||
|
|
@ -161,18 +161,18 @@ class TestMap:
|
||||||
)
|
)
|
||||||
def test_map_key_not_hashable(self, value: str):
|
def test_map_key_not_hashable(self, value: str):
|
||||||
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_NOT_HASHABLE))):
|
with pytest.raises(InvalidSyntaxException, match=re.escape(str(MAP_KEY_NOT_HASHABLE))):
|
||||||
Script({"map": value}).resolve()
|
Script({"dict": value}).resolve()
|
||||||
|
|
||||||
def test_map_key_is_hashable_variable(self):
|
def test_map_key_is_hashable_variable(self):
|
||||||
assert Script(
|
assert Script(
|
||||||
{
|
{
|
||||||
"map": "{{key_variable : 'value' }}",
|
"dict": "{{key_variable : 'value' }}",
|
||||||
"key_variable": "hashable",
|
"key_variable": "hashable",
|
||||||
}
|
}
|
||||||
).resolve() == ScriptOutput(
|
).resolve() == ScriptOutput(
|
||||||
{
|
{
|
||||||
"key_variable": String("hashable"),
|
"key_variable": String("hashable"),
|
||||||
"map": Map({String("hashable"): String("value")}),
|
"dict": Map({String("hashable"): String("value")}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -183,7 +183,7 @@ class TestMap:
|
||||||
):
|
):
|
||||||
Script(
|
Script(
|
||||||
{
|
{
|
||||||
"map": "{{key_variable : 'value' }}",
|
"dict": "{{key_variable : 'value' }}",
|
||||||
"key_variable": "{['non-hashable']}",
|
"key_variable": "{['non-hashable']}",
|
||||||
}
|
}
|
||||||
).resolve()
|
).resolve()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue