Browse Source

Merge pull request #202 from datalad/suites

Extensible suites
Michael Hanke 1 year ago
parent
commit
e5c8ba1442

+ 15 - 6
datalad_gooey/__init__.py

@@ -22,16 +22,25 @@ command_suite = (
 )
 
 from datalad.support.extensions import register_config
-from datalad.support.constraints import EnsureChoice
+from datalad.support.constraints import (
+    EnsureChoice,
+    EnsureStr,
+)
 register_config(
-    'datalad.gooey.ui-mode',
-    'Which user interface mode to use in the application',
+    'datalad.gooey.active-suite',
+    'Which user interface suite to use in the application',
     description=\
-    "In 'simplified' mode advanced operations operations are hidden "
+    "A suite is a particular set of commands that is available through "
+    "the application. The command interface can be customized, such that "
+    "different features and different levels of complexity can be exposed "
+    "for the same command in different suites. "
+    "Two standard suites are provided, but extension package may provide "
+    "additional suites that can be configured. "
+    "In the 'simplified' suite advanced operations operations are hidden "
     "in the user interface. In 'complete' mode, all functionality "
     'is exposed.',
-    type=EnsureChoice('simplified', 'complete'),
-    default='simplified',
+    type=EnsureStr() | EnsureChoice('gooey-simplified', 'gooey-complete'),
+    default='gooey-simplified',
     scope='global')
 register_config(
     'datalad.gooey.ui-theme',

+ 0 - 63
datalad_gooey/active_api.py

@@ -1,63 +0,0 @@
-from datalad import cfg
-
-#
-# API specifications
-#
-# superset of all API scopes, full set of all supported commands
-# the order in this dict (and generally also those below) defines
-# the order in which commands appear in the respective places
-# the API is listed
-api = None
-# commands that operate on datasets
-dataset_api = None
-# commands that operate on any directory
-directory_api = None
-# commands that operate on directories in datasets
-directory_in_ds_api = None
-# commands that operate on any file
-file_api = None
-# commands that operate on any file in a dataset
-file_in_ds_api = None
-# command that operate on annex'ed files
-annexed_file_api = None
-
-# names of parameters to exclude for any command
-exclude_parameters = set()
-
-# mapping of parameter names to display names
-# to be applied across all commands
-parameter_display_names = {}
-
-# mapping of group name/title to sort index
-api_group_order = {}
-
-
-ui_mode = cfg.obtain('datalad.gooey.ui-mode')
-if ui_mode == 'simplified':
-    from .simplified_api import (
-        api,
-        dataset_api,
-        directory_api,
-        directory_in_ds_api,
-        file_api,
-        file_in_ds_api,
-        annexed_file_api,
-        exclude_parameters,
-        parameter_display_names,
-        api_group_order,
-    )
-elif ui_mode == 'complete':
-    from .complete_api import (
-        api,
-        dataset_api,
-        directory_api,
-        directory_in_ds_api,
-        file_api,
-        file_in_ds_api,
-        annexed_file_api,
-        exclude_parameters,
-        parameter_display_names,
-        api_group_order,
-    )
-else:
-    raise NotImplementedError

+ 58 - 0
datalad_gooey/active_suite.py

@@ -0,0 +1,58 @@
+from types import MappingProxyType
+from datalad import cfg
+
+spec = None
+# names of parameters to exclude for any command
+# exclude_parameters = set()
+
+# mapping of parameter names to display names
+# to be applied across all commands
+# parameter_display_names = {}
+
+# mapping of group name/title to sort index
+# api_group_order = {}
+
+#
+# API specifications
+#
+# commands that operate on datasets
+dataset_api = None
+# commands that operate on any directory
+directory_api = None
+# commands that operate on directories in datasets
+directory_in_ds_api = None
+# commands that operate on any file
+file_api = None
+# commands that operate on any file in a dataset
+file_in_ds_api = None
+# command that operate on annex'ed files
+annexed_file_api = None
+# commands that have no specific target type, or another than
+# dataset, dir, file etc from above
+other_api = None
+
+
+active_suite = cfg.obtain('datalad.gooey.active-suite')
+
+from datalad.support.entrypoints import iter_entrypoints
+for sname, _, sload in iter_entrypoints(
+        'datalad.gooey.suites', load=False):
+    if sname != active_suite:
+        continue
+
+    # deposit the spec in read-only form
+    spec = MappingProxyType(sload())
+
+    # deploy convenience importable symbols
+    for apiname, api in spec.get('apis', {}).items():
+        globals()[f"{apiname}_api"] = api
+
+if spec is None:
+    raise RuntimeError(
+        f'No active Gooey suite {active_suite!r}! Imploding...')
+
+    api = dict()
+    print(active_suite)
+    for a in active_suite.get('apis', {}).values():
+        if a:
+            api.update(a)

+ 22 - 2
datalad_gooey/app.py

@@ -58,6 +58,7 @@ class GooeyApp(QObject):
         'menuDataset': QMenu,
         'menuHelp': QMenu,
         'menuView': QMenu,
+        'menuSuite': QMenu,
         'menuUtilities': QMenu,
         'statusbar': QStatusBar,
         'actionCheck_for_new_version': QAction,
@@ -165,6 +166,8 @@ class GooeyApp(QObject):
         #self._fsbrowser._tree.currentItemChanged.connect(
         #    lambda cur, prev: self._cmdui.reset_form())
 
+        # TODO could be done lazily to save in entrypoint iteration
+        self._setup_suites()
         self._connect_menu_view(self.get_widget('menuView'))
 
     def _setup_ongoing_cmdexec(self, thread_id, cmdname, cmdargs, exec_params):
@@ -253,7 +256,7 @@ class GooeyApp(QObject):
 
     def _populate_dataset_menu(self):
         """Private slot to populate connected QMenus with dataset actions"""
-        from .active_api import dataset_api
+        from .active_suite import dataset_api
         add_cmd_actions_to_menu(
             self, self._cmdui.configure, dataset_api, self.sender())
         # immediately sever the connection to avoid repopulating the menu
@@ -310,7 +313,7 @@ class GooeyApp(QObject):
 
     def _connect_menu_view(self, menu: QMenu):
         for cfgvar, menuname, subject in (
-                ('datalad.gooey.ui-mode', 'menuInterface', 'interface mode'),
+                ('datalad.gooey.active-suite', 'menuSuite', 'suite'),
                 ('datalad.gooey.ui-theme', 'menuTheme', 'theme'),
         ):
             mode = dlcfg.obtain(cfgvar)
@@ -327,6 +330,7 @@ class GooeyApp(QObject):
         action = self.sender()
         cfgvar, subject = action.data()
         mode = action.objectName().split('_')[-1]
+        assert mode
         dlcfg.set(cfgvar, mode, scope='global')
         QMessageBox.information(
             self.main_window, 'Note',
@@ -352,6 +356,22 @@ class GooeyApp(QObject):
                 return
             qtapp.setStyleSheet(qdarktheme.load_stylesheet(uitheme))
 
+    def _setup_suites(self):
+        # put known suites in menu
+        suite_menu = self.get_widget('menuSuite')
+        from datalad.support.entrypoints import iter_entrypoints
+        for sname, _, suite in iter_entrypoints(
+                'datalad.gooey.suites', load=True):
+            title = suite.get('title')
+            if not title:
+                title = sname.capitalize()
+            description = suite.get('description')
+            action = QAction(title, parent=suite_menu)
+            action.setObjectName(f"actionSetGooeySuite_{sname}")
+            if description:
+                action.setToolTip(description)
+            suite_menu.addAction(action)
+
 
 def main():
     qtapp = QApplication(sys.argv)

+ 4 - 5
datalad_gooey/cmd_actions.py

@@ -1,9 +1,7 @@
 from PySide6.QtGui import QAction
 from PySide6.QtWidgets import QMenu
 
-from .active_api import (
-    api_group_order,
-)
+from .active_suite import spec as active_suite
 
 
 def add_cmd_actions_to_menu(parent, receiver, api, menu=None, cmdkwargs=None):
@@ -38,7 +36,7 @@ def add_cmd_actions_to_menu(parent, receiver, api, menu=None, cmdkwargs=None):
         # the name of the command is injected into the action
         # as user data. We wrap it in a dict to enable future
         # additional payload
-        adata = dict(__cmd_name__=cmdname)
+        adata = dict(__cmd_name__=cmdname, __api__=api)
         # put on record, if we are generating actions for a specific
         # dataset
         if cmdkwargs is not None:
@@ -61,7 +59,8 @@ def add_cmd_actions_to_menu(parent, receiver, api, menu=None, cmdkwargs=None):
     for group, submenu in sorted(
             submenus.items(),
             # sort items with no sorting indicator last
-            key=lambda x: api_group_order.get(x[0], ('zzzz'))):
+            key=lambda x: active_suite.get('api_group_order', {}).get(
+                x[0], ('zzzz'))):
         # skip menus without actions
         if not submenu.actions():
             continue

+ 28 - 28
datalad_gooey/complete_api.py

@@ -84,31 +84,31 @@ dataset_api = {
     if name in api
 }
 
-# commands that operate on any directory
-directory_api = api
-# commands that operate on directories in datasets
-directory_in_ds_api = api
-# commands that operate on any file
-file_api = api
-# commands that operate on any file in a dataset
-file_in_ds_api = api
-# command that operate on annex'ed files
-annexed_file_api = api
-
-# these generic parameters never make sense
-exclude_parameters = set((
-    # cmd execution wants a generator
-    'return_type',
-    # could be useful internally, but a user cannot chain commands
-    'result_filter',
-    # we cannot deal with non-dict results, and override any transform
-    'result_xfm',
-))
-
-# generic name overrides
-parameter_display_names = {}
-
-# mapping of group name/title to sort index
-api_group_order = {
-    spec[1]: spec[0] for spec in get_interface_groups()
-}
+gooey_suite = dict(
+    title='Complete',
+    description='Generic access to all command available in this DataLad installation',
+    apis=dict(
+        dataset=dataset_api,
+        directory=api,
+        directory_in_ds=api,
+        file=api,
+        file_in_ds=api,
+        annexed_file=api,
+        other=api,
+    ),
+    # mapping of group name/title to sort index
+    api_group_order={
+        spec[1]: spec[0] for spec in get_interface_groups()
+    },
+    # these generic parameters never make sense
+    exclude_parameters=set((
+        # cmd execution wants a generator
+        'return_type',
+        # could be useful internally, but a user cannot chain commands
+        'result_filter',
+        # we cannot deal with non-dict results, and override any transform
+        'result_xfm',
+    )),
+    # generic name overrides
+    parameter_display_names={},
+)

+ 3 - 0
datalad_gooey/constraints.py

@@ -31,3 +31,6 @@ class EnsureExistingDirectory(Constraint):
             raise ValueError(
                 f"{value} is not an existing directory")
         return value
+
+    def short_description(self):
+        return 'existing directory'

+ 9 - 4
datalad_gooey/dataladcmd_ui.py

@@ -18,7 +18,7 @@ from PySide6.QtWidgets import (
 
 from .param_form_utils import populate_form_w_params
 from .api_utils import get_cmd_displayname
-from .active_api import api
+from .active_suite import spec as active_suite
 
 
 class GooeyDataladCmdUI(QObject):
@@ -63,6 +63,7 @@ class GooeyDataladCmdUI(QObject):
     @Slot(str, dict)
     def configure(
             self,
+            api=None,
             cmdname: str = None,
             cmdkwargs: Dict or None = None):
         if cmdkwargs is None:
@@ -73,14 +74,17 @@ class GooeyDataladCmdUI(QObject):
         # we can use this to update the method parameter values
         # with information from menu-items, or tree nodes clicked
         sender = self.sender()
-        if sender is not None:
-            if cmdname is None and isinstance(sender, QAction):
+        if sender is not None and isinstance(sender, QAction):
+            if api is None:
+                api = sender.data().get('__api__')
+            if cmdname is None:
                 cmdname = sender.data().get('__cmd_name__')
                 # pull in any signal-provided kwargs for the command
                 # unless they have been also specified directly to the method
                 cmdkwargs = {
                     k: v for k, v in sender.data().items()
-                    if k != '__cmd_name__' and k not in cmdkwargs
+                    if k not in ('__cmd_name__', '__api')
+                    and k not in cmdkwargs
                 }
 
         assert cmdname is not None, \
@@ -90,6 +94,7 @@ class GooeyDataladCmdUI(QObject):
 
         self.reset_form()
         populate_form_w_params(
+            api,
             self._app.rootpath,
             self.pform,
             cmdname,

+ 6 - 6
datalad_gooey/fsbrowser.py

@@ -402,7 +402,7 @@ class GooeyFilesystemBrowser(QObject):
         cmdkwargs = dict()
         context = QMenu(parent=self._tree)
         if path_type == 'dataset':
-            from .active_api import dataset_api as cmdapi
+            from .active_suite import dataset_api as cmdapi
             submenu = context.addMenu('Dataset commands')
             cmdkwargs['dataset'] = ipath
         elif path_type == 'directory':
@@ -410,23 +410,23 @@ class GooeyFilesystemBrowser(QObject):
             # path the directory path to the command's `path` argument
             cmdkwargs['path'] = ipath
             if dsroot:
-                from .active_api import directory_in_ds_api as cmdapi
+                from .active_suite import directory_in_ds_api as cmdapi
                 # also pass dsroot
                 cmdkwargs['dataset'] = dsroot
             else:
-                from .active_api import directory_api as cmdapi
+                from .active_suite import directory_api as cmdapi
             submenu = context.addMenu('Directory commands')
         elif path_type in ('file', 'symlink', 'annexed-file'):
             dsroot = get_dataset_root(ipath)
             cmdkwargs['path'] = ipath
             if dsroot:
                 if path_type == 'annexed-file':
-                    from .active_api import annexed_file_api as cmdapi
+                    from .active_suite import annexed_file_api as cmdapi
                 else:
-                    from .active_api import file_in_ds_api as cmdapi
+                    from .active_suite import file_in_ds_api as cmdapi
                 cmdkwargs['dataset'] = dsroot
             else:
-                from .active_api import file_api as cmdapi
+                from .active_suite import file_api as cmdapi
             submenu = context.addMenu('File commands')
         # TODO context menu for annex'ed files
 

+ 73 - 43
datalad_gooey/param_form_utils.py

@@ -18,7 +18,6 @@ from PySide6.QtWidgets import (
 
 from datalad.interface.common_opts import eval_params
 from datalad.support.constraints import EnsureChoice
-from datalad.support.param import Parameter
 from datalad.utils import (
     get_wrapped_class,
 )
@@ -26,14 +25,11 @@ from datalad.utils import (
 
 from . import param_widgets as pw
 from .param_multival_widget import MultiValueInputWidget
-from .active_api import (
-    api,
-    exclude_parameters,
-    parameter_display_names,
-)
+from .active_suite import spec as active_suite
 from .api_utils import get_cmd_params
 from .utils import _NoValue
 from .constraints import (
+    Constraint,
     EnsureExistingDirectory,
     EnsureDatasetSiblingName,
 )
@@ -42,6 +38,7 @@ __all__ = ['populate_form_w_params']
 
 
 def populate_form_w_params(
+        api,
         basedir: Path,
         formlayout: QFormLayout,
         cmdname: str,
@@ -62,6 +59,27 @@ def populate_form_w_params(
     # collect widgets for a later connection setup
     form_widgets = dict()
 
+    def _get_nargs(pname, argparse_spec):
+        # TODO we must consider the following action specs for widget selection
+        # - 'store_const'
+        # - 'store_true' and 'store_false'
+        # - 'append'
+        # - 'append_const'
+        # - 'count'
+        # - 'extend'
+        if pname in cmd_api_spec.get('parameter_nargs', []):
+            # take as gospel
+            return cmd_api_spec['parameter_nargs'][pname]
+        elif argparse_spec.get('action') == 'append':
+            return '*'
+        else:
+            nargs = argparse_spec.get('nargs', None)
+            try:
+                nargs = int(nargs)
+            except (ValueError, TypeError):
+                pass
+            return nargs
+
     # loop over all parameters of the command (with their defaults)
     def _specific_params():
         for pname, pdefault in get_cmd_params(cmd):
@@ -83,7 +101,7 @@ def populate_form_w_params(
                 cmd_api_spec.get(
                     'parameter_order', {}).get(x[0], 99),
                 x[0])):
-        if pname in exclude_parameters:
+        if pname in active_suite.get('exclude_parameters', []):
             continue
         if pname in cmd_api_spec.get('exclude_parameters', []):
             continue
@@ -93,13 +111,21 @@ def populate_form_w_params(
             param_spec.constraints = \
                 cmd_api_spec['parameter_constraints'][pname]
         # populate the layout with widgets for each of them
+        # we do not pass Parameter instances further down, but disassemble
+        # and homogenize here
         pwidget = _get_parameter_widget(
-            basedir,
-            formlayout.parentWidget(),
-            param_spec,
-            pname,
+            basedir=basedir,
+            parent=formlayout.parentWidget(),
+            name=pname,
+            constraints=cmd_api_spec['parameter_constraints'][pname]
+            if pname in cmd_api_spec.get('parameter_constraints', [])
+            else param_spec.constraints,
+            nargs=_get_nargs(pname, param_spec.cmd_kwargs),
             # will also be _NoValue, if there was none
-            pdefault,
+            default=pdefault,
+            docs=param_spec._doc,
+            # TODO make obsolete
+            argparse_spec=param_spec.cmd_kwargs,
         )
         form_widgets[pname] = pwidget
         # query for a known display name
@@ -107,7 +133,7 @@ def populate_form_w_params(
         display_name = cmd_param_display_names.get(
             pname,
             # fallback to API specific override
-            parameter_display_names.get(
+            active_suite.get('parameter_display_names', {}).get(
                 pname,
                 # last resort:
                 # use capitalized orginal with _ removed as default
@@ -141,9 +167,12 @@ def populate_form_w_params(
 def _get_parameter_widget(
         basedir: Path,
         parent: QWidget,
-        param: Parameter,
         name: str,
-        default: Any = pw._NoValue) -> QWidget:
+        constraints: Constraint,
+        nargs: int or str,
+        default: Any = pw._NoValue,
+        docs: str = '',
+        argparse_spec: Dict = None) -> QWidget:
     """Populate a given layout with a data entry widget for a command parameter
 
     `value` is an explicit setting requested by the caller. A value of
@@ -156,16 +185,18 @@ def _get_parameter_widget(
     pwid_factory = _get_parameter_widget_factory(
         name,
         default,
-        param.constraints,
-        param.cmd_kwargs,
-        basedir)
+        constraints,
+        nargs,
+        basedir,
+        # TODO make obsolete
+        argparse_spec)
     return pw.load_parameter_widget(
         parent,
         pwid_factory,
         name=name,
-        docs=param._doc,
+        docs=docs,
         default=default,
-        validator=param.constraints,
+        validator=constraints,
     )
 
 
@@ -173,24 +204,14 @@ def _get_parameter_widget_factory(
         name: str,
         default: Any,
         constraints: Callable or None,
-        argparse_spec: Dict,
-        basedir: Path) -> Callable:
+        nargs: int or str,
+        basedir: Path,
+        # TODO make obsolete
+        argparse_spec: Dict) -> Callable:
     """Translate DataLad command parameter specs into Gooey input widgets"""
-    # for now just one to play with
-    # TODO each factory must provide a standard widget method
-    # to return the final value, ready to pass onto the respective
-    # parameter of the command call
+    if argparse_spec is None:
+        argparse_spec = {}
     argparse_action = argparse_spec.get('action')
-    # we must consider the following action specs for widget selection
-    # - 'store_const'
-    # - 'store_true' and 'store_false'
-    # - 'append'
-    # - 'append_const'
-    # - 'count'
-    # - 'extend'
-    #if name == 'path':
-    #    return get_pathselection_widget
-
     # if we have no idea, use a simple line edit
     type_widget = pw.StrParamWidget
     # now some parameters where we can derive semantics from their name
@@ -198,6 +219,8 @@ def _get_parameter_widget_factory(
         type_widget = functools.partial(
             pw.PathParamWidget,
             pathtype=QFileDialog.Directory,
+            disable_manual_edit=active_suite.get('options', {}).get(
+                'disable_manual_path_input', False),
             basedir=basedir)
     elif name == 'path':
         type_widget = functools.partial(
@@ -209,11 +232,15 @@ def _get_parameter_widget_factory(
     # now parameters where we make decisions based on their configuration
     elif isinstance(constraints, EnsureDatasetSiblingName):
         type_widget = pw.SiblingChoiceParamWidget
+    # TODO ideally the suite API would normalize this to a EnsureBool
+    # constraint
     elif argparse_action in ('store_true', 'store_false'):
         type_widget = pw.BoolParamWidget
     elif isinstance(constraints, EnsureChoice) and argparse_action is None:
         type_widget = functools.partial(
             pw.ChoiceParamWidget, choices=constraints._allowed)
+    # TODO ideally the suite API would normalize this to a EnsureChoice
+    # constraint
     elif argparse_spec.get('choices'):
         type_widget = functools.partial(
             pw.ChoiceParamWidget, choices=argparse_spec.get('choices'))
@@ -222,12 +249,15 @@ def _get_parameter_widget_factory(
     # (int, '*', '+'), plus action=append
     # in all these cases, we need to expect multiple instances of the data type
     # for which we have selected the input widget above
-    argparse_nargs = argparse_spec.get('nargs')
-    if (argparse_action == 'append'
-            or argparse_nargs in ('+', '*')
-            or isinstance(argparse_nargs, int)):
-        type_widget = functools.partial(
-            # TODO give a fixed N as a parameter too
-            MultiValueInputWidget, type_widget)
+    if isinstance(nargs, int):
+        # we have a concrete number
+        if nargs > 1:
+            type_widget = functools.partial(
+                # TODO give a fixed N as a parameter too
+                MultiValueInputWidget, type_widget)
+    else:
+        if nargs in ('+', '*') or argparse_action == 'append':
+            type_widget = functools.partial(
+                MultiValueInputWidget, type_widget)
 
     return type_widget

+ 3 - 5
datalad_gooey/param_widgets.py

@@ -3,7 +3,6 @@ from types import MappingProxyType
 from typing import (
     Any,
     Dict,
-    List,
 )
 
 from PySide6.QtCore import (
@@ -22,8 +21,6 @@ from PySide6.QtWidgets import (
     QWidget,
 )
 
-from datalad import cfg as dlcfg
-
 from .resource_provider import gooey_resources
 from .utils import _NoValue
 
@@ -278,6 +275,7 @@ class StrParamWidget(QLineEdit, GooeyParamWidgetMixin):
 class PathParamWidget(QWidget, GooeyParamWidgetMixin):
     def __init__(self, basedir=None,
                  pathtype: QFileDialog.FileMode = QFileDialog.AnyFile,
+                 disable_manual_edit: bool = False,
                  parent=None):
         """Supported `pathtype` values are
 
@@ -298,8 +296,8 @@ class PathParamWidget(QWidget, GooeyParamWidgetMixin):
 
         # the main widget is a simple line edit
         self._edit = QLineEdit(self)
-        if dlcfg.obtain('datalad.gooey.ui-mode') == 'simplified':
-            # in simplified mode we do not allow manual entry of paths
+        if disable_manual_edit:
+            # in e.g. simplified mode we do not allow manual entry of paths
             # to avoid confusions re interpretation of relative paths
             # https://github.com/datalad/datalad-gooey/issues/106
             self._edit.setDisabled(True)

+ 3 - 25
datalad_gooey/resources/ui/main_window.ui

@@ -235,12 +235,10 @@
     <property name="title">
      <string>&amp;View</string>
     </property>
-    <widget class="QMenu" name="menuInterface">
+    <widget class="QMenu" name="menuSuite">
      <property name="title">
-      <string>&amp;Interface mode</string>
+      <string>&amp;Suite</string>
      </property>
-     <addaction name="actionInterfaceMode_simplified"/>
-     <addaction name="actionInterfaceMode_complete"/>
     </widget>
     <widget class="QMenu" name="menuTheme">
      <property name="title">
@@ -250,7 +248,7 @@
      <addaction name="actionViewTheme_light"/>
      <addaction name="actionViewTheme_dark"/>
     </widget>
-    <addaction name="menuInterface"/>
+    <addaction name="menuSuite"/>
     <addaction name="menuTheme"/>
    </widget>
    <widget class="QMenu" name="menuHelp">
@@ -276,31 +274,11 @@
     <string>Check for new version</string>
    </property>
   </action>
-  <action name="actionRun_stuff">
-   <property name="text">
-    <string>Run stuff</string>
-   </property>
-  </action>
-  <action name="actionConfigure_stuff">
-   <property name="text">
-    <string>Configure stuff</string>
-   </property>
-  </action>
   <action name="action_Quit">
    <property name="text">
     <string>&amp;Quit</string>
    </property>
   </action>
-  <action name="actionInterfaceMode_simplified">
-   <property name="text">
-    <string>Simplified</string>
-   </property>
-  </action>
-  <action name="actionInterfaceMode_complete">
-   <property name="text">
-    <string>Complete</string>
-   </property>
-  </action>
   <action name="actionViewTheme_system">
    <property name="text">
     <string>System</string>

+ 48 - 18
datalad_gooey/simplified_api.py

@@ -202,22 +202,6 @@ api = dict(
     ),
 )
 
-exclude_parameters = set((
-    'result_renderer',
-    'return_type',
-    'result_filter',
-    'result_xfm',
-    'on_failure',
-    'jobs',
-    'recursion_limit',
-))
-
-parameter_display_names = dict(
-    annex='Dataset with file annex',
-    cfg_proc='Configuration procedure(s)',
-    dataset='Dataset location',
-)
-
 dataset_api = {
     c: s for c, s in api.items()
     if c in (
@@ -242,6 +226,52 @@ annexed_file_api = {
     c: s for c, s in api.items()
     if c in ('drop', 'get', 'push', 'save')
 }
+# get of a single annexed files can be simpler
+from copy import deepcopy
+annexed_file_get = deepcopy(annexed_file_api['get'])
+parameter_constraints=dict(
+            path=EnsureExistingDirectory(),
+        ),
+annexed_file_get['exclude_parameters'].update((
+    # not getting data for an annexed file makes no sense
+    'get_data',
+    # recursion underneath a file is not possible
+    'recursive',
+))
+annexed_file_get['parameter_nargs'] = dict(
+    path=1,
+)
+annexed_file_api['get'] = annexed_file_get
+
 
-# simplified API has no groups
-api_group_order = {}
+gooey_suite = dict(
+    title='Simplified',
+    description='Simplified access to the most essential operations',
+    options=dict(
+        disable_manual_path_input=True,
+    ),
+    apis=dict(
+        dataset=dataset_api,
+        directory=directory_api,
+        directory_in_ds=directory_in_ds_api,
+        file=file_api,
+        file_in_ds=file_in_ds_api,
+        annexed_file=annexed_file_api,
+    ),
+    # simplified API has no groups
+    api_group_order={},
+    exclude_parameters=set((
+        'result_renderer',
+        'return_type',
+        'result_filter',
+        'result_xfm',
+        'on_failure',
+        'jobs',
+        'recursion_limit',
+    )),
+    parameter_display_names=dict(
+        annex='Dataset with file annex',
+        cfg_proc='Configuration procedure(s)',
+        dataset='Dataset location',
+    ),
+)

+ 1 - 1
datalad_gooey/tests/test_dataladcmd_ui.py

@@ -18,7 +18,7 @@ def test_GooeyDataladCmdUI(gooey_app, *, qtbot):
     qtbot.addWidget(gooey_app.main_window)
     cmdui = GooeyDataladCmdUI(gooey_app, gooey_app.get_widget('cmdTab'))
 
-    cmdui.configure('wtf', {})
+    cmdui.configure({}, 'wtf', {})
 
     # command tab is set up:
     assert_true(cmdui.pwidget.isEnabled())

+ 3 - 0
setup.cfg

@@ -41,6 +41,9 @@ datalad.extensions =
     # the entrypoint can point to any symbol of any name, as long it is
     # valid datalad interface specification (see demo in this extensions)
     gooey = datalad_gooey:command_suite
+datalad.gooey.suites =
+    gooey-simplified = datalad_gooey.simplified_api:gooey_suite
+    gooey-complete = datalad_gooey.complete_api:gooey_suite
 # install the GUI starter as a direct entrypoint to avoid the datalad CLI
 # overhead
 gui_scripts =