mirror of
https://github.com/derrod/legendary.git
synced 2026-04-07 14:58:46 +00:00
feat: support interactive sdl prompts
This commit is contained in:
parent
9ebe1f6e8b
commit
64b99ddb6f
|
|
@ -27,7 +27,7 @@ from legendary.utils.custom_parser import HiddenAliasSubparsersAction
|
|||
from legendary.utils.env import is_windows_mac_or_pyi
|
||||
from legendary.lfs.eos import add_registry_entries, query_registry_entries, remove_registry_entries
|
||||
from legendary.lfs.utils import validate_files, clean_filename
|
||||
from legendary.utils.selective_dl import get_sdl_data
|
||||
from legendary.utils.selective_dl import get_sdl_data, LGDEvaluationContext
|
||||
from legendary.lfs.wine_helpers import read_registry, get_shell_folders, case_insensitive_file_search
|
||||
|
||||
# todo custom formatter for cli logger (clean info, highlighted error/warning)
|
||||
|
|
@ -931,7 +931,9 @@ class LegendaryCLI:
|
|||
sdl_enabled = False
|
||||
|
||||
if sdl_enabled:
|
||||
# FIXME: Consider UpgradePathLogic - it lets automatically select options in new manifests when corresponding option was selected with older version
|
||||
if not self.core.is_installed(game.app_name) or config_tags is None or args.reset_sdl:
|
||||
context = LGDEvaluationContext(self.core)
|
||||
sdl_data = get_sdl_data(self.core.lgd.egl_content_path, game.app_name, game.app_version(args.platform))
|
||||
if sdl_data:
|
||||
if args.skip_sdl:
|
||||
|
|
@ -940,7 +942,7 @@ class LegendaryCLI:
|
|||
if entry.get('IsRequired', 'false').lower() == 'true':
|
||||
args.install_tag.extend(entry.get('Tags', []))
|
||||
else:
|
||||
args.install_tag = sdl_prompt(sdl_data, game.app_title)
|
||||
args.install_tag = sdl_prompt(sdl_data, game.app_title, context)
|
||||
# self.core.lgd.config.set(game.app_name, 'install_tags', ','.join(args.install_tag))
|
||||
else:
|
||||
logger.error(f'Unable to get SDL data for {game.app_name}')
|
||||
|
|
@ -949,7 +951,6 @@ class LegendaryCLI:
|
|||
elif args.install_tag and not game.is_dlc and not args.no_install:
|
||||
config_tags = ','.join(args.install_tag)
|
||||
logger.info(f'Saving install tags for "{game.app_name}" to config: {config_tags}')
|
||||
self.core.lgd.config.set(game.app_name, 'install_tags', config_tags)
|
||||
elif not game.is_dlc:
|
||||
if config_tags and args.reset_sdl:
|
||||
logger.info('Clearing install tags from config.')
|
||||
|
|
@ -957,7 +958,8 @@ class LegendaryCLI:
|
|||
elif config_tags:
|
||||
logger.info(f'Using install tags from config: {config_tags}')
|
||||
args.install_tag = config_tags.split(',')
|
||||
|
||||
|
||||
logger.debug(f'Selected tags: {args.install_tag}')
|
||||
logger.info(f'Preparing download for "{game.app_title}" ({game.app_name})...')
|
||||
# todo use status queue to print progress from CLI
|
||||
# This has become a little ridiculous hasn't it?
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
from InquirerPy import inquirer
|
||||
from InquirerPy.base.control import Choice
|
||||
from InquirerPy.separator import Separator
|
||||
|
||||
from epic_expreval import Tokenizer
|
||||
from legendary.utils.selective_dl import LGDEvaluationContext, EXTRA_FUNCTIONS
|
||||
|
||||
def get_boolean_choice(prompt, default=True):
|
||||
yn = 'Y/n' if default else 'y/N'
|
||||
|
||||
|
|
@ -43,33 +50,63 @@ def get_int_choice(prompt, default=None, min_choice=None, max_choice=None, retur
|
|||
return choice
|
||||
|
||||
|
||||
def sdl_prompt(sdl_data, title):
|
||||
tags = ['']
|
||||
if '__required' in sdl_data:
|
||||
tags.extend(sdl_data['__required']['tags'])
|
||||
def sdl_prompt(sdl_data, title, context):
|
||||
tags = set()
|
||||
|
||||
print(f'You are about to install {title}, this application supports selective downloads.')
|
||||
print('The following optional packs are available (tag - name):')
|
||||
for tag, info in sdl_data.items():
|
||||
if tag == '__required':
|
||||
choices = []
|
||||
required_categories = {}
|
||||
for element in sdl_data['Data']:
|
||||
if (element.get('IsRequired', 'false').lower() == 'true' and not 'Children' in element) or element.get('Invisible', 'false').lower() == 'true':
|
||||
continue
|
||||
print(' *', tag, '-', info['name'])
|
||||
|
||||
examples = ', '.join([g for g in sdl_data.keys() if g != '__required'][:2])
|
||||
print(f'Please enter tags of pack(s) to install (space/comma-separated, e.g. "{examples}")')
|
||||
print('Leave blank to use defaults (only required data will be downloaded).')
|
||||
choices = input('Additional packs [Enter to confirm]: ')
|
||||
if not choices:
|
||||
return tags
|
||||
|
||||
for c in choices.strip('"').replace(',', ' ').split():
|
||||
c = c.strip()
|
||||
if c in sdl_data:
|
||||
tags.extend(sdl_data[c]['tags'])
|
||||
|
||||
if element.get('ConfigHandler'):
|
||||
choices.append(Separator(4 * '-' + ' ' + element['Title'] + ' ' + 4 * '-'))
|
||||
is_required = element.get('IsRequired', 'false').lower() == 'true'
|
||||
if is_required: required_categories[element['UniqueId']] = []
|
||||
for child in element.get('Children', []):
|
||||
enabled = element.get('IsDefaultSelected', 'false').lower() == 'true'
|
||||
choices.append(Choice(child['UniqueId'], name=child['Title'], enabled=enabled))
|
||||
if is_required: required_categories[element['UniqueId']].append(child['UniqueId'])
|
||||
else:
|
||||
print('Invalid tag:', c)
|
||||
enabled = False
|
||||
if element.get('IsDefaultSelected', 'false').lower() == 'true':
|
||||
expression = element.get('DefaultSelectedExpression')
|
||||
if expression:
|
||||
tk = Tokenizer(expression, context)
|
||||
tk.extend_functions(EXTRA_FUNCTIONS)
|
||||
tk.compile()
|
||||
if tk.execute(''):
|
||||
enabled = True
|
||||
else:
|
||||
enabled = True
|
||||
choices.append(Choice(element['UniqueId'], name=element['Title'], enabled=enabled))
|
||||
|
||||
return tags
|
||||
selected_packs = inquirer.checkbox(message='Select optional packs to install',
|
||||
choices=choices,
|
||||
cycle=True,
|
||||
validate=lambda selected: not required_categories or all(any(item in selected for item in category) for category in required_categories.values())).execute()
|
||||
context.selection = set(selected_packs)
|
||||
|
||||
for element in sdl_data['Data']:
|
||||
if element.get('IsRequired', 'false').lower() == 'true':
|
||||
tags.update(element.get('Tags', []))
|
||||
continue
|
||||
if element.get('Invisible', 'false').lower() == 'true':
|
||||
tk = Tokenizer(element['InvisibleSelectedExpression'], context)
|
||||
tk.extend_functions(EXTRA_FUNCTIONS)
|
||||
tk.compile()
|
||||
if tk.execute(''):
|
||||
tags.update(element.get('Tags', []))
|
||||
|
||||
if element['UniqueId'] in selected_packs:
|
||||
tags.update(element.get('Tags', []))
|
||||
if element.get('ConfigHandler'):
|
||||
for child in element.get('Children', []):
|
||||
if child['UniqueId'] in selected_packs:
|
||||
tags.update(child.get('Tags', []))
|
||||
|
||||
return list(tags)
|
||||
|
||||
|
||||
def strtobool(val):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,24 @@ import os
|
|||
import json
|
||||
from epic_expreval import Tokenizer, EvaluationContext
|
||||
|
||||
def has_access(context, app):
|
||||
return bool(context.core.get_game(app))
|
||||
|
||||
def is_selected(context, input):
|
||||
return input in context.selection
|
||||
|
||||
EXTRA_FUNCTIONS = {'HasAccess': has_access, "IsComponentSelected": is_selected}
|
||||
|
||||
class LGDEvaluationContext(EvaluationContext):
|
||||
def __init__(self, core):
|
||||
super().__init__()
|
||||
self.core = core
|
||||
self.selection = set()
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
self.selection = set()
|
||||
|
||||
def run_expression(expression, input):
|
||||
"""Runs expression with default EvauluationContext"""
|
||||
tk = Tokenizer(expression, EvaluationContext())
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
requests<3.0
|
||||
filelock
|
||||
epic-expreval=0.2
|
||||
epic-expreval=0.2
|
||||
InquirerPy
|
||||
Loading…
Reference in a new issue