class EWorld::RouteScanner

Constants

CHILDREN
CODE
COMPONENT
DISABLED
FULLPATH
HAS_PAGE
ICON
INVISIBLE
META
NAME
PATH
ROUTES
SCHEMA_FILE
TAB_PARENT
TITLE

Public Class Methods

scan(project) click to toggle source

Responsible for scanning the .codegen/routes/routes.yml file. @return Array

# File lib/scanners/route_scanner.rb, line 23
def self.scan(project)
    raise RuntimeError, "Expected project type to be: #{Blufin::Projects::TYPE_QUASAR}, instead got: #{project[Blufin::Projects::TYPE]}" unless project[Blufin::Projects::TYPE] == Blufin::Projects::TYPE_QUASAR
    @errors         = []
    @codes          = []
    @files          = [] # Holds short version of file name.
    @files_expected = [] # Holds full-path to file.
    @files_to_write = [] # Holds list of files that need to be created/stubbed.
    begin
        @td                 = project[Blufin::Projects::TRANSIENT_DATA]
        @path_root          = Blufin::Strings::remove_surrounding_slashes(@td[Blufin::Projects::CG_QUASAR_ROOT])
        @path_routes_file   = Blufin::Strings::remove_surrounding_slashes(@td[Blufin::Projects::CG_QUASAR_ROUTES_FILE])
        @path_pages         = Blufin::Strings::remove_surrounding_slashes(@td[Blufin::Projects::CG_QUASAR_PAGES])
        @path_pages_ignore  = @td[Blufin::Projects::CG_QUASAR_PAGES_IGNORE]
        @project_path       = Blufin::Projects::get_project_path(project[Blufin::Projects::PROJECT_ID])
        @project_path_inner = Blufin::Projects::get_project_path(project[Blufin::Projects::PROJECT_ID], true)
        routes_file         = "#{@project_path_inner}/#{@path_routes_file}"
        # Make sure file exists.
        unless Blufin::Files::file_exists(routes_file)
            @errors << Blufin::ScannerError::add_error(nil, "File not found: #{Blufin::Terminal::format_invalid(routes_file)}")
            return {}, @errors
        end
        # Extract routes file content so we can parse/validate it.
        routes_content = []
        Blufin::Files::read_file(routes_file).each do |line|
            line = line.gsub("\n", '').gsub(/^\s*const\s*routes\s*=\s*\[/, '{ Routes: [')
            # Handle component/import lines.
            if line =~ /^\s*component:\s*\(\)\s*=>\s*import\s*\(['"]\s*@/
                line  = line.gsub(/component:\s*\(\)\s*=>\s*import\s*\(['"]\s*@/, '')
                line  = line.gsub(/['"]\s*\)\s*/, '')
                comma = (line == line.gsub(/,?\s*$/, '')) ? '' : ','
                routes_content << "#{line.split('/')[0]}component: '#{line.gsub(/,?\s*$/, '').strip}'#{comma}"
                next
            end
            break if line =~ /^\s*if\s*\(/
            routes_content << line unless line.strip == ''
        end
        # Remove trailing semi-colon.
        routes_content[routes_content.length - 1] = routes_content[routes_content.length - 1].gsub(/;*\s*$/, '')
        routes_content << '}'
        hash = {}
        eval("hash = #{Blufin::Arrays::convert_line_array_to_string(routes_content)}")
        json         = JSON.parse(hash.to_json)
        # Create tmp file so we can read it. There might be a better way but no time.
        tmp_filename = "/tmp/routes-content-#{Blufin::Strings::random_string(2)}.yaml"
        # Write tmp file.
        Blufin::Files::write_file(tmp_filename, Blufin::Arrays::convert_string_to_line_array(json.to_yaml))
        @routes_yml = Blufin::Yml::read_file(tmp_filename, SCHEMA_FILE)
        @routes_yml[ROUTES].each do |parent|
            parent_path      = parent[PATH]
            parent_title     = parent.has_key?(META) ? parent[META][TITLE] : nil
            parent_icon      = parent.has_key?(META) ? parent[META][ICON] : nil
            parent_has_page  = parent.has_key?(META) ? (parent[META][HAS_PAGE] ? true : false) : false
            parent_invisible = parent.has_key?(META) ? parent[META][INVISIBLE] : nil
            error_prefix     = "Parent path: #{parent_path} \xe2\x80\x94 "
            # Make sure HAS_PAGE is only ever false.
            @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Unless has_page == true, it is not required.", "has_page: '#{parent_has_page}'") if parent.has_key?(META) && parent[META].has_key?(HAS_PAGE) && !parent_has_page
            # Make sure we have the correct meta properties for parent (based on invisible property).
            if parent_invisible
                @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Invisible routes shouldn't have: icon") unless parent_icon.nil?
            else
                @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Missing meta property: icon") if parent_icon.nil?
            end
            @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Missing meta property: title") if parent_title.nil?
            # Make sure properties are in the correct order.
            expected = {
                PATH      => true,
                META      => false,
                COMPONENT => true,
                CHILDREN  => false
            }
            Blufin::Validate::assert_valid_keys(expected, parent.keys, routes_file)
            # Make sure meta properties are in the correct order.
            if parent.has_key?(META)
                expected = {
                    TITLE     => false,
                    ICON      => false,
                    HAS_PAGE  => false,
                    INVISIBLE => false
                }
                Blufin::Validate::assert_valid_keys(expected, parent[META].keys, routes_file)
            end
            # Make sure parent path(s) have a preceding slash.
            @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Parent path must have preceding slash.") unless parent[PATH] =~ /^\//
            # Validate children, if they exist.
            validate_children(parent, Blufin::Strings::remove_surrounding_slashes(parent[PATH]), routes_file, error_prefix) if parent.has_key?(CHILDREN)
        end

    rescue => e

        Blufin::Terminal::print_exception(e)

    end

    # Check for rogue files.
    path_to_pages = "#{@project_path_inner}/#{@path_pages}"
    Blufin::Files::get_files_in_dir(path_to_pages).each do |file|
        next if file =~ /#{path_to_pages}\/(#{@path_pages_ignore.join('|')})\/[a-z0-9]/
        unless @files_expected.include?(file.downcase)
            @errors << Blufin::ScannerError::add_error(routes_file, "Rogue file: #{file}")
        end
    end

    return @files_to_write, @errors

end

Private Class Methods

validate_children(parent, parent_path, routes_file, error_prefix, validate_nested = true) click to toggle source

Recursive function to validate children. @return void

# File lib/scanners/route_scanner.rb, line 133
def self.validate_children(parent, parent_path, routes_file, error_prefix, validate_nested = true)
    raise RuntimeError, "Expected key: #{CHILDREN}" unless parent.has_key?(CHILDREN)
    children        = parent[CHILDREN]
    parent_has_page = parent.has_key?(META) ? (parent[META][HAS_PAGE] ? true : false) : false
    # Make sure we have an array with items.
    unless children.is_a?(Array) || children.length == 0
        @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Children must be an Array and cannot be empty.")
        return
    end
    children.each_with_index do |child, idx|
        # Make a more direct error prefix.
        error_prefix = "Route: #{child[NAME]} \xe2\x80\x94 " if child.has_key?(NAME)
        # Make sure properties are in the correct order.
        expected     = {
            PATH      => true,
            NAME      => true,
            META      => false,
            COMPONENT => true,
            CHILDREN  => false
        }
        Blufin::Validate::assert_valid_keys(expected, child.keys, routes_file)
        # Make sure meta properties are in the correct order.
        if child.has_key?(META)
            expected = {
                FULLPATH   => false,
                CODE       => false,
                TITLE      => false,
                ICON       => false,
                TAB_PARENT => false,
                DISABLED   => false,
            }
            Blufin::Validate::assert_valid_keys(expected, child[META].keys, routes_file)
        end

        # TODO - If non-tabs, must have FULLPATH, CODE, TITLE
        # TODO - If tabs, must have ICON, TAB_PARENT (AND TAB PARENT MUST MATCH PARENT).

        expected_name = Blufin::Strings::remove_surrounding_slashes("#{parent_path}/#{child[PATH]}").gsub('/', '-')
        # Handle special case for dashboard.
        expected_name = 'dashboard' if child[NAME] == 'dashboard' && expected_name.strip == ''
        expected_file = Blufin::Strings::remove_surrounding_slashes("#{parent_path}/#{child[PATH]}").split('/')
        raise RuntimeError, "Expected file count should never be greater than 3: #{expected_file.inspect}" if expected_file.length > 3
        fullpath      = '' if expected_file.length == 0
        fullpath      = expected_file[0] if expected_file.length == 1
        fullpath      = "#{expected_file[0]}/#{expected_file[1]}" if expected_file.length == 2
        fullpath      = "#{expected_file[0]}/#{expected_file[1]}/#{expected_file[2]}" if expected_file.length == 3
        expected_file = '' if expected_file.length == 0
        expected_file = expected_file[0] if expected_file.length == 1
        expected_file = "#{expected_file[0]}/#{expected_file[1]}" if expected_file.length == 2
        expected_file = "#{expected_file[0]}/#{expected_file[1]}-#{expected_file[2]}" if expected_file.length == 3
        # Check full path is correct.
        @errors << Blufin::ScannerError::add_error(routes_file, "Expected meta.fullpath: #{fullpath}", "Got meta.fullpath: '#{child[META][FULLPATH]}'") if child[META].has_key?(FULLPATH) && child[META][FULLPATH].strip != fullpath
        # Check the parent path.
        if idx == 0
            if validate_nested
                @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}If #{HAS_PAGE} flag is set, 1st child must have empty path.", "path: '#{child[PATH]}'") if parent_has_page && child[PATH] != ''
                @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}If #{HAS_PAGE} flag is NOT set, 1st child cannot have empty path.", "path: '#{child[PATH]}'") if !parent_has_page && child[PATH].strip == ''
                # This handles the root paths, IE: 'dashboard/dashboard.vue'
                expected_file = "#{expected_name}/#{expected_name}" if parent_has_page
            else
                @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Cannot have empty path.", "path: '#{child[PATH]}'") if child[PATH].strip == ''
            end
        else
            @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Cannot have empty path.", "path: '#{child[PATH]}'") if child[PATH].strip == ''
        end
        expected_file = "/#{@path_pages}/#{Blufin::Strings::remove_surrounding_slashes(expected_file)}.vue"
        actual_file   = "#{@project_path_inner}/#{Blufin::Strings::remove_surrounding_slashes(expected_file)}"
        # Make sure we don't have any duplicate files.
        if @files.include?(expected_file)
            @errors << Blufin::ScannerError::add_error(routes_file, "Duplicate file: #{expected_file}")
        else
            @files << expected_file
            @files_expected << actual_file.downcase
        end

        @files_to_write << actual_file unless Blufin::Files::file_exists(actual_file)

        @errors << Blufin::ScannerError::add_error(routes_file, "Expected name: #{expected_name}", "Got name: '#{child[NAME]}'") if child[NAME].strip != expected_name
        @errors << Blufin::ScannerError::add_error(routes_file, "Expected component: #{expected_file}", "Got component: '#{child[COMPONENT]}'") if child[COMPONENT].strip != expected_file

        # Make sure there are no duplicate codes.
        if child[META].has_key?(CODE)
            code = child[META][CODE]
            if @codes.include?(code)
                @errors << Blufin::ScannerError::add_error(routes_file, "Duplicate code: #{code}")
            else
                @codes << code
            end
        end

        # Validate nested children (if any).
        if child.has_key?(CHILDREN)
            unless validate_nested
                # This will probably never get hit, but just in case.
                @errors << Blufin::ScannerError::add_error(routes_file, "#{error_prefix}Cannot go more than 3 levels deep.") unless validate_nested
                return
            end
            validate_children(child, "#{parent_path}/#{child[PATH]}", routes_file, error_prefix, false)
        end

    end

end