Skip to content

Commit f6ee875

Browse files
authored
Delete plugins directory (#1523)
1 parent 8a5d1bc commit f6ee875

32 files changed

+412
-1149
lines changed

.coveragerc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# .coveragerc to control coverage.py
22
[run]
33
# Source
4-
source = plugins/*/cmd2_*/
5-
cmd2/
4+
source = cmd2/
5+
66
# (boolean, default False): whether to measure branch coverage in addition to statement coverage.
77
branch = False
88

.github/CODEOWNERS

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ docs/* @tleonhardt
5454
examples/modular* @anselor
5555
examples/*.py @kmvanbrunt @tleonhardt
5656

57-
# Plugins
58-
plugins/* @anselor
59-
6057
# Unit and Integration Tests
6158
tests/* @kmvanbrunt @tleonhardt
6259

@@ -76,5 +73,6 @@ MANIFEST.in @tleonhardt
7673
mkdocs.yml @tleonhardt
7774
package.json @tleonhardt
7875
pyproject.toml @tleonhardt @kmvanbrunt
76+
ruff.toml @tleonhardt
7977
README.md @kmvanbrunt @tleonhardt
8078
tasks.py @tleonhardt

.github/CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ We have a [Makefile](../Makefile) with commands that make it quick and easy for
5151
everything set up and perform common development tasks.
5252

5353
Nearly all project configuration, including for dependencies and quality tools is in the
54-
[pyproject.toml](../pyproject.toml) file.
54+
[pyproject.toml](../pyproject.toml) file other than for `ruff` which is in
55+
[ruff.toml](../ruff.toml).
5556

5657
> _Updating to the latest releases for all prerequisites via `uv` is recommended_. This can be done
5758
> with `uv lock --upgrade` followed by `uv sync`.

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ repos:
99
- id: trailing-whitespace
1010

1111
- repo: https://github.com/astral-sh/ruff-pre-commit
12-
rev: "v0.13.0"
12+
rev: "v0.13.1"
1313
hooks:
1414
- id: ruff-format
15-
args: [--config=pyproject.toml]
15+
args: [--config=ruff.toml]
1616
- id: ruff-check
17-
args: [--config=pyproject.toml, --fix, --exit-non-zero-on-fix]
17+
args: [--config=ruff.toml, --fix, --exit-non-zero-on-fix]
1818

1919
- repo: https://github.com/pre-commit/mirrors-prettier
2020
rev: "v3.1.0"

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
include LICENSE README.md CHANGELOG.md mkdocs.yml pyproject.toml tasks.py
1+
include LICENSE README.md CHANGELOG.md mkdocs.yml pyproject.toml ruff.toml tasks.py
22
recursive-include examples *
33
recursive-include tests *
44
recursive-include docs *

cmd2/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Classes for the cmd2 plugin system."""
1+
"""Classes for the cmd2 lifecycle hooks that you can register multiple callback functions/methods with."""
22

33
from dataclasses import (
44
dataclass,

codecov.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ component_management:
55
name: cmd2 # this is a display name, and can be changed freely
66
paths:
77
- cmd2/**
8-
- component_id: plugins
9-
name: plugins
10-
paths:
11-
- plugins/**
128

139
# Ignore certain paths, all files under these paths will be skipped during processing
1410
ignore:

docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ app.cmdloop()
5353
end="<!--intro-end-->"
5454
%}
5555

56-
## Plugins
56+
## Mixins
5757

5858
{%
59-
include-markdown "./plugins/index.md"
59+
include-markdown "./mixins/index.md"
6060
start="<!--intro-start-->"
6161
end="<!--intro-end-->"
6262
%}

docs/mixins/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Mixins
2+
3+
<!--intro-start-->
4+
5+
- [cmd2 Mixin Template](mixin_template.md)
6+
7+
<!--intro-end-->

docs/mixins/mixin_template.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# cmd2 Mixin Template
2+
3+
## Mixin Classes in General
4+
5+
In Python, a mixin is a class designed to provide a specific set of functionalities to other classes
6+
through multiple inheritance. Mixins are not intended to be instantiated on their own; rather, they
7+
serve as a way to "mix in" or compose behaviors into a base class without creating a rigid "is-a"
8+
relationship.
9+
10+
For more information about Mixin Classes, we recommend this `Real Python` article on
11+
[What Are Mixin Classes in Python?](https://realpython.com/python-mixin/).
12+
13+
## Overview of cmd2 mixins
14+
15+
If you have some set of re-usable behaviors that you wish to apply to multiple different `cmd2`
16+
applications, then creating a mixin class to encapsulate this behavior can be a great idea. It is
17+
one way to extend `cmd2` by relying on multiple inheritance. It is quick and easy, but there are
18+
some potential pitfalls you should be aware of so you know how to do it correctly.
19+
20+
The [mixins.py](https://github.com/python-cmd2/cmd2/blob/main/examples/mixins.py) example is a
21+
general example that shows you how you can develop a mixin class for `cmd2` applicaitons. In the
22+
past we have referred to these as "Plugins", but in retrospect that probably isn't the best name for
23+
them. They are generally mixin classes that add some extra functionality to your class which
24+
inherits from [cmd2.Cmd][].
25+
26+
## Using this template
27+
28+
This file provides a very basic template for how you can create your own cmd2 Mixin class to
29+
encapsulate re-usable behavior that can be applied to multiple `cmd2` applications via multiple
30+
inheritance.
31+
32+
## Naming
33+
34+
If you decide to publish your Mixin as a Python package, you should consider prefixing the name of
35+
your project with `cmd2-`. If you take this approach, then within that project, you should have a
36+
package with a prefix of `cmd2_`.
37+
38+
## Adding functionality
39+
40+
There are many ways to add functionality to `cmd2` using a mixin. A mixin is a class that
41+
encapsulates and injects code into another class. Developers who use a mixin in their `cmd2`
42+
project, will inject the mixin's code into their subclass of [cmd2.Cmd][].
43+
44+
### Mixin and Initialization
45+
46+
The following short example shows how to create a mixin class and how everything gets initialized.
47+
48+
Here's the mixin:
49+
50+
```python
51+
class MyMixin:
52+
def __init__(self, *args, **kwargs):
53+
# code placed here runs before cmd2.Cmd initializes
54+
super().__init__(*args, **kwargs)
55+
# code placed here runs after cmd2.Cmd initializes
56+
```
57+
58+
and an example app which uses the mixin:
59+
60+
```python
61+
import cmd2
62+
63+
64+
class Example(MyMixin, cmd2.Cmd):
65+
"""A cmd2 application class to show how to use a mixin class."""
66+
67+
def __init__(self, *args, **kwargs):
68+
# code placed here runs before cmd2.Cmd or
69+
# any mixins initialize
70+
super().__init__(*args, **kwargs)
71+
# code placed here runs after cmd2.Cmd and
72+
# all mixins have initialized
73+
```
74+
75+
Note how the mixin must be inherited (or mixed in) before `cmd2.Cmd`. This is required for two
76+
reasons:
77+
78+
- The `cmd.Cmd.__init__()` method in the python standard library does not call `super().__init__()`.
79+
Because of this oversight, if you don't inherit from `MyMixin` first, the `MyMixin.__init__()`
80+
method will never be called.
81+
- You may want your mixin to be able to override methods from `cmd2.Cmd`. If you mixin the mixin
82+
class after `cmd2.Cmd`, the python method resolution order will call `cmd2.Cmd` methods before it
83+
calls those in your mixin.
84+
85+
### Add commands
86+
87+
Your mixin can add user visible commands. You do it the same way in a mixin that you would in a
88+
`cmd2.Cmd` app:
89+
90+
```python
91+
class MyMixin:
92+
93+
def do_say(self, statement):
94+
"""Simple say command"""
95+
self.poutput(statement)
96+
```
97+
98+
You have all the same capabilities within the mixin that you do inside a `cmd2.Cmd` app, including
99+
argument parsing via decorators and custom help methods.
100+
101+
### Add (or hide) settings
102+
103+
A mixin may add user controllable settings to the application. Here's an example:
104+
105+
```python
106+
class MyMixin:
107+
def __init__(self, *args, **kwargs):
108+
# code placed here runs before cmd2.Cmd initializes
109+
super().__init__(*args, **kwargs)
110+
# code placed here runs after cmd2.Cmd initializes
111+
self.mysetting = 'somevalue'
112+
self.settable.update({'mysetting': 'short help message for mysetting'})
113+
```
114+
115+
You can also hide settings from the user by removing them from `self.settable`.
116+
117+
### Decorators
118+
119+
Your mixin can provide a decorator which users of your mixin can use to wrap functionality around
120+
their own commands.
121+
122+
### Override methods
123+
124+
Your mixin can override core `cmd2.Cmd` methods, changing their behavior. This approach should be
125+
used sparingly, because it is very brittle. If a developer chooses to use multiple mixins in their
126+
application, and several of the mixins override the same method, only the first mixin to be mixed in
127+
will have the overridden method called.
128+
129+
Hooks are a much better approach.
130+
131+
### Hooks
132+
133+
Mixins can register hooks, which are called by `cmd2.Cmd` during various points in the application
134+
and command processing lifecycle. Mixins should not override any of the legacy `cmd` hook methods,
135+
instead they should register their hooks as
136+
[described](https://cmd2.readthedocs.io/en/latest/hooks.html) in the `cmd2` documentation.
137+
138+
You should name your hooks so that they begin with the name of your mixin. Hook methods get mixed
139+
into the `cmd2` application and this naming convention helps avoid unintentional method overriding.
140+
141+
Here's a simple example:
142+
143+
```python
144+
class MyMixin:
145+
146+
def __init__(self, *args, **kwargs):
147+
# code placed here runs before cmd2 initializes
148+
super().__init__(*args, **kwargs)
149+
# code placed here runs after cmd2 initializes
150+
# this is where you register any hook functions
151+
self.register_postparsing_hook(self.cmd2_mymixin_postparsing_hook)
152+
153+
def cmd2_mymixin_postparsing_hook(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData:
154+
"""Method to be called after parsing user input, but before running the command"""
155+
self.poutput('in postparsing_hook')
156+
return data
157+
```
158+
159+
Registration allows multiple mixins (or even the application itself) to each inject code to be
160+
called during the application or command processing lifecycle.
161+
162+
See the [cmd2 hook documentation](https://cmd2.readthedocs.io/en/latest/hooks.html) for full details
163+
of the application and command lifecycle, including all available hooks and the ways hooks can
164+
influence the lifecycle.
165+
166+
### Classes and Functions
167+
168+
Your mixin can also provide classes and functions which can be used by developers of `cmd2` based
169+
applications. Describe these classes and functions in your documentation so users of your mixin will
170+
know what's available.

0 commit comments

Comments
 (0)