CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Beginner:
Parsing CSV file - Add Concatenated field/sort

 

First page Previous page 1 2 3 Next page Last page  View All


BigRedEO
Novice

Mar 21, 2016, 1:21 PM

Post #1 of 53 (12148 views)
Parsing CSV file - Add Concatenated field/sort Can't Post

I'm being thrown back into Perl for the first time in 9 years, so I'm considering myself a "beginner" again. I have a .csv file which I need to manipulate. Each line starts with three fields. I need to append a new field to the front of each line that is a concatenation of the first three fields, i.e. if each line starts with

$field1 $field2 $field3

I need to add CONCAT($field1, $field2 $field3) to the beginning of each line/record. To be honest, not a clue where to start with that.

Then I also need to sort the entire file in order of occurence by "init-tran-date", "update-tran-date" and "update-tran-time" which are at spread out locations in each 40 field line/record. Would it be easier to put the fields into an array and sort based on their index location?

I at least remember some of the basics from the old days! Hope it still holds true -


Code
#!/usr/bin/perl/ 

$filename = '/mypath/myfile.csv';
open(FILE, $filename) or die "Could not read from $filename, program halting.";
while (<FILE>)
{
chomp;
($field1, $field2, $field3, etc) = split(","); #or array?

#sort
print;
}

close (FILE);
exit;



FishMonger
Veteran / Moderator

Mar 21, 2016, 6:21 PM

Post #2 of 53 (12140 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

Lets start at the beginning. Every perl script should begin by loading the strict and warnings pragmas.

Code
#!/usr/bin/perl 

use strict;
use warnings;

The strict pragma will require you to declare your vars, which is done with the my keyword.

Code
my $filename = '/mypath/myfile.csv';


For the reading/parsing/writing of the csv file, use the Text::CSV_XS module. http://search.cpan.org/~hmbrand/Text-CSV_XS-1.22/CSV_XS.pm


Quote
I need to add CONCAT($field1, $field2 $field3) to the beginning of each line/record. To be honest, not a clue where to start with that.

There several ways to concatenate the fields. You could simply make a double quoted string with the 3 fields, or you could use the join function. Then use the unshift function to add that string to the beginning of the array.

Your multi field sorting requirement can be done by using the sort function. The documentation on the sort function gives a couple examples and a simple google search will provide you with more extensive examples.


(This post was edited by FishMonger on Mar 21, 2016, 6:23 PM)


Laurent_R
Veteran / Moderator

Mar 22, 2016, 12:41 AM

Post #3 of 53 (12125 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

I agree with everything Fishmonger has said.

Just a small additional comment:


In Reply To

Code
 ($field1, $field2, $field3, etc) = split(","); #or array? 

}



You may do that to handle your line (for example for putting the three fields at the beginning), but in the end, for the sorting in accordance with those fields you need to use, you'll need each line to be in an array, and, therefore, the file to be an array or arrays. I would personally go for an array immediately.


BigRedEO
Novice

Mar 22, 2016, 5:51 AM

Post #4 of 53 (12122 views)
Re: [Laurent_R] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

Thank you both for getting me started on this!


FishMonger
Veteran / Moderator

Mar 22, 2016, 6:59 AM

Post #5 of 53 (12118 views)
Re: [Laurent_R] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

Your comment implies slurping the data into an array before doing anything with it. If the csv data is simple and doesn't have any complexities such as embedded comma's or some fields with quotes and some without , then I'd probably agree. However, if the fields aren't that simple, then I'd use the module loop over the data and build the array.


(This post was edited by FishMonger on Mar 22, 2016, 7:02 AM)


BigRedEO
Novice

Mar 22, 2016, 1:19 PM

Post #6 of 53 (12107 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

In looking at what I need to really do here (I'm parsing this file with over 2 million records), I've realized I actually need to add the concatenated field to the beginning of each record first (that will become a primary key for a MySQL table), then sort it by that new primary key field at the beginning of each record, THEN a subsort by init-date, update-date, update time, and write it all back to the file.

This file will then be used to load a MySQL table and the program that handles the load needs the records in that sorted order so that it deletes all but the most recent record for each key.

The date fields are in MM/DD/YYYY format so I guess I could use a date sort, but the last key needs to be based on the update-date/update-time combination.


FishMonger
Veteran / Moderator

Mar 22, 2016, 1:24 PM

Post #7 of 53 (12103 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

Before you get too far in the process, you should take a look at the DBD::CSV module.
http://search.cpan.org/~hmbrand/DBD-CSV-0.48/lib/DBD/CSV.pm


FishMonger
Veteran / Moderator

Mar 22, 2016, 1:36 PM

Post #8 of 53 (12096 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post


Quote
I've realized I actually need to add the concatenated field to the beginning of each record first (that will become a primary key for a MySQL table)

That sounds like an odd design and it sounds like the DB isn't normalized.


Quote
The date fields are in MM/DD/YYYY format so I guess I could use a date sort, but the last key needs to be based on the update-date/update-time combination.

That's not the format used for a mysql date field. You'll want to reformat that before inserting into the DB.

There's no need to presort the data before inserting into the DB. The sorting should be done when you do your select statements.


(This post was edited by FishMonger on Mar 22, 2016, 1:44 PM)


BigRedEO
Novice

Mar 23, 2016, 6:05 AM

Post #9 of 53 (12081 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

A script that someone else has written will be used to load the table - it has a built-in "update" function that overwrites a record if the key already exists, hence it's need to be "pre-sorted." Only the most recent record should be loaded into the MySQL table.

I went to your above link, but most of the language there is a bit above me.


FishMonger
Veteran / Moderator

Mar 23, 2016, 6:42 AM

Post #10 of 53 (12075 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

The DBD::CSV module allows you to access the csv file via sql statements as if it was an actual database. Using an sql statement should allow you to do the 3 field concat and multi field sorting in one step.


BigRedEO
Novice

Mar 23, 2016, 1:24 PM

Post #11 of 53 (12070 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

Trying to just get a simple start and test whether I can open/read a file. I put just the first four lines of the file into a test.csv, then wanted to make sure I could get Perl to open and read the file. I was expecting it to show me just the first field of each record. Instead, it's showing me the entire first line/record and that's it. What am I doing wrong here?


Code
#!/usr/bin/perl/ 

use strict;
use warnings;

my $filename = '/swpkg/shared/batch_processing/mistints/test.csv';
open(FILE, $filename) or die "Could not read from $filename, program halting.";

# Read the header line.
my $line = <FILE>;

# Display the header, just to check things are working.
print($line);

# Read the lines one by one.
while($line = <FILE>)

#split the fields, concatenate the first three fields, add to the beginning of each line in the file
{
chomp($line);

my @values = split(',', $line);

print ($values[0]);

}

close (FILE);
exit;



FishMonger
Veteran / Moderator

Mar 23, 2016, 2:11 PM

Post #12 of 53 (12066 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

That should work, but could stand some improvements.

Can you post (as an attachment) your test script so I can run a test?


Laurent_R
Veteran / Moderator

Mar 24, 2016, 3:14 AM

Post #13 of 53 (12051 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post


In Reply To
I was expecting it to show me just the first field of each record. Instead, it's showing me the entire first line/record and that's it. What am I doing wrong here?


In theory, your code should work and do what you want.

When you say "the entire first line/record", do you mean the header line which you are processing separately, or the next line?

Since the code seems correct to me (there may be some improvements, but it should work), please show your input file, so that we can try to figure out why: 1. it reads apparently only the first record; 2. it apparently does not split the record into fields.


BigRedEO
Novice

Mar 24, 2016, 6:36 AM

Post #14 of 53 (12038 views)
Re: [Laurent_R] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

All of a sudden this morning, it's working. Don't ask me why. My guess is the VI editor I'm having to use may be leaving in characters I'm not seeing. I usually try to edit the script in a simple text file, then copy it into the VI editor for the server I'm working on and I re-copied it this morning after deleting the script first, so my guess is there was an hidden character I wasn't seeing.

The one thing I wasn't expecting was how it displayed the fields from each record. I expected to see:

1334
2525
1350

But I get

133425251350

What do I need to add to show each record on it's own line? I am attaching my test CSV file.

This is at least helping me to get a better idea of how the data is manipulated and I'm hoping will get me closer to how to add the new field to the front of each record.

Thank you!
Attachments: test.csv (1.40 KB)


FishMonger
Veteran / Moderator

Mar 24, 2016, 7:01 AM

Post #15 of 53 (12033 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

The output was put all on 1 line because your print statement is missing the \n new line character.

Here's a slightly adjusted/improved version.

Code
#!/usr/bin/perl/  

use strict;
use warnings;

my $filename = '/swpkg/shared/batch_processing/mistints/test.csv';
$filename = 'test.csv';

open my $FH, $filename
or die "Could not read from $filename <$!>, program halting.";

# Read the header line.
my $line = <$FH>;

# Display the header, just to check things are working.
print $line, $/;

# Read the lines one by one.
while($line = <$FH>) {

# split the fields, concatenate the first three fields,
# and add it to the beginning of each line in the file
chomp($line);
my @fields = split(/,/, $line);
unshift @fields, join '_', @fields[0..2];
print $fields[0], $/;
}

close $FH;
exit;


Which outputs:

Quote
c:\test>BigRedEO.pl
STORE_NBR,CONTROL_NBR,LINE_NBR,SALES_NBR,QTY_MISTINT,REASON_CODE,MISTINT_COMM,SZ_CDE,TINTER_MODEL,TINTER_SERL_NBR,SPECTRO_MODEL,SPECTRO_SERL_NBR,EMP_NBR,TRAN_DATE,TRAN_TIME,CDS_ADL_FLD,PROD_NBR,PALETTE,COLOR_ID,INIT_TRAN_DATE,GALLONS_MISTINTED,UPDATE_EMP_NBR,UPDATE_TRAN_DATE,GALLONS,FORM_SOURCE,UPDATE_TRAN_TIME,SOURCE_IND,CANCEL_DATE,COLOR_TYPE,CANCEL_EMP_NBR,NEED_EXTRACTED,MISTINT_MQ_XTR,DATA_SOURCE,GUID,QUEUE_NAME,BROKER_NAME,MESSAGE_ID,PUT_TIME,CREATED_TS

1334_53927_1
2525_67087_1
1350_163689_1



(This post was edited by FishMonger on Mar 24, 2016, 7:05 AM)


BigRedEO
Novice

Mar 24, 2016, 7:13 AM

Post #16 of 53 (12028 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

Thank you!

If I want it without the underscore, would it just be

Code
unshift @fields, join @fields[0..2];

And I will look up unshift on my own!


FishMonger
Veteran / Moderator

Mar 24, 2016, 7:32 AM

Post #17 of 53 (12024 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

The join function requires 2 arguments, so if you don't want the underscore use an empty string.

Code
unshift @fields, join '', @fields[0..2];



(This post was edited by FishMonger on Mar 24, 2016, 7:32 AM)


BigRedEO
Novice

Mar 24, 2016, 8:16 AM

Post #18 of 53 (12017 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

The boss has decided he wants the underscore, so I've put it back in and added these two lines -


Code
    $" = ","; 
print "@fields\n" ;


so I can see the completed records and with the comma back in as a separator.

Now I just have to figure out the sort on the "init-tran-date", "update-tran-date" and "update-tran-time" - and how to write it TO a file and I should be able to start loading my MySQL table!

Thank you again. (always fun to be thrown back into a language you haven't touched for 8 years and asked to try and have it done before you leave for a vacation)


FishMonger
Veteran / Moderator

Mar 24, 2016, 8:21 AM

Post #19 of 53 (12014 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

You'll want to use the Schwartzian transform sorting idiom.
https://en.wikipedia.org/wiki/Schwartzian_transform
https://www.google.com/search?q=perl+schwartzian+transform&spell=1&sa=X&ved=0ahUKEwi12_fGz9nLAhUH52MKHdcXDIIQvwUIGigA&biw=1920&bih=977


BigRedEO
Novice

Mar 24, 2016, 10:20 AM

Post #20 of 53 (12009 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

This sorting has my head spinning so much.

Much of the sorting language is over my head - I understand the cmp commands in the sorts, but the mapping has me lost. I need to sort on init-tran-date, update-tran-date and update-tran-time because the latter two fields could be empty (if the initial record never had an update to it). And the .csv file needs to be sorted because it will also be used with a sqlBatch60 Java program (over which I have no control - the data just needs to already be sorted to work with that already written sqlBatch60 program).

I think I'd rather load a MySQL table with what I have now, SELECT * with a sort on those three fields, and output that back into a new .csv file.


FishMonger
Veteran / Moderator

Mar 24, 2016, 1:31 PM

Post #21 of 53 (12000 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

See if this sorting test does what you want.

Code
#!/usr/bin/perl/  

use strict;
use warnings;
use Data::Dumper;

my $filename = '/swpkg/shared/batch_processing/mistints/test.csv';
$filename = 'test.csv';

open my $FH, $filename
or die "Could not read from $filename <$!>, program halting.";

# Read the header line.
chomp(my $line = <$FH>);
my @fields = split(/,/, $line);
print "Field Names:\n", Dumper(@fields), $/;

my @data;
# Read the lines one by one.
while($line = <$FH>) {

# split the fields, concatenate the first three fields,
# and add it to the beginning of each line in the file
chomp($line);
my @fields = split(/,/, $line);
unshift @fields, join '_', @fields[0..2];
push @data, \@fields;
}
close $FH;
print "Unsorted:\n", Dumper(@data), $/;

@data = sort {
$a->[0] cmp $b->[0] ||
$a->[20] cmp $b->[20] ||
$a->[23] cmp $b->[23] ||
$a->[26] cmp $b-> [26]
} @data;

print "Sorted:\n", Dumper(@data);

exit;



(This post was edited by FishMonger on Mar 24, 2016, 1:41 PM)


FishMonger
Veteran / Moderator

Mar 24, 2016, 2:48 PM

Post #22 of 53 (11994 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

Here's an example using sql statement to read, parse and sort the csv file. I didn't include the concatenating of the 3 fields, but that can be added.

Code
#!/usr/bin/perl 

use strict;
use warnings FATAL => 'all';
use DBI;
use DBD::CSV;
use Text::CSV_XS;
use Data::Dumper;

my $dbh = DBI->connect("dbi:CSV:", undef, undef, {
RaiseError => 1,
f_dir => 'c:/test',
csv_eol => "\n",
csv_class => 'Text::CSV_XS',
csv_tables => {
test => { f_file => 'test.csv'}
},
});

my $sth = $dbh->prepare("SELECT *
FROM test
ORDER by INIT_TRAN_DATE,
UPDATE_TRAN_DATE,
UPDATE_TRAN_TIME");
$sth->execute;

while (my $row = $sth->fetch) {
print Dumper $row;
#print join(',', @$row), $/;
}
$dbh->disconnect;


Output:
c:\test>BigRedEO_v2.pl

Code
$VAR1 = [ 
'2525',
'67087',
'1',
'650462328',
'1',
'4',
'Tinted Wrong Product',
'14',
'IFC 8012NP',
'Standalone-5',
'',
'',
'11',
'10/23/2015',
'104314',
'',
'A91W00353',
'',
'',
'10/20/2015',
'0.25',
'0',
'',
'0.25',
'',
'',
'COMP',
'',
'CUSTOM MATCH',
'0',
'TRUE',
'TRUE',
'O',
'1AC5D8742D47435EA05343D57372AD32',
'POS.MISTINT.V0000.UP.Q',
'PROD_SMISC_BK',
'414D512050524F445F504F533235323531C2295605350020',
'10/23/2015 10:46',
'10/23/2015 10:47'
];
$VAR1 = [
'1334',
'53927',
'1',
'100551589',
'1',
'6',
'Bad Shercolor Match',
'16',
'IFC 8112NP',
'01DX8005513',
'',
'',
'77',
'10/23/2015',
'95816',
'',
'OV0020001',
'',
'MANUAL',
'10/21/2015',
'1',
'0',
'',
'1',
'MAN',
'',
'CUST',
'',
'CUSTOM MATCH',
'0',
'TRUE',
'TRUE',
'O',
'5394A0E67FFF4D01A0D9AD16FA29ABB1',
'POS.MISTINT.V0000.UP.Q',
'PROD_SMISC_BK',
'414D512050524F445F504F533133333464EB2956052C0020',
'10/23/2015 10:45',
'10/23/2015 10:45'
];
$VAR1 = [
'1350',
'163689',
'1',
'650462302',
'1',
'3',
'Tinted Wrong Color',
'14',
'IFC 8012NP',
'06DX8006805',
'',
'',
'1',
'10/23/2015',
'104907',
'',
'A91W00351',
'COLOR',
'6233',
'10/23/2015',
'0.25',
'0',
'',
'0.5',
'ENG',
'',
'SW',
'',
'PALETTE',
'0',
'TRUE',
'TRUE',
'O',
'F1A072BCC548412FA22052698B5B0C28',
'POS.MISTINT.V0000.UP.Q',
'PROD_SMISC_BK',
'414D512050524F445F504F53313335307BC12956053C0020',
'10/23/2015 10:52',
'10/23/2015 10:52'
];



BigRedEO
Novice

Mar 28, 2016, 5:32 AM

Post #23 of 53 (11930 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

I tested this Perl script (which is great, thank you), but ran into one weird little hiccup with the last variable - here was the output (just the first two results) -

Code
Field Names: 
$VAR1 = 'STORE_NBR';
$VAR2 = 'CONTROL_NBR';
$VAR3 = 'LINE_NBR';
$VAR4 = 'SALES_NBR';
$VAR5 = 'QTY_MISTINT';
$VAR6 = 'REASON_CODE';
$VAR7 = 'MISTINT_COMM';
$VAR8 = 'SZ_CDE';
$VAR9 = 'TINTER_MODEL';
$VAR10 = 'TINTER_SERL_NBR';
$VAR11 = 'SPECTRO_MODEL';
$VAR12 = 'SPECTRO_SERL_NBR';
$VAR13 = 'EMP_NBR';
$VAR14 = 'TRAN_DATE';
$VAR15 = 'TRAN_TIME';
$VAR16 = 'CDS_ADL_FLD';
$VAR17 = 'PROD_NBR';
$VAR18 = 'PALETTE';
$VAR19 = 'COLOR_ID';
$VAR20 = 'INIT_TRAN_DATE';
$VAR21 = 'GALLONS_MISTINTED';
$VAR22 = 'UPDATE_EMP_NBR';
$VAR23 = 'UPDATE_TRAN_DATE';
$VAR24 = 'GALLONS';
$VAR25 = 'FORM_SOURCE';
$VAR26 = 'UPDATE_TRAN_TIME';
$VAR27 = 'SOURCE_IND';
$VAR28 = 'CANCEL_DATE';
$VAR29 = 'COLOR_TYPE';
$VAR30 = 'CANCEL_EMP_NBR';
$VAR31 = 'NEED_EXTRACTED';
$VAR32 = 'MISTINT_MQ_XTR';
$VAR33 = 'DATA_SOURCE';
$VAR34 = 'GUID';
$VAR35 = 'QUEUE_NAME';
$VAR36 = 'BROKER_NAME';
$VAR37 = 'MESSAGE_ID';
$VAR38 = 'PUT_TIME';
';AR39 = 'CREATED_TS

Unsorted:
$VAR1 = [
'1334_53927_1',
'1334',
'53927',
'1',
'100551589',
'1',
'6',
'Bad Shercolor Match',
'16',
'IFC 8112NP',
'01DX8005513',
'',
'',
'77',
'10/23/2015',
'95816',
'',
'OV0020001',
'',
'MANUAL',
'10/21/2015',
'1',
'0',
'',
'1',
'MAN',
'',
'CUST',
'',
'CUSTOM MATCH',
'0',
'TRUE',
'TRUE',
'O',
'5394A0E67FFF4D01A0D9AD16FA29ABB1',
'POS.MISTINT.V0000.UP.Q',
'PROD_SMISC_BK',
'414D512050524F445F504F533133333464EB2956052C0020',
'10/23/2015 10:45',
' '10/23/2015 10:45
];



BigRedEO
Novice

Mar 28, 2016, 8:10 AM

Post #24 of 53 (11926 views)
Re: [FishMonger] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

And just to make sure something didn't copy strangely with the VI editor, here is the code I used -

Code
#!/usr/bin/perl/ 

use strict;
use warnings;
use Data::Dumper;

my $filename = '/swpkg/shared/batch_processing/mistints/test.csv';
$filename = 'test.csv';

open my $FH, $filename
or die "Could not read from $filename <$!>, program halting.";

# Read the header line.
chomp(my $line = <$FH>);
my @fields = split(/,/, $line);
print "Field Names:\n", Dumper(@fields), $/;

my @data;
# Read the lines one by one.
while($line = <$FH>) {

# split the fields, concatenate the first three fields,
# and add it to the beginning of each line in the file
chomp($line);
my @fields = split(/,/, $line);
unshift @fields, join '_', @fields[0..2];
push @data, \@fields;
}
close $FH;
print "Unsorted:\n", Dumper(@data), $/;

@data = sort {
$a->[0] cmp $b->[0] ||
$a->[20] cmp $b->[20] ||
$a->[23] cmp $b->[23] ||
$a->[26] cmp $b-> [26]
} @data;

print "Sorted:\n", Dumper(@data);

exit;



FishMonger
Veteran / Moderator

Mar 28, 2016, 8:40 AM

Post #25 of 53 (11921 views)
Re: [BigRedEO] Parsing CSV file - Add Concatenated field/sort [In reply to] Can't Post

I'm unable to duplicate that problem using the sample data you provided. Maybe the test.csv file you tested is different from what you posted.

Have you tried the other version using DBI and DBD::CSV?

Here's the adjustment it needs to add the concatenated fields.

Code
my $sth = $dbh->prepare("SELECT * 
FROM test
ORDER by STORE_NBR,
CONTROL_NBR,
LINE_NBR,
INIT_TRAN_DATE,
UPDATE_TRAN_DATE,
UPDATE_TRAN_TIME");
$sth->execute;

while (my $row = $sth->fetch) {
my @row = @$row;
unshift @row, join '_', @row[0..2];
print Dumper \@row;
#print join(',', @row), $/;
}



(This post was edited by FishMonger on Mar 28, 2016, 8:48 AM)

First page Previous page 1 2 3 Next page Last page  View All
 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives