# 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