# 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 typing import Iterator
from extra_platforms import ALL_PLATFORMS
from meta_package_manager.base import Package, PackageManager
from meta_package_manager.capabilities import (
search_capabilities,
version_not_implemented,
)
from meta_package_manager.version import parse_version
[docs]
class Gem(PackageManager):
"""The RubyGems package manager.
..tip::
Installs require ``sudo`` on system ruby. I (@tresni) recommend doing something
like:
.. code-block:: shell-session
► sudo dseditgroup -o edit -a -t user wheel
And then do ``visudo`` to make it so the ``wheel`` group does not require
a password. There is a line already there for it, you just need to
uncomment it and save.
"""
name = "Ruby Gems"
homepage_url = "https://rubygems.org"
platforms = ALL_PLATFORMS
# Default to the version shipped with the latest maintained macOS version,
# i.e. macOS 10.13 High Sierra, which is bundled with gem 2.5.2.
requirement = "2.5.0"
"""
.. code-block:: shell-session
► gem --version
3.0.3
"""
# Help mpm a little bit in its search for the `gem` binary.
cli_search_path = ("/usr/local/opt/ruby/bin/gem", "/usr/local/opt/ruby/bin")
post_args = ("--quiet",) # Silence command progress meter
@property
def installed(self) -> Iterator[Package]:
"""Fetch installed packages.
.. code-block:: shell-session
► gem list --quiet
bigdecimal (default: 1.4.1)
bundler (default: 1.17.2)
CFPropertyList (2.3.6)
cmath (default: 1.0.0)
csv (default: 3.0.9)
date (default: 2.0.0)
fileutils (1.4.1, default: 1.1.0)
io-console (0.5.6, default: 0.4.7)
ipaddr (default: 1.2.2)
molinillo (0.5.4, 0.4.5, 0.2.3)
nokogiri (1.5.6)
psych (2.0.0)
rake (0.9.6)
rdoc (4.0.0)
sqlite3 (1.3.7)
test-unit (2.0.0.0)
"""
output = self.run_cli("list")
regexp = re.compile(r"(\S+) \((.+)\)")
for package in output.splitlines():
match = regexp.match(package)
if match:
package_id, versions = match.groups()
# Guess latest installed version.
version = max(
parse_version(v)
for v in re.compile(r",|default:| ").split(versions)
if v
)
yield self.package(id=package_id, installed_version=version)
@property
def outdated(self) -> Iterator[Package]:
"""Fetch outdated packages.
.. code-block:: shell-session
► gem outdated --quiet
did_you_mean (1.0.0 < 1.0.2)
io-console (0.4.5 < 0.4.6)
json (1.8.3 < 2.0.1)
minitest (5.8.3 < 5.9.0)
power_assert (0.2.6 < 0.3.0)
psych (2.0.17 < 2.1.0)
"""
output = self.run_cli("outdated")
regexp = re.compile(r"(\S+) \((\S+) < (\S+)\)")
for package in output.splitlines():
match = regexp.match(package)
if match:
package_id, current_version, latest_version = match.groups()
yield self.package(
id=package_id,
installed_version=current_version,
latest_version=latest_version,
)
[docs]
@search_capabilities(extended_support=False)
def search(self, query: str, extended: bool, exact: bool) -> Iterator[Package]:
"""Fetch matching packages.
.. caution::
Search does not supports extended mode.
.. code-block:: shell-session
► gem search python --versions --quiet
at_coder_friends-generator-python_ref (0.2.0)
bee_python (0.2.3)
dependabot-python (0.117.5)
logstash-filter-python (0.0.1 java)
python (0.0.1)
python-generator (1.1.0)
python_with_git_test (2.499.8)
rabbit-slide-niku-erlangvm-for-pythonista (2015.09.12)
RubyToPython (0.0)
.. code-block:: shell-session
► gem search python --versions --exact --quiet
python (0.0.1)
"""
search_arg = []
if exact:
search_arg.append("--exact")
output = self.run_cli("search", query, "--versions", search_arg)
regexp = re.compile(
r"""
(?P<package_id>\S+) # Any string.
\ # A space.
\( # Start of content in parenthesis.
(?P<version>\S+) # Version string.
(?:\ \S+)? # Optional platform value after space.
\)
""",
re.VERBOSE,
)
for package_id, version in regexp.findall(output):
yield self.package(id=package_id, latest_version=version)
@version_not_implemented
def install(self, package_id: str, version: str | None = None) -> str:
"""Install one package.
.. code-block:: shell-session
► gem install --user-install --quiet markdown
Fetching kramdown-2.3.1.gem
Fetching concurrent-ruby-1.1.9.gem
(...)
Fetching rubyzip-2.3.2.gem
Fetching logutils-0.6.1.gem
Fetching markdown-1.2.0.gem
Successfully installed kramdown-2.3.1
Successfully installed rubyzip-2.3.2
(...)
Successfully installed markdown-1.2.0
(...)
Parsing documentation for markdown-1.2.0
Installing ri documentation for markdown-1.2.0
Done installing documentation for (...) markdown after 19 seconds
12 gems installed
"""
return self.run_cli(
"install",
"--user-install",
*self.post_args,
package_id,
auto_post_args=False,
)
[docs]
def upgrade_all_cli(self) -> tuple[str, ...]:
"""Generates the CLI to upgrade all packages (default) or only the one provided
as parameter.
.. code-block:: shell-session
► gem update --user-install --quiet
"""
return self.build_cli(
"update",
"--user-install",
*self.post_args,
auto_post_args=False,
)
@version_not_implemented
def upgrade_one_cli(
self,
package_id: str,
version: str | None = None,
) -> tuple[str, ...]:
"""Generates the CLI to upgrade all packages (default) or only the one provided
as parameter.
.. code-block:: shell-session
► gem update --user-install --quiet markdown
"""
return self.build_cli(
"update",
"--user-install",
*self.post_args,
package_id,
auto_post_args=False,
)
[docs]
def remove(self, package_id: str) -> str:
"""Remove one package.
.. code-block:: shell-session
► gem uninstall --user-install --quiet left-pad
Successfully uninstalled left-pad-1.1.0
"""
return self.run_cli("uninstall", package_id)
[docs]
def cleanup(self) -> None:
"""Removes things we don't need anymore.
.. code-block:: shell-session
► gem cleanup --quiet
Cleaning up installed gems...
Attempting to uninstall test-unit-3.2.9
Unable to uninstall test-unit-3.2.9:
Gem::FilePermissionError: You don't have write permissions \
for the /Library/Ruby/Gems/2.6.0 directory.
Attempting to uninstall did_you_mean-1.3.0
Unable to uninstall did_you_mean-1.3.0:
Gem::FilePermissionError: You don't have write permissions \
for the /Library/Ruby/Gems/2.6.0 directory.
Clean up complete
"""
self.run_cli("cleanup")