Sullivan Beck

NAME

Set::Files - routines to work with files, each definining a single set

SYNOPSIS

  use Set::Files;
  $Version = $Set::Files::VERSION;

  $obj     = new Set::Files(OPT => VAL, OPT => VAL, ...);

  @set     = $obj->list_sets( [TYPE] );

  @uid     = $obj->owner;
  $uid     = $obj->owner(SET);

  @set     = $obj->owned_by(UID [,TYPE]);

  @ele     = $obj->members(SET);

  $flag    = $obj->is_member(SET, ELE);

  @type    = $obj->list_types( [SET] );

  @dir     = $obj->dir;
  $dir     = $obj->dir(SET);

  %opts    = $obj->opts(SET);
  $val     = $obj->opts(SET,VAR);

  $obj->cache;

  $num     = $obj->add   (SET, FORCE, COMMIT, ELE1,ELE2,...);
  $num     = $obj->remove(SET, ELE1,ELE2,...);

  $obj->commit(SET1,SET2,...);

  $obj->delete(SET);
  $obj->delete(SET,1);

DESCRIPTION

This is a module for working with simple sets of elements where each set is defined in a separate file (one file for each set to be defined).

The advantages of putting each set in a separate file are:

Set managment can be delegated

If all sets are defined in a single file, management of all sets must be done by a single user, or by using a suid program. By putting each set in a separate file, different files can be owned by different users so management of different sets can be delegated.

Set files are a simple format

Because a file consists of a single set only, there is no need to have a complex file format which has to be parsed to get information about the set. As a result, set files can easily be autogenerated or edited with any simple text editor, and errors are less likely to be introduced into the file.

The disadvantages are:

Permissions problems

Some applications may need to read all of the data, but since the different set files may be owned by different people, permissions may get set such that not all set files are readable.

Applications which actually gather all of the data will need to be run as root in order to be reliable. Alternately, some means of enforcing the appropriate permissions needs to be in place.

No central data location

Usually, when you want to define sets, the data ultimately needs to be stored in one central location (which might be a single file or database).

To get around this, a wrapper must be written using this module to copy the data to the central location.

Simple elements only

Many types of sets have elements which have attributes (for example, a ranking within the set or some other attribute). When you start adding attributes, you need a more complex file structure in order to store this information, so that type of set is not addressed with this module. The only attribute that an element has is membership in the set.

Slow data access

Because the data is spread out over several files, each of which must be parsed, and any error checking done, accessing the data can be significantly slower than if the data were stored in a central location.

Features of this module include:

Data caching

This module provides routines for caching the information from all the set files. This can be used to avoid the permissions problems (allowing user run applications access to all cached data) and decrease access time (no parsing is left, and error checking can be done prior to caching the information).

This still requires that a privileged user or suid script be used to update the cache.

Multiple type of sets

Often, it is conveniant to define different types of sets using a single set of files as there may be considerable overlap between the sets of different types.

For example, it might be useful to create files containing sets of users who belong to different committees in a department. Also, there might be sets of users who belong to various departmental mailing lists. One solution is to have two different directories, one with set files with lists of users on the various committees; one with set files with lists of users on each mailing list. Since there might be overlap between these groups, it might be nice to have the two sets of files overlap. For example, some committees may want to have a mailing list associated with the group, others don't want a mailing list, and there may be mailing lists not associated with a committee.

This allows you to have a single file for each set of users, but some sets will have mailing lists, some will be committees, and some will be both.

Set ownership

Since the different files may be owned by different people, operations based on set ownership can be done.

METHODS

The following methods are available:

VERSION
  use Set::Files;
  $Version=$Set::Files::VERSION;

Check the module version.

new
  $obj = new Set::Files(OPT => VAL, OPT => VAL, ...);

This creates a new Set::Files object which reads the appropriate set files (or a cache of the information in set files). The initialization options available are described below.

list_sets
  @set     = $obj->list_sets( [TYPE] );

Returns a list of all defined sets or the sets of the specified type.

owner
  @uid     = $obj->owner;
  $uid     = $obj->owner(SET);

Lists all UIDs who own a set, or the owner of the specified set.

owned_by
  @set     = $obj->owned_by(UID [,TYPE]);

Lists all sets owned by the specified UID (or those of a specific type).

members
  @ele     = $obj->members(SET);

Lists all elements in the specified set.

is_member
  $flag    = $obj->is_member(SET, ELE);

Returns 1 if ELE is a member of SET.

list_types
  @type    = $obj->list_types( [SET] );

A list of all types defined, or the types that the specified set belong to.

dir
  @dir     = $obj->dir;
  $dir     = $obj->dir(SET);

All directories containing set files, or the directory containing the file of the specified set.

opts
  %opts    = $obj->opts(SET);
  $val     = $obj->opts(SET,VAR);

Returns a hash of all options set for a set, or the value of a specific option. If the specific option is not set, 0 is returned.

delete
  $obj->delete($set);
  $obj->delete($set,1);

This removes the specified set file. By default, it renames the set file to .set_files.$set (which are ignored when reading in set data). If the optional second argument is passed in, no backup is made (i.e. the set file is deleted completely).

This method is only available to those who have write access to the directory containing the set file.

cache
  $obj->cache;

This dumps the current set information to a cache file. This method is only valid if the data was read in from files. If it was read in from the cache, this method will fail.

add, remove
  $num = $obj->add   (SET, FORCE, COMMIT, ELE1,ELE2,...);
  $num = $obj->remove(SET, FORCE, COMMIT, ELE1,ELE2,...);

These functions add/remove the specified elements to/from the set.

When adding elements to a set, it is first checked to see if the element is already in the set, and if so, whether it is explicitely excluded in the set file, or comes from some other set file via. an INCLUDE tag.

If the element is not in the set, it is added. If the FORCE flag is true, the element will be added to the set file explicitly if it is already in the set, but only via. an INCLUDE tag. In either case, any OMIT tag which removes this element will be removed from the list.

When removing elements from a set, a similar set of tests are done. If the element is in the set, it is removed from the file (if it appears in the file) AND a OMIT tag is included. If the element does NOT appear in the set, the file is unmodified unless the FORCE flag is true, in which case an OMIT tag is added.

The COMMIT flag is used to determine whether the file should be written out over the existing file. The file can only be written out if data was read from the files. If it was read in from the cache, this will fail.

The return value is the number of changes made to the set.

commit
  $obj->commit(SET1,SET2,...);

Any changes that have been made with the add and remove methods can be written out to the set file(s) with this method. This method is only valid if the data was read in from files. If it was read in from the cache, this method will fail.

INIT OPTIONS

The following options can be passed in to the new method:

path
  path => DIR1:DIR2:...
  path => [ DIR1, DIR2, ... ]

The set files may be stored in one or more different directories. By default, set files are assumed to be in the current directory, but using this option, the directory (or directories) can be explicitely set.

One thing to note. If multiple directories are used, and a file of the same name exists in more than one of the directories, the first one found (in the order that the directories are included in the list) is used. A warning will be issued for files of the same name in other directories, but they will be ignored.

Warnings will be issued for unreadable directories, or unreadable files within a directory.

valid_file
  valid_file => REGEXP
  valid_file => !REGEXP
  valid_file => \&FUNCTION

By default, all files in the directories are used. With this option, filenames are tested and only those that pass will be used. Others will be silently ignored.

REGEXP is a regular expression. Only filenames which match the REGEXP will pass (or if !REGEXP is used, only filenames which do NOT match REGEXP will pass).

If a reference to a function is passed in, the function &FUNCTION(dir,file) will be evaluated for each file. If it returns 0, the file will be silently ignored. Otherwise it will be used.

invalid_quiet
  invalid_quiet = 1

By default, when a file is ignored due to failing a valid_file test, or when an element is ignored due to failing a valid_ele test, a warning is issued. With this option, no warning is issued.

cache
  cache => DIR

Data from the set files may be cached in order to speed up data access. If this option is used, you must specify the directory where the data will be cached. The directory may be the same as one of the directories containing the set files.

The cache directory defaults to the first directory given in the path option (or the current directory if no path option is given).

read
  read => "cache"
  read => "files"
  read => "file"

When an application wants to use data from the set files, they can either read the data from set files or the cache.

If the cache option was used, the default is to read from the cache if it exists, read from the files otherwise. If no cache option was used, the default is to read from the files. When data is read in from the cache, the commit and cache methods are disabled.

If the file option is used, it reads a single set from a single file along with all dependancy sets (i.e. sets that are included or excluded via. the appropriate tags). This allows someone to make changes to a single set file that they own even if permissions are set so that they cannot read other set files. The commit method is available, but the cache method is disabled. The file option requires that the set option also be present.

With the files option, all set files are read. Both the commit and cache methods are enabled.

set
  set => SET

This defines which set to read when the read = file> option is used. This option is required when read = file> and ignored for any other value for read.

types
  types => TYPE
  types => [ TYPE1, TYPE2, ... ]

Sets can be of one or more types (or they can belong to no type and be used solely in building other sets using the INCLUDE or EXCLUDE tags described in the FILE FORMAT section below).

This option can be used to specify the names of the different types of sets defined by these files.

If this option is not given, then there is only one type and by default, all sets belong to it.

default_types
  default_types => [ TYPEa, TYPEb, ... ]
  default_types => "all"
  default_types => "none"
  default_typew => TYPE

Some types of sets may be more common than others, and you may or may not want to have to explicitely define which types a set belong to.

If a list of types are passed in, every type must be defined in the types option (warnings will be issued if they weren't). If a value of "all" is passed in, sets belong to all types by default. If a value of "none" is passed in, sets don't belong to any type by default.

By default, sets belong to all types available.

comment
  comment => REGEXP

This defines a regular expression used to recognize (and strip out) comments from a set file. The default expression is "#.*" which means that all characters from a pound sign to the end of the line are removed.

If REGEXP is passed in as an empty string, there are no comments. All lines are either empty or contain an element.

tagchars
  tagchars => STRING

This defines a character (or a string) which marks a line of the set file as containing a tag. The default value is "@".

valid_ele
  valid_ele => REGEXP
  valid_ele => !REGEXP
  valid_ele => \&FUNCTION

By default, every non-blank line (after comments have been stripped out) is treated as an element. If this option is used, elements are tested, and only those that pass the test are treated as valid. Others are invalid and produce a warning.

If a reference to a function is passed in, the function &FUNCTION(set,ele) will be evaluated for each element. If it returns 0, the element will be silently ignored. Otherwise it will be included in the set.

scratch
  scratch => DIR

When automatically updating a set file, the directory where the files live may or may not be writable by a user who owns a set file.

If the directory is writable by the user, there is no problem. In this case, when a new set file is written, the old one is backed up and the new one written in it's place.

If the directory is NOT writable by the user, the old copy is backed up to the scratch directory. This directory must be writable by the user. It defaults to /tmp.

FILE FORMAT

A set file has a very simple format. It consists of blank lines, tags, and elements. Comments may be included as whole lines or part of one of the above lines.

Each line is checked for comments and they are removed before any other processing is done. A comment is anything that matches a regular expression which can be set using the comment Init option. The default regular expression is "#.*" which means that comments start with a pound sign anywhere on the line and go to the end of the line.

Tags are lines which begin with begin with a special string (which can be set with the tagchars Init option. The default string is "@". Tag lines are of one of the formats:

  @TAG
  @TAG VAL1,VAL2,...

All other lines are elements. Elements are any string (one per line).

Leading/trailing spaces are ignored in all cases.

The set name is the name of the set file.

The following TAGs are known:

INCLUDE SET1,SET2,...

This includes all members of one or more other sets in the current set.

EXCLUDE SET1,SET2,...

This excludes all members of one or more other sets from the current set. This overrides any members included from other sets, but does NOT exclude members explicitely included in the set file.

OMIT ELE

This exludes a specific element from the current set. This overrides any elements included via. an INCLUDE tag, or any elements explicitly included in the set file.

Each element must be specified separately since there is no guarantee that elements may not contain commas.

TYPE TYPE1,TYPE2,...

The default types that this set belongs to are determined by the types and default_types Init options.

This tag explicitely puts this set if the specified types, even if it is not in those types of default.

NOTYPE TYPE1,TYPE2,....

Similar to the TYPE tag, but this tag explicitely removes the set from the specified types, even if it is in them by default.

OPTION VARIABLE [= VALUE]

Although there is no support for element specific attributes, there IS support for attributes which apply to the entire set (and which can be made available to applications using these sets).

Each set may have a hash associated with with key/value pairs (if no value is include, it defaults to 1). These attributes are available using the info method.

All tag lines can be repeated any number of times, so:

  @INCLUDE foo,bar

is equivalent to

  @INCLUDE foo
  @INCLUDE bar

All tags are case insensitive.

When determining the members of a set which includes and excludes other sets, or omits specific elements from the set, all inclusions are evaluted first, followed by all exclusions (i.e. all exclusions override all inclusions). If there is a cyclic dependancy (i.e. A depends on B depends on A where a dependancy can either be an INCLUDE or EXCLUDE), an error is reported and the cyclic dependancy is ignored.

A few examples illustrate the use of INCLUDE, EXCLUDE, and OMIT tags. In the examples, the set file A contains the elements: E1, E2, E3. The set file B contains the elements: E3, E4, E5. The set file contains the following lines:

  @INCLUDE A
  @EXCLUDE B
  E5
  E6

defines a set contains the elements: E1, E2, E5, E6. The first line includes E1, E2, E3. The second line excludes E3. It does NOT exclude E5 since the EXCLUDE tag does not override elements explicitly included in the set file. Finally, the E5 and E6 elements are added.

The set file containing the following lines:

  @INCLUDE A
  @EXCLUDE B
  @OMIT    E2
  @OMIT    E6
  E5
  E6

defines a set contains the elements: E1, E5. This is similar to the above example, except that the OMIT tags override elements included via. the INCLUDE tag AND elements explicitly included in the set file.

FILES

Several files are used by the Set::Files module. They all live in the directory set by the cache Init Option except for set specific files which live in the same directory as the set file. Files are:

.set_files.SET

A backup of the given set. When a set file is updated, the original file is stored in this file. The file is stored either in the same directory as the set file (if it is writable) or in the directory specified by the scratch Init Option.

.set_files.SET.new

A temporary file where a new set file (or the update to an old one) is written. Once completed, this file is moved into place as the new set file. This file lives in the same directory as the set file or in the scratch directory.

.set_files.cache

The file containing the cache. This is created using the cache method.

.set_files.template

When creating a new set file (or updating an existing one), this file is used (if it exists) as a starting point and then all the data is appended to it. This is a good place to store comments describin how to edit the set files, etc., that set file maintainers can read for help.

KNOWN PROBLEMS

None at this point.

LICENSE

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

AUTHOR

Sullivan Beck (sbeck@cpan.org)