classdef PathRegistrationSet < extmgr.RegistrationSet
    %PathRegistrationSet   Define the PathRegistrationSet class.
    
    %   Copyright 2012-2015 The MathWorks, Inc.
    
    properties (SetAccess = private)
        
        % Name of the application-specific extension registration file
        FileName = '';
    end
    
    properties (Access = private)
        DebugFlag = false;
    end
    
    methods
        
        function this = PathRegistrationSet(varargin)
            %PathRegistrationSet   Construct the PathRegistrationSet class.
            mlock;
            this@extmgr.RegistrationSet(varargin{:});
        end
    end
    
    methods (Access = protected)
        function nodeName = getNodeName(this)
            nodeName = this.FileName;
        end
        
        function parseInputs(this, fileName, debugFlag)
            this.FileName = fileName;
            if nargin > 2
                this.DebugFlag = debugFlag;
            end
        end
        
        function files = findRegistrationFiles(this)
            files = which('-all',this.FileName);
        end
        
        function runRegistrationFile(this, fileName)
            %REGISTER Find and register scope extensions.
            %   REGISTER finds and register scope extensions without invoking
            %   any license checkout on the scopext.m description files.
            %   Errors are logged to MessageLog, if available.
            %
            %   Scopes may be enhanced by user-provided "plug-in" files
            %   for the purpose of providing new data sources and formats,
            %   adding new tools and measurements, or adding new renderers.
            %   These are collectively known as "scope extensions."
            %
            %   Scope extensions are registered by adding the file named
            %   "scopext.m" to the MATLAB path.  These files define the
            %   namespace and class name implementing each scope extension.
            %   Typically, scopext.m is a namespace function, placed in the
            %   same namespace that implements the scope extension itself.
            %
            %   All scope extensions implement their functionality by extending
            %   (or subclassing) specified base classes, and implementing a
            %   set of required methods for the appropriate interface.  Different
            %   base classes are subclassed depending on the type of extension
            %   to be implemented.  See SCOPEXT_INTF for help on the process of
            %   implementing new scope functionality.  SCOPEXT_REG only
            %   addresses the registration of those extensions to make them
            %   known to the scope environment.
            %
            %   The registration function "scopext.m" invokes one or more extension
            %   registration methods on an object passed into the function for this
            %   purpose.  The function prototype of scopext.m must be:
            %
            %      function scopext(ext)
            %
            %   Note that the argument name "ext" must not be changed.  Within this
            %   function, methods are optionally invoked on the object to register
            %   scope extensions.  The method names are specific to each extension
            %   type, and include data source, tool, and visual extension methods
            %   as defined below.
            %
            %   ext.add('sources', ClassName,Name,Description)
            %      registers a new data source extension.
            %
            %   ext.add('tools', ClassName,Name,Description)
            %      registers a new tool extension.
            %
            %   ext.add('visual',ClassName,Name,Description)
            %      registers a new visualization extension.
            %
            %   ext.add('custom',ClassName,Name,Description)
            %      registers a new custom extension, which is optionally
            %      used by other extension types and are not defined here.
            %
            %   All arguments to the methods above are strings.
            %   Note that 'ClassName' may include the namespace name as well as
            %   the class, as in 'namespace.Class'.
            %
            %   Example: Define a scope extension file that registers
            %      one new data source and two new measurement tools.
            %      To do this, create a file named scopext.m on the MATLAB
            %      path with the following content:
            %
            %      function scopext(ext)
            %      ext.add('source', 'CustomFmt','MyPkg.SrcClass','Read custom format');
            %      ext.add('tool','Peaks','MyPkg.PeakFindClass', 'Find data peaks');
            %      ext.add('tool','Variance','MyPkg.VarClass',  'Compute sample variance');
            %
            %      Presumably, MyPkg.PeakFindClass is a class that subclasses
            %      the appropriate base class defined in SCOPEXT_INTF and
            %      implements a new peak finder tool.
            %
            %  See also SCOPEXT, SCOPEXT_INTF.
            
            % NOTE: For plug-in behaviors in other (non-scope) code, see:
            %   /toolbox/matlab/demos/finddemo.m
            %
            % This function creates and populates an RegistrationSet object.
            
            % Get marker file name that plug-in's must provide.
            % This is the name of a MATLAB function, possibly multiple,
            % each of which is either on the MATLAB path, or in a namespace
            % whose parent is on the MATLAB path.
            
            if this.DebugFlag
                % Run the files instead of evaluating them when in debug mode.  This
                % allows us to place breakpoints in the extension files.
                run_ext_file(this, fileName);
            else
                % Evaluate files to get registration data Automatically adds to
                % this object.  Execute the content of each registration file
                eval_ext_file(this, fileName);
            end
        end
        
        function fcnStr = readExtensionFile(~, fileName)
            %read_plugin_content Read and return contents of plug-in file.
            %  Returns string fcnStr, processed such that "eval" can be executed on it.
            %  This means turning the "function" into a "script."  This is generally
            %  dangerous, since there is no guarantee that this conversion is
            % unambiguous.  There are several steps we take:
            %
            %  - The "function" declaration line is removed
            %  - Line-continuations (...) are removed, as is the remainder of
            %     text on that line to conform with MATLAB syntax
            
            fcnStr = '';  % Initialize string containing script contents
            
            % Open file, read-only, text mode
            % (text mode: only on a PC, CR is removed preceding LF)
            [fid,errmsg] = fopen(fileName,'rt');
            error(errmsg);
            
            % Find function definition line
            fcnFound = false;
            while ~feof(fid)
                fcnLine = fgetl(fid);
                if ~ischar(fcnLine) && ~(isstring(fcnLine) && isscalar(fcnLine))
                    break % EOF or read error
                end
                fcnFound = ~isempty(strfind(lower(fcnLine),'function'));
                if fcnFound, break; end
            end
            
            % Were we successful in finding "function" line?
            err = ~fcnFound;
            if err
                if ~feof(fid)
                    errmsg = ferror(fid);
                else
                    errmsg = 'Function declaration not found.';
                end
                fclose(fid);
                error(errmsg);
            end
            
            % Must have found "function" line
            %
            % NOTE: Here we could parse fcnLine string to extract
            %       the input argument name, but we don't.
            %
            %    Benefit: user could change name of formal arg in plug-in function,
            %             and we would not know this unless the parse
            %   Drawback: more work to parse file, and to set up evaluation
            
            % Start reading the registration file
            cr = sprintf('\n');
            while ~feof(fid)
                fcnLine = fgetl(fid);  % Read next line in file
                if ~ischar(fcnLine) && ~(isstring(fcnLine) && isscalar(fcnLine))
                    break  % EOF or read error
                end
                % Must remove any line-continuations (...), since eval()
                % does not allow these.
                %
                dotsIdx = strfind(fcnLine,'...');
                if isempty(dotsIdx)
                    % No line continuation
                    % Must reintroduce carriage-return, in case a comment was at
                    % the end of the line of text.  Concatenation without CR would
                    % cause all new chars to be part of the comment, resulting in
                    % incorrect evaluation of the plug-in.
                    fcnStr = [fcnStr fcnLine cr]; %#ok
                else
                    % Remove "..." and concatenate with the next line in file
                    % with no carriage return.
                    %
                    % Note: this will remove '...' inside any quoted strings.
                    % To fix this, we would need to contextually parse each line,
                    % requiring more time to execute.
                    %
                    % We must remove not only the "...", but also the rest of the line
                    % after the "...".  This is because MATLAB treats all characters
                    % after the ellipsis as a comment.  If a user adds additional chars,
                    % these would accidentally be "executed" and possibly cause bugs.
                    %
                    % So instead of removing from dotsIdx:dotsIdx+2,
                    % we remove ALL chars to the end of the line:
                    %
                    fcnStr = [fcnStr fcnLine(1:dotsIdx-1) ' ']; %#ok
                end
            end
            err = ~feof(fid);
            if err
                fclose(fid);  % get error msg before closing fid
                error(ferror(fid));
            end
            fclose(fid);
        end
    end
end

% ------------------------------------------------------------------------
function run_ext_file(this, fname)

[root, path] = strtok(fname, '@');
if isempty(path)
    [file_path, file_name] = fileparts(root);
    % Since the file may be shadowed in multiple directories, the only way
    % to execute a particular version is to cd into the directory and eval
    % the file. We could have read the file and executed its contents as a
    % script, but that defeats the whole purpose of being in debug mode.
    % Capture the current working directory.
    orig_dir = pwd;
    % CD into the directory which contains the file that needs to be evaluated.
    cd(file_path);
    % Evaluate the file.
    try
        feval(file_name, this);
    catch e
        cd(orig_dir);
        error(e.message);
    end
    cd(orig_dir);
else
    path(1) = [];
    [namespace, file] = fileparts(path);
    file = sprintf('%s.%s', namespace, file);
    feval(file, this);
end

end

% ------------------------------------------------------------------------
function eval_ext_file(this,fname)
%EVAL_EXT_FILE Read and execute content from a registration file.
%   Content is read in and evaluated using "eval"
%   This is done to avoid license checkout.

% Read registration content, converting "function" to "script"
%
fcnStr = readExtensionFile(this, fname);

% We want to detect if any registrations occur after executing
% the plug-in.
%  - no additional plug-in's?  issue warning, since the file
%    did not do what it should do
%  - one or more plug-in's?  we want to know which, so we can
%    associate the name of the plug-in file with each new
%    registered plug-in


% Execute the plug-in function
%
% Protect against empty strings, which eval does not like
if ~isempty(fcnStr)
    % Run registration on a newly instantiated
    % Extensions registry database object (RegistrationSet).
    %
    % All data is manipulated "in-place" via the ext object
    
    % Set the name of the extension registration file we are reading
    % into this.  This is done purely for error-reporting purposes.
    % this is the only variable passed in to the evaluation,
    % and if errors occur during the underlying "add" operation only
    % this is available to deduce the context of the error.
    % We want to associate the plug-in file name with the error message,
    % so a temporary "scratch" string has been added to this just
    % for this purpose.  We clear it when done, just to make sure
    % it's not being used for any other "persistent" purposes.
    %
    
    % Evaluate registration function content as a script
    % Pass extension registration database as an additional argument
    % This gets passed as "ext" in the eval_ext_script() function,
    % which is what the script expects to have as a formal arg.
    
    % Errors could occur during "add" operation
    % That's where the "FileBeingProcessed" property is used
    %
    eval_ext_script(fcnStr,this);
    
    % NOTE:
    %  We register success messages here, and not in RegistrationDb:add,
    %  That's because the add() method could get called with only
    %  a partial amount of information during construction, say,
    %  only the extension type and name.  Yet we want the message log
    %  to indicate the file, description, dependencies, etc, all of
    %  which might have only been added after the constructor line
    %  executed, e.g., via property set such as h.Description=blah.
    %  So we must wait until the entire extension file executes.
    %  Forcing us to do this here, and not add()
    %
    
end
end



% -------------------------------------------------------------------------
function eval_ext_script(script__content__,ext) %#ok
% This is done in a local function to reduce the accidents
% possible when evaluating user code.  The user code could
% create any number of variables that could conflict with
% local variables in the caller's context.  So we reduce the
% variables in scope of this eval as much as possible
%
% script__content__ could be named just about anything, but
% we choose to make it a variable that is unlikely
% to be utilized by user code ... if it is overwritten,
% it won't make a difference to this code.  However,
% the user will find it to be declared if they accidentally
% utilize it.
%
% On the other hand, 'ext' (which is really this) must be
% utilized exactly as spelled in the user code - no change in name
% can be made to this variable!  That's because we're evaluating
% the function as a script, and all variables must be defined.
%
% NOTE:
% It is expected that the user code invokes RegistrationDB::add()
% to add entries to the extension database.
%
eval(script__content__);
end

% [EOF]
