# Making conda richer notes
This project will require the following steps:
1. A refactor to replace all the usages of `print` and `sys.stdout.write`
with a higher level abstraction that will be able to either route to
the built in print function or to rich.
2. A settings mechanism that will allow us to turn rich `on` or `off` (this
will default to `off`)
3. Implement our first rich enabled layout with the `conda info` command
This refactor will give us a good opportunity to make sure that we are
adhering to the principles laid out in this section:
https://clig.dev/#output
links:
- https://github.com/ForgottenProgramme/rich-conda-mocks
- https://github.com/conda/conda/issues/12766
- https://github.com/conda/conda/issues/13707
## Refactoring
- Finding all locations where stdout is printed (e.g. print statements, logging)
- Planning how to refactor the UI elements such as `Spinner` and `ProgressBar`
We also need to think about building an interface for plugins to use and endorse
this as the official way to print things for users.
## Parts of the code to refactor
### Exception handler
code:
- https://github.com/conda/conda/blob/main/conda/exception_handler.py#L190
This should be a pretty easy part of the code to refactor. Plus, it will allow
us to use rich's nice exception printing:
- https://rich.readthedocs.io/en/stable/traceback.html
### Everything using `conda.cli.install`
This is a view of everything that needs to be refactored that uses the
`conda.cli.install` module. This include commands like, `install`, `create`
and `update`.
Components:
- [Spinner](https://github.com/conda/conda/blob/main/conda/common/io.py#L377)
- Used to display notices, repodata fetching and solving
- [ProgressBar](https://github.com/conda/conda/blob/main/conda/common/io.py#L448)
- [Update warning](https://github.com/conda/conda/blob/main/conda/core/solve.py#L1248-L1271)
- Worth noting that conda-libmamba-solver has to be updated separately
- [Package plan](https://github.com/conda/conda/blob/main/conda/core/link.py#L1242)
- [Confirmation](https://github.com/conda/conda/blob/main/conda/cli/common.py#L57)
- [Downloading and extracting packages](https://github.com/conda/conda/blob/main/conda/core/package_cache_data.py#L764)
- [Environment activation](https://github.com/conda/conda/blob/main/conda/cli/install.py#L135)
### Other conda commands
These commands should be considerably easier to refactor as there are not nearly
as many moving parts as `install`, `create` and `update`.
#### conda clean
Components used:
- Confirmation
#### conda compare
Just uses print statements
#### conda config
Just uses print statements. It should YAML which could be nicely formatted with `rich`
#### conda info
Uses print statements
#### conda list
Uses print statements
#### conda notices
Components:
- Spinner
#### conda package
Listed as "experimental". Will not refactor.
#### conda remove
There's a couple print statements being used in this command (see `conda.cli.main_remove`)
Components:
- Spinner
- For collection package metadata and transaction status
- Package plan
- Confirmation
#### conda run
This prints the output from the command that it runs. Does not need refactor
#### conda search
Customized print functions which is a lot like `conda info`
Components:
- Spinner
- For collection of repodata.json ("Loading channels")
### Notes on components
#### Progress Bar
Updating this component to support either tqdm or rich will require a refactor of the `ProgressBar` class.
I think the best way to do that will be to make a ProgressBarBase class which will serve as an abstract
base class. The ReporterHandler will then have a method for retrieving this class based on the settings
in `context.reporters`.
## Class design
### `ReporterHandler`
The first abstract base class that will be written will be the `ReporterHandler`
The abstract methods will correspond to display components in conda itself. As of now,
they are the following:
- `detail_view` (currently used in `conda info`)
- `string_view`
- `progress_bar` (used in `install`, `update`, `create`)
- `spinner` (used in several commands)
- `package_list`
```python
from abc import ABC, abstractmethod
class ReporterHandlerBase(ABC):
"""
Abstract Base Class for all backend/reporters.
"""
@abstractmethod
def detail_view():
"""method to display data in a tabular format"""
...
@abstractmethod
def string_view():
"""method to display data like a regular string, e.g. `conda info --base` simply prints the root prefix string."""
...
class JsonHandler(ReporterHandlerBase):
"""class handling json output"""
def detail_view():
...
def string_view():
...
class StdHandler(ReporterHandlerBase):
"""class handling std output"""
def detail_view():
...
def string_view():
...
```
### `OutputHandler`
The second abstract base class will concern itself with output. We want to do this because
we would eventually like to have a plugin hook that will allow developers add arbitrary
output handlers.
This class will be named `OutputHandler` and will contain the following abstract methods:
- `render`
- Takes in a string and *renders* to the chosen output stream
#### Example code
```python
from abc import ABC, abstractmethod
import sys
class OutputHandlerBase(ABC):
"""
Base class for all output handlers
"""
@property
@abstractmethod
def name(self) -> str:
"""
Name of the output handler; this will be how this output handler is referenced
in configuration files
"""
...
@abstractmethod
def render(self, output: str) -> None:
"""Render implementation goes here"""
...
class StdoutHandler(OutputHandlerBase):
"""
Handles writing output strings to stdout
"""
@property
def name(self) -> str:
return "stdout"
def render(self, output: str) -> None:
sys.stdout.write(output)
```
### Where does the configuration come from?
```python
reporters = (
{"backend": "stdout", "output": "stdout"},
{"backend": "json", "output": "file"},
)
```