Skip to content

Commit 611119f

Browse files
authored
✨ Plugin System (experimental, #628)
Add a Plugin System for tmuxp before we drop support for Python 2.7. This should allow customization for areas where yaml alone doesn't cut it. Thank you @joseph-flinn for this contribution! The rollout plan will be to release 1.7.0 as a prerelease and dogfood the plugin system before releasing the final version.
2 parents 888b2b3 + ceb7b6b commit 611119f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1248
-14
lines changed

CHANGES

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ current
88
-------
99
- *Insert changes/features/fixes for next release here*
1010

11+
tmuxp 1.7.0a1 (2020-11-07)
12+
--------------------------
13+
- :issue:`530` Add plugin system for user customization of tmuxp
14+
- :issue:`530` Add tests for the plugin system
15+
- :issue:`530` Update existing tests for the plugin system
16+
- :issue:`530` Add the plugin interface to the tmuxp package
17+
- :issue:`530` Add in depth documentation for the plugin system
18+
1119
tmuxp 1.6.1 (2020-11-07)
1220
------------------------
1321
- :issue:`641` Improvements to ``shell``

README.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ Snapshot your tmux layout, pane paths, and window/session names.
155155
156156
See more about `freezing tmux`_ sessions.
157157
158-
159158
Convert a session file
160159
----------------------
161160
@@ -176,6 +175,11 @@ You can auto confirm the prompt. In this case no preview will be shown.
176175
$ tmuxp convert -y filename
177176
$ tmuxp convert --yes filename
178177
178+
Plugin System
179+
-------------
180+
181+
tmuxp has a plugin system to allow for custom behavior. See more about the
182+
`Plugin System`_.
179183
180184
Docs / Reading material
181185
-----------------------
@@ -200,6 +204,7 @@ Want to learn more about tmux itself? `Read The Tao of Tmux online`_.
200204
.. _teamocil: https://github.com/remiprev/teamocil
201205
.. _Examples: http://tmuxp.git-pull.com/examples.html
202206
.. _freezing tmux: http://tmuxp.git-pull.com/cli.html#freeze-sessions
207+
.. _Plugin System: http://tmuxp.git-pull.com/plugin_system.html
203208
.. _bootstrap_env.py: https://github.com/tmux-python/tmuxp/blob/master/bootstrap_env.py
204209
.. _testing: http://tmuxp.git-pull.com/developing.html#test-runner
205210
.. _python objects: http://tmuxp.git-pull.com/api.html#api

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Table of Contents
2828
quickstart
2929
examples
3030
cli
31+
plugin_system
3132
developing
3233
api
3334
history

docs/plugin_system.rst

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
.. _plugin_system:
2+
3+
=============
4+
Plugin System
5+
=============
6+
7+
The plugin system allows users to customize and extend different aspects of
8+
tmuxp without the need to change tmuxp itself.
9+
10+
11+
Using a Plugin
12+
--------------
13+
14+
To use a plugin, install it in your local python environment and add it to
15+
your tmuxp configuration file.
16+
17+
Example Configurations
18+
^^^^^^^^^^^^^^^^^^^^^^
19+
YAML
20+
~~~~
21+
22+
.. literalinclude:: ../examples/plugin-system.yaml
23+
:language: yaml
24+
25+
JSON
26+
~~~~
27+
28+
.. literalinclude:: ../examples/plugin-system.json
29+
:language: json
30+
31+
.. _poetry: https://python-poetry.org/
32+
33+
34+
Developing a Plugin
35+
-------------------
36+
37+
tmuxp expects all plugins to be class within a python submodule named
38+
``plugin`` that is within a python module that is installed in the local
39+
python environment. A plugin interface is provided by tmuxp to inherit.
40+
41+
`poetry`_ is the chosen python package manager for tmuxp. It is highly
42+
suggested to use it when developing plugins; however, ``pip`` will work
43+
just as well. Only one of the configuration files is needed for the packaging
44+
tool that the package developer desides to use.
45+
46+
.. code-block:: bash
47+
48+
python_module
49+
├── tmuxp_plugin_my_plugin_module
50+
│   ├── __init__.py
51+
│   └── plugin.py
52+
├── pyproject.toml # Poetry's module configuration file
53+
└── setup.py # pip's module configuration file
54+
55+
56+
When publishing plugins to pypi, tmuxp advocates for standardized naming:
57+
``tmuxp-plugin-{your-plugin-name}`` to allow for easier searching. To create a
58+
module configuration file with poetry, run ``poetry init`` in the module
59+
directory. The resulting file looks something like this:
60+
61+
.. code-block:: toml
62+
63+
[tool.poetry]
64+
name = "tmuxp-plugin-my-tmuxp-plugin"
65+
version = "0.0.2"
66+
description = "An example tmuxp plugin."
67+
authors = ["Author Name <author.name@<domain>.com>"]
68+
69+
[tool.poetry.dependencies]
70+
python = "~2.7 || ^3.5"
71+
tmuxp = "^1.6.0"
72+
73+
[tool.poetry.dev-dependencies]
74+
75+
[build-system]
76+
requires = ["poetry>=0.12"]
77+
build-backend = "poetry.masonry.api"
78+
79+
80+
The `plugin.py` file could contain something like the following:
81+
82+
.. code-block:: python
83+
84+
from tmuxp.plugin import TmuxpPlugin
85+
import datetime
86+
87+
class MyTmuxpPlugin(TmuxpPlugin):
88+
def __init__(self):
89+
"""
90+
Initialize my custom plugin.
91+
"""
92+
# Optional version dependency configuration. See Plugin API docs
93+
# for all supported config parameters
94+
config = {
95+
'tmuxp_min_version' = '1.6.2'
96+
}
97+
98+
TmuxpPlugin.__init__(
99+
self,
100+
plugin_name='tmuxp-plugin-my-tmuxp-plugin',
101+
**config
102+
)
103+
104+
def before_workspace_builder(self, session):
105+
session.rename_session('my-new-session-name')
106+
107+
def reattach(self, session):
108+
now = datetime.datetime.now().strftime('%Y-%m-%d')
109+
session.rename_session('session_{}'.format(now))
110+
111+
112+
Once this plugin is installed in the local python environment, it can be used
113+
in a configuration file like the following:
114+
115+
.. code-block:: yaml
116+
117+
session_name: plugin example
118+
plugins:
119+
- my_plugin_module.plugin.MyTmuxpPlugin
120+
# ... the rest of your config
121+
122+
123+
Plugin API
124+
----------
125+
126+
.. automethod:: tmuxp.plugin.TmuxpPlugin.before_workspace_builder
127+
.. automethod:: tmuxp.plugin.TmuxpPlugin.on_window_create
128+
.. automethod:: tmuxp.plugin.TmuxpPlugin.after_window_finished
129+
.. automethod:: tmuxp.plugin.TmuxpPlugin.before_script
130+
.. automethod:: tmuxp.plugin.TmuxpPlugin.reattach

examples/plugin-system.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"session_name": "plugin-system",
3+
"plugins": [
4+
"tmuxp_plugin_extended_build.plugin.PluginExtendedBuild"
5+
],
6+
"windows": [
7+
{
8+
"window_name": "editor",
9+
"layout": "tiled",
10+
"shell_command_before": [
11+
"cd ~/"
12+
],
13+
"panes": [
14+
{
15+
"shell_command": [
16+
"cd /var/log",
17+
"ls -al | grep *.log"
18+
]
19+
},
20+
"echo \"hello world\""
21+
]
22+
}
23+
]
24+
}

examples/plugin-system.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
session_name: plugin-system
2+
plugins:
3+
- 'tmuxp_plugin_extended_build.plugin.PluginExtendedBuild'
4+
windows:
5+
- window_name: editor
6+
layout: tiled
7+
shell_command_before:
8+
- cd ~/
9+
panes:
10+
- shell_command:
11+
- cd /var/log
12+
- ls -al | grep *.log
13+
- echo "hello world"

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ skip-string-normalization = true
33

44
[tool.poetry]
55
name = "tmuxp"
6-
version = "1.6.1"
6+
version = "1.7.0a1"
77
description = "tmux session manager"
88
license = "MIT"
99
authors = ["Tony Narlock <tony@git-pull.com>"]
@@ -69,6 +69,12 @@ pytest-mock = [
6969
{version="<3.0.0", python="<3"},
7070
{version="*", python=">=3"}
7171
]
72+
tmuxp-test-plugin-bwb = { path = "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bwb/"}
73+
tmuxp-test-plugin-bs = { path = "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_bs/"}
74+
tmuxp-test-plugin-r = { path = "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_r/"}
75+
tmuxp-test-plugin-owc = { path = "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_owc/"}
76+
tmuxp-test-plugin-awf = { path = "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_awf/"}
77+
tmuxp-test-plugin-fail = { path = "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/"}
7278

7379
### Coverage ###
7480
codecov = "*"

tests/fixtures/pluginsystem/__init__.py

Whitespace-only changes.

tests/fixtures/pluginsystem/partials/__init__.py

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from .test_plugin_helpers import MyTestTmuxpPlugin
2+
3+
4+
class AllVersionPassPlugin(MyTestTmuxpPlugin):
5+
def __init__(self):
6+
config = {
7+
'plugin_name': 'tmuxp-plugin-my-tmuxp-plugin',
8+
'tmux_min_version': '1.8',
9+
'tmux_max_version': '100.0',
10+
'tmux_version_incompatible': ['2.3'],
11+
'libtmux_min_version': '0.8.3',
12+
'libtmux_max_version': '100.0',
13+
'libtmux_version_incompatible': ['0.7.1'],
14+
'tmuxp_min_version': '1.6.0',
15+
'tmuxp_max_version': '100.0.0',
16+
'tmuxp_version_incompatible': ['1.5.6'],
17+
'tmux_version': '3.0',
18+
'tmuxp_version': '1.6.0',
19+
}
20+
MyTestTmuxpPlugin.__init__(self, config)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from .test_plugin_helpers import MyTestTmuxpPlugin
2+
3+
4+
class LibtmuxVersionFailMinPlugin(MyTestTmuxpPlugin):
5+
def __init__(self):
6+
config = {
7+
'plugin_name': 'libtmux-min-version-fail',
8+
'libtmux_min_version': '0.8.3',
9+
'libtmux_version': '0.7.0',
10+
}
11+
MyTestTmuxpPlugin.__init__(self, config)
12+
13+
14+
class LibtmuxVersionFailMaxPlugin(MyTestTmuxpPlugin):
15+
def __init__(self):
16+
config = {
17+
'plugin_name': 'libtmux-max-version-fail',
18+
'libtmux_max_version': '3.0',
19+
'libtmux_version': '3.5',
20+
}
21+
MyTestTmuxpPlugin.__init__(self, config)
22+
23+
24+
class LibtmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin):
25+
def __init__(self):
26+
config = {
27+
'plugin_name': 'libtmux-incompatible-version-fail',
28+
'libtmux_version_incompatible': ['0.7.1'],
29+
'libtmux_version': '0.7.1',
30+
}
31+
MyTestTmuxpPlugin.__init__(self, config)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from tmuxp.plugin import TmuxpPlugin
2+
3+
4+
class MyTestTmuxpPlugin(TmuxpPlugin):
5+
def __init__(self, config):
6+
tmux_version = config.pop('tmux_version', None)
7+
libtmux_version = config.pop('libtmux_version', None)
8+
tmuxp_version = config.pop('tmuxp_version', None)
9+
10+
TmuxpPlugin.__init__(self, **config)
11+
12+
# WARNING! This should not be done in anything but a test
13+
if tmux_version:
14+
self.version_constraints['tmux']['version'] = tmux_version
15+
if libtmux_version:
16+
self.version_constraints['libtmux']['version'] = libtmux_version
17+
if tmuxp_version:
18+
self.version_constraints['tmuxp']['version'] = tmuxp_version
19+
20+
self._version_check()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from .test_plugin_helpers import MyTestTmuxpPlugin
2+
3+
4+
class TmuxVersionFailMinPlugin(MyTestTmuxpPlugin):
5+
def __init__(self):
6+
config = {
7+
'plugin_name': 'tmux-min-version-fail',
8+
'tmux_min_version': '1.8',
9+
'tmux_version': '1.7',
10+
}
11+
MyTestTmuxpPlugin.__init__(self, config)
12+
13+
14+
class TmuxVersionFailMaxPlugin(MyTestTmuxpPlugin):
15+
def __init__(self):
16+
config = {
17+
'plugin_name': 'tmux-max-version-fail',
18+
'tmux_max_version': '3.0',
19+
'tmux_version': '3.5',
20+
}
21+
MyTestTmuxpPlugin.__init__(self, config)
22+
23+
24+
class TmuxVersionFailIncompatiblePlugin(MyTestTmuxpPlugin):
25+
def __init__(self):
26+
config = {
27+
'plugin_name': 'tmux-incompatible-version-fail',
28+
'tmux_version_incompatible': ['2.3'],
29+
'tmux_version': '2.3',
30+
}
31+
32+
MyTestTmuxpPlugin.__init__(self, config)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from .test_plugin_helpers import MyTestTmuxpPlugin
2+
3+
4+
class TmuxpVersionFailMinPlugin(MyTestTmuxpPlugin):
5+
def __init__(self):
6+
config = {
7+
'plugin_name': 'tmuxp-min-version-fail',
8+
'tmuxp_min_version': '1.6.0',
9+
'tmuxp_version': '1.5.6',
10+
}
11+
MyTestTmuxpPlugin.__init__(self, config)
12+
13+
14+
class TmuxpVersionFailMaxPlugin(MyTestTmuxpPlugin):
15+
def __init__(self):
16+
config = {
17+
'plugin_name': 'tmuxp-max-version-fail',
18+
'tmuxp_max_version': '2.0.0',
19+
'tmuxp_version': '2.5',
20+
}
21+
MyTestTmuxpPlugin.__init__(self, config)
22+
23+
24+
class TmuxpVersionFailIncompatiblePlugin(MyTestTmuxpPlugin):
25+
def __init__(self):
26+
config = {
27+
'plugin_name': 'tmuxp-incompatible-version-fail',
28+
'tmuxp_version_incompatible': ['1.5.0'],
29+
'tmuxp_version': '1.5.0',
30+
}
31+
MyTestTmuxpPlugin.__init__(self, config)

0 commit comments

Comments
 (0)