# frozen_string_literal: true class Project class Cache DISALLOWED_EXTENSIONS = %w[.exe] attr_writer :cache def initialize(os_path, allowed_paths, disallowed_paths) @os_path = os_path @allowed_paths = allowed_paths @disallowed_paths = disallowed_paths @top_level_index = 0 end def cache @cache ||= initialize_cache(@allowed_paths, @disallowed_paths) end alias_method :entries, :cache def top_level_entries entries.select { |key, _| key.size == @top_level_index } .values # Sort directories first, then files. Both sorted alphabetically (case-insensitive). .sort_by { |entry| [(entry.directory? ? 0 : 1), entry.name.downcase] } end def find(*key) entries[key] end def find_by_path(path) return if path.blank? find(*path_to_array(path)) end def children(parent_or_key, level: 1) parent = if parent_or_key.is_a?(Entry) parent_or_key else find(*parent_or_key) end return [] if parent.nil? level += parent.key.size entries.select do |key, entry| next if key.size != level next if entry == parent parent.child?(entry) end end private def path_to_array(path) path.split("/") .reject(&:blank?) .map(&:to_sym) end def os_path_to_array(path) return [] if path.blank? path_to_array(path.to_s.sub(@os_path.to_s, "")) end def initialize_cache(allowed_paths, disallowed_paths) allowed_paths = Trie.new(allowed_paths.map { |p| os_path_to_array(p) }) disallowed_paths = Trie.new(disallowed_paths.map { |p| os_path_to_array(p) }) # If the only path is an asterisk, allow all files # Perf: We only need to check this once allow_all_files = allowed_paths.exists?([:*]) cache = Dir["#{@os_path}/**/*"].each_with_object({}) do |path, cache| path = Pathname.new(path) # Convert the file path into an array of symbols # "lib/directory/file.txt" => [:lib, :directory, :"file.txt"] path_array = path_to_array(path.to_s.sub(@os_path.to_s, "")) entry = if ::File.directory?(path) Project::Directory.new(self, path_array, path) else Project::File.new(path_array, path) end next if DISALLOWED_EXTENSIONS.include?(entry.type) next unless allow_all_files || allowed_paths.exists?(entry.key) next if disallowed_paths.exists?(entry.key) cache[path_array] = entry end # Not every project's top level entries will have 1 path element @top_level_index = cache.keys.map(&:count).min cache end end end