Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 9 additions & 72 deletions +labkit/+ui/+app/dispatchRequest.m
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
function [handled, outputs, debugContext] = dispatchRequest(appName, args, nout, handlers)
%DISPATCHAPPREQUEST Dispatch app test/debug launch requests.
function [handled, outputs, debugContext] = dispatchRequest(appName, args, nout)
%DISPATCHREQUEST Dispatch app debug launch requests.
%
% Usage:
% [handled, outputs, debug] = labkit.ui.app.dispatchRequest( ...
% "labkit_Example_app", varargin, nargout, handlers);
% "labkit_Example_app", varargin, nargout);
%
% Inputs:
% appName - app entry-point name used to build app-scoped error IDs.
% args - input argument cell from the app entry point.
% nout - requested output count from the app entry point.
% handlers - struct array with fields command, minArgs, maxArgs,
% maxOutputs, and run. The run function accepts command args as a cell
% array and returns outputs as a cell array.
%
% Outputs:
% handled - true when a "__labkit_test__" request was dispatched.
% outputs - cell array to assign to varargout for handled test requests.
% handled - false for normal and debug launches.
% outputs - empty cell array reserved for future launch request handlers.
% debugContext - disabled for normal launches; enabled for "debug",
% "-debug", "--debug", or "__labkit_debug__" launches. Debug launch
% requests do not consume app launch.
Expand All @@ -25,10 +22,6 @@
outputs = {};
debugContext = labkit.ui.diag.createContext(appName, struct('enabled', false));

if nargin < 4
handlers = struct('command', {}, 'minArgs', {}, ...
'maxArgs', {}, 'maxOutputs', {}, 'run', {});
end
if isempty(args)
return;
end
Expand All @@ -48,14 +41,8 @@
return;
end

switch request
case "__labkit_test__"
handled = true;
outputs = dispatchTestRequest(appName, args(2:end), nout, handlers);
otherwise
error(errorId(appName, 'UnsupportedInput'), ...
'%s does not accept input arguments.', appName);
end
error(errorId(appName, 'UnsupportedInput'), ...
'%s does not accept input arguments.', appName);
end

function tf = isDebugRequest(request)
Expand All @@ -65,13 +52,13 @@
function opts = debugOptions(appName, request, args)
opts = struct();
if numel(args) > 2
error(errorId(appName, 'InvalidTestRequest'), ...
error(errorId(appName, 'InvalidDebugOptions'), ...
'%s accepts at most one options struct.', char(request));
elseif numel(args) == 2
opts = args{2};
end
if ~isstruct(opts)
error(errorId(appName, 'InvalidTestRequest'), ...
error(errorId(appName, 'InvalidDebugOptions'), ...
'%s options must be a struct.', char(request));
end
opts.enabled = true;
Expand All @@ -80,56 +67,6 @@
end
end

function outputs = dispatchTestRequest(appName, requestArgs, nout, handlers)
if isempty(requestArgs) || ...
~(ischar(requestArgs{1}) || (isstring(requestArgs{1}) && isscalar(requestArgs{1})))
error(errorId(appName, 'InvalidTestRequest'), ...
'__labkit_test__ requires a string command name.');
end

validateHandlers(appName, handlers);
command = string(requestArgs{1});
commandArgs = requestArgs(2:end);
match = find(strcmp(command, string({handlers.command})), 1, 'first');
if isempty(match)
error(errorId(appName, 'UnknownTestCommand'), ...
'Unknown __labkit_test__ command: %s.', command);
end

handler = handlers(match);
argCount = numel(commandArgs);
if argCount < handler.minArgs || argCount > handler.maxArgs
error(errorId(appName, 'InvalidTestArguments'), ...
'Command %s expects %d to %d argument(s), got %d.', ...
command, handler.minArgs, handler.maxArgs, argCount);
end
if nout > handler.maxOutputs
error(errorId(appName, 'TooManyOutputs'), ...
'Command %s returns at most %d output(s).', command, handler.maxOutputs);
end

outputs = handler.run(commandArgs);
if ~iscell(outputs)
error(errorId(appName, 'InvalidTestRequest'), ...
'Command %s handler must return a cell array of outputs.', command);
end
if numel(outputs) < nout
error(errorId(appName, 'InvalidTestRequest'), ...
'Command %s returned fewer outputs than requested.', command);
end
outputs = outputs(1:nout);
end

function validateHandlers(appName, handlers)
required = {'command', 'minArgs', 'maxArgs', 'maxOutputs', 'run'};
for k = 1:numel(required)
if ~isfield(handlers, required{k})
error(errorId(appName, 'InvalidTestRequest'), ...
'App test handler is missing field "%s".', required{k});
end
end
end

function id = errorId(appName, suffix)
id = sprintf('%s:%s', appName, suffix);
end
22 changes: 18 additions & 4 deletions +labkit/+ui/+tool/createRuntime.m
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,28 @@ function captureDrag(motionFcn, releaseFcn)
state.fig.WindowButtonUpFcn = @onDragRelease;

function onDragMotion(src, evt)
if ~isempty(motionFcn)
motionFcn(src, evt);
try
if ~isempty(motionFcn)
motionFcn(src, evt);
end
catch ME
trace(sprintf('drag motion error for session %s: %s', ...
char(sessionState.name), ME.identifier));
releaseDrag();
rethrow(ME);
end
end

function onDragRelease(src, evt)
if ~isempty(releaseFcn)
releaseFcn(src, evt);
try
if ~isempty(releaseFcn)
releaseFcn(src, evt);
end
catch ME
trace(sprintf('drag release error for session %s: %s', ...
char(sessionState.name), ME.identifier));
releaseDrag();
rethrow(ME);
end
releaseDrag();
end
Expand Down
2 changes: 1 addition & 1 deletion +labkit/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- `docs/ui.md` for `+labkit/+ui`
- `docs/dta.md` for `+labkit/+dta`
- `docs/biosignal.md` for `+labkit/+biosignal`
- affected package tests under `tests/suites/labkit/`
- affected package tests under `tests/unit/labkit/` or `tests/gui/structural/labkit/`

## Boundary Rules

Expand Down
33 changes: 33 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
* text=auto

*.fig binary
*.mat binary
*.mdl binary diff merge=mlAutoMerge
*.mdlp binary
*.mex* binary
*.mlapp binary
*.mldatx binary merge=mlAutoMerge
*.mlproj binary
*.mlx binary
*.p binary
*.plprj binary
*.sbproj binary
*.sfx binary
*.sldd binary
*.slreqx binary merge=mlAutoMerge
*.slmx binary merge=mlAutoMerge
*.sltx binary
*.slxc binary
*.slx binary merge=mlAutoMerge
*.slxp binary

## MATLAB Project metadata files use LF line endings
/resources/project/**/*.xml text eol=lf

## Other common binary file types
*.docx binary
*.exe binary
*.jpg binary
*.pdf binary
*.png binary
*.xlsx binary
Loading
Loading