update
This commit is contained in:
@@ -98,7 +98,7 @@ from SYS.result_table import Table
|
||||
|
||||
from SYS.worker import WorkerManagerRegistry, WorkerStages, WorkerOutputMirror, WorkerStageSession
|
||||
from SYS.pipeline import PipelineExecutor
|
||||
from PluginCore.registry import plugin_inline_query_choices
|
||||
from PluginCore.registry import plugin_inline_query_choices, plugin_query_field_map
|
||||
|
||||
|
||||
|
||||
@@ -1242,10 +1242,13 @@ class CmdletCompleter(Completer):
|
||||
if query_fragment is not None:
|
||||
field_choices: Dict[str, List[str]] = {}
|
||||
ordered_fields: List[str] = []
|
||||
query_only_field_names: set = set()
|
||||
for spec in query_specs:
|
||||
key = str(spec.get("query_key") or spec.get("name") or "").strip().lower()
|
||||
if not key:
|
||||
continue
|
||||
if spec.get("query_only"):
|
||||
query_only_field_names.add(key)
|
||||
if key not in field_choices:
|
||||
ordered_fields.append(key)
|
||||
field_choices[key] = [str(choice) for choice in list(spec.get("choices", []) or [])]
|
||||
@@ -1255,6 +1258,31 @@ class CmdletCompleter(Completer):
|
||||
continue
|
||||
field_choices.setdefault(alias_text, field_choices[key])
|
||||
|
||||
plugin_field_map: Dict[str, List[str]] = {}
|
||||
if selected_plugin:
|
||||
try:
|
||||
plugin_field_map = plugin_query_field_map(selected_plugin, config)
|
||||
except Exception:
|
||||
plugin_field_map = {}
|
||||
if plugin_field_map:
|
||||
ordered_fields = [f for f in ordered_fields if f not in query_only_field_names]
|
||||
removed_keys = set()
|
||||
for key in query_only_field_names:
|
||||
field_choices.pop(key, None)
|
||||
removed_keys.add(key)
|
||||
aliases_to_remove = set()
|
||||
for alias_key, alias_values in list(field_choices.items()):
|
||||
if alias_key in removed_keys:
|
||||
continue
|
||||
if alias_key not in ordered_fields and alias_key not in plugin_field_map:
|
||||
aliases_to_remove.add(alias_key)
|
||||
for akey in aliases_to_remove:
|
||||
field_choices.pop(akey, None)
|
||||
for field_name, choices in plugin_field_map.items():
|
||||
if field_name not in field_choices:
|
||||
ordered_fields.append(field_name)
|
||||
field_choices[field_name] = choices
|
||||
|
||||
raw_fragment = str(query_fragment or "")
|
||||
segment = raw_fragment[1:] if raw_fragment[:1] in {"'", '"'} else raw_fragment
|
||||
if "," in segment:
|
||||
@@ -1267,8 +1295,8 @@ class CmdletCompleter(Completer):
|
||||
partial_lower = partial.strip().lower()
|
||||
|
||||
inline_choices = []
|
||||
if effective_cmd == "search-file" and provider_name:
|
||||
inline_choices = self._inline_query_choices(provider_name, field, config)
|
||||
if selected_plugin and effective_cmd in {"search-file", "download-file"}:
|
||||
inline_choices = self._inline_query_choices(selected_plugin, field, config)
|
||||
|
||||
choice_pool = inline_choices or field_choices.get(field, [])
|
||||
if choice_pool:
|
||||
|
||||
@@ -863,6 +863,36 @@ def plugin_inline_query_choices(
|
||||
return []
|
||||
|
||||
|
||||
def plugin_query_field_map(
|
||||
plugin_name: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, List[str]]:
|
||||
pname = str(plugin_name or "").strip().lower()
|
||||
if not pname:
|
||||
return {}
|
||||
try:
|
||||
info = REGISTRY.get(pname)
|
||||
if info is not None:
|
||||
mapping = collect_choice(info.plugin_class)
|
||||
else:
|
||||
plugin = get_plugin(pname, config)
|
||||
if plugin is None:
|
||||
return {}
|
||||
mapping = collect_choice(plugin)
|
||||
result: Dict[str, List[str]] = {}
|
||||
for field, choices in mapping.items():
|
||||
texts = []
|
||||
for c in choices:
|
||||
text = str(c.get("text") or c.get("value") or "")
|
||||
if text:
|
||||
texts.append(text)
|
||||
if texts:
|
||||
result[field] = texts
|
||||
return result
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def get_plugin_for_url(url: str,
|
||||
config: Optional[Dict[str, Any]] = None) -> Optional[Provider]:
|
||||
name = match_plugin_name_for_url(url)
|
||||
|
||||
+2
-2
@@ -2076,8 +2076,8 @@ class PipelineExecutor:
|
||||
except Exception:
|
||||
row_action = None
|
||||
if row_action:
|
||||
prefer_row_action = True
|
||||
preferred_row_action = list(row_action)
|
||||
stages.insert(0, list(row_action))
|
||||
return True, None
|
||||
# Command expansion via @N:
|
||||
# - Default behavior: expand ONLY for single-row selections.
|
||||
# - Special case: allow multi-row expansion for add-file directory tables by
|
||||
|
||||
+140
-1
@@ -32,6 +32,7 @@ _PREFERENCES_BROWSE_PATH = "__preferences__"
|
||||
_PLUGINS_BROWSE_PATH = "__plugins__"
|
||||
_PLUGIN_CATEGORY_KEYS = ("plugin",)
|
||||
_CREATE_INSTANCE_FLAG = "-create-instance"
|
||||
_CHOOSE_FLAG = "-choose"
|
||||
_KNOWN_SECTION_LABELS = {
|
||||
"plugin": "Plugins",
|
||||
}
|
||||
@@ -65,6 +66,7 @@ _CONFIG_ITEM_FIELDS = (
|
||||
"type",
|
||||
"display_path",
|
||||
"instance_target",
|
||||
"choices",
|
||||
)
|
||||
|
||||
CMDLET = Cmdlet(
|
||||
@@ -576,6 +578,7 @@ def _build_value_item(
|
||||
key_path: str,
|
||||
name: str,
|
||||
value: Any,
|
||||
choices: Any = None,
|
||||
) -> Dict[str, Any]:
|
||||
display_value = "***" if _is_sensitive_config_key(key_path) else _format_config_value(value)
|
||||
path_parts = [part for part in str(key_path or "").split(".") if part]
|
||||
@@ -583,6 +586,7 @@ def _build_value_item(
|
||||
[_format_config_path_label(".".join(path_parts[:-1]))] if len(path_parts) > 1 else []
|
||||
+ [_format_config_label(path_parts[-1])] if path_parts else [_format_config_label(name)]
|
||||
)
|
||||
choice_list = list(choices) if isinstance(choices, (list, tuple)) else []
|
||||
return {
|
||||
"kind": "value",
|
||||
"key": key_path,
|
||||
@@ -592,6 +596,7 @@ def _build_value_item(
|
||||
"value_display": display_value,
|
||||
"display_path": display_path,
|
||||
"type": type(value).__name__,
|
||||
"choices": choice_list,
|
||||
}
|
||||
|
||||
|
||||
@@ -700,6 +705,25 @@ def _build_root_config_items(config_data: Dict[str, Any]) -> List[Dict[str, Any]
|
||||
return items
|
||||
|
||||
|
||||
def _lookup_plugin_choices(browse_path: str, key_name: str) -> Optional[List[str]]:
|
||||
parts = _split_config_path(browse_path)
|
||||
if len(parts) < 2 or parts[0] != "plugin":
|
||||
return None
|
||||
plugin_name = parts[1]
|
||||
try:
|
||||
from SYS.plugin_config import get_plugin_schema
|
||||
schema = get_plugin_schema(plugin_name)
|
||||
except Exception:
|
||||
return None
|
||||
for field in schema:
|
||||
if field.get("key") == key_name:
|
||||
choices = field.get("choices")
|
||||
if isinstance(choices, list):
|
||||
return [str(c) for c in choices]
|
||||
break
|
||||
return None
|
||||
|
||||
|
||||
def _build_nested_config_items(
|
||||
config_data: Dict[str, Any],
|
||||
browse_path: str,
|
||||
@@ -737,6 +761,7 @@ def _build_nested_config_items(
|
||||
key_path=full_key,
|
||||
name=key,
|
||||
value=value,
|
||||
choices=_lookup_plugin_choices(browse_path, key),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -799,6 +824,21 @@ def _extract_browse_arg(args: Sequence[str]) -> Optional[str]:
|
||||
return _extract_arg_value(args, flags={"-browse", "--browse"}, allow_positional=False)
|
||||
|
||||
|
||||
def _extract_choose_arg(args: Sequence[str]) -> Optional[str]:
|
||||
return _extract_arg_value(args, flags={_CHOOSE_FLAG}, allow_positional=False)
|
||||
|
||||
|
||||
def _resolve_config_value_raw(config: Dict[str, Any], key_path: str) -> Any:
|
||||
parts = [p for p in str(key_path or "").split(".") if p]
|
||||
current: Any = config
|
||||
for part in parts:
|
||||
if isinstance(current, dict):
|
||||
current = current.get(part)
|
||||
else:
|
||||
return None
|
||||
return current
|
||||
|
||||
|
||||
def _extract_selected_update_value(args: Sequence[str]) -> Optional[str]:
|
||||
explicit = _extract_arg_value(args, flags=VALUE_ARG_FLAGS, allow_positional=False)
|
||||
if explicit is not None:
|
||||
@@ -871,6 +911,44 @@ def _normalize_config_item(candidate: Any) -> Optional[Dict[str, Any]]:
|
||||
return normalized or None
|
||||
|
||||
|
||||
def _show_choice_table(
|
||||
target_key: str,
|
||||
choices: List[str],
|
||||
current_value: str,
|
||||
display_name: str,
|
||||
) -> int:
|
||||
items: List[Dict[str, Any]] = []
|
||||
for choice in choices:
|
||||
choice_str = str(choice)
|
||||
marker = " ← current" if choice_str == current_value else ""
|
||||
items.append({
|
||||
"kind": "choice",
|
||||
"key": target_key,
|
||||
"title": choice_str,
|
||||
"value": choice_str,
|
||||
"display": f"{choice_str}{marker}",
|
||||
})
|
||||
|
||||
table = Table(f"Configuration: {display_name}", preserve_order=True)
|
||||
table.set_table("config")
|
||||
table.set_source_command(".config", [target_key])
|
||||
table.set_header_lines([
|
||||
f"Available values for {display_name}. Current: {current_value}",
|
||||
"Use @N to select a value. Use @.. to go back.",
|
||||
])
|
||||
|
||||
for idx, item in enumerate(items):
|
||||
row = table.add_row()
|
||||
row.add_column("Choice", item.get("display", item.get("title", "")))
|
||||
table.set_row_selection_action(idx, [".config", str(item.get("key")), str(item.get("value"))])
|
||||
|
||||
ctx.set_last_result_table(table, items)
|
||||
ctx.set_current_stage_table(table)
|
||||
print()
|
||||
print(table)
|
||||
return 0
|
||||
|
||||
|
||||
def _show_config_table(
|
||||
config_data: Dict[str, Any],
|
||||
*,
|
||||
@@ -893,13 +971,23 @@ def _show_config_table(
|
||||
for idx, item in enumerate(items):
|
||||
row = table.add_row()
|
||||
row.add_column("Name", item.get("title", ""))
|
||||
row.add_column("Value", item.get("summary") or item.get("value_display", ""))
|
||||
value_text = item.get("summary") or item.get("value_display", "")
|
||||
choices = item.get("choices")
|
||||
if choices and isinstance(choices, list) and len(choices) > 0:
|
||||
choices_repr = ", ".join(str(c) for c in choices)
|
||||
value_text = f"{value_text} [{choices_repr}]"
|
||||
row.add_column("Value", value_text)
|
||||
row.add_column("Type", item.get("type", ""))
|
||||
if item.get("kind") == "section" and item.get("browse_path"):
|
||||
table.set_row_selection_action(
|
||||
idx,
|
||||
[".config", "-browse", str(item.get("browse_path"))],
|
||||
)
|
||||
elif item.get("kind") == "value" and choices and isinstance(choices, list) and len(choices) > 0 and item.get("key"):
|
||||
table.set_row_selection_action(
|
||||
idx,
|
||||
[".config", "-choose", str(item.get("key"))],
|
||||
)
|
||||
elif item.get("kind") == "create_instance" and item.get("instance_target"):
|
||||
table.set_row_selection_action(
|
||||
idx,
|
||||
@@ -966,6 +1054,22 @@ def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
|
||||
if browse_path:
|
||||
return _show_config_table(current_config, browse_path=browse_path)
|
||||
|
||||
choose_key = _extract_choose_arg(args)
|
||||
if choose_key:
|
||||
choices = _lookup_plugin_choices(choose_key, choose_key.split(".")[-1]) or []
|
||||
if not choices:
|
||||
nested_branch = _resolve_config_branch(current_config, choose_key)
|
||||
if isinstance(nested_branch, dict):
|
||||
return _show_config_table(current_config, browse_path=choose_key)
|
||||
print(f"No choices available for '{_format_config_path_label(choose_key)}'")
|
||||
return 0
|
||||
try:
|
||||
current_value = str(_resolve_config_value_raw(current_config, choose_key) or "").strip()
|
||||
except Exception:
|
||||
current_value = ""
|
||||
display_name = _format_config_path_label(choose_key)
|
||||
return _show_choice_table(choose_key, choices, current_value, display_name)
|
||||
|
||||
selection_item = _get_selected_config_item() or _normalize_config_item(piped_result)
|
||||
|
||||
create_instance_target = _extract_create_instance_target(args)
|
||||
@@ -1025,6 +1129,31 @@ def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
|
||||
log(f"Error updating config '{target_key}': {exc}")
|
||||
print(f"Error updating config: {exc}")
|
||||
return 1
|
||||
choices = (selection_item or {}).get("choices")
|
||||
if choices and isinstance(choices, list) and len(choices) > 0:
|
||||
current_value = str((selection_item or {}).get("value") or "").strip()
|
||||
display_name = selection_display_path or selection_key
|
||||
return _show_choice_table(selection_key, choices, current_value, display_name)
|
||||
|
||||
if selection_kind == "choice" and selection_key:
|
||||
choice_value = str((selection_item or {}).get("value") or "").strip()
|
||||
if choice_value:
|
||||
try:
|
||||
target_key = _resolve_update_key(current_config, selection_key)
|
||||
set_nested_config(current_config, target_key, choice_value)
|
||||
_save_updated_config(current_config, target_key)
|
||||
print(f"Updated '{_format_config_path_label(selection_key)}' to '{choice_value}'")
|
||||
current_config = load_config()
|
||||
key_parts = [p for p in str(selection_key).split(".") if p]
|
||||
if len(key_parts) >= 2:
|
||||
parent_path = ".".join(key_parts[:-1])
|
||||
if _resolve_config_branch(current_config, parent_path):
|
||||
return _show_config_table(current_config, browse_path=parent_path)
|
||||
return 0
|
||||
except Exception as exc:
|
||||
log(f"Error updating config '{selection_key}': {exc}")
|
||||
print(f"Error updating config: {exc}")
|
||||
return 1
|
||||
|
||||
if not args:
|
||||
if sys.stdin.isatty() and not piped_result:
|
||||
@@ -1047,6 +1176,16 @@ def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
|
||||
set_nested_config(current_config, key, value)
|
||||
_save_updated_config(current_config, key)
|
||||
print(f"Updated '{key}' to '{value}'")
|
||||
current_config = load_config()
|
||||
parent_path = _resolve_direct_browse_path(current_config, key)
|
||||
if not parent_path:
|
||||
key_parts = [p for p in str(key).split(".") if p]
|
||||
if len(key_parts) >= 2:
|
||||
candidate = ".".join(key_parts[:-1])
|
||||
if _resolve_config_branch(current_config, candidate):
|
||||
parent_path = candidate
|
||||
if parent_path:
|
||||
return _show_config_table(current_config, browse_path=parent_path)
|
||||
return 0
|
||||
except Exception as exc:
|
||||
log(f"Error updating config '{key}': {exc}")
|
||||
|
||||
@@ -251,6 +251,12 @@ class Local(Provider):
|
||||
folder_name = str(kwargs.get("folder_name") or "").strip()
|
||||
if not folder_name and not direct_export_download:
|
||||
folder_name = self._folder_name_from_pipe(kwargs.get("pipe_obj"))
|
||||
if not folder_name and not direct_export_download:
|
||||
title_hint = str(kwargs.get("title") or "").strip()
|
||||
if not title_hint:
|
||||
title_hint = source_path.stem.replace("_", " ").strip()
|
||||
if title_hint:
|
||||
folder_name = title_hint
|
||||
|
||||
export_root = destination_root
|
||||
if folder_name and not direct_export_download:
|
||||
|
||||
@@ -612,6 +612,13 @@ class OpenLibrary(Provider):
|
||||
TABLE_AUTO_STAGES = {
|
||||
"openlibrary.edition": ["download-file"],
|
||||
}
|
||||
QUERY_ARG_CHOICES = {
|
||||
"quality": ["high", "medium", "low"],
|
||||
"language": [
|
||||
"english", "spanish", "french", "german", "italian",
|
||||
"portuguese", "polish", "russian", "chinese", "japanese",
|
||||
],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def config_schema(cls) -> List[Dict[str, Any]]:
|
||||
@@ -1175,7 +1182,7 @@ class OpenLibrary(Provider):
|
||||
|
||||
Returns a dict with the downloaded path and SearchResult when successful.
|
||||
"""
|
||||
self,
|
||||
sr = self.search_result_from_url(url)
|
||||
if sr is None:
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user