Source code for meta_package_manager.managers.cpan
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from __future__ import annotations
import re
from extra_platforms import ALL_PLATFORMS
from ..base import PackageManager
from ..capabilities import version_not_implemented
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterator
from ..base import Package
[docs]
class CPAN(PackageManager):
"""The Comprehensive Perl Archive Network package manager.
.. tip::
Installs may require ``sudo`` when using the system Perl. Consider using
``local::lib`` for user-local installs.
.. caution::
On first run, ``cpan`` may launch an interactive configuration. This is
suppressed by the ``PERL_MM_USE_DEFAULT`` environment variable.
"""
name = "Perl's CPAN"
homepage_url = "https://www.cpan.org"
platforms = ALL_PLATFORMS
requirement = ">=1.64"
"""
.. code-block:: shell-session
$ cpan -v
>(info): /usr/bin/cpan script version 1.676, CPAN.pm version 2.28
"""
extra_env = {"PERL_MM_USE_DEFAULT": "1"} # noqa: RUF012
"""Suppress interactive prompts during install and configuration."""
version_cli_options = ("-v",)
version_regexes = (r"CPAN\.pm\s+version\s+(?P<version>\S+)",)
"""
.. code-block:: shell-session
$ cpan -v
>(info): /usr/bin/cpan script version 1.676, CPAN.pm version 2.28
"""
_INSTALLED_REGEXP = re.compile(r"^(?P<package_id>\S+)\t(?P<version>\S+)$")
_OUTDATED_REGEXP = re.compile(
r"^(?P<package_id>\S+)\s+(?P<installed_version>\S+)\s+(?P<latest_version>\S+)$",
)
@property
def installed(self) -> Iterator[Package]:
"""Fetch installed packages.
.. code-block:: shell-session
$ cpan -l 2>/dev/null
Loading internal logger. Log::Log4perl recommended for better logging
O 1.03
Errno 1.33
Config 5.034001
Encode 3.08_01
meta_notation undef
"""
output = self.run_cli("-l")
for line in output.splitlines():
match = self._INSTALLED_REGEXP.match(line)
if match:
package_id = match.group("package_id")
version = match.group("version")
yield self.package(
id=package_id,
installed_version=version if version != "undef" else None,
)
@property
def outdated(self) -> Iterator[Package]:
"""Fetch outdated packages.
.. code-block:: shell-session
$ cpan -O 2>/dev/null
Loading internal logger. Log::Log4perl recommended for better logging
Reading '/Users/kde/.cpan/Metadata'
Database was generated on Thu, 26 Mar 2026 13:41:03 GMT
Module Name Local CPAN
---------------------------------------------------------------
Algorithm::C3 0.1000 0.1100
Archive::Tar 2.3800 3.0400
App::Cpan 1.6760 1.6780
"""
output = self.run_cli("-O")
for line in output.splitlines():
match = self._OUTDATED_REGEXP.match(line)
if match:
package_id = match.group("package_id")
installed_version = match.group("installed_version")
latest_version = match.group("latest_version")
yield self.package(
id=package_id,
installed_version=installed_version,
latest_version=latest_version,
)
@version_not_implemented
def install(self, package_id: str, version: str | None = None) -> str:
"""Install one package.
.. code-block:: shell-session
$ cpan JSON
"""
return self.run_cli(package_id)
[docs]
def upgrade_all_cli(self) -> tuple[str, ...]:
"""Generates the CLI to upgrade all packages.
.. code-block:: shell-session
$ cpan -u
"""
return self.build_cli("-u")
@version_not_implemented
def upgrade_one_cli(
self,
package_id: str,
version: str | None = None,
) -> tuple[str, ...]:
"""Generates the CLI to upgrade one package.
.. code-block:: shell-session
$ cpan -i JSON
"""
return self.build_cli("-i", package_id)