├── .gitignore ├── .perltidyrc ├── Changes ├── LICENSE ├── README ├── api.psgi ├── app.psgi ├── author └── bin │ ├── copy_assets │ ├── tweak_bootstrap │ └── update_cpanfile ├── bin ├── daily_tasks ├── grep ├── hourly_tasks ├── maint │ ├── check_revision │ ├── migrate │ ├── setup │ ├── show_ddl │ └── status ├── run ├── run_all └── run_tasks ├── cpanfile ├── lib └── WWW │ ├── CPANTS.pm │ └── CPANTS │ ├── API.pm │ ├── API │ ├── Context.pm │ ├── Controller │ │ ├── OpenApi.pm │ │ └── Root.pm │ ├── Model.pm │ ├── Model │ │ ├── Acme.pm │ │ ├── Acme │ │ │ └── CPANAuthors │ │ │ │ ├── Authors.pm │ │ │ │ ├── Modules.pm │ │ │ │ └── Stats │ │ │ │ ├── ActiveAuthors.pm │ │ │ │ ├── NewAuthors.pm │ │ │ │ ├── NewReleases.pm │ │ │ │ └── Releases.pm │ │ ├── V4.pm │ │ ├── V4 │ │ │ ├── Search.pm │ │ │ └── Table │ │ │ │ ├── CPANDistributionsBy.pm │ │ │ │ ├── FailsIn.pm │ │ │ │ ├── Ranking.pm │ │ │ │ ├── Recent.pm │ │ │ │ ├── RecentBy.pm │ │ │ │ ├── ReleasesOf.pm │ │ │ │ └── ReverseDependenciesOf.pm │ │ ├── V5.pm │ │ └── V5 │ │ │ ├── Author.pm │ │ │ ├── Author │ │ │ ├── CPANDistributions.pm │ │ │ ├── Feed.pm │ │ │ └── RecentReleases.pm │ │ │ ├── Dist │ │ │ ├── Common.pm │ │ │ ├── Errors.pm │ │ │ ├── Files.pm │ │ │ ├── Metadata.pm │ │ │ ├── Overview.pm │ │ │ ├── Prereq.pm │ │ │ ├── Releases.pm │ │ │ └── UsedBy.pm │ │ │ ├── Kwalitee.pm │ │ │ ├── Kwalitee │ │ │ ├── Fail.pm │ │ │ └── Indicator.pm │ │ │ ├── Ranking.pm │ │ │ ├── Recent.pm │ │ │ ├── Release │ │ │ ├── Common.pm │ │ │ ├── Errors.pm │ │ │ ├── Files.pm │ │ │ ├── Metadata.pm │ │ │ ├── Overview.pm │ │ │ ├── Prereq.pm │ │ │ ├── Releases.pm │ │ │ └── UsedBy.pm │ │ │ ├── Search.pm │ │ │ └── Status.pm │ ├── Plugin │ │ └── DoesAccept.pm │ └── Util │ │ ├── Format.pm │ │ └── Validate.pm │ ├── Bin │ ├── Context.pm │ ├── Runner.pm │ ├── Task.pm │ └── Task │ │ ├── Acme │ │ ├── UpdateModules.pm │ │ └── UpdateStats.pm │ │ ├── Analyze.pm │ │ ├── Analyze │ │ ├── UpdateErrors.pm │ │ ├── UpdateKwalitee.pm │ │ ├── UpdateProvides.pm │ │ ├── UpdateRequiresAndUses.pm │ │ └── UpdateResources.pm │ │ ├── AnalyzeAll.pm │ │ ├── Enqueue.pm │ │ ├── EnqueueTasks.pm │ │ ├── Grep.pm │ │ ├── Maint │ │ ├── CheckRevision.pm │ │ ├── CheckVersions.pm │ │ ├── Cleanup.pm │ │ ├── Migrate.pm │ │ ├── Setup.pm │ │ ├── ShowDDL.pm │ │ └── ShowStatus.pm │ │ ├── Notify.pm │ │ ├── ParseRecentJson.pm │ │ ├── PostProcess.pm │ │ ├── PostProcess │ │ ├── UpdateAuthorStats.pm │ │ ├── UpdateCaches.pm │ │ ├── UpdateRanking.pm │ │ ├── UpdateReverseDependency.pm │ │ └── UpdateSecurityAdvisories.pm │ │ ├── RegisterNew.pm │ │ ├── RunTasks.pm │ │ ├── Traverse.pm │ │ ├── Traverse │ │ ├── UpdateAuthorJson.pm │ │ ├── UpdateCPANStatus.pm │ │ └── UpdateDistributions.pm │ │ ├── UpdateCPANIndices.pm │ │ └── UpdateCPANIndices │ │ ├── PackagesDetails.pm │ │ ├── Permissions.pm │ │ └── Whois.pm │ ├── DB.pm │ ├── DB │ ├── Handle.pm │ ├── Handle │ │ ├── MySQL.pm │ │ └── SQLite.pm │ ├── Table.pm │ └── Table │ │ ├── AcmeAuthors.pm │ │ ├── AcmeModules.pm │ │ ├── AcmeStats.pm │ │ ├── Analysis.pm │ │ ├── Authors.pm │ │ ├── Distributions.pm │ │ ├── Errors.pm │ │ ├── Kwalitee.pm │ │ ├── PackagesDetails.pm │ │ ├── Permissions.pm │ │ ├── Provides.pm │ │ ├── Queue.pm │ │ ├── RequiresAndUses.pm │ │ ├── Resources.pm │ │ ├── Tasks.pm │ │ ├── Uploads.pm │ │ └── Whois.pm │ ├── Model │ ├── Archive.pm │ ├── CPAN.pm │ ├── CPAN │ │ ├── Packages.pm │ │ ├── Perms.pm │ │ ├── Recent.pm │ │ └── Whois.pm │ ├── Kwalitee.pm │ ├── PidFile.pm │ ├── Revision.pm │ ├── TableGuard.pm │ ├── TempDir.pm │ └── Timer.pm │ ├── Role │ ├── API │ │ └── Model │ │ │ └── V5 │ │ │ ├── Author │ │ │ └── Status.pm │ │ │ └── Dist │ │ │ └── GetUid.pm │ ├── CPAN │ │ ├── Index.pm │ │ ├── Indices.pm │ │ └── Path.pm │ ├── Logger.pm │ ├── OpenApiSchemaBuilder.pm │ ├── Options.pm │ └── Task │ │ └── FixAnalysis.pm │ ├── Test.pm │ ├── Test │ ├── Fixture.pm │ ├── Selenium.pm │ └── TestPAN.pm │ ├── Util │ ├── CoreList.pm │ ├── Datetime.pm │ ├── Diff.pm │ ├── Distname.pm │ ├── HideInternal.pm │ ├── JSON.pm │ ├── Loader.pm │ ├── Parallel.pm │ ├── Path.pm │ ├── PathUid.pm │ └── Version.pm │ ├── Web.pm │ └── Web │ ├── Context.pm │ ├── Controller.pm │ ├── Controller │ ├── Author.pm │ ├── Dist.pm │ ├── Home.pm │ ├── Kwalitee.pm │ ├── Ranking.pm │ ├── Recent.pm │ ├── Release.pm │ ├── Root.pm │ ├── Search.pm │ └── Stats.pm │ ├── Plugin │ ├── Helpers.pm │ └── Hooks.pm │ └── Util │ ├── Badge.pm │ └── URL.pm ├── package.json ├── public ├── bootstrap │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js ├── cpants │ └── css │ │ └── cpants.css ├── datatables-responsive │ ├── css │ │ ├── responsive.bootstrap.css │ │ ├── responsive.dataTables.css │ │ ├── responsive.foundation.css │ │ └── responsive.jqueryui.css │ └── js │ │ ├── dataTables.responsive.js │ │ ├── responsive.bootstrap.js │ │ ├── responsive.foundation.js │ │ └── responsive.jqueryui.js ├── datatables │ ├── css │ │ ├── jquery.dataTables.css │ │ └── jquery.dataTables.min.css │ ├── images │ │ ├── Sorting icons.psd │ │ ├── favicon.ico │ │ ├── sort_asc.png │ │ ├── sort_asc_disabled.png │ │ ├── sort_both.png │ │ ├── sort_desc.png │ │ └── sort_desc_disabled.png │ └── js │ │ ├── jquery.dataTables.js │ │ └── jquery.dataTables.min.js ├── font-awesome │ ├── css │ │ ├── font-awesome.css │ │ ├── font-awesome.css.map │ │ └── font-awesome.min.css │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── highcharts │ ├── adapters │ │ ├── standalone-framework.js │ │ └── standalone-framework.src.js │ ├── highcharts.js │ ├── modules │ │ ├── broken-axis.js │ │ ├── broken-axis.src.js │ │ ├── canvas-tools.js │ │ ├── canvas-tools.src.js │ │ ├── data.js │ │ ├── data.src.js │ │ ├── drilldown.js │ │ ├── drilldown.src.js │ │ ├── exporting.js │ │ ├── exporting.src.js │ │ ├── funnel.js │ │ ├── funnel.src.js │ │ ├── heatmap.js │ │ ├── heatmap.src.js │ │ ├── no-data-to-display.js │ │ ├── no-data-to-display.src.js │ │ ├── solid-gauge.js │ │ ├── solid-gauge.src.js │ │ ├── treemap.js │ │ └── treemap.src.js │ └── themes │ │ ├── dark-blue.js │ │ ├── dark-green.js │ │ ├── dark-unica.js │ │ ├── gray.js │ │ ├── grid-light.js │ │ ├── grid.js │ │ ├── sand-signika.js │ │ └── skies.js ├── html5shiv │ ├── html5shiv-printshiv.js │ ├── html5shiv-printshiv.min.js │ ├── html5shiv.js │ └── html5shiv.min.js ├── jquery-jsonview │ ├── jquery.jsonview.css │ └── jquery.jsonview.js ├── jquery-migrate │ ├── jquery-migrate.js │ ├── jquery-migrate.min.js │ └── jquery-migrate.min.map ├── jquery │ ├── core.js │ ├── jquery.js │ ├── jquery.min.js │ ├── jquery.min.map │ ├── jquery.slim.js │ ├── jquery.slim.min.js │ └── jquery.slim.min.map ├── respond-js │ ├── respond.matchmedia.addListener.min.js │ ├── respond.matchmedia.addListener.src.js │ ├── respond.min.js │ └── respond.src.js ├── sprintf-js │ ├── angular-sprintf.min.js │ ├── angular-sprintf.min.js.map │ ├── sprintf.min.js │ └── sprintf.min.js.map ├── strftime │ └── strftime-min.js └── swagger-ui │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ └── swagger-ui.css.map ├── t ├── 00_load.t ├── 10_tasks.t ├── analysis │ └── required_perl.t ├── api_model │ ├── acme │ │ └── cpan_authors │ │ │ ├── authors.t │ │ │ ├── modules.t │ │ │ └── stats │ │ │ ├── active_authors.t │ │ │ ├── new_authors.t │ │ │ ├── new_releases.t │ │ │ └── releases.t │ ├── v4 │ │ ├── search.t │ │ └── table │ │ │ ├── cpan_distributions_by.t │ │ │ ├── fails_in.t │ │ │ ├── ranking.t │ │ │ ├── recent.t │ │ │ ├── recent_by.t │ │ │ ├── releases_of.t │ │ │ └── reverse_dependencies_of.t │ └── v5 │ │ ├── author.t │ │ ├── author │ │ ├── cpan_distributions.t │ │ ├── feed.t │ │ └── recent_releases.t │ │ ├── dist │ │ ├── common.t │ │ ├── errors.t │ │ ├── files.t │ │ ├── metadata.t │ │ ├── overview.t │ │ ├── prereq.t │ │ ├── releases.t │ │ └── used_by.t │ │ ├── kwalitee.t │ │ ├── kwalitee │ │ ├── fail.t │ │ └── indicator.t │ │ ├── ranking.t │ │ ├── recent.t │ │ ├── search.t │ │ └── status.t ├── kwalitee │ ├── buildtool_not_executable.t │ ├── configure_prereq_matches_use.t │ ├── consistent_version.t │ ├── distname_matches_name_in_meta.t │ ├── extractable.t │ ├── extracts_nicely.t │ ├── has_abstract_in_pod.t │ ├── has_buildtool.t │ ├── has_changelog.t │ ├── has_human_readable_license.t │ ├── has_known_license_in_source_file.t │ ├── has_license_in_source_file.t │ ├── has_manifest.t │ ├── has_meta_json.t │ ├── has_meta_yml.t │ ├── has_proper_version.t │ ├── has_readme.t │ ├── has_separate_license_file.t │ ├── has_tests.t │ ├── has_tests_in_t_dir.t │ ├── has_version.t │ ├── main_module_version_matches_dist_version.t │ ├── manifest_matches_dist.t │ ├── meta_json_conforms_to_known_spec.t │ ├── meta_json_is_parsable.t │ ├── meta_yml_conforms_to_known_spec.t │ ├── meta_yml_declares_perl_version.t │ ├── meta_yml_has_license.t │ ├── meta_yml_has_provides.t │ ├── meta_yml_has_repository_resource.t │ ├── meta_yml_is_parsable.t │ ├── no_abstract_stub_in_pod.t │ ├── no_broken_auto_install.t │ ├── no_broken_module_install.t │ ├── no_dot_dirs.t │ ├── no_dot_underscore_files.t │ ├── no_files_to_be_skipped.t │ ├── no_generated_files.t │ ├── no_invalid_versions.t │ ├── no_local_dirs.t │ ├── no_maniskip_error.t │ ├── no_missing_files_in_provides.t │ ├── no_mymeta_files.t │ ├── no_pax_headers.t │ ├── no_pod_errors.t │ ├── no_stdin_for_prompting.t │ ├── no_symlinks.t │ ├── no_unauthorized_packages.t │ ├── portable_filenames.t │ ├── prereq_matches_use.t │ ├── proper_libs.t │ ├── test_prereq_matches_use.t │ ├── use_strict.t │ ├── use_warnings.t │ └── valid_signature.t ├── root.t └── web │ ├── author.t │ ├── author │ └── ignore_backpan_latest.t │ ├── dist.t │ ├── dist │ ├── latest_dev_and_stable.t │ └── pod_escapes.t │ ├── home.t │ ├── kwalitee.t │ ├── ranking.t │ ├── recent.t │ ├── search.t │ └── stats.t ├── templates ├── _notice.html.ep ├── author.html.ep ├── author │ └── _sidebar.html.ep ├── dist │ ├── _sidebar.html.ep │ ├── _tab.html.ep │ ├── errors.html.ep │ ├── files.html.ep │ ├── metadata.html.ep │ ├── overview.html.ep │ ├── prereq.html.ep │ ├── releases.html.ep │ └── used_by.html.ep ├── exception.html.ep ├── home.html.ep ├── kwalitee.html.ep ├── kwalitee │ ├── _sidebar.html.ep │ ├── fail.html.ep │ └── indicator.html.ep ├── layouts │ └── layout.html.ep ├── mojolicious │ └── plugin │ │ └── openapi │ │ └── layout.html.ep ├── not_found.html.ep ├── ranking │ ├── _sidebar.html.ep │ ├── five_or_more.html.ep │ └── less_than_five.html.ep ├── recent.html.ep ├── search.html.ep ├── stats.html.ep └── stats │ ├── _sidebar.html.ep │ └── module_install.html.ep ├── web.psgi ├── xt ├── api_model.t ├── cpanfile.t ├── kwalitee.t ├── perlcritic.t ├── util │ └── validate.t └── web.t └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.old 3 | /tmp/ 4 | /etc/ 5 | /log/ 6 | /db/ 7 | /backup/ 8 | /local/ 9 | /cover_db/ 10 | /nytprof* 11 | /etc/config.pl 12 | blib/ 13 | tmp/ 14 | Makefile 15 | !Makefile.PL 16 | pm_to_blib 17 | MYMETA.* 18 | *.bat 19 | /*.pl 20 | /*.zip 21 | .perl-version 22 | $~* 23 | /node_modules/ 24 | /qc.dat 25 | /public/img/ 26 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | -b 2 | -bbt=0 3 | -bext=/ 4 | -blbs=0 5 | -boa 6 | -boc 7 | -bok 8 | -bol 9 | -bot 10 | -bt=1 11 | -cab=3 12 | -ce 13 | -ci=4 14 | -cti=0 15 | -i=4 16 | -kis 17 | -l=0 18 | -nbbc 19 | -nicb 20 | -nicp 21 | -nsfs 22 | -nsts 23 | -ohbr 24 | -opr 25 | -osbr 26 | -otr 27 | -pt=2 28 | -sbt=2 29 | -sct 30 | -sot 31 | -wn 32 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for WWW-CPANTS 2 | 3 | 5.00 2020/07/19 4 | - public release 5 | 6 | 4.00 2016/05/15 7 | - public release 8 | 9 | 1.00 2012/12/24 10 | - public release 11 | 12 | 0.01 2012/08/22 13 | - initial import 14 | - forked from Module-CPANTS-Site 15 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | WWW-CPANTS 2 | ========== 3 | 4 | Yet another CPANTS frontend. 5 | 6 | HOW TO USE 7 | ---------- 8 | 9 | 1. Clone git://github.com/cpants/www-cpants into somewhere. 10 | 2. Install dependencies by "cpanm --installdeps ." or "carton install" 11 | 3. Run "prove -lwr t/" to make sure if it works. 12 | 4. Run "perl bin/run_all --workers 3". 13 | 5. Run "plackup (or starman, etc) app.psgi". 14 | 6. Register bin/run_all to your crontab. 15 | 16 | I advise you not to "install" this into your Perl lib directories. 17 | 18 | COPYRIGHT AND LICENSE 19 | --------------------- 20 | 21 | Copyright (C) 2012- Kenichi Ishigaki 22 | 23 | This program is free software; you can redistribute it and/or 24 | modify it under the same terms as Perl itself. 25 | 26 | -------------------------------------------------------------------------------- /api.psgi: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use FindBin; 3 | use lib "$FindBin::Bin/lib"; 4 | use lib glob "extlib/*/lib"; 5 | use WWW::CPANTS::API; 6 | use Plack::Builder; 7 | 8 | my $api = WWW::CPANTS::API->new; 9 | 10 | builder { 11 | enable 'ReverseProxy'; 12 | $api->start('psgi'); 13 | }; 14 | -------------------------------------------------------------------------------- /app.psgi: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use FindBin; 3 | use lib "$FindBin::Bin/lib"; 4 | use lib glob "extlib/*/lib"; 5 | use WWW::CPANTS::Web; 6 | use WWW::CPANTS::API; 7 | use Plack::Builder; 8 | use Plack::App::URLMap; 9 | 10 | my $web = WWW::CPANTS::Web->new; 11 | my $api = WWW::CPANTS::API->new; 12 | my $map = Plack::App::URLMap->new; 13 | $map->map("/" => $web->start('psgi')); 14 | $map->map("/api" => $api->start('psgi')); 15 | 16 | builder { 17 | enable 'ReverseProxy'; 18 | $map->to_app; 19 | }; 20 | -------------------------------------------------------------------------------- /bin/daily_tasks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks(qw( 9 | EnqueueTasks 10 | Maint::CheckRevision 11 | Traverse 12 | PostProcess::UpdateReverseDependency 13 | PostProcess::UpdateSecurityAdvisories 14 | PostProcess::UpdateCaches 15 | Acme::UpdateModules 16 | Acme::UpdateStats 17 | Maint::Cleanup 18 | )); 19 | -------------------------------------------------------------------------------- /bin/grep: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks('Grep'); 9 | -------------------------------------------------------------------------------- /bin/hourly_tasks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks(qw( 9 | EnqueueTasks 10 | UpdateCPANIndices 11 | ParseRecentJson 12 | Enqueue 13 | AnalyzeAll 14 | PostProcess::UpdateAuthorStats 15 | PostProcess::UpdateRanking 16 | PostProcess::UpdateCaches 17 | )); 18 | -------------------------------------------------------------------------------- /bin/maint/check_revision: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks('Maint::CheckRevision'); 9 | -------------------------------------------------------------------------------- /bin/maint/migrate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks('Maint::Migrate'); 9 | -------------------------------------------------------------------------------- /bin/maint/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks('Maint::Setup'); 9 | -------------------------------------------------------------------------------- /bin/maint/show_ddl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks('Maint::ShowDDL'); 9 | -------------------------------------------------------------------------------- /bin/maint/status: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks('Maint::ShowStatus'); 9 | -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks; 9 | -------------------------------------------------------------------------------- /bin/run_all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks(qw( 9 | Maint::Setup 10 | Maint::CheckRevision 11 | UpdateCPANIndices 12 | Traverse 13 | Enqueue 14 | AnalyzeAll 15 | PostProcess 16 | Acme::UpdateModules 17 | Acme::UpdateStats 18 | Maint::Cleanup 19 | Notify 20 | )); 21 | -------------------------------------------------------------------------------- /bin/run_tasks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use FindBin; 5 | use lib "$FindBin::Bin/../lib"; 6 | use WWW::CPANTS::Bin::Runner; 7 | 8 | WWW::CPANTS::Bin::Runner->new->run_tasks('RunTasks'); 9 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API; 2 | 3 | use Mojo::Base 'Mojolicious', -signatures; 4 | use Digest::MD5 qw/md5_hex/; 5 | use WWW::CPANTS::Util::Loader; 6 | use WWW::CPANTS::API::Context; 7 | use String::CamelCase qw/camelize/; 8 | use Mock::MonkeyPatch; 9 | use JSON::Validator; 10 | use JSON::Validator::Schema::OpenAPIv3; 11 | 12 | has 'ctx' => \&_build_ctx; 13 | 14 | sub startup ($app) { 15 | if ($ENV{CPANTS_API_DEBUG} or $^O eq 'MSWin32') { 16 | $app->mode('development'); 17 | } else { 18 | $app->mode('production'); 19 | } 20 | $app->secrets([md5_hex($$ . time)]); 21 | 22 | $app->{mock} = Mock::MonkeyPatch->patch( 23 | 'JSON::Validator::Schema::OpenAPIv3::base_url' => sub ($self, $url = undef) { 24 | # No need to modify on the fly 25 | return Mojo::URL->new($self->get('/servers/0/url') || ''); 26 | }, 27 | ); 28 | 29 | my $r = $app->routes->under('/')->to('root#check_maintenance'); 30 | 31 | for my $path (qw/v5 v4 acme/) { 32 | my %conf = ( 33 | schema => 'v3', 34 | url => $app->build_openapi_schema($path), 35 | route => $r->under("/$path")->to("open_api#$path"), 36 | ); 37 | $conf{default_response_code} = []; 38 | $app->plugin(OpenAPI => \%conf); 39 | } 40 | 41 | $r->get('/')->to('open_api#root'); 42 | 43 | $app->plugin('WWW::CPANTS::API::Plugin::DoesAccept'); 44 | } 45 | 46 | sub _build_ctx ($self) { 47 | WWW::CPANTS::API::Context->new; 48 | } 49 | 50 | sub build_openapi_schema ($self, $path) { 51 | my $name = ucfirst($path); 52 | my $class = "WWW::CPANTS::API::Model::$name"; 53 | use_module($class)->build_schema($self->ctx); 54 | } 55 | 56 | 1; 57 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Context.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Context; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use WWW::CPANTS; 6 | use WWW::CPANTS::DB; 7 | use WWW::CPANTS::Util::Loader; 8 | use WWW::CPANTS::Model::Kwalitee; 9 | use Mojo::URL; 10 | 11 | with qw( 12 | WWW::CPANTS::Role::Logger 13 | WWW::CPANTS::Role::Options 14 | ); 15 | 16 | our @OPTIONS = ( 17 | 'trace:1', 18 | 'verbose|v', 19 | ); 20 | 21 | has 'db' => \&_build_db; 22 | has 'quiet' => \&_build_quiet; 23 | has 'kwalitee' => \&_build_kwalitee; 24 | has 'api_base' => \&_build_api_base; 25 | 26 | sub _build_db ($self) { 27 | WWW::CPANTS::DB->new(trace => $self->trace); 28 | } 29 | 30 | sub _build_quiet ($self) { 31 | return if $self->verbose; 32 | WWW::CPANTS->instance->quiet; 33 | } 34 | 35 | sub _build_kwalitee ($self) { 36 | WWW::CPANTS::Model::Kwalitee->new; 37 | } 38 | 39 | sub _build_api_base ($self) { 40 | my $base = WWW::CPANTS->instance->config->{api_base} // '/api/'; 41 | $base .= '/' unless substr($base, -1, 1) eq '/'; 42 | Mojo::URL->new($base); 43 | } 44 | 45 | 1; 46 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Controller/Root.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Controller::Root; 2 | 3 | use Mojo::Base 'Mojolicious::Controller', -signatures; 4 | use WWW::CPANTS::Util::JSON; 5 | use WWW::CPANTS; 6 | 7 | sub check_maintenance ($c) { 8 | if (WWW::CPANTS->instance->is_under_maintenance) { 9 | $c->stash("cpants.has_notice" => 1); 10 | $c->stash("cpants.notice_maintenance" => 1); 11 | } 12 | my $json = slurp_json('Task::AnalyzeAll'); 13 | $c->stash('last_analyzed', $json->{last_executed}); 14 | return 1; 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Model/Acme.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Model::Acme; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base 'WWW::CPANTS::API::Model', -signatures; 5 | use constant api_info => +{ 6 | title => 'Acme::CPANAuthors API', 7 | version => '1.0', 8 | description => 'This API is mostly used internally to build the [Acme::CPANAuthors](https://acme.cpanauthors.org/) web frontend.', 9 | }; 10 | 11 | with qw/WWW::CPANTS::Role::OpenApiSchemaBuilder/; 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Model/V4.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Model::V4; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base 'WWW::CPANTS::API::Model', -signatures; 5 | use constant api_info => +{ 6 | title => 'CPANTS API', 7 | version => '4.0; deprecated', 8 | description => 'This API is mostly used internally to build the older version of [CPANTS](https://cpants.cpanauthors.org/) web frontend.', 9 | }; 10 | 11 | with qw/WWW::CPANTS::Role::OpenApiSchemaBuilder/; 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Model/V5.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Model::V5; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base 'WWW::CPANTS::API::Model', -signatures; 5 | use constant api_info => +{ 6 | title => 'CPANTS API', 7 | version => '5.0', 8 | description => 'This API is mostly used internally to build the [CPANTS](https://cpants.cpanauthors.org/) web frontend.', 9 | }; 10 | 11 | with qw/WWW::CPANTS::Role::OpenApiSchemaBuilder/; 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Model/V5/Release/Common.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Model::V5::Release::Common; 2 | 3 | use Mojo::Base 'WWW::CPANTS::API::Model::V5::Dist::Common', -signatures; 4 | 5 | 1; 6 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Model/V5/Status.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Model::V5::Status; 2 | 3 | use Mojo::Base 'WWW::CPANTS::API::Model', -signatures; 4 | use WWW::CPANTS::API::Util::Validate; 5 | use WWW::CPANTS::Util::JSON; 6 | 7 | sub operation ($self) { 8 | +{ 9 | tags => ['Status'], 10 | description => 'Returns CPANTS status', 11 | parameters => [], 12 | responses => { 13 | 200 => { 14 | description => 'CPANTS status', 15 | content => { 16 | 'application/json' => { 17 | schema => { 18 | type => 'object', 19 | properties => { 20 | maintenance => { type => 'integer' }, 21 | last_analyzed => { 22 | description => 'Epoch time', 23 | type => 'integer', 24 | }, 25 | }, 26 | }, 27 | }, 28 | }, 29 | }, 30 | }, 31 | }; 32 | } 33 | 34 | sub _load ($self, $params = {}) { 35 | my $maintenance = WWW::CPANTS->instance->is_under_maintenance; 36 | 37 | my $json = slurp_json('Task::AnalyzeAll'); 38 | my $last_analyzed = $json->{last_executed}; 39 | 40 | return { 41 | maintenance => $maintenance, 42 | last_analyzed => $last_analyzed, 43 | }; 44 | } 45 | 46 | 1; 47 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Plugin/DoesAccept.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Plugin::DoesAccept; 2 | 3 | use Mojo::Base 'Mojolicious::Plugin', -signatures; 4 | 5 | sub register ($self, $app, $conf) { 6 | $app->helper(does_accept => \&does_accept); 7 | } 8 | 9 | sub does_accept ($c, $type) { 10 | my @accepts = split ',', $c->req->headers->accept // ''; 11 | for my $accept (@accepts) { 12 | return 1 if $accept =~ /\A$type(;|$)/; 13 | } 14 | return; 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Util/Format.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Util::Format; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | 6 | our @EXPORT = qw( 7 | percent decimal kwalitee_score 8 | release_availability 9 | ); 10 | 11 | sub kwalitee_score ($score) { $score ? 0 + sprintf '%.2f', $score : 0 } 12 | 13 | sub decimal ($decimal) { 14 | 0 + sprintf '%0.2f', int(($decimal || 0) * 100 + 0.5) / 100; 15 | } 16 | 17 | sub percent ($numerator, $denominator) { 18 | decimal(($numerator || 0) / ($denominator || 100) * 100); 19 | } 20 | 21 | sub release_availability ($release) { 22 | return "" unless ref $release eq 'HASH'; 23 | return "Latest Dev" if $release->{latest} && !$release->{stable}; 24 | return "Latest" if $release->{latest} && $release->{stable}; 25 | return "CPAN" if $release->{cpan}; 26 | return "BackPAN"; 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/API/Util/Validate.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::API::Util::Validate; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | 6 | our @EXPORT = qw( 7 | is_int is_alphanum is_dist is_path is_pause_id 8 | is_availability_type 9 | ); 10 | 11 | sub is_int ($value, $length = 19) { 12 | return unless defined $value; 13 | return unless $value =~ /\A[0-9]+\z/ && length($value) <= $length; 14 | return $value; 15 | } 16 | 17 | sub is_alphanum ($value, $length = 255) { 18 | return unless defined $value; 19 | return unless $value =~ /\A[0-9A-Za-z_\.\-\+]+\z/ && length($value) <= $length; 20 | return $value; 21 | } 22 | 23 | sub is_dist ($value, $length = 255) { 24 | return unless defined $value; 25 | return unless $value =~ /\A[0-9A-Za-z_\.\-\+]+\z/ && length($value) <= $length; 26 | return $value; 27 | } 28 | 29 | sub is_path ($value, $length = 255) { 30 | return unless defined $value; 31 | return unless $value =~ /\A[0-9A-Za-z_\.\-\/\+]+\z/ && length($value) <= $length; 32 | return $value; 33 | } 34 | 35 | sub is_pause_id ($value, $length = 9) { 36 | return unless defined $value; 37 | return unless length($value) <= $length; 38 | $value = uc $value; 39 | return unless $value =~ /\A[A-Z][A-Z0-9][0-9A-Z\-]*\z/; 40 | return $value; 41 | } 42 | 43 | my %availability = map { $_ => 1 } qw/latest cpan backpan/; 44 | sub is_availability_type ($value) { 45 | return unless defined $value; 46 | return $value if exists $availability{$value}; 47 | } 48 | 49 | 1; 50 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Context.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Context; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use WWW::CPANTS; 6 | use WWW::CPANTS::DB; 7 | use WWW::CPANTS::Util::Loader; 8 | use WWW::CPANTS::Model::CPAN; 9 | use WWW::CPANTS::Model::Kwalitee; 10 | use Path::Tiny (); 11 | 12 | with qw( 13 | WWW::CPANTS::Role::Logger 14 | WWW::CPANTS::Role::Options 15 | ); 16 | 17 | our @OPTIONS = ( 18 | 'dry_run|dry-run', 19 | 'all', 20 | 'force', 21 | 'trace:1', 22 | 'verbose|v', 23 | ); 24 | 25 | has 'db' => \&_build_db; 26 | has 'cpan' => \&_build_cpan; 27 | has 'backpan' => \&_build_backpan; 28 | has 'quiet' => \&_build_quiet; 29 | has 'kwalitee' => \&_build_kwalitee; 30 | 31 | sub _build_db ($self) { 32 | WWW::CPANTS::DB->new(trace => $self->trace); 33 | } 34 | 35 | sub _build_cpan ($self) { 36 | WWW::CPANTS::Model::CPAN->new( 37 | path => WWW::CPANTS->instance->cpan_path, 38 | ); 39 | } 40 | 41 | sub _build_backpan ($self) { 42 | WWW::CPANTS::Model::CPAN->new( 43 | path => WWW::CPANTS->instance->backpan_path, 44 | ); 45 | } 46 | 47 | sub _build_kwalitee ($self) { 48 | WWW::CPANTS::Model::Kwalitee->new; 49 | } 50 | 51 | sub _build_quiet ($self) { 52 | return if $self->verbose; 53 | WWW::CPANTS->instance->quiet; 54 | } 55 | 56 | sub new_db ($self) { 57 | $self->db(_build_db($self)); 58 | $self->db; 59 | } 60 | 61 | sub load_task ($self, $name) { 62 | my $task_class = use_module("WWW::CPANTS::Bin::Task::$name"); 63 | $task_class->new(name => $name, ctx => $self); 64 | } 65 | 66 | sub task_names ($self) { 67 | my @names = sort keys submodules("WWW::CPANTS::Bin::Task")->%*; 68 | \@names; 69 | } 70 | 71 | 1; 72 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Analyze/UpdateKwalitee.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Analyze::UpdateKwalitee; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 5 | use WWW::CPANTS::Util::Datetime qw/year/; 6 | 7 | our @READ = qw/Analysis/; 8 | our @WRITE = qw/Kwalitee/; 9 | 10 | with qw/WWW::CPANTS::Role::Task::FixAnalysis/; 11 | 12 | sub update ($self, $uid, $stash) { 13 | return unless exists $stash->{kwalitee}; 14 | 15 | return if $self->dry_run; 16 | 17 | $self->ctx->kwalitee->set_scores($stash); 18 | 19 | my $pause_id = $stash->{author}; 20 | my $distribution = $stash->{dist}; 21 | my $kwalitee = $stash->{kwalitee}; 22 | 23 | $self->db->table('Kwalitee')->update_kwalitee($uid, $pause_id, $distribution, year($stash->{released_epoch}), $kwalitee); 24 | } 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Analyze/UpdateResources.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Analyze::UpdateResources; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 5 | use WWW::CPANTS::Util::JSON; 6 | 7 | our @WRITE = qw/Resources/; 8 | 9 | with qw/WWW::CPANTS::Role::Task::FixAnalysis/; 10 | 11 | sub update ($self, $uid, $stash) { 12 | return unless exists $stash->{meta_yml}; 13 | return unless ref $stash->{meta_yml} eq 'HASH'; 14 | return unless exists $stash->{meta_yml}{resources}; 15 | return if $self->dry_run; 16 | 17 | my $pause_id = $stash->{author}; 18 | my $resources = $stash->{meta_yml}{resources}; 19 | $resources = {} unless ref $resources eq 'HASH'; 20 | 21 | my $repository = ''; 22 | if ($resources->{repository}) { 23 | if (ref $resources->{repository} eq 'HASH') { 24 | $repository = $resources->{repository}{web} // $resources->{repository}{url}; 25 | } elsif (!ref $resources->{repository}) { 26 | $repository = $resources->{repository}; 27 | } 28 | } 29 | my $bugtracker = ''; 30 | if ($resources->{bugtracker}) { 31 | if (ref $resources->{bugtracker} eq 'HASH') { 32 | $bugtracker = $resources->{bugtracker}{web}; 33 | } elsif (!ref $resources->{bugtracker}) { 34 | $bugtracker = $resources->{bugtracker}; 35 | } 36 | } 37 | 38 | $self->db->table('Resources')->update_resources($uid, $pause_id, encode_json($resources), $repository, $bugtracker); 39 | } 40 | 41 | 1; 42 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/EnqueueTasks.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::EnqueueTasks; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | our @READ = qw/Tasks/; 6 | our @WRITE = qw/Tasks/; 7 | 8 | sub run ($self, @args) { 9 | return unless @args; 10 | 11 | my @rows = map { 12 | +{ 13 | name => $_, 14 | created_at => time, 15 | pid => 0, 16 | } 17 | } @args; 18 | 19 | my $table = $self->db->table('Tasks'); 20 | 21 | for my $task (@{ $table->select_all_running_tasks // [] }) { 22 | my $pid = $task->{pid}; 23 | next if kill 0, $pid; 24 | $self->log(warn => "deleting $task->{name} from queue (pid $pid is gone)"); 25 | $table->force_remove($task->{id}); 26 | } 27 | 28 | $table->bulk_insert(\@rows, { ignore => 1 }); 29 | } 30 | 31 | 1; 32 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Maint/CheckRevision.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Maint::CheckRevision; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use WWW::CPANTS::Model::Revision; 5 | use version; 6 | 7 | has 'revision' => \&_build_revision; 8 | 9 | sub _build_revision ($self) { 10 | WWW::CPANTS::Model::Revision->new; 11 | } 12 | 13 | sub run ($self, @args) { 14 | my $diff = $self->revision->check; 15 | if ($diff) { 16 | $self->log(notice => $diff); 17 | $self->revision->save; 18 | } 19 | 20 | my $packages = $self->cpan->packages; 21 | my $versions = $self->revision->data->{versions}; 22 | for my $module (sort keys %$versions) { 23 | next if $module eq 'Archive::Zip'; ## Recent versions are broken 24 | my $installed = $versions->{$module}; 25 | my $latest = $packages->find($module)->{version}; 26 | if (version->parse($latest) > version->parse($installed)) { 27 | $self->log(notice => "$module: $latest (installed: $installed)"); 28 | } 29 | } 30 | } 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Maint/Cleanup.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Maint::Cleanup; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use WWW::CPANTS::Util::Datetime; 5 | use WWW::CPANTS; 6 | use Syntax::Keyword::Try; 7 | 8 | sub run ($self, @args) { 9 | my $app_root = WWW::CPANTS->instance->app_root; 10 | 11 | for my $target (qw/analyze test/) { 12 | my $tmp_dir = $app_root->child("tmp/$target"); 13 | next unless -d $tmp_dir; 14 | 15 | for my $dir ($tmp_dir->children) { 16 | my $path = $dir->relative($app_root); 17 | if ($dir->stat->mtime < days_ago(1)->epoch) { 18 | try { 19 | $dir->remove_tree({ safe => 0 }); 20 | $self->log(info => "Remove $path"); 21 | } catch { 22 | $self->log(error => "Failed to remove $path: $@"); 23 | }; 24 | } 25 | } 26 | } 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Maint/Migrate.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Maint::Migrate; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | sub run ($self, @args) { 6 | my $db = $self->db; 7 | for my $name ($db->table_names) { 8 | my $table = $db->table($name); 9 | next unless $table->is_setup; 10 | $table->migrate; 11 | } 12 | } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Maint/Setup.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Maint::Setup; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | sub run ($self, @args) { 6 | my $db = $self->db; 7 | for my $name ($db->table_names) { 8 | my $table = $db->table($name); 9 | if ($table->is_setup) { 10 | $self->log(debug => "$name is ready"); 11 | next; 12 | } 13 | 14 | $self->log(info => "setup $name"); 15 | $table->setup; 16 | $table->migrate; 17 | } 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Maint/ShowDDL.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Maint::ShowDDL; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | our @OPTIONS = ( 6 | 'table=s', 7 | ); 8 | 9 | sub run ($self, @args) { 10 | my $table = $self->table or return; 11 | say $self->db->table($table)->schema; 12 | } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Maint/ShowStatus.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Maint::ShowStatus; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use WWW::CPANTS::Util::Datetime; 5 | use WWW::CPANTS::Model::Revision; 6 | 7 | has 'revision' => \&_build_revision; 8 | 9 | sub _build_revision ($self) { 10 | WWW::CPANTS::Model::Revision->new; 11 | } 12 | 13 | sub run ($self, @args) { 14 | $self->show_task_status; 15 | $self->show_cpants_revision; 16 | $self->show_queue_status; 17 | } 18 | 19 | sub show_task_status ($self) { 20 | my $table = $self->db->table('Tasks'); 21 | return unless $table->is_setup; 22 | 23 | for my $task (@{ $table->select_all_running_tasks // [] }) { 24 | say sprintf "%s is running, started at %s (pid: %i)", 25 | $task->{name}, ymdhms($task->{started_at}), $task->{pid}; 26 | } 27 | for my $task (@{ $table->select_all_waiting_tasks // [] }) { 28 | say sprintf "%s is waiting, created at %s", 29 | $task->{name}, ymdhms($task->{created_at}); 30 | } 31 | } 32 | 33 | sub show_cpants_revision ($self) { 34 | my $cpants_revision = $self->revision->id; 35 | say "CPANTS revision: $cpants_revision"; 36 | 37 | my $table = $self->db->table('Analysis'); 38 | return unless $table->is_setup; 39 | 40 | if (my $uploads_with_older_revisions = $table->count_older_revisions($cpants_revision)) { 41 | say "$uploads_with_older_revisions rows with older revisions"; 42 | } 43 | } 44 | 45 | sub show_queue_status ($self) { 46 | my $table = $self->db->table('Queue'); 47 | return unless $table->is_setup; 48 | 49 | my $queued = $table->count; 50 | say "$queued rows in queue"; 51 | } 52 | 53 | 1; 54 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Notify.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Notify; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use Furl::HTTP; 5 | use IO::Socket::SSL; 6 | use JSON::XS; 7 | use WWW::CPANTS; 8 | 9 | sub run ($self, @args) { 10 | my $config = WWW::CPANTS->instance->config->{notify} or return; 11 | if (my $slack = $config->{slack}) { 12 | my $ua = Furl::HTTP->new( 13 | ssl_opts => { SSL_verify_mode => SSL_VERIFY_NONE() }, 14 | ); 15 | my $to = $slack->{to} // '@here'; 16 | my ($protocol, $code, $msg, $headers, $body) = $ua->post( 17 | $slack->{hook}, 18 | ['Content-Type' => 'application/json'], 19 | encode_json({ text => "<$to> $0 has ended" }), 20 | ); 21 | } 22 | } 23 | 24 | 1; 25 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/PostProcess.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::PostProcess; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | has subtasks => \&_build_subtasks; 6 | 7 | sub _build_subtasks ($self) { 8 | my @subtask_names = qw( 9 | UpdateAuthorStats 10 | UpdateSecurityAdvisories 11 | UpdateReverseDependency 12 | UpdateRanking 13 | UpdateCaches 14 | ); 15 | [map { $self->subtask("PostProcess\::$_") } @subtask_names]; 16 | } 17 | 18 | sub run ($self, @args) { 19 | $_->ctx($self->ctx)->run(@args) for $self->subtasks->@*; 20 | } 21 | 22 | 1; 23 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/PostProcess/UpdateAuthorStats.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::PostProcess::UpdateAuthorStats; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | our @READ = qw/Uploads/; 6 | our @WRITE = qw/Authors/; 7 | 8 | sub run ($self, @args) { 9 | $self->_update_release_stats; 10 | $self->_update_last_analyzed_at; 11 | } 12 | 13 | sub _update_release_stats ($self) { 14 | my $db = $self->db; 15 | my $authors = $db->table('Authors'); 16 | 17 | my $rows = $db->table('Uploads')->select_release_stats; 18 | for my $row (@$rows) { 19 | my $pause_id = delete $row->{pause_id}; 20 | $authors->update_release_stats($pause_id, $row); 21 | } 22 | } 23 | 24 | sub _update_last_analyzed_at ($self) { 25 | my $db = $self->db; 26 | my $authors = $db->table('Authors'); 27 | 28 | my $started_at = $ENV{CPANTS_RUNNER_STARTED_AT} // 0; 29 | my $rows = $db->table('Analysis')->select_last_analyzed_at_since($started_at); 30 | for my $row (@$rows) { 31 | my $pause_id = delete $row->{author}; 32 | $authors->update_last_analyzed_at($pause_id, $row->{last_analyzed_at}); 33 | } 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/PostProcess/UpdateCaches.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::PostProcess::UpdateCaches; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use WWW::CPANTS::API::Context; 5 | use WWW::CPANTS::Util::Loader; 6 | 7 | sub run ($self, @args) { 8 | @args = submodule_names("WWW::CPANTS::API::Model") unless @args; 9 | 10 | my $ctx = WWW::CPANTS::API::Context->new(verbose => $self->ctx->verbose); 11 | for my $name (@args) { 12 | my $module = use_module("WWW::CPANTS::API::Model::$name"); 13 | my $model = $module->new(ctx => $ctx); 14 | $model->save and $self->log(info => "cached $name"); 15 | } 16 | } 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/RunTasks.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::RunTasks; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | our @READS = qw/Tasks/; 6 | our @WRITES = qw/Tasks/; 7 | 8 | sub run ($self, @args) { 9 | my $tasks = $self->db->table('Tasks'); 10 | while (my $task_info = $tasks->next) { 11 | my $subtask = $self->subtask($task_info->{name}); 12 | $subtask->run; 13 | $tasks->remove($task_info->{id}); 14 | } 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/Traverse/UpdateCPANStatus.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::Traverse::UpdateCPANStatus; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | our @READ = qw/Authors Uploads/; 6 | our @WRITE = qw/Uploads Kwalitee/; 7 | 8 | has 'changed' => sub ($self) { +{} }; 9 | has 'uid_map' => sub ($self) { +{} }; 10 | 11 | sub run ($self, @args) { 12 | # FIXME 13 | } 14 | 15 | sub load_uids_for ($self, $pause_id) { 16 | my %map = map { $_ => 0 } $self->db->table('Uploads')->select_all_cpan_uids_by_author($pause_id)->@*; 17 | $self->uid_map(\%map); 18 | $self->changed({}); 19 | } 20 | 21 | sub has_uid ($self, $uid) { 22 | exists $self->uid_map->{$uid} ? 1 : 0; 23 | } 24 | 25 | sub mark ($self, $uid) { 26 | if (!$self->uid_map->{$uid}) { 27 | $self->changed->{$uid} = 1; 28 | } 29 | $self->uid_map->{$uid} = 1; 30 | } 31 | 32 | sub removed_uids ($self) { 33 | grep { !$self->uid_map->{$_} } keys $self->uid_map->%*; 34 | } 35 | 36 | sub changed_cpan_uids ($self) { 37 | grep { $self->changed->{$_} } keys $self->uid_map->%*; 38 | } 39 | 40 | sub mark_backpan ($self) { 41 | my $db = $self->db; 42 | if (my @removed_uids = $self->removed_uids) { 43 | $db->table('Uploads')->mark_backpan(\@removed_uids); 44 | $db->table('Kwalitee')->mark_backpan(\@removed_uids); 45 | } 46 | } 47 | 48 | sub mark_cpan ($self) { 49 | my $db = $self->db; 50 | if (my @cpan_uids = $self->changed_cpan_uids) { 51 | $db->table('Uploads')->mark_cpan(\@cpan_uids); 52 | $db->table('Kwalitee')->mark_cpan(\@cpan_uids); 53 | } 54 | } 55 | 56 | 1; 57 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/UpdateCPANIndices.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::UpdateCPANIndices; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | 5 | has subtasks => \&_build_subtasks; 6 | 7 | sub _build_subtasks ($self) { 8 | my @subtask_names = qw( 9 | PackagesDetails 10 | Permissions 11 | Whois 12 | ); 13 | [map { $self->subtask("UpdateCPANIndices\::$_") } @subtask_names]; 14 | } 15 | 16 | sub run ($self, @args) { 17 | $_->ctx($self->ctx)->run(@args) for $self->subtasks->@*; 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/UpdateCPANIndices/PackagesDetails.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::UpdateCPANIndices::PackagesDetails; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use Syntax::Keyword::Try; 5 | 6 | our @WRITE = qw/PackagesDetails/; 7 | 8 | sub run ($self, @args) { 9 | my $table = $self->db->table('PackagesDetails'); 10 | 11 | $self->log(info => "updating packages.details"); 12 | 13 | try { 14 | my $txn = $table->txn; 15 | $table->truncate; 16 | $table->bulk_insert($self->cpan->packages->list); 17 | $txn->commit; 18 | } catch { 19 | $self->log(error => $@) 20 | } 21 | 22 | $self->log(info => "updated packages.details"); 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/UpdateCPANIndices/Permissions.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::UpdateCPANIndices::Permissions; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use Syntax::Keyword::Try; 5 | 6 | our @WRITE = qw/Permissions/; 7 | 8 | sub run ($self, @args) { 9 | my $table = $self->db->table('Permissions'); 10 | 11 | $self->log(info => "updating permissions"); 12 | 13 | try { 14 | my $txn = $table->txn; 15 | $table->truncate; 16 | $table->bulk_insert($self->cpan->perms->list); 17 | $txn->commit; 18 | } catch { 19 | $self->log(error => $@) 20 | } 21 | 22 | $self->log(info => "updated permissions"); 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Bin/Task/UpdateCPANIndices/Whois.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Bin::Task::UpdateCPANIndices::Whois; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Bin::Task', -signatures; 4 | use Syntax::Keyword::Try; 5 | 6 | our @WRITE = qw/Authors Whois/; 7 | 8 | sub run ($self, @args) { 9 | $self->log(info => "updating whois"); 10 | $self->update_whois; 11 | $self->update_authors; 12 | $self->log(info => "updated whois"); 13 | } 14 | 15 | sub update_whois ($self) { 16 | my $table = $self->db->table('Whois'); 17 | 18 | try { 19 | my $txn = $table->txn; 20 | $table->truncate; 21 | $table->bulk_insert($self->cpan->whois->list); 22 | $txn->commit; 23 | } catch { 24 | $self->log(error => $@) 25 | } 26 | } 27 | 28 | sub update_authors ($self, @args) { 29 | my $table = $self->db->table('Authors'); 30 | 31 | $table->bulk_insert($self->cpan->whois->list, { ignore => 1 }); 32 | } 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB; 2 | 3 | use Mojo::Base -base, -signatures; 4 | use WWW::CPANTS::Util::Loader; 5 | 6 | has 'handle' => \&_build_handle; 7 | has 'cache' => sub ($self) { +{} }; 8 | has 'pid' => sub ($self) { $$ }; 9 | 10 | sub _build_handle ($self) { 11 | my $config = WWW::CPANTS->instance->config->{db}; 12 | my $name = $config->{handle_class} // "SQLite"; 13 | my $module = use_module("WWW::CPANTS::DB::Handle::$name"); 14 | 15 | $module->new( 16 | name => $name, 17 | config => $config->{$name}, 18 | trace => $self->trace, 19 | ); 20 | } 21 | 22 | sub table ($self, $name) { 23 | $self->check_pid; 24 | if (!exists $self->cache->{$name}) { 25 | my $table = use_module("WWW::CPANTS::DB::Table::$name")->new; 26 | $table->handle($self->handle->connect($table)); 27 | $self->cache->{$name} = $table; 28 | } 29 | $self->cache->{$name}; 30 | } 31 | 32 | sub trace ($self, $value = undef) { 33 | $value //= $ENV{CPANTS_API_TRACE}; 34 | if (defined $value) { 35 | $self->{trace} = $value; 36 | for my $name (keys $self->cache->%*) { 37 | $self->cache->{$name}->handle->trace($value); 38 | } 39 | } 40 | $self->{trace}; 41 | } 42 | 43 | sub table_names ($self) { 44 | submodule_names("WWW::CPANTS::DB::Table"); 45 | } 46 | 47 | sub check_pid ($self) { 48 | return if $self->pid == $$; 49 | $self->cache({}); 50 | $self->pid($$); 51 | } 52 | 53 | 1; 54 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/AcmeAuthors.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::AcmeAuthors; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 5 | use Syntax::Keyword::Try; 6 | 7 | with qw/WWW::CPANTS::Role::Logger/; 8 | 9 | sub columns ($self) { ( 10 | [module_id => '_acme_id_'], 11 | [pause_id => '_pause_id_'], 12 | ) } 13 | 14 | sub indices ($self) { ({ unique => 1, columns => [qw/module_id pause_id/] }) } 15 | 16 | sub insert_for_module_id ($self, $module_id, $authors) { 17 | my $sql = <<~';'; 18 | INSERT INTO acme_authors (module_id, pause_id) VALUES (?, ?); 19 | ; 20 | my @rows = map { [$module_id, $_] } @$authors; 21 | my $sth = $self->prepare($sql); 22 | my $txn = $self->txn; 23 | try { 24 | $sth->execute(@$_) for @rows; 25 | $txn->commit; 26 | } catch { 27 | my $error = $@; 28 | $self->log(error => $error); 29 | $txn->rollback; 30 | } 31 | } 32 | 33 | sub remove_by_module_id ($self, $module_id) { 34 | my $sql = <<~';'; 35 | DELETE FROM acme_authors WHERE module_id = ? 36 | ; 37 | $self->do($sql, $module_id); 38 | } 39 | 40 | sub select_authors_by_module_id ($self, $module_id) { 41 | my $sql = <<~';'; 42 | SELECT pause_id FROM acme_authors WHERE module_id = ? 43 | ; 44 | $self->select_all_col($sql, $module_id); 45 | } 46 | 47 | sub select_module_ids_by_pause_id ($self, $pause_id) { 48 | my $sql = <<~';'; 49 | SELECT module_id FROM acme_authors WHERE pause_id = ? 50 | ; 51 | $self->select_all_col($sql, $pause_id); 52 | } 53 | 54 | 1; 55 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/AcmeModules.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::AcmeModules; 2 | 3 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 4 | 5 | sub columns ($self) { ( 6 | [module_id => '_acme_id_', primary => 1], 7 | [module => '_module_name_'], 8 | [version => '_version_string_'], 9 | [released => '_epoch_'], 10 | [authors => 'integer'], 11 | [updated_at => '_epoch_'], 12 | ) } 13 | 14 | sub indices ($self) { ({ unique => 1, columns => [qw/module/] }) } 15 | 16 | sub has_module_id ($self, $module_id) { 17 | my $sql = <<~';'; 18 | SELECT 1 FROM acme_modules WHERE module_id = ? 19 | ; 20 | $self->select_col($sql, $module_id); 21 | } 22 | 23 | sub insert_module ($self, $module_id, $module, $version, $released, $authors, $mtime) { 24 | my $sql = <<~';'; 25 | INSERT INTO acme_modules (module_id, module, version, released, authors, updated_at) 26 | VALUES (?, ?, ?, ?, ?, ?) 27 | ; 28 | $self->do($sql, $module_id, $module, $version, $released, $authors, $mtime); 29 | } 30 | 31 | sub update_module ($self, $module_id, $module, $version, $released, $authors, $mtime) { 32 | my $sql = <<~';'; 33 | UPDATE acme_modules 34 | SET version = ?, released = ?, authors = ?, updated_at = ? 35 | WHERE module_id = ? 36 | ; 37 | $self->do($sql, $version, $released, $authors, $mtime, $module_id); 38 | } 39 | 40 | sub remove_by_module_id ($self, $module_id) { 41 | my $sql = <<~';'; 42 | DELETE FROM acme_modules WHERE module_id = ? 43 | ; 44 | $self->do($sql, $module_id); 45 | } 46 | 47 | sub select_modules ($self) { 48 | my $sql = <<~';'; 49 | SELECT module_id, module, version, released, authors FROM acme_modules; 50 | ; 51 | $self->select_all($sql); 52 | } 53 | 54 | 1; 55 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/PackagesDetails.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::PackagesDetails; 2 | 3 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 4 | 5 | sub columns ($self) { ( 6 | [uid => '_upload_id_'], 7 | [module => '_module_name_'], 8 | [version => '_version_string_'], 9 | [path => '_cpan_path_'], 10 | [dist => '_dist_name_'], 11 | ) } 12 | 13 | sub indices ($self) { ( 14 | [qw/module/], 15 | ) } 16 | 17 | sub select_all_by_modules ($self, $modules) { 18 | my $sql = <<~';'; 19 | SELECT * FROM packages_details 20 | WHERE module IN (:modules) 21 | ; 22 | $self->select_all($sql, [modules => $modules]); 23 | } 24 | 25 | sub select_unique_dists_by_modules ($self, $modules) { 26 | my $sql = <<~';'; 27 | SELECT DISTINCT(id), path FROM packages_details 28 | WHERE id IS NOT NULL AND module IN (:modules) 29 | ; 30 | $self->select_all($sql, [modules => $modules]); 31 | } 32 | 33 | 1; 34 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/Permissions.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::Permissions; 2 | 3 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 4 | 5 | sub columns ($self) { ( 6 | [module => '_module_name_'], 7 | [pause_id => '_pause_id_'], 8 | [type => 'varchar(1)'], 9 | ) } 10 | 11 | sub indices ($self) { ( 12 | [qw/pause_id/], 13 | [qw/module/], 14 | ) } 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/Provides.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::Provides; 2 | 3 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 4 | 5 | sub columns ($self) { ( 6 | [uid => '_upload_id_', primary => 1], 7 | [pause_id => '_pause_id_'], 8 | [modules => '_json_'], 9 | [provides => '_json_'], 10 | [special_files => '_json_'], 11 | [unauthorized => '_json_'], 12 | ) } 13 | 14 | sub select_by_uid ($self, $uid) { 15 | my $sql = <<~';'; 16 | SELECT * FROM provides WHERE uid = ? 17 | ; 18 | $self->select($sql, $uid); 19 | } 20 | 21 | sub delete_by_uids ($self, $uids) { 22 | my $sql = <<~';'; 23 | DELETE FROM provides WHERE uid IN (:uids) 24 | ; 25 | $self->delete($sql, [uids => $uids]); 26 | } 27 | 28 | sub update_provides ($self, $uid, $pause_id, $modules, $provides, $special_files, $unauthorized) { 29 | my $sql = <<~';'; 30 | UPDATE provides 31 | SET 32 | pause_id = ?, 33 | modules = ?, 34 | provides = ?, 35 | special_files = ?, 36 | unauthorized = ? 37 | WHERE uid = ? 38 | ; 39 | $self->update($sql, $pause_id, $modules, $provides, $special_files, $unauthorized, $uid); 40 | } 41 | 42 | 1; 43 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/RequiresAndUses.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::RequiresAndUses; 2 | 3 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 4 | 5 | sub columns ($self) { ( 6 | [uid => '_upload_id_', primary => 1], 7 | [pause_id => '_pause_id_'], 8 | [requires => '_json_'], 9 | [uses => '_json_'], 10 | ) } 11 | 12 | sub select_requires_by_uid ($self, $uid) { 13 | my $sql = <<~';'; 14 | SELECT requires FROM requires_and_uses WHERE uid = ? 15 | ; 16 | $self->select_col($sql, $uid); 17 | } 18 | 19 | sub update_requires_and_uses ($self, $uid, $pause_id, $requires, $uses) { 20 | my $sql = <<~';'; 21 | UPDATE requires_and_uses 22 | SET pause_id = ?, requires = ?, uses = ? 23 | WHERE uid = ? 24 | ; 25 | $self->update($sql, $pause_id, $requires, $uses, $uid); 26 | } 27 | 28 | sub delete_by_uids ($self, $uids) { 29 | my $sql = <<~';'; 30 | DELETE FROM requires_and_uses WHERE uid IN (:uids) 31 | ; 32 | $self->delete($sql, [uids => $uids]); 33 | } 34 | 35 | 1; 36 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/Resources.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::Resources; 2 | 3 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 4 | 5 | sub columns ($self) { ( 6 | [uid => '_upload_id_', primary => 1], 7 | [pause_id => '_pause_id_'], 8 | [resources => '_json_'], 9 | [repository_url => 'varchar(255)'], 10 | [bugtracker_url => 'varchar(255)'], 11 | ) } 12 | 13 | sub select_by_uid ($self, $uid) { 14 | my $sql = <<~';'; 15 | SELECT * FROM resources WHERE uid = ? 16 | ; 17 | $self->select($sql, $uid); 18 | } 19 | 20 | sub delete_by_uids ($self, $uids) { 21 | my $sql = <<~';'; 22 | DELETE FROM resources WHERE uid IN (:uids) 23 | ; 24 | $self->delete($sql, [uids => $uids]); 25 | } 26 | 27 | sub update_resources ($self, $uid, $pause_id, $resources, $repository, $bugtracker) { 28 | my $sql = <<~';'; 29 | UPDATE resources 30 | SET pause_id = ?, resources = ?, repository_url = ?, bugtracker_url = ? 31 | WHERE uid = ? 32 | ; 33 | $self->update($sql, $pause_id, $resources, $repository, $bugtracker, $uid); 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/DB/Table/Whois.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::DB::Table::Whois; 2 | 3 | use Mojo::Base 'WWW::CPANTS::DB::Table', -signatures; 4 | 5 | sub columns ($self) { ( 6 | [pause_id => '_pause_id_', primary => 1], 7 | [name => 'varchar(255)', character_set => 'utf8mb4'], 8 | [ascii_name => 'varchar(255)'], 9 | [email => 'varchar(255)'], 10 | [homepage => 'varchar(255)'], 11 | [has_cpandir => '_bool_', default => 0], 12 | [introduced => '_epoch_'], 13 | [year => '_year_'], 14 | [nologin => '_bool_', default => 0], 15 | [deleted => '_bool_', default => 0], 16 | [system => '_bool_', default => 0], 17 | ) } 18 | 19 | sub select_all_by_pause_ids ($self, $pause_ids) { 20 | my $sql = <<~";"; 21 | SELECT * FROM whois WHERE pause_id IN (:pause_ids) 22 | ; 23 | $self->select_all($sql, [pause_ids => $pause_ids]); 24 | } 25 | 26 | sub author_stats_of_the_year ($self, $year, $pause_ids) { 27 | my $cond = $year ? '=' : '!='; 28 | my $sql = <<~";"; 29 | SELECT COUNT(*) AS new_authors FROM whois 30 | WHERE year $cond ? AND pause_id IN (:pause_ids) 31 | ; 32 | $self->select($sql, $year, [pause_ids => $pause_ids]); 33 | } 34 | 35 | 1; 36 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/CPAN.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::CPAN; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use Path::Tiny (); 6 | 7 | has 'path' => \&_path_is_required; 8 | has 'root' => \&_build_root; 9 | 10 | with qw( 11 | WWW::CPANTS::Role::CPAN::Indices 12 | WWW::CPANTS::Role::CPAN::Path 13 | ); 14 | 15 | sub _path_is_required ($self) { 16 | Carp::confess "path is required"; 17 | } 18 | 19 | sub _build_root ($self) { 20 | my $root = Path::Tiny::path($self->path); 21 | Carp::croak "$root does not exist" unless -d $root; 22 | Carp::croak "$root seems not a CPAN mirror" unless -d $root->child("authors/id"); 23 | $root; 24 | } 25 | 26 | sub preload_indices ($self) { 27 | $_->preload for $self->indices->@*; 28 | } 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/CPAN/Perms.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::CPAN::Perms; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | 6 | has 'path' => 'modules/06perms.txt'; 7 | has 'perms' => \&_build_perms; 8 | 9 | with qw/WWW::CPANTS::Role::CPAN::Index/; 10 | 11 | sub _build_perms ($self) { 12 | my $file = $self->fetch; 13 | my $fh = $file->openr; 14 | my %perms; 15 | my $seen_header; 16 | while (<$fh>) { 17 | chomp; 18 | if (!$seen_header) { 19 | if (/^$/) { 20 | $seen_header = 1; 21 | } 22 | next; 23 | } 24 | 25 | my ($module, $pause_id, $type) = split /,/; 26 | if (!$module or !$pause_id or !$type) { ## Broken for whatever reasons 27 | $self->log(warn => "06perms is broken"); 28 | close $fh; 29 | unlink $self->file; 30 | return $self->_build_perms; 31 | } 32 | $perms{ lc $module }{ uc $pause_id } = { 33 | module => $module, 34 | pause_id => $pause_id, 35 | type => $type, 36 | }; 37 | } 38 | \%perms; 39 | } 40 | 41 | sub list ($self) { 42 | my @rows = map { values %$_ } values $self->perms->%*; 43 | \@rows; 44 | } 45 | 46 | sub can_upload ($self, $pause_id, $module) { 47 | $pause_id = uc $pause_id; 48 | $module = lc $module; 49 | 50 | return 1 if !exists $self->perms->{$module}; 51 | return 1 if exists $self->perms->{$module}{$pause_id}; 52 | return; 53 | } 54 | 55 | sub preload ($self) { $self->perms } 56 | 57 | 1; 58 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/CPAN/Recent.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::CPAN::Recent; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use WWW::CPANTS::Util::JSON; 6 | use Class::Method::Modifiers; 7 | 8 | with qw/WWW::CPANTS::Role::CPAN::Index/; 9 | 10 | has 'root' => \&_root_is_required; 11 | has 'type' => '6h'; 12 | has 'path' => \&_build_path; 13 | has 'file' => \&_build_file; 14 | has 'distributions' => \&_build_distributions; 15 | 16 | our %Types = map { $_ => 1 } qw/1M 1Q 1W 1Y 1d 1h 6h Z/; 17 | 18 | before 'type' => sub ($self, @args) { 19 | return unless @args; 20 | my $type = $args[0]; 21 | Carp::croak "Invalid type: $type" unless exists $Types{$type}; 22 | }; 23 | 24 | sub _root_is_required ($self) { 25 | Carp::confess "root is required"; 26 | } 27 | 28 | sub _build_path ($self) { 29 | my $type = $self->type; 30 | return "RECENT-$type.json"; 31 | } 32 | 33 | sub _build_file ($self) { 34 | $self->root->child($self->path); 35 | } 36 | 37 | sub _build_distributions ($self) { 38 | my $file = $self->fetch; 39 | 40 | decode_json($file->slurp_utf8)->{recent}; 41 | } 42 | 43 | sub preload ($self) { $self->distributions } 44 | 45 | 1; 46 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/CPAN/Whois.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::CPAN::Whois; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use Parse::CPAN::Whois; 6 | use WWW::CPANTS::Util::Datetime; 7 | 8 | has 'path' => 'authors/00whois.xml'; 9 | has 'authors' => \&_build_authors; 10 | 11 | with qw/WWW::CPANTS::Role::CPAN::Index/; 12 | 13 | our %SYSTEM_USER = map { $_ => 1 } qw( 14 | ADOPTME 15 | HANDOFF 16 | NEEDHELP 17 | NOXFER 18 | PAUSE 19 | RECAPTCHA 20 | ); 21 | 22 | sub _build_authors ($self) { 23 | my $file = $self->fetch; 24 | 25 | $XML::SAX::ParserPackage = "XML::LibXML::SAX"; 26 | my %authors; 27 | for my $author (Parse::CPAN::Whois->new("$file")->authors) { 28 | my $pause_id = $author->{id}; 29 | 30 | $authors{$pause_id} = { 31 | pause_id => $pause_id, 32 | name => $author->{fullname}, 33 | ascii_name => $author->{asciiname} // $author->{fullname}, 34 | email => $author->{email} // '', 35 | homepage => $author->{homepage} // '', 36 | has_cpandir => $author->{has_cpandir} // 0, 37 | introduced => $author->{introduced} // 0, 38 | year => year($author->{introduced} // 0), 39 | nologin => $author->{nologin} // 0, 40 | deleted => $author->{deleted} // 0, 41 | system => (exists $SYSTEM_USER{$pause_id} ? 1 : 0), 42 | }; 43 | } 44 | \%authors; 45 | } 46 | 47 | sub list ($self) { 48 | my @rows = values $self->authors->%*; 49 | \@rows; 50 | } 51 | 52 | sub preload ($self) { $self->authors } 53 | 54 | 1; 55 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/PidFile.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::PidFile; 2 | 3 | use Mojo::Base -base, -signatures; 4 | use WWW::CPANTS::Util::Path; 5 | 6 | has 'id' => \&_id_is_required; 7 | has 'file' => \&_build_file; 8 | has 'pid' => sub ($self) { $$ }; 9 | 10 | sub _id_is_required ($self) { 11 | Carp::confess "id is required"; 12 | } 13 | 14 | sub _build_file ($self) { 15 | my $id = $self->id; 16 | cpants_path("tmp/pid/$id.pid"); 17 | } 18 | 19 | sub exists ($self) { 20 | -f $self->file ? 1 : 0; 21 | } 22 | 23 | sub slurp ($self) { 24 | $self->exists ? $self->file->slurp : undef; 25 | } 26 | 27 | sub spew ($self, $pid) { 28 | $self->file->parent->mkpath; 29 | $self->file->spew($pid); 30 | } 31 | 32 | sub DESTROY ($self) { 33 | return unless $self->pid eq $$; 34 | my $pid = $self->slurp or return; 35 | $self->file->remove if $pid eq $self->pid; 36 | } 37 | 38 | 1; 39 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/TableGuard.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::TableGuard; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use WWW::CPANTS::Util::Path; 6 | 7 | has 'dir' => \&_build_dir; 8 | has 'pid' => sub ($self) { $$ }; 9 | has 'files'; 10 | 11 | with qw/WWW::CPANTS::Role::Logger/; 12 | 13 | sub _build_dir ($self) { 14 | my $dir = cpants_path("tmp/table_guard"); 15 | $dir->mkpath unless -d $dir; 16 | $dir; 17 | } 18 | 19 | sub check ($self, @tables) { 20 | my @files; 21 | for my $table (@tables) { 22 | my $file = $self->dir->child($table); 23 | if (-f $file) { 24 | my $pid = $file->slurp; 25 | $self->log(info => "Another task ($pid) is using $table"); 26 | $_->remove for @files; 27 | return; 28 | } 29 | $file->spew($$); 30 | push @files, $file; 31 | } 32 | $self->files(\@files); 33 | return 1; 34 | } 35 | 36 | sub DESTROY ($self) { 37 | my $files = $self->files or return; 38 | for my $file (@$files) { 39 | next unless -f $file; 40 | my $pid = $file->slurp; 41 | $file->remove if $pid eq $self->pid; 42 | } 43 | } 44 | 45 | 1; 46 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/TempDir.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::TempDir; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use File::Path::Tiny (); 6 | use Path::Tiny (); 7 | use File::Temp (); 8 | use Syntax::Keyword::Try; 9 | use overload 10 | '""' => \&_stringify, 11 | '0+' => \&_stringify, 12 | 'bool' => \&_stringify, 13 | 'cmp' => \&_stringify; 14 | 15 | our $AUTOLOAD; 16 | 17 | has 'root' => \&_root_is_required; 18 | has 'path' => \&_build_path; 19 | has 'pid' => sub ($self) { $$ }; 20 | 21 | with qw/WWW::CPANTS::Role::Logger/; 22 | 23 | sub _root_is_required ($self) { 24 | Carp::confess "root is required"; 25 | } 26 | 27 | sub _build_path ($self) { 28 | my $root = $self->root; 29 | $root->mkpath unless -d $root; 30 | my $path = Path::Tiny::path(File::Temp->newdir( 31 | DIR => $root->path, 32 | TEMPLATE => join('.', time, $$, "XXXXXXXX"), 33 | )); 34 | $path->mkpath unless -d $path; 35 | $path; 36 | } 37 | 38 | sub path_str ($self) { $self->path->stringify } 39 | 40 | sub _stringify ($self, @args) { $self->path . "" } 41 | 42 | sub AUTOLOAD ($self, @args) { 43 | return if $AUTOLOAD =~ /::DESTROY$/; 44 | $AUTOLOAD =~ s/.*:://; 45 | $self->path->$AUTOLOAD(@args); 46 | } 47 | 48 | sub DESTROY ($self) { 49 | return unless $self->pid eq $$; 50 | my $path = $self->path; 51 | return unless $path && -e $path; 52 | try { File::Path::Tiny::rm($path) } 53 | catch { 54 | my $error = $@; 55 | warn "[ALERT] Failed to remove tmpdir: $error [$$] $@\n"; 56 | } 57 | if (-e $path) { 58 | warn "[ALERT] Failed to remove tmpdir $! [$$]\n"; 59 | } 60 | } 61 | 62 | 1; 63 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Model/Timer.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Model::Timer; 2 | 3 | use Role::Tiny::With; 4 | use Mojo::Base -base, -signatures; 5 | use Time::Duration qw/duration/; 6 | use Time::HiRes qw/time/; 7 | use WWW::CPANTS::Util::Datetime qw/strftime/; 8 | 9 | with qw/WWW::CPANTS::Role::Logger/; 10 | 11 | has 'name' => 'Timer'; 12 | has 'total'; 13 | has 'started_at'; 14 | has 'pid'; 15 | 16 | sub start ($self) { 17 | my $name = $self->name; 18 | return if $name =~ /^Maint::/; 19 | 20 | $self->started_at(time); 21 | $self->pid($$); 22 | $self->log(notice => "[$name] started [$$]"); 23 | } 24 | 25 | sub show_progress ($self, $done) { 26 | return unless $done; 27 | return unless $self->started_at; 28 | 29 | my $name = $self->name; 30 | my $elapsed = time - $self->started_at; 31 | if (my $total = $self->total) { 32 | my $estimate = sprintf "(%0.2f/s; estimated end time: %s) ", 33 | ($elapsed ? $done / $elapsed : '-'), 34 | strftime("%Y-%m-%d %H:%M", $elapsed * $total / $done + $self->started_at); 35 | $self->log(info => "[$name] $done/$total $estimate [$$]"); 36 | } else { 37 | $self->log(info => "[$name] $done [$$]"); 38 | } 39 | } 40 | 41 | sub DESTROY ($self) { 42 | return unless $self->started_at and $self->pid eq $$; 43 | local $Time::Duration::MILLISECOND = 1; 44 | my $duration = duration(time - $self->started_at); 45 | my $name = $self->name; 46 | $self->log(notice => "[$name] ended (elapsed $duration) [$$]"); 47 | } 48 | 49 | 1; 50 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Role/API/Model/V5/Author/Status.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Role::API::Model::V5::Author::Status; 2 | 3 | use Mojo::Base -role, -signatures; 4 | 5 | sub check_whois_status ($self, $pause_id) { 6 | my $db = $self->db; 7 | my $whois = $db->table('Whois')->find($pause_id) or return $self->bad_request("'$pause_id' not found"); 8 | 9 | # Do not expose deleted user's information 10 | if ($whois->{deleted}) { 11 | return unless wantarray; 12 | return (undef, { pause_id => $pause_id, deleted => 1 }); 13 | } 14 | 15 | # Hide spammers too 16 | if ($whois->{nologin} and !$whois->{system}) { 17 | return unless wantarray; 18 | return (undef, { pause_id => $pause_id, banned => 1 }); 19 | } 20 | 21 | return 1 unless wantarray; 22 | return (1, $whois); 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Role/CPAN/Indices.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Role::CPAN::Indices; 2 | 3 | use Mojo::Base -role, -signatures; 4 | use WWW::CPANTS::Model::CPAN::Packages; 5 | use WWW::CPANTS::Model::CPAN::Perms; 6 | use WWW::CPANTS::Model::CPAN::Whois; 7 | use WWW::CPANTS::Model::CPAN::Recent; 8 | 9 | requires 'root'; 10 | 11 | has 'packages' => \&_build_packages; 12 | has 'perms' => \&_build_perms; 13 | has 'whois' => \&_build_whois; 14 | has 'recent' => \&_build_recent; 15 | 16 | sub indices ($self) { 17 | my @indices = map { $self->$_ } qw/packages perms whois recent/; 18 | \@indices; 19 | } 20 | 21 | sub _build_packages ($self) { 22 | WWW::CPANTS::Model::CPAN::Packages->new(root => $self->root); 23 | } 24 | 25 | sub _build_perms ($self) { 26 | WWW::CPANTS::Model::CPAN::Perms->new(root => $self->root); 27 | } 28 | 29 | sub _build_whois ($self) { 30 | WWW::CPANTS::Model::CPAN::Whois->new(root => $self->root); 31 | } 32 | 33 | sub _build_recent ($self) { 34 | WWW::CPANTS::Model::CPAN::Recent->new(root => $self->root); 35 | } 36 | 37 | 1; 38 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Role/CPAN/Path.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Role::CPAN::Path; 2 | 3 | use Mojo::Base -role, -signatures; 4 | 5 | sub child ($self, $path) { $self->root->child($path) } 6 | 7 | sub distribution ($self, $path) { 8 | $self->child("authors/id/$path"); 9 | } 10 | 11 | sub author_dir ($self, $pause_id) { 12 | my $path = join "/", substr($pause_id, 0, 1), substr($pause_id, 0, 2), $pause_id; 13 | my $dir = $self->child("authors/id/$path"); 14 | return unless -d $dir; 15 | $dir; 16 | } 17 | 18 | sub author_dir_iterator ($self, $pause_id) { 19 | my $dir = $self->author_dir($pause_id) or return; 20 | $dir->iterator({ recurse => 1, follow_symlinks => 0 }); 21 | } 22 | 23 | 1; 24 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Role/Logger.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Role::Logger; 2 | 3 | use Mojo::Base -role, -signatures; 4 | 5 | sub log ($self, $level, $message) { 6 | WWW::CPANTS->instance->logger->log($level => $message); 7 | } 8 | 9 | 1; 10 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Role/Options.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Role::Options; 2 | 3 | use Mojo::Base -role, -signatures; 4 | 5 | my @SeenOptions; 6 | 7 | sub options ($self) { 8 | my $class = ref $self || $self; 9 | no strict 'refs'; 10 | @{"$class\::OPTIONS"}; 11 | } 12 | 13 | around 'new' => sub ($orig, $class, @args) { 14 | my @rules; 15 | for my $option ($class->options) { 16 | my ($rule, @attr_args) = ref $option ? @$option : ($option); 17 | my ($name) = $rule =~ /^(\w+)/; 18 | Mojo::Base::attr($class, $name, @attr_args); 19 | push @rules, $rule; 20 | } 21 | 22 | Mojo::Util::getopt \@ARGV, [qw/bundling pass_through/], 23 | \my %opts => @rules; 24 | 25 | push @SeenOptions, @rules; 26 | 27 | if (%opts) { 28 | if (@args == 1 and ref $args[0] eq 'HASH') { 29 | $args[0]{$_} = $opts{$_} for keys %opts; 30 | } else { 31 | push @args, %opts; 32 | } 33 | } 34 | $orig->($class, @args); 35 | }; 36 | 37 | sub show_help ($self) { 38 | say "options:"; 39 | for my $option (@SeenOptions) { 40 | my $name = ref $option ? $option->[0] : $option; 41 | $name =~ s/[\|=].+$//; 42 | say " $name"; 43 | } 44 | } 45 | 46 | 1; 47 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Role/Task/FixAnalysis.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Role::Task::FixAnalysis; 2 | 3 | use Mojo::Base -role, -signatures; 4 | use WWW::CPANTS::Util::JSON; 5 | use WWW::CPANTS::Util::HideInternal; 6 | use WWW::CPANTS::Util::PathUid; 7 | use Parse::Distname qw/parse_distname/; 8 | 9 | sub run ($self, @args) { 10 | if (@args) { 11 | $self->fix_with_args(\@args); 12 | } else { 13 | $self->fix_all; 14 | } 15 | } 16 | 17 | sub fix_all ($self) { 18 | my $table = $self->db->table('Analysis'); 19 | my $iterator = $table->iterate; 20 | while (my $row = $iterator->next) { 21 | my ($uid, $path, $json) = @$row{qw/uid path json/}; 22 | next unless $json; 23 | my $stash = decode_json($json); 24 | $self->update($uid, $stash) or next; 25 | $self->log(info => "fixed $path"); 26 | 27 | if ($self->dump) { 28 | say hide_internal(encode_pretty_json($stash)); 29 | } 30 | } 31 | } 32 | 33 | sub fix_with_args ($self, $args) { 34 | my $table = $self->db->table('Analysis'); 35 | for my $path (@$args) { 36 | next unless defined $path and $path ne ''; 37 | my $info = parse_distname($path); 38 | if (!$info) { 39 | $self->log(error => "$path seems not a CPAN distribution"); 40 | next; 41 | } 42 | 43 | $path = $info->{cpan_path}; 44 | my $uid = path_uid($path); 45 | my $json = $table->select_json_by_uid($uid) or next; 46 | my $stash = decode_json($json); 47 | $self->update($uid, $stash) or next; 48 | $self->log(info => "fixed $path"); 49 | 50 | if ($self->dump) { 51 | say hide_internal(encode_pretty_json($stash)); 52 | } 53 | } 54 | } 55 | 56 | 1; 57 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/CoreList.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::CoreList; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | use Module::CoreList; 6 | 7 | our @EXPORT = qw/is_core core_since removed_core_since deprecated_core_since/; 8 | our $MinPerlVersion = 5.008001; 9 | 10 | sub is_core ($module, $perl_version = $MinPerlVersion) { 11 | if (!$perl_version or $perl_version < $MinPerlVersion) { 12 | $perl_version = $MinPerlVersion; 13 | } 14 | Module::CoreList::is_core($module, undef, $perl_version); 15 | } 16 | 17 | sub core_since ($module, $version = 0) { 18 | Module::CoreList::first_release($module, $version); 19 | } 20 | 21 | sub removed_core_since ($module) { 22 | Module::CoreList::removed_from($module); 23 | } 24 | 25 | sub deprecated_core_since ($module) { 26 | Module::CoreList::deprecated_in($module); 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/Datetime.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::Datetime; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | use Time::Moment; 6 | use Time::Zone qw/tz_local_offset/; 7 | 8 | our @EXPORT = qw( 9 | strftime ymd ymdhms days_ago year datetime 10 | epoch_from_date 11 | ); 12 | 13 | our $TZ_OFFSET = tz_local_offset() / 60; 14 | 15 | sub _from_epoch ($epoch = time) { 16 | Time::Moment->from_epoch($epoch // time)->with_offset_same_instant($TZ_OFFSET); 17 | } 18 | 19 | sub strftime ($format, $epoch = time) { 20 | _from_epoch($epoch // time)->strftime($format); 21 | } 22 | 23 | sub ymd ($epoch) { strftime('%F', $epoch) } 24 | 25 | sub ymdhms ($epoch) { strftime('%F %T', $epoch) } 26 | 27 | sub datetime ($epoch) { strftime('%FT%TZ', $epoch) } 28 | 29 | sub year ($epoch) { _from_epoch($epoch)->year } 30 | 31 | sub days_ago ($days) { _from_epoch()->minus_days($days) } 32 | 33 | sub epoch_from_date ($date) { 34 | my ($year, $month, $day) = split '-', $date; 35 | Time::Moment->new( 36 | year => $year, 37 | month => $month, 38 | day => $day, 39 | )->epoch; 40 | } 41 | 42 | 1; 43 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/Diff.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::Diff; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | 6 | our @EXPORT = qw/diff/; 7 | our @EXPORT_OK = qw/kwalitee_diff/; 8 | 9 | if (eval { require Text::Diff::Unified::XS; 1 }) { 10 | *diff = \&_xs_diff; 11 | } else { 12 | require Text::Diff; 13 | *diff = \&_pp_diff; 14 | } 15 | 16 | sub _xs_diff ($old, $new) { 17 | Text::Diff::Unified::XS::diff($old, $new); 18 | } 19 | 20 | sub _pp_diff ($old, $new) { 21 | Text::Diff::diff($old, $new, { STYLE => 'Unified' }); 22 | } 23 | 24 | sub kwalitee_diff ($old, $new) { 25 | my @diff; 26 | for my $key (sort keys $new->{kwalitee}->%*) { 27 | my $old_value = $old->{kwalitee}{$key} // ''; 28 | my $new_value = $new->{kwalitee}{$key} // ''; 29 | if ($old_value ne $new_value) { 30 | push @diff, "$key: $old_value=>$new_value"; 31 | } 32 | } 33 | return join ', ', @diff; 34 | } 35 | 36 | 1; 37 | 38 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/HideInternal.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::HideInternal; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | use WWW::CPANTS; 6 | 7 | our @EXPORT = qw/hide_internal/; 8 | 9 | sub hide_internal ($str) { 10 | my $root = WWW::CPANTS->instance->app_root; 11 | my ($home) = $root =~ m|^((?:[A-Z]:)?/home/[^/]+)|i; 12 | no warnings 'uninitialized'; 13 | $str =~ s!$home/\.plenv/versions/[^/]+/lib/perl5/(site_perl/)?5\.\d+\.\d+/!$1lib/!g; 14 | $str =~ s!$home/((?:backpan|cpan)/)!$1!g; 15 | $str =~ s!$root/tmp/analyze/[^/]+/[^/]+/!!g; 16 | $str =~ s!$root/extlib/[^/]+/!!g; 17 | $str =~ s!$root/!!g; 18 | $str =~ s!$home/!!g; 19 | $str; 20 | } 21 | 22 | 1; 23 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/Loader.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::Loader; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Module::Find qw/findallmod/; 5 | use Module::Runtime (); 6 | use Exporter 5.57 qw/import/; 7 | 8 | our @EXPORT = qw/use_module submodules submodule_names/; 9 | our %CACHE; 10 | 11 | sub use_module ($name) { 12 | $CACHE{$name} //= Module::Runtime::use_module($name); 13 | } 14 | 15 | sub submodules ($namespace) { 16 | my %map; 17 | 18 | $Module::Find::followMode = 0; 19 | for my $package (findallmod $namespace) { 20 | my $name = $package =~ s/^${namespace}:://r; 21 | $map{$name} = $package; 22 | } 23 | \%map; 24 | } 25 | 26 | sub submodule_names ($namespace) { 27 | sort keys submodules($namespace)->%*; 28 | } 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/Parallel.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::Parallel; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Parallel::Runner; 5 | use Exporter qw/import/; 6 | use Syntax::Keyword::Try; 7 | use WWW::CPANTS; 8 | 9 | our @EXPORT = qw/parallel/; 10 | 11 | sub parallel ($workers, $code) { 12 | $workers = 0 if $INC{'Devel/Cover.pm'} or $^O eq 'MSWin32'; 13 | 14 | my $runner = 15 | ($workers) 16 | ? Parallel::Runner->new($workers) 17 | : WWW::CPANTS::Util::Parallel::Dummy->new($workers); 18 | 19 | $0 = "$^X $0 @ARGV (master)" unless $0 =~ /\((?:master|worker)\)/; 20 | 21 | my $runner_r = \$runner; 22 | local $SIG{TERM} = sub { 23 | warn "CAUGHT TERM"; 24 | $$runner_r->killall('TERM'); 25 | $$runner_r->finish; 26 | exit; 27 | }; 28 | local $SIG{INT} = sub { 29 | warn "CAUGHT INT"; 30 | $$runner_r->killall('INT'); 31 | $$runner_r->finish; 32 | exit; 33 | }; 34 | 35 | try { $code->($runner) } 36 | catch { 37 | my $error = $@; 38 | WWW::CPANTS->instance->logger->log(error => $error); 39 | } 40 | 41 | $runner->finish; 42 | } 43 | 44 | package WWW::CPANTS::Util::Parallel::Dummy; 45 | 46 | use Mojo::Base -strict, -signatures; 47 | use WWW::CPANTS; 48 | 49 | sub new ($class, $max_workers) { bless {}, $class } 50 | 51 | sub run ($self, $code) { 52 | try { $code->() } 53 | catch { 54 | my $error = $@; 55 | WWW::CPANTS->instance->logger->log(error => $error); 56 | } 57 | } 58 | sub pid ($self) { $$ } 59 | sub killall ($self, $sig) { kill $sig, $$ } 60 | sub finish ($self) { } 61 | sub max ($self) { 1 } 62 | 63 | 1; 64 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/Path.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::Path; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | use WWW::CPANTS; 6 | 7 | our @EXPORT = qw/cpants_app_path cpants_path/; 8 | 9 | sub cpants_app_path ($path) { 10 | WWW::CPANTS->instance->app_root->child($path); 11 | } 12 | 13 | sub cpants_path ($path) { 14 | WWW::CPANTS->instance->root->child($path); 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/PathUid.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::PathUid; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | use Digest::FNV::XS (); 6 | 7 | our @EXPORT = qw/path_uid/; 8 | 9 | sub path_uid ($path) { 10 | return unless defined $path; 11 | Digest::FNV::XS::fnv1a_64($path); 12 | } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Util/Version.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Util::Version; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Exporter qw/import/; 5 | use version; 6 | use Syntax::Keyword::Try; 7 | 8 | our @EXPORT = qw/numify_version/; 9 | 10 | sub numify_version ($version) { 11 | try { 12 | no warnings; 13 | my $num = version->parse($version)->numify; 14 | $num =~ s/_//g; 15 | return $num || 0; 16 | } catch { 17 | return 0; 18 | } 19 | } 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Context.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Context; 2 | 3 | use Mojo::Base -base, -signatures; 4 | use Mojo::URL; 5 | 6 | has 'api_ctx' => \&_build_api_ctx; 7 | has 'base_url' => \&_build_base_url; 8 | 9 | sub _build_api_ctx ($self) { 10 | require WWW::CPANTS::API::Context; 11 | WWW::CPANTS::API::Context->new; 12 | } 13 | 14 | sub _build_base_url ($self) { 15 | Mojo::URL->new('https://cpants.cpanauthors.org'); 16 | } 17 | 18 | sub api_url ($self, $path, $query = undef) { 19 | my $url = $self->api_ctx->api_base->clone; 20 | $path =~ s|^/||; 21 | $url->path($path); 22 | $url->query(%$query) if $query; 23 | $url; 24 | } 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Controller/Home.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Controller::Home; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Web::Controller', -signatures; 4 | use experimental qw/switch/; 5 | no warnings qw/deprecated/; 6 | 7 | sub index ($c) { 8 | $c->render_with( 9 | sub ($c, $params, $format) { 10 | my $res = $c->get_api('Recent') or return; 11 | 12 | given ($format) { 13 | when ('') { 14 | return { 15 | render => 'home', 16 | stash => { 17 | releases => $res->{data}, 18 | }, 19 | }; 20 | } 21 | } 22 | return; 23 | }, 24 | ); 25 | } 26 | 27 | 1; 28 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Controller/Kwalitee.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Controller::Kwalitee; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Web::Controller', -signatures; 4 | use experimental qw/switch/; 5 | no warnings qw/deprecated/; 6 | 7 | sub index ($c) { 8 | $c->render_with( 9 | sub ($c, $params, $format) { 10 | my $res = $c->get_api('Kwalitee') or return; 11 | 12 | given ($format) { 13 | when ('json') { 14 | return { json => $res }; 15 | } 16 | when ('') { 17 | return { 18 | render => 'kwalitee', 19 | stash => $res, 20 | }; 21 | } 22 | } 23 | return; 24 | }, 25 | ); 26 | } 27 | 28 | sub indicator ($c) { 29 | $c->render_with( 30 | sub ($c, $params, $format) { 31 | my $tab = $params->{tab} // 'Indicator'; 32 | my $tab_class = $c->tab_class("Kwalitee", $tab); 33 | my $res = $c->get_api($tab_class, $params) or return; 34 | 35 | given ($format) { 36 | when ('json') { 37 | return { json => $res }; 38 | } 39 | when ('') { 40 | return { 41 | render => $c->template_name($tab_class), 42 | stash => $res, 43 | }; 44 | } 45 | } 46 | }, 47 | ); 48 | } 49 | 50 | 1; 51 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Controller/Ranking.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Controller::Ranking; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Web::Controller', -signatures; 4 | use experimental qw/switch/; 5 | use String::CamelCase qw/decamelize/; 6 | no warnings qw/deprecated/; 7 | 8 | sub index ($c) { 9 | $c->render_with( 10 | sub ($c, $params, $format) { 11 | 12 | my $tab = $params->{tab} // 'FiveOrMore'; 13 | my $league = $params->{league} = decamelize($tab); 14 | my $data = $c->get_api('Ranking', $params) or return; 15 | 16 | given ($format) { 17 | when ('json') { 18 | return { json => $data }; 19 | } 20 | when ('') { 21 | return { 22 | render => "ranking/$league", 23 | stash => { 24 | league => $league, 25 | ranking => $data, 26 | }, 27 | }; 28 | } 29 | } 30 | }, 31 | ); 32 | } 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Controller/Recent.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Controller::Recent; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Web::Controller', -signatures; 4 | use experimental qw/switch/; 5 | no warnings qw/deprecated/; 6 | 7 | sub index ($c) { 8 | $c->render_with( 9 | sub ($c, $params, $format) { 10 | my $res = $c->get_api('Recent', $params) or return; 11 | 12 | given ($format) { 13 | when ('json') { 14 | return { json => $res }; 15 | } 16 | when ('') { 17 | return { 18 | render => 'recent', 19 | stash => $res, 20 | }; 21 | } 22 | } 23 | return; 24 | }, 25 | ); 26 | } 27 | 28 | 1; 29 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Controller/Root.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Controller::Root; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Web::Controller', -signatures; 4 | 5 | sub check_maintenance ($c) { 6 | my $status = $c->get_api('Status') // {}; 7 | 8 | if ($status->{maintenance}) { 9 | $c->stash("cpants.has_notice" => 1); 10 | $c->stash("cpants.notice_maintenance" => 1); 11 | } 12 | $c->stash('last_analyzed', $status->{last_analyzed}); 13 | $c->stash('tracking_id', WWW::CPANTS->instance->config->{tracking_id}); 14 | return 1; 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Controller/Search.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Controller::Search; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Web::Controller', -signatures; 4 | use experimental qw/switch/; 5 | no warnings qw/deprecated/; 6 | 7 | sub search ($c) { 8 | $c->render_with( 9 | sub ($c, $params, $format) { 10 | my $name = $params->{name}; 11 | my $res; 12 | if (defined $name and $name ne '') { 13 | $res = $c->get_api('Search', $params) or return; 14 | if (@{ $res->{authors} } == 1 && !@{ $res->{dists} }) { 15 | return { redirect_to => '/author/' . $res->{authors}[0] }; 16 | } 17 | if (@{ $res->{dists} } == 1 && !@{ $res->{authors} }) { 18 | return { redirect_to => '/dist/' . $res->{dists}[0] }; 19 | } 20 | } 21 | 22 | given ($format) { 23 | when ('') { 24 | return $res if $res->{redirect_to}; 25 | return { 26 | render => 'search', 27 | stash => $res, 28 | }; 29 | } 30 | } 31 | return; 32 | }, 33 | ); 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Controller/Stats.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Controller::Stats; 2 | 3 | use Mojo::Base 'WWW::CPANTS::Web::Controller', -signatures; 4 | use experimental qw/switch/; 5 | no warnings qw/deprecated/; 6 | 7 | sub index ($c) { 8 | $c->render_with( 9 | sub ($c, $params, $format) { 10 | my $tab = $params->{tab} // ''; 11 | my $tab_class = $c->tab_class("Stats", $tab); 12 | my $res = $c->get_api($tab_class, $params) or return; 13 | 14 | given ($format) { 15 | when ('json') { 16 | return { json => $res }; 17 | } 18 | when ('') { 19 | return { 20 | render => $c->template_name($tab_class), 21 | stash => $res, 22 | }; 23 | } 24 | } 25 | }, 26 | ); 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Plugin/Hooks.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Plugin::Hooks; 2 | 3 | use Mojo::Base 'Mojolicious::Plugin', -signatures; 4 | use Mojo::Path; 5 | use IO::Compress::Gzip qw(gzip $GzipError); 6 | 7 | sub register ($self, $app, $conf) { 8 | $app->hook(before_dispatch => \&before_dispatch); 9 | $app->hook(after_render => \&after_render); 10 | } 11 | 12 | sub before_dispatch ($c) { 13 | return if $c->req->headers->header('X-Forwarded-For'); 14 | return unless ($c->req->headers->accept_encoding || '') =~ /gzip/i; 15 | my $path = $c->stash->{path} || $c->req->url->path->clone->canonicalize; 16 | return unless my @parts = @{ Mojo::Path->new("$path")->parts }; 17 | return if $parts[0] eq '..'; 18 | my $rel = join('/', @parts) . '.gz'; 19 | if (my $file = $c->app->static->file($rel)) { 20 | my $type = $rel =~ /\.(\w+)\.gz$/ ? $c->app->types->type($1) : undef; 21 | $c->res->headers->content_type($type || 'text/plain'); 22 | $c->res->headers->content_encoding('gzip'); 23 | $c->app->static->serve_asset($c, $file) or return; 24 | $c->stash->{'mojo.static'}++; 25 | return !!$c->rendered; 26 | } 27 | } 28 | 29 | sub after_render ($c, $output, $format) { 30 | return if $c->stash->{'mojo.static'}; 31 | return if $c->req->headers->header('X-Forwarded-For'); 32 | return if $c->req->is_xhr; 33 | return unless ($c->req->headers->accept_encoding || '') =~ /gzip/i; 34 | return if $c->res->content->is_multipart || $c->res->content->is_dynamic; 35 | 36 | $c->res->headers->content_encoding('gzip'); 37 | gzip $output, \my $compressed or die $GzipError; 38 | $$output = $compressed; 39 | } 40 | 41 | 1; 42 | -------------------------------------------------------------------------------- /lib/WWW/CPANTS/Web/Util/URL.pm: -------------------------------------------------------------------------------- 1 | package WWW::CPANTS::Web::Util::URL; 2 | 3 | use Mojo::Base -strict, -signatures; 4 | use Mojo::URL; 5 | use Gravatar::URL (); 6 | use HTML::Entities (); 7 | use Regexp::Common qw/URI/; 8 | 9 | sub metacpan_url ($dist) { 10 | Mojo::URL->new(sprintf 'https://metacpan.org/release/%s/%s', @$dist{qw/author name_version/}); 11 | } 12 | 13 | sub bugtracker_url ($dist) { 14 | if (my $url = $dist->{bugtracker_url}) { 15 | Mojo::URL->new($url); 16 | } else { 17 | $url = Mojo::URL->new('https://rt.cpan.org/Public/Dist/Display.html'); 18 | $url->query(Name => $dist->{name}); 19 | $url; 20 | } 21 | } 22 | 23 | sub repository_url ($dist) { 24 | my $url = $dist->{repository_url} or return; 25 | $url =~ s!^git://github!https://github!; 26 | Mojo::URL->new($url); 27 | } 28 | 29 | sub gravatar_url ($pause_id) { 30 | Gravatar::URL::gravatar_url( 31 | email => ($pause_id // '__dummy__') . '@cpan.org', 32 | size => 130, 33 | default => 'identicon', 34 | https => 1, 35 | ); 36 | } 37 | 38 | sub linkify ($text) { 39 | return '' unless defined $text; 40 | $text = HTML::Entities::encode_entities($text); 41 | $text =~ s!($RE{URI}{HTTP}{-scheme => 'https?'})!$1!gr; 42 | } 43 | 44 | 1; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpants", 3 | "version": "5.0.0", 4 | "description": "CPAN Testing Service", 5 | "author": "Kenichi Ishigaki", 6 | "license": "Artistic-1.0-Perl", 7 | "repository": "https://github.com/cpants/www-cpants", 8 | "dependencies": { 9 | "bootstrap": "^3.4.1", 10 | "datatables": "^1.10.18", 11 | "datatables-responsive": "^1.0.7", 12 | "font-awesome": "^4.7.0", 13 | "html5shiv": "^3.7.3", 14 | "jquery": "^3.6.0", 15 | "jquery-jsonview": "^1.2.3", 16 | "jquery-migrate": "^3.3.2", 17 | "respond.js": "^1.4.2", 18 | "sprintf-js": "^1.1.2", 19 | "strftime": "^0.10.0", 20 | "swagger-ui-dist": "^5.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /public/cpants/css/cpants.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | display: inline; 3 | } 4 | h2 { 5 | margin-top: 10px; 6 | margin-bottom: 30px; 7 | } 8 | h3 { 9 | margin-bottom: 30px; 10 | } 11 | section { 12 | padding-bottom: 50px; 13 | } 14 | img { 15 | max-width: 100%; 16 | } 17 | #footer { 18 | margin-top: 50px; 19 | } 20 | .sidebar ul.dropdown-menu li a { 21 | padding-left: 3px; 22 | } 23 | .list-flat-disc { 24 | padding-left: 0px; 25 | list-style: outside disc none; 26 | } 27 | .list-csv { 28 | list-style: none; 29 | padding-left: 0px; 30 | display: inline; 31 | } 32 | .list-csv li { 33 | display: inline; 34 | } 35 | .list-csv li:after { 36 | content: ", "; 37 | } 38 | .list-csv li:last-child:after { 39 | content: ""; 40 | } 41 | .navbar-static-top { 42 | margin-bottom: 0px; 43 | } 44 | #main { 45 | padding-top: 20px; 46 | } 47 | -------------------------------------------------------------------------------- /public/datatables-responsive/js/responsive.jqueryui.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI integration for DataTables' Responsive 2 | * ©2015 SpryMedia Ltd - datatables.net/license 3 | */ 4 | 5 | (function( factory ){ 6 | if ( typeof define === 'function' && define.amd ) { 7 | // AMD 8 | define( ['jquery', 'datatables.net-jqui', 'datatables.net-responsive'], function ( $ ) { 9 | return factory( $, window, document ); 10 | } ); 11 | } 12 | else if ( typeof exports === 'object' ) { 13 | // CommonJS 14 | module.exports = function (root, $) { 15 | if ( ! root ) { 16 | root = window; 17 | } 18 | 19 | if ( ! $ || ! $.fn.dataTable ) { 20 | $ = require('datatables.net-jqui')(root, $).$; 21 | } 22 | 23 | if ( ! $.fn.dataTable.Responsive ) { 24 | require('datatables.net-responsive')(root, $); 25 | } 26 | 27 | return factory( $, root, root.document ); 28 | }; 29 | } 30 | else { 31 | // Browser 32 | factory( jQuery, window, document ); 33 | } 34 | }(function( $, window, document, undefined ) { 35 | 'use strict'; 36 | var DataTable = $.fn.dataTable; 37 | 38 | 39 | var _display = DataTable.Responsive.display; 40 | var _original = _display.modal; 41 | 42 | _display.modal = function ( options ) { 43 | return function ( row, update, render ) { 44 | if ( ! $.fn.dialog ) { 45 | _original( row, update, render ); 46 | } 47 | else { 48 | if ( ! update ) { 49 | $( '
' ) 50 | .append( render() ) 51 | .appendTo( 'body' ) 52 | .dialog( $.extend( true, { 53 | title: options && options.header ? options.header( row ) : '', 54 | width: 500 55 | }, options.dialog ) ); 56 | } 57 | } 58 | }; 59 | }; 60 | 61 | 62 | return DataTable.Responsive; 63 | })); 64 | -------------------------------------------------------------------------------- /public/datatables/images/Sorting icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/datatables/images/Sorting icons.psd -------------------------------------------------------------------------------- /public/datatables/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/datatables/images/favicon.ico -------------------------------------------------------------------------------- /public/datatables/images/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/datatables/images/sort_asc.png -------------------------------------------------------------------------------- /public/datatables/images/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/datatables/images/sort_asc_disabled.png -------------------------------------------------------------------------------- /public/datatables/images/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/datatables/images/sort_both.png -------------------------------------------------------------------------------- /public/datatables/images/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/datatables/images/sort_desc.png -------------------------------------------------------------------------------- /public/datatables/images/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/datatables/images/sort_desc_disabled.png -------------------------------------------------------------------------------- /public/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpants/www-cpants/cf926b448b67039889c0877d618d76ff9abd4a51/public/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/highcharts/modules/no-data-to-display.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highcharts JS v4.1.4 (2015-03-10) 3 | Plugin for displaying a message when there is no data visible in chart. 4 | 5 | (c) 2010-2014 Highsoft AS 6 | Author: Oystein Moseng 7 | 8 | License: www.highcharts.com/license 9 | */ 10 | (function(c){function i(){return!!this.points.length}function e(){this.hasData()?this.hideNoData():this.showNoData()}var f=c.seriesTypes,d=c.Chart.prototype,g=c.getOptions(),h=c.extend,j=c.each;h(g.lang,{noData:"No data to display"});g.noData={position:{x:0,y:0,align:"center",verticalAlign:"middle"},attr:{},style:{fontWeight:"bold",fontSize:"12px",color:"#60606a"}};j(["pie","gauge","waterfall","bubble"],function(a){if(f[a])f[a].prototype.hasData=i});c.Series.prototype.hasData=function(){return this.visible&& 11 | this.dataMax!==void 0&&this.dataMin!==void 0};d.showNoData=function(a){var b=this.options,a=a||b.lang.noData,b=b.noData;if(!this.noDataLabel)this.noDataLabel=this.renderer.label(a,0,0,null,null,null,null,null,"no-data").attr(b.attr).css(b.style).add(),this.noDataLabel.align(h(this.noDataLabel.getBBox(),b.position),!1,"plotBox")};d.hideNoData=function(){if(this.noDataLabel)this.noDataLabel=this.noDataLabel.destroy()};d.hasData=function(){for(var a=this.series,b=a.length;b--;)if(a[b].hasData()&&!a[b].options.isInternal)return!0; 12 | return!1};d.callbacks.push(function(a){c.addEvent(a,"load",e);c.addEvent(a,"redraw",e)})})(Highcharts); 13 | -------------------------------------------------------------------------------- /public/highcharts/themes/grid-light.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid-light theme for Highcharts JS 3 | * @author Torstein Honsi 4 | */ 5 | 6 | // Load the fonts 7 | Highcharts.createElement('link', { 8 | href: '//fonts.googleapis.com/css?family=Dosis:400,600', 9 | rel: 'stylesheet', 10 | type: 'text/css' 11 | }, null, document.getElementsByTagName('head')[0]); 12 | 13 | Highcharts.theme = { 14 | colors: ["#7cb5ec", "#f7a35c", "#90ee7e", "#7798BF", "#aaeeee", "#ff0066", "#eeaaee", 15 | "#55BF3B", "#DF5353", "#7798BF", "#aaeeee"], 16 | chart: { 17 | backgroundColor: null, 18 | style: { 19 | fontFamily: "Dosis, sans-serif" 20 | } 21 | }, 22 | title: { 23 | style: { 24 | fontSize: '16px', 25 | fontWeight: 'bold', 26 | textTransform: 'uppercase' 27 | } 28 | }, 29 | tooltip: { 30 | borderWidth: 0, 31 | backgroundColor: 'rgba(219,219,216,0.8)', 32 | shadow: false 33 | }, 34 | legend: { 35 | itemStyle: { 36 | fontWeight: 'bold', 37 | fontSize: '13px' 38 | } 39 | }, 40 | xAxis: { 41 | gridLineWidth: 1, 42 | labels: { 43 | style: { 44 | fontSize: '12px' 45 | } 46 | } 47 | }, 48 | yAxis: { 49 | minorTickInterval: 'auto', 50 | title: { 51 | style: { 52 | textTransform: 'uppercase' 53 | } 54 | }, 55 | labels: { 56 | style: { 57 | fontSize: '12px' 58 | } 59 | } 60 | }, 61 | plotOptions: { 62 | candlestick: { 63 | lineColor: '#404048' 64 | } 65 | }, 66 | 67 | 68 | // General 69 | background2: '#F0F0EA' 70 | 71 | }; 72 | 73 | // Apply the theme 74 | Highcharts.setOptions(Highcharts.theme); 75 | -------------------------------------------------------------------------------- /public/jquery-jsonview/jquery.jsonview.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | .jsonview { 3 | font-family: monospace; 4 | font-size: 1.1em; 5 | white-space: pre-wrap; } 6 | .jsonview .prop { 7 | font-weight: bold; } 8 | .jsonview .null { 9 | color: red; } 10 | .jsonview .bool { 11 | color: blue; } 12 | .jsonview .num { 13 | color: blue; } 14 | .jsonview .string { 15 | color: green; 16 | white-space: pre-wrap; } 17 | .jsonview .string.multiline { 18 | display: inline-block; 19 | vertical-align: text-top; } 20 | .jsonview .collapser { 21 | position: absolute; 22 | left: -1em; 23 | cursor: pointer; } 24 | .jsonview .collapsible { 25 | transition: height 1.2s; 26 | transition: width 1.2s; } 27 | .jsonview .collapsible.collapsed { 28 | height: .8em; 29 | width: 1em; 30 | display: inline-block; 31 | overflow: hidden; 32 | margin: 0; } 33 | .jsonview .collapsible.collapsed:before { 34 | content: "…"; 35 | width: 1em; 36 | margin-left: .2em; } 37 | .jsonview .collapser.collapsed { 38 | transform: rotate(0deg); } 39 | .jsonview .q { 40 | display: inline-block; 41 | width: 0px; 42 | color: transparent; } 43 | .jsonview li { 44 | position: relative; } 45 | .jsonview ul { 46 | list-style: none; 47 | margin: 0 0 0 2em; 48 | padding: 0; } 49 | .jsonview h1 { 50 | font-size: 1.2em; } 51 | 52 | /*# sourceMappingURL=jquery.jsonview.css.map */ 53 | -------------------------------------------------------------------------------- /public/sprintf-js/angular-sprintf.min.js: -------------------------------------------------------------------------------- 1 | /*! sprintf-js v1.1.2 | Copyright (c) 2007-present, Alexandru Mărășteanu | BSD-3-Clause */ 2 | !function(){"use strict";angular.module("sprintf",[]).filter("sprintf",function(){return function(){return sprintf.apply(null,arguments)}}).filter("fmt",["$filter",function(t){return t("sprintf")}]).filter("vsprintf",function(){return function(t,n){return vsprintf(t,n)}}).filter("vfmt",["$filter",function(t){return t("vsprintf")}])}(); 3 | //# sourceMappingURL=angular-sprintf.min.js.map 4 | -------------------------------------------------------------------------------- /public/sprintf-js/angular-sprintf.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["angular-sprintf.js"],"names":["angular","module","filter","sprintf","apply","arguments","$filter","format","argv","vsprintf"],"mappings":";CAEC,WACG,aAEAA,QACIC,OAAO,UAAW,IAClBC,OAAO,UAAW,WACd,OAAO,WACH,OAAOC,QAAQC,MAAM,KAAMC,cAGnCH,OAAO,MAAO,CAAC,UAAW,SAASI,GAC/B,OAAOA,EAAQ,cAEnBJ,OAAO,WAAY,WACf,OAAO,SAASK,EAAQC,GACpB,OAAOC,SAASF,EAAQC,MAGhCN,OAAO,OAAQ,CAAC,UAAW,SAASI,GAChC,OAAOA,EAAQ,eAnB1B","file":"angular-sprintf.min.js","sourcesContent":["/* global angular, sprintf, vsprintf */\n\n!function() {\n 'use strict'\n\n angular.\n module('sprintf', []).\n filter('sprintf', function() {\n return function() {\n return sprintf.apply(null, arguments)\n }\n }).\n filter('fmt', ['$filter', function($filter) {\n return $filter('sprintf')\n }]).\n filter('vsprintf', function() {\n return function(format, argv) {\n return vsprintf(format, argv)\n }\n }).\n filter('vfmt', ['$filter', function($filter) {\n return $filter('vsprintf')\n }])\n}(); // eslint-disable-line\n"]} -------------------------------------------------------------------------------- /t/00_load.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use FindBin; 3 | use Test::More; 4 | use File::Find; 5 | use lib glob "$FindBin::Bin/../extlib/*/lib"; 6 | 7 | my $root = "$FindBin::Bin/.."; 8 | 9 | my $fail; 10 | find({ 11 | wanted => sub { 12 | my $file = $File::Find::name; 13 | my ($package) = $file =~ m!lib/(.+)\.pm$!; 14 | return unless $package; 15 | $package =~ s|/|::|g; 16 | require_ok $package or $fail++; 17 | }, 18 | no_chdir => 1, 19 | }, 20 | "$root/lib" 21 | ); 22 | 23 | require_ok "$root/api.psgi" or $fail++; 24 | 25 | BAIL_OUT if $fail; 26 | 27 | done_testing; 28 | -------------------------------------------------------------------------------- /t/10_tasks.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use FindBin; 3 | use Test::More; 4 | use WWW::CPANTS::Test::TestPAN; 5 | use WWW::CPANTS::Bin::Runner; 6 | use Syntax::Keyword::Try; 7 | 8 | my $testpan = WWW::CPANTS::Test::TestPAN->new->setup; 9 | 10 | my $runner = WWW::CPANTS::Bin::Runner->new; 11 | 12 | for my $name ($runner->ctx->task_names->@*) { 13 | try { 14 | local @ARGV; 15 | ok $runner->run_tasks($name), $name; 16 | } catch { 17 | fail "$name: $@" 18 | } 19 | } 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/analysis/required_perl.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use Test::More; 4 | 5 | test_analysis( 6 | '/required_perl', 7 | # test requires 5.006, runtime recommends 5.008, declared runtime requires 5.008008 8 | ['HUGMEIR/Params-Lazy-0.005.tar.gz', '5.008008'], 9 | 10 | # declared runtime requires 5.006, test requires v5.18.1, runtime requires v5.18.2 11 | ['HUGMEIR/MariaDB-NonBlocking-0.20.tar.gz', '5.018002'], 12 | 13 | # declared runtime requires 5.008005, runtime requires 5.010001 14 | ['NIKOLAS/Shout-2.1.4.tar.gz', '5.010001'], 15 | 16 | # declared runtime requires 5.008001 17 | ['PALIK/Gearman-2.004.015.tar.gz', '5.008001'], 18 | 19 | # runtime requires 5.026_000 20 | ['VBAR/Regexp-Compare-0.31.tar.gz', '5.026000'], 21 | 22 | # declared runtime requires 0 23 | ['SEBNOW/ResourcePool-Resource-Redis-1.tar.gz', undef], 24 | ); 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/api_model/acme/cpan_authors/stats/active_authors.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply re any array_each subhashof); 6 | 7 | $ENV{TEST_ACME_MODULES} = 1; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/Acme-CPANAuthors-0.26.tar.gz', 12 | 'ISHIGAKI/Acme-CPANAuthors-Japanese-0.190426.tar.gz', 13 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 14 | ); 15 | my $testpan = setup_testpan(@files); 16 | $testpan->cpan->update_indices; 17 | 18 | load_task('UpdateCPANIndices::Whois')->run; 19 | load_task('Traverse')->run(qw/ISHIGAKI/); 20 | load_task('Analyze')->run(@files); 21 | load_task('PostProcess::UpdateAuthorStats')->run; 22 | load_task('PostProcess::UpdateRanking')->run; 23 | load_task('Acme::UpdateModules')->run; 24 | load_task('Acme::UpdateStats')->run; 25 | }; 26 | 27 | my $model = api_model('Acme::CPANAuthors::Stats::ActiveAuthors'); 28 | 29 | subtest 'japanese' => sub { 30 | my $res = $model->load({ module_id => 'japanese' }); 31 | cmp_deeply $res => { 32 | 'data' => array_each(subhashof({ 33 | 'active_authors' => re('[0-9]+'), 34 | 'year' => re('20[0-9]+'), 35 | })), 36 | 'recordsTotal' => re('[0-9]+'), 37 | }; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/api_model/acme/cpan_authors/stats/new_authors.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply re any array_each subhashof); 6 | 7 | $ENV{TEST_ACME_MODULES} = 1; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/Acme-CPANAuthors-0.26.tar.gz', 12 | 'ISHIGAKI/Acme-CPANAuthors-Japanese-0.190426.tar.gz', 13 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 14 | ); 15 | my $testpan = setup_testpan(@files); 16 | $testpan->cpan->update_indices; 17 | 18 | load_task('UpdateCPANIndices::Whois')->run; 19 | load_task('Traverse')->run(qw/ISHIGAKI/); 20 | load_task('Analyze')->run(@files); 21 | load_task('PostProcess::UpdateAuthorStats')->run; 22 | load_task('PostProcess::UpdateRanking')->run; 23 | load_task('Acme::UpdateModules')->run; 24 | load_task('Acme::UpdateStats')->run; 25 | }; 26 | 27 | my $model = api_model('Acme::CPANAuthors::Stats::NewAuthors'); 28 | 29 | subtest 'japanese' => sub { 30 | my $res = $model->load({ module_id => 'japanese' }); 31 | cmp_deeply $res => { 32 | 'data' => array_each(subhashof({ 33 | 'new_authors' => re('[0-9]+'), 34 | 'year' => re('20[0-9]+'), 35 | })), 36 | 'recordsTotal' => re('[0-9]+'), 37 | }; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/api_model/acme/cpan_authors/stats/new_releases.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply re any array_each subhashof); 6 | 7 | $ENV{TEST_ACME_MODULES} = 1; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/Acme-CPANAuthors-0.26.tar.gz', 12 | 'ISHIGAKI/Acme-CPANAuthors-Japanese-0.190426.tar.gz', 13 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 14 | ); 15 | my $testpan = setup_testpan(@files); 16 | $testpan->cpan->update_indices; 17 | 18 | load_task('UpdateCPANIndices::Whois')->run; 19 | load_task('Traverse')->run(qw/ISHIGAKI/); 20 | load_task('Analyze')->run(@files); 21 | load_task('PostProcess::UpdateAuthorStats')->run; 22 | load_task('PostProcess::UpdateRanking')->run; 23 | load_task('Acme::UpdateModules')->run; 24 | load_task('Acme::UpdateStats')->run; 25 | }; 26 | 27 | my $model = api_model('Acme::CPANAuthors::Stats::NewReleases'); 28 | 29 | subtest 'japanese' => sub { 30 | my $res = $model->load({ module_id => 'japanese' }); 31 | cmp_deeply $res => { 32 | 'data' => array_each(subhashof({ 33 | 'new_releases' => re('[0-9]+'), 34 | 'year' => re('20[0-9]+'), 35 | })), 36 | 'recordsTotal' => re('[0-9]+'), 37 | }; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/api_model/acme/cpan_authors/stats/releases.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply re any array_each subhashof); 6 | 7 | $ENV{TEST_ACME_MODULES} = 1; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/Acme-CPANAuthors-0.26.tar.gz', 12 | 'ISHIGAKI/Acme-CPANAuthors-Japanese-0.190426.tar.gz', 13 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 14 | ); 15 | my $testpan = setup_testpan(@files); 16 | $testpan->cpan->update_indices; 17 | 18 | load_task('UpdateCPANIndices::Whois')->run; 19 | load_task('Traverse')->run(qw/ISHIGAKI/); 20 | load_task('Analyze')->run(@files); 21 | load_task('PostProcess::UpdateAuthorStats')->run; 22 | load_task('PostProcess::UpdateRanking')->run; 23 | load_task('Acme::UpdateModules')->run; 24 | load_task('Acme::UpdateStats')->run; 25 | }; 26 | 27 | my $model = api_model('Acme::CPANAuthors::Stats::Releases'); 28 | 29 | subtest 'japanese' => sub { 30 | my $res = $model->load({ module_id => 'japanese' }); 31 | cmp_deeply $res => { 32 | 'data' => array_each(subhashof({ 33 | 'releases' => re('[0-9]+'), 34 | 'year' => re('20[0-9]+'), 35 | })), 36 | 'recordsTotal' => re('[0-9]+'), 37 | }; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/api_model/v4/search.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my $testpan = setup_testpan( 9 | 'HEIKO/IsUTF8-0.2.tar.gz', 10 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 11 | 'ISAAC/DBIx-Objects-0.04.tar.gz', 12 | ); 13 | load_task('Traverse')->run(qw/HEIKO ISHIGAKI ISAAC/); 14 | }; 15 | 16 | my $model = api_model('V4::Search'); 17 | 18 | subtest 'matches both' => sub { 19 | my $res = $model->load({ name => 'Is' }); 20 | cmp_deeply $res => { 21 | 'authors' => [ 22 | 'ISAAC', 23 | 'ISHIGAKI' 24 | ], 25 | 'dists' => ['IsUTF8'], 26 | }; 27 | }; 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /t/api_model/v4/table/cpan_distributions_by.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 10 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', 11 | ); 12 | my $testpan = setup_testpan(@files); 13 | load_task('Traverse')->run(qw/ISHIGAKI/); 14 | load_task('Analyze')->run(@files); 15 | }; 16 | 17 | my $model = api_model('V4::Table::CPANDistributionsBy'); 18 | 19 | subtest 'some of mine' => sub { 20 | my $res = $model->load({ pause_id => 'ISHIGAKI' }); 21 | cmp_deeply $res => { 22 | 'data' => [{ 23 | 'date' => '2011-05-31', 24 | 'fails' => [], 25 | 'latest' => 1, 26 | 'name' => 'Path-Extended', 27 | 'score' => 100, 28 | 'version' => '0.19' 29 | }, 30 | { 31 | 'date' => '2011-01-06', 32 | 'fails' => ['main_module_version_matches_dist_version'], 33 | 'latest' => 1, 34 | 'name' => 'Pod-Perldocs', 35 | 'score' => '96.88', 36 | 'version' => '0.17' 37 | }, 38 | ], 39 | 'recordsTotal' => 2, 40 | }; 41 | }; 42 | 43 | done_testing; 44 | -------------------------------------------------------------------------------- /t/api_model/v4/table/fails_in.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | load_task('Traverse')->run(qw/ISHIGAKI/); 13 | load_task('Analyze')->run(@files); 14 | load_task('PostProcess::UpdateCaches')->run; 15 | }; 16 | 17 | my $model = api_model('V4::Table::FailsIn'); 18 | 19 | subtest 'some of mine' => sub { 20 | my $res = $model->load({ name => 'main_module_version_matches_dist_version' }); 21 | cmp_deeply $res => { 22 | 'data' => [{ 23 | 'author' => 'ISHIGAKI', 24 | 'availability' => 'Latest', 25 | 'date' => '2011-01-06', 26 | 'name_version' => 'Pod-Perldocs-0.17' 27 | }], 28 | 'indicator' => { 29 | 'defined_in' => 'Module::CPANTS::SiteKwalitee::Version', 30 | 'description' => "The version and/or name of the main module in this distribution doesn't match the distribution version and/or name.", 31 | 'level' => 'core', 32 | 'name' => 'main_module_version_matches_dist_version', 33 | 'remedy' => 'Make sure that the main module name and version are the same of the distribution.' 34 | }, 35 | 'recordsTotal' => 1 36 | }; 37 | }; 38 | 39 | done_testing; 40 | -------------------------------------------------------------------------------- /t/api_model/v4/table/ranking.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply re); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run; 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess::UpdateAuthorStats')->run; 18 | load_task('PostProcess::UpdateRanking')->run; 19 | }; 20 | 21 | my $model = api_model('V4::Table::Ranking'); 22 | 23 | subtest 'some of mine' => sub { 24 | my $res = $model->load({ league => 'less_than_five' }); 25 | cmp_deeply $res => { 26 | 'data' => [{ 27 | 'average_core_kwalitee' => 100, 28 | 'average_kwalitee' => '153.12', 29 | 'cpan_dists' => 1, 30 | 'has_perl6' => 0, 31 | 'json' => undef, 32 | 'json_updated_at' => undef, 33 | 'last_new_release_at' => '1306782406', 34 | 'last_release_at' => '1306782406', 35 | 'last_analyzed_at' => re('^[0-9]+$'), 36 | 'pause_id' => 'ISHIGAKI', 37 | 'rank' => 1, 38 | 'recent_dists' => 0, 39 | }], 40 | 'recordsTotal' => 1, 41 | }; 42 | }; 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/api_model/v4/table/recent.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use WWW::CPANTS::Util::Datetime; 5 | use Test::More; 6 | use Test::Deep qw(cmp_deeply); 7 | use Test::MockTime::HiRes; 8 | 9 | fixture { 10 | my @files = ( 11 | 'BAYASHI/App-jl-0.12.tar.gz', 12 | 'ISHIGAKI/JSON-4.02.tar.gz', 13 | ); 14 | my $testpan = setup_testpan(@files); 15 | load_task('Traverse')->run(qw/BAYASHI ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | }; 18 | 19 | my $model = api_model('V4::Table::Recent'); 20 | 21 | subtest 'some of mine' => sub { 22 | my $epoch = epoch_from_date('2019-04-30'); 23 | mock_time { 24 | my $res = $model->load; 25 | cmp_deeply $res => { 26 | 'data' => [{ 27 | 'date' => '2019-06-23', 28 | 'name' => 'App-jl', 29 | 'pause_id' => 'BAYASHI', 30 | 'score' => '100', 31 | 'version' => '0.12' 32 | }, { 33 | 'date' => '2019-02-23', 34 | 'name' => 'JSON', 35 | 'pause_id' => 'ISHIGAKI', 36 | 'score' => '100', 37 | 'version' => '4.02' 38 | }, 39 | ], 40 | 'recordsTotal' => 2, 41 | }; 42 | } 43 | $epoch; 44 | }; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/api_model/v4/table/recent_by.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use WWW::CPANTS::Util::Datetime; 5 | use Test::More; 6 | use Test::Deep qw(cmp_deeply); 7 | use Test::MockTime::HiRes; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/JSON-4.02.tar.gz', 12 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 13 | ); 14 | my $testpan = setup_testpan(@files); 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | }; 18 | 19 | my $model = api_model('V4::Table::RecentBy'); 20 | 21 | subtest 'some of mine' => sub { 22 | my $epoch = epoch_from_date('2019-04-30'); 23 | mock_time { 24 | my $res = $model->load({ pause_id => 'ISHIGAKI' }); 25 | cmp_deeply $res => { 26 | 'data' => [{ 27 | 'date' => '2019-02-23', 28 | 'fails' => [], 29 | 'name' => 'JSON', 30 | 'score' => '100', 31 | 'version' => '4.02' 32 | }, 33 | ], 34 | 'recordsTotal' => 1, 35 | }; 36 | } 37 | $epoch; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/api_model/v4/table/releases_of.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-4.02.tar.gz', 10 | 'MAKAMAKA/JSON-2.90.tar.gz', 11 | ); 12 | my $testpan = setup_testpan(@files); 13 | load_task('Traverse')->run(qw/ISHIGAKI MAKAMAKA/); 14 | load_task('Analyze')->run(@files); 15 | }; 16 | 17 | my $model = api_model('V4::Table::ReleasesOf'); 18 | 19 | subtest 'some of mine' => sub { 20 | my $res = $model->load({ name => 'JSON' }); 21 | cmp_deeply $res => { 22 | 'data' => [{ 23 | 'author' => 'ISHIGAKI', 24 | 'availability' => 'CPAN', 25 | 'date' => '2019-02-23', 26 | 'name' => 'JSON', 27 | 'score' => '100', 28 | 'version' => '4.02' 29 | }, 30 | { 31 | 'author' => 'MAKAMAKA', 32 | 'availability' => 'CPAN', 33 | 'date' => '2013-10-31', 34 | 'name' => 'JSON', 35 | 'score' => '100', 36 | 'version' => '2.90' 37 | } 38 | 39 | ], 40 | 'recordsTotal' => 2, 41 | }; 42 | }; 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/api_model/v4/table/reverse_dependencies_of.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Pod-PerldocJp-0.19.tar.gz', 10 | 'TOKUHIROM/LiBot-v0.0.3.tar.gz', 11 | ); 12 | my $testpan = setup_testpan(@files); 13 | load_task('Traverse')->run(qw/ISHIGAKI TOKUHIROM/); 14 | load_task('Analyze')->run(@files); 15 | load_task('PostProcess::UpdateReverseDependency')->run; 16 | }; 17 | 18 | my $model = api_model('V4::Table::ReverseDependenciesOf'); 19 | 20 | subtest 'some of mine' => sub { 21 | my $res = $model->load({ name => 'Pod-PerldocJp' }); 22 | cmp_deeply $res => { 23 | 'data' => [{ 24 | 'author' => 'TOKUHIROM', 25 | 'date' => '2013-10-12', 26 | 'name_version' => 'LiBot-v0.0.3', 27 | 'score' => '96.88' 28 | }], 29 | 'recordsTotal' => 1, 30 | }; 31 | }; 32 | 33 | done_testing; 34 | -------------------------------------------------------------------------------- /t/api_model/v5/author.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply re); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess::UpdateAuthorStats')->run; 18 | load_task('PostProcess::UpdateRanking')->run; 19 | }; 20 | 21 | my $model = api_model('V5::Author'); 22 | 23 | subtest 'me' => sub { 24 | my $res = $model->load({ pause_id => 'ISHIGAKI' }); 25 | cmp_deeply $res => { 26 | 'ascii_name' => 'Kenichi Ishigaki', 27 | 'average_core_kwalitee' => '100', 28 | 'average_kwalitee' => '153.12', 29 | 'cpan_dists' => 1, 30 | 'deleted' => 0, 31 | 'email' => 'ishigaki@cpan.org', 32 | 'has_perl6' => 0, 33 | 'homepage' => 'http://d.hatena.ne.jp/charsbar', 34 | 'joined_on' => '2005-12-17', 35 | 'last_analyzed_at' => re('^[0-9]+$'), 36 | 'last_new_release_on' => '2011-05-31', 37 | 'last_release_on' => '2011-05-31', 38 | 'name' => 'Kenichi Ishigaki', 39 | 'nologin' => 0, 40 | 'pause_id' => 'ISHIGAKI', 41 | 'rank' => 1, 42 | 'recent_dists' => 0, 43 | 'system' => 0, 44 | 'year' => 2005, 45 | }; 46 | }; 47 | 48 | done_testing; 49 | -------------------------------------------------------------------------------- /t/api_model/v5/author/cpan_distributions.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 10 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', 11 | ); 12 | my $testpan = setup_testpan(@files); 13 | load_task('UpdateCPANIndices::Whois')->run; 14 | load_task('Traverse')->run(qw/ISHIGAKI/); 15 | load_task('Analyze')->run(@files); 16 | }; 17 | 18 | my $model = api_model('V5::Author::CPANDistributions'); 19 | 20 | subtest 'mine' => sub { 21 | my $res = $model->load({ pause_id => 'ISHIGAKI' }); 22 | cmp_deeply $res => { 23 | 'data' => [{ 24 | 'date' => '2011-05-31', 25 | 'fails' => [], 26 | 'latest' => 1, 27 | 'name' => 'Path-Extended', 28 | 'score' => '100', 29 | 'version' => '0.19' 30 | }, 31 | { 32 | 'date' => '2011-01-06', 33 | 'fails' => ['main_module_version_matches_dist_version'], 34 | 'latest' => 1, 35 | 'name' => 'Pod-Perldocs', 36 | 'score' => '96.88', 37 | 'version' => '0.17' 38 | }, 39 | ], 40 | 'recordsTotal' => 2, 41 | }; 42 | }; 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/api_model/v5/author/feed.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 10 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', 11 | ); 12 | my $testpan = setup_testpan(@files); 13 | load_task('UpdateCPANIndices::Whois')->run; 14 | load_task('Traverse')->run(qw/ISHIGAKI/); 15 | load_task('Analyze')->run(@files); 16 | }; 17 | 18 | my $model = api_model('V5::Author::Feed'); 19 | 20 | subtest 'mine' => sub { 21 | my $res = $model->load({ pause_id => 'ISHIGAKI' }); 22 | cmp_deeply $res => { 23 | 'entries' => [{ 24 | 'id' => 'Path-Extended-0.19', 25 | 'link' => '/release/ISHIGAKI/Path-Extended-0.19', 26 | 'summary' => 'Kwalitee: 100', 27 | 'title' => 'Path-Extended-0.19', 28 | 'updated' => '2011-05-31T04:06:46Z' 29 | }, 30 | { 31 | 'id' => 'Pod-Perldocs-0.17', 32 | 'link' => '/release/ISHIGAKI/Pod-Perldocs-0.17', 33 | 'summary' => 'Kwalitee: 96.88; Core Fails: main_module_version_matches_dist_version', 34 | 'title' => 'Pod-Perldocs-0.17', 35 | 'updated' => '2011-01-06T16:12:00Z' 36 | }, 37 | ], 38 | 'feed' => { 39 | 'author' => 'CPANTS', 40 | 'title' => 'CPANTS Feed for ISHIGAKI', 41 | 'updated' => '2011-05-31T04:06:46Z' 42 | }, 43 | }; 44 | }; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/api_model/v5/author/recent_releases.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use WWW::CPANTS::Util::Datetime; 5 | use Test::More; 6 | use Test::Deep qw(cmp_deeply); 7 | use Test::MockTime::HiRes; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/JSON-4.02.tar.gz', 12 | ); 13 | my $testpan = setup_testpan(@files); 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | }; 18 | 19 | my $model = api_model('V5::Author::RecentReleases'); 20 | 21 | subtest 'matches both' => sub { 22 | my $epoch = epoch_from_date('2019-04-30'); 23 | mock_time { 24 | my $res = $model->load({ pause_id => 'ISHIGAKI' }); 25 | cmp_deeply $res => { 26 | 'data' => [{ 27 | 'date' => '2019-02-23', 28 | 'fails' => [], 29 | 'name' => 'JSON', 30 | 'score' => '100', 31 | 'version' => '4.02' 32 | }, 33 | ], 34 | 'recordsTotal' => 1 35 | }; 36 | } 37 | $epoch; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/api_model/v5/dist/errors.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | load_task('Traverse')->run(qw/ISHIGAKI/); 13 | load_task('Analyze')->run(@files); 14 | }; 15 | 16 | my $model = api_model('V5::Dist::Errors'); 17 | 18 | my @params = ( 19 | [dist => { name => 'Pod-Perldocs' }], 20 | [release => { name => 'Pod-Perldocs-0.17', pause_id => 'ISHIGAKI' }], 21 | ); 22 | 23 | for my $param (@params) { 24 | my ($name, $load_arg) = @$param; 25 | my $subtest = sub ($load_arg) { 26 | return sub { 27 | my $res = $model->load($load_arg); 28 | cmp_deeply $res => { 29 | 'data' => { 30 | 'errors' => [{ 31 | 'category' => 'configure_prereq_matches_use', 32 | 'error' => ['Module::Build'], 33 | }, 34 | ], 35 | }, 36 | }; 37 | }; 38 | }; 39 | subtest $name => $subtest->($load_arg); 40 | } 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/api_model/v5/dist/metadata.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use WWW::CPANTS::Util::JSON; 5 | use Test::More; 6 | use Test::Deep qw(cmp_deeply); 7 | 8 | fixture { 9 | my @files = ( 10 | 'ISHIGAKI/Test-CPANfile-0.06.tar.gz', 11 | ); 12 | my $testpan = setup_testpan(@files); 13 | load_task('Traverse')->run(qw/ISHIGAKI/); 14 | load_task('Analyze')->run(@files); 15 | }; 16 | 17 | my $model = api_model('V5::Dist::Metadata'); 18 | 19 | my @params = ( 20 | [dist => { name => 'Test-CPANfile' }], 21 | [release => { name => 'Test-CPANfile-0.06', pause_id => 'ISHIGAKI' }], 22 | ); 23 | 24 | for my $param (@params) { 25 | my ($name, $load_arg) = @$param; 26 | my $subtest = sub ($load_arg) { 27 | return sub { 28 | my $res = $model->load($load_arg); 29 | # too big and fragile to compare 30 | ok my $metadata = $res->{data}{metadata}; 31 | ok ref $metadata; 32 | note explain $metadata; 33 | }; 34 | }; 35 | subtest $name => $subtest->($load_arg); 36 | } 37 | 38 | done_testing; 39 | -------------------------------------------------------------------------------- /t/api_model/v5/dist/releases.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Test-CPANfile-0.06.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | load_task('Traverse')->run(qw/ISHIGAKI/); 13 | load_task('Analyze')->run(@files); 14 | }; 15 | 16 | my $model = api_model('V5::Dist::Releases'); 17 | 18 | my @params = ( 19 | [dist => { name => 'Test-CPANfile' }], 20 | [release => { name => 'Test-CPANfile-0.06', pause_id => 'ISHIGAKI' }], 21 | ); 22 | 23 | for my $param (@params) { 24 | SKIP: { 25 | my ($name, $load_arg) = @$param; 26 | skip "$name is not supported yet", 1 if $name ne 'dist'; ## FIXME! 27 | my $subtest = sub ($load_arg) { 28 | return sub { 29 | my $res = $model->load($load_arg); 30 | note explain $res; 31 | cmp_deeply $res => { 32 | 'data' => [{ 33 | 'author' => 'ISHIGAKI', 34 | 'availability' => 'CPAN', 35 | 'date' => '2019-01-16', 36 | 'name' => 'Test-CPANfile', 37 | 'score' => '100', 38 | 'version' => '0.06' 39 | }], 40 | 'recordsTotal' => 1 41 | }; 42 | }; 43 | }; 44 | subtest $name => $subtest->($load_arg); 45 | } 46 | } 47 | 48 | done_testing; 49 | -------------------------------------------------------------------------------- /t/api_model/v5/dist/used_by.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-PP-4.02.tar.gz', 10 | 'SKAJI/Perl-Build-1.29.tar.gz', 11 | ); 12 | my $testpan = setup_testpan(@files); 13 | load_task('Traverse')->run(qw/ISHIGAKI SKAJI/); 14 | load_task('Analyze')->run(@files); 15 | load_task('PostProcess::UpdateReverseDependency')->run; 16 | }; 17 | 18 | my $model = api_model('V5::Dist::UsedBy'); 19 | 20 | my @params = ( 21 | [dist => { name => 'JSON-PP' }], 22 | [release => { name => 'JSON-PP-4.02', pause_id => 'ISHIGAKI' }], 23 | ); 24 | 25 | for my $param (@params) { 26 | my ($name, $load_arg) = @$param; 27 | my $subtest = sub ($load_arg) { 28 | return sub { 29 | my $res = $model->load($load_arg); 30 | cmp_deeply $res => { 31 | 'data' => [{ 32 | 'author' => 'SKAJI', 33 | 'date' => '2018-12-21', 34 | 'name_version' => 'Perl-Build-1.29', 35 | 'score' => '100' 36 | }], 37 | 'recordsTotal' => 1 38 | }; 39 | }; 40 | }; 41 | subtest $name => $subtest->($load_arg); 42 | } 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/api_model/v5/kwalitee.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | load_task('Traverse')->run(qw/ISHIGAKI/); 13 | load_task('Analyze')->run(@files); 14 | }; 15 | 16 | my $model = api_model('V5::Kwalitee'); 17 | 18 | subtest 'mine' => sub { 19 | my $res = $model->load({}); 20 | 21 | # too big and too fragile for cmp_deeply 22 | for my $type (qw/core extra experimental/) { 23 | ok exists $res->{data}{ $type . '_indicators' }, "data for $type indicator exists"; 24 | } 25 | ok !grep { $_->{backpan_fails} } $res->{data}{core_indicators}->@*; 26 | ok grep { $_->{backpan_fails} } $res->{data}{extra_indicators}->@*; 27 | ok grep { $_->{backpan_fails} } $res->{data}{experimental_indicators}->@*; 28 | }; 29 | 30 | done_testing; 31 | -------------------------------------------------------------------------------- /t/api_model/v5/kwalitee/fail.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | load_task('Traverse')->run(qw/ISHIGAKI/); 13 | load_task('Analyze')->run(@files); 14 | load_task('PostProcess::UpdateCaches')->run; 15 | }; 16 | 17 | my $model = api_model('V5::Kwalitee::Fail'); 18 | 19 | subtest 'matches both' => sub { 20 | my $res = $model->load({ name => 'main_module_version_matches_dist_version' }); 21 | note explain $res; 22 | cmp_deeply $res => { 23 | 'data' => [{ 24 | 'author' => 'ISHIGAKI', 25 | 'availability' => 'Latest', 26 | 'date' => '2011-01-06', 27 | 'name_version' => 'Pod-Perldocs-0.17' 28 | }, 29 | ], 30 | 'indicator' => { 31 | 'defined_in' => 'Module::CPANTS::SiteKwalitee::Version', 32 | 'description' => "The version and/or name of the main module in this distribution doesn't match the distribution version and/or name.", 33 | 'level' => 'core', 34 | 'name' => 'main_module_version_matches_dist_version', 35 | 'remedy' => 'Make sure that the main module name and version are the same of the distribution.' 36 | }, 37 | 'recordsTotal' => 1 38 | }; 39 | }; 40 | 41 | done_testing; 42 | -------------------------------------------------------------------------------- /t/api_model/v5/ranking.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess::UpdateAuthorStats')->run; 18 | load_task('PostProcess::UpdateRanking')->run; 19 | }; 20 | 21 | my $model = api_model('V5::Ranking'); 22 | 23 | subtest 'less_than_five' => sub { 24 | my $res = $model->load({ league => 'less_than_five' }); 25 | cmp_deeply $res => { 26 | 'data' => [{ 27 | 'average_core_kwalitee' => '100', 28 | 'cpan_dists' => 1, 29 | 'last_release_on' => '2011-05-31', 30 | 'pause_id' => 'ISHIGAKI', 31 | 'rank' => 1 32 | }, 33 | ], 34 | 'recordsTotal' => 1 35 | }; 36 | }; 37 | 38 | done_testing; 39 | -------------------------------------------------------------------------------- /t/api_model/v5/recent.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use WWW::CPANTS::Util::Datetime; 5 | use Test::More; 6 | use Test::Deep qw(cmp_deeply); 7 | use Test::MockTime::HiRes; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/JSON-4.02.tar.gz', 12 | ); 13 | my $testpan = setup_testpan(@files); 14 | load_task('Traverse')->run(qw/ISHIGAKI/); 15 | load_task('Analyze')->run(@files); 16 | }; 17 | 18 | my $model = api_model('V5::Recent'); 19 | 20 | subtest 'recent' => sub { 21 | my $epoch = epoch_from_date('2019-04-30'); 22 | mock_time { 23 | my $res = $model->load; 24 | cmp_deeply $res => { 25 | 'data' => [{ 26 | 'date' => '2019-02-23', 27 | 'name' => 'JSON', 28 | 'pause_id' => 'ISHIGAKI', 29 | 'score' => '100', 30 | 'version' => '4.02' 31 | }, 32 | ], 33 | 'recordsTotal' => 1 34 | }; 35 | } 36 | $epoch; 37 | }; 38 | 39 | done_testing; 40 | -------------------------------------------------------------------------------- /t/api_model/v5/search.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | fixture { 8 | my @files = ( 9 | 'HEIKO/IsUTF8-0.2.tar.gz', 10 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 11 | 'ISAAC/DBIx-Objects-0.04.tar.gz', 12 | ); 13 | my $testpan = setup_testpan(@files); 14 | load_task('Traverse')->run(qw/HEIKO ISHIGAKI ISAAC/); 15 | }; 16 | 17 | my $model = api_model('V5::Search'); 18 | 19 | subtest 'matches both' => sub { 20 | my $res = $model->load({ name => 'Is' }); 21 | cmp_deeply $res => { 22 | 'authors' => [ 23 | 'ISAAC', 24 | 'ISHIGAKI' 25 | ], 26 | 'dists' => ['IsUTF8'], 27 | }; 28 | }; 29 | 30 | done_testing; 31 | -------------------------------------------------------------------------------- /t/api_model/v5/status.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Deep qw(cmp_deeply); 6 | 7 | my $model = api_model('V5::Status'); 8 | 9 | subtest 'status' => sub { 10 | my $testpan = setup_testpan; 11 | load_task('AnalyzeAll')->run; 12 | 13 | my $res = $model->load({}); 14 | ok $res->{last_analyzed}; 15 | ok !$res->{maintenance}; 16 | }; 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/buildtool_not_executable.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | plan skip_all => "This test doesn't work well under Windows" if $^O eq 'MSWin32'; 5 | 6 | test_kwalitee( 7 | 'buildtool_not_executable', 8 | ['COOLMEN/Test-More-Color-0.04.tar.gz', 0], # 2963 9 | ['CODECHILD/Thread-SharedTreeSet-0.01.tar.gz', 0], # 3191 10 | ['CODECHILD/Set-Definition-0.01.tar.gz', 0], # 4242 11 | ['JEEN/WebService-Aladdin-0.08.tar.gz', 0], # 4287 12 | ['DSYRTM/Guitar-Scale-0.06.tar.gz', 0], # 4469 13 | ['OVNTATAR/GitHub-Jobs-0.04.tar.gz', 0], # 5322 14 | ['EGILES/X11-Terminal-v1.0.0.tar.gz', 0], # 6205 15 | ['LTP/Game-Life-0.05.tar.gz', 0], # 6535 16 | ['LTP/IBM-SONAS-0.021.tar.gz', 0], # 7177 17 | ['DSYRTM/File-BetweenTree-1.02.tar.gz', 0], # 7590 18 | ); 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/kwalitee/configure_prereq_matches_use.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'configure_prereq_matches_use', 6 | ['SAPER/Shell-Parser-0.04.tar.gz', 0], # Module::Build 7 | ['INFOFLEX/DBD_SQLFLEX_8.2.tar.gz', 0], # DBI etc 8 | ['TRIZK/Mail-SpamAssassin-Contrib-Plugin-IPFilter-1.2.tar.gz', 0], # Devel::AssertOS 9 | ['FINNPERL/App-PerlXLock-0.08.tar.gz', 0], # Inline::Module 10 | ); 11 | 12 | done_testing; 13 | -------------------------------------------------------------------------------- /t/kwalitee/consistent_version.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'consistent_version', 6 | ['ANDK/CPAN-Test-Dummy-Perl5-Make-1.05.tar.gz', 0], # 2225 7 | ['CWEST/ACME-Error-0.03.tar.gz', 0], # 2233 8 | 9 | # no modules 10 | ['GAAS/Perl-API-0.01.tar.gz', 1], # 2003 11 | ['QJZHOU/killperl-1.01.tar.gz', 1], # 2069 12 | ['AWRIGLEY/prep-1.03.tar.gz', 1], # 2125 13 | ['LEOCHARRE/m4a2mp3-1.01.tar.gz', 1], # 2161 14 | ['NKUITSE/pathup-1.01.tar.gz', 1], # 2190 15 | 16 | # no version 17 | ['PINYAN/bitflags-0.10.tar.gz', 1], # 2053 18 | ['NI-S/Regexp-0.001.tar.gz', 1], # 2137 19 | 20 | # undef (invalid version) only 21 | ['TIMA/Bundle-Melody-Test-0.9.6a.tar.gz', 1], # 2042 22 | ); 23 | 24 | done_testing; 25 | -------------------------------------------------------------------------------- /t/kwalitee/distname_matches_name_in_meta.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'distname_matches_name_in_meta', 6 | # .pm 7 | ['LEEJO/CGI.pm-4.02.tar.gz', 0], 8 | 9 | # name with :: 10 | ['VLADO/CGI-AuthRegister-1.0.tar.gz', 0], 11 | ['BARBIE/CPAN-Testers-WWW-Reports-3.48.tar.gz', 0], 12 | 13 | # two dashes 14 | ['JESUS/Net--RabbitMQ-0.2.7.tar.gz', 0], 15 | 16 | # lacks name field in META 17 | ['DRSTEVE/stylehouse-20140427.tar.gz', 0], 18 | 19 | # name not to have been updated after rename (or copy) 20 | ['INA/Char/Latin1/Char-Latin1-0.96.tar.gz', 0], 21 | ['SADAMS/Mojolicious-Command-generate-upstart-0.02.tar.gz', 0], 22 | 23 | # distributed with the basename only 24 | ['JRUBIN/FT817COMM-0.9.9.tar.gz', 0], 25 | 26 | # with a prefix such as p5- and perl- 27 | ['SHMORIMO/p5-Text-Xslate-Syntax-Any-1.5015.tar.gz', 0], 28 | ['LOTTC/perl-OSDial-2.2.9.083.2.tar.gz', 0], 29 | ); 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/kwalitee/extractable.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'extractable', 6 | ['JHI/Statistics-DEA-0.04.tar.gz', -100], # 3468 7 | ['JACKS/CallerItem-1.0.tar.gz', -100], # 3479 8 | ['ACESTER/Math-GMatrix-0.2.tar.gz', -100], # 3775 9 | ['JACKS/AlarmCall-1.1.tar.gz', -100], # 3858 10 | ['JKAST/StatisticsDescriptive-1.1.tar.gz', -100], # 4098 11 | ['JHI/Statistics-Frequency-0.03.tar.gz', -100], # 4271 12 | ['MPOCOCK/GIFgraphExtensions-1.0.tar.gz', -100], # 4815 13 | ['SWORDSMAN/TSM_0.60.tar.gz', -100], # 5321 14 | ['STBEY/DFA-Kleene-1.0.tar.gz', -100], # 5353 15 | 16 | # invalid header blocks 17 | ['BLCKSMTH/String-RexxParse-1.10.tar.gz', -100], 18 | ['GROMMEL/Acme-Turing-0.02.tar.gz', -100], 19 | 20 | # link errors 21 | ['KAMIPO/autobox-String-Inflector-0.02.tar.gz', -100], 22 | ['KAZUHO/DBIx-Replicate-0.04.tar.gz', -100], 23 | ); 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/kwalitee/extracts_nicely.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'extracts_nicely', 6 | # one directory, without dist version 7 | ['STEFANOS/Finance-Currency-Convert-ECB-0.3.tar.gz', 1], 8 | ['XINZHENG/BIE-Data-HDF5-Data-0.01.tar.gz', 1], 9 | # more than one directories (with a directory with prepending _) 10 | ['SUNNYP/Captcha-reCAPTCHA-0.98.tar.gz', 0], 11 | # no top directory 12 | ['SMS/Net-SMS-CSNetworks-0.08.tar.gz', 0], 13 | ['KEUVGRVL/Log-Basic-1.2.1.tar.gz', 0], 14 | ); 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/kwalitee/has_abstract_in_pod.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_abstract_in_pod', 6 | ['HACHI/MogileFS-Plugin-MetaData-0.01.tar.gz', 0], # 2059 7 | ['URI/Acme-Madness-1.00.tar.gz', 0], # 2090 8 | ['PJF/Payroll-AU-PAYG-0.01.tar.gz', 0], # 2097 9 | ['LLAP/Plack-Middleware-OptionsOK-0.01.tar.gz', 0], # 2117 10 | ['GMCCAR/Acme-ManekiNeko-0.03.tar.gz', 0], # 2161 11 | ['KALEYCHEV/Math-Combination_out-0.21.tar.gz', 0], # 2163 12 | ['LEOCHARRE/Getopt-Std-Strict-1.01.tar.gz', 0], # 2165 13 | ['TJC/CGI-Untaint-telephone-0.03.tar.gz', 0], # 2178 14 | ['STRZELEC/CGI-SpeedUp-0.11.tar.gz', 0], # 2187 15 | 16 | # has a dash, though not in the same line as the package 17 | ['HODEL/Brasil-Checar-CGC-1.01a.tar.gz', 0], # 2018 18 | 19 | # abstract in non-.pm file 20 | ['LEONT/App-find2perl-1.003.tar.gz', 1], 21 | 22 | # invalid =encoding (utf-8;) 23 | ['INGY/IO-All-0.40.tar.gz', 0], 24 | 25 | # script name with a dash instead of package name 26 | ['SKIRMESS/App-ReportPrereqs-0.001.tar.gz', 1], 27 | ); 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /t/kwalitee/has_buildtool.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_buildtool', 6 | ['LAWSONK/Gtk2-Ex-MPlayerEmbed-0.03.tar.gz', 0], # 465 7 | ['HEDWIG/Template-Plugin-Duration-0.01.tar.gz', 0], # 474 8 | ['WEBY/Mojolicious-Plugin-Mobi-0.02.tar.gz', 0], # 474 9 | ['TIMA/Amazon-Bezos-0.001.tar.gz', 0], # 510 10 | ['LEOCHARRE/LEOCHARRE-X11-WMCtrl-0.01.tar.gz', 0], # 514 11 | ['WOLDRICH/Bundle-Woldrich-Term-0.02.tar.gz', 0], # 530 12 | ['WOLDRICH/App-epic-0.014.tar.gz', 0], # 812 13 | ['TAG/AnyEvent-Peer39-0.32.tar.gz', 0], # 824 14 | ['ABCABC/CFTP_01.tar.gz', 0], # 897 15 | ['DAMBAR/Catalyst-Plugin-Imager-0.01.tar.gz', 0], # 1026 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_changelog.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_changelog', 6 | ['UNBIT/Net-uwsgi-1.1.tar.gz', 0], # 2409 7 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 8 | ['BENNING/Math-BaseMulti-1.00.tar.gz', 0], # 2942 9 | ['HEYTRAV/Mojolicious-Plugin-Libravatar-1.08.tar.gz', 0], # 3415 10 | ['TXH/Template-Plugin-Filter-MinifyHTML-0.02.tar.gz', 0], # 3484 11 | ['MANIGREW/SEG7-1.0.1.tar.gz', 0], # 3847 12 | ['MUIR/modules/rinetd.pl-1.2.tar.gz', 0], # 4319 13 | ['GSB/WWW-Crab-Client-0.03.tar.gz', 0], # 4352 14 | ['RSHADOW/libmojolicious-plugin-human-perl_0.6.orig.tar.gz', 0], # 4504 15 | ['SRPATT/Finance-Bank-CooperativeUKPersonal-0.02.tar.gz', 0], # 4991 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_human_readable_license.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_human_readable_license', 6 | ['JJUDD/DBIx-Class-TimeStamp-HiRes-v1.0.0.tar.gz', 0], # 2596 7 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 8 | ['SCILLEY/POE/Component/IRC/Plugin/IRCDHelp-0.02.tar.gz', 0], # 3243 9 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 10 | ['JEEN/WebService-Aladdin-0.08.tar.gz', 0], # 4287 11 | ['SMUELLER/Math-SymbolicX-Complex-1.01.tar.gz', 0], # 4719 12 | ['IAMCAL/Flickr-API-1.06.tar.gz', 0], # 5172 13 | ['ANANSI/Anansi-ObjectManager-0.06.tar.gz', 0], # 5246 14 | ); 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/kwalitee/has_known_license_in_source_file.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_known_license_in_source_file', 6 | ['JJUDD/DBIx-Class-TimeStamp-HiRes-v1.0.0.tar.gz', 0], # 2596 7 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 8 | ['SMUELLER/Math-SymbolicX-Complex-1.01.tar.gz', 0], # 4719 9 | ['IAMCAL/Flickr-API-1.06.tar.gz', 0], # 5172 10 | 11 | # =head1 AUTHOR / COPYRIGHT / LICENSE 12 | ['BJOERN/AI-CRM114-0.01.tar.gz', 1], 13 | 14 | # has =head1 COPYRIGHT AND LICENSE without closing =cut 15 | ['DAMI/DBIx-DataModel-2.39.tar.gz', 1], 16 | 17 | # has =head1 LICENSE followed by =head1 COPYRIGHT 18 | ['YSASAKI/App-pfswatch-0.08.tar.gz', 1], 19 | 20 | # ignore inc/Devel/CheckLib 21 | ['DJERIUS/Lua-API-0.02.tar.gz', 1], 22 | 23 | # https://github.com/cpants/www-cpants/issues/44 24 | ['NEILB/Business-CCCheck-0.09.tar.gz', 1], 25 | ); 26 | 27 | done_testing; 28 | -------------------------------------------------------------------------------- /t/kwalitee/has_license_in_source_file.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_license_in_source_file', 6 | ['JJUDD/DBIx-Class-TimeStamp-HiRes-v1.0.0.tar.gz', 0], # 2596 7 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 8 | ['SMUELLER/Math-SymbolicX-Complex-1.01.tar.gz', 0], # 4719 9 | ['IAMCAL/Flickr-API-1.06.tar.gz', 0], # 5172 10 | 11 | # =head1 AUTHOR / COPYRIGHT / LICENSE 12 | ['BJOERN/AI-CRM114-0.01.tar.gz', 1], 13 | 14 | # has =head1 COPYRIGHT AND LICENSE without closing =cut 15 | ['DAMI/DBIx-DataModel-2.39.tar.gz', 1], 16 | 17 | # has =head1 LICENSE followed by =head1 COPYRIGHT 18 | ['YSASAKI/App-pfswatch-0.08.tar.gz', 1], 19 | 20 | # bin/ only 21 | ['JONALLEN/pod2pdf-0.42.tar.gz', 1], 22 | 23 | # script/ only 24 | ['GOEVERT/RePrec-0.032.tar.gz', 1], 25 | 26 | # scripts/ only 27 | ['NKH/App-Term-Jump-0.04.32.tar.gz', 1], 28 | ); 29 | 30 | done_testing; 31 | -------------------------------------------------------------------------------- /t/kwalitee/has_manifest.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_manifest', 6 | ['LAWSONK/Gtk2-Ex-MPlayerEmbed-0.03.tar.gz', 0], # 465 7 | ['WEBY/Mojolicious-Plugin-Mobi-0.02.tar.gz', 0], # 474 8 | ['WOLDRICH/Bundle-Woldrich-Term-0.02.tar.gz', 0], # 530 9 | ['WOLDRICH/App-epic-0.014.tar.gz', 0], # 812 10 | ['TAG/AnyEvent-Peer39-0.32.tar.gz', 0], # 824 11 | ['ABCABC/CFTP_01.tar.gz', 0], # 897 12 | ['SAULIUS/Date-Holidays-LT-0.01.tar.gz', 0], # 1532 13 | ['RUFF/DJabberd-Authen-Dovecot-0.1.tar.gz', 0], # 2105 14 | ['UNBIT/Net-uwsgi-1.1.tar.gz', 0], # 2409 15 | ['GVENKAT/JSON-HPack-0.0.3.tar.gz', 0], # 2475 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_meta_json.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_meta_json', 6 | ['AANOAA/WebService-Naver-TTS-v0.0.3.tar.gz', 0], # 3610 7 | ['DLIMA/Business-BR-CNJ-0.01.tar.gz', 0], # 2419 8 | ['INA/Fake/Our/Fake-Our-0.12.tar.gz', 0], # 7333 9 | ['JJNAPIORK/Catalyst-Plugin-DBIC-ConsoleQueryLog-0.002.tar.gz', 0], # 9867 10 | ['KORSANI/Log-Funlog-Lang-0.4.tar.gz', 0], # 3472 11 | ['MASAKYST/Acme-Kiyoshi-Array-0.01.tar.gz', 0], # 8628 12 | ['RRVCKU/Lingua-Stem-Uk-0.01.tar.gz', 0], # 4724 13 | ['RSN/IO-All-SFTP-0.01.tar.gz', 0], # 2720 14 | ['TTNDY/Sendmail-AbuseIPDB-0.10.tar.gz', 0], # 6078 15 | ['VVELOX/Log-Colorize-Helper-0.1.1.tar.gz', 0], # 5890 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_meta_yml.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_meta_yml', 6 | ['UNBIT/Net-uwsgi-1.1.tar.gz', 0], # 2409 7 | ['ANANSI/Anansi-Singleton-0.02.tar.gz', 0], # 2664 8 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 9 | ['DAVIEIRA/Data_Translate-0.3.tar.gz', 0], # 2970 10 | ['ANANSI/Anansi-Class-0.03.tar.gz', 0], # 3028 11 | ['ANANSI/Anansi-Actor-0.04.tar.gz', 0], # 3157 12 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 13 | ['MANIGREW/SEG7-1.0.1.tar.gz', 0], # 3847 14 | ['HITHIM/Socket-Mmsg-0.02.tar.gz', 0], # 3946 15 | ['STEFANOS/Net-SMTP_auth-SSL-0.2.tar.gz', 0], # 4058 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_proper_version.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_proper_version', 6 | ['WINTRU/Mica-1.a.0.tar.gz', 0], # 1196 7 | ['TSUNODA/Sledge-Plugin-SNMP-0.01a.tar.gz', 0], # 1767 8 | ['TIMA/Bundle-Melody-Test-0.9.6a.tar.gz', 0], # 2042 9 | ['CFABER/libuuid-perl_0.02.orig.tar.gz', 0], # 2091 10 | ['DANPEDER/MIME-Base32-1.02a.tar.gz', 0], # 3136 11 | ['MOBILEART/Net-OmaDrm-0.10a.tar.gz', 0], # 3208 12 | ['ASKADNA/CGI-Application-Plugin-Eparam-0.04f.tar.gz', 0], # 3228 13 | ['SPECTRUM/Math-BigSimple-1.1a.tar.gz', 0], # 3269 14 | ['TSKIRVIN/HTML-FormRemove-0.3a.tar.gz', 0], # 3625 15 | ['SHY/Wifi/Wifi-0.01a.tar.gz', 0], # 3767 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_readme.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_readme', 6 | ['SEVEAS/Term-Multiplexed-0.1.0.tar.gz', 0], # 1701 7 | ['SROMANOV/App-Nopaste-Service-Dpaste-0.01.tar.gz', 0], # 2448 8 | ['SROMANOV/Games-Chess-Position-Unicode-0.01.tar.gz', 0], # 2629 9 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 10 | ['BOOK/Bundle-MetaSyntactic-1.026.tar.gz', 0], # 3178 11 | ['BENMEYER/Finance-btce-0.02.tar.gz', 0], # 3575 12 | ['SYSADM/Mojolicious-Plugin-DeCSRF-0.94.tar.gz', 0], # 3654 13 | ['BKB/Lingua-EN-PluralToSingular-0.06.tar.gz', 0], # 3747 14 | ['MANIGREW/SEG7-1.0.1.tar.gz', 0], # 3847 15 | ['BKB/Lingua-JA-Gairaigo-Fuzzy-0.02.tar.gz', 0], # 4159 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_separate_license_file.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_separate_license_file', 6 | ['TOBYINK/Platform-Windows-0.002.tar.gz', 0], # 2206 7 | ['TOBYINK/Platform-Unix-0.002.tar.gz', 0], # 2264 8 | ['BOOK/Acme-MetaSyntactic-errno-1.003.tar.gz', 0], # 2889 9 | ['COOLMEN/Test-More-Color-0.04.tar.gz', 0], # 2963 10 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 11 | ['HITHIM/Socket-Mmsg-0.02.tar.gz', 0], # 3946 12 | ['COOLMEN/Test-Mojo-More-0.04.tar.gz', 0], # 4301 13 | ['MUGENKEN/Bundle-Unicheck-0.02.tar.gz', 0], # 4596 14 | ['SMUELLER/Math-SymbolicX-Complex-1.01.tar.gz', 0], # 4719 15 | ['CHENRYN/Nagios-Plugin-ByGmond-0.01.tar.gz', 0], # 5159 16 | 17 | # LICENSE.txt (RT #114274) 18 | ['IKEGAMI/Syntax-Feature-Void-v1.0.0.tar.gz', 1], # 19 | ); 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/kwalitee/has_tests.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_tests', 6 | ['MSTROUT/pumpkin-pragma-placeholder-0.001.tar.gz', 0], # 961 7 | ['SEVEAS/Term-Multiplexed-0.1.0.tar.gz', 0], # 1701 8 | ['PBLAIR/Copy-From-Git-0.000302.tar.gz', 0], # 2235 9 | ['UNBIT/Net-uwsgi-1.1.tar.gz', 0], # 2409 10 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 11 | ['NKH/Devel-Depend-Cl-0.06.tar.gz', 0], # 3540 12 | ['SEMUELF/WWW-Github-Files-0.02.tar.gz', 0], # 3634 13 | ['MUIR/modules/rinetd.pl-1.2.tar.gz', 0], # 4319 14 | ['SRPATT/Finance-Bank-CooperativeUKPersonal-0.02.tar.gz', 0], # 4991 15 | ['NANARDON/RT-Interface-Email-Filter-CheckMessageId-0.1.tar.gz', 0], # 5398 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_tests_in_t_dir.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_tests_in_t_dir', 6 | ['SEVEAS/Term-Multiplexed-0.1.0.tar.gz', 0], # 1701 7 | ['UNBIT/Net-uwsgi-1.1.tar.gz', 0], # 2409 8 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 9 | ['MUIR/modules/rinetd.pl-1.2.tar.gz', 0], # 4319 10 | ['SRPATT/Finance-Bank-CooperativeUKPersonal-0.02.tar.gz', 0], # 4991 11 | ['IAMCAL/Flickr-API-1.06.tar.gz', 0], # 5172 12 | ['PJB/Speech-Speakup-1.04.tar.gz', 0], # 7410 13 | ['FIBO/Task-Viral-20130508.tar.gz', 0], # 8128 14 | ['ADAMBA/Algorithm-MOS-0.001.tar.gz', 0], # 8570 15 | ['FIBO/Task-BeLike-FIBO-20130508.tar.gz', 0], # 8922 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/has_version.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'has_version', 6 | ['IDIVISION/nginx.pm.tar.gz', 0], # 1059 7 | ['PIROLIX/MIME_Base32_Readable.zip', 0], # 1461 8 | ['WILSONPM/OutlineNumber.tar.gz', 0], # 1798 9 | ['STEFANOS/Text-Phonetic-Caverphone.tar.gz', 0], # 1912 10 | ['JACKS/SelfStubber.tar.gz', 0], # 1934 11 | ['DAHILLMA/Geo-GoogleEarth-Document-modules.tar.gz', 0], # 1965 12 | ['SMAN/rpn.tar.gz', 0], # 1966 13 | 14 | # version 0, though problematic 15 | ['DAMOG/WWW-Tumblr-0.tar.gz', 1], 16 | ['TOKUHIROM/Perl-Build-0.tar.gz', 1], 17 | ['STEFANOS/Text-Phonetic-MatchRatingCodex-1-0.tar.gz', 1], 18 | 19 | # Parse::Distname allows the following 20 | ['ANDK/Memo-bindist-any-bin-2-archname-compiler.tar.gz', 1], # 1076 21 | ); 22 | 23 | done_testing; 24 | -------------------------------------------------------------------------------- /t/kwalitee/main_module_version_matches_dist_version.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'main_module_version_matches_dist_version', 6 | ['JEROMEMCK/Net-ICQ-On-1.7.tar.gz', 0], # 1005 7 | ['ARCANEZ/WWW-Mailchimp-0.006_02.tar.gz', 0], # 1007 8 | ['IDIVISION/nginx.pm.tar.gz', 0], # 1059 9 | ['MALUKU/sofu-config/sofu-config-0.2.tar.gz', 0], # 1059 10 | ['ZLIPTON/Bundle-Bonsai-0.02.tar.gz', 0], # 1075 11 | ['ANDK/Memo-bindist-any-bin-2-archname-compiler.tar.gz', 0], # 1076 12 | ['IDIVISION/nginx-0.0.1.tar.gz', 0], # 1082 13 | ['MTHURN/Devel-Fail-Make-1.005.tar.gz', 0], # 1088 14 | ['ILYAZ/os2/tk/binary/update-03.zip', 0], # 1125 15 | 16 | # illegal provides 17 | ['DJERIUS/Lua-API-0.02.tar.gz', 0], 18 | 19 | # v-version without v in the dist version 20 | ['SHLOMIF/HTML-TabbedExamples-Generate-0.0.5.tar.gz', 1], 21 | ['JANDREW/Data-Walk-Extracted-0.024.004.tar.gz', 1], 22 | ['DOMM/Module-ExtractUse-0.33.tar.gz', 1], 23 | 24 | # no main module 25 | ['MEWILCOX/apache.authznetldap.02.tar.gz', 0], # 1051 26 | 27 | # four dots (may lose info if parsed as a number) 28 | ['MISHIN/POD2-RU-5.18.0.1.33.tar.gz', 1], 29 | 30 | # provides without version(s) 31 | ['BURNERSK/Encoding-HandleUtf8-0.002.tar.gz', 0], 32 | ); 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/kwalitee/manifest_matches_dist.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'manifest_matches_dist', 6 | ['UNBIT/Net-uwsgi-1.1.tar.gz', 0], # 2409 7 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 8 | ['COOLMEN/Test-More-Color-0.04.tar.gz', 0], # 2963 9 | ['APIOLI/YAMC-0.2.tar.gz', 0], # 3245 10 | ['BENMEYER/Finance-btce-0.02.tar.gz', 0], # 3575 11 | ['SJQUINNEY/MooseX-Types-EmailAddress-1.1.2.tar.gz', 0], # 4257 12 | ['RSHADOW/libmojolicious-plugin-human-perl_0.6.orig.tar.gz', 0], # 4504 13 | ['LEPREVOST/Math-SparseMatrix-Operations-0.06.tar.gz', 0], # 4593 14 | ['SRPATT/Finance-Bank-CooperativeUKPersonal-0.02.tar.gz', 0], # 4991 15 | ['SULLR/Net-PcapWriter-0.71.tar.gz', 0], # 5337 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/meta_json_conforms_to_known_spec.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_json_conforms_to_known_spec', 6 | # scalar license 7 | ['DMAKI/XML-RSS-Liberal-0.04.tar.gz', 0], 8 | 9 | # no abstract 10 | ['MLEHMANN/AnyEvent-DBus-0.31.tar.gz', 0], 11 | 12 | # resource -> license 13 | ['BARBIE/Template-Plugin-Lingua-EN-NameCase-0.01.tar.gz', 0], 14 | 15 | # invalid license 16 | ['TOMO/src/Net-SMTPS-0.03.tar.gz', 0], # perl 17 | ['RSAVAGE/DBIx-Admin-CreateTable-2.08.tgz', 0], # artistic_2_0 18 | 19 | # 'origin' for 'repository' does not have a URL scheme 20 | ['RJBS/Sub-Import-0.092800.tar.gz', 0], 21 | ['MARCEL/Permute-Named-1.100980.tar.gz', 0], 22 | 23 | # '' for 'repository' is not a valid URL. 24 | ['KEEDI/Pod-Weaver-Section-Encoding-0.100830.tar.gz', 0], 25 | 26 | # git@github.com:... does not have a URL authority 27 | ['TIMB/PostgreSQL-PLPerl-Trace-1.001.tar.gz', 0], 28 | 29 | # Custom key must begin with 'x_' or 'X_'. 30 | ['AVAR/Bot-Twatterhose-0.04.tar.gz', 0], 31 | 32 | # value is an undefined string 33 | ['TOBYINK/Return-Type-0.004.tar.gz', 0], 34 | ); 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /t/kwalitee/meta_json_is_parsable.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_json_is_parsable', 6 | # invalid control characters in abstract 7 | ['JOHND/Data-Properties-YAML-0.04.tar.gz', 0], # \r 8 | ['WINFINIT/Catalyst-Plugin-ModCluster-0.02.tar.gz', 0], # \t 9 | ['LIKHATSKI/Ubic-Watchdog-Notice-0.31.tar.gz', 0], # \@ 10 | 11 | # '"' expected 12 | ['SHURD/DMTF-CIM-WSMan-v0.09.tar.gz', 0], 13 | ['RFREIMUTH/RandomJungle-0.05.tar.gz', 0], 14 | 15 | # VMS 16 | ['PFAUT/VMS-Time-0_1.zip', 0], 17 | 18 | # malformed JSON string 19 | ['MAXS/Palm-MaTirelire-1.12.tar.gz', 0], # \x{fffd} 20 | 21 | # illegal backslash escape sequence 22 | ['JHTHORSEN/Convos-0.6.tar.gz', 0], 23 | 24 | # missing trailing comma (seemingly edited by hand) 25 | ['PLOCKABY/TAP-Formatter-BambooExtended-1.01.tar.gz', 0], 26 | ); 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/kwalitee/meta_yml_conforms_to_known_spec.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_yml_conforms_to_known_spec', 6 | # '' for 'Carp' is not a valid version. 7 | ['MATIU/WWW-AfinimaKi-0.1.tar.gz', 0], # 518 8 | 9 | # 'HASH(0xb8924f0)' for 'repository' does not have a URL scheme 10 | ['JBERGER/Alien-GSL-0.01.tar.gz', 0], # 556 11 | 12 | # License 'Public domain' is invalid (license) 13 | ['SEVEAS/Term-Multiplexed-0.2.2.tar.gz', 0], # 1683 14 | 15 | # Missing mandatory field, 'author' (author) 16 | ['ANDK/CPAN-Test-Dummy-Perl5-Make-CircDepeOne-1.00.tar.gz', 0], # 1893 17 | 18 | # 'ExtUtils::MakeMaker version 6.17' for 'generated_by' is not a valid version. (requires -> generated_by) 19 | ['MITTI/PDF-Report-Table-1.00.tar.gz', 0], # 2300 20 | 21 | # Expected a map structure from string or file. (requires) 22 | ['ANDK/CPAN-Test-Dummy-Perl5-Make-Expect-1.00.tar.gz', 0], # 2323 23 | 24 | # 'DateTime::Event::Easter' for 'Time::Piece' is not a valid version. 25 | ['CLIFFORDJ/Date-Holidays-EnglandWales-0.01.tar.gz', 0], # 3085 26 | 27 | # Key 'Acme::Unic<ö>de' is not a legal module name. 28 | ['SCHWIGON/acme-unicode/Acme-Uenicoede-0.0501.tar.gz', 0], # 3651 29 | 30 | # 'meta-spec' => '1.1' is kind of broken, but it's not regarded 31 | # as a fatal error as of CPAN::Meta 2.132830. 32 | ['JOSEPHW/XML-Writer-0.545.tar.gz', 1], 33 | ); 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/kwalitee/meta_yml_declares_perl_version.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_yml_declares_perl_version', 6 | ['TOBYINK/Platform-Windows-0.002.tar.gz', 0], # 2206 7 | ['TOBYINK/Platform-Unix-0.002.tar.gz', 0], # 2264 8 | ['COOLMEN/Test-More-Color-0.04.tar.gz', 0], # 2963 9 | ['TXH/Template-Plugin-Filter-MinifyHTML-0.02.tar.gz', 0], # 3484 10 | ['SMUELLER/Math-SymbolicX-Complex-1.01.tar.gz', 0], # 4719 11 | ['SMUELLER/Math-Symbolic-Custom-CCompiler-1.03.tar.gz', 0], # 5244 12 | ['LTP/Game-Life-0.05.tar.gz', 0], # 6535 13 | ['KPEE/Carp-Growl-0.0.3.tar.gz', 0], # 6682 14 | ); 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/kwalitee/meta_yml_has_license.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_yml_has_license', 6 | ['CHENGANG/Log-Lite-0.05.tar.gz', 0], # 2739 7 | ['SCILLEY/POE/Component/IRC/Plugin/IRCDHelp-0.02.tar.gz', 0], # 3243 8 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 9 | ['HITHIM/Socket-Mmsg-0.02.tar.gz', 0], # 3946 10 | ['FAYLAND/Acme-CPANAuthors-Chinese-0.26.tar.gz', 0], # 4474 11 | ['BENNIE/ACME-KeyboardMarathon-1.15.tar.gz', 0], # 4479 12 | ['ALEXP/Catalyst-Model-DBI-0.32.tar.gz', 0], # 4686 13 | ['YTURTLE/Nephia-Plugin-Response-YAML-0.01.tar.gz', 0], # 4948 14 | ['CHENRYN/Nagios-Plugin-ByGmond-0.01.tar.gz', 0], # 5159 15 | ['IAMCAL/Flickr-API-1.06.tar.gz', 0], # 5172 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/meta_yml_has_provides.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_yml_has_provides', 6 | ['TOBYINK/Platform-Windows-0.002.tar.gz', 0], # 2206 7 | ['TOBYINK/Platform-Unix-0.002.tar.gz', 0], # 2264 8 | ['COOLMEN/Test-More-Color-0.04.tar.gz', 0], # 2963 9 | ['COOLMEN/Test-Mojo-More-0.04.tar.gz', 0], # 4301 10 | ['SMUELLER/Math-SymbolicX-Complex-1.01.tar.gz', 0], # 4719 11 | ['CHENRYN/Nagios-Plugin-ByGmond-0.01.tar.gz', 0], # 5159 12 | ['SMUELLER/Math-Symbolic-Custom-CCompiler-1.03.tar.gz', 0], # 5244 13 | ['LTP/Game-Life-0.05.tar.gz', 0], # 6535 14 | ); 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/kwalitee/meta_yml_has_repository_resource.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_yml_has_repository_resource', 6 | ['ISHIGAKI/Acme-CPANAuthors-Japanese-0.131002.tar.gz', 0], 7 | ['ISHIGAKI/Acme-CPANAuthors-0.23.tar.gz', 1], 8 | ); 9 | done_testing; 10 | -------------------------------------------------------------------------------- /t/kwalitee/meta_yml_is_parsable.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'meta_yml_is_parsable', 6 | # No META.yml 7 | ['UNBIT/Net-uwsgi-1.1.tar.gz', 1], # 2409 8 | 9 | # Stream does not end with newline character 10 | ['SCILLEY/POE/Component/IRC/Plugin/IRCDHelp-0.02.tar.gz', 0], # 3243 11 | 12 | # Error reading from file: utf8 "\xB0" does not map to Unicode 13 | ['WINTRU/Mica-1.a.0.tar.gz', 0], # 1196 14 | 15 | # CPAN::Meta::YAML does not support a feature in line 16 | ['STRO/Task-CPANAuthors-STRO-PPMCreator-2009.1018.tar.gz', 0], # 1555 17 | 18 | # CPAN::Meta::YAML failed to classify line ' --- #YAML:1.0' 19 | ['XPANEL/XPanel-0.0.7.tar.gz', 0], # 2207 20 | 21 | # CPAN::Meta::YAML found bad indenting 22 | ['NUFFIN/Devel-STDERR-Indent-0.01.tar.gz', 0], # 2594 23 | 24 | # CPAN::Meta::YAML found illegal characters 25 | ['SOCK/WWW-Search-Feedster-0.02.tar.gz', 0], # 3220 26 | ); 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/kwalitee/no_abstract_stub_in_pod.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_abstract_stub_in_pod', 6 | # h2xs 7 | ['ERICW/Roll-1.0.tar.gz', 0], # 2188 8 | ['IEFREMOV/Statistics-CountAverage-0.02.tar.gz', 0], # 2839 9 | ['HSLEE/Search-Equidistance-0.01.tar.gz', 0], # 3142 10 | ['OPITZ/URL-Grab-1.4.tar.gz', 0], # 3210 11 | 12 | # Module::Starter etc 13 | ['IKEBE/WebService-Livedoor-Auth-0.01.tar.gz', 0], 14 | 15 | # Minilla 16 | ['NNUTTER/Git-Repository-Plugin-Gerrit-0.03.tar.gz', 0], 17 | ); 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/kwalitee/no_broken_auto_install.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_broken_auto_install', 6 | ['GUGOD/WWW-Shorten-0rz-0.07.tar.gz', 0], # 14671 7 | ['CLKAO/IPC-Run-SafeHandles-0.04.tar.gz', 0], # 19431 8 | ['TBR/WKHTMLTOPDF-0.02.tar.gz', 0], # 19819 9 | ['MONS/Test-More-UTF8-0.04.tar.gz', 0], # 19952 10 | ['SKAUFMAN/Template-Plugin-Devel-StackTrace-0.02.tar.gz', 0], # 19960 11 | ['LUKEC/Test-Mock-LWP-0.06.tar.gz', 0], # 20054 12 | ['GAOU/Ubigraph-0.05.tar.gz', 0], # 20092 13 | ['YVESAGO/Jifty-Plugin-Userpic-0.9.tar.gz', 0], # 20161 14 | ['YVESAGO/Jifty-Plugin-SiteNews-0.9.tar.gz', 0], # 20249 15 | ['BOLAV/DateTime-Format-Duration-DurationString-0.03.tar.gz', 0], # 20527 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/no_broken_module_install.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_broken_module_install', 6 | ['GUGOD/Kwiki-Session-0.01.tar.gz', 0], # 7880 7 | ['GUGOD/Kwiki-Widgets-Links-0.01.tar.gz', 0], # 8214 8 | ['CLSUNG/Lingua-ZH-Segment-0.02.tar.gz', 0], # 8236 9 | ['RHUNDT/Catalyst-Model-Oryx-0.01.tar.gz', 0], # 8255 10 | ['XERN/Template-Plugin-IO-All-0.01.tar.gz', 0], # 8462 11 | ['IJLIAO/WWW-Scraper-ISBN-TWSrbook_Driver-0.01.tar.gz', 0], # 9139 12 | ['IJLIAO/WWW-Scraper-ISBN-TWYlib_Driver-0.01.tar.gz', 0], # 9199 13 | ['IJLIAO/WWW-Scraper-ISBN-TWTenlong_Driver-0.01.tar.gz', 0], # 9210 14 | ['IJLIAO/WWW-Scraper-ISBN-TWPchome_Driver-0.01.tar.gz', 0], # 9308 15 | ['IJLIAO/WWW-Scraper-ISBN-TWSoidea_Driver-0.01.tar.gz', 0], # 9348 16 | 17 | # M::I 1.04 18 | ['KARMAN/Dezi-UI-0.001000.tar.gz', 0], 19 | ); 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/kwalitee/no_dot_dirs.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_dot_dirs', 6 | # .svn 7 | ['WAZZUTEKE/Net-Twitter-RandomUpdate-1.2.tar.gz', 0], # 4710 8 | ['HOVENKO/HTML-ScriptLoader-1.00.tar.gz', 0], # 5160 9 | 10 | # .git 11 | ['EXODIST/local-lib-deps-0.02.tar.gz', 0], # 11662 12 | ['AQUILINA/Acme-Warn-LOLCAT-0.01.tar.gz', 0], # 12638 13 | 14 | # .hg 15 | ['LICHTKIND/Tie-Wx-Widget-0.5.tar.gz', 0], # 15259 16 | 17 | # .bzr 18 | ['GOOZBACH/asterisk-store/Asterisk-Store-Queue-Member-0.1.tar.gz', 0], # 19811 19 | 20 | # others 21 | ['BRENDAN/Test-Mimic-0.009007.tar.gz', 0], # 11809 22 | ['MUENALAN/Workflow-Aline-0.03.tar.gz', 0], # 24529 23 | ['NACHBAUR/Test-Story-0.07.tar.gz', 0], # 41977 24 | ['RKIES/ec-1.6.tar.gz', 0], # 77537 25 | ['VVELOX/Toader-1.1.0.tar.gz', 0], # 99633 26 | ['BBYRD/DBIx-Class-0.08204_01.tar.gz', 0], # 697468 27 | ); 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /t/kwalitee/no_dot_underscore_files.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_dot_underscore_files', 6 | ['LEPT/String-Iota-0.85.tar.gz', 0], # 2441 7 | ['DAMOG/Data-Format-HTML-0.5.1.tar.gz', 0], # 2737 8 | ['BRENTDAX/Template-Plugin-Lingua-Conjunction-0.02.tar.gz', 0], # 2875 9 | ['SOCK/WWW-Search-UrbanDictionary-0.4.tar.gz', 0], # 3176 10 | ['CLADI/SmarTalk_v10.tar.gz', 0], # 3289 11 | ['KAOSAGNT/CGI-Session-Serialize-php-1.1.tar.gz', 0], # 3336 12 | ['EBRAGIN/Cache-Memcached-Tags-0.02.tar.gz', 0], # 3399 13 | ['AHICOX/XML-Parser-YahooRESTGeocode-0.2.tar.gz', 0], # 3503 14 | ['RECSKY/Bot-BasicBot-Pluggable-Module-Pastebin-0.01.tar.gz', 0], # 3663 15 | ['SOCK/WWW-Yahoo-KeywordExtractor-0.5.2.tar.gz', 0], # 3806 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/no_files_to_be_skipped.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_files_to_be_skipped', 6 | ['ABELTJE/Test-DBIC-SQLite-0.01.tar.gz', 0], # 4821 7 | ['GTERMARS/Data-FormValidator-EmailValid-0.05.tar.gz', 0], # 5597 8 | ['LARION/Lingua-Translator-Microsoft-1.1.1.tar.gz', 0], # 6794 9 | ['MANWAR/Devel-Timer-0.11.tar.gz', 0], # 8150 10 | ['MIKEH/Bundle-Interchange-1.08.tar.gz', 0], # 2564 11 | ['MOOCOW/Alien-Moot-0.003.tar.gz', 0], # 3384 12 | ['RSN/ORDB-DebianModules-Generator-0.02.tar.gz', 0], # 3854 13 | ['SREZIC/Geo-Coder-Googlev3-0.17.tar.gz', 0], # 8640 14 | ['TOBYINK/Alien-LibXML-0.004.tar.gz', 0], # 9119 15 | ['TOMK/lib-archive-0.3.tar.gz', 0], # 8196 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/no_generated_files.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_generated_files', 6 | ['SANTEX/Finance-Quant-Quotes-0.01.tar.gz', 0], # 3159 7 | ['SANTEX/Finance-Quant-TA-0.01.tar.gz', 0], # 3269 8 | ['TAKERU/Catalyst-Model-Estraier-v0.0.6.tar.gz', 0], # 6175 9 | ['BDFOY/Unicode-Support-0.001.tar.gz', 0], # 6633 10 | ['RPETTETT/Module-PortablePath-0.17.tar.gz', 0], # 6951 11 | ['ROBN/Class-Constant-0.06.tar.gz', 0], # 7557 12 | ['TUSHAR/Log-SelfHistory_0.1.tar.gz', 0], # 8412 13 | ['CCCP/Plugins-Factory-0.01.tar.gz', 0], # 8876 14 | ['JAMHED/Dancer-Plugin-Scoped-0.02fix.tar.gz', 0], # 8885 15 | ['JKRAMER/SQL-Beautify-0.04.tar.gz', 0], # 8972 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/no_invalid_versions.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_invalid_versions', 6 | # string in version 7 | ['TIMA/Bundle-Melody-Test-0.9.6a.tar.gz', 0], # 2042 8 | ['JHARDING/Text-Typoifier-0.04a.tar.gz', 0], # 2334 9 | ['SPECTRUM/Math-BigSimple-1.1a.tar.gz', 0], # 3269 10 | ['TSKIRVIN/HTML-FormRemove-0.3a.tar.gz', 0], # 3625 11 | ['TBONE/Chess-Elo-1.0a.tar.gz', 0], # 3979 12 | 13 | # exponential 14 | ['ALTREUS/Catalyst-Authentication-Store-MongoDB-1e-4.tar.gz', 0], # 3788 (1e-4) 15 | 16 | # version from other module 17 | ['ROBAU/Data-ACL-0.02.tar.gz', 0], # 2844 18 | 19 | # scalar ref 20 | ['MAUKE/Defaults-Mauke-0.09.tar.gz', 1], # 3136 21 | 22 | # my empty string 23 | ['INGY/YAML-MLDBM-0.10.tar.gz', 0], # 3420 24 | 25 | # others 26 | ['ARTO/CGI-Application-Plugin-Config-IniFiles-0.03.tar.gz', 0], # 3004 27 | ); 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /t/kwalitee/no_local_dirs.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_local_dirs', 6 | # local, but no .pm files 7 | ['ERMEYERS/Bundle-Modules-2006.0606.tar.gz', 1], # 29296 8 | 9 | # perl5 (with non-portable files) 10 | # ['PERLER/MooseX-Attribute-Deflator-2.1.3.tar.gz', 0], # 109876 11 | 12 | # fatlib 13 | ['GETTY/Installer-0.005.tar.gz', 0], # 295629 14 | ); 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/kwalitee/no_maniskip_error.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_maniskip_error', 6 | # Unrecognized escape \i passed through in regex 7 | ['GARU/Net-TinyERP-0.05.tar.gz', 0], # 9814 8 | ['GARU/Data-Printer-0.40.tar.gz', 0], # 58847 9 | 10 | # Unrecognized escape \M passed through in regex 11 | ['FRANCKC/AnyEvent-Riak-0.01.tar.gz', 0], # 3878 12 | 13 | # Quantifier follows nothing in regex 14 | ['DVWRIGHT/WWW-TasteKid-0.1.4.tar.gz', 0], # 38770 15 | ['JANE/Data-TDMA-0.2.tar.gz', 0], # 17907 16 | 17 | # ^* matches null string many times in regex 18 | ['MANWAR/Term-ProgressBar-2.22.tar.gz', 0], # 19506 19 | ['SCHWIGON/class-methodmaker/Class-MethodMaker-2.24.tar.gz', 0], # 77242 20 | ); 21 | 22 | done_testing; 23 | -------------------------------------------------------------------------------- /t/kwalitee/no_missing_files_in_provides.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_missing_files_in_provides', 6 | ['MIYAGAWA/CPAN-Test-Dummy-Perl5-DifferentProvides-0.01.tar.gz', 0], # 9453 7 | 8 | ['LNATION/Moonshine-Bootstrap-0.01.tar.gz', 0], # 4621 9 | ['MANWAR/WWW-Google-APIDiscovery-0.23.tar.gz', 0], # 9876 10 | 11 | # trailing comma 12 | ['DFARRELL/stasis-0.07.tar.gz', 0], # 4705 13 | 14 | # inconsistent case 15 | ['PINE/SemVer-V2-Strict-0.13.tar.gz', 0], # 8597 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/no_mymeta_files.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_mymeta_files', 6 | ['SZABGAB/File-Open-OOP-0.01.tar.gz', 0], # 2431 7 | ['JHTHORSEN/The-synthesizer-0.01.tar.gz', 0], # 2514 8 | ['TENGU/Catalyst-Authentication-Credential-MultiFactor-1.0.tar.gz', 0], # 2577 9 | ['BDFOY/Psychic-Ninja-0.10_01.tar.gz', 0], # 2689 10 | ['JHTHORSEN/The-synthesizer-0.02.tar.gz', 0], # 2708 11 | ['ZZZ/Here-Template-0.1.tar.gz', 0], # 2793 12 | ['KIMOTO/Mojolicious-Plugin-AutoRoute-0.04.tar.gz', 0], # 2811 13 | ['BDFOY/Net-SSH-Perl-WithSocks-0.02.tar.gz', 0], # 2894 14 | ['ZZZ/Here-Template-0.2.tar.gz', 0], # 2902 15 | ['KIMOTO/Mojolicious-Plugin-AutoRoute-0.02.tar.gz', 0], # 2902 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/no_pax_headers.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_pax_headers', 6 | ['LAWALSH/mem-0.3.0.tar.gz', 0], # 1596 7 | ['ZAR/Mojolicious-Plugin-Captcha-0.01.tar.gz', 0], # 3591 8 | ['ATRICKETT/Config-Trivial-0.50.tar.gz', 0], # 10028 9 | ['LAWALSH/mem-0.3.1.tar.gz', 0], # 11201 10 | ['LAWALSH/P-1.0.19.tar.gz', 0], # 17520 11 | ['LAWALSH/P-1.0.20.tar.gz', 0], # 17760 12 | ['MARKOV/XML-Compile-SOAP12-2.03.tar.gz', 0], # 19182 13 | ['MARKOV/Net-OAuth2-0.53.tar.gz', 0], # 20529 14 | ['MARKOV/XML-LibXML-Simple-0.93.tar.gz', 0], # 22821 15 | ['TBENK/App-nrun-v1.0.0_1.tar.gz', 0], # 27074 16 | ['MSCHILLI/libwww-perl-6.06.tar.gz', 0], 17 | ); 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/kwalitee/no_pod_errors.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_pod_errors', 6 | ['ALEXP/Catalyst-Model-Proxy-0.04.tar.gz', 0], # 3671 7 | ['LEEYM/Geo-Coder-Cache-0.06.tar.gz', 0], # 3907 8 | ['SULLR/Net-PcapWriter-0.71.tar.gz', 0], # 5337 9 | ['LTP/Game-Life-0.05.tar.gz', 0], # 6535 10 | ['JHALLOCK/StormX-Query-DeleteWhere-0.10.tar.gz', 0], # 6869 11 | ['DSYRTM/File-BetweenTree-1.02.tar.gz', 0], # 7590 12 | ['JHALLOCK/GappX-Dialogs-0.005.tar.gz', 0], # 7766 13 | ['FIBO/Task-Viral-20130508.tar.gz', 0], # 8128 14 | ['JSOBRIER/WebService-Browshot-1.11.0.tar.gz', 0], # 9434 15 | ['SALVA/Class-StateMachine-0.23.tar.gz', 0], # 9859 16 | 17 | # broken pod for testing should not be counted 18 | ['RJBS/Pod-Elemental-0.103000.tar.gz', 1], 19 | 20 | # has binary data 21 | ['DCANTRELL/Number-Phone-3.3000.tar.gz', 1], 22 | 23 | # no actual data 24 | ['APLA/DBI-Easy-0.18.tar.gz', 1], 25 | ); 26 | 27 | done_testing; 28 | -------------------------------------------------------------------------------- /t/kwalitee/no_stdin_for_prompting.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_stdin_for_prompting', 6 | ['GMCCAR/Jabber-SimpleSend-0.03.tar.gz', 0], # 3455 7 | ['SPEEVES/Apache-AuthenNIS-0.13.tar.gz', 0], # 4517 8 | ['SPEEVES/Apache2-AuthenSmb-0.01.tar.gz', 0], # 5219 9 | ['KROW/DBIx-Password-1.9.tar.gz', 0], # 5478 10 | ['GEOTIGER/Data-Fax-0.02.tar.gz', 0], # 5944 11 | ['GEOTIGER/CGI-Getopt-0.13.tar.gz', 0], # 6014 12 | ['SPEEVES/Apache2-AuthNetLDAP-0.01.tar.gz', 0], # 6855 13 | ['SPEEVES/Apache-AuthNetLDAP-0.29.tar.gz', 0], # 6952 14 | ['AMALTSEV/XAO-MySQL-1.02.tar.gz', 0], # 7242 15 | ['BHODGES/Mail-IMAPFolderSearch-0.03.tar.gz', 0], # 7326 16 | ); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/kwalitee/no_symlinks.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | plan skip_all => "This test doesn't work well under Windows" if $^O eq 'MSWin32'; 5 | 6 | test_kwalitee( 7 | 'no_symlinks', 8 | ['CMORRIS/Parse-Extract-Net-MAC48-0.01.tar.gz', 0], # 3094 9 | ['BRUMLEVE/autobless-1.0.1.tar.gz', 0], # 3318 10 | ['BRUMLEVE/wildproto-1.0.1.tar.gz', 0], # 3617 11 | ['BRUMLEVE/vm-1.0.1.tar.gz', 0], # 4236 12 | ['CRUSOE/Template-Plugin-Filter-ANSIColor-0.0.3.tar.gz', 0], # 4963 13 | ['GSLONDON/Devel-AutoProfiler-1.200.tar.gz', 0], # 6139 14 | ['PHAM/Business-Stripe-0.04.tar.gz', 0], # 6412 15 | ['GAVINC/Config-Directory-0.05.tar.gz', 0], # 8774 16 | ['NETVARUN/Net-Semantics3-0.10.tar.gz', 0], # 8930 17 | ['GAVINC/File-DirCompare-0.7.tar.gz', 0], # 9018 18 | ); 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/kwalitee/no_unauthorized_packages.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'no_unauthorized_packages', 6 | ['ECARROLL/URI-http-Dump-0.03.tar.gz', 0], # URI::http 7 | ['PMAKHOLM/Devel-RemoteTrace-0.3.tar.gz', 0], # DB 8 | ['SIMKIN/Apache-LoggedAuthDBI-0.12.tar.gz', 0], # DBI etc 9 | ['WILLERT/Catalyst-Model-EmailStore-0.03.tar.gz', 0], # Email::Store::DBI 10 | ['KNM/Ambrosia-0.010.tar.gz', 0], # deferred 11 | ['HACHI/MogileFS-Plugin-MetaData-0.01.tar.gz', 0], # MogileFS::Store 12 | ); 13 | 14 | done_testing; 15 | -------------------------------------------------------------------------------- /t/kwalitee/portable_filenames.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | my @tests = ( 5 | ['DGL/Acme-mA-1337.1.tar.gz', 0], # 3411 6 | ['SCHWIGON/acme-unicode/Acme-Uenicoede-0.0501.tar.gz', 0], # 3651 7 | ['DGL/Acme-3mxA-1337.37.tar.gz', 0], # 4093 8 | ['KOORCHIK/Mojolicious-Plugin-RenderFile-0.06.tar.gz', 0], # 4114 9 | ); 10 | 11 | # The followings are only valid for non-Win32 env 12 | # (because invalid files will not be extracted on Win32). 13 | push @tests, ( 14 | ['PERFSONAR/perfSONAR_PS-Status-Common-0.09.tar.gz', 0], # 5439 15 | ['PERFSONAR/perfSONAR_PS-Client-Echo-0.09.tar.gz', 0], # 6654 16 | ['FRASE/Test-Builder-Clutch-0.05.tar.gz', 0], # 6764 17 | ['PERFSONAR/perfSONAR_PS-DB-File-0.09.tar.gz', 0], # 7704 18 | ['PERFSONAR/perfSONAR_PS-Client-LS-Remote-0.09.tar.gz', 0], # 8232 19 | ) unless $^O eq 'MSWin32'; 20 | 21 | test_kwalitee('portable_filenames', @tests); 22 | 23 | done_testing; 24 | -------------------------------------------------------------------------------- /t/kwalitee/prereq_matches_use.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | #plan skip_all => 'TODO'; 5 | 6 | test_kwalitee( 7 | 'prereq_matches_use', 8 | ['KAWABATA/Text-HikiDoc-1.023.tar.gz', 0], # Text::Highlight etc 9 | ['PVIGIER/XML-Compile-SOAP-Daemon-Dancer2-0.07.tar.gz', 0], # Log::Report etc 10 | ['MICKEY/PONAPI-Server-0.003002.tar.gz', 0], # URI::Escape 11 | ['TEAM/Net-Async-Pusher-0.004.tar.gz', 0], # Syntax::Keyword::Try 12 | ['DORNER/UI-Various-0.39.tar.gz', 1], # Curses::UI and Tk are declared as recommends 13 | ); 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/kwalitee/proper_libs.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'proper_libs', 6 | # pm files not in the lib/base dir 7 | ['NIELSD/Speech-Google-0.5.tar.gz', 0], # 2907 (Google/TTS.pm) 8 | 9 | # didn't extract nicely (dot underscore files) 10 | ['AQUILINA/WWW-LaQuinta-Returns-0.02.tar.gz', 0], # 4055 11 | 12 | # multiple pm files in the basedir 13 | ['DGRAHAM/simpleXMLParse/simplexmlparse_v1.4.tar.gz', 0], # 4336 14 | 15 | # no modules 16 | ['LAWSONK/Gtk2-Ex-MPlayerEmbed-0.03.tar.gz', 1], # 465 17 | ['WOLDRICH/App-epic-0.014.tar.gz', 1], # 812 18 | ['TAG/AnyEvent-Peer39-0.32.tar.gz', 1], # 824 19 | ['CASIANO/Git-Export-0.04.tar.gz', 1], # 2593 20 | ['MILOVIDOV/APP-Yatranslate-0.02.tar.gz', 1], # 3773 21 | ['DBR/pdoc-0.900.tar.gz', 1], # 3876 22 | ['MUIR/modules/rinetd.pl-1.2.tar.gz', 1], # 4319 23 | ); 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/kwalitee/test_prereq_matches_use.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'test_prereq_matches_use', 6 | ['STEVAN/decorators-0.01.tar.gz', 0], # Test::Fatal 7 | ['MANOWAR/WWW-KeenIO-0.02.tar.gz', 0], # Test::Mouse 8 | ['JLMARTIN/CloudDeploy-1.05.tar.gz', 0], # File::Slurp etc 9 | ['YANICK/Bread-Board-0.36.tar.gz', 0], # Moo 10 | ); 11 | 12 | done_testing; 13 | -------------------------------------------------------------------------------- /t/kwalitee/use_strict.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'use_strict', 6 | ['TOBYINK/Platform-Windows-0.002.tar.gz', 0], # 2206 7 | ['TOBYINK/Platform-Unix-0.002.tar.gz', 0], # 2264 8 | ['SCILLEY/POE/Component/IRC/Plugin/IRCDHelp-0.02.tar.gz', 0], # 3243 9 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 10 | ['TXH/Template-Plugin-Filter-MinifyHTML-0.02.tar.gz', 0], # 3484 11 | ['ANANSI/Anansi-ObjectManager-0.06.tar.gz', 0], # 5246 12 | ['MARCEL/Web-Library-0.01.tar.gz', 0], # 7345 13 | ['PJB/Speech-Speakup-1.04.tar.gz', 0], # 7410 14 | ['TEMPIRE/Eponymous-Hash-0.01.tar.gz', 0], # 8503 15 | ['SULLR/Net-SSLGlue-1.03.tar.gz', 0], # 8720 16 | 17 | # use 5.012 and higher 18 | ['ZDM/Pharaoh-BootStrap-3.00.tar.gz', 1], # use 5.12.0 19 | ['MALLEN/Acme-Github-Test-0.03.tar.gz', 1], # use 5.014 20 | 21 | # no .pm files 22 | ['RCLAMP/cvn-0.02.tar.gz', 1], 23 | 24 | # .pod without package declaration 25 | ['ETHER/Moose-2.1209.tar.gz', 1], 26 | 27 | # v6 module inside a Perl 5 distribution 28 | ['NINE/Inline-Perl6-0.07.tar.gz', 1], 29 | ); 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/kwalitee/use_warnings.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | 4 | test_kwalitee( 5 | 'use_warnings', 6 | ['TOBYINK/Platform-Windows-0.002.tar.gz', 0], # 2206 7 | ['TOBYINK/Platform-Unix-0.002.tar.gz', 0], # 2264 8 | ['BOOK/Acme-MetaSyntactic-errno-1.003.tar.gz', 0], # 2889 9 | ['ANANSI/Anansi-Library-0.02.tar.gz', 0], # 3365 10 | ['TXH/Template-Plugin-Filter-MinifyHTML-0.02.tar.gz', 0], # 3484 11 | ['LTP/Game-Life-0.05.tar.gz', 0], # 6535 12 | ['PJB/Speech-Speakup-1.04.tar.gz', 0], # 7410 13 | ['JBAZIK/Archive-Ar-1.15.tar.gz', 0], # 7983 14 | ['SULLR/Net-SSLGlue-1.03.tar.gz', 0], # 8720 15 | ['SHARYANTO/Term-ProgressBar-Color-0.00.tar.gz', 0], # 9746 16 | 17 | # no .pm files 18 | ['RCLAMP/cvn-0.02.tar.gz', 1], 19 | 20 | # .pod without package declaration 21 | ['ETHER/Moose-2.1209.tar.gz', 1], 22 | 23 | # v6 module inside a Perl 5 distribution 24 | ['NINE/Inline-Perl6-0.07.tar.gz', 1], 25 | 26 | # v5.36.0 27 | ['GUGOD/PerlX-ScopeFunction-0.03.tar.gz', 1], 28 | ); 29 | 30 | done_testing; 31 | -------------------------------------------------------------------------------- /t/kwalitee/valid_signature.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use Test::RequiresInternet ('pool.sks-keyservers.net' => 80); 4 | 5 | test_kwalitee( 6 | 'valid_signature', 7 | ['HUGUEI/Net-Stomp-Receipt-0.36.tar.gz', 0], # 3686 8 | ['JJORE/perl-lint-mode-0.02.tar.gz', 0], # 4391 9 | ['SIMON/Lingua-EN-Keywords-2.0.tar.gz', 0], # 4639 10 | ['RPAGITSCH/Win32-Process-User-0.02.tar.gz', 0], # 5063 11 | ['HUGUEI/Finance-Currency-Convert-BChile-0.04.tar.gz', 0], # 5108 12 | [ 13 | 'RKOBES/File-HomeDir-Win32-0.04.tar.gz', 14 | 0, 15 | sub { 16 | my $stash = shift; 17 | ok $stash->{error}{valid_signature}; 18 | } 19 | ], 20 | [ 21 | 'RIVY/Win32-CommandLine-0.938.tar.gz', 22 | 1, 23 | sub { 24 | my $stash = shift; 25 | like $stash->{error}{valid_signature} => qr/Old SIGNATURE detected/; 26 | } 27 | ], 28 | 29 | # No public key: May take a long time sometimes 30 | ['CRAIHA/Geo-Coordinates-Parser-0.01.tar.gz', 0], # 4009 31 | ['DMAKI/Class-Validating-0.02.tar.gz', 0], # 4624 32 | ['PELAGIC/List-Rotation-Cycle-1.009.tar.gz', 0], # 4648 33 | ['JMEHNLE/net-address-ipv4-local/Net-Address-IPv4-Local-0.12.tar.gz', 0], # 4848 34 | ); 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /t/root.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use FindBin; 3 | use Test::More; 4 | use WWW::CPANTS; 5 | use Path::Tiny; 6 | 7 | my $root = path("$FindBin::Bin/..")->realpath; 8 | 9 | my $api_app_root = WWW::CPANTS->instance->app_root; 10 | is $api_app_root => $root, "api app root"; 11 | 12 | done_testing; 13 | -------------------------------------------------------------------------------- /t/web/author.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-4.02.tar.gz', 10 | 'ISHIGAKI/Path-Extended-0.19.tar.gz', 11 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', 12 | ); 13 | my $testpan = setup_testpan(@files); 14 | $testpan->cpan->update_indices; 15 | 16 | load_task('UpdateCPANIndices')->run; 17 | load_task('Traverse')->run(qw/ISHIGAKI/); 18 | load_task('Analyze')->run(@files); 19 | load_task('PostProcess')->run; 20 | }; 21 | 22 | subtest 'get' => sub { 23 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 24 | $t->get_ok('/author/ishigaki')->status_is(200); 25 | }; 26 | 27 | subtest 'get json' => sub { 28 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 29 | $t->get_ok('/author/ishigaki.json')->status_is(200); 30 | }; 31 | 32 | subtest 'get png' => sub { 33 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 34 | $t->get_ok('/author/ishigaki.png')->status_is(200); 35 | }; 36 | 37 | subtest 'get svg' => sub { 38 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 39 | $t->get_ok('/author/ishigaki.svg')->status_is(200); 40 | }; 41 | 42 | subtest 'feed' => sub { 43 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 44 | $t->get_ok('/author/ishigaki/feed')->status_is(200); 45 | }; 46 | 47 | done_testing; 48 | -------------------------------------------------------------------------------- /t/web/author/ignore_backpan_latest.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-4.02.tar.gz', 10 | 'ISHIGAKI/JSON-4.00.tar.gz', 11 | 'ISHIGAKI/Parse-PMFile-0.42.tar.gz', 12 | ); 13 | my $testpan = setup_testpan(@files); 14 | $testpan->cpan->update_indices; 15 | 16 | $testpan->backpan->add_files( 17 | 'ISHIGAKI/Pod-Perldocs-0.17.tar.gz', ## backpan latest with kwalitee issues 18 | ); 19 | 20 | load_task('UpdateCPANIndices')->run; 21 | load_task('Traverse')->run(qw/ISHIGAKI/); 22 | load_task('Analyze')->run(@files); 23 | load_task('PostProcess')->run; 24 | }; 25 | 26 | subtest 'get json' => sub { 27 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 28 | $t->get_ok('/author/ishigaki.json')->status_is(200); 29 | my $json = $t->tx->res->json; 30 | is $json->{author}{average_core_kwalitee} => 100, "Pod::Perldocs is ignored"; 31 | }; 32 | 33 | done_testing; 34 | -------------------------------------------------------------------------------- /t/web/dist/pod_escapes.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | fixture { 8 | my @files = ( 9 | 'RRA/podlators-4.14.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices')->run; 15 | load_task('Traverse')->run(qw/RRA/); 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess')->run; 18 | }; 19 | 20 | my @paths = qw( 21 | /dist/podlators 22 | /release/RRA/podlators-4.14 23 | ); 24 | 25 | subtest 'no E<>' => sub { 26 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 27 | 28 | for my $path (@paths) { 29 | $t->get_ok($path)->status_is(200); 30 | $t->text_is('h2' => 'podlators 4.14'); 31 | my $tbody = $t->tx->res->dom->at('#modules tbody'); 32 | for my $tr ($tbody->find('tr')->each) { 33 | for my $td ($tr->find('td')->each) { 34 | unlike $td->text => qr/E<[^>]+>/, $td->text; 35 | } 36 | } 37 | } 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/web/home.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-4.02.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess')->run; 18 | }; 19 | 20 | subtest 'get' => sub { 21 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 22 | $t->get_ok('/')->status_is(200); 23 | }; 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/web/kwalitee.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-4.02.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess')->run; 18 | }; 19 | 20 | subtest 'get' => sub { 21 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 22 | $t->get_ok('/kwalitee')->status_is(200); 23 | }; 24 | 25 | subtest 'get indicator' => sub { 26 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 27 | $t->get_ok('/kwalitee/use_strict')->status_is(200); 28 | }; 29 | 30 | done_testing; 31 | -------------------------------------------------------------------------------- /t/web/ranking.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-4.02.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess')->run; 18 | }; 19 | 20 | subtest 'get' => sub { 21 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 22 | $t->get_ok('/ranking')->status_is(200); 23 | }; 24 | 25 | subtest 'get five_or_more' => sub { 26 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 27 | $t->get_ok('/ranking/five_or_more')->status_is(200); 28 | }; 29 | 30 | subtest 'get less_than_five' => sub { 31 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 32 | $t->get_ok('/ranking/less_than_five')->status_is(200); 33 | }; 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/web/recent.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use WWW::CPANTS::Util::Datetime; 5 | use Test::More; 6 | use Test::Mojo; 7 | use Test::MockTime::HiRes; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/JSON-4.02.tar.gz', 12 | ); 13 | my $testpan = setup_testpan(@files); 14 | load_task('Traverse')->run(qw/ISHIGAKI/); 15 | load_task('Analyze')->run(@files); 16 | }; 17 | 18 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 19 | 20 | subtest 'get' => sub { 21 | my $epoch = epoch_from_date('2019-12-31'); 22 | mock_time { 23 | $t->get_ok('/recent')->status_is(200); 24 | } 25 | $epoch; 26 | }; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/web/search.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | fixture { 8 | my @files = ( 9 | 'ISHIGAKI/JSON-4.02.tar.gz', 10 | ); 11 | my $testpan = setup_testpan(@files); 12 | $testpan->cpan->update_indices; 13 | 14 | load_task('UpdateCPANIndices::Whois')->run; 15 | load_task('Traverse')->run(qw/ISHIGAKI/); 16 | load_task('Analyze')->run(@files); 17 | load_task('PostProcess')->run; 18 | }; 19 | 20 | subtest 'get' => sub { 21 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 22 | $t->get_ok('/search')->status_is(200); 23 | }; 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/web/stats.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::Test::Fixture; 4 | use Test::More; 5 | use Test::Mojo; 6 | 7 | plan skip_all => "not implemented yet"; 8 | 9 | fixture { 10 | my @files = ( 11 | 'ISHIGAKI/JSON-4.02.tar.gz', 12 | ); 13 | my $testpan = setup_testpan(@files); 14 | $testpan->cpan->update_indices; 15 | 16 | load_task('UpdateCPANIndices::Whois')->run; 17 | load_task('Traverse')->run(qw/ISHIGAKI/); 18 | load_task('Analyze')->run(@files); 19 | load_task('PostProcess')->run; 20 | }; 21 | 22 | subtest 'get' => sub { 23 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 24 | $t->get_ok('/stats')->status_is(200); 25 | }; 26 | 27 | subtest 'get module_install' => sub { 28 | my $t = Test::Mojo->new('WWW::CPANTS::Web'); 29 | $t->get_ok('/stats/module_install')->status_is(200); 30 | }; 31 | 32 | done_testing; 33 | -------------------------------------------------------------------------------- /templates/_notice.html.ep: -------------------------------------------------------------------------------- 1 | % if (stash('cpants.notice_maintenance')) { 2 |
3 |
4 |

Under maintenance.

5 |

Most probably CPANTS databases are being regenerated from scratch due to major changes in Kwalitee metrics or updates of relevant modules/perl. Usually this maintenance takes about a day or two, and some of the information may be old or missing tentatively. Sorry for the inconvenience.

6 |
7 |
8 | % } elsif (stash('cpants.notice_analyzing')) { 9 |
10 |
11 |

Analyzing.

12 |

Scheduled analysis is going on. Some of the information may be inconsistent. Sorry for the inconvenience.

13 |
14 |
15 | % } 16 | -------------------------------------------------------------------------------- /templates/dist/_tab.html.ep: -------------------------------------------------------------------------------- 1 | % my @tabs = (['' => 'Overview'], [prereq => 'Prerequisites'], [used_by => 'Users'], [errors => 'Errors'], [releases => 'Releases'], [files => 'Files'], [metadata => 'Metadata']); 2 | 3 | % my $stash = stash('cpants'); 4 | % my $data = $stash->{data}; 5 | % my $dist = $stash->{distribution} // $data->{distribution} // {}; 6 | % my $tab = stash('tab') // ''; 7 | % my $controller = stash('controller') // 'dist'; 8 | 9 | 19 | 20 | % # latest? avaliability? score? badge? feeds? 21 | 22 | 27 | -------------------------------------------------------------------------------- /templates/dist/errors.html.ep: -------------------------------------------------------------------------------- 1 | % my $stash = stash('cpants'); 2 | % my $data = $stash->{data}; 3 | % my $distribution = $stash->{distribution}; 4 | % my $errors = $data->{errors} // []; 5 | 6 | % layout 'layout'; 7 | 8 | %= include 'dist/_sidebar'; 9 | 10 |
11 | 12 | %= include 'dist/_tab'; 13 | 14 |
15 |
16 |

Errors

17 | 18 | % if (!@$errors) { 19 |

No error details.

20 | % } else { 21 |
22 | % for my $error (@$errors) { 23 |
<%= $error->{category} %>
24 |
25 | % my $reftype = ref $error->{error}; 26 | % if (!$reftype) { 27 | % if ($error->{error} =~ /\n/s) { 28 |
<%= $error->{error} %>
29 | % } else { 30 | <%= $error->{error} %> 31 | % } 32 | % } elsif ($reftype eq 'ARRAY') { 33 |
    34 | % for (@{$error->{error}}) { 35 | % if (/\n/s) { 36 |
  • <%= $_ %>
  • 37 | % } else { 38 |
  • <%= $_ %>
  • 39 | % } 40 | % } 41 |
42 | % } elsif ($reftype eq 'HASH') { 43 |
    44 | % for (sort keys %{$error->{error}}) { 45 | % if ($error->{error}{$_} =~ /\n/s) { 46 |
  • <%= $_ %>: <%= $error->{error}{$_} %>
  • 47 | % } else { 48 |
  • <%= $_ %>: <%= $error->{error}{$_} %>
  • 49 | % } 50 | % } 51 |
52 | % } 53 |
54 | % } 55 |
56 | % } 57 |
58 |
59 | 60 |
61 | -------------------------------------------------------------------------------- /templates/dist/files.html.ep: -------------------------------------------------------------------------------- 1 | % my $stash = stash('cpants'); 2 | % my $files = $stash->{data}{files}; 3 | % my $distribution = $stash->{distribution}; 4 | % layout 'layout'; 5 | 6 | %= include 'dist/_sidebar'; 7 | 8 |
9 | 10 | %= include 'dist/_tab'; 11 | 12 |
13 |
14 |

Files

15 |
    16 | % for my $file (sort keys %$files) { 17 |
  • 18 | <%= $file %> 19 | % if ($files->{$file}{unreadable}) { 20 | (unreadable) 21 | % } 22 | % if ($files->{$file}{symlink}) { 23 | (symlink) 24 | % } 25 | % if ($files->{$file}{maniskip}) { 26 | (manifest skip) 27 | % } 28 |
  • 29 | % } 30 |
31 |
32 |
33 | 34 |
35 | -------------------------------------------------------------------------------- /templates/dist/metadata.html.ep: -------------------------------------------------------------------------------- 1 | % my $stash = stash('cpants'); 2 | % my $data = $stash->{data}; 3 | % my $distribution = $stash->{distribution}; 4 | % layout 'layout', 'cpants.requires_jsonview' => 1; 5 | 6 | %= include 'dist/_sidebar'; 7 | 8 |
9 | 10 | %= include 'dist/_tab'; 11 | 12 |
13 |
14 |

Metadata

15 |

16 |   
17 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /templates/exception.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'layout'; 2 |

Server Error

3 | -------------------------------------------------------------------------------- /templates/not_found.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'layout'; 2 |

Not found

3 | -------------------------------------------------------------------------------- /templates/ranking/_sidebar.html.ep: -------------------------------------------------------------------------------- 1 | % my $stash = stash('cpants'); 2 | % my $data = $stash->{data}; 3 | 4 |
5 | 6 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /templates/stats.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'layout'; 2 | 3 |
4 | 5 |
6 |
7 |

CPANTS Statistics

8 |
9 |
10 | 11 |
12 | 13 | %= include 'stats/_sidebar'; 14 | -------------------------------------------------------------------------------- /templates/stats/_sidebar.html.ep: -------------------------------------------------------------------------------- 1 | % my $stash = stash('cpants'); 2 | % my $data = $stash->{data}; 3 | 4 |
5 | 6 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /web.psgi: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use FindBin; 3 | use lib "$FindBin::Bin/lib"; 4 | use lib glob "extlib/*/lib"; 5 | use WWW::CPANTS::Web; 6 | use Plack::Builder; 7 | 8 | my $web = WWW::CPANTS::Web->new; 9 | 10 | builder { 11 | enable 'ReverseProxy'; 12 | $web->start('psgi'); 13 | }; 14 | -------------------------------------------------------------------------------- /xt/api_model.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use Test::More; 3 | use WWW::CPANTS::Util::Loader; 4 | use WWW::CPANTS::Util::Path; 5 | use String::CamelCase qw/decamelize/; 6 | 7 | my @names = submodule_names("WWW::CPANTS::API::Model"); 8 | my $t_dir = cpants_app_path("t/api_model"); 9 | my %seen; 10 | 11 | subtest 'all the tests exists' => sub { 12 | for my $name (@names) { 13 | next unless $name =~ /::/; 14 | next if $name =~ /V5::Release::/; 15 | my $path = $name =~ s|::|/|gr; 16 | $path = decamelize($path) . ".t"; 17 | $seen{$path} = 1; 18 | my $file = $t_dir->child($path); 19 | ok -f $file, "test for $name exists" or next; 20 | my $body = $file->slurp; 21 | ok $body =~ /api_model\('$name'\);/, "contains $name"; 22 | } 23 | }; 24 | 25 | subtest 'all the tests for removed api are removed' => sub { 26 | my $iter = $t_dir->iterator({ recurse => 1 }); 27 | while (my $file = $iter->()) { 28 | next if -d $file; 29 | my $path = $file->relative($t_dir); 30 | ok exists $seen{$path}, "api for $path exists"; 31 | } 32 | }; 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /xt/cpanfile.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use FindBin; 3 | use Test::More; 4 | use Test::CPANfile 0.07; 5 | 6 | my @extlibs = glob "$FindBin::Bin/../extlib/*/lib"; 7 | require lib; lib->import(@extlibs); 8 | 9 | cpanfile_has_all_used_modules( 10 | parsers => [':bundled'], 11 | scan_also => \@extlibs, 12 | use_index => 'Mirror', 13 | recommends => 1, 14 | suggests => 1, 15 | develop => 1, 16 | exclude_core => 1, 17 | perl_version => '5.028', 18 | private_re => qr/Module::CPANTS::(Site)?Kwalitee/, 19 | features => { 20 | extlib => { 21 | paths => \@extlibs, 22 | }, 23 | }, 24 | ); 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /xt/kwalitee.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use Test::More; 3 | use WWW::CPANTS::Model::Kwalitee; 4 | use WWW::CPANTS::Util::Path; 5 | 6 | my $model = WWW::CPANTS::Model::Kwalitee->new; 7 | my $t_dir = cpants_app_path("t/kwalitee"); 8 | my %seen; 9 | 10 | subtest 'all tests exist' => sub { 11 | for my $name (sort $model->names->@*) { 12 | my $path = "$name.t"; 13 | $seen{$path}++; 14 | my $file = $t_dir->child($path); 15 | ok -f $file, "$path exists"; 16 | } 17 | }; 18 | 19 | subtest 'all removed tests are removed' => sub { 20 | for my $name (sort map { $_->basename } $t_dir->children) { 21 | ok exists $seen{$name}, "$name exists"; 22 | } 23 | }; 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /xt/perlcritic.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use Test::More; 3 | use Test::Perl::Critic; 4 | 5 | all_critic_ok(qw/author bin lib t xt/, glob "*.psgi"); 6 | -------------------------------------------------------------------------------- /xt/util/validate.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Test; 3 | use WWW::CPANTS::API::Util::Validate; 4 | use WWW::CPANTS::Model::CPAN::Packages; 5 | use Test::More; 6 | use Parse::Distname qw/parse_distname/; 7 | 8 | my %fails; 9 | my $packages = WWW::CPANTS::Model::CPAN::Packages->new(root => WWW::CPANTS->instance->root); 10 | for my $package ($packages->list->@*) { 11 | my $path = $package->{path}; 12 | my $info = parse_distname($path); 13 | my ($name, $version) = @$info{qw/name version/}; 14 | ok defined(is_path($path)), "$path is path" or $fails{$path}{$path} = 1; 15 | ok defined(is_alphanum($name)), "$path name ($name) is alphanum" or $fails{$name}{$path} = 1; 16 | if (defined $version) { 17 | ok defined(is_alphanum($version)), "$path version ($version) is alphanum" or $fails{$version}{$path} = 1; 18 | } 19 | } 20 | note explain [map { $_ => [sort keys $fails{$_}->%*] } sort keys %fails]; 21 | 22 | done_testing; 23 | -------------------------------------------------------------------------------- /xt/web.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use WWW::CPANTS::Util::Loader; 3 | use WWW::CPANTS::Util::Path; 4 | use Test::More; 5 | use String::CamelCase qw/decamelize/; 6 | 7 | my @names = submodule_names("WWW::CPANTS::Web::Controller"); 8 | my $t_dir = cpants_app_path("t/web"); 9 | my %seen; 10 | 11 | subtest 'all the tests exists' => sub { 12 | for my $name (@names) { 13 | next if $name =~ /Root|Release/; 14 | my $path = $name =~ s|::|/|gr; 15 | $path = decamelize($path) . ".t"; 16 | $seen{$path} = 1; 17 | my $file = $t_dir->child($path); 18 | ok -f $file, "test for $name exists" or next; 19 | my $body = $file->slurp; 20 | ok $body =~ /Test::Mojo->new\('WWW::CPANTS::Web'\);/, "creates a tester for $name"; 21 | } 22 | }; 23 | 24 | subtest 'all the tests for removed controller are removed' => sub { 25 | my $iter = $t_dir->iterator({ recurse => 1 }); 26 | while (my $file = $iter->()) { 27 | next if -d $file; 28 | my $path = $file->relative($t_dir); 29 | my $parent_path = $file->parent->relative($t_dir) . ".t"; 30 | ok exists $seen{$path} || exists $seen{$parent_path}, "test for $path exists"; 31 | } 32 | }; 33 | 34 | done_testing; 35 | --------------------------------------------------------------------------------