NAME

Config::Column - simply packages input and output of "config" / "BBS log" file whose records are separated by any delimiter.

SYNOPSIS

# Copy the datalist in a tab separated file to readable formatted text file.

use utf8;
use lib './lib';
use Config::Column;
my $order_delim = [qw(1 subject date value)];
my $order_nodelim = ['' => 1 => ': [' => subject => '] ' => date => ' : ' => value => ''];
my $delimiter = "\t";

# MAIN file instance
my $ccmain = Config::Column->new(
	'mainfile.dat', # the data file path
	'utf8', # character encoding of the data file (PerlIO ":encoding($layer)") or PerlIO layer (ex. ':encoding(utf8)') 
	$order_delim, # list of key names
	$delimiter, # delimiter that separates data column
	1, # first index for data list
	"\0" # delimiter that separates data record ("lines")
);
# SUB file (human readable)
my $ccsub = Config::Column->new(
	'', # this can be empty because data file will be opened externally and file handle is passed to this instance
	'', # same reason
	$order_nodelim, # list of key names and delimiters
	undef, # do not define delimiter
	1, # first index for data list
	# delimiter that separates data record ("lines") is Default
);

# Read data from MAIN file.
my $data = $ccmain->read_data;
# Add new data.
push @$data,{subject => 'YATTA!', date => '2012/03/06T23:33:00+09:00', value => 'All tests passed!'};print $data;
# Write data to MAIN file.
$ccmain->write_data($data);
# Write header to SUB file
open my $fh,'+<:encoding(utf8)','subfile.txt';
flock $fh,2;
truncate $fh,0;
seek $fh,0,0;
print $fh 'Single line diary?',"\n";
# Add data to SUB file. Don't close and don't truncate $fh.
$ccsub->write_data($data,$fh,1,1);
print $fh 'The end of the worl^h^h^h^hfile';
close $fh;

INTRODUCTION

This module generalizes the list of keys and delimiters that is common in "config" / "BBS log" file format and packageizes data list input and output of these files.

It treats data list as simple array reference of hash references.

my $datalist = [
	{}, # If the first index for data list (see below section) is 1, 0th data is empty.
	{title => "hoge",value => "huga"},
	{title => "hoge2",value => "huga"},
	{title => "hoge3",value => "huga"},
];

It manages only IO of that data list format and leaves data list manipulating to basic Perl operation.

DESCRIPTION

Constructor

new()

my $cc = Config::Column->new(
	$datafile, # the data file path
	$layer, # character encoding of the data file (PerlIO ":encoding($layer)") or PerlIO layer (ex. ':encoding(utf8)') 
	$order, # the "order" (see below section) (ARRAY REFERENCE)
	$delimiter, # delimiter that separates data column
	$indexshift, # first index for data list (may be 0 or 1 || can omit, and use 0 as default) (Integer >= 0)
	$linedelimiter # delimiter that separates data record ("lines")(can omit, and use Perl default (may be $/ == "\n"))
);

$indexshift is 0 or 1 in general. For example, if $indexshift == 1, you can get first data record by accessing to $datalist->[1], and $datalist->[0] is empty.

There is two types of definition of $order and $delimiter for 2 following case.

single delimiter (You must define delimiter.)
my $cc = Config::Column->new(
	'./filename.dat', # the data file path
	'utf8', # character encoding of the data file or PerlIO layer
	[qw(1 author id title date summary)], # the "order" [keys]
	"\t", # You MUST define delimiter.
	1, # first index for data list
	"\n" # delimiter that separates data record
);

In this case, "order" is names (hash keys) of each data column.

It is for data formats such as tab/comma separated data.

multiple delimiters (Never define delimiter.)
my $cc = Config::Column->new(
	'./filename.dat', # the data file path
	'utf8', # character encoding of the data file or PerlIO layer
	[qw('' 1 ': ' author "\t" id "\t" title "\t" date "\t" summary)], # [delim key delim key ...]
	undef, # NEVER define delimiter (or omit).
	1, # first index for data list
	"\n" # delimiter that separates data record
);

In this case, "order" is names (hash keys) of each data column and delimiters.

$order's 0,2,4...th (even) elements are delimiter, and 1,3,5...th (odd) elements are names (hash keys).

It is for data formats such as ...

['', 1, ' [', subject, '] : ', date, ' : ', article]
1 [This is the subject] : 2012/02/07 : Article is there. HAHAHA!
2 [Easy to read] : 2012/02/07 : Tab separated data is for only computers.
['', thread_number, '.dat<>', subject, ' (', res_number, ')'] # subject.txt (bracket delimiter is errorous)
1325052927.dat<>Nurupo (988)
1325387590.dat<>OKOTOWARI!!!!!! Part112 [AA] (444)
1321698127.dat<>Marked For Death 18 (159)

Index column

The name "1" in $order means the index of data records.

If the name "1" exists in $order, integer in the index column will be used as array references' index.

$delimiter = "\t";
$order = [1,somedata1,somedata2];

# data file
1	somedata	other
2	foobar	2000
3	hoge	piyo
5	English	isDifficult

 |
 | read_data()
 v

$datalist = [
	{}, # 0
	{somedata1 => 'somedata', somedata2 => 'other'}, # 1
	{somedata1 => 'foobar', somedata2 => '2000'}, # 2
	{somedata1 => 'hoge', somedata2 => 'piyo'}, # 3
	{}, # 4
	{somedata1 => 'English', somedata2 => 'isDifficult'}, # 5
];

 |               ^
 | write_data()  | read_data()
 v               |

# data file
1	somedata	other
2	foobar	2000
3	hoge	piyo
4		
5	English	isDifficult

Methods

add_data_last()

This method adds data records to the data file after previous data in the file. Indexes of these data records are automatically setted by reading the data file before.

$cc->add_data_last($data,$fh,$fhflag);

my $data = {title => "hoge",value => "huga"} || [
	{title => "hoge",value => "huga"},
	{title => "hoge2",value => "huga"},
	{title => "hoge3",value => "huga"},
]; # hash reference of single data record or array reference of hash references of multiple data records
my $fh; # file handle (can omit)
my $fhflag = 1; # if true, file handle will not be closed (can omit)

If you give a file handle to the argument, file that defined by constructor is omitted and this method uses given file handle and adds data from the place current file pointer points not from the head of file.

Return value:

Succeed > first: 1 , second: (if $fhflag is true) file handle

Fail > first: false (return;)

add_data()

This method adds data records to the data file.

$cc->add_data($datalist,$startindex,$fh,$fhflag);

my $datalist = {title => "hoge",value => "huga"} || [
	{title => "hoge",value => "huga"},
	{title => "hoge2",value => "huga"},
	{title => "hoge3",value => "huga"},
]; # hash reference of single data record or array reference of hash references of multiple data records
my $startindex = 12; # first index of the data record (can omit if you don't want index numbers)
my $fh; # file handle (can omit)
my $fhflag = 1; # if true, file handle will not be closed (can omit)

If you give a file handle to the argument, file that defined by constructor is omitted and this method uses given file handle and adds data from the place current file pointer points not from the head of file.

Return value:

Succeed > first: 1 , second: (if $fhflag is true) file handle

Fail > first: false (return;)

write_data()

This method writes data records to the data file. Before writing data, the contents of the data file will be erased.

$cc->write_data($datalist,$fh,$fhflag,$noempty);

my $datalist = [
	{title => "hoge",value => "huga"},
	{title => "hoge2",value => "huga"},
	{title => "hoge3",value => "huga"},
]; # array reference of hash references of multiple data records
my $fh; # file handle (can omit)
my $fhflag = 1; # if true, file handle will not be closed (can omit)
my $noempty = 1; # see below

If you give a file handle to the argument, file that defined by constructor is omitted and this method uses given file handle. If $noempty is true, the contents of the data file will not be erased, and writes data from the place current file pointer points not from the head of file.

Return value:

Succeed > first: 1 , second: (if $fhflag is true) file handle

Fail > first: false (return;)

read_data()

This method reads data records from the data file.

$cc->read_data($fh,$fhflag);

my $fh; # file handle (can omit)
my $fhflag = 1; # if true, file handle will not be closed (can omit)

If you give a file handle to the argument, file that defined by constructor is omitted and this method uses given file handle and reads data from the place current file pointer points not from the head of file.

Return value:

Succeed > first: data list (array reference of hash references) , second: (if $fhflag is true) file handle

Fail > first: false (return;)

read_data_num()

This method reads data record's last index number from the data file.

$cc->read_data_num($fh,$fhflag);

my $fh; # file handle (can omit)
my $fhflag = 1; # if true, file handle will not be closed (can omit)

If you give a file handle to the argument, file that defined by constructor is omitted and this method uses given file handle and reads data from the place current file pointer points not from the head of file.

Return value:

Succeed > first: last index , second: (if $fhflag is true) file handle

Fail > first: false (return;)

DEPENDENCIES

This module requires no other modules and libraries.

NOTES

This module is written in object-oriented style but treating data by naked array or file handle so you should treat data by procedural style.

For example, if you want to delete 3,6 and 8th element in data list completely, the following code will be required.

splice @$datalist,$_,1 for sort {$b <=> $a} qw(3 6 8);

So, if you want more smart OO, it will be better to use another modules that wraps naked array or file handle in OO (such as Object::Array ... etc?), or create Config::Column::OO etc. which inherits this module and can use methods pop, shift, splice, delete, etc.

TODO

Odd Engrish

AUTHOR

Narazaka (http://narazaka.net/)

COPYRIGHT AND LICENSE

Copyright 2011-2012 by Narazaka, all rights reserved.

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