lib/tangle/mixin/directory.rb
# frozen_string_literal: true
module Tangle
module Mixin
# Tangle mixin for loading a directory structure as a graph
#
# Graph.new(directory: { options })
#
# options are:
# root: root directory for the structure (mandatory)
# loaders: list of object loader lambdas (mandatory)
# ->(graph, **) { ... } => finished?
# follow_links: bool for following symlinks to directories
# exclude_root: bool for excluding the root directory
#
# All bool options default to false.
#
# A loader lambda is called with the graph as only positional
# argument, and a number of keyword arguments:
#
# path: Path of current filesystem object
# parent: Path of filesystem parent object
# lstat: File.lstat for path
# stat: File.stat for path, if lstat.symlink?
#
# The lambdas are called in order until one returns true.
#
# Example:
# loader = lambda do |g, path:, parent:, lstat:, **|
# vertex = kwargs[:lstat]
# g.add_vertex(vertex, name: path)
# g.add_edge(g[parent], vertex) unless parent.nil?
# end
# Tangle::DiGraph.new(mixins: [Tangle::Mixins::Directory],
# directory: { root: '.', loaders: [loader] })
module Directory
# Tangle::Graph mixin for loading a directory structure
module Graph
attr_reader :root_directory
private
def initialize_kwarg_directory(options)
@root_directory = options.fetch(:root)
@directory_loaders = options.fetch(:loaders)
@follow_directory_links = options[:follow_links]
@exclude_root = options[:exclude_root]
load_directory_graph(@root_directory)
end
def load_directory_graph(path, parent = nil)
return unless load_directory_object(path, parent)
Dir.each_child(path) do |file|
load_directory_graph(File.join(path, file), path)
end
end
# Load a filesystem object into the graph, returning
# +true+ if the object was a directory (or link to one,
# and we're following links).
def load_directory_object(path, parent = nil)
if @exclude_root
return true if path == @root_directory
parent = nil if parent == @root_directory
end
try_directory_loaders(path, parent)
end
# Try each directory loader, returning true if the object has
# children to follow
def try_directory_loaders(path, parent)
stat = lstat = File.lstat(path)
stat = File.stat(path) if lstat.symlink?
@directory_loaders.any? do |loader|
loader.to_proc.call(self, path: path, parent: parent,
lstat: lstat, stat: stat)
end
return if lstat.symlink? && !@follow_directory_links
stat.directory?
end
end
end
end
end