Badger
view release on metacpan or search on metacpan
lib/Badger/Config/Filesystem.pm view on Meta::CPAN
}
sub init_filesystem {
my ($self, $config) = @_;
my $class = $self->class;
$self->debug_data( filesystem_config => $config ) if DEBUG;
# The filespec can be specified as a hash of options for file objects
# created by the top-level directory object. If unspecified, we construct
# it using any encoding option, or falling back on a $ENCODING package
# variable. This is then passed to init_workplace().
my $encoding = $config->{ encoding }
|| $class->any_var(ENCODING);
my $filespec = $config->{ filespec } ||= {
encoding => $encoding
};
# now initialise the workplace root directory
$self->init_workplace($config);
# Configuration files can be in any data format which Badger::Codecs can
# handle (e.g. JSON, YAML, etc). The 'extensions' configuration option
# and any $EXTENSIONS defined in package variables (for the current class
# and all base classes) will be tried in order
my $exts = $class->list_vars(
EXTENSIONS => $config->{ extensions }
);
$exts = [
map { @{ split_to_list($_) } }
@$exts
];
# Construct a regex to match any of the above
my $qm_ext = join('|', map { quotemeta $_ } @$exts);
my $ext_re = qr/.($qm_ext)$/i;
$self->debug(
"extensions: ", $self->dump_data($exts), "\n",
"extension regex: $ext_re"
) if DEBUG;
# The 'codecs' option can provide additional mapping from filename extension
# to codec for any that Badger::Codecs can't grok automagically
my $codecs = $class->hash_vars(
CODECS => $config->{ codecs }
);
my $data = $config->{ data } || { };
$self->{ data } = $data;
$self->{ extensions } = $exts;
$self->{ match_ext } = $ext_re;
$self->{ codecs } = $codecs;
$self->{ encoding } = $encoding;
$self->{ filespec } = $filespec;
$self->{ quiet } = $config->{ quiet } || FALSE;
$self->{ dir_tree } = $config->{ dir_tree } // TRUE;
$self->{ stat_ttl } = $config->{ stat_ttl } // $data->{ stat_ttl } // $STAT_TTL;
$self->{ not_found } = { };
# Add any item schemas
$self->items( $config->{ schemas } )
if $config->{ schemas };
# Configuration file allows further data items (and schemas) to be defined
$self->init_file( $config->{ file } )
if $config->{ file };
return $self;
}
sub init_file {
my ($self, $file) = @_;
my $data = $self->get($file);
if ($data) {
# must copy data so as not to damage cached version
$data = { %$data };
$self->debug(
"config file data from $file: ",
$self->dump_data($data)
) if DEBUG;
# file can contain 'items' or 'schemas' (I don't love this, but it'll do for now)
$self->items(
delete $data->{ items },
delete $data->{ schemas }
);
# anything else is config data
extend($self->{ data }, $data);
$self->debug("merged data: ", $self->dump_data($self->{ data })) if DEBUG;
}
elsif (! $self->{ quiet }) {
return $self->no_config_file($file);
}
return $self;
}
sub no_config_file {
shift->warn_msg( no_config_file => @_ );
}
#-----------------------------------------------------------------------------
# Redefine head() method in Badger::Config to hook into fetch() to load data
#-----------------------------------------------------------------------------
sub head {
my ($self, $name) = @_;
return $self->{ data }->{ $name }
// $self->fetch($name);
}
sub tail {
my ($self, $name, $data) = @_;
return $data;
}
#-----------------------------------------------------------------------------
# Filesystem-specific fetch methods
#-----------------------------------------------------------------------------
sub fetch {
my ($self, $uri) = @_;
return if $self->previously_not_found($uri);
$self->debug("fetch($uri)") if DEBUG or DEBUG_FETCH;
my $file = $self->config_file($uri);
my $dir = $self->dir($uri);
my $fok = $file && $file->exists;
my $dok = $dir && $dir->exists;
if ($dok) {
$self->debug("Found directory for $uri, loading tree") if DEBUG or DEBUG_FETCH;
return $self->config_tree($uri, $file, $dir);
}
if ($fok) {
$self->debug("Found file for $uri, loading file data => ", $file->absolute) if DEBUG or DEBUG_FETCH;
my $data = $file->try->data;
return $self->error_msg( load_fail => $file => $@ ) if $@;
return $self->tail(
$uri, $data,
$self->item_schema_from_data(
$uri, $data
)
);
}
$self->debug("No file or directory found for $uri") if DEBUG or DEBUG_FETCH;
$self->{ not_found }->{ $uri } = time();
return undef;
}
sub previously_not_found {
my ($self, $uri) = @_;
my $sttl = $self->{ stat_ttl } || return 0;
my $when = $self->{ not_found }->{ $uri } || return 0;
# we maintain the "not_found" status until stat_ttl seconds have elapsed
if (time < $when + $sttl) {
$self->debug("$uri NOT FOUND at $when") if DEBUG; # or DEBUG_FETCH;
return 1
}
else {
return 0;
}
}
#-----------------------------------------------------------------------------
# Tree walking
#-----------------------------------------------------------------------------
sub config_tree {
my $self = shift;
my $name = shift;
my $file = shift || $self->config_file($name);
my $dir = shift || $self->dir($name);
my $do_tree = $self->{ dir_tree };
my $data = undef; #{ };
my ($file_data, $binder, $more);
unless ($file && $file->exists || $dir->exists) {
return $self->decline_msg( not_found => 'file or directory' => $name );
}
# start by looking for a data file
if ($file && $file->exists) {
$file_data = $file->try->data;
return $self->error_msg( load_fail => $file => $@ ) if $@;
$self->debug("Read metadata from file '$file':", $self->dump_data($file_data)) if DEBUG;
}
# fetch a schema for this data item constructed from the default schema
# specification, any named schema for this item, any arguments, then any
# local schema defined in the data file
my $schema = $self->item_schema_from_data($name, $file_data);
$self->debug(
"combined schema for $name: ",
$self->dump_data($schema)
) if DEBUG;
if ($more = $schema->{ tree_type }) {
$self->debug("schema.tree_type: $more") if DEBUG;
if ($more eq NONE) {
$self->debug("schema rules indicate we shouldn't descend into the tree") if DEBUG;
$do_tree = FALSE;
}
elsif ($binder = $self->tree_binder($more)) {
$self->debug("schema rules indicate a $more tree tree") if DEBUG;
$do_tree = TRUE;
}
else {
return $self->error_msg( invalid => tree_type => $more );
}
}
if ($do_tree) {
# merge file data using binder
$data ||= { };
$binder ||= $self->tree_binder('nest');
$binder->($self, $data, [ ], $file_data, $schema);
if ($dir->exists) {
# create a virtual file system rooted on the metadata directory
# so that all file paths are resolved relative to it
my $vfs = VFS->new( root => $dir );
$self->debug("Reading metadata from dir: ", $dir->name) if DEBUG;
$self->scan_config_dir($vfs->root, $data, [ ], $schema, $binder);
}
}
else {
$data = $file_data;
}
$self->debug("$name config: ", $self->dump_data($data)) if DEBUG;
return $self->tail(
$name, $data, $schema
);
}
sub scan_config_dir {
lib/Badger/Config/Filesystem.pm view on Meta::CPAN
$option ||= $self->{ uri_paths } || return $uri;
if ($option eq 'absolute') {
$self->debug("setting absolute URI path") if DEBUG;
$uri = "/$uri" unless $uri =~ /^\//;
}
elsif ($option eq 'relative') {
$self->debug("setting relative URI path") if DEBUG;
$uri =~ s/^\///;
}
else {
return $self->error_msg( invalid => 'uri_paths option' => $option );
}
return $uri;
}
#-----------------------------------------------------------------------------
# Internal methods
#-----------------------------------------------------------------------------
sub config_file {
my ($self, $name) = @_;
return $self->{ config_file }->{ $name }
||= $self->find_config_file($name);
}
sub config_file_data {
my $self = shift;
my $file = $self->config_file(@_) || return;
my $data = $file->try->data;
return $self->error_msg( load_fail => $file => $@ ) if $@;
return $data;
}
sub config_filespec {
my $self = shift;
my $defaults = $self->{ filespec };
return @_
? extend({ }, $defaults, @_)
: { %$defaults };
}
sub find_config_file {
my ($self, $name) = @_;
my $root = $self->root;
my $exts = $self->extensions;
foreach my $ext (@$exts) {
my $path = $name.DOT.$ext;
my $file = $self->file($path);
if ($file->exists) {
$file->codec($self->codec($ext));
return $file;
}
}
return $self->decline_msg(
not_found => file => $name
);
}
sub write_config_file {
my ($self, $name, $data) = @_;
my $root = $self->root;
my $exts = $self->extensions;
my $ext = $exts->[0];
my $path = $name.DOT.$ext;
my $file = $self->file($path);
$file->codec($self->codec($ext));
$file->data($data);
return $file;
}
sub codec {
my ($self, $name) = @_;
return $self->codecs->{ $name }
|| $name;
}
#-----------------------------------------------------------------------------
# item schema management
#-----------------------------------------------------------------------------
sub items {
return extend(
shift->{ item },
@_
);
}
sub item {
my ($self, $name) = @_;
$self->debug_data("looking for $name in items: ", $self->{ item }) if DEBUG;
return $self->{ item }->{ $name }
||= $self->lookup_item($name);
}
sub lookup_item {
# hook for subclasses
return undef;
}
sub item_schema {
my ($self, $name, $schema) = @_;
my $data = $self->item($name);
if (DEBUG) {
$self->debug_data("$name item schema data: ", $data);
$self->debug_data("$name file schema: ", $schema);
}
if ($schema) {
$data = extend({ }, $data, $schema);
( run in 0.859 second using v1.01-cache-2.11-cpan-98e64b0badf )