classdef RootFolder < handle
% ROOTFOLDER stores a root folder in the form of a Token to Path association
% and provides alt names and regexp pattern for replacements.

% Copyright 2021-2023 The MathWorks, Inc.
    properties (SetAccess=private)
        Token {mustBeText, mustBeValidToken} = '$(EMPTY)'
        Path  {mustBeText} = ''
        EnableRelativeAnchorOverride {logical} = false
    end
    
    properties (SetAccess=private, Hidden)
        Variable        % Token  name, undecorated e.g. 'FOO'
        TMFToken        % Variable decorated for TMF, e.g. '|>FOO<|'

        AltPath         % Alt Path, used in Win64 platforms to avoid issue with path using space
        TokenPattern    % Prepared regular expression of Token to use in regexprep
        PathPattern     % Prepared regular expression of Path to use in regexprep
        AltPathPattern  % Prepared regular expression of AltPath to use in regexprep
    end

    properties (GetAccess=private, SetAccess=immutable)
        % allow the possibility of an empty path
        % used when START_DIR path has not been set
        AppendPWDToRelativePaths {logical}
    end

    methods
        function obj = RootFolder(token, lPath, appendPWDToRelativePaths, ...
                                    enableRelativeAnchorOverride)
            arguments
                token
                lPath
                appendPWDToRelativePaths = true
                enableRelativeAnchorOverride = false
            end
            if ~appendPWDToRelativePaths
                assert(isempty(lPath), 'Only allow ~appendPWDToRelativePaths for empty paths');
            end
            obj.AppendPWDToRelativePaths = appendPWDToRelativePaths;
            obj.EnableRelativeAnchorOverride = enableRelativeAnchorOverride;

            % Set the token and all derived text strings
            obj.Token = token;
            % Token is '$(TOK)' Variable is extracted by reading from
            % character 3 to end-1, hence removing '$(' and ')'
            obj.Variable  = token(3:end-1);
            obj.TMFToken  = ['|>' obj.Variable '<|'];
            obj.TokenPattern = regexptranslate('escape',obj.Token);

            % Set the Path
            if isLeadingFileSepSingleBackSlash(lPath)
                obj.Path = lPath;
            elseif obj.AppendPWDToRelativePaths && ...
                    coder.make.internal.isRelativePath(lPath) && ...
                    ~obj.EnableRelativeAnchorOverride
                obj.Path = fullfile(pwd, lPath);
            else
                obj.Path = lPath;
            end
            % Set all derived forms of path for regexp use
            obj.AltPath          = coder.make.internal.transformPaths(obj.Path, 'ignoreErrors',true);
            obj.PathPattern        = regexptranslate('escape',obj.Path);
            obj.AltPathPattern = regexptranslate('escape',obj.AltPath);
        end
    end

    methods (Hidden)
        function disp(obj)
            disp([{obj.Token}', {obj.Path}']);
        end
    end
end

function mustBeValidToken(token)
    % Tokens must be wrapped in $()
    invalidToken = ~(startsWith(token, "$(") && endsWith(token, ")"));
    % Tokens are used as Make variables, strip off leading '$(' and trailing ')'
    makeVariable = token(3:end-1);
    if invalidToken || ~isvarname(makeVariable)
        msg = message('coder_compile:CoderCompile:InvalidRootFolderToken',...
            token);
        error(msg.Identifier, '%s', string(msg))
    end
end

function isSingleBackSlash = isLeadingFileSepSingleBackSlash(lPath)
    % There is a legacy behavior of buildInfo where it returns an invalid
    % path because buildInfo.Settings.FileSep has been set to a value other
    % than the value of filesep for the current platform. On Linux, this 
    % behavior is created by calling updateFileSeparator(buildInfo, '\'). 
    % To avoid incorrect determination of relative path that contains the 
    % leading single '\', we can call this utility function to skip 
    % prepending the pwd to the path. The further work (g1928777) is 
    % needed to eliminate use of updateFileSeparator entirely.
    % TopTester: test/toolbox/coder/compile/compile/tCrossPlatform.m
    
    if ~iscell(lPath)
        lPath = {lPath};
    end

    isLeadingSingleBackSlashExpr = '^\\\w';
    isSingleBackSlash = ~cellfun(@isempty, ...
        regexp(lPath, isLeadingSingleBackSlashExpr, 'once'));
end