# frozen-string-literal: true # # Copyright (C) 2019 Thomas Baron # # This file is part of term_utils. # # term_utils is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # term_utils is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with term_utils. If not, see . module TermUtils # The ff module provides a way to find files. module FF # Represents a query cursor. class Cursor # Represents a directory context. Context = Struct.new(:path, :components, :entries) # @return [Integer] attr_reader :index # @param config [TermUtils::FF::Config] # @param path [String] def initialize(config, path) @config = config @path = path @contexts = [] push_context(path) @index = 0 end # Starts the query. # @return [TermUtils::FF::Cursor, nil] def bootstrap res = self if @config.min_depth while depth < @config.min_depth res = following0 break unless res end end res end # Returns the next cursor. # @return [TermUtils::FF::Cursor, nil] def following res = nil loop do res = following0 break unless res if @config.min_depth next if depth < @config.min_depth end if @config.max_depth next if depth > @config.max_depth end @index += 1 break end res end # Returns the result associated to this one. # @return [String] def result @contexts.last.path end # @return [Integer] def depth @contexts.last.components.length end # @return [String] def relative_path @contexts.last.components.join("/") end # @return [String] def name @contexts.last.components[-1] end # Returns the path components. # @return [Array] def components @contexts.last.components.dup end private # Returns the next cursor. # @return [TermUtils::FF::Cursor, nil] def following0 ctx = @contexts.last if ctx if ctx.entries # Directory. until ctx.entries.empty? name = ctx.entries.first break if accept_name(name) ctx.entries.shift end if ctx.entries.empty? # Walked through every entry of directory. @contexts.pop following0 else # Directory entry. new_path = StringIO.new new_path << ctx.path new_path << "/" if ctx.path[-1] != "/" new_path << ctx.entries.first new_components = ctx.components.dup new_components << ctx.entries.first push_context(new_path.string, new_components) ctx.entries.shift self end else # Not directory. @contexts.pop following0 end end end # Pushes a new context on the stask. # @param path [String] def push_context(path, components = []) entries = nil if File.directory? path entries = Dir.entries(path) entries.sort! if @config.sorted end @contexts.push(Context.new(path, components, entries)) end # Tests whether a given name shall be accepted. # @param name [String] # @return [Boolean] def accept_name(name) if (name != ".") and (name != "..") ret = true @config.ignore_list.each do |i| if i.match? name ret = false break end end ret else false end end end end end