Reputation: 3011
so my file looks like this:
--some comments--
--a couple of lines of header info--
comp:
name: some_name_A
type: some_type
id: an id_1
owner: who owns it
path: path_A to more data
end_comp
comp:
name: some_name_B
type: some_type
id: an id_2
owner: who owns it
path: path_B to more data
end_comp
What I want to do: Get the name from the name field and see if it matches one of the names we want to search (already provided in an array), after that get the path, go to that path, do some perforce stuff and obtain new id, then replace the current id with the new id, only if it is different then current one.
What I have done (just a pseudo):
@filedata = <read_file> #read file in an array
$names_to_search = join("|", @some_names);
while(lines=@filedata)
{
if( $line =~ /comp:/ )
{
$line = <next line>;
if( $line =~ /name: $names_to_search/ )
{
#loop until we find the id
#remember this index since we need to change this id
#loop until we find the path field
#get the path, go to that path, do some perforce commands and obtain new id
if( id is same as current id ) no action required
else replace current id with new id
}
}
}
The problem: My current implementation has like three while loops! Is there a better/efficient/elegant way of doing this?
Upvotes: 1
Views: 125
Reputation:
Since no two blocks can have the same name
value, you can use a hash reference of hash references:
{
"name1"=>{type=>"type1",id=>"id1",owner=>"owner1",path=>"path1"},
"name2"=>{type=>"type2",id=>"id2",owner=>"owner2",path=>"path2"},
#etc
}
Something like this should work (Warning: Untested):
use strict;
use warnings;
open(my $read,"<","input_file.txt") or die $!;
my $data={};
my $current_name=""; #Placeholder for the name that we're currently using.
while(<$read>)
{
chomp; #get rid of trailing newline character.
if(/^\s*name:\s*([\w]+)\s*$/) #If we hit a line specifying a name,
#then this is the name we're working with
{
$current_name=$1;
}
elsif(/^\s*(type|id|owner|path):\s*([\w]+)\s*$/) #If it's data to go with the name,
#then assign it.
{
$data->{$current_name}->{$1}=$2;
}
}
close($read);
#Now you can search your given array for each of the names and do what you want from there.
However, if you can, I'd really recommend storing the data in your file in some kind of standardized format (YAML, INI, JSON, XML, etc) and then parsing it appropriately. I should also add that this code depends on each name
appearing before the corresponding type
, id
, owner
and path
.
Upvotes: 1
Reputation: 33655
Here is some pseudocode:
index = 0;
index_of_id = 0; // this is the index of the line that contains the current company id
have_company = false; // track whether we are processing a copmany
while (line in @filedata)
{
if (!have_company)
{
if (line is not "company")
{
++index;
continue;
}
else
{
index_of_id = 0;
have_company = true;
}
}
else
{
if (line is "end_comp")
{
have_company = false; // force to start looking for new company
++index;
continue;
}
if (line is "id")
index_of_id = index; // save the index
if (line is "path")
{
// do your stuff then replace the string at the index given by index_of_id
}
}
// line index
++index;
}
// Now write the modified array to file
Upvotes: 1
Reputation: 53994
You've written a config file in a custom format, and then attempting to parse it manually. Instead, why not write the file in an established format such as YAML or INI, and then use existing modules to parse it?
For example, using YAML:
use YAML::Any;
my @data = YAML::Any::LoadFile($filename) or die "Could not read from $filename: $!":
# now you have your data structure in @data; parse it using while/for/map loops.
You can read INI files with Config::INI or Config::INI::Simple.
Upvotes: 4