App-GHGen

 view release on metacpan or  search on metacpan

lib/App/GHGen/Detector.pm  view on Meta::CPAN

package App::GHGen::Detector;

use v5.36;
use strict;
use warnings;

use Path::Tiny;

use Exporter 'import';
our @EXPORT_OK = qw(
	detect_project_type
	get_project_indicators
);

our $VERSION = '0.06';

=head1 NAME

App::GHGen::Detector - Detect project type from repository contents

=head1 SYNOPSIS

	use App::GHGen::Detector qw(detect_project_type);

	my $type = detect_project_type();
	# Returns: 'perl', 'node', 'python', etc.

=head1 FUNCTIONS

=head2 detect_project_type()

Detect the project type by examining indicator files in the current directory.

=head3 Purpose

Score the current working directory against ten known project types
(perl, node, python, rust, go, ruby, php, java, cpp, docker) and return
the highest-scoring type.

=head3 Arguments

None.

=head3 Returns

In scalar context: the type string (e.g. C<'perl'>) of the best match, or
C<undef> when no type scores above zero.

In list context: a list of C<{ type => Str, score => Int, indicators => [] }>
hash references sorted by descending score.

=head3 Side Effects

Reads the filesystem; results depend on the current working directory.

=head3 Usage Example

    use App::GHGen::Detector qw(detect_project_type);
    my $type = detect_project_type();
    say $type // 'unknown';

=head3 API SPECIFICATION

=head4 Input

    # No parameters.

=head4 Output (scalar context)

    { type => 'scalar' }   # may be undef

=head4 Output (list context)

    { type => 'array', element => { type => 'hashref' } }

=head3 FORMAL SPECIFICATION

    detect_project_type : → ℤ* ∪ { ⊥ } | seq Detection

    Detection ≔ { type: ProjectType, score: ℕ, indicators: seq ℤ* }
    detections ≔ [ { type: t, score: score(t) } ∣ t ∈ Types, score(t) > 0 ]
    ranked     ≔ sort desc-score detections

    scalar context: ranked = ∅ → ⊥  |  ranked ≠ ∅ → ranked[0].type
    list context:   ranked

=cut

sub detect_project_type() {
	my @detections;

	# Check for each project type
	for my $type (qw(perl node python rust go ruby php java cpp docker)) {
		my $detector = "_detect_$type";
		my $score = __PACKAGE__->can($detector)->();
		push @detections, { type => $type, score => $score, indicators => [] } if $score > 0;
	}

	# Sort by score (highest first)
	@detections = sort { $b->{score} <=> $a->{score} } @detections;

	return undef unless @detections;
	return wantarray ? @detections : $detections[0]->{type};
}

=head2 get_project_indicators($type)

Return the indicator file patterns associated with a given project type.

=head3 Purpose

Provide a human-readable or programmatic list of the files and patterns that
the detector uses to score each project type.

=head3 Arguments

=over 4

=item C<$type> (Str, optional)

A project type string such as C<'perl'>, C<'node'>, C<'python'>, etc.
When omitted or C<undef>, the full indicator table is returned.

=back

=head3 Returns

When C<$type> is given and known: an array reference of strings (file names /
glob patterns).

When C<$type> is C<undef> or omitted: a hash reference mapping every known
type to its indicator list.

When C<$type> is given but unknown: C<undef>.

=head3 Side Effects

None.  Pure lookup function.

=head3 Usage Example

    my $indicators = get_project_indicators('perl');
    say for @$indicators;

    my $all = get_project_indicators();
    for my $type (sort keys %$all) {
        say "$type: " . join(', ', @{$all->{$type}});
    }

=head3 API SPECIFICATION

=head4 Input

    { type => { type => 'scalar', optional => 1 } }

=head4 Output

    known type  → { type => 'arrayref' }
    all types   → { type => 'hashref'  }
    unknown type → undef

=head3 FORMAL SPECIFICATION

    get_project_indicators : ℤ* ∪ { ⊥ } → seq ℤ* ∪ (ℤ* → seq ℤ*) ∪ { ⊥ }

    t given ∧ t ∈ KnownTypes → indicators[t]      (ArrayRef)
    t given ∧ t ∉ KnownTypes → ⊥                  (undef)
    t absent                  → indicators          (HashRef of all types)

=cut

sub get_project_indicators($type = undef) {
	my %indicators = (
		perl => [
			'cpanfile', 'dist.ini', 'Makefile.PL', 'Build.PL',
			'lib/*.pm', 't/*.t', 'META.json', 'META.yml'
		],
		node => [
			'package.json', 'package-lock.json', 'yarn.lock',
			'node_modules/', 'tsconfig.json', '.npmrc'
		],
		python => [
			'requirements.txt', 'setup.py', 'pyproject.toml',
			'Pipfile', 'poetry.lock', 'setup.cfg', 'tox.ini'
		],
		rust => [
			'Cargo.toml', 'Cargo.lock', 'src/main.rs',
			'src/lib.rs', 'rust-toolchain.toml'
		],
		go => [
			'go.mod', 'go.sum', 'main.go', '*.go'
		],
		ruby => [
			'Gemfile', 'Gemfile.lock', 'Rakefile',
			'.ruby-version', 'config.ru'
		],
		php => [
			'composer.json', 'composer.lock', 'phpunit.xml',
			'phpunit.xml.dist', 'src/', 'tests/'
		],
		java => [
			'pom.xml', 'build.gradle', 'build.gradle.kts',
			'gradlew', 'mvnw', 'src/main/java/'
		],
		cpp => [
			'CMakeLists.txt', 'Makefile', 'configure.ac',
			'*.cpp', '*.hpp', '*.cc', '*.h'



( run in 0.610 second using v1.01-cache-2.11-cpan-9169edd2b0e )