Commit 8b195ed1 by Sébastien Eustace

Add show command

parent 206e1d5d
...@@ -2,7 +2,11 @@ ...@@ -2,7 +2,11 @@
## [Unreleased] ## [Unreleased]
## Changed ### Added
- Add `show` command.
### Changed
- Dependencies of each package is now stored in `poetry.lock`. - Dependencies of each package is now stored in `poetry.lock`.
......
...@@ -319,6 +319,36 @@ poetry remove pendulum ...@@ -319,6 +319,36 @@ poetry remove pendulum
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose). * `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose).
### show
To list all of the available packages, you can use the `show` command.
```bash
poetry show
```
If you want to see the details of a certain package, you can pass the package name.
```bash
poetry show pendulum
name : pendulum
version : 1.4.2
description : Python datetimes made easy
dependencies:
- python-dateutil >=2.6.1
- tzlocal >=1.4
- pytzdata >=2017.2.2
```
#### Options
* `--tree`: List the dependencies as a tree.
* `-l|--latest`: Show the latest version.
* `-o|--outdated`: Show the latest version but only for packages that are outdated.
### package ### package
The `package` command builds the source and wheels archives. The `package` command builds the source and wheels archives.
......
...@@ -11,6 +11,7 @@ from .commands import InstallCommand ...@@ -11,6 +11,7 @@ from .commands import InstallCommand
from .commands import LockCommand from .commands import LockCommand
from .commands import NewCommand from .commands import NewCommand
from .commands import RemoveCommand from .commands import RemoveCommand
from .commands import ShowCommand
from .commands import UpdateCommand from .commands import UpdateCommand
...@@ -48,6 +49,7 @@ class Application(BaseApplication): ...@@ -48,6 +49,7 @@ class Application(BaseApplication):
LockCommand(), LockCommand(),
NewCommand(), NewCommand(),
RemoveCommand(), RemoveCommand(),
ShowCommand(),
UpdateCommand(), UpdateCommand(),
] ]
......
...@@ -4,4 +4,5 @@ from .install import InstallCommand ...@@ -4,4 +4,5 @@ from .install import InstallCommand
from .lock import LockCommand from .lock import LockCommand
from .new import NewCommand from .new import NewCommand
from .remove import RemoveCommand from .remove import RemoveCommand
from .show import ShowCommand
from .update import UpdateCommand from .update import UpdateCommand
from poetry.semver import statisfies
from poetry.version.version_selector import VersionSelector
from .command import Command from .command import Command
...@@ -7,8 +10,208 @@ class ShowCommand(Command): ...@@ -7,8 +10,208 @@ class ShowCommand(Command):
show show
{ package? : Package to inspect. } { package? : Package to inspect. }
{ version? : Version to inspect. } { --t|tree : List the dependencies as a tree. }
{ --l|latest : Show the latest version. }
{ --o|outdated : Show the latest version
but only for packages that are outdated. }
""" """
help = """The show command displays detailed information about a package, or help = """The show command displays detailed information about a package, or
lists all packages available.""" lists all packages available."""
colors = [
'green',
'yellow',
'cyan',
'magenta',
'blue',
]
def handle(self):
package = self.argument('package')
if self.option('tree'):
self.init_styles()
if self.option('outdated'):
self.input.set_option('latest', True)
installed_repo = self.poetry.locker.locked_repository(True)
# Show tree view if requested
if self.option('tree') and not package:
requires = self.poetry.package.requires + self.poetry.package.dev_requires
packages = installed_repo.packages
for package in packages:
for require in requires:
if package.name == require.name:
self.display_package_tree(package, installed_repo)
break
return 0
table = self.table(style='compact')
table.get_style().set_vertical_border_char('')
locked_packages = installed_repo.packages
if package:
pkg = None
for locked in locked_packages:
if package.lower() == locked.name:
pkg = locked
break
if not pkg:
raise ValueError(f'Package {package} not found')
if self.option('tree'):
self.display_package_tree(pkg, installed_repo)
return 0
rows = [
['<info>name</>', f' : <fg=cyan>{pkg.pretty_name}</>'],
['<info>version</>', f' : <comment>{pkg.pretty_version}</>'],
]
table.add_rows(rows)
table.render()
if pkg.requires:
self.line('')
self.line('<info>dependencies</info>')
for dependency in pkg.requires:
self.line(f' - {dependency.pretty_name} '
f'<comment>{dependency.pretty_constraint}</>')
return 0
show_latest = self.option('latest')
for locked in locked_packages:
row = [f'<fg=cyan>{locked.pretty_name}</>', ' ' + locked.version]
if show_latest:
latest = self.find_latest_package(locked)
if not latest:
latest = locked
update_status = self.get_update_status(latest, locked)
color = 'green'
if update_status == 'semver-safe-update':
color = 'red'
elif update_status == 'update-possible':
color = 'yellow'
row.append(f' <fg={color}>{latest.version}</>')
if self.option('outdated') and update_status == 'up-to-date':
continue
row.append(locked.description)
table.add_row(row)
table.render()
def display_package_tree(self, package, installed_repo):
self.write(f'<info>{package.pretty_name}</info>')
self.line(f' {package.pretty_version}')
dependencies = package.requires
dependencies = sorted(dependencies, key=lambda x: x.name)
tree_bar = '├'
j = 0
total = len(dependencies)
for dependency in dependencies:
j += 1
if j == total:
tree_bar = '└'
level = 1
color = self.colors[level]
info = f'{tree_bar}── <{color}>{dependency.name}</{color}> ' \
f'{dependency.pretty_constraint}'
self._write_tree_line(info)
tree_bar = tree_bar.replace('└', ' ')
packages_in_tree = [package.name, dependency.name]
self._display_tree(
dependency, installed_repo, packages_in_tree,
tree_bar, level + 1
)
def _display_tree(self,
dependency, installed_repo, packages_in_tree,
previous_tree_bar='├', level=1):
previous_tree_bar = previous_tree_bar.replace('├', '│')
dependencies = []
for package in installed_repo.packages:
if package.name == dependency.name:
dependencies = package.requires
break
dependencies = sorted(dependencies, key=lambda x: x.name)
tree_bar = previous_tree_bar + ' ├'
i = 0
total = len(dependencies)
for dependency in dependencies:
i += 1
current_tree = packages_in_tree
if i == total:
tree_bar = previous_tree_bar + '└'
color_ident = level % len(self.colors)
color = self.colors[color_ident]
circular_warn = ''
if dependency.name in current_tree:
circular_warn = '(circular dependency aborted here)'
info = f'{tree_bar}── <{color}>{dependency.name}</{color} ' \
f'{dependency.pretty_constraint} {circular_warn}'
self._write_tree_line(info)
tree_bar = tree_bar.replace('└', ' ')
if dependency.name not in current_tree:
current_tree.append(dependency.name)
self._display_tree(
dependency, installed_repo, current_tree,
tree_bar, level + 1
)
def _write_tree_line(self, line):
if not self.output.is_decorated():
line = line.replace('└', '`-')
line = line.replace('├', '|-')
line = line.replace('──', '-')
line = line.replace('│', '|')
self.line(line)
def init_styles(self):
for color in self.colors:
self.set_style(color, color)
def find_latest_package(self, package):
# find the latest version allowed in this pool
name = package.name
selector = VersionSelector(self.poetry.pool)
return selector.find_best_candidate(name, f'>={package.version}')
def get_update_status(self, latest, package):
if latest.full_pretty_version == package.full_pretty_version:
return 'up-to-date'
constraint = package.version
if latest.version and statisfies(latest.version, constraint):
# It needs an immediate semver-compliant upgrade
return 'semver-safe-update'
# it needs an upgrade but has potential BC breaks so is not urgent
return 'update-possible'
...@@ -8,6 +8,8 @@ class PoetryStyle(CleoStyle): ...@@ -8,6 +8,8 @@ class PoetryStyle(CleoStyle):
super().__init__(i, o) super().__init__(i, o)
self.output.get_formatter().add_style('warning', 'black', 'yellow')
@property @property
def venv(self): def venv(self):
return self._venv return self._venv
...@@ -134,6 +134,16 @@ class Installer: ...@@ -134,6 +134,16 @@ class Installer:
ops = solver.solve(request, self._pool, fixed=fixed) ops = solver.solve(request, self._pool, fixed=fixed)
else: else:
self._io.writeln('<info>Installing dependencies from lock file</>') self._io.writeln('<info>Installing dependencies from lock file</>')
if not self._locker.is_fresh():
self._io.writeln(
'<warning>'
'Warning: The lock file is not up to date with '
'the latest changes in composer.json. '
'You may be getting outdated dependencies. '
'Run update to update them.'
'</warning>'
)
# If we are installing from lock # If we are installing from lock
# Filter the operations by comparing it with what is # Filter the operations by comparing it with what is
# currently installed # currently installed
......
...@@ -50,6 +50,8 @@ class Package: ...@@ -50,6 +50,8 @@ class Package:
self._version = version self._version = version
self._pretty_version = pretty_version self._pretty_version = pretty_version
self._description = ''
self._stability = parse_stability(version) self._stability = parse_stability(version)
self._dev = self._stability == 'dev' self._dev = self._stability == 'dev'
...@@ -87,6 +89,10 @@ class Package: ...@@ -87,6 +89,10 @@ class Package:
return self._pretty_version return self._pretty_version
@property @property
def description(self):
return self._description
@property
def unique_name(self): def unique_name(self):
return self.name + '-' + self._version return self.name + '-' + self._version
......
...@@ -9,8 +9,8 @@ from poetry.semver.version_parser import VersionParser ...@@ -9,8 +9,8 @@ from poetry.semver.version_parser import VersionParser
class VersionSelector(object): class VersionSelector(object):
def __init__(self, repository, parser=VersionParser()): def __init__(self, pool, parser=VersionParser()):
self._repository = repository self._pool = pool
self._parser = parser self._parser = parser
def find_best_candidate(self, def find_best_candidate(self,
...@@ -26,7 +26,7 @@ class VersionSelector(object): ...@@ -26,7 +26,7 @@ class VersionSelector(object):
else: else:
constraint = None constraint = None
candidates = self._repository.find_packages(package_name, constraint) candidates = self._pool.find_packages(package_name, constraint)
if not candidates: if not candidates:
return False return False
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment