former33t has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks,

I am writing a routine to route files around. All my files will come in to a central directory that my process will check. Suppose for the sake of argument that the files are MP3’s with IDv3 tags. I already have a package written that examines the new file and grabs all the relevant tag data from it. I get lots of MP3’s because I’m a real music enthusiast, but I’m also a neat freak. The names of the incoming files aren’t intuitive at all and tell me nothing about the actual music. Because I’m an organization freak, I need to move these files from the incoming directory to an appropriate holding directory. This is simple enough, but some music only needs to be filed by artist while some might be more complicated (like a folder for all orchestra not performed by Mozart). Also suppose I’m rather schizophrenic and may need to change my filing scheme at any time to adjust to a new whim in music. I want to put someone else in charge of my filing, but they don’t know Perl at all. By the way, when the filing scheme changes, it will only affect new incoming music. No sense in re-filing old music.

Right now I have hardcoded an if/elsif/elsif routine to sort my music based on the criteria I have selected. The most specific matches are placed on top and finally there is a catchall at the bottom. If a new filing scheme needs to be implemented, I need to change the code. What I would like to do is set up a configuration file I can parse to get rule sets. That way, the person maintaining my filing system only needs to know how to manipulate the configuration file.

I hacked something together that looks like this: RULE = artist eq “Garth Brooks” and album ne “The Hits” to /music/GarthOther

The code parses each rule in the configuration file and breaks rule up into criteria/destination combinations. What I am having trouble doing is this getting the “eq” and “ne” assigned to a variable. Additionally, the “and” and “or” statements will probably prove problematic. In the above rule, I would parse the statement out to look like:

if ($param1 $cond1 $value1 $join $param2 $cond2 $value2) { return true; } Where: $param1 = $artist (variable populated by my tag reading module). $cond1 = eq $value1 = “Garth Brooks” $join = && $param2 = $album $cond2 = ne $value2 = “The Hits”

The above code chokes, because it expects some conditionals, not just a bunch of variables.

Ideally I’d like to make this work out to an arbitrary rule length (any number of conditions). I thought of checking to see what my conditional was going to be and sending to one of a couple of subroutines. It seems like there would be a better way.

Also, any suggestions on how to easily deal with parenthesis in the rules (like the rule below)? If I go the route of parsing the rule into a giant ‘if’ statement as above (if someone can tell me how to get the conditionals into variables) this seems pretty trivial.
RULE = (genre eq “Rap” or genre eq “HipHop”) and artist ne “RunDMC” to /music/Other

One idea I had (although this seemed bad) was to separate this into two actual scripts. The rule parsing script would be called first it would write a new package based on the configuration file every time it was invoked. Once it had written the new package (I could write out all my complicated if/elsif/elsif code that way), it would exec the filing script that used the newly written package. This seemed needlessly complicated, but I might do this if nobody else has a better idea.

Thanks in advance for ideas.

Replies are listed 'Best First'.
Re: file routing
by zentara (Cardinal) on Nov 23, 2006 at 17:25 UTC
    See dialup spam removal with Net::POP3 and look at the improvements by aristotle. You can setup big pre-compiled regexes based on lists of criteria. It uses qr/ / (quoted regex) to make a hash of regexes to test against, then the file is handled accordingly.

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
Re: file routing
by shmem (Chancellor) on Nov 24, 2006 at 10:31 UTC
    I can think of two ways to go. One is writing a parser for your rule language. You have operators, precedence and grouping, and you'll learn a lot by doing that brain excercise.

    The quick way - but maybe not the best: reformat your rules on the fly until they are valid perl, and eval them (you wrote "write a new package" which you would then use, but that's just the same - the underlying inclusion mechanism is a string eval). Roughly something like this:

    my %hash = ( genre => 'Rap', artist => 'Mozart', ); sub genre () { $hash {'genre'} } sub artist () { $hash {'artist'} } sub album () { $hash {'album'} } my $file = 'some_file.mp3'; $_ = "RULE = (genre eq Rap or genre eq HipHop) and artist ne RunDMC to + /music/Other"; if(s/^RULE\s*=\s*//) { my ($cond, $target) = split /\s+to\s+/, $_; eval $cond and print "rename $file, $target/$file\n"; }

    Of course you must make sure your rules don't look like

    RULE = (genre eq Rap or genre eq HipHop) and artist ne RunDMC to `rm - +r /music/Other`
    i.e. you will have to sanitize your rules, and you'll probably run with -T (taint checks on).

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      Thanks. This is precisely the kind of thing I was looking for. I am just getting into Perl as a language and didn't even think about eval as an approach.