The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Data::Properties -- Flexible properties handling

SUMMARY

    use Data::Properties;

    my $cfg = new Data::Properties;

    # Preset a property.
    $cfg->set_property("config.version", "1.23");

    # Parse a properties file.
    $cfg->parse_file("config.prp");

    # Get a property value
    $version = $cfg->get_property("config.version");
    # Same, but with a default value.
    $version = $cfg->get_property("config.version", "1.23");

    # Get the list of subkeys for a property, and process them.
    my $aref = $cfg->get_property_keys("item.list");
    foreach my $item ( @$aref ) {
        if ( $cfg->get_property("item.list.$item") ) {
            ....
        }
    }

DESCRIPTION

The property mechanism is modelled after the Java implementation of properties.

In general, a property is a string value that is associated with a key. A key is a series of names (identifiers) separated with periods. Names are treated case insensitive. Unlike in Java, the properties are really hierarchically organized. This means that for a given property you can fetch the list of its subkeys, and so on. Moreover, the list of subkeys is returned in the order the properties were defined.

Data::Properties can also be used to define data structures, just like JSON but with much less quotes.

Property lookup can use a preset property context. If a context ctx has been set using set_context('ctx'), get_property('foo.bar') will first try 'ctx.foo.bar' and then 'foo.bar'. get_property('.foo.bar') (note the leading period) will only try 'ctx.foo.bar' and raise an exception if no context was set.

Design goals:

  • properties must be hierarchical of unlimited depth;

  • manual editing of the property files (hence unambiguous syntax and lay out);

  • it must be possible to locate all subkeys of a property in the order they appear in the property file(s);

  • lightweight so shell scripts can use it to query properties.

new

new is the standard constructor. new doesn't require any arguments, but you can pass it a list of initial properties to store in the resultant properties object.

clone

clone is like new, but it takes an existing properties object as its invocant and returns a new object with the contents copied.

WARNING This is not a deep copy, so take care.

parse_file file [ , context ]

parse_file reads a properties file and adds the contents to the properties object.

file is the name of the properties file. This file is searched in all elements of the current search path (see set_path()) unless the name starts with a slash.

context can be used to designate an initial context where all properties from the file will be subkeys of.

For the detailed format of properties files see "PROPERTY FILES".

Reading the file is handled by File::LoadLines. See its documentation for more power.

parse_lines lines [ , filename [ , context ] ]

As parse_file, but processes an array of lines.

filename is used for diagnostic purposes only.

context can be used to designate an initial context where all properties from the file will be subkeys of.

set_path paths

Sets a search path for file lookup.

paths must be reference to an array of paths.

Default path is [ '.' ] (current directory).

get_path

Gets the current search path for file lookup.

get_property prop [ , default ]

Get the value for a given property prop.

If a context ctx has been set using set_context('ctx'), get_property('foo.bar') will first try 'ctx.foo.bar' and then 'foo.bar'. get_property('.foo.bar') (note the leading period) will only try 'ctx.foo.bar' and raise an exception if no context was set.

If no value can be found, default is used.

In either case, the resultant value is examined for references to other properties or environment variables. See "PROPERTY FILES" below.

get_property_noexpand prop [ , default ]

This is like get_property, but does not do any expansion.

gps prop [ , default ]

This is like get_property, but raises an exception if no value could be established.

This is probably the best and safest method to use.

get_property_keys prop

Returns an array reference with the names of the (sub)keys for the given property. The names are unqualified, e.g., when properties foo.bar and foo.blech exist, get_property_keys('foo') would return ['bar', 'blech'].

expand value [ , context ]

Perform the expansion as described with get_property.

set_property prop, value

Set the property to the given value.

set_properties prop1 => value1, ...

Add a hash (key/value pairs) of properties to the set of properties.

set_context context

Set the search context. Without argument, clears the current context.

get_context

Get the current search context.

result_in_context

Get the context status of the last search.

Empty means it was found out of context, a string indicates the context in which the result was found, and undef indicates search failure.

data [ start ]

Produces a Perl data structure created from all the properties from a given point in the hierarchy.

Note that since Perl hashes do not have an ordering, this information will get lost. Also, properties can not have both a value and a substructure.

dump [ start [ , stream ] ]

Produces a listing of all properties from a given point in the hierarchy and write it to the stream.

Without stream, returns a string.

In general, stream should be UTF-8 capable.

dumpx [ start [ , stream ] ]

Like dump, but dumps with all values expanded.

package Tokenizer;

sub new { my ( $pkg, $lines ) = @_; bless { _line => "", _token => undef, _lineno => 0, _lines => $lines, } => $pkg; }

sub next { my ( $self ) = @_; while ( $self->{_line} !~ /\S/ && @{$self->{_lines} } ) { $self->{_line} = shift(@{ $self->{_lines} }); $self->{_lineno}++; $self->{_line} = "" if $self->{_line} =~ /^\s*#/; } return $self->{_token} = undef unless $self->{_line} =~ /\S/;

    $self->{_line} =~ s/^\s+//;

    if ( $self->{_line} =~ s/^([\[\]\{\}=:])// ) {
        return $self->{_token} = $1;
    }

    # Double quoted string.
    if ( $self->{_line} =~ s/^ " ((?>[^\\"]*(?:\\.[^\\"]*)*)) " //xs ) {
        return $self->{_token} = qq{"$1"};
    }

    # Single quoted string.
    if ( $self->{_line} =~ s/^ ' ((?>[^\\']*(?:\\.[^\\']*)*)) ' //xs ) {
        return $self->{_token} = qq{'$1'}
    }

    $self->{_line} =~ s/^([^\[\]\{\}=:"'\s]+)//;
    return $self->{_token} = $1;
}

sub token { $_[0]->{_token } } sub lineno { $_[0]->{_lineno } }

PROPERTY FILES

Property files contain definitions for properties. This module uses an augmented version of the properties as used in e.g. Java.

In general, each line of the file defines one property.

    version: 1
    foo.bar = blech
    foo.xxx = yyy
    foo.xxx = "yyy"
    foo.xxx = 'yyy'

The latter three settings for foo.xxx are equivalent.

Whitespace has no significance. A colon : may be used instead of =. Lines that are blank or empty, and lines that start with # are ignored.

Property names consist of one or more identifiers (series of letters and digits) separated by periods.

Valid values are a plain text (whitespace, but not trailing, allowed), a single-quoted string, or a double-quoted string. Single-quoted strings allow embedded single-quotes by escaping them with a backslash \. Double-quoted strings allow common escapes like \n, \t, \7, \x1f and \x{20cd}.

Note that in plain text backslashes are taken literally. The following alternatives yield the same results:

    foo = a'\nb
    foo = 'a\'\nb'
    foo = "a'\\nb"

IMPORTANT: All values are strings. These three are equivalent:

    foo = 1
    foo = "1"
    foo = '1'

and so are these:

    foo = Hello World!
    foo = "Hello World!"
    foo = 'Hello World!'

Quotes are required when you want leading and/or trailing whitespace. Also, the value null is special so if you want to use this as a string it needs to be quoted.

Single quotes defer expansion, see "Expansion" below.

Context

When several properties with a common prefix must be set, they can be grouped in a context:

    foo {
       bar = blech
       xxx = "yyy"
       zzz = 'zyzzy'
    }

Contexts may be nested.

Arrays

When a property has a number of sub-properties with keys that are consecutive numbers starting at 0, it may be considered as an array. This is only relevant when using the data() method to retrieve a Perl data structure from the set of properties.

    list {
       0 = aap
       1 = noot
       2 = mies
    }

When retrieved using data(), this returns the Perl structure

    [ "aap", "noot", "mies" ]

For convenience, arrays can be input in several more concise ways:

    list = [ aap noot mies ]
    list = [ aap
             noot
             mies ]

The opening bracket must be followed by one or more values. This will currently not work:

    list = [
             aap
             noot
             mies ]

Includes

Property files can include other property files:

    include "myprops.prp"

All properties that are read from the file are entered in the current context. E.g.,

    foo {
      include "myprops.prp"
    }

will enter all the properties from the file with an additional foo. prefix.

Expansion

Property values can be anything. The value will be expanded before being assigned to the property unless it is placed between single quotes ''.

Expansion means:

  • A tilde ~ in what looks like a file name will be replaced by the value of ${HOME}.

  • If the value contains ${name}, name is first looked up in the current environment. If an environment variable name can be found, its value is substituted.

    If no suitable environment variable exists, name is looked up as a property and, if it exists and has a non-empty value, this value is substituted.

    Otherwise, the ${name} part is removed.

    Note that if a property is referred as ${.name}, name is looked up in the current context only.

    Important: Property lookup is case insensitive, except for the names of environment variables except on Microsoft Windows where environment variable names are looked up case insensitive.

  • If the value contains ${name:value}, name is looked up as described above. If, however, no suitable value can be found, value is substituted.

Expansion is delayed if single quotes are used around the value.

    x = 1
    a = ${x}
    b = "${x}"
    c = '${x}'
    x = 2

Now a and b will be '1', but c will be '2'.

Substitution is handled by String::Interpolate::Named. See its documentation for more power.

In addition, you can test for a property being defined (not null) by appending a ? to its name.

    result = ${x?|${x|value|empty}|null}

This will yield value if x is not null and not empty, empty if not null and empty, and null if not defined or defined as null.

SEE ALSO

File::LoadLines, String::Interpolate::Named.

BUGS

Although in production for over 25 years, this module is still slightly experimental and subject to change.

AUTHOR

Johan Vromans, <JV at cpan.org>

SUPPORT AND DOCUMENTATION

Development of this module takes place on GitHub: https://github.com/sciurius/perl-Data-Properties.

You can find documentation for this module with the perldoc command.

    perldoc Data::Properties

Please report any bugs or feature requests using the issue tracker on GitHub.

ACKNOWLEDGEMENTS

This module was initially developed in 1994 as part of the Multihouse MH-Doc (later: MMDS) software suite. Multihouse kindly waived copyrights.

In 2002 it was revamped as part of the Compuware OptimalJ development process. Compuware kindly waived copyrights.

In 2020 it was updated to support arrays and released to the general public.

COPYRIGHT & LICENSE

Copyright 1994,2002,2020 Johan Vromans, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.