infinitloop
infinitloop

Reputation: 3011

How do I do this file manipulation in perl?

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

Answers (3)

user554546
user554546

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

Nim
Nim

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

Ether
Ether

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

Related Questions