Saturday, March 15, 2008

Portfind

Although done several months ago, this is one of my most memorable Perl scripts. It's a script designed to search for ebuilds under any Portage tree, regardless of package managers. Unlike most package managers I've seen, it also searches the metadata.xml files in the tree.

portfind-usql

#!/usr/bin/perl
use strict;
use File::Basename;
use XML::DOM;
use Term::ANSIColor;
use Digest::MD5 qw(md5 md5_hex);
use MIME::Base64;
use Encode;
use DBI;
use Switch;

our $searchall;
our $colorreplace = -1;
our $insensitive = -1;
our $printg = 1;
our $arch = undef;
our %portdirs;
our %eclasses;
our %modtimes;
our $dbfile = undef;
our $monitorupdates = -1;
our $newdb;
our $nocolor = -1;
#our $ebmode = -1;
our @searchterms;




if(-e "/etc/portfind.conf") {
open(FILE, "</etc/portfind.conf") || die "Configuration file is unreadable.\n";
my @lines = <FILE>;
chomp(@lines);
close(FILE);
foreach my $line (@lines) {
if("$line" =~ /^[\s]*\#/) {
}
elsif("$line" =~ /^[\s]*[Hh]ighlight[\:\s]+([Yy]es|[Nn]o)[\s]*$/) {
my $pre = "$line";
$pre =~ s/^[\s]*[Hh]ighlight[\:\s]+([Yy]es|[Nn]o)[\s]*$/\1/;
$pre = lc($pre);
switch($pre) {
case "yes" { $colorreplace = 1 }
else {$colorreplace = 0 }
}
}
elsif("$line" =~ /^[\s]*[Cc]ase[Ii]nsensitive[\:\s]+([Yy]es|[Nn]o)[\s]*$/) {
my $pre = "$line";
$pre =~ s/^[\s]*[Cc]ase[Ii]nsensitive[\:\s]+([Yy]es|[Nn]o)[\s]*$/\1/;
$pre = lc($pre);
switch($pre) {
case "yes" { $insensitive = 1 }
else {$insensitive = 0 }
}
}
elsif("$line" =~ /^[\s]*[Mm]onitor[Uu]pdates[\:\s]+([Yy]es|[Nn]o)[\s]*$/) {
my $pre = "$line";
$pre = lc($pre);
$pre =~ s/^[\s]*[Mm]onitor[Uu]pdates[\:\s]+([Yy]es|[Nn]o)[\s]*$/\1/;
switch($pre) {
case "yes" { $monitorupdates = 1 }
else { $monitorupdates = 0 }
}
}
# elsif("$line" =~ /^[\s]*[Ee]build[Mm]ode[\:\s]+([Ss]hell|[Oo]ld)[\s]*$/) {
# my $pre = "$line";
# $pre = lc($pre);
# $pre =~ s/^[\s]*[Ee]build[Mm]ode[\:\s]+([Ss]hell|[Oo]ld)[\s]*$/\1/;
# switch($pre) {
# case "shell" { $monitorupdates = 1 }
# else { $monitorupdates = 0 }
# }
# }
elsif("$line" =~ /^[\s]*[Aa]rch[\:\s]+[A-z0-9\-]+?[\s]*$/) {
my $pre = "$line";
$pre =~ s/^[\s]*[Aa]rch[\:\s]+([A-z0-9\-]+?)[\s]*$/\1/;
$pre = lc($pre);
$arch = "$pre";
}
elsif("$line" =~ /^[\s]*[Ss][Qq][Ll][Ff]ile[\:\s]+[^\:\s]+?[\s]*$/) {
my $pre = "$line";
$pre =~ s/^[\s]*[Ss][Qq][Ll][Ff]ile[\:\s]+([^\:\s]+?)[\s]*$/\1/;
$pre = lc($pre);
$dbfile = "$pre";
}
elsif("$line" =~ /^[\s]*[Pp]ort[Dd]ir[\:\s]+[a-z0-9_-]+?[\:\s]+[^\:\s]+?[\:\s]+[^\:\s]+?[\s]*$/) {
my $key = "$line";
$key =~ s/^[\s]*[Pp]ort[Dd]ir[\:\s]+([a-z0-9_-]+?)[\:\s]+[^\:\s]+?[\:\s]+[^\:\s]+?[\s]*$/\1/;
my $pre = "$line";
$pre =~ s/^[\s]*[Pp]ort[Dd]ir[\:\s]+[a-z0-9_-]+?[\:\s]+([^\:\s]+?)[\:\s]+[^\:\s]+?[\s]*$/\1/;
my $eclass = "$line";
$eclass =~ s/^[\s]*[Pp]ort[Dd]ir[\:\s]+[a-z0-9_-]+?[\:\s]+[^\:\s]+?[\:\s]+([^\:\s]+?)[\s]*$/\1/;
$portdirs{$key} = $pre;
$eclasses{$key} = $eclass;
}
}
}
if($colorreplace == -1) {
$colorreplace = 1;
}
if($insensitive == -1) {
$insensitive = 1;
}
if($nocolor == -1) {
$nocolor = 0;
}
if($monitorupdates == -1) {
$monitorupdates = 0;
}
#if($ebmode == -1) {
# $ebmode = 1;
#}
if(!$arch) {
$arch = "x86";
}
if(!$dbfile) {
$dbfile = "/usr/portfind.dbl";
}
if(!scalar %portdirs) {
%portdirs = ("gentoo" => "/usr/portage");
%eclasses = ("gentoo" => "/usr/portage/eclass");
}



my @opts;
foreach my $ARG (@ARGV) {
if($ARG =~ /^\-\-[^-]+.*$/) {
push(@opts, $ARG);
}
}


if(grep(/^\-\-insensitive$/, @opts)) {
$insensitive = 1;
}
elsif(grep(/^\-\-no\-insensitive$/, @opts)) {
$insensitive = 0;
}
if(grep(/^\-\-highlight$/, @opts)) {
$colorreplace = 1;
}
elsif(grep(/^\-\-no\-highlight$/, @opts)) {
$colorreplace = 0;
}
if(grep(/^\-\-monitor\-updates$/, @opts)) {
$monitorupdates = 1;
}
elsif(grep(/^\-\-no\-monitor\-updates$/, @opts)) {
$monitorupdates = 0;
}
if(grep(/^\-\-no-color$/, @opts)) {
$nocolor = 1;
}






if($printg) {
my $text .= "Portfind-uSQL pre-alpha, Copyright (C) 2007 Neil E. Hodges\n";
$text .= "portfind-uSQL comes with ABSOLUTELY NO WARRANTY\n";
print $text;
}





if(-e "$dbfile" && $monitorupdates) {
my $dbh = DBI->connect("dbi:SQLite:$dbfile") || die "Unable to open DBFile: $dbfile\n";
foreach my $key (%portdirs) {
my $portdir = $portdirs{$key};
if(-e "$portdir") {
my $modtime = -A "$portdir";
my $dbmodtime;
my $dbdata = $dbh->selectall_arrayref("SELECT * FROM modtimes WHERE dir = \'$portdir\'");
foreach my $dbdat (@{$dbdata}) {
$dbmodtime = ${$dbdat}[2];
}
if(abs($dbmodtime - $modtime) >= 0.02) {
print colored("$portdir has been modified", 'bold red') . ": Please " . colored("--update", 'bold yellow') . "\n";
$modtimes{$key} = 1;
}
elsif(!$dbmodtime) {
print colored("${portdir}'s modification time hasn't been recorded", 'bold red') . ": Please " . colored("--update", 'bold yellow') . "\n";
}
else {
$modtimes{$key} = 0;
}
}
}
$dbh->disconnect;
}


if(scalar @ARGV < 1) {
$searchall = 1;
}
elsif(grep(/^\-\-update$/, @opts)) {
$newdb = 1;
$searchall = 1;
}
elsif(grep(/^\-\-help$/, @opts)) {
my $text = "Usage: " . basename($0) . " " . colored("\[", 'bold') . " " . colored("option", 'bold green') . " " . colored("\|", 'bold') . " search terms " . colored("\]", 'bold') . "\n";
$text .= "\t" . colored("Options:\n", 'bold');
$text .= "\t\t" . colored("--help", 'bold green') . ": Display this message.\n";
$text .= "\t\t" . colored("--update", 'bold yellow') . ": Update database.\n";
$text .= "\t\t" . colored("--help-config", 'bold red') . ": Configuration help.\n";
$text .= "\t\t" . colored("--no-color", 'bold blue') . ": Disables colored output.\n";
$text .= "\tThe following options can be given the " . colored("--no", 'bold') . " beginning. E.G.: --no-insensitive\n";
$text .= "\t\t" . colored("--insensitive", 'bold cyan') . ": Toggles case sensitivity.\n";
$text .= "\t\t" . colored("--highlight", 'bold cyan') . ": Toggles highlighting of search terms in found text.\n";
$text .= "\t\t" . colored("--monitor-updates", 'bold cyan') . ": Toggles Portage tree update monitoring by access time.\n";
if($nocolor) {
print &nocolor("$text");
}
else {
print "$text";
}
exit;
}
elsif(grep(/^\-\-help-config$/, @opts)) {
my $text = colored(basename($0) . " Configuration:", 'bold') . "\n";
$text .= "Configuration file lies at " . colored("/etc/portfind.conf", 'bold blue') . ": " . ((-e "/etc/portfind.conf") ? colored("Found", 'bold green') : colored("Not found", 'bold red')) . "\n";
$text .= "\tAny line began with a \# is a comment.\n";
$text .= "\tLines are written a leading config option, and separators are \: and/or spaces, ending with a newline.\n";
$text .= "\t\tE.G.: \"CaseInsensitive\: Yes\"\n";
$text .= "\t" . colored("CaseInsensitive", 'bold cyan') . ": " . colored("Yes", 'bold green') . " or " . colored("No", 'bold red') . ": Toggles whether to search without case sensitivity.\n";
$text .= "\t\t" . colored("Note", 'bold') . ": The case sensitive search is much quicker.\n";
$text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . (($insensitive) ? colored("Yes", 'bold green') : colored("No", 'bold red')) . "\n";
$text .= "\t" . colored("Highlight", 'bold cyan') . ": " . colored("Yes", 'bold green') . " or " . colored("No", 'bold red') . ": Toggles whether to highlight search terms in found text.\n";
$text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . (($colorreplace) ? colored("Yes", 'bold green') : colored("No", 'bold red')) . "\n";
$text .= "\t" . colored("MonitorUpdates", 'bold cyan') . ": " . colored("Yes", 'bold green') . " or " . colored("No", 'bold red') . ": Toggles whether updates are monitored via access times.\n";
$text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . (($monitorupdates) ? colored("Yes", 'bold green') : colored("No", 'bold red')) . "\n";
# $text .= "\t" . colored("EbuildMode", 'bold cyan') . ": " . colored("Shell", 'bold green') . " or " . colored("Old", 'bold red') . ": Toggles whether to use the new " . colored("ebuld_parse.sh", 'bold green') . " shell script or the old " . colored("ebuild_parse", 'bold red') . " program.\n";
# $text .= "\t\t" . colored("Note", 'bold') . ": The shell script method is faster.\n";
# $text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . (($ebmode) ? colored("Shell", 'bold green') : colored("Old", 'bold red')) . "\n";
$text .= "\t" . colored("Arch", 'bold cyan') . ": The architecture as put by Portage of your current systems.\n";
$text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . colored($arch, 'bold green') . "\n";
$text .= "\t" . colored("SQLFile", 'bold cyan') . ": The location of the SQLite database\n";
$text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . colored($dbfile, 'bold green') . ": " . ((-e $dbfile) ? colored("Found", 'bold green') : colored("Not Found, please ", 'bold red') . colored("--update", 'bold yellow')) . "\n";
$text .= "\t" . colored("PortDir", 'bold cyan') . ": The Portage trees, including overlays and the main tree\n";
$text .= "\t\tMay be specified multiple times for multiple trees.\n";
$text .= "\t\tPortDir: name /path/to/tree /path/to/eclass\n";
if($monitorupdates) {
foreach my $key (keys %portdirs) {
$text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . colored("$key", 'green') . " -> " . colored("$portdirs{$key}", 'bold green') . ": " . ((-e $portdirs{$key}) ? colored("Found", 'bold green') . (($modtimes{$key}) ? "/" . colored("Update", 'bold red') : "") : colored("Not found", 'bold red')) . " (" . colored("Eclass: ", 'bold yellow') . colored("$eclasses{$key}", 'bold green') . ": " . ((-e $eclasses{$key}) ? colored("Found", 'bold green') : colored("Not Found", 'bold red')) . ")\n";
}
}
else {
foreach my $key (keys %portdirs) {
$text .= "\t\t" . colored("Currently", 'bold yellow') . ": " . colored("$key", 'green') . " -> " . colored("$portdirs{$key}", 'bold green') . ": " . ((-e $portdirs{$key}) ? colored("Found", 'bold green') : colored("Not found", 'bold red')) . " (" . colored("Eclass: ", 'bold yellow') . colored("$eclasses{$key}", 'bold green') . ": " . ((-e $eclasses{$key}) ? colored("Found", 'bold green') : colored("Not Found", 'bold red')) . ")\n";
}
}
if($nocolor) {
print &nocolor("$text");
}
else {
print "$text";
}
exit;
}
else {
$searchall = 0;
foreach my $ARG (@ARGV) {
if(!($ARG =~ /^--[^-]+.*$/)) {
push(@searchterms, $ARG);
}
}
}
if($newdb && -e "$dbfile") {
unlink("$dbfile");
}

my $dbh = DBI->connect("dbi:SQLite:$dbfile") || die "Unable to open DBFile: $dbfile\n";



if($newdb) {
STDOUT->autoflush(1);
print "Creating new DB at $dbfile...\n";
my @categories = &generate_db;


$dbh->do("CREATE TABLE categories(id INTEGER PRIMARY KEY AUTOINCREMENT, name CHAR(32))");
$dbh->do("CREATE TABLE ebuilds(id INTEGER PRIMARY KEY AUTOINCREMENT, name CHAR(64), catname CHAR(32), longdesc CHAR(2048), longhash CHAR(32))");
$dbh->do("CREATE TABLE shortdescs(id INTEGER PRIMARY KEY AUTOINCREMENT, ename CHAR(64), catname CHAR(32), version CHAR(32), mask CHAR(8), shortdesc CHAR(1024), shorthash CHAR(32), use CHAR(512))");
$dbh->do("CREATE TABLE modtimes(id INTEGER PRIMARY KEY AUTOINCREMENT, dir CHAR(512), modtime REAL)");

foreach my $key (%portdirs) {
my $portdir = $portdirs{$key};
if(-e "$portdir") {
my $modtime = -A "$portdir";
$dbh->do("INSERT INTO modtimes (dir, modtime) VALUES(\'$portdir\', $modtime)");
}
}


my $sth = $dbh->prepare("INSERT INTO ebuilds (name, catname, longdesc, longhash) VALUES(?, ?, ?, ?)");
my $lth = $dbh->prepare("INSERT INTO shortdescs (ename, catname, version, mask, shortdesc, shorthash, use) VALUES(?, ?, ?, ?, ?, ?, ?)");
print "Building SQLite database.\n";
my $precent = 0;
my $len = scalar @categories;
for(my $i = 0; $i < $len; $i++) {
my $category = $categories[$i];
my $catname = $category->name;
$dbh->do("INSERT INTO categories (name) VALUES(\'$catname\')");
my @ebuilds = @{$category->ebuilds};
foreach my $ebuild (@ebuilds) {
my $ename = $ebuild->name;
my $longdesc = $ebuild->longdesc;
my %shortdesc = %{$ebuild->shortdesc};
my %use = %{$ebuild->use};
my %mask = %{$ebuild->mask};
$longdesc = (($longdesc) ? "$longdesc" : "NULL");
$sth->execute($ename, $catname, $longdesc, &ascii_md5_hex("$longdesc"));
foreach my $key (sort keys %shortdesc) {
$lth->execute($ename, $catname, $key, $mask{$key}, $shortdesc{$key}, &ascii_md5_hex("$shortdesc{$key}"), $use{$key});
}
}
my $percent = (($i + 1) / $len) * 100;
if(abs($precent - $percent) >= 0.7) {
print "\b\b\b\b" . &round($percent) . "%";
$precent = $percent;
}
}
print "\nComplete.\n";
}
else {
if($searchall) {
my @categories = &extract_from_db($dbh);
if($nocolor) {
foreach my $category (@categories) {
foreach my $ebuild (@{$category->ebuilds}) {
print &nocolor(&printinfo($ebuild, $category->name));
}
}
}
else {
foreach my $category (@categories) {
foreach my $ebuild (@{$category->ebuilds}) {
print (&printinfo($ebuild, $category->name));
}
}
}
}
else {
my @found;
STDOUT->autoflush(1);
@found = &search_from_db($dbh);
STDOUT->autoflush(0);
if($nocolor) {
if(scalar @found == 0) {
print "No results.\n";
}
else {
foreach my $sf (@found) {
print &nocolor(&printinfo($sf->ebuild, $sf->category->name));
}
}
}
else {
if(scalar @found == 0) {
print colored("No results.", 'bold red') . "\n";
}
else {
foreach my $sf (@found) {
print (&printinfo($sf->ebuild, $sf->category->name));
}
}
}
}
}
$dbh->disconnect;

sub extract_from_db {
my $dbh = shift;
my @categories;
my $dbcategories = $dbh->selectall_arrayref("SELECT * FROM categories");
foreach my $dbcategory (@{$dbcategories}) {
my $category = new Category;
$category->name(${$dbcategory}[1]);

my $dbebuilds = $dbh->selectall_arrayref("SELECT * FROM ebuilds WHERE catname = \'${$dbcategory}[1]\'");
foreach my $dbebuild (@{$dbebuilds}) {
my $ebuild = new Ebuild;
$ebuild->name(${$dbebuild}[1]);
my $ename = $ebuild->name;
my $longdesc = ${$dbebuild}[3];
$ebuild->longdesc(($longdesc eq "NULL") ? undef : $longdesc);
my $dbshortdescs = $dbh->selectall_arrayref("SELECT * FROM shortdescs WHERE ename = \'$ename\'");
foreach my $dbshortdesc (@{$dbshortdescs}) {
my $version = ${$dbshortdesc}[2];
${$ebuild->shortdesc}{$version} = ${$dbshortdesc}[4];
${$ebuild->mask}{$version} = ${$dbshortdesc}[3];
}
push(@{$category->ebuilds}, $ebuild);
}
push(@categories, $category);
}
return @categories;
}
sub search_from_db {
my $dbh = shift;
my @found;
my $dbcategories = $dbh->selectall_arrayref("SELECT * FROM categories");
print STDERR "Initiating search.\n";
my $len = scalar @{$dbcategories};
my $precent = 0;
for(my $i = 0; $i < $len; $i++) {
my $dbcategory = ${$dbcategories}[$i];
my $category = new Category;
$category->name(${$dbcategory}[1]);
my $catname = ${$dbcategory}[1];
foreach my $term (@searchterms) {
my $dbebuilds;
if($insensitive) {
$dbebuilds = $dbh->selectall_arrayref("SELECT * FROM ebuilds WHERE catname = \'$catname\' AND (name LIKE \'\%$term\%\' OR longdesc LIKE \'\%$term\%\')");
}
else {
$dbebuilds = $dbh->selectall_arrayref("SELECT * FROM ebuilds WHERE catname = \'$catname\' AND (name GLOB \'\*$term\*\' OR longdesc GLOB \'\*$term\*\')");
}
foreach my $dbebuild (@{$dbebuilds}) {
my $ebuild = new Ebuild;
$ebuild->name(${$dbebuild}[1]);
my $ename = ${$dbebuild}[1];
$ebuild->name($ename);
my $longdesc = ${$dbebuild}[3];
if($longdesc ne "NULL") {
}
else {
$longdesc = undef;
}
$ebuild->longdesc($longdesc);




my $dbshortdescs = $dbh->selectall_arrayref("SELECT * FROM shortdescs WHERE ename = \'${$dbebuild}[1]\' AND catname = \'$catname\'");
foreach my $dbshortdesc (@{$dbshortdescs}) {
my $version = ${$dbshortdesc}[3];
my $shortdesc = ${$dbshortdesc}[5];
${$ebuild->shortdesc}{$version} = $shortdesc;
${$ebuild->mask}{$version} = ${$dbshortdesc}[4];
${$ebuild->use}{$version} = ${$dbshortdesc}[7];
}
my $enc = new Encapsulated;
$enc->ebuild($ebuild);
$enc->category($category);
push(@found, $enc);
}


my $dbshortdescs;
if($insensitive) {
$dbshortdescs = $dbh->selectall_arrayref("SELECT * FROM shortdescs WHERE shortdesc LIKE \'\%$term\%\' AND catname = \'$catname\'");
}
else {
$dbshortdescs = $dbh->selectall_arrayref("SELECT * FROM shortdescs WHERE shortdesc GLOB \'\*$term\*\' AND catname = \'$catname\'");
}
foreach my $dbshortdesc(@{$dbshortdescs}) {
my $ename = ${$dbshortdesc}[1];
if(&findebuild_encapsulated("$catname/$ename", 0, @found) == -1) {
my $dbebuilds = $dbh->selectall_arrayref("SELECT * FROM ebuilds WHERE catname = \'$catname\' AND name = \'$ename\'");
my $dbename = undef;
my $longdesc = undef;
foreach my $dbebuild (@{$dbebuilds}) {
if($ename eq ${$dbebuild}[1] && $catname eq ${$dbebuild}[2]) {
$dbename = ${$dbebuild}[1];
$longdesc = ${$dbebuild}[3];
}
}
if($dbename) {
my $ebuild = new Ebuild;
$ebuild->name($ename);
$ebuild->longdesc(($longdesc eq "NULL") ? undef : $longdesc);


my $dbebuild_shortdescs = $dbh->selectall_arrayref("SELECT * FROM shortdescs WHERE ename = \'$ename\' AND catname = \'$catname\'");
foreach my $dbebuild_shortdesc (@{$dbebuild_shortdescs}) {
my $version = ${$dbshortdesc}[3];
my $shortdesc = ${$dbshortdesc}[5];
${$ebuild->shortdesc}{$version} = $shortdesc;
${$ebuild->mask}{$version} = ${$dbshortdesc}[4];
${$ebuild->use}{$version} = ${$dbshortdesc}[7];
}
my $enc = new Encapsulated;
$enc->category($category);
$enc->ebuild($ebuild);
push(@found, $enc);
}
}
}
}
my $percent = (($i + 1) / $len) * 100;
if(abs($precent - $percent) >= 0.7) {
print STDERR "\b\b\b\b" . &round($percent) . "%";
$precent = $percent;
}
}
print STDERR "\nSearch complete.\n";
return @found;
}
sub generate_db {
my @categories;
print "Reading directory tree.\n";
foreach my $key (keys %portdirs) {
my $portdir = $portdirs{$key};
if(-e "$portdir") {
print "\nFor $portdir:\nFinding categories\n";
my @categorydirs = <$portdir/*-*>;
my $len = scalar @categorydirs;
my $precent = 0;
for(my $i = 0; $i < $len; $i++) {
my $category = $categorydirs[$i];
if(-d "$category" && !(&findcategory(@categories, basename("$category")))) {
my $cat = new Category;
$cat->name(basename("$category"));
push(@categories, $cat);
}

my $percent = (($i + 1) / $len) * 100;
if(abs($precent - $percent) >= 0.7) {
print "\b\b\b\b" . &round($percent) . "%";
$precent = $percent;
}
}
print "\nFinding ebuilds\n";



my $precent = 0;
my $len = scalar @categories;
for(my $i = 0; $i < $len; $i++) {
my $category = $categories[$i];
my $catname = $category->name;
foreach my $subdir (<$portdir/$catname/*>) {
my @tfiles = <$subdir/*>; # TEMPORARY TO FIND EBUILDS
if(-d "$subdir" && scalar @tfiles) {
my $ebuild = undef;
my $found = &findebuild(@{$category->ebuilds}, basename("$subdir"));
if($found > -1) {
$ebuild = ${$category->ebuilds}[$found];
}
else {
$ebuild = new Ebuild;
}
$ebuild->name(basename("$subdir"));
foreach my $file (<$subdir/*>) {
if(! -d "$file") {
push(@{$ebuild->files}, basename("$file"));
}
}
if($found > -1) {
${$category->ebuilds}[$found] = $ebuild;
}
else {
push(@{$category->ebuilds}, $ebuild);
}
}
}
my $percent = (($i + 1) / $len) * 100;
if(abs($precent - $percent) >= 0.7) {
print "\b\b\b\b" . &round($percent) . "%";
$precent = $percent;
}
}
}
}
print "\nCreating database from files.";
foreach my $key (keys %portdirs) {
my $portdir = $portdirs{$key};
my $eclass = $eclasses{$key};
if(-e "$portdir") {
print "\nFor $portdir:\n";
my $precent = 0;
my $len = scalar @categories;
for(my $i = 0; $i < $len; $i++) {
my $category = $categories[$i];
my $catname = $category->name;
foreach my $ebuild (@{$category->ebuilds}) {
my $ename = $ebuild->name;
if(-d "$portdir/$catname/$ename") {
foreach my $file (@{$ebuild->files}) {
if("$file" eq "metadata.xml" && -e "$portdir/$catname/$ename/$file") {
my $parser = new XML::DOM::Parser;
my $xml = $parser->parsefile("<$portdir/$catname/$ename/$file");
my $nodes = $xml->getElementsByTagName("longdescription");
my $n = $nodes->getLength;
for(my $i = 0; $i < $n; $i++) {
my $node = $nodes->item($i);
my $lang = $node->getAttributeNode("lang");
if($n == 1 || ($lang && $lang->getValue eq "en")) {
my $children = $node->getChildNodes;
my $nc = $children->getLength;
for(my $c = 0; $c < $nc; $c++) {
my $subnode = $children->item($c);
if($subnode->getNodeType == TEXT_NODE) {
$ebuild->longdesc($subnode->getNodeValue);
}
}
}
}
$xml->dispose;
}
elsif("$file" =~ /\.ebuild$/ && -e "$portdir/$catname/$ename/$file") {
my $version = "$file";
my $tmpename = "$ename";
my $descfound = 0;
my $maskfound = 0;
$tmpename =~ s/\+/\\\+/g;
$version =~ s/^$tmpename-([a-z0-9_.-]+)\.ebuild$/\1:$key/;
# open(FILE, "<$portdir/$catname/$ename/$file");
# my @lines = <FILE>;
# chomp(@lines);
# close(FILE);
my %varhash;
# if($ebmode) {
%varhash = &ebuild_get_vars_sh($catname, "$portdir/$catname/$ename/$file", $eclass);
# }
# else {
# %varhash = &ebuild_get_vars("$portdir/$catname/$ename/$file", $eclass);
# }
# foreach my $line (@lines) {
# if("$line" =~ /DESCRIPTION=\".*?\"/) {
# my $desc = "$line";
# $desc =~ s/^.*?DESCRIPTION=\"(.*?)\".*?$/\1/;
# ${$ebuild->shortdesc}{$version} = "$desc";
# $descfound = 1;
# }
# elsif("$line" =~ /KEYWORDS=\".*?\"/) {
# my $keywords = "$line";
# my $keyword = undef;
#
# if("$keywords" =~ /[\"\s]\~$arch([\"\s])/) {
# $keyword = "~";
# }
# elsif("$keywords" =~ /[\"\s]$arch([\"\s])/) {
# $keyword = "+";
# }
# elsif("$keywords" =~ /[\"\s]\-$arch([\"\s])/) {
# $keyword = "-";
# }
# else {
# $keyword = " ";
# }
# ${$ebuild->mask}{$version} = "$keyword";
# $maskfound = 1;
# }
# }
# DESCRIPTION
${$ebuild->shortdesc}{$version} = $varhash{"DESCRIPTION"};
# MASK
my $keywords = $varhash{"KEYWORDS"};
my $keyword = undef;

if("$keywords" =~ /\~($arch)([^a-z0-9-]+|$)/) {
$keyword = "~";
}
elsif("$keywords" =~ /($arch)([^a-z0-9-]+|$)/) {
$keyword = "+";
}
elsif("$keywords" =~ /\-($arch)([^a-z0-9-]+|$)/) {
$keyword = "-";
}
else {
$keyword = " ";
}
${$ebuild->mask}{$version} = "$keyword";
# END MASK

${$ebuild->use}{$version} = $varhash{"IUSE"};
if($varhash{"DESCRIPTION"} =~ /^[\s\t]*$/) {
${$ebuild->shortdesc}{$version} = undef;
}
}
}
}
}
my $percent = (($i + 1) / $len) * 100;
if(abs($precent - $percent) >= 0.7) {
print "\b\b\b\b" . &round($percent) . "%";
$precent = $percent;
}
}
}
}
print "\nGeneration complete.\n";
return @categories;
}
# NEW EBUILD PARSING CODE

#sub ebuild_get_vars {
# my $efile = shift;
# my $eclass = shift;
# my $foo;
# my %varhash;
# $foo = `ebuild_parse $efile $eclass`;
# my @data = split(/\n/, $foo);
# foreach my $dat (@data) {
# my $varname = $dat;
# $varname =~ s/^\'([A-z0-9_-]*?)\': \'\'\'.*\'\'\'$/\1/;
# my $varvalue = $dat;
# $varvalue =~ s/^\'[A-z0-9_-]*?\': \'\'\'(.*)\'\'\'$/\1/;
# $varhash{$varname} = $varvalue;
# }
# return %varhash;
#}
sub ebuild_get_vars_sh {
my $catname = shift;
my $efile = shift;
my $eclass = shift;
my $foo;
my %varhash;
my $pv = basename($efile);
my $pdir = $efile; # Package name
$pdir =~ s/\/[^\/]*\.ebuild$//;
$pdir = basename($pdir);
$pdir =~ s/(\W)/\\\1/g;
if($pv =~ /^$pdir-.*?-r[0-9]+\.ebuild/) {
$pv =~ s/^$pdir-(.*?)-r[0-9]+\.ebuild$/\1/;
}
else {
$pv =~ s/^$pdir-(.*?)\.ebuild$/\1/;
}
$foo = `ebuild_parse.sh $efile $catname $pdir $pv $eclass`;
my @data = split(/\n/, $foo);
foreach my $dat (@data) {
my $varname = $dat;
$varname =~ s/^\'([A-z0-9_-]*?)\': \'\'\'.*\'\'\'$/\1/;
my $varvalue = $dat;
$varvalue =~ s/^\'[A-z0-9_-]*?\': \'\'\'(.*)\'\'\'$/\1/;
$varhash{$varname} = $varvalue;
}
return %varhash;
}



sub findcategory {
my $catname = pop @_;
my @categories = @_;
foreach my $category (@categories) {
my $name = $category->name;
if("$name" eq "$catname") {
return 1;
}
}
return 0;
}
sub findebuild {
my $ename = pop @_;
my @ebuilds = @_;
for(my $i = 0; $i < scalar @ebuilds; $i++) {
my $name = $ebuilds[$i]->name;
if("$name" eq "$ename") {
return $i;
}
}
return -1;
}
sub findebuild_encapsulated {
my $ename = shift;
my $i0 = shift;
my @encapsulated = @_;
for(my $i = $i0; $i < scalar @encapsulated; $i++) {
my $enc = $encapsulated[$i];
my $category = $enc->category;
my $catname = $category->name;

my $ebuild = $enc->ebuild;
my $cename = $ebuild->name;
if($ename eq "$catname/$cename") {
return $i;
}
}
return -1;
}
sub highlight {
my $term = shift;
my $text = shift;
my $colors = shift;
my $recolor = color shift;
my $uncolor = color 'reset';
my @captured;
if($insensitive) {
@captured = "$text" =~ /$term/ig;
}
else {
@captured = "$text" =~ /$term/g;
}
foreach my $found (@captured) {
my $colored = colored("$found", $colors);
my $filterfound = "$found";
$filterfound =~ s/(\W)/\\\1/g;
$text =~ s/$filterfound/${uncolor}${colored}${recolor}/g;
}
return $text;
}

sub printinfo {
my $ebuild = shift;
my $catname = shift;
my $ename = $ebuild->name;
my $longdesc = $ebuild->longdesc;
my %shortdesc = %{$ebuild->shortdesc};
my %mask = %{$ebuild->mask};
my %use = %{$ebuild->use};
if($colorreplace) {
foreach my $term (@searchterms) {
$ename = &highlight($term, $ename, 'cyan', 'bold blue');
$longdesc = &highlight($term, $longdesc, 'cyan', 'reset');
foreach my $version (keys %shortdesc) {
$shortdesc{$version} = &highlight($term, $shortdesc{$version}, 'cyan', 'reset');
}
}
}

my $ret = colored("$catname/$ename\n", 'bold blue');
$ret .= "\t" . colored("Short Description\n", 'bold green');
foreach my $version (reverse sort keys %shortdesc) {
my $maskcolor = color('reset') . colored("$mask{$version}", 'red') . color('bold yellow');
$ret .= "\t\t" . colored("$version [$maskcolor]: ", 'bold yellow') . "$shortdesc{$version}\n";
if($use{$version}) {
$ret .= "\t\t\t" . colored("USE:", 'bold blue') . " " . colored($use{$version}, 'green') . "\n";
}
}
if($longdesc && !("$longdesc" =~ /^[\s\t\n]*$/)) {
my @arraydesc = split(/\n/, "$longdesc");
chomp(@arraydesc);
if(!$arraydesc[0]) {
shift(@arraydesc);
}
$ret .= "\t" . colored("Long Description\n", 'bold red');
foreach my $line (@arraydesc) {
$line =~ s/^[\s\t]*/\t\t/g;
$ret .= "$line\n";
}
}
$ret .= "\n";
return "$ret";
}
sub ascii_md5_hex {
my $text = shift;
$text = encode("ascii", "$text");
return md5_hex("$text");
}
sub round {
my $number = shift;
return int($number + .5 * ($number <=> 0));
}
sub nocolor {
my $text = shift;
$text =~ s/\e\[.+?m//g;
return $text;
}



package Category;
sub new {
my $self = { };
my $class = shift;
return bless($self, $class);
}
sub name {
my $self = shift;
$self->{NAME} = shift if @_;
return $self->{NAME};
}
sub ebuilds {
my $self = shift;
if(!defined $self->{EBUILDS}) {
$self->{EBUILDS} = [ ];
}
return $self->{EBUILDS};
}
package Encapsulated;
sub new {
my $self = { };
my $class = shift;
return bless($self, $class);
}
sub category {
my $self = shift;
$self->{CATEGORY} = shift if @_;
return $self->{CATEGORY};
}
sub ebuild {
my $self = shift;
$self->{EBUILD} = shift if @_;
return $self->{EBUILD};
}
package Ebuild;
sub new {
my $self = { };
my $class = shift;
return bless($self, $class);
}
sub name {
my $self = shift;
$self->{NAME} = shift if @_;
return $self->{NAME};
}
sub files {
my $self = shift;
if(!defined $self->{FILES}) {
$self->{FILES} = [ ];
}
return $self->{FILES};
}
sub longdesc {
my $self = shift;
$self->{LDESC} = shift if @_;
return $self->{LDESC};
}
sub shortdesc {
my $self = shift;
if(!defined $self->{SDESC}) {
$self->{SDESC} = { };
}
return $self->{SDESC};
}
sub use {
my $self = shift;
if(!defined $self->{USE}) {
$self->{USE} = { };
}
return $self->{USE};
}
sub mask {
my $self = shift;
if(!defined $self->{MASK}) {
$self->{MASK} = { };
}
return $self->{MASK};
}

Example portfind.conf

Highlight: yes
CaseInsensitive: no
Arch: amd64
SeachMode: DB
Portdir: gentoo /usr/portage /usr/portage/eclass
SQLFile: /usr/portfind.dbl
MonitorUpdates: no

ebuild_parse.sh

#!/bin/bash
env -i
set +o posix
if [ "$5" = "" ]; then
echo "Usage: $0 ebuild catname ename vers eclassdir"
exit 1
fi
GCONF_DEBUG='yes'
ARTS_REQUIRED='no'
EBUILDFILE=$1
CATEGORY=$2
PN=$3
PV=$4
ECLASSDIR=$5


# BEGIN COPYRIGHT
# Copyright (c) 1999-2005 Gentoo Foundation

SANDBOX_PREDICT="${SANDBOX_PREDICT}:/proc/self/maps:/dev/console:/dev/random"
export SANDBOX_PREDICT="${SANDBOX_PREDICT}:${PORTAGE_PYM_PATH}:${PORTAGE_DEPCACHEDIR}"
export SANDBOX_WRITE="${SANDBOX_WRITE}:/dev/shm:/dev/stdout:/dev/stderr:${PORTAGE_TMPDIR}"
export SANDBOX_READ="${SANDBOX_READ}:/dev/shm:/dev/stdin:${PORTAGE_TMPDIR}"

if [ ! -z "${PORTAGE_GPG_DIR}" ]; then
SANDBOX_PREDICT="${SANDBOX_PREDICT}:${PORTAGE_GPG_DIR}"
fi

declare -rx EBUILD_PHASE

function qa_source () {
local shopts=$(shopt) OLDIFS="$IFS"
source "$@" || return 1
# [[ $shopts != $(shopt) ]] &&
# eqawarn "QA Notice: Global shell options changed and were not restored while sourcing '$*'"
# [[ "$IFS" != "$OLDIFS" ]] &&
# eqawarn "QA Notice: Global IFS changed and was not restored while sourcing '$*'"
return 0
}

function qa_call() {
local shopts=$(shopt) OLDIFS="$IFS"
"$@" || return 1
# [[ $shopts != $(shopt) ]] &&
# eqawarn "QA Notice: Global shell options changed and were not restored while calling '$*'"
# [[ "$IFS" != "$OLDIFS" ]] &&
# eqawarn "QA Notice: Global IFS changed and was not restored while calling '$*'"
return 0
}

# We need this next line for "die" and "assert". It expands
# It _must_ preceed all the calls to die and assert.
shopt -s expand_aliases
function die () {
echo -n
}
#alias die='exit 1'
alias assert='_pipestatus="${PIPESTATUS[*]}"; [[ "${_pipestatus// /}" -eq 0 ]] || exit 1'
alias save_IFS='[ "${IFS:-unset}" != "unset" ] && old_IFS="${IFS}"'
alias restore_IFS='if [ "${old_IFS:-unset}" != "unset" ]; then IFS="${old_IFS}"; unset old_IFS; else unset IFS; fi'

OCC="$CC"
OCXX="$CXX"


# the sandbox is disabled by default except when overridden in the relevant stages
export SANDBOX_ON="0"

function addread () {
echo -n
}

function addwrite () {
echo -n
}

function adddeny () {
echo -n
}

function addpredict () {
echo -n
}

function lchown () {
chown -h "$@"
}

function lchgrp () {
chgrp -h "$@"
}
function esyslog () {
return 0
}

function use () {
useq ${1}
}

function usev () {
if useq ${1}; then
echo "${1}"
return 0
fi
return 1
}

function useq () {
local u=$1
local found=0

# if we got something like '!flag', then invert the return value
if [[ ${u:0:1} == "!" ]] ; then
u=${u:1}
found=1
fi

# Make sure we have this USE flag in IUSE
if ! hasq "${u}" ${IUSE} ${E_IUSE} && ! hasq "${u}" ${PORTAGE_ARCHLIST} selinux; then
# eqawarn "QA Notice: USE Flag '${u}' not in IUSE for ${CATEGORY}/${PF}"
echo -n
fi

if hasq ${u} ${USE} ; then
return ${found}
else
return $((!found))
fi
}
function has_version () {
if [ "${EBUILD_PHASE}" == "depend" ]; then
return 1
fi
if "${PORTAGE_BIN_PATH}/portageq" 'has_version' "${ROOT}" "$1"; then
return 0
else
return 1
fi
}

function portageq () {
if [ "${EBUILD_PHASE}" == "depend" ]; then
return 1
fi
"${PORTAGE_BIN_PATH}/portageq" "$@"
}


function hasq () {
[[ " ${*:2} " == *" $1 "* ]]
}
function has () {
hasq "$@"
}
function debug-print () {
echo -n
}

function debug-print-function () {
echo -n
}

function debug-print-section () {
echo -n
}
function use_with () {
if [ -z "$1" ]; then
echo "!!! use_with() called without a parameter." >&2
echo "!!! use_with <USEFLAG> [<flagname> [value]]" >&2
return 1
fi

local UW_SUFFIX=""
if [ ! -z "${3}" ]; then
UW_SUFFIX="=${3}"
fi

local UWORD="$2"
if [ -z "${UWORD}" ]; then
UWORD="$1"
fi

if useq $1; then
echo "--with-${UWORD}${UW_SUFFIX}"
else
echo "--without-${UWORD}"
fi
return 0
}

function use_enable () {
if [ -z "$1" ]; then
echo "!!! use_enable() called without a parameter." >&2
echo "!!! use_enable <USEFLAG> [<flagname> [value]]" >&2
return 1
fi

local UE_SUFFIX=""
if [ ! -z "${3}" ]; then
UE_SUFFIX="=${3}"
fi

local UWORD="$2"
if [ -z "${UWORD}" ]; then
UWORD="$1"
fi

if useq $1; then
echo "--enable-${UWORD}${UE_SUFFIX}"
else
echo "--disable-${UWORD}"
fi
return 0
}
function eerror () {
echo -n
}
# END COPYRIGHT
function EXPORT_FUNCTIONS {
for i in $*
do
export $i
done
}
function inherit () {
for i in $*
do
if [ -e "$ECLASSDIR/$i.eclass" ]; then
source "$ECLASSDIR/$i.eclass"
else
source "/usr/portage/eclass/$i.eclass"
fi
done
}

source "$EBUILDFILE"
echo "'IUSE': '''$IUSE'''"
echo "'KEYWORDS': '''$KEYWORDS'''"
echo "'DESCRIPTION': '''$DESCRIPTION'''"

Help output

# portfind-usql --help
Portfind-uSQL pre-alpha, Copyright (C) 2007 Neil E. Hodges
portfind-uSQL comes with ABSOLUTELY NO WARRANTY
Usage: portfind-usql [ option | search terms ]
Options:
--help: Display this message.
--update: Update database.
--help-config: Configuration help.
--no-color: Disables colored output.
The following options can be given the --no beginning. E.G.: --no-insensitive
--insensitive: Toggles case sensitivity.
--highlight: Toggles highlighting of search terms in found text.
--monitor-updates: Toggles Portage tree update monitoring by access time.
# portfind-usql --help-config
Portfind-uSQL pre-alpha, Copyright (C) 2007 Neil E. Hodges
portfind-uSQL comes with ABSOLUTELY NO WARRANTY
portfind-usql Configuration:
Configuration file lies at /etc/portfind.conf: Found
Any line began with a # is a comment.
Lines are written a leading config option, and separators are : and/or spaces, ending with a newline.
E.G.: "CaseInsensitive: Yes"
CaseInsensitive: Yes or No: Toggles whether to search without case sensitivity.
Note: The case sensitive search is much quicker.
Currently: No
Highlight: Yes or No: Toggles whether to highlight search terms in found text.
Currently: Yes
MonitorUpdates: Yes or No: Toggles whether updates are monitored via access times.
Currently: No
Arch: The architecture as put by Portage of your current systems.
Currently: amd64
SQLFile: The location of the SQLite database
Currently: /usr/portfind.dbl: Found
PortDir: The Portage trees, including overlays and the main tree
May be specified multiple times for multiple trees.
PortDir: name /path/to/tree /path/to/eclass
Currently: gentoo -> /usr/portage: Found (Eclass: /usr/portage/eclass: Found)
)

No comments: