You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
6.6 KiB
213 lines
6.6 KiB
package ksb::KDEProjectsReader 0.50; |
|
|
|
# Class: KDEProjectsReader |
|
# |
|
# Enumerates and provides basic metadata of KDE projects, based on |
|
# the YAML metadata included in sysadmin/repo-management. |
|
|
|
use strict; |
|
use warnings; |
|
use 5.014; |
|
|
|
use File::Find; |
|
|
|
sub _verifyYAMLModuleLoaded |
|
{ |
|
# Load YAML-reading module if available without causing compile error if it |
|
# isn't. Note that YAML::Tiny and YAML do not work since some metadata files |
|
# use features it doesn't support |
|
my @YAML_Opts = qw(Dump Load LoadFile); |
|
my @YAML_Mods = qw(YAML::XS YAML::Syck YAML::PP); |
|
my $success = 0; |
|
|
|
foreach my $mod (@YAML_Mods) { |
|
$success ||= eval "require $mod; $mod->import(\@YAML_Opts); 1;"; |
|
last if $success; |
|
} |
|
|
|
if (!$success) { |
|
die "Unable to load one of " . |
|
join(', ', @YAML_Mods) . |
|
" modules, one of which is needed to handle KDE project data."; |
|
} |
|
} |
|
|
|
# Method: new |
|
# |
|
# Constructs a new KDEProjectsReader. This doesn't contradict any part of the class |
|
# documentation which claims this class is a singleton however. This should be |
|
# called as a method (e.g. KDEProjectsReader->new(...)). |
|
# |
|
# Parameters: |
|
# $projectMetadataModule - ksb::Module reference to the repo-metadata module. |
|
# $desiredProtocol - Normally 'git', but other protocols like 'http' can also |
|
# be preferred (e.g. for proxy compliance). |
|
sub new |
|
{ |
|
my $class = shift; |
|
my $projectMetadataModule = shift; |
|
my $desiredProtocol = shift; |
|
|
|
_verifyYAMLModuleLoaded(); |
|
|
|
my $self = { |
|
# Maps short names to repo info blocks |
|
repositories => { }, |
|
protocol => $desiredProtocol, |
|
}; |
|
|
|
$self = bless ($self, $class); |
|
$self->_readProjectData($projectMetadataModule); |
|
|
|
return $self; |
|
} |
|
|
|
# The 'main' method for this class. Reads in *all* KDE projects and notes |
|
# their details for later queries. |
|
# Be careful, can throw exceptions. |
|
sub _readProjectData |
|
{ |
|
my ($self, $projectMetadataModule) = @_; |
|
|
|
my $srcdir = $projectMetadataModule->fullpath('source'); |
|
my $file_searched = 0; |
|
|
|
File::Find::find(sub { |
|
$self->_readYAML($_) if $_ eq 'metadata.yaml'; |
|
$file_searched = 1; |
|
}, "$srcdir/projects"); |
|
} |
|
|
|
sub _readYAML |
|
{ |
|
my ($self, $filename) = @_; |
|
|
|
my $proj_data = LoadFile($filename); |
|
|
|
if (!$proj_data->{repoactive} || |
|
!$proj_data->{hasrepo} || |
|
($proj_data->{type} ne 'project' && $proj_data->{type} ne 'module')) |
|
{ |
|
return; |
|
}; |
|
|
|
my $repoName = $proj_data->{repopath}; |
|
my $repoPath = $self->{protocol} . "://anongit.kde.org/$repoName"; |
|
|
|
my $curRepository = { |
|
'fullName' => $proj_data->{projectpath}, |
|
'repo' => $repoPath, |
|
'name' => $repoName, |
|
'active' => !!$proj_data->{repoactive}, |
|
'found_by' => 'direct', # can be changed in getModulesForProject |
|
}; |
|
|
|
$self->{repositories}->{$repoName} = $curRepository; |
|
} |
|
|
|
# Note on $proj: A /-separated path is fine, in which case we look |
|
# for the right-most part of the full path which matches all of searchProject. |
|
# e.g. kde/kdebase/kde-runtime would be matched by a proj of either |
|
# "kdebase/kde-runtime" or simply "kde-runtime". |
|
sub getModulesForProject |
|
{ |
|
my ($self, $proj) = @_; |
|
|
|
my $repositoryRef = $self->{repositories}; |
|
my @results; |
|
my $findResults = sub { |
|
my @matchList = |
|
grep { |
|
_projectPathMatchesWildcardSearch( |
|
$repositoryRef->{$_}->{'fullName'}, $proj) |
|
} (keys %{$repositoryRef}); |
|
|
|
if ($proj =~ m/\*/) { |
|
$repositoryRef->{$_}->{found_by} = 'wildcard' foreach @matchList; |
|
} |
|
|
|
push @results, @matchList; |
|
}; |
|
|
|
# Wildcard matches happen as specified if asked for. |
|
# Non-wildcard matches have an implicit "$proj/*" search as well for |
|
# compatibility with previous use-modules |
|
# Project specifiers ending in .git are forced to be non-wildcarded. |
|
if ($proj !~ /\*/ && $proj !~ /\.git$/) { |
|
# We have to do a search to account for over-specified module names |
|
# like phonon/phonon |
|
$findResults->(); |
|
|
|
# Now setup for a wildcard search to find things like kde/kdelibs/baloo |
|
# if just 'kdelibs' is asked for. |
|
$proj .= '/*'; |
|
} |
|
|
|
$proj =~ s/\.git$//; |
|
|
|
# If still no wildcard and no '/' then we can use direct lookup by module |
|
# name. |
|
if ($proj !~ /\*/ && $proj !~ /\// && exists $repositoryRef->{$proj}) { |
|
push @results, $proj; |
|
} |
|
else { |
|
$findResults->(); |
|
} |
|
|
|
return @{$repositoryRef}{@results}; |
|
} |
|
|
|
# Utility subroutine, returns true if the given kde-project full path (e.g. |
|
# kde/kdelibs/nepomuk-core) matches the given search item. |
|
# |
|
# The search item itself is based on path-components. Each path component in |
|
# the search item must be present in the equivalent path component in the |
|
# module's project path for a match. A '*' in a path component position for the |
|
# search item matches any project path component. |
|
# |
|
# Finally, the search is pinned to search for a common suffix. E.g. a search |
|
# item of 'kdelibs' would match a project path of 'kde/kdelibs' but not |
|
# 'kde/kdelibs/nepomuk-core'. However 'kdelibs/*' would match |
|
# 'kde/kdelibs/nepomuk-core'. |
|
# |
|
# First parameter is the full project path from the kde-projects database. |
|
# Second parameter is the search item. |
|
# Returns true if they match, false otherwise. |
|
sub _projectPathMatchesWildcardSearch |
|
{ |
|
my ($projectPath, $searchItem) = @_; |
|
|
|
my @searchParts = split(m{/}, $searchItem); |
|
my @nameStack = split(m{/}, $projectPath); |
|
|
|
if (scalar @nameStack >= scalar @searchParts) { |
|
my $sizeDifference = scalar @nameStack - scalar @searchParts; |
|
|
|
# We might have to loop if we somehow find the wrong start point for our search. |
|
# E.g. looking for a/b/* against a/a/b/c, we'd need to start with the second a. |
|
my $i = 0; |
|
while ($i <= $sizeDifference) { |
|
# Find our common prefix, then ensure the remainder matches item-for-item. |
|
for (; $i <= $sizeDifference; $i++) { |
|
last if $nameStack[$i] eq $searchParts[0]; |
|
} |
|
|
|
return if $i > $sizeDifference; # Not enough room to find it now |
|
|
|
# At this point we have synched up nameStack to searchParts, ensure they |
|
# match item-for-item. |
|
my $found = 1; |
|
for (my $j = 0; $found && ($j < @searchParts); $j++) { |
|
return 1 if $searchParts[$j] eq '*'; # This always works |
|
$found = 0 if $searchParts[$j] ne $nameStack[$i + $j]; |
|
} |
|
|
|
return 1 if $found; # We matched every item to the substring we found. |
|
$i++; # Try again |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
1;
|
|
|